spawnfile 0.1.11 → 0.1.13
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 +21 -3
- package/dist/auth/runtimeCredentials.js +18 -2
- package/dist/cli/devCommands.d.ts +3 -0
- package/dist/cli/devCommands.js +115 -0
- package/dist/cli/lifecycleCommands.js +103 -1
- package/dist/cli/modelCommands.js +3 -0
- package/dist/cli/resolveCommandInput.d.ts +19 -0
- package/dist/cli/resolveCommandInput.js +25 -0
- package/dist/cli/runCli.d.ts +10 -1
- package/dist/cli/runCli.js +41 -13
- package/dist/cli/runtimeCommands.js +1 -0
- package/dist/cli/statusCommand.d.ts +43 -0
- package/dist/cli/statusCommand.js +428 -0
- package/dist/cli/surfaceCommands.js +4 -0
- package/dist/compiler/agentSurfaces.js +2 -4
- package/dist/compiler/buildProject.d.ts +9 -1
- package/dist/compiler/buildProject.js +44 -5
- package/dist/compiler/compileProject.d.ts +2 -0
- package/dist/compiler/compileProject.js +22 -11
- package/dist/compiler/containerArtifacts.d.ts +1 -0
- package/dist/compiler/containerArtifacts.js +208 -32
- package/dist/compiler/containerArtifactsPlans.js +7 -4
- package/dist/compiler/containerArtifactsRender.d.ts +7 -1
- package/dist/compiler/containerArtifactsRender.js +31 -69
- package/dist/compiler/containerArtifactsTypes.d.ts +9 -0
- package/dist/compiler/containerEntrypointRender.js +22 -32
- package/dist/compiler/containerPackageRender.d.ts +15 -0
- package/dist/compiler/containerPackageRender.js +63 -0
- package/dist/compiler/containerTargetResources.js +3 -1
- package/dist/compiler/index.d.ts +1 -0
- package/dist/compiler/index.js +1 -0
- package/dist/compiler/moltnetArtifacts.js +21 -1
- package/dist/compiler/moltnetBinaries.d.ts +4 -1
- package/dist/compiler/moltnetBinaries.js +24 -3
- package/dist/compiler/moltnetClientConfig.d.ts +1 -1
- package/dist/compiler/moltnetClientConfig.js +14 -16
- package/dist/compiler/moltnetNodeConfig.js +2 -4
- package/dist/compiler/moltnetResolution.js +1 -1
- package/dist/compiler/moltnetRoomMemberships.js +1 -2
- package/dist/compiler/moltnetRuntimeConfig.d.ts +1 -1
- package/dist/compiler/moltnetRuntimeConfig.js +9 -10
- package/dist/compiler/networkBinding.d.ts +14 -0
- package/dist/compiler/networkBinding.js +34 -0
- package/dist/compiler/publishProject.d.ts +13 -0
- package/dist/compiler/publishProject.js +72 -0
- package/dist/compiler/runProject.d.ts +26 -12
- package/dist/compiler/runProject.js +109 -126
- package/dist/compiler/runProjectAuth.d.ts +5 -0
- package/dist/compiler/runProjectAuth.js +98 -0
- package/dist/compiler/runProjectDocker.d.ts +19 -0
- package/dist/compiler/runProjectDocker.js +202 -0
- package/dist/compiler/types.d.ts +1 -2
- package/dist/compiler/upProject.d.ts +6 -0
- package/dist/compiler/upProject.js +43 -8
- package/dist/compiler/view/buildOrganizationView.js +4 -1
- package/dist/compiler/view/declarations.d.ts +4 -0
- package/dist/compiler/view/declarations.js +104 -0
- package/dist/compiler/view/renderNetworks.js +1 -2
- package/dist/compiler/view/renderTree.js +6 -2
- package/dist/compiler/view/types.d.ts +64 -1
- package/dist/deployment/dockerInspect.d.ts +29 -0
- package/dist/deployment/dockerInspect.js +130 -0
- package/dist/deployment/dockerLabels.d.ts +16 -0
- package/dist/deployment/dockerLabels.js +32 -0
- package/dist/deployment/dockerLogs.d.ts +24 -0
- package/dist/deployment/dockerLogs.js +96 -0
- package/dist/deployment/dockerManager.d.ts +59 -0
- package/dist/deployment/dockerManager.js +76 -0
- package/dist/deployment/dockerProbeGateway.d.ts +16 -0
- package/dist/deployment/dockerProbeGateway.js +48 -0
- package/dist/deployment/dockerRecover.d.ts +17 -0
- package/dist/deployment/dockerRecover.js +129 -0
- package/dist/deployment/homeStore.d.ts +19 -0
- package/dist/deployment/homeStore.js +130 -0
- package/dist/deployment/index.d.ts +10 -0
- package/dist/deployment/index.js +10 -0
- package/dist/deployment/names.d.ts +3 -0
- package/dist/deployment/names.js +12 -0
- package/dist/deployment/record.d.ts +76 -0
- package/dist/deployment/record.js +174 -0
- package/dist/deployment/target.d.ts +35 -0
- package/dist/deployment/target.js +96 -0
- package/dist/dev/activity.d.ts +14 -0
- package/dist/dev/activity.js +48 -0
- package/dist/dev/docker.d.ts +16 -0
- package/dist/dev/docker.js +71 -0
- package/dist/dev/index.d.ts +2 -0
- package/dist/dev/index.js +2 -0
- package/dist/dev/project.d.ts +35 -0
- package/dist/dev/project.js +164 -0
- package/dist/distribution/buildDistributionReport.d.ts +22 -0
- package/dist/distribution/buildDistributionReport.js +51 -0
- package/dist/distribution/consumeImage.d.ts +29 -0
- package/dist/distribution/consumeImage.js +254 -0
- package/dist/distribution/consumeImageSupport.d.ts +18 -0
- package/dist/distribution/consumeImageSupport.js +63 -0
- package/dist/distribution/distributionReportSchema.d.ts +84 -0
- package/dist/distribution/distributionReportSchema.js +107 -0
- package/dist/distribution/dockerRunner.d.ts +5 -0
- package/dist/distribution/dockerRunner.js +21 -0
- package/dist/distribution/extractImage.d.ts +25 -0
- package/dist/distribution/extractImage.js +96 -0
- package/dist/distribution/fingerprint.d.ts +2 -0
- package/dist/distribution/fingerprint.js +14 -0
- package/dist/distribution/imageRef.d.ts +9 -0
- package/dist/distribution/imageRef.js +65 -0
- package/dist/distribution/imageRuntimeAuth.d.ts +19 -0
- package/dist/distribution/imageRuntimeAuth.js +71 -0
- package/dist/distribution/index.d.ts +24 -0
- package/dist/distribution/index.js +16 -0
- package/dist/distribution/preflight.d.ts +11 -0
- package/dist/distribution/preflight.js +65 -0
- package/dist/distribution/projectImageView.d.ts +8 -0
- package/dist/distribution/projectImageView.js +83 -0
- package/dist/distribution/projectName.d.ts +1 -0
- package/dist/distribution/projectName.js +12 -0
- package/dist/distribution/renderImageInterface.d.ts +30 -0
- package/dist/distribution/renderImageInterface.js +86 -0
- package/dist/distribution/tarReader.d.ts +10 -0
- package/dist/distribution/tarReader.js +69 -0
- package/dist/distribution/types.d.ts +80 -0
- package/dist/distribution/types.js +4 -0
- package/dist/distribution/verifyDistributionReport.d.ts +12 -0
- package/dist/distribution/verifyDistributionReport.js +29 -0
- package/dist/filesystem/paths.d.ts +6 -0
- package/dist/filesystem/paths.js +11 -0
- package/dist/manifest/loadManifest.js +22 -2
- package/dist/manifest/renderSpawnfile.js +2 -4
- package/dist/manifest/schemas.d.ts +17 -41
- package/dist/manifest/surfaceSchemas.d.ts +19 -47
- package/dist/manifest/surfaceSchemas.js +3 -6
- package/dist/report/createReport.d.ts +7 -1
- package/dist/report/createReport.js +28 -1
- package/dist/report/types.d.ts +43 -0
- package/dist/runtime/common.d.ts +2 -0
- package/dist/runtime/common.js +1 -1
- package/dist/runtime/container.d.ts +1 -0
- package/dist/runtime/container.js +82 -15
- package/dist/runtime/index.d.ts +1 -0
- package/dist/runtime/index.js +1 -0
- package/dist/runtime/install.d.ts +1 -11
- package/dist/runtime/install.js +27 -30
- package/dist/runtime/openclaw/adapter.js +9 -1
- package/dist/runtime/openclaw/configAuth.d.ts +15 -0
- package/dist/runtime/openclaw/configAuth.js +46 -0
- package/dist/runtime/openclaw/runAuth.d.ts +6 -0
- package/dist/runtime/openclaw/runAuth.js +7 -55
- package/dist/runtime/openclaw/statusProbes.d.ts +2 -0
- package/dist/runtime/openclaw/statusProbes.js +31 -0
- package/dist/runtime/pi/adapter.d.ts +4 -0
- package/dist/runtime/pi/adapter.js +198 -0
- package/dist/runtime/pi/appActivitySource.d.ts +1 -0
- package/dist/runtime/pi/appActivitySource.js +87 -0
- package/dist/runtime/pi/appCoreSource.d.ts +1 -0
- package/dist/runtime/pi/appCoreSource.js +354 -0
- package/dist/runtime/pi/appPreludeSource.d.ts +1 -0
- package/dist/runtime/pi/appPreludeSource.js +103 -0
- package/dist/runtime/pi/appSource.d.ts +1 -0
- package/dist/runtime/pi/appSource.js +8 -0
- package/dist/runtime/pi/appTemplate.d.ts +28 -0
- package/dist/runtime/pi/appTemplate.js +144 -0
- package/dist/runtime/pi/runAuth.d.ts +2 -0
- package/dist/runtime/pi/runAuth.js +68 -0
- package/dist/runtime/picoclaw/adapter.js +10 -0
- package/dist/runtime/picoclaw/runAuth.d.ts +7 -0
- package/dist/runtime/picoclaw/runAuth.js +8 -68
- package/dist/runtime/picoclaw/statusProbes.d.ts +2 -0
- package/dist/runtime/picoclaw/statusProbes.js +100 -0
- package/dist/runtime/registry.d.ts +0 -5
- package/dist/runtime/registry.js +5 -12
- package/dist/runtime/statusProbes.d.ts +16 -0
- package/dist/runtime/statusProbes.js +41 -0
- package/dist/runtime/types.d.ts +53 -1
- package/dist/shared/errors.d.ts +6 -0
- package/dist/shared/errors.js +9 -0
- package/dist/status/buildStatus.d.ts +13 -0
- package/dist/status/buildStatus.js +266 -0
- package/dist/status/compileReport.d.ts +96 -0
- package/dist/status/compileReport.js +230 -0
- package/dist/status/compiledContainerObservations.d.ts +5 -0
- package/dist/status/compiledContainerObservations.js +63 -0
- package/dist/status/deploymentLogs.d.ts +12 -0
- package/dist/status/deploymentLogs.js +45 -0
- package/dist/status/deployments.d.ts +14 -0
- package/dist/status/deployments.js +121 -0
- package/dist/status/imageStatusReport.d.ts +10 -0
- package/dist/status/imageStatusReport.js +73 -0
- package/dist/status/index.d.ts +14 -0
- package/dist/status/index.js +14 -0
- package/dist/status/moltnetProbes.d.ts +21 -0
- package/dist/status/moltnetProbes.js +240 -0
- package/dist/status/registryDrift.d.ts +28 -0
- package/dist/status/registryDrift.js +119 -0
- package/dist/status/renderStatus.d.ts +2 -0
- package/dist/status/renderStatus.js +230 -0
- package/dist/status/runtimeProbes.d.ts +13 -0
- package/dist/status/runtimeProbes.js +111 -0
- package/dist/status/selectionSubjects.d.ts +8 -0
- package/dist/status/selectionSubjects.js +77 -0
- package/dist/status/selectors.d.ts +14 -0
- package/dist/status/selectors.js +90 -0
- package/dist/status/traversal.d.ts +6 -0
- package/dist/status/traversal.js +15 -0
- package/dist/status/types.d.ts +113 -0
- package/dist/status/types.js +1 -0
- package/package.json +9 -2
- package/runtimes.yaml +44 -21
- package/dist/runtime/tinyclaw/adapter.d.ts +0 -2
- package/dist/runtime/tinyclaw/adapter.js +0 -284
- package/dist/runtime/tinyclaw/runAuth.d.ts +0 -2
- package/dist/runtime/tinyclaw/runAuth.js +0 -49
- package/dist/runtime/tinyclaw/scaffold-assets/AGENTS.md +0 -160
- package/dist/runtime/tinyclaw/scaffold-assets/CLAUDE.md +0 -5
- package/dist/runtime/tinyclaw/scaffold-assets/SOUL.md +0 -177
- package/dist/runtime/tinyclaw/scaffold.d.ts +0 -2
- package/dist/runtime/tinyclaw/scaffold.js +0 -24
- package/dist/runtime/tinyclaw/schedules.d.ts +0 -9
- package/dist/runtime/tinyclaw/schedules.js +0 -62
- package/dist/runtime/tinyclaw/surfaces.d.ts +0 -8
- package/dist/runtime/tinyclaw/surfaces.js +0 -94
package/README.md
CHANGED
|
@@ -37,12 +37,25 @@ spawnfile init # scaffold an agent (defaults t
|
|
|
37
37
|
spawnfile validate # check the graph
|
|
38
38
|
spawnfile view . # read-only graph view; writes no files
|
|
39
39
|
spawnfile compile # lower to runtime-native output
|
|
40
|
+
spawnfile status . # read declared/compiled status
|
|
40
41
|
spawnfile auth sync --profile dev --env-file .env
|
|
41
42
|
spawnfile build --tag my-agent # compile + docker build
|
|
42
|
-
spawnfile
|
|
43
|
+
spawnfile up . --context gpu-4090 --detach # build and run on a Docker context
|
|
44
|
+
spawnfile dev up . --auth-profile dev # detached dev loop in .spawn-dev
|
|
45
|
+
spawnfile dev apply . --agent researcher # hot-add/reload one Pi agent
|
|
46
|
+
spawnfile dev activity . --agent researcher # inspect buffered Pi activity events
|
|
47
|
+
spawnfile run --tag my-agent --auth-profile dev --detach
|
|
48
|
+
spawnfile status . --live # inspect the detached deployment
|
|
49
|
+
spawnfile publish . --tag you/my-agent:1.0.0 # compile + build + verify + push
|
|
43
50
|
```
|
|
44
51
|
|
|
45
|
-
Compiled output lands under `.spawn/` by default, including a `Dockerfile`, `entrypoint.sh`, `.env.example`, and a prebuilt `container/rootfs/` tree. `spawnfile build` uses the pinned runtime artifacts from `runtimes.yaml`; it does not rebuild runtimes from source.
|
|
52
|
+
Compiled output lands under `.spawn/` by default, including a `Dockerfile`, `entrypoint.sh`, `.env.example`, and a prebuilt `container/rootfs/` tree. `spawnfile build` uses the pinned runtime artifacts from `runtimes.yaml`; it does not rebuild runtimes from source. Daimon, OpenClaw, and PicoClaw use published copyable artifact images by default, so normal prompt/config edits reuse their dependency layers. To test a local runtime artifact instead, set `SPAWNFILE_DAIMON_RUNTIME_IMAGE`, `SPAWNFILE_OPENCLAW_RUNTIME_IMAGE`, or `SPAWNFILE_PICOCLAW_RUNTIME_IMAGE` to a local image tag. For Pi-heavy orgs, build a reusable runtime base with `npm run runtime:pi-base -- noopolis/spawnfile-pi-runtime:0.79.9-node24`, then set `SPAWNFILE_PI_RUNTIME_BASE_IMAGE` during `spawnfile up`. For `build`/`up` on a docker `--context`, Moltnet release assets are staged for that context's architecture (`amd64` or `arm64`); for local-only manual compile targeting a fixed architecture, set `SPAWNFILE_MOLTNET_TARGET_ARCH=amd64|arm64`.
|
|
53
|
+
|
|
54
|
+
`spawnfile status` is read-only. By default it shows authored and compiled state without Docker, runtime, or Moltnet calls. With `--live`, it reads the selected detached deployment record, inspects the recorded Docker target, runs adapter-owned runtime probes, and checks Moltnet metadata without reading message bodies. Add `--logs` for a redacted Docker log tail, or `--watch` to refresh status continuously. For a remote Docker context where the local record is missing, pass `--context <name>` with `--live` to recover the deployment from Spawnfile container labels.
|
|
55
|
+
|
|
56
|
+
`spawnfile dev` is the source-backed interactive loop. It uses `.spawn-dev/` by default, starts a detached dev deployment with `spawnfile dev up`, and can hot-apply one Daimon runtime agent with `spawnfile dev apply --agent <id>` without restarting the rest of the org. Hot apply recompiles source, copies the selected agent workspace, Daimon config, matching Moltnet node configs, and managed Moltnet server configs into the running container, loads it through the Daimon control endpoint, and starts only that agent's Moltnet bridges when it is new. `spawnfile dev activity` reads the generated Daimon app's bounded activity buffer as JSON lines so operators can see queued wakes, turn starts/completions, runtime event types, output completions, and errors without mixing those diagnostics into Moltnet chat. Running managed Moltnet servers keep their current in-memory room membership until an operator-token `moltnet apply` or server restart reconciles the copied server config.
|
|
57
|
+
|
|
58
|
+
Compiled images are self-describing: `spawnfile publish` pushes one to any OCI registry, and anyone can run it with no source — `spawnfile up you/my-agent:1.0.0 --deployment prod --detach --auth-profile me` — or inspect what it needs first with `spawnfile status you/my-agent:1.0.0`. See [`specs/DISTRIBUTION.md`](specs/DISTRIBUTION.md).
|
|
46
59
|
|
|
47
60
|
Declare external credentials in `secrets:` and provide values through an ignored env file or the shell environment. `spawnfile auth sync --env-file .env` stores declared model auth and project secrets in a local auth profile; `spawnfile run --env-file .env` can inject the same values directly for a single run. This is the intended pattern for credentials like `GH_TOKEN`, MCP tokens, and provider API keys.
|
|
48
61
|
|
|
@@ -91,9 +104,12 @@ v0.1 targets autonomous agent runtimes that share a markdown workspace identity
|
|
|
91
104
|
|-----------|---------------|---------|-----------------------------------------------|
|
|
92
105
|
| OpenClaw | active | ✅ | Discord, Telegram, WhatsApp, Slack |
|
|
93
106
|
| PicoClaw | active | | Discord, Telegram, Slack (WhatsApp blocked) |
|
|
94
|
-
|
|
|
107
|
+
| Pi | active | | Embedded org app, Moltnet client config |
|
|
95
108
|
| NullClaw | exploratory | | No active adapter yet |
|
|
96
109
|
| ZeroClaw | exploratory | | No active adapter yet |
|
|
110
|
+
| OpenFang | exploratory | | No active adapter yet |
|
|
111
|
+
| Hermes Agent | exploratory | | No active adapter yet |
|
|
112
|
+
| OpenCode | exploratory | | No active adapter yet |
|
|
97
113
|
|
|
98
114
|
Each adapter maps the portable schema into its native forms. The compiler reports a machine-readable `spawnfile-report.json` with the resolved graph, chosen runtimes, and capability outcomes (`supported`, `degraded`, `unsupported`). See [`specs/RUNTIMES.md`](specs/RUNTIMES.md) for the live matrix and pinned versions, or [`runtimes.yaml`](runtimes.yaml) for the registry source of truth.
|
|
99
115
|
|
|
@@ -113,6 +129,8 @@ The source-of-truth specs live in this repo:
|
|
|
113
129
|
- [`specs/CONTAINERS.md`](specs/CONTAINERS.md) — container compilation
|
|
114
130
|
- [`specs/RUNTIMES.md`](specs/RUNTIMES.md) — runtime registry and version pinning
|
|
115
131
|
- [`specs/SURFACES.md`](specs/SURFACES.md) — messaging surface model
|
|
132
|
+
- [`specs/STATUS.md`](specs/STATUS.md) — static and live operational status
|
|
133
|
+
- [`specs/DISTRIBUTION.md`](specs/DISTRIBUTION.md) — image distribution, publish, and sourceless run
|
|
116
134
|
- [`fixtures/`](fixtures/) — canonical example projects
|
|
117
135
|
|
|
118
136
|
## From source
|
|
@@ -14,6 +14,21 @@ const parseJson = (filePath, content) => {
|
|
|
14
14
|
throw new SpawnfileError("validation_error", `Invalid imported auth file ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
15
15
|
}
|
|
16
16
|
};
|
|
17
|
+
const decodeJwtExpiry = (token) => {
|
|
18
|
+
const payload = token.split(".")[1];
|
|
19
|
+
if (!payload) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
const decoded = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
|
|
24
|
+
return typeof decoded.exp === "number" && Number.isFinite(decoded.exp)
|
|
25
|
+
? decoded.exp * 1000
|
|
26
|
+
: null;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
17
32
|
export const loadImportedClaudeCodeCredential = async (sourceDirectory) => {
|
|
18
33
|
const credentialsPath = path.join(sourceDirectory, ".credentials.json");
|
|
19
34
|
if (!(await fileExists(credentialsPath))) {
|
|
@@ -66,11 +81,12 @@ export const loadImportedCodexCredential = async (sourceDirectory) => {
|
|
|
66
81
|
refresh.length === 0) {
|
|
67
82
|
return null;
|
|
68
83
|
}
|
|
69
|
-
const
|
|
84
|
+
const jwtExpiry = decodeJwtExpiry(access);
|
|
85
|
+
const expires = jwtExpiry ?? (await stat(authPath)).mtimeMs + 60 * 60 * 1000;
|
|
70
86
|
return {
|
|
71
87
|
access,
|
|
72
88
|
...(typeof accountId === "string" && accountId.length > 0 ? { accountId } : {}),
|
|
73
|
-
expires
|
|
89
|
+
expires,
|
|
74
90
|
refresh
|
|
75
91
|
};
|
|
76
92
|
};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { InvalidArgumentError } from "commander";
|
|
2
|
+
const parsePositiveIntegerOption = (value) => {
|
|
3
|
+
if (!/^[1-9]\d*$/u.test(value)) {
|
|
4
|
+
throw new InvalidArgumentError("must be a positive integer");
|
|
5
|
+
}
|
|
6
|
+
return Number(value);
|
|
7
|
+
};
|
|
8
|
+
export const registerDevCommands = (program, handlers, streams) => {
|
|
9
|
+
const dev = program.command("dev").description("Run an interactive Spawnfile development deployment");
|
|
10
|
+
dev
|
|
11
|
+
.command("up")
|
|
12
|
+
.description("Compile, build, and start a detached dev deployment under .spawn-dev")
|
|
13
|
+
.argument("[path]", "Project directory or Spawnfile path", process.cwd())
|
|
14
|
+
.option("--auth-profile <name>", "Local Spawnfile auth profile")
|
|
15
|
+
.option("--context <name>", "Docker context for the deployment target")
|
|
16
|
+
.option("--deployment <name>", "Detached deployment record name")
|
|
17
|
+
.option("--docker-command <command>", "Docker command")
|
|
18
|
+
.option("--env-file <file>", "Path to an env file for runtime secrets")
|
|
19
|
+
.option("--name <container>", "Docker container name")
|
|
20
|
+
.option("-o, --out <directory>", "Dev output directory")
|
|
21
|
+
.option("-t, --tag <image>", "Docker image tag")
|
|
22
|
+
.action(async (inputPath, options) => {
|
|
23
|
+
const result = await handlers.devUpProject(inputPath, {
|
|
24
|
+
authProfile: options.authProfile,
|
|
25
|
+
containerName: options.name,
|
|
26
|
+
deploymentName: options.deployment,
|
|
27
|
+
dockerCommand: options.dockerCommand,
|
|
28
|
+
dockerContext: options.context,
|
|
29
|
+
envFilePath: options.envFile,
|
|
30
|
+
imageTag: options.tag,
|
|
31
|
+
outputDirectory: options.out
|
|
32
|
+
});
|
|
33
|
+
streams.stdout(`dev deployment: ${result.containerName ?? "unknown"}`);
|
|
34
|
+
streams.stdout(`image: ${result.imageTag}`);
|
|
35
|
+
streams.stdout(`compiled to ${result.outputDirectory}`);
|
|
36
|
+
if (result.deploymentRecordPath) {
|
|
37
|
+
streams.stdout(`record: ${result.deploymentRecordPath}`);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
dev
|
|
41
|
+
.command("apply")
|
|
42
|
+
.description("Hot-apply one agent into a running dev deployment")
|
|
43
|
+
.argument("[path]", "Project directory or Spawnfile path", process.cwd())
|
|
44
|
+
.requiredOption("--agent <id>", "Agent id, slug, or name to hot-apply")
|
|
45
|
+
.option("--deployment <name>", "Detached deployment record name")
|
|
46
|
+
.option("--docker-command <command>", "Docker command")
|
|
47
|
+
.option("-o, --out <directory>", "Dev output directory")
|
|
48
|
+
.action(async (inputPath, options) => {
|
|
49
|
+
const result = await handlers.devApplyProject(inputPath, {
|
|
50
|
+
agent: options.agent,
|
|
51
|
+
deploymentName: options.deployment,
|
|
52
|
+
dockerCommand: options.dockerCommand,
|
|
53
|
+
outputDirectory: options.out
|
|
54
|
+
});
|
|
55
|
+
streams.stdout(`applied agent ${result.agentId}`);
|
|
56
|
+
streams.stdout(`container: ${result.containerName}`);
|
|
57
|
+
streams.stdout(`bridge: ${result.bridgeStarted ? "started" : "unchanged"}`);
|
|
58
|
+
});
|
|
59
|
+
dev
|
|
60
|
+
.command("restart")
|
|
61
|
+
.description("Reload one agent in a running dev deployment")
|
|
62
|
+
.argument("[path]", "Project directory or Spawnfile path", process.cwd())
|
|
63
|
+
.requiredOption("--agent <id>", "Agent id, slug, or name to reload")
|
|
64
|
+
.option("--deployment <name>", "Detached deployment record name")
|
|
65
|
+
.option("--docker-command <command>", "Docker command")
|
|
66
|
+
.option("-o, --out <directory>", "Dev output directory")
|
|
67
|
+
.action(async (inputPath, options) => {
|
|
68
|
+
const result = await handlers.devRestartProject(inputPath, {
|
|
69
|
+
agent: options.agent,
|
|
70
|
+
deploymentName: options.deployment,
|
|
71
|
+
dockerCommand: options.dockerCommand,
|
|
72
|
+
outputDirectory: options.out
|
|
73
|
+
});
|
|
74
|
+
streams.stdout(`restarted agent ${result.agentId}`);
|
|
75
|
+
streams.stdout(`container: ${result.containerName}`);
|
|
76
|
+
streams.stdout(`bridge: ${result.bridgeStarted ? "started" : "unchanged"}`);
|
|
77
|
+
});
|
|
78
|
+
dev
|
|
79
|
+
.command("activity")
|
|
80
|
+
.description("Print the current Pi agent activity buffer from a running dev deployment")
|
|
81
|
+
.argument("[path]", "Project directory or Spawnfile path", process.cwd())
|
|
82
|
+
.option("--agent <id>", "Only show events for one agent id, slug, or name")
|
|
83
|
+
.option("--deployment <name>", "Detached deployment record name")
|
|
84
|
+
.option("--docker-command <command>", "Docker command")
|
|
85
|
+
.option("-o, --out <directory>", "Dev output directory")
|
|
86
|
+
.option("--tail <count>", "Maximum buffered events to print", parsePositiveIntegerOption)
|
|
87
|
+
.action(async (inputPath, options) => {
|
|
88
|
+
const result = await handlers.devActivityProject(inputPath, {
|
|
89
|
+
agent: options.agent,
|
|
90
|
+
deploymentName: options.deployment,
|
|
91
|
+
dockerCommand: options.dockerCommand,
|
|
92
|
+
outputDirectory: options.out,
|
|
93
|
+
tail: options.tail
|
|
94
|
+
});
|
|
95
|
+
for (const event of result.events) {
|
|
96
|
+
streams.stdout(JSON.stringify(event));
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
dev
|
|
100
|
+
.command("stop")
|
|
101
|
+
.description("Stop the recorded dev deployment container")
|
|
102
|
+
.argument("[path]", "Project directory or Spawnfile path", process.cwd())
|
|
103
|
+
.option("--deployment <name>", "Detached deployment record name")
|
|
104
|
+
.option("--docker-command <command>", "Docker command")
|
|
105
|
+
.option("-o, --out <directory>", "Dev output directory")
|
|
106
|
+
.action(async (inputPath, options) => {
|
|
107
|
+
const result = await handlers.devStopProject(inputPath, {
|
|
108
|
+
deploymentName: options.deployment,
|
|
109
|
+
dockerCommand: options.dockerCommand,
|
|
110
|
+
outputDirectory: options.out
|
|
111
|
+
});
|
|
112
|
+
streams.stdout(`stopped dev deployment ${result.deploymentName}`);
|
|
113
|
+
streams.stdout(`container: ${result.containerName}`);
|
|
114
|
+
});
|
|
115
|
+
};
|
|
@@ -1,6 +1,27 @@
|
|
|
1
|
+
import { readRunEnvFile } from "../compiler/runProjectAuth.js";
|
|
2
|
+
import { SpawnfileError } from "../shared/index.js";
|
|
3
|
+
import { resolveCommandInput } from "./resolveCommandInput.js";
|
|
4
|
+
const resolveAuthProfileForImage = async (handlers, authProfile) => {
|
|
5
|
+
if (!authProfile || !handlers.requireAuthProfile) {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
return handlers.requireAuthProfile(authProfile);
|
|
9
|
+
};
|
|
10
|
+
const shortDigest = (digest) => digest ? digest.replace(/^sha256:/, "").slice(0, 12) : "unknown";
|
|
11
|
+
/** Summarizes a redeploy as previous → new ref/digest, per specs/DISTRIBUTION.md. */
|
|
12
|
+
const formatRedeploySummary = (previous, newRef, newDigest) => {
|
|
13
|
+
// Only claim "unchanged" when we actually resolved a matching digest; two null
|
|
14
|
+
// digests (local-only images) are unknown, not provably identical.
|
|
15
|
+
if (previous.ref === newRef && newDigest !== null && previous.digest === newDigest) {
|
|
16
|
+
return `redeployed image ${newRef} (digest unchanged ${shortDigest(newDigest)})`;
|
|
17
|
+
}
|
|
18
|
+
return (`redeployed image: ${previous.ref} (${shortDigest(previous.digest)}) ` +
|
|
19
|
+
`→ ${newRef} (${shortDigest(newDigest)})`);
|
|
20
|
+
};
|
|
1
21
|
export const registerLifecycleCommands = (program, handlers, streams) => {
|
|
2
22
|
program
|
|
3
23
|
.command("compile")
|
|
24
|
+
.description("Compile a project into runtime-native output under .spawn")
|
|
4
25
|
.argument("[path]", "Project directory or Spawnfile path", process.cwd())
|
|
5
26
|
.option("-o, --out <directory>", "Output directory")
|
|
6
27
|
.action(async (inputPath, options) => {
|
|
@@ -10,12 +31,15 @@ export const registerLifecycleCommands = (program, handlers, streams) => {
|
|
|
10
31
|
});
|
|
11
32
|
program
|
|
12
33
|
.command("build")
|
|
34
|
+
.description("Compile and build the organization Docker image")
|
|
13
35
|
.argument("[path]", "Project directory or Spawnfile path", process.cwd())
|
|
36
|
+
.option("--context <name>", "Docker context for the build target")
|
|
14
37
|
.option("--docker-command <command>", "Docker command")
|
|
15
38
|
.option("-o, --out <directory>", "Output directory")
|
|
16
39
|
.option("-t, --tag <image>", "Docker image tag")
|
|
17
40
|
.action(async (inputPath, options) => {
|
|
18
41
|
const result = await handlers.buildProject(inputPath, {
|
|
42
|
+
dockerContext: options.context,
|
|
19
43
|
dockerCommand: options.dockerCommand,
|
|
20
44
|
imageTag: options.tag,
|
|
21
45
|
outputDirectory: options.out
|
|
@@ -26,20 +50,36 @@ export const registerLifecycleCommands = (program, handlers, streams) => {
|
|
|
26
50
|
});
|
|
27
51
|
program
|
|
28
52
|
.command("run")
|
|
53
|
+
.description("Compile, build, and run the organization container locally")
|
|
29
54
|
.argument("[path]", "Project directory or Spawnfile path", process.cwd())
|
|
30
55
|
.option("-o, --out <directory>", "Output directory")
|
|
31
56
|
.option("-t, --tag <image>", "Docker image tag")
|
|
32
57
|
.option("--auth-profile <name>", "Local Spawnfile auth profile")
|
|
58
|
+
.option("--context <name>", "Docker context for the deployment target")
|
|
59
|
+
.option("--deployment <name>", "Detached deployment record name")
|
|
33
60
|
.option("--docker-command <command>", "Docker command")
|
|
34
61
|
.option("--name <container>", "Docker container name")
|
|
35
62
|
.option("--env-file <file>", "Path to an env file for runtime secrets")
|
|
63
|
+
.option("--image", "Interpret the argument as an image reference")
|
|
36
64
|
.option("-d, --detach", "Run the container in detached mode")
|
|
37
65
|
.action(async (inputPath, options) => {
|
|
66
|
+
const runInput = resolveCommandInput(inputPath, { forceImage: options.image });
|
|
67
|
+
if (runInput.kind === "image" || (runInput.kind === "invalid" && options.image)) {
|
|
68
|
+
throw new SpawnfileError("validation_error", `Image-mode run is not supported. Deploy the image with: ` +
|
|
69
|
+
`spawnfile up ${inputPath} --auth-profile <profile> ` +
|
|
70
|
+
"(add --env-file for any extra secrets; image deployments always detach).");
|
|
71
|
+
}
|
|
72
|
+
if (runInput.kind === "invalid") {
|
|
73
|
+
throw new SpawnfileError("validation_error", `Cannot resolve "${inputPath}" as a project path or image reference. ` +
|
|
74
|
+
"Pass a project directory, a Spawnfile path, or an image like 'name:tag' (use --image to force image mode).");
|
|
75
|
+
}
|
|
38
76
|
const result = await handlers.runProject(inputPath, {
|
|
39
77
|
authProfile: options.authProfile,
|
|
40
78
|
containerName: options.name,
|
|
41
79
|
detach: options.detach,
|
|
80
|
+
deploymentName: options.deployment,
|
|
42
81
|
dockerCommand: options.dockerCommand,
|
|
82
|
+
dockerContext: options.context,
|
|
43
83
|
envFilePath: options.envFile,
|
|
44
84
|
imageTag: options.tag,
|
|
45
85
|
outputDirectory: options.out
|
|
@@ -50,21 +90,83 @@ export const registerLifecycleCommands = (program, handlers, streams) => {
|
|
|
50
90
|
}
|
|
51
91
|
});
|
|
52
92
|
program
|
|
53
|
-
.command("
|
|
93
|
+
.command("publish")
|
|
94
|
+
.description("Compile, build, verify, and push the organization image to a registry")
|
|
54
95
|
.argument("[path]", "Project directory or Spawnfile path", process.cwd())
|
|
96
|
+
.requiredOption("-t, --tag <image>", "Registry image reference to push")
|
|
97
|
+
.option("-o, --out <directory>", "Output directory")
|
|
98
|
+
.option("--docker-command <command>", "Docker command")
|
|
99
|
+
.action(async (inputPath, options) => {
|
|
100
|
+
const publishInput = resolveCommandInput(inputPath);
|
|
101
|
+
if (publishInput.kind !== "project") {
|
|
102
|
+
throw new SpawnfileError("validation_error", "publish operates on a project path, not an image reference");
|
|
103
|
+
}
|
|
104
|
+
const result = await handlers.publishProject(inputPath, {
|
|
105
|
+
dockerCommand: options.dockerCommand,
|
|
106
|
+
imageTag: options.tag,
|
|
107
|
+
outputDirectory: options.out
|
|
108
|
+
});
|
|
109
|
+
streams.stdout(`published ${result.imageTag}`);
|
|
110
|
+
streams.stdout(`digest: ${result.digest ?? "unknown"}`);
|
|
111
|
+
streams.stdout(`next: spawnfile up ${result.imageTag} --detach --auth-profile <profile>`);
|
|
112
|
+
});
|
|
113
|
+
program
|
|
114
|
+
.command("up")
|
|
115
|
+
.description("Deploy an organization from a project or a published image reference")
|
|
116
|
+
.argument("[path]", "Project directory, Spawnfile path, or image reference", process.cwd())
|
|
55
117
|
.option("-o, --out <directory>", "Output directory")
|
|
56
118
|
.option("-t, --tag <image>", "Docker image tag")
|
|
57
119
|
.option("--auth-profile <name>", "Local Spawnfile auth profile")
|
|
120
|
+
.option("--context <name>", "Docker context for the deployment target")
|
|
121
|
+
.option("--deployment <name>", "Detached deployment record name")
|
|
58
122
|
.option("--docker-command <command>", "Docker command")
|
|
59
123
|
.option("--name <container>", "Docker container name")
|
|
60
124
|
.option("--env-file <file>", "Path to an env file for runtime secrets")
|
|
125
|
+
.option("--image", "Interpret the argument as an image reference")
|
|
126
|
+
.option("--pull", "Pull the image before deploying")
|
|
61
127
|
.option("-d, --detach", "Run the container in detached mode")
|
|
62
128
|
.action(async (inputPath, options) => {
|
|
129
|
+
const upInput = resolveCommandInput(inputPath, { forceImage: options.image });
|
|
130
|
+
if (upInput.kind === "invalid") {
|
|
131
|
+
throw new SpawnfileError("validation_error", `Cannot resolve "${inputPath}" as a project path or image reference. ` +
|
|
132
|
+
"Pass a project directory, a Spawnfile path, or an image like 'name:tag' (use --image to force image mode).");
|
|
133
|
+
}
|
|
134
|
+
if (upInput.kind === "image") {
|
|
135
|
+
const [authProfile, envFileEnv] = await Promise.all([
|
|
136
|
+
resolveAuthProfileForImage(handlers, options.authProfile),
|
|
137
|
+
readRunEnvFile(options.envFile)
|
|
138
|
+
]);
|
|
139
|
+
const consumed = await handlers.consumeImageUp(upInput.ref, {
|
|
140
|
+
authProfile,
|
|
141
|
+
authProfileName: options.authProfile ?? null,
|
|
142
|
+
authValues: authProfile?.env ?? {},
|
|
143
|
+
deploymentName: options.deployment,
|
|
144
|
+
dockerCommand: options.dockerCommand,
|
|
145
|
+
dockerContext: options.context,
|
|
146
|
+
envFileEnv,
|
|
147
|
+
envFilePath: options.envFile ?? null,
|
|
148
|
+
pull: options.pull
|
|
149
|
+
});
|
|
150
|
+
if (consumed.previous) {
|
|
151
|
+
const newDigest = consumed.record.source.kind === "image" ? consumed.record.source.digest : null;
|
|
152
|
+
streams.stdout(formatRedeploySummary(consumed.previous, consumed.imageRef, newDigest));
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
streams.stdout(`deployed image ${consumed.imageRef}`);
|
|
156
|
+
}
|
|
157
|
+
streams.stdout(`deployment: ${consumed.deploymentName}`);
|
|
158
|
+
streams.stdout(`running container ${consumed.containerName}`);
|
|
159
|
+
streams.stdout(`record: ${consumed.recordPath}`);
|
|
160
|
+
streams.stdout(`next: spawnfile status --deployment ${consumed.deploymentName} --live`);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
63
163
|
const result = await handlers.upProject(inputPath, {
|
|
64
164
|
authProfile: options.authProfile,
|
|
65
165
|
containerName: options.name,
|
|
66
166
|
detach: options.detach,
|
|
167
|
+
deploymentName: options.deployment,
|
|
67
168
|
dockerCommand: options.dockerCommand,
|
|
169
|
+
dockerContext: options.context,
|
|
68
170
|
envFilePath: options.envFile,
|
|
69
171
|
imageTag: options.tag,
|
|
70
172
|
outputDirectory: options.out
|
|
@@ -4,6 +4,7 @@ export const registerModelCommands = (program, handlers, streams) => {
|
|
|
4
4
|
.description("Edit primary and fallback model declarations");
|
|
5
5
|
modelCommand
|
|
6
6
|
.command("set")
|
|
7
|
+
.description("Set an agent's primary model")
|
|
7
8
|
.argument("<provider>", "Model provider")
|
|
8
9
|
.argument("<name>", "Model name")
|
|
9
10
|
.argument("[path]", "Project directory or Spawnfile path", process.cwd())
|
|
@@ -29,6 +30,7 @@ export const registerModelCommands = (program, handlers, streams) => {
|
|
|
29
30
|
});
|
|
30
31
|
modelCommand
|
|
31
32
|
.command("add-fallback")
|
|
33
|
+
.description("Append a fallback model to an agent")
|
|
32
34
|
.argument("<provider>", "Model provider")
|
|
33
35
|
.argument("<name>", "Model name")
|
|
34
36
|
.argument("[path]", "Project directory or Spawnfile path", process.cwd())
|
|
@@ -54,6 +56,7 @@ export const registerModelCommands = (program, handlers, streams) => {
|
|
|
54
56
|
});
|
|
55
57
|
modelCommand
|
|
56
58
|
.command("clear-fallbacks")
|
|
59
|
+
.description("Remove all fallback models from an agent")
|
|
57
60
|
.argument("[path]", "Project directory or Spawnfile path", process.cwd())
|
|
58
61
|
.option("--recursive", "Update the target project and its descendants")
|
|
59
62
|
.action(async (inputPath, options) => {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type CommandInput = {
|
|
2
|
+
kind: "project";
|
|
3
|
+
path: string;
|
|
4
|
+
} | {
|
|
5
|
+
kind: "image";
|
|
6
|
+
ref: string;
|
|
7
|
+
} | {
|
|
8
|
+
kind: "invalid";
|
|
9
|
+
value: string;
|
|
10
|
+
};
|
|
11
|
+
export interface ResolveCommandInputOptions {
|
|
12
|
+
forceImage?: boolean;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Resolves a positional argument that may be a project path or an image
|
|
16
|
+
* reference. An existing directory or file always wins unless --image forces
|
|
17
|
+
* image mode. Implicit image refs require a tag, digest, or registry component.
|
|
18
|
+
*/
|
|
19
|
+
export declare const resolveCommandInput: (value: string, options?: ResolveCommandInputOptions) => CommandInput;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { parseImplicitImageReference } from "../distribution/index.js";
|
|
4
|
+
/**
|
|
5
|
+
* Resolves a positional argument that may be a project path or an image
|
|
6
|
+
* reference. An existing directory or file always wins unless --image forces
|
|
7
|
+
* image mode. Implicit image refs require a tag, digest, or registry component.
|
|
8
|
+
*/
|
|
9
|
+
export const resolveCommandInput = (value, options = {}) => {
|
|
10
|
+
if (!options.forceImage) {
|
|
11
|
+
const resolved = path.resolve(value);
|
|
12
|
+
if (existsSync(resolved)) {
|
|
13
|
+
return { kind: "project", path: value };
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
if (options.forceImage) {
|
|
17
|
+
// --image accepts Docker's full reference grammar, including bare names.
|
|
18
|
+
return { kind: "image", ref: value };
|
|
19
|
+
}
|
|
20
|
+
const parsed = parseImplicitImageReference(value);
|
|
21
|
+
if (parsed) {
|
|
22
|
+
return { kind: "image", ref: value };
|
|
23
|
+
}
|
|
24
|
+
return { kind: "invalid", value };
|
|
25
|
+
};
|
package/dist/cli/runCli.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { importClaudeCodeAuth, importCodexAuth, importEnvFile, requireAuthProfile } from "../auth/index.js";
|
|
2
|
-
import { addAgentProject, addProjectSurface, addProjectModelFallback, addSubagentProject, addTeamProject, buildOrganizationView, buildCompilePlan, buildProject, clearProjectModelFallbacks, compileProject, initProject, upProject, removeProjectSurface, runProject, setProjectPrimaryModel, setProjectRuntime, setProjectSurfaceAccess, showProjectSurfaces, syncProjectAuth } from "../compiler/index.js";
|
|
2
|
+
import { addAgentProject, addProjectSurface, addProjectModelFallback, addSubagentProject, addTeamProject, buildOrganizationView, buildCompilePlan, buildProject, clearProjectModelFallbacks, compileProject, initProject, publishProject, upProject, removeProjectSurface, runProject, setProjectPrimaryModel, setProjectRuntime, setProjectSurfaceAccess, showProjectSurfaces, syncProjectAuth } from "../compiler/index.js";
|
|
3
|
+
import { devActivityProject, devApplyProject, devRestartProject, devStopProject, devUpProject } from "../dev/index.js";
|
|
4
|
+
import { consumeImageUp } from "../distribution/index.js";
|
|
3
5
|
import { listRuntimeAdapters } from "../runtime/index.js";
|
|
4
6
|
export interface CliStreams {
|
|
5
7
|
stderr: (message: string) => void;
|
|
@@ -15,6 +17,7 @@ export interface CliHandlers {
|
|
|
15
17
|
buildOrganizationView: typeof buildOrganizationView;
|
|
16
18
|
buildProject: typeof buildProject;
|
|
17
19
|
compileProject: typeof compileProject;
|
|
20
|
+
publishProject: typeof publishProject;
|
|
18
21
|
addAgentProject: typeof addAgentProject;
|
|
19
22
|
addProjectModelFallback: typeof addProjectModelFallback;
|
|
20
23
|
addProjectSurface: typeof addProjectSurface;
|
|
@@ -30,6 +33,12 @@ export interface CliHandlers {
|
|
|
30
33
|
requireAuthProfile: typeof requireAuthProfile;
|
|
31
34
|
runProject: typeof runProject;
|
|
32
35
|
upProject: typeof upProject;
|
|
36
|
+
consumeImageUp: typeof consumeImageUp;
|
|
37
|
+
devActivityProject: typeof devActivityProject;
|
|
38
|
+
devApplyProject: typeof devApplyProject;
|
|
39
|
+
devRestartProject: typeof devRestartProject;
|
|
40
|
+
devStopProject: typeof devStopProject;
|
|
41
|
+
devUpProject: typeof devUpProject;
|
|
33
42
|
setProjectPrimaryModel: typeof setProjectPrimaryModel;
|
|
34
43
|
setProjectRuntime: typeof setProjectRuntime;
|
|
35
44
|
setProjectSurfaceAccess: typeof setProjectSurfaceAccess;
|
package/dist/cli/runCli.js
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { Command } from "commander";
|
|
3
3
|
import { importClaudeCodeAuth, importCodexAuth, importEnvFile, requireAuthProfile } from "../auth/index.js";
|
|
4
|
-
import { addAgentProject, addProjectSurface, addProjectModelFallback, addSubagentProject, addTeamProject, buildOrganizationView, buildCompilePlan, buildProject, clearProjectModelFallbacks, compileProject, initProject, upProject, removeProjectSurface, runProject, setProjectPrimaryModel, setProjectRuntime, setProjectSurfaceAccess, showProjectSurfaces, syncProjectAuth } from "../compiler/index.js";
|
|
5
|
-
import {
|
|
4
|
+
import { addAgentProject, addProjectSurface, addProjectModelFallback, addSubagentProject, addTeamProject, buildOrganizationView, buildCompilePlan, buildProject, clearProjectModelFallbacks, compileProject, initProject, publishProject, upProject, removeProjectSurface, runProject, setProjectPrimaryModel, setProjectRuntime, setProjectSurfaceAccess, showProjectSurfaces, syncProjectAuth } from "../compiler/index.js";
|
|
5
|
+
import { devActivityProject, devApplyProject, devRestartProject, devStopProject, devUpProject } from "../dev/index.js";
|
|
6
|
+
import { consumeImageUp } from "../distribution/index.js";
|
|
7
|
+
import { errorExitCode, isSpawnfileError } from "../shared/index.js";
|
|
6
8
|
import { listRuntimeAdapters } from "../runtime/index.js";
|
|
9
|
+
import { registerDevCommands } from "./devCommands.js";
|
|
7
10
|
import { registerLifecycleCommands } from "./lifecycleCommands.js";
|
|
8
11
|
import { registerModelCommands } from "./modelCommands.js";
|
|
9
12
|
import { registerRuntimeCommands } from "./runtimeCommands.js";
|
|
10
13
|
import { registerSurfaceCommands } from "./surfaceCommands.js";
|
|
14
|
+
import { registerStatusCommand } from "./statusCommand.js";
|
|
11
15
|
import { registerViewCommand } from "./viewCommand.js";
|
|
12
16
|
const packageJsonPath = new URL("../../package.json", import.meta.url);
|
|
13
17
|
const readPackageVersion = () => {
|
|
@@ -24,12 +28,13 @@ const createDefaultRenderEnvironment = () => ({
|
|
|
24
28
|
stdoutIsTty: process.stdout.isTTY === true
|
|
25
29
|
});
|
|
26
30
|
const createDefaultHandlers = () => ({
|
|
27
|
-
buildCompilePlan, buildOrganizationView, buildProject, compileProject,
|
|
31
|
+
buildCompilePlan, buildOrganizationView, buildProject, compileProject, publishProject,
|
|
28
32
|
addAgentProject, addProjectModelFallback, addProjectSurface,
|
|
29
33
|
addSubagentProject, addTeamProject, clearProjectModelFallbacks,
|
|
30
34
|
importClaudeCodeAuth, importCodexAuth, importEnvFile,
|
|
31
35
|
initProject, listRuntimeAdapters, removeProjectSurface, requireAuthProfile,
|
|
32
|
-
runProject, setProjectPrimaryModel, setProjectRuntime, upProject,
|
|
36
|
+
runProject, setProjectPrimaryModel, setProjectRuntime, upProject, consumeImageUp,
|
|
37
|
+
devActivityProject, devApplyProject, devRestartProject, devStopProject, devUpProject,
|
|
33
38
|
setProjectSurfaceAccess, showProjectSurfaces, syncProjectAuth
|
|
34
39
|
});
|
|
35
40
|
const isCliStreams = (value) => {
|
|
@@ -62,6 +67,16 @@ const isCommanderError = (error) => {
|
|
|
62
67
|
&& candidate.code.startsWith("commander.")
|
|
63
68
|
&& typeof candidate.exitCode === "number";
|
|
64
69
|
};
|
|
70
|
+
const formatCliErrorMessage = (error) => {
|
|
71
|
+
if (isSpawnfileError(error)) {
|
|
72
|
+
// Surface the human message only; the internal code is not user-facing.
|
|
73
|
+
return error.message;
|
|
74
|
+
}
|
|
75
|
+
if (error instanceof Error) {
|
|
76
|
+
return error.message;
|
|
77
|
+
}
|
|
78
|
+
return String(error);
|
|
79
|
+
};
|
|
65
80
|
const formatPlanSummary = (plan) => [
|
|
66
81
|
`root: ${plan.root}`,
|
|
67
82
|
`nodes: ${plan.nodes.length}`,
|
|
@@ -82,6 +97,7 @@ export const runCli = async (argv, optionsOrStreams, handlerOverrides = {}) => {
|
|
|
82
97
|
const cliOptions = normalizeRunCliOptions(optionsOrStreams, handlerOverrides);
|
|
83
98
|
const streams = cliOptions.streams;
|
|
84
99
|
const handlers = { ...createDefaultHandlers(), ...cliOptions.handlers };
|
|
100
|
+
let commandExitCode = 0;
|
|
85
101
|
const program = new Command();
|
|
86
102
|
program.name("spawnfile").description("Spawnfile v0.1 compiler").version(readPackageVersion());
|
|
87
103
|
program.exitOverride();
|
|
@@ -91,8 +107,10 @@ export const runCli = async (argv, optionsOrStreams, handlerOverrides = {}) => {
|
|
|
91
107
|
writeOut: (message) => writeCommanderOutput(streams.stdout, message)
|
|
92
108
|
});
|
|
93
109
|
registerLifecycleCommands(program, handlers, streams);
|
|
110
|
+
registerDevCommands(program, handlers, streams);
|
|
94
111
|
program
|
|
95
112
|
.command("init")
|
|
113
|
+
.description("Scaffold a new Spawnfile project or team in a directory")
|
|
96
114
|
.argument("[path]", "Directory to initialize", process.cwd())
|
|
97
115
|
.option("--team", "Initialize a team project")
|
|
98
116
|
.option("--runtime <name>", "Runtime for agent scaffolds")
|
|
@@ -108,6 +126,7 @@ export const runCli = async (argv, optionsOrStreams, handlerOverrides = {}) => {
|
|
|
108
126
|
const addCommand = program.command("add").description("Add children to an existing Spawnfile project");
|
|
109
127
|
addCommand
|
|
110
128
|
.command("agent")
|
|
129
|
+
.description("Add an agent member to a team project")
|
|
111
130
|
.argument("<id>", "Agent member id")
|
|
112
131
|
.argument("[path]", "Team project directory or Spawnfile path", process.cwd())
|
|
113
132
|
.option("--runtime <name>", "Runtime for the new agent member")
|
|
@@ -122,6 +141,7 @@ export const runCli = async (argv, optionsOrStreams, handlerOverrides = {}) => {
|
|
|
122
141
|
});
|
|
123
142
|
addCommand
|
|
124
143
|
.command("subagent")
|
|
144
|
+
.description("Add a subagent to an agent project")
|
|
125
145
|
.argument("<id>", "Subagent id")
|
|
126
146
|
.argument("[path]", "Agent project directory or Spawnfile path", process.cwd())
|
|
127
147
|
.action(async (id, inputPath) => {
|
|
@@ -134,6 +154,7 @@ export const runCli = async (argv, optionsOrStreams, handlerOverrides = {}) => {
|
|
|
134
154
|
});
|
|
135
155
|
addCommand
|
|
136
156
|
.command("team")
|
|
157
|
+
.description("Add a nested team to a team project")
|
|
137
158
|
.argument("<id>", "Nested team id")
|
|
138
159
|
.argument("[path]", "Team project directory or Spawnfile path", process.cwd())
|
|
139
160
|
.action(async (id, inputPath) => {
|
|
@@ -147,9 +168,13 @@ export const runCli = async (argv, optionsOrStreams, handlerOverrides = {}) => {
|
|
|
147
168
|
registerModelCommands(program, handlers, streams);
|
|
148
169
|
registerRuntimeCommands(program, handlers, streams);
|
|
149
170
|
registerSurfaceCommands(program, handlers, streams);
|
|
171
|
+
registerStatusCommand(program, handlers, streams, (exitCode) => {
|
|
172
|
+
commandExitCode = exitCode;
|
|
173
|
+
});
|
|
150
174
|
registerViewCommand(program, handlers, streams, cliOptions.renderEnvironment);
|
|
151
175
|
program
|
|
152
176
|
.command("validate")
|
|
177
|
+
.description("Validate a Spawnfile and report the resolved compile plan")
|
|
153
178
|
.argument("[path]", "Project directory or Spawnfile path", process.cwd())
|
|
154
179
|
.action(async (inputPath) => {
|
|
155
180
|
const plan = await handlers.buildCompilePlan(inputPath);
|
|
@@ -170,6 +195,7 @@ export const runCli = async (argv, optionsOrStreams, handlerOverrides = {}) => {
|
|
|
170
195
|
.description("Import auth material into a local auth profile");
|
|
171
196
|
authImportCommand
|
|
172
197
|
.command("env")
|
|
198
|
+
.description("Import secrets from an env file into a profile")
|
|
173
199
|
.argument("<file>", "Path to an env file")
|
|
174
200
|
.option("-p, --profile <name>", "Auth profile name", "default")
|
|
175
201
|
.action(async (filePath, options) => {
|
|
@@ -178,6 +204,7 @@ export const runCli = async (argv, optionsOrStreams, handlerOverrides = {}) => {
|
|
|
178
204
|
});
|
|
179
205
|
authImportCommand
|
|
180
206
|
.command("claude-code")
|
|
207
|
+
.description("Import Claude Code subscription credentials into a profile")
|
|
181
208
|
.option("-p, --profile <name>", "Auth profile name", "default")
|
|
182
209
|
.option("--from <directory>", "Source Claude Code config directory")
|
|
183
210
|
.action(async (options) => {
|
|
@@ -186,6 +213,7 @@ export const runCli = async (argv, optionsOrStreams, handlerOverrides = {}) => {
|
|
|
186
213
|
});
|
|
187
214
|
authImportCommand
|
|
188
215
|
.command("codex")
|
|
216
|
+
.description("Import Codex subscription credentials into a profile")
|
|
189
217
|
.option("-p, --profile <name>", "Auth profile name", "default")
|
|
190
218
|
.option("--from <directory>", "Source Codex config directory")
|
|
191
219
|
.action(async (options) => {
|
|
@@ -194,6 +222,7 @@ export const runCli = async (argv, optionsOrStreams, handlerOverrides = {}) => {
|
|
|
194
222
|
});
|
|
195
223
|
authCommand
|
|
196
224
|
.command("sync")
|
|
225
|
+
.description("Provision auth material a project requires into a profile")
|
|
197
226
|
.argument("[path]", "Project directory or Spawnfile path", process.cwd())
|
|
198
227
|
.option("-p, --profile <name>", "Auth profile name", "default")
|
|
199
228
|
.option("--env-file <file>", "Path to an env file with model keys and runtime secrets")
|
|
@@ -210,6 +239,7 @@ export const runCli = async (argv, optionsOrStreams, handlerOverrides = {}) => {
|
|
|
210
239
|
});
|
|
211
240
|
authCommand
|
|
212
241
|
.command("show")
|
|
242
|
+
.description("Show the contents of a local auth profile")
|
|
213
243
|
.option("-p, --profile <name>", "Auth profile name", "default")
|
|
214
244
|
.action(async (options) => {
|
|
215
245
|
const profile = await handlers.requireAuthProfile(options.profile);
|
|
@@ -217,18 +247,16 @@ export const runCli = async (argv, optionsOrStreams, handlerOverrides = {}) => {
|
|
|
217
247
|
});
|
|
218
248
|
try {
|
|
219
249
|
await program.parseAsync(argv, { from: "user" });
|
|
220
|
-
return
|
|
250
|
+
return commandExitCode;
|
|
221
251
|
}
|
|
222
252
|
catch (error) {
|
|
223
253
|
if (isCommanderError(error)) {
|
|
224
|
-
|
|
254
|
+
// Commander prints its own usage message; usage errors exit 2.
|
|
255
|
+
return error.exitCode === 0 ? 0 : 2;
|
|
225
256
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
: String(error);
|
|
231
|
-
streams.stderr(message);
|
|
232
|
-
return 1;
|
|
257
|
+
streams.stderr(`error: ${formatCliErrorMessage(error)}`);
|
|
258
|
+
// Usage/input errors exit 2; runtime failures exit 1, matching the
|
|
259
|
+
// documented status exit-code contract across all commands.
|
|
260
|
+
return errorExitCode(error);
|
|
233
261
|
}
|
|
234
262
|
};
|