Scaffold a Fresh app, a typed oRPC service, Postgres, Redis, and the Aspire resource graph that runs and observes them. At the end you will know which files are yours, which files the CLI owns, and which generators to reach for before writing a feature by hand.
1. Install the CLI
deno install --global --allow-all --name netscript jsr:@netscript/cli@0.0.6
The same command works in Bash and PowerShell; Deno prints the executable directory to add to PATH if netscript is not found.
Then confirm the CLI is on your path:
netscript --help
Confirm netscript --help lists the public command groups: agent, config, contract, db, deploy, generate, init, marketplace, plugin, service, and the ui:* family (ui:list, ui:add, ui:update, ui:remove).
2. Scaffold the workspace
netscript init my-app --db postgres --yes
cd my-app
netscript service add --name users --with-client
service add is a separate step so the same command works later when you add a second service to a real workspace. --yes accepts the remaining defaults without prompts, including the dashboard Fresh app and shared Redis cache. Add --editor zed, --editor vscode, or --editor none when you want the scaffold to apply an editor choice explicitly; inspect every option with netscript init --help.
The CLI prints the real file and directory totals for the options you selected. Those totals vary with the database, service, editor, and agent tooling, so treat the printed result—not a static number in this guide—as the authority.
Know what you own
The scaffold is deliberately navigable. [owned] files are where product work belongs. [generated] files are replaced by a generator; do not patch them. [guidance] files explain the local conventions to humans and coding agents.
my-app/
├── AGENTS.md # [guidance] added by `agent init`
├── .mcp.json # [generated] agent-host MCP wiring
├── .netscript/ # [generated] CLI/runtime support
├── apps/dashboard/
│ ├── AGENTS.md # [guidance] app-specific agent rules
│ ├── WEB-LAYER.md # [guidance] contract → page → island map
│ ├── client.ts # [owned] Fresh client entry; imports app CSS
│ ├── lib/ # [owned] per-service data-layer modules
│ │ └── users.ts # [owned] users client + query factories
│ ├── utils.ts # [owned] web utilities & definePage helper
│ ├── routes/ # [owned] Fresh pages and route handlers
│ ├── islands/ # [owned] interactive Preact boundaries
│ ├── components/ui/ # [owned] copied UI registry source (~60 components)
│ └── .generated/ # [generated] Fresh manifest and route types
├── contracts/versions/v1/ # [owned] oRPC routes and Zod boundaries
├── services/users/src/ # [owned] contract-bound handler logic (main.ts, routers/)
├── database/postgres/
│ ├── schema/schema.prisma # [owned] persistence models
│ ├── migrations/ # [generated history] review and commit
│ └── schema/.generated/ # [generated] Prisma client + Zod schemas
├── plugins/ # [CLI-managed] installed capability workspaces
├── tests/ # [owned] workspace-level integration test suite
├── aspire/
│ ├── apphost.mts # [generated] resource-graph entry point
│ └── .helpers/ # [generated] services/plugins/infrastructure
├── appsettings.json # [owned config] resource declarations
├── netscript.config.ts # [owned config] framework paths and wiring
├── deno.json # [owned config] workspace, imports, and tasks
├── package.json # [owned config] Node compatibility marker
└── node_modules/ # [generated] local materialised npm dependencies
The CLI decides placement. Use its contract, service, db, plugin, and ui:* generators so new files land where the rest of the workspace and Aspire already expect them.
If a coding agent will work in the project, install its local guidance and MCP configuration now:
netscript agent init
Use --host claude|vscode|all, --editor none|zed|vscode, and --with-docs as needed. Re-running the command preserves unrelated host configuration.
How the database contract is derived
After db generate, database model schemas and CRUD input boundaries are exported through @database/zod. In deno.json, @database/zod resolves to ./database/<engine>/schema/.generated/zod/crud.ts—a NetScript-owned multi-model aggregate exporting <Model>Schema, <Model>CreateInput, and <Model>UpdateInput for every model defined in schema.prisma.
To derive API contracts without duplicating Prisma models:
import { UserSchema as DatabaseUserSchema } from '@database/zod';
import { z } from 'zod';
// The scaffolded User model has id, name, createdAt, updatedAt. Pick only fields
// that exist in schema.prisma; introduce new public fields with .extend().
export const UserSchemaV1 = DatabaseUserSchema
.pick({ id: true, name: true, createdAt: true })
.extend({
name: z.string().min(1).max(120),
status: z.enum(['draft', 'active', 'archived']),
});
The database generator remains the source for column types and nullability; the contract owns what crosses the boundary and its stricter public rules. That distinction is the contract register bar described in Contracts & type flow. Re-running netscript db generate rebuilds crud.ts for all models in schema.prisma.
3. Start the resource graph
Nothing runs until Aspire starts. From the workspace root:
cd aspire
aspire restore
aspire start
The CLI's own printed next steps recommend deno task aspire:start instead of a bare aspire start: it is the same command wrapped with ASPIRE_CLI_START_TIMEOUT=300, which gives container images a 300-second cold-start budget on first run. Use the wrapper unless you are reproducing this walk exactly. In PowerShell the path step is Set-Location .\aspire; the remaining commands are unchanged.
When run interactively in a TTY, leave the terminal running and open the dashboard URL and login token printed by aspire start. If your terminal is not interactive (a CI job or an agent tool call), aspire start detaches, prints the AppHost PID and dashboard URL, and returns; the resources keep running. The dashboard itself needs a browser and a login token, so read the running graph from the shell instead:
aspire describe --apphost ./aspire/apphost.mts --format Json --non-interactive --nologo
That prints every resource with its allocated endpoint — the Fresh app URL, the service URL, Postgres, and Redis. From an agent host, the MCP equivalent is list_api_services, which returns baseUrl, specUrl, and docsUrl per resource. Do not use get_app_status or doctor for this: get_app_status returns only a health verdict and aggregate counts — no names, no URLs — and reads a telemetry endpoint that defaults to a port Aspire did not allocate; doctor checks project files on disk, not running processes.
Aspire is the resource graph for local development. It starts Postgres and Redis, injects service discovery, assigns free ports at runtime, runs health checks, and collects logs and distributed traces. There is no application or service port to memorise: use the URLs beside each resource in the dashboard (or the baseUrl of each row returned by list_api_services). The dashboard (or aspire describe / list_api_services output) is the authority when a copied port, an old terminal, and the running system disagree.
Wait until the Fresh app, example service (users), Postgres, and Redis report healthy. Open a resource in the dashboard to read its console logs; after making a request, open the trace view to follow that request across processes. See Aspire & the AppHost and Observability for the full model.
4. Prepare the database
Open a second terminal at my-app/ while Aspire runs in the background. Each netscript db
command starts an explicit operation resource inside that resident AppHost and receives its existing
Postgres connection; it does not provision another database or mount .data/postgres again:
cd ..
netscript db init --db postgres --name init
netscript db generate --db postgres
netscript db seed --db postgres
deno task check
This order is mandatory for an Aspire-managed database. If the resident AppHost is stopped, the DB
command fails fast instead of reconstructing the resource graph. db init creates and applies the
first Prisma migration; without it the scaffold has no tables. db generate then produces both the
Prisma client and the Zod model schemas in crud.ts, and db seed runs the workspace seed script.
db init is the first end-to-end checkpoint: success proves the AppHost resource, connection string, Prisma schema, and migration path agree before product code can obscure the failure.
Verify your progress
Use the resource links from the Aspire dashboard (or aspire describe / MCP list_api_services) rather than substituting a remembered port. From the project root, run:
deno task check
- [ ]
netscript --helplists the public command groups (agent,config,contract,db,deploy,generate,init,marketplace,plugin,service,ui:*). - [ ]
my-app/containsapps/,contracts/,services/,database/,plugins/,aspire/, andtests/. - [ ] Aspire is running; the dashboard — or
aspire describe --apphost ./aspire/apphost.mts --format Json— shows the Fresh app, example service (users), Postgres, and Redis with endpoints. (MCPlist_api_servicescovers the HTTP resources only: the Fresh app and the service. Postgres and Redis publishtcp://endpoints and do not appear there.) - [ ]
netscript db init --name initsucceeded and the initial migration exists. - [ ] The Fresh app answers at the URL shown in the dashboard /
aspire describeoutput. - [ ] Opening
<fresh-app-url>/designrenders the generated design reference (it returns an HTTP 302 redirect to/design/tokens, so scripted checks requirecurl -L). - [ ]
deno task checkpasses with no errors.
Do not begin customising until every box is ticked. An unverified base makes every later failure look like your code.
Take the first tour
Two generated pages are worth opening before you edit anything:
<fresh-app-url>/designrenders the tokens, components, and composition rules already in the app. Read/design/compositionbefore deciding what belongs on the server or in an island.<service-url>/api/docsis the service's Scalar API reference, generated from the same contract the handler implements. Use it to inspect and exercise the running oRPC/OpenAPI surface.
Base URLs are allocated dynamically by Aspire. Inspect them via the Aspire dashboard, aspire describe --apphost ./aspire/apphost.mts --format Json, or MCP list_api_services.
Build a feature through the CLI
The first feature is a loop, not a hunt for folders. From the workspace root, add an orders contract, one route, a service, and its compiling handler stub:
netscript contract add orders
netscript contract add-route orders recent --method GET --path /orders/recent
netscript service add --name orders --with-client
netscript service add-handler orders recent
contract add seeds the v1 contract; add-route appends the recent procedure. service add --with-client creates the service workspace, registers it with Aspire, and writes the typed app data
layer at apps/dashboard/lib/orders.ts. service add-handler binds that exact procedure in the
correct router and emits a stub that compiles while throwing Not implemented at runtime. Replace
the throw with business logic; do not hand-write a parallel router.
service add regenerates the Aspire helper files under aspire/.helpers/. A running AppHost does not pick up the new service graph automatically—stop it (aspire stop --apphost ./aspire/apphost.mts) and restart it (deno task aspire:start) before expecting the new service in the dashboard or MCP tool status.
When the feature needs a new table, edit database/postgres/schema/schema.prisma, then keep the database and generated types in lockstep:
netscript db migrate --name add_orders
netscript db generate
UI component library and generator leverage
Your application workspace comes pre-populated with a full component registry under apps/dashboard/components/ui/ (over 60 components including data-table, stats-grid, detail-layout, empty-state, panel, filter-form, chart-block, and more).
Use the ui:* family to list, copy, update, or manage UI registry items:
netscript ui:list --project-root apps/dashboard
Every ui:* command needs --project-root apps/dashboard. Without it they resolve to the
workspace root — where components/ui/ does not exist — so ui:list reports the components the
scaffold already copied as if they were missing, and ui:add writes a second copy into the
workspace root and edits the root deno.json.
Reach for an existing registry item before building UI by hand: netscript ui:add <item> --project-root apps/dashboard copies one, ui:update --project-root apps/dashboard syncs
unmodified copies, and ui:remove <item> --project-root apps/dashboard removes one.
To scaffold a new Fresh page, colocated island, and query loader:
netscript ui:add page orders --island --project-root apps/dashboard
The generator writes apps/dashboard/routes/orders/index.tsx, OrdersIsland.tsx, and query-loaders.ts. Note that deno task check checks .ts files under apps/, services/, and contracts/; type-check generated .tsx files directly:
deno check --unstable-kv "apps/dashboard/routes/orders/index.tsx" "apps/dashboard/routes/orders/(_islands)/OrdersIsland.tsx"
The generator supplies a compiling composition and correct file layout; connect its query loader to
the generated data layer in apps/dashboard/lib/orders.ts and implement feature logic.
For breaking contract evolution, start with netscript contract version --help. To add a background capability, use netscript plugin install <kind>—for example, netscript plugin install worker --name workers scaffolds the plugin workspace and registers it with Aspire.
Give agents the framework surface
netscript agent init configures a client to launch netscript agent mcp. That command is a standard-input/output server launched by the client—not a process to run and watch by hand.
The current server exposes 21 bounded tools in seven task families:
| Family | Tools |
|---|---|
| Running app | get_app_status, list_runs, get_run, get_recent_errors, get_last_job_result, analyze_service_performance, analyze_db_bottlenecks |
| Project diagnosis | doctor |
| Documentation | search_docs, list_docs, get_doc |
| Export discovery | find_export, list_package_exports, get_export, search_exports |
| Live service APIs | list_api_services, list_service_operations, get_operation_schema |
| CLI bridge | list_commands, execute_command |
| Evidence | record_drift |
Reach for doctor first when the stack is red, find_export before guessing a package subpath, list_api_services to resolve runtime URLs headlessly, and get_operation_schema before inventing a request with curl. The complete schemas and safety policy live in Agent tooling and the @netscript/mcp reference.
First-hour CLI command reference
Common CLI commands for your first hour in a NetScript workspace:
| Goal | Command |
|---|---|
| Scaffold workspace | netscript init my-app --db postgres --service --yes |
| Configure agent tooling | netscript agent init --host all --with-docs |
| Start local stack | deno task aspire:start |
| Initialize database | netscript db init --name init |
| Update types after schema edit | netscript db migrate --name <name> && netscript db generate |
| Add contract route | netscript contract add-route <contract> <procedure> --method GET --path /<route> |
| Add oRPC service and app data layer | netscript service add --name <name> --with-client |
| Bind handler stub | netscript service add-handler <service> <procedure> |
| Inspect UI component registry | netscript ui:list --project-root apps/dashboard |
| Scaffold Fresh page & island | netscript ui:add page <name> --island --project-root apps/dashboard |
| Type-check workspace | deno task check |
| Stop local stack | aspire stop --apphost ./aspire/apphost.mts |
See the complete CLI reference for detailed command flags and configuration options.
What next, by intent
- Add a service: Add a service for the CLI-to-contract, handler, discovery, and client path.
- Add a background job: Background jobs and add a plugin for workers and their Aspire wiring.
- Add a screen: Customize Fresh UI and the Fresh page model for
ui:add,definePage, loaders, and islands. - Secure a route: Add authentication and the authorization chapter.
- Go to production: Deploy for deployable units, managed backing services, and the boundary between local Aspire and a remote target.
For a continuous build, continue with the Storefront tutorial. When you are finished locally, stop the AppHost from the workspace root:
aspire stop --apphost ./aspire/apphost.mts
The Postgres container is declared Persistent in appsettings.json, so it deliberately survives AppHost shutdown. Use Docker (docker stop / docker rm) when you want to remove the database container and free up disk space.
Note: every command, flag, and path on this page is verified against NetScript CLI source and live --help output, and the flow was walked end to end on Linux with Deno 2.x. The Windows guidance is source-derived and has not been executed on Windows.