Skip to main content
0.0.x

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 --help lists the public command groups (agent, config, contract, db, deploy, generate, init, marketplace, plugin, service, ui:*).
  • [ ] my-app/ contains apps/, contracts/, services/, database/, plugins/, aspire/, and tests/.
  • [ ] 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. (MCP list_api_services covers the HTTP resources only: the Fresh app and the service. Postgres and Redis publish tcp:// endpoints and do not appear there.)
  • [ ] netscript db init --name init succeeded and the initial migration exists.
  • [ ] The Fresh app answers at the URL shown in the dashboard / aspire describe output.
  • [ ] Opening <fresh-app-url>/design renders the generated design reference (it returns an HTTP 302 redirect to /design/tokens, so scripted checks require curl -L).
  • [ ] deno task check passes 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>/design renders the tokens, components, and composition rules already in the app. Read /design/composition before deciding what belongs on the server or in an island.
  • <service-url>/api/docs is 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

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.