@sequenceholdings/studio-cli 0.1.21 → 0.1.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,8 +2,10 @@
2
2
 
3
3
  Standalone CLI for the Sequence platform: typed agents, Lattice processes, Artifact Studio
4
4
  apps, Managed Functions, Managed Secrets, ORM namespaces, Data Pipelines stage
5
- specs, and platform git repos. Runs from any repo against the platform over
6
- HTTP — no monorepo checkout required.
5
+ specs, and platform git repos. Its published workflows run from any repo
6
+ against the platform over HTTP — no monorepo checkout required. The v0
7
+ app-monorepo commands below are currently internal; see their availability
8
+ notice.
7
9
 
8
10
  ```
9
11
  seq-studio process lint
@@ -43,7 +45,7 @@ equivalent and would happily install a freshly-published malicious
43
45
  version of any transitive dep. **External process repos scaffolded by
44
46
  `seq-studio process init` ship a `pnpm-workspace.yaml` with the same
45
47
  guard.** Stick with pnpm so the policy actually applies.
46
- (The seq-studio publish chain — `atlas-ui`, `lattice-form-renderer`,
48
+ (The seq-studio publish chain — `agent-spec`, `atlas-ui`, `lattice-form-renderer`,
47
49
  `artifact-studio`, `lattice`, `studio-cli` — is excluded from the quarantine;
48
50
  those come from the Studio repo's own publish pipeline, so new `seq-studio`
49
51
  releases install immediately.)
@@ -134,6 +136,54 @@ Pass `--env <name>` (or `-e <name>`) on commands that talk to the platform.
134
136
  the artifact folder's `.artifact-studio/config.json` `defaultEnv` (set by
135
137
  `artifact link` / `artifact env use`).
136
138
 
139
+ ## App monorepo commands
140
+
141
+ One app repo can contain ORM, managed functions, and an Artifact Studio UI.
142
+ The root `sequence.app.yml` manifest declares the structure — required for
143
+ scaffolding and for the eventual Applications UI. Do not hand-author apps
144
+ without it.
145
+
146
+ > **Availability:** This is currently an internal orchestration workflow.
147
+ > Generated ORM apps depend on unpublished `@sequenceholdings/orm`, so
148
+ > standalone external installs and the function-to-ORM runtime path are not
149
+ > supported yet.
150
+
151
+ | Command | What it does |
152
+ |---------|--------------|
153
+ | `seq-studio init <dir> --with <kinds>` | Scaffold an app monorepo + `sequence.app.yml` + selected primitive subfolders |
154
+ | `seq-studio add function&#124;artifact <name>` | Add a function or Artifact Studio UI to the current app (updates the manifest) |
155
+ | `seq-studio deploy -e <env> [--yes] [--only id1,id2] [--dry-run]` | Deploy every primitive in `deploy.order` (orm → functions → artifact) |
156
+
157
+ `init` supports v0 kinds `orm`, `function`, and `artifact`; pass them as a
158
+ comma-list (`--with orm,function,artifact`) or boolean flags (`--orm`). `add`
159
+ currently supports functions and one Artifact Studio UI; ORM is created only by
160
+ `init`.
161
+
162
+ ```bash
163
+ # Full-stack toy (ORM + function + artifact) — workspace / internal CLI only
164
+ seq-studio init pokedex --with orm,function,artifact --function-name get-pokemon
165
+ cd pokedex
166
+ (cd orm/pokedex && pnpm install)
167
+ (cd functions/get-pokemon && pnpm install)
168
+ (cd artifact && pnpm install)
169
+
170
+ seq-studio login
171
+ seq-studio deploy -e local --yes
172
+
173
+ # Day-2: add a second function without re-init
174
+ seq-studio add function list-types
175
+ (cd functions/list-types && pnpm install)
176
+ seq-studio deploy -e local --only list-types --yes
177
+ ```
178
+
179
+ Each primitive keeps its own `package.json` / lockfile. `deploy` fans out to
180
+ the existing per-primitive commands (`orm apply`, `functions deploy`,
181
+ `artifact deploy`). Pass `--yes` for non-interactive function deploys.
182
+
183
+ `deploy.on_error` defaults to `stop`. Set it to `continue` to deploy independent
184
+ primitives after a failure; any primitive whose `depends_on` prerequisite failed
185
+ or was skipped is skipped as well.
186
+
137
187
  ## Agent commands
138
188
 
139
189
  Typed agent repositories export one or more `defineAgent(...)` values from files
@@ -145,13 +195,17 @@ bundle; it never implicitly deletes agents.
145
195
  | Command | What it does |
146
196
  |---------|--------------|
147
197
  | `seq-studio agents init <dir>` | Scaffold a standalone typed agent repository |
148
- | `seq-studio agents validate [--dir <dir>] [--target <APP_ENV>]` | Compile and validate locally, without API access |
149
- | `seq-studio agents plan [--dir <dir>]` | Offline compile/hash plan |
150
- | `seq-studio agents plan [--dir <dir>] -e <env> [--target <APP_ENV>]` | Diff creates, updates, and unchanged definitions against an environment |
151
- | `seq-studio agents apply [--dir <dir>] -e <env> [--target <APP_ENV>] [--yes]` | Apply creates and updates after showing the plan |
198
+ | `seq-studio agents validate [--dir <dir>] [--target <APP_ENV>] [--only <id1,id2>]` | Compile and validate locally, without API access |
199
+ | `seq-studio agents plan [--dir <dir>] [--only <id1,id2>]` | Offline compile/hash plan |
200
+ | `seq-studio agents plan [--dir <dir>] -e <env> [--target <APP_ENV>] [--only <id1,id2>]` | Diff creates, updates, and unchanged definitions against an environment |
201
+ | `seq-studio agents apply [--dir <dir>] -e <env> [--target <APP_ENV>] [--only <id1,id2>] [--yes]` | Apply creates and updates after showing the plan |
152
202
  | `seq-studio agents list -e <env>` | List visible runtime agents |
153
203
  | `seq-studio agents show <id> -e <env>` | Show one runtime agent |
154
204
 
205
+ For `validate`, `plan`, and `apply`, `--only` accepts a comma-separated list of
206
+ agent IDs after deployment-environment selection; every requested ID must be
207
+ selected.
208
+
155
209
  An optional `deploy-manifest.json` targets definitions by deployment identity:
156
210
 
157
211
  ```json
@@ -187,8 +241,10 @@ Use `--repo agents/<name> [--ref <ref>]` or `--git-url <url>` instead of
187
241
  ## ORM commands
188
242
 
189
243
  `seq-studio orm` authors and deploys governed ORM v2 namespaces: TypeScript
190
- table definitions and policies plus named GraphQL documents compiled into
191
- persisted operations.
244
+ table definitions, Drizzle-authored read-only views, and policies plus named
245
+ GraphQL documents compiled into persisted operations. Import Drizzle query
246
+ helpers from `@sequenceholdings/orm/drizzle`; managed view builders are
247
+ compiled to canonical SQL before registration.
192
248
 
193
249
  | Command | What it does |
194
250
  |---------|--------------|
@@ -335,6 +391,75 @@ same repo share Git review and commit provenance, but keep separate manifests,
335
391
  versions, runtime resources, secrets, and permissions. `--path` accepts only a
336
392
  canonical relative directory inside a remote repo; use `--dir` for local source.
337
393
 
394
+ ### Scaling
395
+
396
+ Managed functions scale to zero by default. Set a bounded warm pool in
397
+ `managed-function.yml` when first-request latency matters:
398
+
399
+ ```yaml
400
+ limits:
401
+ min_instances: 1
402
+ max_instances: 3
403
+ ```
404
+
405
+ `min_instances` defaults to `0`, cannot exceed `max_instances`, and incurs
406
+ Cloud Run idle-instance charges while warm.
407
+
408
+ ### Server-owned resource authorization
409
+
410
+ Some functions expose regulated external resources whose authorization must be
411
+ enforced by Atlas rather than by author code. Those functions select a reviewed
412
+ adapter in `managed-function.yml`:
413
+
414
+ ```yaml
415
+ authorization:
416
+ version: 1
417
+ adapter: encompass.loan-read-by-number
418
+ ```
419
+
420
+ The adapter name is a closed platform registry. Authors cannot provide JSON
421
+ pointers or custom filtering logic. `seq-studio functions build` rejects an
422
+ unknown adapter, an adapter on an unregistered function, or a protected
423
+ function whose required adapter is missing. The manifest declares only the
424
+ server-owned adapter; authors cannot configure its runtime resource selection
425
+ or policy.
426
+
427
+ ### ORM data access
428
+
429
+ A function declares its ORM Data API reach in `capabilities.data`, grouped by
430
+ namespace: `tables` it may read, v1 `actions` and ORM v2 persisted `operations`
431
+ it may invoke, and whether raw read `query` is allowed. At invoke time the
432
+ platform mints a short-lived data token scoped to exactly these refs — an
433
+ operation is scoped as `<namespace>/ops/<OperationName>` (the GraphQL operation
434
+ name from the namespace's `graphql/` documents, case-sensitive), and anything
435
+ undeclared is denied by the Data API. A function that reaches any namespace
436
+ must also attach a top-level `service_account`:
437
+
438
+ ```yaml
439
+ service_account: lucky-svc
440
+ capabilities:
441
+ data:
442
+ lucky:
443
+ tables: [lucky_draws]
444
+ operations: [RollLuckyNumber]
445
+ ```
446
+
447
+ ## Managed Secret commands
448
+
449
+ | Command | What it does |
450
+ |---------|--------------|
451
+ | `seq-studio secrets create <NAME> -e <env> [--org <slug>]` | Register an org-owned secret (no value) |
452
+ | `seq-studio secrets set <NAME> -e <env> [--org <slug>] [--from-file <path>]` | Set the shared default (write-only; file is not echoed) |
453
+ | `seq-studio secrets list -e <env>` | Secrets you can see (never values) |
454
+
455
+ `--org` targets a managed-scope org other than the login tenant and selects
456
+ the intended same-named secret for `set`, `attach`, `detach`, `versions`,
457
+ `set-default`, and `pin`; it is required when the name exists in multiple
458
+ orgs. Tenant pipeline credentials must be created on that tenant's deployment:
459
+ `seq-studio secrets create NAME -e <tenant> --org <tenant>`. Sequence-owned
460
+ environments (`local`, `staging`, `production`) only accept `--org sequence`.
461
+ The API requires Managed Functions Admin on that org.
462
+
338
463
  ## Artifact commands
339
464
 
340
465
  `seq-studio artifact <sub>` is the entry point for Artifact Studio. It runs
@@ -467,11 +592,34 @@ JSON API — the same repos `--repo <ns>/<name>` sources build from.
467
592
  | `seq-studio repos clone <ns>/<name> \| --url <clone-url> \| --id <uuid> -e <env> [--ref <r>] [--out <dir>] [--force]` | smart-HTTP `git clone` when `ATLAS_GIT_PAT` is set (`--url`/`--id` need no seqapi); otherwise JSON materialize + PAT hint |
468
593
  | `seq-studio repos pull <ns>/<name> -e <env> [--ref <r>] [--out <dir>] [--force]` | always materialize via JSON API (no `.git` dir); refuses a non-empty destination unless `--force` |
469
594
  | `seq-studio repos delete <ns>/<name> -e <env> [--yes]` | delete a repo — interactive confirm unless `--yes` |
595
+ | `seq-studio repos ci show <ns>/<name> -e <env> [--ref <r>]` | preview CI checks discovered from the ref |
596
+ | `seq-studio repos ci require <ns>/<name> --check <name> -e <env>` | reserved for requiring a named CI check; currently refuses to write until the sandboxed runner is live |
597
+ | `seq-studio repos ci import <ns>/<name> -e <env> [--ref <r>]` | reserved for requiring every discovered check; currently refuses to write until the sandboxed runner is live |
470
598
 
471
599
  `show` prints the smart-HTTP clone URL (`…/repos/<id>/git`). Basic auth:
472
600
  any username, PAT as password. Prefer `repos clone` over hand-rolling the
473
601
  tree API.
474
602
 
603
+ PR CI discovers `ci/check` from the first available `lint`, `typecheck`, or
604
+ `check` script and `ci/test` from `test`. A `.seq/ci.json` takes precedence;
605
+ its `checks` array can declare script/argv checks or be empty to opt out.
606
+
607
+ For example:
608
+
609
+ { "checks": [
610
+ { "phase": "check", "script": "lint" },
611
+ { "phase": "test", "command": ["pnpm", "test"] }
612
+ ] }
613
+
614
+ Each check needs `phase` (`check` or `test`) and exactly one of `script` or
615
+ `command`; `name` is optional and otherwise defaults to `ci/<phase>`
616
+ (`-2`, etc. for additional checks in that phase).
617
+ Discovery alone never blocks a merge. Until the sandboxed executor is live,
618
+ `repos ci show` is preview-only, Settings controls are disabled, and
619
+ `require`/`import` refuse to write (discovery currently posts `neutral`
620
+ check-runs). Once the executor is live, repo owners can opt in by requiring
621
+ check names in Settings or with `repos ci require`/`repos ci import`.
622
+
475
623
  ## Pipeline commands
476
624
 
477
625
  `seq-studio pipeline <sub>` authors and validates Data Pipelines **stage
@@ -482,13 +630,33 @@ install it alongside the CLI to use this family.
482
630
 
483
631
  | Command | What it does |
484
632
  |---------|--------------|
485
- | `seq-studio pipeline init --type ingestion\|transformation\|serving <name> [--dir <dir>]` | Scaffold `<name>.stage.yml` (commented per-kind template) plus a `src/` entrypoint stub (serving stages are declarative — no stub). Refuses to overwrite an existing spec |
486
- | `seq-studio pipeline validate [dir] [--assets <file\|url>] [--json]` | Run the full offline spec gate: envelope + body validation, `schema_ref` resolution, and repo-level graph validation (reference resolution, single-writer, cycles, column subsets, serving projection checks). Exit 0/1 |
487
- | `seq-studio pipeline plan --repo pipelines/<slug> --ref <sha\|branch> -e <env> [--json]` | Plan a Pipeline deploy (materialize → SDK/`validateSpecGraph` → compile → live-diff → provision findings). Does **not** run Databricks `bundle validate` (that is a Trigger deploy-path hard gate). Exit 1 on destructive findings (CI-safe). `--json` emits the stable plan envelope |
488
- | `seq-studio pipeline deploy --repo pipelines/<slug> --ref <sha> -e <env> [--approved-by <sub>] [--no-wait]` | Plan then enqueue deploy; Trigger runs `bundle validate` then `bundle deploy` against reviewed bytes. Polls to terminal unless `--no-wait`. Production/banksouth require a pinned 40-hex SHA (client + server) |
489
- | `seq-studio pipeline run-now --stage <slug> -e <env> [--repo pipelines/<slug>] [--json]` | Run the stage's active job or DLT pipeline immediately and print its Databricks run URL |
490
- | `seq-studio pipeline promote --stage <slug> --version <v> -e <env> [--repo pipelines/<slug>] [--approved-by <you>] [--no-wait]` | Promote a validated version to another environment. Prod/banksouth require `--approved-by` naming the authenticated caller (approvals are self-recorded); `--repo` disambiguates a slug that exists in multiple Pipelines |
491
- | `seq-studio pipeline rollback --stage <slug> -e <env> [--repo pipelines/<slug>] [--approved-by <you>] [--no-wait]` | Redeploy the previously retired deployment's version. Prod/banksouth require `--approved-by` approvals are explicit even for rollbacks |
633
+ | `seq-studio pipeline init --type ingestion\|transformation\|serving <name> [--dir <dir>]` | Scaffold `<name>.stage.yml` (commented per-kind template) plus a `src/` Databricks-notebook entrypoint stub (begins with `# Databricks notebook source`; serving stages are declarative — no stub). Refuses to overwrite an existing spec |
634
+ | `seq-studio pipeline validate [dir] [--assets <file\|url>] [--orm-contracts <file\|url>] [--json]` | Run the full offline spec gate: envelope + body validation, `schema_ref` resolution, and repo-level graph validation (reference resolution, single-writer, cycles, column subsets, serving projection checks). Auto-loads `orm-contracts.json` from the pipeline dir when present. Exit 0/1 |
635
+ | `seq-studio pipeline plan --repo pipelines/<slug> --ref <sha\|branch> -e <env> [--target <id>] [--json]` | Plan a Pipeline deploy (materialize → SDK/`validateSpecGraph` → compile → live-diff → provision findings). Fails closed listing **every** missing target binding (alert channels, workspace, Databricks `source.credential`) plus Data Sync edge-worker machine/operation/`credEnvFamilies` mismatches before registry writes. Does **not** run Databricks `bundle validate` (that is a Trigger deploy-path hard gate). Exit 1 on destructive findings (CI-safe). `--json` emits the stable plan envelope |
636
+ | `seq-studio pipeline deploy --repo pipelines/<slug> --ref <sha\|branch> -e <env> [--target <id>] [--approved-by <sub>] [--no-wait]` | Plan then enqueue deploy; Trigger runs `bundle validate` then `bundle deploy` against reviewed bytes. Polls to terminal unless `--no-wait`. Targets that require approval need `--approved-by` naming the authenticated caller. |
637
+ | `seq-studio pipeline adopt --stage <slug> --ref <sha\|branch> -e <env> [--target <id>] --native-id <id> --approved-by <you> [--resource-key <key>] [--kind job\|dlt_pipeline] [--old-source-removal-pr <url>] [--repo pipelines/<slug>]` | Bind a live Databricks job/pipeline into the stage without recreation (`bundle deployment bind` on Trigger). Always requires `--approved-by` naming the caller. When the key is still in the monorepo DAB, pass `--old-source-removal-pr` and follow the returned cutover checklist: unbind the old bundle state without deleting the remote, then remove its DAB declaration and add the target-specific adopted-resource entry in the same PR before redeploying. |
638
+ | `seq-studio pipeline unbind --stage <slug> --ref <sha\|branch> -e <env> [--target <id>] --approved-by <you> [--resource-key <key>] [--repo pipelines/<slug>]` | Release an adopted binding on Trigger; the remote object stays live (never deleted) |
639
+ | `seq-studio pipeline run-now --stage <slug> -e <env> [--target <id>] [--repo pipelines/<slug>] [--json]` | Run the stage's active job or DLT pipeline immediately and print its Databricks run URL |
640
+ | `seq-studio pipeline promote --stage <slug> --version <v> -e <env> [--target <id>] [--repo pipelines/<slug>] [--approved-by <you>] [--no-wait]` | Promote a validated version to another target. Targets that require approval need `--approved-by`; `--repo` disambiguates a slug that exists in multiple Pipelines |
641
+ | `seq-studio pipeline rollback --stage <slug> -e <env> [--target <id>] [--repo pipelines/<slug>] [--approved-by <you>] [--no-wait]` | Redeploy the previously retired deployment's version. Targets that require approval need `--approved-by`. |
642
+
643
+ `-e/--env` selects the Atlas connection. `--target` selects the logical
644
+ pipeline target advertised by that endpoint. It is optional when the alias
645
+ matches a target id or when the endpoint has exactly one target.
646
+
647
+ Unless `--no-wait` is set, `deploy`, `promote`, and `rollback` report status or
648
+ status-detail changes while waiting, then emit a 20-second progress heartbeat.
649
+ Terminal output includes the deployment ID and elapsed time; failures include
650
+ the status detail, and any available Trigger run ID is shown. `--json` output
651
+ is unchanged.
652
+
653
+ ### Managed Pipelines asset identity
654
+
655
+ Compiled Databricks assets use stable, environment-qualified identities: display names are `{env}-MP-{type}-{stage}` (with `-trigger` for a scheduled DLT runner), DAB resource keys are `mp_{type}_{stage_snake_case}` (with `_trigger` for that runner), task keys are `{type}_{task}`, and bundles are `{env}-MP-pipelines-{domain}`. Compiled jobs and pipelines also carry `mp: "true"` and `mp_type: <type>` tags.
656
+
657
+ Changing a DAB resource key is a delete-and-create operation. Treat these identities as the existing-resource contract when planning, adopting, unbinding, or inspecting a Managed Pipeline; Unity Catalog catalogs, volumes, tables, and synced tables are not renamed by this convention.
658
+
659
+ The `{env}-MP-…` scheme itself is a one-shot break from the unprefixed names (`silverlake-core`, `pipelines-<domain>-<env>`). We do not migrate DAB bundle state or DLT resource identity: the first deploy after this change creates a new bundle and new jobs/pipelines, and can cascade-drop DLT-managed tables on the deleted resource. Operators should pause or delete leftover unprefixed jobs once the new ones are healthy. Each enabled OpCo deployment advertises and owns its own target profile.
492
660
 
493
661
  `validate` is offline — no network or database. `--assets` supplies a
494
662
  registry **asset export** (JSON) so inputs referencing other Pipelines'
@@ -6,5 +6,5 @@ export declare function agentsPlanCommand(args: ParsedArgs): Promise<number>;
6
6
  export declare function agentsApplyCommand(args: ParsedArgs): Promise<number>;
7
7
  export declare function agentsListCommand(args: ParsedArgs): Promise<number>;
8
8
  export declare function agentsShowCommand(args: ParsedArgs): Promise<number>;
9
- export declare const AGENTS_USAGE = "usage:\n seq-studio agents init <dir> scaffold a typed agent\n seq-studio agents validate [--dir d] [--target app] offline compile + validation\n seq-studio agents plan [--dir d] [-e <env>] offline bundle plan or live diff\n seq-studio agents apply [--dir d] -e <env> [--yes] apply creates/updates; never deletes\n seq-studio agents list -e <env> list visible agents\n seq-studio agents show <id> -e <env> show one agent\n\n Source: local --dir (default .), --repo agents/<name>, or --git-url <url>.\n Use --ref for remote sources. --target selects the deployment APP_ENV when it\n differs from the CLI environment alias (notably OpCo registrations).\n";
9
+ export declare const AGENTS_USAGE = "usage:\n seq-studio agents init <dir> scaffold a typed agent\n seq-studio agents validate [--dir d] [--target app] [--only ids] offline compile + validation\n seq-studio agents plan [--dir d] [-e <env>] [--only ids] offline bundle plan or live diff\n seq-studio agents apply [--dir d] -e <env> [--only ids] [--yes] apply creates/updates; never deletes\n seq-studio agents list -e <env> list visible agents\n seq-studio agents show <id> -e <env> show one agent\n\n Source: local --dir (default .), --repo agents/<name>, or --git-url <url>.\n Use --ref for remote sources. --target selects the deployment APP_ENV when it\n differs from the CLI environment alias (notably OpCo registrations). --only\n accepts a comma-separated list of agent IDs after environment selection.\n";
10
10
  export declare function runAgentsCommand(sub: string | undefined, args: ParsedArgs): Promise<number>;
@@ -7,6 +7,22 @@ import { compileAgentSource as compileSource, materializeAgentSource as material
7
7
  import { agentsInitCommand } from './scaffold.js';
8
8
  export { agentsInitCommand };
9
9
  const LOG = '[seq-studio]';
10
+ function requestedAgentIds(args) {
11
+ const only = args.flags.only;
12
+ if (only === undefined)
13
+ return undefined;
14
+ if (typeof only !== 'string') {
15
+ throw new Error('--only must be a comma-separated list of agent IDs');
16
+ }
17
+ const ids = only
18
+ .split(',')
19
+ .map((id) => id.trim())
20
+ .filter(Boolean);
21
+ if (ids.length === 0) {
22
+ throw new Error('--only must include at least one agent ID');
23
+ }
24
+ return [...new Set(ids)];
25
+ }
10
26
  export async function agentsValidateCommand(args) {
11
27
  const { source } = await materialize({ args, requireEnvironment: false });
12
28
  try {
@@ -15,6 +31,7 @@ export async function agentsValidateCommand(args) {
15
31
  directory: source.dir,
16
32
  targetEnvironment: target,
17
33
  deployEnvironments: await deployEnvironmentNames(),
34
+ onlyIds: requestedAgentIds(args),
18
35
  });
19
36
  if (bundle.definitions.length === 0) {
20
37
  console.error(`${LOG} no named agent.ts definitions found`);
@@ -34,6 +51,7 @@ async function deploymentBundle({ args, source, context, }) {
34
51
  directory: source.dir,
35
52
  targetEnvironment: target,
36
53
  deployEnvironments: await deployEnvironmentNames(),
54
+ onlyIds: requestedAgentIds(args),
37
55
  });
38
56
  }
39
57
  /**
@@ -75,6 +93,7 @@ export async function agentsPlanCommand(args) {
75
93
  directory: source.dir,
76
94
  targetEnvironment: target,
77
95
  deployEnvironments: await deployEnvironmentNames(),
96
+ onlyIds: requestedAgentIds(args),
78
97
  });
79
98
  console.log(`${LOG} offline plan: ${bundle.definitions.length} valid definition${bundle.definitions.length === 1 ? '' : 's'}, bundle ${bundle.hash}`);
80
99
  console.log(`${LOG} pass -e <env> for create/update/unchanged live diff`);
@@ -174,15 +193,16 @@ export async function agentsShowCommand(args) {
174
193
  }
175
194
  export const AGENTS_USAGE = `usage:
176
195
  seq-studio agents init <dir> scaffold a typed agent
177
- seq-studio agents validate [--dir d] [--target app] offline compile + validation
178
- seq-studio agents plan [--dir d] [-e <env>] offline bundle plan or live diff
179
- seq-studio agents apply [--dir d] -e <env> [--yes] apply creates/updates; never deletes
196
+ seq-studio agents validate [--dir d] [--target app] [--only ids] offline compile + validation
197
+ seq-studio agents plan [--dir d] [-e <env>] [--only ids] offline bundle plan or live diff
198
+ seq-studio agents apply [--dir d] -e <env> [--only ids] [--yes] apply creates/updates; never deletes
180
199
  seq-studio agents list -e <env> list visible agents
181
200
  seq-studio agents show <id> -e <env> show one agent
182
201
 
183
202
  Source: local --dir (default .), --repo agents/<name>, or --git-url <url>.
184
203
  Use --ref for remote sources. --target selects the deployment APP_ENV when it
185
- differs from the CLI environment alias (notably OpCo registrations).
204
+ differs from the CLI environment alias (notably OpCo registrations). --only
205
+ accepts a comma-separated list of agent IDs after environment selection.
186
206
  `;
187
207
  export async function runAgentsCommand(sub, args) {
188
208
  try {
@@ -10,9 +10,11 @@ export declare function materializeAgentSource({ args, requireEnvironment, }: {
10
10
  source: ResolvedSource;
11
11
  context: CommandContext | null;
12
12
  }>;
13
- export declare function compileAgentSource({ directory, targetEnvironment, deployEnvironments, }: {
13
+ export declare function compileAgentSource({ directory, targetEnvironment, deployEnvironments, onlyIds, }: {
14
14
  directory: string;
15
15
  targetEnvironment?: string;
16
16
  /** Registered deployment environments, so a real env absent from the manifest is not read as a typo. */
17
17
  deployEnvironments?: readonly string[];
18
+ /** Restrict a plan or apply to explicit agent IDs after environment selection. */
19
+ onlyIds?: readonly string[];
18
20
  }): Promise<CompiledAgentBundle>;
@@ -87,10 +87,25 @@ async function compileManifestEntries({ directory, entries, allowDuplicateIds =
87
87
  sources: compiled.sources,
88
88
  };
89
89
  }
90
- export async function compileAgentSource({ directory, targetEnvironment, deployEnvironments = [], }) {
90
+ export async function compileAgentSource({ directory, targetEnvironment, deployEnvironments = [], onlyIds, }) {
91
91
  const manifest = await readManifest(directory);
92
- if (!manifest)
93
- return compileAgentDirectory({ rootDir: directory });
92
+ if (!manifest) {
93
+ const compiled = await compileAgentDirectory({ rootDir: directory });
94
+ if (!onlyIds || onlyIds.length === 0)
95
+ return compiled;
96
+ const requested = new Set(onlyIds);
97
+ const definitions = compiled.definitions.filter((definition) => requested.has(definition.id));
98
+ const missing = [...requested].filter((id) => !definitions.some((definition) => definition.id === id));
99
+ if (missing.length > 0) {
100
+ throw new Error(`Requested agent IDs were not selected: ${missing.join(', ')}`);
101
+ }
102
+ return {
103
+ ...compiled,
104
+ definitions,
105
+ hash: hashAgentBundle({ definitions }),
106
+ sources: compiled.sources.filter((source) => requested.has(source.definition.id)),
107
+ };
108
+ }
94
109
  if (targetEnvironment !== undefined) {
95
110
  assertKnownTargetEnvironment({
96
111
  manifest,
@@ -109,10 +124,18 @@ export async function compileAgentSource({ directory, targetEnvironment, deployE
109
124
  }
110
125
  // With a target: last-wins by id (tenant overrides). Without: every distinct
111
126
  // path, so validate still compiles override sources that lose a collapse.
112
- const entries = selectAgentEntries({
127
+ const selectedEntries = selectAgentEntries({
113
128
  manifest,
114
129
  environment: targetEnvironment,
115
130
  });
131
+ const requested = new Set(onlyIds);
132
+ const entries = requested.size === 0
133
+ ? selectedEntries
134
+ : selectedEntries.filter((entry) => requested.has(entry.id));
135
+ const missing = [...requested].filter((id) => !entries.some((entry) => entry.id === id));
136
+ if (missing.length > 0) {
137
+ throw new Error(`Requested agent IDs were not selected: ${missing.join(', ')}`);
138
+ }
116
139
  return compileManifestEntries({
117
140
  directory,
118
141
  entries,
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Top-level app monorepo commands:
3
+ * seq-studio init <dir> --with orm,function,artifact
4
+ * seq-studio add <kind> <name>
5
+ * seq-studio deploy -e <env>
6
+ */
7
+ import type { ParsedArgs } from '../process/commands.js';
8
+ export declare const APP_INIT_USAGE: string;
9
+ export declare const APP_ADD_USAGE = "usage:\n seq-studio add <kind> <name>\n\n Scaffold another primitive into the current app monorepo (must contain\n sequence.app.yml) and append it to the manifest.\n\n Kinds: function | artifact\n (orm is only created at init today)\n\n Examples:\n seq-studio add function list-types\n seq-studio add artifact ui\n\n Run from the app root (directory with sequence.app.yml).\n";
10
+ export declare const APP_DEPLOY_USAGE = "usage:\n seq-studio deploy -e <env> [options]\n\n Deploy every primitive in sequence.app.yml in manifest deploy.order\n (default: orm \u2192 functions \u2192 artifact). Runs each kind's existing command:\n orm \u2192 seq-studio orm apply <path> -e <env>\n function \u2192 seq-studio functions deploy --dir <path> -e <env> [--yes]\n artifact \u2192 seq-studio artifact deploy <path> -e <env>\n\n Options:\n -e, --env <name> target environment (required)\n --dir <path> app root (default: cwd; must contain sequence.app.yml)\n --only <id1,id2> deploy only these primitive ids (order preserved)\n --yes non-interactive (forwarded to functions deploy)\n --dry-run print the plan without deploying\n\n Examples:\n seq-studio deploy -e staging --yes\n seq-studio deploy -e local --only get-pokemon --yes\n seq-studio deploy --dir ./pokedex -e staging --dry-run\n\n Run from the app root (directory with sequence.app.yml), or pass --dir.\n";
11
+ export declare function runAppInitCommand(args: ParsedArgs): Promise<number>;
12
+ export declare function runAppAddCommand({ kindArg, args, }: {
13
+ kindArg: string | undefined;
14
+ args: ParsedArgs;
15
+ }): Promise<number>;
16
+ export declare function runAppDeployCommand(args: ParsedArgs): Promise<number>;
@@ -0,0 +1,227 @@
1
+ /**
2
+ * Top-level app monorepo commands:
3
+ * seq-studio init <dir> --with orm,function,artifact
4
+ * seq-studio add <kind> <name>
5
+ * seq-studio deploy -e <env>
6
+ */
7
+ import { basename, resolve } from 'node:path';
8
+ import { REQUIRE_EXPLICIT_ENV_MESSAGE } from '../env-flags.js';
9
+ import { deployApp } from './deploy.js';
10
+ import { resolveRequestedKinds } from './kinds.js';
11
+ import { APP_MANIFEST_FILENAME, isPrimitiveKind, PRIMITIVE_KINDS, slugifyAppId, } from './manifest.js';
12
+ import { addPrimitiveToApp, defaultScaffoldNames, initAppMonorepo, printInitNextSteps, } from './scaffold.js';
13
+ const LOG = '[seq-studio]';
14
+ export const APP_INIT_USAGE = `usage:
15
+ seq-studio init <dir> --with <kinds> [options]
16
+ seq-studio init <dir> --orm --function --artifact [options]
17
+
18
+ Scaffold an app monorepo with ${APP_MANIFEST_FILENAME} and one subfolder per
19
+ selected primitive. At least one kind is required.
20
+
21
+ Kinds: ${PRIMITIVE_KINDS.join(', ')}
22
+
23
+ Options:
24
+ --with <k1,k2,…> comma-separated kinds to scaffold
25
+ --orm --function … boolean aliases for --with
26
+ --function-name <n> name/slug for the first function (default: hello)
27
+ --description <text> optional app.description in the manifest
28
+
29
+ Examples:
30
+ seq-studio init pokedex --with orm,function,artifact
31
+ seq-studio init pokedex --with orm,function,artifact --function-name get-pokemon
32
+ seq-studio init loan-tools --with function --function-name get-loan
33
+ `;
34
+ export const APP_ADD_USAGE = `usage:
35
+ seq-studio add <kind> <name>
36
+
37
+ Scaffold another primitive into the current app monorepo (must contain
38
+ ${APP_MANIFEST_FILENAME}) and append it to the manifest.
39
+
40
+ Kinds: function | artifact
41
+ (orm is only created at init today)
42
+
43
+ Examples:
44
+ seq-studio add function list-types
45
+ seq-studio add artifact ui
46
+
47
+ Run from the app root (directory with ${APP_MANIFEST_FILENAME}).
48
+ `;
49
+ export const APP_DEPLOY_USAGE = `usage:
50
+ seq-studio deploy -e <env> [options]
51
+
52
+ Deploy every primitive in ${APP_MANIFEST_FILENAME} in manifest deploy.order
53
+ (default: orm → functions → artifact). Runs each kind's existing command:
54
+ orm → seq-studio orm apply <path> -e <env>
55
+ function → seq-studio functions deploy --dir <path> -e <env> [--yes]
56
+ artifact → seq-studio artifact deploy <path> -e <env>
57
+
58
+ Options:
59
+ -e, --env <name> target environment (required)
60
+ --dir <path> app root (default: cwd; must contain ${APP_MANIFEST_FILENAME})
61
+ --only <id1,id2> deploy only these primitive ids (order preserved)
62
+ --yes non-interactive (forwarded to functions deploy)
63
+ --dry-run print the plan without deploying
64
+
65
+ Examples:
66
+ seq-studio deploy -e staging --yes
67
+ seq-studio deploy -e local --only get-pokemon --yes
68
+ seq-studio deploy --dir ./pokedex -e staging --dry-run
69
+
70
+ Run from the app root (directory with ${APP_MANIFEST_FILENAME}), or pass --dir.
71
+ `;
72
+ function printError(error) {
73
+ const message = error instanceof Error ? error.message : String(error);
74
+ console.error(`${LOG} ${message}`);
75
+ }
76
+ export async function runAppInitCommand(args) {
77
+ if (args.flags.help === true || args.flags.h === true) {
78
+ console.log(APP_INIT_USAGE);
79
+ return 0;
80
+ }
81
+ const target = args.positional[0];
82
+ if (!target) {
83
+ console.error(APP_INIT_USAGE);
84
+ return 1;
85
+ }
86
+ let kinds;
87
+ try {
88
+ kinds = resolveRequestedKinds(args.flags);
89
+ }
90
+ catch (error) {
91
+ printError(error);
92
+ console.error(APP_INIT_USAGE);
93
+ return 1;
94
+ }
95
+ if (kinds.length === 0) {
96
+ console.error(`${LOG} select at least one primitive via --with orm,function,artifact`);
97
+ console.error(APP_INIT_USAGE);
98
+ return 1;
99
+ }
100
+ const appRoot = resolve(target);
101
+ const appId = slugifyAppId(basename(appRoot));
102
+ if (!appId) {
103
+ console.error(`${LOG} could not derive an app id from "${target}" — use a directory name with letters/numbers`);
104
+ return 1;
105
+ }
106
+ const names = defaultScaffoldNames(appId);
107
+ if (typeof args.flags['function-name'] === 'string') {
108
+ names.functionName = slugifyAppId(args.flags['function-name']) || names.functionName;
109
+ }
110
+ else if (args.flags['function-name'] === true) {
111
+ console.error(`${LOG} --function-name requires a value`);
112
+ return 1;
113
+ }
114
+ const description = typeof args.flags.description === 'string' ? args.flags.description : undefined;
115
+ if (args.flags.description === true) {
116
+ console.error(`${LOG} --description requires a value`);
117
+ return 1;
118
+ }
119
+ try {
120
+ const manifest = await initAppMonorepo({
121
+ rootDir: appRoot,
122
+ appId,
123
+ kinds,
124
+ names,
125
+ description,
126
+ });
127
+ printInitNextSteps({ appRoot, manifest });
128
+ return 0;
129
+ }
130
+ catch (error) {
131
+ printError(error);
132
+ return 1;
133
+ }
134
+ }
135
+ export async function runAppAddCommand({ kindArg, args, }) {
136
+ if (!kindArg || kindArg === 'help' || kindArg === '--help' || kindArg === '-h') {
137
+ console.log(APP_ADD_USAGE);
138
+ return kindArg ? 0 : 1;
139
+ }
140
+ if (!isPrimitiveKind(kindArg)) {
141
+ console.error(`${LOG} unknown kind '${kindArg}' — expected one of: ${PRIMITIVE_KINDS.join(', ')}`);
142
+ console.error(APP_ADD_USAGE);
143
+ return 1;
144
+ }
145
+ if (kindArg === 'orm') {
146
+ console.error(`${LOG} \`seq-studio add orm\` is not supported — orm is created at init`);
147
+ return 1;
148
+ }
149
+ const nameRaw = args.positional[0];
150
+ if (!nameRaw) {
151
+ console.error(`${LOG} usage: seq-studio add ${kindArg} <name>`);
152
+ return 1;
153
+ }
154
+ const name = slugifyAppId(nameRaw);
155
+ if (!name) {
156
+ console.error(`${LOG} invalid name "${nameRaw}" — use kebab-case`);
157
+ return 1;
158
+ }
159
+ try {
160
+ const { entry } = await addPrimitiveToApp({
161
+ rootDir: process.cwd(),
162
+ kind: kindArg,
163
+ name,
164
+ });
165
+ console.log(`${LOG} added ${entry.kind} "${entry.id}" at ${entry.path}`);
166
+ console.log(`${LOG} updated ${APP_MANIFEST_FILENAME}`);
167
+ if (entry.kind === 'function') {
168
+ console.log(`${LOG} next: cd ${entry.path} && pnpm install && seq-studio functions deploy --dir ${entry.path} -e local`);
169
+ }
170
+ return 0;
171
+ }
172
+ catch (error) {
173
+ printError(error);
174
+ return 1;
175
+ }
176
+ }
177
+ function flagString(flags, key) {
178
+ const value = flags[key];
179
+ return typeof value === 'string' ? value : undefined;
180
+ }
181
+ function flagBool(flags, key) {
182
+ return flags[key] === true;
183
+ }
184
+ export async function runAppDeployCommand(args) {
185
+ if (args.flags.help === true || args.flags.h === true) {
186
+ console.log(APP_DEPLOY_USAGE);
187
+ return 0;
188
+ }
189
+ const env = flagString(args.flags, 'env') ?? flagString(args.flags, 'e');
190
+ if (!env) {
191
+ console.error(`${LOG} ${REQUIRE_EXPLICIT_ENV_MESSAGE}`);
192
+ console.error(APP_DEPLOY_USAGE);
193
+ return 1;
194
+ }
195
+ const dirFlag = flagString(args.flags, 'dir');
196
+ const rootDir = resolve(dirFlag ?? process.cwd());
197
+ let only;
198
+ const onlyRaw = flagString(args.flags, 'only');
199
+ if (args.flags.only === true) {
200
+ console.error(`${LOG} --only requires a comma-separated list of primitive ids`);
201
+ return 1;
202
+ }
203
+ if (onlyRaw) {
204
+ only = onlyRaw
205
+ .split(',')
206
+ .map((part) => part.trim())
207
+ .filter(Boolean);
208
+ if (only.length === 0) {
209
+ console.error(`${LOG} --only requires at least one primitive id`);
210
+ return 1;
211
+ }
212
+ }
213
+ try {
214
+ const result = await deployApp({
215
+ rootDir,
216
+ env,
217
+ only,
218
+ yes: flagBool(args.flags, 'yes'),
219
+ dryRun: flagBool(args.flags, 'dry-run'),
220
+ });
221
+ return result.exitCode;
222
+ }
223
+ catch (error) {
224
+ printError(error);
225
+ return 1;
226
+ }
227
+ }