spawnfile 0.1.2 → 0.1.4
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 +3 -0
- package/dist/cli/index.js +0 -0
- package/dist/cli/runCli.d.ts +18 -2
- package/dist/cli/runCli.js +75 -62
- package/dist/cli/viewCommand.d.ts +3 -0
- package/dist/cli/viewCommand.js +87 -0
- package/dist/compiler/buildCompilePlan.js +2 -0
- package/dist/compiler/containerEntrypointRender.js +3 -3
- package/dist/compiler/index.d.ts +2 -0
- package/dist/compiler/index.js +2 -0
- package/dist/compiler/moltnetArtifacts.js +7 -3
- package/dist/compiler/moltnetResolution.js +29 -48
- package/dist/compiler/moltnetRoomMemberships.d.ts +3 -0
- package/dist/compiler/moltnetRoomMemberships.js +140 -0
- package/dist/compiler/runProject.d.ts +2 -0
- package/dist/compiler/runProject.js +20 -6
- package/dist/compiler/syncProjectAuth.js +53 -19
- package/dist/compiler/types.d.ts +18 -0
- package/dist/compiler/view/buildOrganizationView.d.ts +2 -0
- package/dist/compiler/view/buildOrganizationView.js +180 -0
- package/dist/compiler/view/index.d.ts +4 -0
- package/dist/compiler/view/index.js +4 -0
- package/dist/compiler/view/renderNetworks.d.ts +2 -0
- package/dist/compiler/view/renderNetworks.js +93 -0
- package/dist/compiler/view/renderTree.d.ts +2 -0
- package/dist/compiler/view/renderTree.js +59 -0
- package/dist/compiler/view/sourcePaths.d.ts +2 -0
- package/dist/compiler/view/sourcePaths.js +19 -0
- package/dist/compiler/view/types.d.ts +80 -0
- package/dist/compiler/view/types.js +1 -0
- package/dist/runtime/picoclaw/adapter.js +7 -0
- package/dist/runtime/picoclaw/runAuth.js +5 -41
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -34,6 +34,7 @@ Node.js 22+ required. See [source install](#from-source) for local development.
|
|
|
34
34
|
```bash
|
|
35
35
|
spawnfile init # scaffold an agent (defaults to openclaw)
|
|
36
36
|
spawnfile validate # check the graph
|
|
37
|
+
spawnfile view . # read-only graph view; writes no files
|
|
37
38
|
spawnfile compile # lower to runtime-native output
|
|
38
39
|
spawnfile auth sync --profile dev --env-file .env
|
|
39
40
|
spawnfile build --tag my-agent # compile + docker build
|
|
@@ -42,6 +43,8 @@ spawnfile run --tag my-agent --auth-profile dev
|
|
|
42
43
|
|
|
43
44
|
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.
|
|
44
45
|
|
|
46
|
+
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.
|
|
47
|
+
|
|
45
48
|
## Project structure
|
|
46
49
|
|
|
47
50
|
A Spawnfile project is either an `agent` or a `team`.
|
package/dist/cli/index.js
CHANGED
|
File without changes
|
package/dist/cli/runCli.d.ts
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import { importClaudeCodeAuth, importCodexAuth, importEnvFile, requireAuthProfile } from "../auth/index.js";
|
|
2
|
-
import { addAgentProject, addProjectSurface, addProjectModelFallback, addSubagentProject, addTeamProject, buildCompilePlan, buildProject, clearProjectModelFallbacks, compileProject, initProject, removeProjectSurface, runProject, setProjectPrimaryModel, setProjectRuntime, setProjectSurfaceAccess, showProjectSurfaces, syncProjectAuth } from "../compiler/index.js";
|
|
2
|
+
import { addAgentProject, addProjectSurface, addProjectModelFallback, addSubagentProject, addTeamProject, buildOrganizationView, buildCompilePlan, buildProject, clearProjectModelFallbacks, compileProject, initProject, removeProjectSurface, runProject, setProjectPrimaryModel, setProjectRuntime, setProjectSurfaceAccess, showProjectSurfaces, syncProjectAuth } from "../compiler/index.js";
|
|
3
3
|
import { listRuntimeAdapters } from "../runtime/index.js";
|
|
4
4
|
export interface CliStreams {
|
|
5
5
|
stderr: (message: string) => void;
|
|
6
6
|
stdout: (message: string) => void;
|
|
7
7
|
}
|
|
8
|
+
export interface CliRenderEnvironment {
|
|
9
|
+
ci: boolean;
|
|
10
|
+
noColor: boolean;
|
|
11
|
+
stdoutIsTty: boolean;
|
|
12
|
+
}
|
|
8
13
|
export interface CliHandlers {
|
|
9
14
|
buildCompilePlan: typeof buildCompilePlan;
|
|
15
|
+
buildOrganizationView: typeof buildOrganizationView;
|
|
10
16
|
buildProject: typeof buildProject;
|
|
11
17
|
compileProject: typeof compileProject;
|
|
12
18
|
addAgentProject: typeof addAgentProject;
|
|
@@ -29,4 +35,14 @@ export interface CliHandlers {
|
|
|
29
35
|
showProjectSurfaces: typeof showProjectSurfaces;
|
|
30
36
|
syncProjectAuth: typeof syncProjectAuth;
|
|
31
37
|
}
|
|
32
|
-
export
|
|
38
|
+
export interface RunCliOptions {
|
|
39
|
+
handlers?: Partial<CliHandlers>;
|
|
40
|
+
renderEnvironment?: CliRenderEnvironment;
|
|
41
|
+
streams?: CliStreams;
|
|
42
|
+
}
|
|
43
|
+
type RunCli = {
|
|
44
|
+
(argv: string[], options?: RunCliOptions): Promise<number>;
|
|
45
|
+
(argv: string[], streams?: CliStreams, handlerOverrides?: Partial<CliHandlers>): Promise<number>;
|
|
46
|
+
};
|
|
47
|
+
export declare const runCli: RunCli;
|
|
48
|
+
export {};
|
package/dist/cli/runCli.js
CHANGED
|
@@ -1,39 +1,60 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import { importClaudeCodeAuth, importCodexAuth, importEnvFile, requireAuthProfile } from "../auth/index.js";
|
|
3
|
-
import { addAgentProject, addProjectSurface, addProjectModelFallback, addSubagentProject, addTeamProject, buildCompilePlan, buildProject, clearProjectModelFallbacks, compileProject, initProject, removeProjectSurface, runProject, setProjectPrimaryModel, setProjectRuntime, setProjectSurfaceAccess, showProjectSurfaces, syncProjectAuth } from "../compiler/index.js";
|
|
3
|
+
import { addAgentProject, addProjectSurface, addProjectModelFallback, addSubagentProject, addTeamProject, buildOrganizationView, buildCompilePlan, buildProject, clearProjectModelFallbacks, compileProject, initProject, removeProjectSurface, runProject, setProjectPrimaryModel, setProjectRuntime, setProjectSurfaceAccess, showProjectSurfaces, syncProjectAuth } from "../compiler/index.js";
|
|
4
4
|
import { isSpawnfileError } from "../shared/index.js";
|
|
5
5
|
import { listRuntimeAdapters } from "../runtime/index.js";
|
|
6
6
|
import { registerModelCommands } from "./modelCommands.js";
|
|
7
7
|
import { registerRuntimeCommands } from "./runtimeCommands.js";
|
|
8
8
|
import { registerSurfaceCommands } from "./surfaceCommands.js";
|
|
9
|
+
import { registerViewCommand } from "./viewCommand.js";
|
|
9
10
|
const createDefaultStreams = () => ({
|
|
10
11
|
stderr: (message) => process.stderr.write(`${message}\n`),
|
|
11
12
|
stdout: (message) => process.stdout.write(`${message}\n`)
|
|
12
13
|
});
|
|
14
|
+
const createDefaultRenderEnvironment = () => ({
|
|
15
|
+
ci: process.env.CI !== undefined && process.env.CI !== "" && process.env.CI !== "0",
|
|
16
|
+
noColor: process.env.NO_COLOR !== undefined && process.env.NO_COLOR !== "",
|
|
17
|
+
stdoutIsTty: process.stdout.isTTY === true
|
|
18
|
+
});
|
|
13
19
|
const createDefaultHandlers = () => ({
|
|
14
|
-
buildCompilePlan,
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
addTeamProject,
|
|
22
|
-
clearProjectModelFallbacks,
|
|
23
|
-
importClaudeCodeAuth,
|
|
24
|
-
importCodexAuth,
|
|
25
|
-
importEnvFile,
|
|
26
|
-
initProject,
|
|
27
|
-
listRuntimeAdapters,
|
|
28
|
-
removeProjectSurface,
|
|
29
|
-
requireAuthProfile,
|
|
30
|
-
runProject,
|
|
31
|
-
setProjectPrimaryModel,
|
|
32
|
-
setProjectRuntime,
|
|
33
|
-
setProjectSurfaceAccess,
|
|
34
|
-
showProjectSurfaces,
|
|
35
|
-
syncProjectAuth
|
|
20
|
+
buildCompilePlan, buildOrganizationView, buildProject, compileProject,
|
|
21
|
+
addAgentProject, addProjectModelFallback, addProjectSurface,
|
|
22
|
+
addSubagentProject, addTeamProject, clearProjectModelFallbacks,
|
|
23
|
+
importClaudeCodeAuth, importCodexAuth, importEnvFile,
|
|
24
|
+
initProject, listRuntimeAdapters, removeProjectSurface, requireAuthProfile,
|
|
25
|
+
runProject, setProjectPrimaryModel, setProjectRuntime,
|
|
26
|
+
setProjectSurfaceAccess, showProjectSurfaces, syncProjectAuth
|
|
36
27
|
});
|
|
28
|
+
const isCliStreams = (value) => {
|
|
29
|
+
const candidate = value;
|
|
30
|
+
return typeof candidate?.stderr === "function" && typeof candidate.stdout === "function";
|
|
31
|
+
};
|
|
32
|
+
const normalizeRunCliOptions = (optionsOrStreams, handlerOverrides = {}) => isCliStreams(optionsOrStreams)
|
|
33
|
+
? {
|
|
34
|
+
handlers: handlerOverrides,
|
|
35
|
+
renderEnvironment: createDefaultRenderEnvironment(),
|
|
36
|
+
streams: optionsOrStreams
|
|
37
|
+
}
|
|
38
|
+
: {
|
|
39
|
+
handlers: optionsOrStreams?.handlers ?? handlerOverrides,
|
|
40
|
+
renderEnvironment: optionsOrStreams?.renderEnvironment ?? createDefaultRenderEnvironment(),
|
|
41
|
+
streams: optionsOrStreams?.streams ?? createDefaultStreams()
|
|
42
|
+
};
|
|
43
|
+
const writeCommanderOutput = (write, message) => {
|
|
44
|
+
const normalized = message.replace(/\n$/, "");
|
|
45
|
+
if (normalized.length > 0) {
|
|
46
|
+
write(normalized);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
const isCommanderError = (error) => {
|
|
50
|
+
if (typeof error !== "object" || error === null) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
const candidate = error;
|
|
54
|
+
return typeof candidate.code === "string"
|
|
55
|
+
&& candidate.code.startsWith("commander.")
|
|
56
|
+
&& typeof candidate.exitCode === "number";
|
|
57
|
+
};
|
|
37
58
|
const formatPlanSummary = (plan) => [
|
|
38
59
|
`root: ${plan.root}`,
|
|
39
60
|
`nodes: ${plan.nodes.length}`,
|
|
@@ -48,10 +69,20 @@ const formatAuthProfileSummary = (profile) => {
|
|
|
48
69
|
`imports: ${importedKinds.length > 0 ? importedKinds.join(", ") : "none"}`
|
|
49
70
|
];
|
|
50
71
|
};
|
|
51
|
-
|
|
52
|
-
|
|
72
|
+
const emitLines = (streams, lines) => lines.forEach((line) => streams.stdout(line));
|
|
73
|
+
const emitFileLines = (streams, label, filePaths) => emitLines(streams, filePaths.map((filePath) => `${label} ${filePath}`));
|
|
74
|
+
export const runCli = async (argv, optionsOrStreams, handlerOverrides = {}) => {
|
|
75
|
+
const cliOptions = normalizeRunCliOptions(optionsOrStreams, handlerOverrides);
|
|
76
|
+
const streams = cliOptions.streams;
|
|
77
|
+
const handlers = { ...createDefaultHandlers(), ...cliOptions.handlers };
|
|
53
78
|
const program = new Command();
|
|
54
79
|
program.name("spawnfile").description("Spawnfile v0.1 compiler");
|
|
80
|
+
program.exitOverride();
|
|
81
|
+
program.configureOutput({
|
|
82
|
+
outputError: (message, write) => write(message),
|
|
83
|
+
writeErr: (message) => writeCommanderOutput(streams.stderr, message),
|
|
84
|
+
writeOut: (message) => writeCommanderOutput(streams.stdout, message)
|
|
85
|
+
});
|
|
55
86
|
program
|
|
56
87
|
.command("compile")
|
|
57
88
|
.argument("[path]", "Project directory or Spawnfile path", process.cwd())
|
|
@@ -82,12 +113,14 @@ export const runCli = async (argv, streams = createDefaultStreams(), handlerOver
|
|
|
82
113
|
.option("-t, --tag <image>", "Docker image tag")
|
|
83
114
|
.option("--auth-profile <name>", "Local Spawnfile auth profile")
|
|
84
115
|
.option("--name <container>", "Docker container name")
|
|
116
|
+
.option("--env-file <file>", "Path to an env file for runtime secrets")
|
|
85
117
|
.option("-d, --detach", "Run the container in detached mode")
|
|
86
118
|
.action(async (inputPath, options) => {
|
|
87
119
|
const result = await handlers.runProject(inputPath, {
|
|
88
120
|
authProfile: options.authProfile,
|
|
89
121
|
containerName: options.name,
|
|
90
122
|
detach: options.detach,
|
|
123
|
+
envFilePath: options.envFile,
|
|
91
124
|
imageTag: options.tag,
|
|
92
125
|
outputDirectory: options.out
|
|
93
126
|
});
|
|
@@ -108,9 +141,7 @@ export const runCli = async (argv, streams = createDefaultStreams(), handlerOver
|
|
|
108
141
|
team: options.team
|
|
109
142
|
});
|
|
110
143
|
streams.stdout(`initialized ${result.directory}`);
|
|
111
|
-
|
|
112
|
-
streams.stdout(`created ${filePath}`);
|
|
113
|
-
}
|
|
144
|
+
emitFileLines(streams, "created", result.createdFiles);
|
|
114
145
|
});
|
|
115
146
|
const addCommand = program.command("add").description("Add children to an existing Spawnfile project");
|
|
116
147
|
addCommand
|
|
@@ -124,12 +155,8 @@ export const runCli = async (argv, streams = createDefaultStreams(), handlerOver
|
|
|
124
155
|
path: inputPath,
|
|
125
156
|
runtime: options.runtime
|
|
126
157
|
});
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
}
|
|
130
|
-
for (const filePath of result.createdFiles) {
|
|
131
|
-
streams.stdout(`created ${filePath}`);
|
|
132
|
-
}
|
|
158
|
+
emitFileLines(streams, "updated", result.updatedFiles);
|
|
159
|
+
emitFileLines(streams, "created", result.createdFiles);
|
|
133
160
|
});
|
|
134
161
|
addCommand
|
|
135
162
|
.command("subagent")
|
|
@@ -140,12 +167,8 @@ export const runCli = async (argv, streams = createDefaultStreams(), handlerOver
|
|
|
140
167
|
id,
|
|
141
168
|
path: inputPath
|
|
142
169
|
});
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
}
|
|
146
|
-
for (const filePath of result.createdFiles) {
|
|
147
|
-
streams.stdout(`created ${filePath}`);
|
|
148
|
-
}
|
|
170
|
+
emitFileLines(streams, "updated", result.updatedFiles);
|
|
171
|
+
emitFileLines(streams, "created", result.createdFiles);
|
|
149
172
|
});
|
|
150
173
|
addCommand
|
|
151
174
|
.command("team")
|
|
@@ -156,16 +179,13 @@ export const runCli = async (argv, streams = createDefaultStreams(), handlerOver
|
|
|
156
179
|
id,
|
|
157
180
|
path: inputPath
|
|
158
181
|
});
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
}
|
|
162
|
-
for (const filePath of result.createdFiles) {
|
|
163
|
-
streams.stdout(`created ${filePath}`);
|
|
164
|
-
}
|
|
182
|
+
emitFileLines(streams, "updated", result.updatedFiles);
|
|
183
|
+
emitFileLines(streams, "created", result.createdFiles);
|
|
165
184
|
});
|
|
166
185
|
registerModelCommands(program, handlers, streams);
|
|
167
186
|
registerRuntimeCommands(program, handlers, streams);
|
|
168
187
|
registerSurfaceCommands(program, handlers, streams);
|
|
188
|
+
registerViewCommand(program, handlers, streams, cliOptions.renderEnvironment);
|
|
169
189
|
program
|
|
170
190
|
.command("validate")
|
|
171
191
|
.argument("[path]", "Project directory or Spawnfile path", process.cwd())
|
|
@@ -192,9 +212,7 @@ export const runCli = async (argv, streams = createDefaultStreams(), handlerOver
|
|
|
192
212
|
.option("-p, --profile <name>", "Auth profile name", "default")
|
|
193
213
|
.action(async (filePath, options) => {
|
|
194
214
|
const profile = await handlers.importEnvFile(options.profile, filePath);
|
|
195
|
-
|
|
196
|
-
streams.stdout(line);
|
|
197
|
-
}
|
|
215
|
+
emitLines(streams, formatAuthProfileSummary(profile));
|
|
198
216
|
});
|
|
199
217
|
authImportCommand
|
|
200
218
|
.command("claude-code")
|
|
@@ -202,9 +220,7 @@ export const runCli = async (argv, streams = createDefaultStreams(), handlerOver
|
|
|
202
220
|
.option("--from <directory>", "Source Claude Code config directory")
|
|
203
221
|
.action(async (options) => {
|
|
204
222
|
const profile = await handlers.importClaudeCodeAuth(options.profile, options.from);
|
|
205
|
-
|
|
206
|
-
streams.stdout(line);
|
|
207
|
-
}
|
|
223
|
+
emitLines(streams, formatAuthProfileSummary(profile));
|
|
208
224
|
});
|
|
209
225
|
authImportCommand
|
|
210
226
|
.command("codex")
|
|
@@ -212,15 +228,13 @@ export const runCli = async (argv, streams = createDefaultStreams(), handlerOver
|
|
|
212
228
|
.option("--from <directory>", "Source Codex config directory")
|
|
213
229
|
.action(async (options) => {
|
|
214
230
|
const profile = await handlers.importCodexAuth(options.profile, options.from);
|
|
215
|
-
|
|
216
|
-
streams.stdout(line);
|
|
217
|
-
}
|
|
231
|
+
emitLines(streams, formatAuthProfileSummary(profile));
|
|
218
232
|
});
|
|
219
233
|
authCommand
|
|
220
234
|
.command("sync")
|
|
221
235
|
.argument("[path]", "Project directory or Spawnfile path", process.cwd())
|
|
222
236
|
.option("-p, --profile <name>", "Auth profile name", "default")
|
|
223
|
-
.option("--env-file <file>", "Path to an env file with model
|
|
237
|
+
.option("--env-file <file>", "Path to an env file with model keys and runtime secrets")
|
|
224
238
|
.option("--claude-from <directory>", "Source Claude Code config directory")
|
|
225
239
|
.option("--codex-from <directory>", "Source Codex config directory")
|
|
226
240
|
.action(async (inputPath, options) => {
|
|
@@ -230,24 +244,23 @@ export const runCli = async (argv, streams = createDefaultStreams(), handlerOver
|
|
|
230
244
|
envFilePath: options.envFile,
|
|
231
245
|
profileName: options.profile
|
|
232
246
|
});
|
|
233
|
-
|
|
234
|
-
streams.stdout(line);
|
|
235
|
-
}
|
|
247
|
+
emitLines(streams, formatAuthProfileSummary(profile));
|
|
236
248
|
});
|
|
237
249
|
authCommand
|
|
238
250
|
.command("show")
|
|
239
251
|
.option("-p, --profile <name>", "Auth profile name", "default")
|
|
240
252
|
.action(async (options) => {
|
|
241
253
|
const profile = await handlers.requireAuthProfile(options.profile);
|
|
242
|
-
|
|
243
|
-
streams.stdout(line);
|
|
244
|
-
}
|
|
254
|
+
emitLines(streams, formatAuthProfileSummary(profile));
|
|
245
255
|
});
|
|
246
256
|
try {
|
|
247
257
|
await program.parseAsync(argv, { from: "user" });
|
|
248
258
|
return 0;
|
|
249
259
|
}
|
|
250
260
|
catch (error) {
|
|
261
|
+
if (isCommanderError(error)) {
|
|
262
|
+
return error.exitCode === 0 ? 0 : 1;
|
|
263
|
+
}
|
|
251
264
|
const message = isSpawnfileError(error)
|
|
252
265
|
? `${error.code}: ${error.message}`
|
|
253
266
|
: error instanceof Error
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import type { CliHandlers, CliRenderEnvironment, CliStreams } from "./runCli.js";
|
|
3
|
+
export declare const registerViewCommand: (program: Command, handlers: CliHandlers, streams: CliStreams, renderEnvironment: CliRenderEnvironment) => void;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { stat } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { Option } from "commander";
|
|
4
|
+
import { renderOrganizationNetworks, renderOrganizationTree } from "../compiler/index.js";
|
|
5
|
+
import { SpawnfileError } from "../shared/index.js";
|
|
6
|
+
const VIEW_MODES = ["tree", "networks"];
|
|
7
|
+
const VIEW_SHOW_OPTIONS = ["paths", "declared"];
|
|
8
|
+
const VIEW_COLOR_OPTIONS = ["auto", "always", "never"];
|
|
9
|
+
const isViewMode = (value) => VIEW_MODES.includes(value);
|
|
10
|
+
const isViewShow = (value) => VIEW_SHOW_OPTIONS.includes(value);
|
|
11
|
+
const manifestPathFor = (inputPath) => path.basename(inputPath) === "Spawnfile"
|
|
12
|
+
? path.resolve(inputPath)
|
|
13
|
+
: path.resolve(inputPath, "Spawnfile");
|
|
14
|
+
const hasResolvedSpawnfile = async (inputPath) => {
|
|
15
|
+
try {
|
|
16
|
+
return (await stat(manifestPathFor(inputPath))).isFile();
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
const suggestModeOptionForPathToken = async (inputPath) => {
|
|
23
|
+
if (!inputPath || !isViewMode(inputPath) || await hasResolvedSpawnfile(inputPath)) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
throw new SpawnfileError("validation_error", `No Spawnfile found for path "${inputPath}". Did you mean "spawnfile view --mode ${inputPath}"?`);
|
|
27
|
+
};
|
|
28
|
+
const resolveColor = (color, environment) => {
|
|
29
|
+
if (color === "always") {
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
if (color === "never") {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
return environment.stdoutIsTty && !environment.ci && !environment.noColor;
|
|
36
|
+
};
|
|
37
|
+
const parseShowLayers = (value) => {
|
|
38
|
+
const layers = new Set();
|
|
39
|
+
if (!value) {
|
|
40
|
+
return layers;
|
|
41
|
+
}
|
|
42
|
+
for (const layer of value.split(",")) {
|
|
43
|
+
const normalized = layer.trim();
|
|
44
|
+
if (!isViewShow(normalized)) {
|
|
45
|
+
throw new SpawnfileError("validation_error", `Unsupported view detail layer "${normalized}". Supported layers: paths, declared.`);
|
|
46
|
+
}
|
|
47
|
+
layers.add(normalized);
|
|
48
|
+
}
|
|
49
|
+
return layers;
|
|
50
|
+
};
|
|
51
|
+
const toRenderOptions = (options, environment) => {
|
|
52
|
+
const showLayers = parseShowLayers(options.show);
|
|
53
|
+
if (options.paths) {
|
|
54
|
+
showLayers.add("paths");
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
ascii: options.ascii,
|
|
58
|
+
color: resolveColor(options.color, environment),
|
|
59
|
+
declared: showLayers.has("declared"),
|
|
60
|
+
paths: showLayers.has("paths")
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
const emitRenderedView = (streams, output) => {
|
|
64
|
+
for (const line of output.split("\n")) {
|
|
65
|
+
streams.stdout(line);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
export const registerViewCommand = (program, handlers, streams, renderEnvironment) => {
|
|
69
|
+
program
|
|
70
|
+
.command("view")
|
|
71
|
+
.description("Render a read-only organization view")
|
|
72
|
+
.argument("[path]", "Project directory or Spawnfile path")
|
|
73
|
+
.addOption(new Option("--mode <mode>", "View mode").choices(VIEW_MODES).default("tree"))
|
|
74
|
+
.option("--show <show>", "Comma-separated additional details")
|
|
75
|
+
.option("--ascii", "Use ASCII tree connectors")
|
|
76
|
+
.addOption(new Option("--color <when>", "Color output").choices(VIEW_COLOR_OPTIONS).default("auto"))
|
|
77
|
+
.option("--paths", "Show source paths")
|
|
78
|
+
.action(async (inputPath, options) => {
|
|
79
|
+
await suggestModeOptionForPathToken(inputPath);
|
|
80
|
+
const renderOptions = toRenderOptions(options, renderEnvironment);
|
|
81
|
+
const view = await handlers.buildOrganizationView(inputPath ?? process.cwd());
|
|
82
|
+
const output = options.mode === "networks"
|
|
83
|
+
? renderOrganizationNetworks(view, renderOptions)
|
|
84
|
+
: renderOrganizationTree(view, renderOptions);
|
|
85
|
+
emitRenderedView(streams, output);
|
|
86
|
+
});
|
|
87
|
+
};
|
|
@@ -9,6 +9,7 @@ import { assertRuntimeSupportsExecutionModelAuth } from "./modelAuth.js";
|
|
|
9
9
|
import { assertRuntimeSupportsAgentSurfaces } from "./surfaceSupport.js";
|
|
10
10
|
import { applyExecutionDefaults } from "./executionDefaults.js";
|
|
11
11
|
import { resolvePlanMoltnetAttachments } from "./moltnetResolution.js";
|
|
12
|
+
import { resolveMoltnetRoomMemberships } from "./moltnetRoomMemberships.js";
|
|
12
13
|
import { normalizeDescription, resolveDescription, resolveRuntime } from "./buildCompilePlanRuntime.js";
|
|
13
14
|
import { resolveTeamExternalIds, resolveTeamNetworks, validateTeamNetworkRooms } from "./buildCompilePlanTeams.js";
|
|
14
15
|
export const buildCompilePlan = async (inputPath) => {
|
|
@@ -249,6 +250,7 @@ export const buildCompilePlan = async (inputPath) => {
|
|
|
249
250
|
root: rootManifestPath,
|
|
250
251
|
runtimes
|
|
251
252
|
};
|
|
253
|
+
compilePlan.moltnetRoomMemberships = resolveMoltnetRoomMemberships(compilePlan);
|
|
252
254
|
resolvePlanMoltnetAttachments(compilePlan);
|
|
253
255
|
return compilePlan;
|
|
254
256
|
};
|
|
@@ -8,9 +8,9 @@ const createEnvironmentAssignments = (plan) => {
|
|
|
8
8
|
if (plan.instancePaths.homePath) {
|
|
9
9
|
envAssignments.push(`HOME=${shellQuote(plan.instancePaths.homePath)}`);
|
|
10
10
|
}
|
|
11
|
-
if (plan.runtimeName === "tinyclaw" &&
|
|
12
|
-
plan.
|
|
13
|
-
(plan.
|
|
11
|
+
if (plan.instancePaths.homePath && ((plan.runtimeName === "tinyclaw" &&
|
|
12
|
+
(plan.modelAuthMethods.openai === "api_key" || plan.modelAuthMethods.openai === "codex")) ||
|
|
13
|
+
(plan.runtimeName === "picoclaw" && plan.modelAuthMethods.openai === "codex"))) {
|
|
14
14
|
envAssignments.push(`CODEX_HOME=${shellQuote(path.posix.join(plan.instancePaths.homePath, ".codex"))}`);
|
|
15
15
|
}
|
|
16
16
|
if (plan.meta.homeEnv && plan.instancePaths.homePath) {
|
package/dist/compiler/index.d.ts
CHANGED
|
@@ -3,9 +3,11 @@ export * from "./buildCompilePlan.js";
|
|
|
3
3
|
export * from "./buildProject.js";
|
|
4
4
|
export * from "./compileProject.js";
|
|
5
5
|
export * from "./initProject.js";
|
|
6
|
+
export * from "./moltnetRoomMemberships.js";
|
|
6
7
|
export * from "./runProject.js";
|
|
7
8
|
export * from "./syncProjectAuth.js";
|
|
8
9
|
export * from "./types.js";
|
|
9
10
|
export * from "./updateProjectModels.js";
|
|
10
11
|
export * from "./updateProjectRuntime.js";
|
|
11
12
|
export * from "./updateProjectSurfaces.js";
|
|
13
|
+
export * from "./view/index.js";
|
package/dist/compiler/index.js
CHANGED
|
@@ -3,9 +3,11 @@ export * from "./buildCompilePlan.js";
|
|
|
3
3
|
export * from "./buildProject.js";
|
|
4
4
|
export * from "./compileProject.js";
|
|
5
5
|
export * from "./initProject.js";
|
|
6
|
+
export * from "./moltnetRoomMemberships.js";
|
|
6
7
|
export * from "./runProject.js";
|
|
7
8
|
export * from "./syncProjectAuth.js";
|
|
8
9
|
export * from "./types.js";
|
|
9
10
|
export * from "./updateProjectModels.js";
|
|
10
11
|
export * from "./updateProjectRuntime.js";
|
|
11
12
|
export * from "./updateProjectSurfaces.js";
|
|
13
|
+
export * from "./view/index.js";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getRuntimeAdapter } from "../runtime/index.js";
|
|
2
2
|
import { SpawnfileError } from "../shared/index.js";
|
|
3
|
+
import { listConcreteMoltnetRoomMemberIds } from "./moltnetRoomMemberships.js";
|
|
3
4
|
const DEFAULT_MOLTNET_PORT = 8787;
|
|
4
5
|
const DEFAULT_TINYCLAW_PORT = 3777;
|
|
5
6
|
const ROOTFS_PREFIX = "container/rootfs";
|
|
@@ -86,14 +87,17 @@ export const generateMoltnetArtifacts = async (plan) => {
|
|
|
86
87
|
const existingPlan = serverPlans.get(serverKey);
|
|
87
88
|
if (existingPlan) {
|
|
88
89
|
for (const room of network.rooms) {
|
|
90
|
+
const concreteMembers = listConcreteMoltnetRoomMemberIds(plan, teamNode.value, network.id, room);
|
|
89
91
|
const existingRoom = existingPlan.rooms.find((entry) => entry.id === room.id);
|
|
90
92
|
if (existingRoom) {
|
|
91
|
-
existingRoom.members = [
|
|
93
|
+
existingRoom.members = [
|
|
94
|
+
...new Set([...existingRoom.members, ...concreteMembers])
|
|
95
|
+
].sort();
|
|
92
96
|
}
|
|
93
97
|
else {
|
|
94
98
|
existingPlan.rooms.push({
|
|
95
99
|
id: room.id,
|
|
96
|
-
members:
|
|
100
|
+
members: concreteMembers
|
|
97
101
|
});
|
|
98
102
|
}
|
|
99
103
|
}
|
|
@@ -107,7 +111,7 @@ export const generateMoltnetArtifacts = async (plan) => {
|
|
|
107
111
|
port: nextPort,
|
|
108
112
|
rooms: network.rooms.map((room) => ({
|
|
109
113
|
id: room.id,
|
|
110
|
-
members:
|
|
114
|
+
members: listConcreteMoltnetRoomMemberIds(plan, teamNode.value, network.id, room)
|
|
111
115
|
})),
|
|
112
116
|
teamSource: teamNode.value.source
|
|
113
117
|
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { SpawnfileError } from "../shared/index.js";
|
|
2
|
-
import {
|
|
2
|
+
import { resolveMoltnetRoomMemberships } from "./moltnetRoomMemberships.js";
|
|
3
|
+
import { resolveMoltnetAttachments } from "./moltnetRepresentativeResolution.js";
|
|
3
4
|
export { resolveMoltnetAttachments, resolveTeamRepresentatives } from "./moltnetRepresentativeResolution.js";
|
|
4
5
|
const findTeamBySource = (plan, source) => {
|
|
5
6
|
const node = plan.nodes.find((entry) => entry.kind === "team" && entry.value.source === source);
|
|
@@ -32,52 +33,26 @@ const validateGlobalMemberIds = (plan) => {
|
|
|
32
33
|
seen.set(context.memberId, label);
|
|
33
34
|
}
|
|
34
35
|
};
|
|
35
|
-
const
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
continue;
|
|
40
|
-
}
|
|
41
|
-
const teamNode = node.value;
|
|
42
|
-
for (const network of teamNode.networks ?? []) {
|
|
43
|
-
for (const room of network.rooms) {
|
|
44
|
-
const expandedMembers = [];
|
|
45
|
-
for (const roomMemberId of room.members) {
|
|
46
|
-
const member = teamNode.members.find((entry) => entry.id === roomMemberId);
|
|
47
|
-
if (!member) {
|
|
48
|
-
throw new SpawnfileError("validation_error", `Team ${teamNode.name} Moltnet room ${room.id} references unknown member ${roomMemberId}`);
|
|
49
|
-
}
|
|
50
|
-
if (member.kind === "agent") {
|
|
51
|
-
expandedMembers.push(member.id);
|
|
52
|
-
continue;
|
|
53
|
-
}
|
|
54
|
-
const childTeam = findTeamBySource(plan, member.nodeSource);
|
|
55
|
-
const representatives = resolveTeamRepresentatives(plan, childTeam);
|
|
56
|
-
if (representatives.length === 0) {
|
|
57
|
-
throw new SpawnfileError("validation_error", `Team ${childTeam.name} has no concrete representative for Moltnet room ${room.id} on ${teamNode.name}`);
|
|
58
|
-
}
|
|
59
|
-
for (const representative of representatives) {
|
|
60
|
-
expandedMembers.push(representative.memberId);
|
|
61
|
-
synthesizedAttachments.push({
|
|
62
|
-
contextRooms: {
|
|
63
|
-
[teamNode.source]: [room.id]
|
|
64
|
-
},
|
|
65
|
-
memberId: representative.memberId,
|
|
66
|
-
network: network.id,
|
|
67
|
-
rooms: {
|
|
68
|
-
[room.id]: {}
|
|
69
|
-
},
|
|
70
|
-
teamSource: teamNode.source
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
room.members = [...new Set(expandedMembers)];
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
return synthesizedAttachments;
|
|
36
|
+
const getRoomMemberships = (plan) => {
|
|
37
|
+
const memberships = plan.moltnetRoomMemberships ?? resolveMoltnetRoomMemberships(plan);
|
|
38
|
+
plan.moltnetRoomMemberships = memberships;
|
|
39
|
+
return memberships;
|
|
79
40
|
};
|
|
41
|
+
const synthesizeRepresentativeAttachments = (memberships) => memberships
|
|
42
|
+
.filter((membership) => membership.representedSlot !== undefined)
|
|
43
|
+
.map((membership) => ({
|
|
44
|
+
contextRooms: {
|
|
45
|
+
[membership.declaringTeamSource]: [membership.roomId]
|
|
46
|
+
},
|
|
47
|
+
memberId: membership.concreteMemberId,
|
|
48
|
+
network: membership.networkId,
|
|
49
|
+
rooms: {
|
|
50
|
+
[membership.roomId]: {}
|
|
51
|
+
},
|
|
52
|
+
teamSource: membership.declaringTeamSource
|
|
53
|
+
}));
|
|
80
54
|
const roomPolicyKey = (policy) => JSON.stringify(policy ?? {});
|
|
55
|
+
const hasRoomPolicy = (policy) => policy.read !== undefined || policy.reply !== undefined;
|
|
81
56
|
const mergeAttachment = (target, next, nodeName) => {
|
|
82
57
|
if (target.dms &&
|
|
83
58
|
next.dms &&
|
|
@@ -89,11 +64,16 @@ const mergeAttachment = (target, next, nodeName) => {
|
|
|
89
64
|
target.rooms ??= {};
|
|
90
65
|
for (const [roomId, policy] of Object.entries(next.rooms ?? {})) {
|
|
91
66
|
const existingPolicy = target.rooms[roomId];
|
|
92
|
-
|
|
67
|
+
const existingHasPolicy = existingPolicy ? hasRoomPolicy(existingPolicy) : false;
|
|
68
|
+
const nextHasPolicy = hasRoomPolicy(policy);
|
|
69
|
+
if (existingHasPolicy &&
|
|
70
|
+
nextHasPolicy &&
|
|
93
71
|
roomPolicyKey(existingPolicy) !== roomPolicyKey(policy)) {
|
|
94
72
|
throw new SpawnfileError("validation_error", `Agent ${nodeName} declares incompatible Moltnet room policy for ${next.network}/${next.memberId ?? "unknown"} room ${roomId}`);
|
|
95
73
|
}
|
|
96
|
-
target.rooms[roomId] =
|
|
74
|
+
target.rooms[roomId] = existingPolicy && existingHasPolicy && !nextHasPolicy
|
|
75
|
+
? { ...existingPolicy }
|
|
76
|
+
: { ...policy };
|
|
97
77
|
}
|
|
98
78
|
if (next.contextRooms) {
|
|
99
79
|
target.contextRooms ??= {};
|
|
@@ -149,7 +129,8 @@ const mergeAgentAttachments = (agentNode, attachments) => {
|
|
|
149
129
|
};
|
|
150
130
|
export const resolvePlanMoltnetAttachments = (plan) => {
|
|
151
131
|
validateGlobalMemberIds(plan);
|
|
152
|
-
const
|
|
132
|
+
const roomMemberships = getRoomMemberships(plan);
|
|
133
|
+
const synthesizedAttachments = synthesizeRepresentativeAttachments(roomMemberships);
|
|
153
134
|
const synthesizedByAgent = new Map();
|
|
154
135
|
for (const attachment of synthesizedAttachments) {
|
|
155
136
|
const representativeContext = (plan.memberships ?? []).find((context) => context.memberId === attachment.memberId);
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { CompilePlan, ResolvedMoltnetRoomMembership, ResolvedTeamNetworkRoom, ResolvedTeamNode } from "./types.js";
|
|
2
|
+
export declare const listConcreteMoltnetRoomMemberIds: (plan: CompilePlan, teamNode: ResolvedTeamNode, networkId: string, room: ResolvedTeamNetworkRoom, memberships?: ResolvedMoltnetRoomMembership[] | undefined) => string[];
|
|
3
|
+
export declare const resolveMoltnetRoomMemberships: (plan: CompilePlan) => ResolvedMoltnetRoomMembership[];
|