mimetic-cli 0.1.0
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/LICENSE +21 -0
- package/README.md +132 -0
- package/dist/argv.d.ts +1 -0
- package/dist/argv.js +8 -0
- package/dist/argv.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +5 -0
- package/dist/cli.js.map +1 -0
- package/dist/feedback.d.ts +48 -0
- package/dist/feedback.js +243 -0
- package/dist/feedback.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/init-templates.d.ts +12 -0
- package/dist/init-templates.js +251 -0
- package/dist/init-templates.js.map +1 -0
- package/dist/init.d.ts +26 -0
- package/dist/init.js +343 -0
- package/dist/init.js.map +1 -0
- package/dist/observer-assets.d.ts +2 -0
- package/dist/observer-assets.js +2322 -0
- package/dist/observer-assets.js.map +1 -0
- package/dist/observer-data.d.ts +53 -0
- package/dist/observer-data.js +123 -0
- package/dist/observer-data.js.map +1 -0
- package/dist/observer.d.ts +36 -0
- package/dist/observer.js +360 -0
- package/dist/observer.js.map +1 -0
- package/dist/oss-lab.d.ts +50 -0
- package/dist/oss-lab.js +298 -0
- package/dist/oss-lab.js.map +1 -0
- package/dist/oss-meta-lab.d.ts +43 -0
- package/dist/oss-meta-lab.js +901 -0
- package/dist/oss-meta-lab.js.map +1 -0
- package/dist/program.d.ts +36 -0
- package/dist/program.js +825 -0
- package/dist/program.js.map +1 -0
- package/dist/run.d.ts +206 -0
- package/dist/run.js +688 -0
- package/dist/run.js.map +1 -0
- package/package.json +78 -0
- package/skills/mimetic-cli/SKILL.md +92 -0
- package/skills/mimetic-cli/agents/openai.yaml +7 -0
package/dist/program.js
ADDED
|
@@ -0,0 +1,825 @@
|
|
|
1
|
+
import { Command, Option } from "commander";
|
|
2
|
+
import { draftFeedback, listFeedback, renderIssueMarkdown, renderIssueUrl, verifyFeedback } from "./feedback.js";
|
|
3
|
+
import { runInit } from "./init.js";
|
|
4
|
+
import { renderObserver, serveObserver } from "./observer.js";
|
|
5
|
+
import { DEFAULT_OSS_REPOS, runOssLab } from "./oss-lab.js";
|
|
6
|
+
import { runOssMetaLab } from "./oss-meta-lab.js";
|
|
7
|
+
import { doctor, listRuns, readReview, runDryRun, verifyRun } from "./run.js";
|
|
8
|
+
export const CLI_RESPONSE_SCHEMA = "mimetic.cli-response.v1";
|
|
9
|
+
const defaultIo = {
|
|
10
|
+
writeOut: (text) => process.stdout.write(text),
|
|
11
|
+
writeErr: (text) => process.stderr.write(text),
|
|
12
|
+
setExitCode: (code) => {
|
|
13
|
+
process.exitCode = code;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
const commonDocs = [
|
|
17
|
+
"docs/product/open-source-install-experience.md",
|
|
18
|
+
"docs/roadmap/world-class-open-source-v0.md",
|
|
19
|
+
"docs/release/open-source-readiness.md"
|
|
20
|
+
];
|
|
21
|
+
export const plannedCommands = [
|
|
22
|
+
{
|
|
23
|
+
name: "init",
|
|
24
|
+
description: "Set up committed mimetic/ source files and ignored .mimetic/ runtime state.",
|
|
25
|
+
issue: "https://github.com/danielgwilson/mimetic-cli/issues/14",
|
|
26
|
+
docs: ["docs/architecture/project-layout.md", ...commonDocs],
|
|
27
|
+
options: [
|
|
28
|
+
{ flags: "--dry-run", description: "Print planned changes without writing files." },
|
|
29
|
+
{ flags: "--yes", description: "Apply safe generated changes without prompting." },
|
|
30
|
+
{ flags: "--cwd <path>", description: "Target project directory.", defaultValue: "." }
|
|
31
|
+
]
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: "doctor",
|
|
35
|
+
description: "Explain project readiness and missing Mimetic setup.",
|
|
36
|
+
issue: "https://github.com/danielgwilson/mimetic-cli/issues/7",
|
|
37
|
+
docs: commonDocs
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: "run",
|
|
41
|
+
description: "Run a persona/scenario simulation or synthetic dry-run bundle.",
|
|
42
|
+
issue: "https://github.com/danielgwilson/mimetic-cli/issues/7",
|
|
43
|
+
docs: commonDocs,
|
|
44
|
+
options: [
|
|
45
|
+
{ flags: "--dry-run", description: "Generate contract proof without browser, keys, or provider spend." },
|
|
46
|
+
{ flags: "--cwd <path>", description: "Target project directory.", defaultValue: "." }
|
|
47
|
+
]
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: "verify",
|
|
51
|
+
description: "Validate a run bundle and public-safety gates.",
|
|
52
|
+
issue: "https://github.com/danielgwilson/mimetic-cli/issues/7",
|
|
53
|
+
docs: commonDocs,
|
|
54
|
+
options: [
|
|
55
|
+
{ flags: "--run <id>", description: "Run id or latest pointer.", defaultValue: "latest" }
|
|
56
|
+
]
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: "review",
|
|
60
|
+
description: "Build a review packet from verified run evidence.",
|
|
61
|
+
issue: "https://github.com/danielgwilson/mimetic-cli/issues/7",
|
|
62
|
+
docs: commonDocs,
|
|
63
|
+
options: [
|
|
64
|
+
{ flags: "--run <id>", description: "Run id or latest pointer.", defaultValue: "latest" }
|
|
65
|
+
]
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: "watch",
|
|
69
|
+
description: "Run sims, open the observer, and keep the shell attached.",
|
|
70
|
+
issue: "https://github.com/danielgwilson/mimetic-cli/issues/10",
|
|
71
|
+
docs: commonDocs,
|
|
72
|
+
options: [
|
|
73
|
+
{ flags: "--run <id>", description: "Watch an existing run id or latest pointer." },
|
|
74
|
+
{ flags: "--sims <count>", description: "Start a fresh synthetic run with this many sims before rendering.", defaultValue: "4 when --run is omitted" },
|
|
75
|
+
{ flags: "--open", description: "Open the observer in the default browser.", defaultValue: "true for human output" },
|
|
76
|
+
{ flags: "--detach", description: "Render/open once and exit without attached watch server." },
|
|
77
|
+
{ flags: "--port <port>", description: "Local observer server port when following.", defaultValue: "0" },
|
|
78
|
+
{ flags: "--no-open", description: "Render without opening a browser." }
|
|
79
|
+
]
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: "runs",
|
|
83
|
+
description: "List local Mimetic runs and latest pointers.",
|
|
84
|
+
issue: "https://github.com/danielgwilson/mimetic-cli/issues/7",
|
|
85
|
+
docs: commonDocs
|
|
86
|
+
}
|
|
87
|
+
];
|
|
88
|
+
export function createProgram(io = {}) {
|
|
89
|
+
const cliIo = { ...defaultIo, ...io };
|
|
90
|
+
const program = new Command();
|
|
91
|
+
program
|
|
92
|
+
.name("mimetic")
|
|
93
|
+
.description("Open-source-safe persona simulation CLI and proof harness.")
|
|
94
|
+
.version("0.1.0")
|
|
95
|
+
.showHelpAfterError()
|
|
96
|
+
.option("--json", "Print machine-readable JSON responses where supported.")
|
|
97
|
+
.configureOutput({
|
|
98
|
+
writeOut: (text) => cliIo.writeOut(text),
|
|
99
|
+
writeErr: (text) => cliIo.writeErr(text)
|
|
100
|
+
})
|
|
101
|
+
.addHelpText("after", [
|
|
102
|
+
"",
|
|
103
|
+
"Examples:",
|
|
104
|
+
" mimetic watch",
|
|
105
|
+
" mimetic watch --run latest --detach",
|
|
106
|
+
" mimetic watch --json --no-open",
|
|
107
|
+
" mimetic lab oss --repos developit/mitt,lukeed/clsx",
|
|
108
|
+
" mimetic lab oss-smoke --limit 1 --keep",
|
|
109
|
+
" mimetic verify --run latest --json",
|
|
110
|
+
"",
|
|
111
|
+
"Public-safety boundary:",
|
|
112
|
+
" Mimetic must not commit or emit PII, PHI, secrets, keys, raw private transcripts,",
|
|
113
|
+
" private screenshots, or private source-system artifacts."
|
|
114
|
+
].join("\n"));
|
|
115
|
+
registerInitCommand(program, cliIo);
|
|
116
|
+
registerDoctorCommand(program, cliIo);
|
|
117
|
+
registerRunCommand(program, cliIo);
|
|
118
|
+
registerVerifyCommand(program, cliIo);
|
|
119
|
+
registerReviewCommand(program, cliIo);
|
|
120
|
+
registerRunsCommand(program, cliIo);
|
|
121
|
+
registerWatchCommand(program, cliIo);
|
|
122
|
+
registerLabCommands(program, cliIo);
|
|
123
|
+
const implementedCommands = new Set(["init", "doctor", "run", "verify", "review", "runs", "watch", "lab"]);
|
|
124
|
+
for (const plannedCommand of plannedCommands.filter((command) => !implementedCommands.has(command.name))) {
|
|
125
|
+
registerUnsupportedCommand(program, plannedCommand, cliIo);
|
|
126
|
+
}
|
|
127
|
+
registerFeedbackCommands(program, cliIo);
|
|
128
|
+
return program;
|
|
129
|
+
}
|
|
130
|
+
function registerInitCommand(parent, io) {
|
|
131
|
+
parent
|
|
132
|
+
.command("init")
|
|
133
|
+
.description("Set up committed mimetic/ source files and ignored .mimetic/ runtime state.")
|
|
134
|
+
.option("--dry-run", "Print planned changes without writing files.")
|
|
135
|
+
.option("--yes", "Apply safe generated changes without prompting.")
|
|
136
|
+
.option("--cwd <path>", "Target project directory.", ".")
|
|
137
|
+
.option("--json", "Print a machine-readable JSON response.")
|
|
138
|
+
.action(async (options, command) => {
|
|
139
|
+
const initOptions = {
|
|
140
|
+
cwd: options.cwd,
|
|
141
|
+
...(options.dryRun === undefined ? {} : { dryRun: options.dryRun }),
|
|
142
|
+
...(options.yes === undefined ? {} : { yes: options.yes })
|
|
143
|
+
};
|
|
144
|
+
const result = await runInit(initOptions);
|
|
145
|
+
if (wantsJson(command)) {
|
|
146
|
+
io.writeOut(`${JSON.stringify(result, null, 2)}\n`);
|
|
147
|
+
}
|
|
148
|
+
else if (result.ok) {
|
|
149
|
+
io.writeOut(formatInitHuman(result));
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
io.writeErr(formatInitHuman(result));
|
|
153
|
+
}
|
|
154
|
+
io.setExitCode(result.ok ? 0 : 2);
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
function registerDoctorCommand(parent, io) {
|
|
158
|
+
parent
|
|
159
|
+
.command("doctor")
|
|
160
|
+
.description("Explain project readiness and missing Mimetic setup.")
|
|
161
|
+
.option("--cwd <path>", "Target project directory.", ".")
|
|
162
|
+
.option("--json", "Print a machine-readable JSON response.")
|
|
163
|
+
.action(async (options, command) => {
|
|
164
|
+
const result = await doctor(options.cwd);
|
|
165
|
+
writeResult(command, io, result, formatDoctorHuman);
|
|
166
|
+
io.setExitCode(result.ok ? 0 : 1);
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
function registerRunCommand(parent, io) {
|
|
170
|
+
parent
|
|
171
|
+
.command("run")
|
|
172
|
+
.description("Run a persona/scenario simulation or synthetic dry-run bundle.")
|
|
173
|
+
.option("--dry-run", "Generate contract proof without browser, keys, or provider spend.")
|
|
174
|
+
.option("--cwd <path>", "Target project directory.", ".")
|
|
175
|
+
.option("--run-id <id>", "Explicit run id for deterministic fixture tests.")
|
|
176
|
+
.option("--json", "Print a machine-readable JSON response.")
|
|
177
|
+
.action(async (options, command) => {
|
|
178
|
+
const result = await runDryRun({
|
|
179
|
+
cwd: options.cwd,
|
|
180
|
+
...(options.dryRun === undefined ? {} : { dryRun: options.dryRun }),
|
|
181
|
+
...(options.runId === undefined ? {} : { runId: options.runId })
|
|
182
|
+
});
|
|
183
|
+
writeResult(command, io, result, formatRunHuman);
|
|
184
|
+
io.setExitCode(result.ok ? 0 : 2);
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
function registerVerifyCommand(parent, io) {
|
|
188
|
+
parent
|
|
189
|
+
.command("verify")
|
|
190
|
+
.description("Validate a run bundle and public-safety gates.")
|
|
191
|
+
.option("--run <id>", "Run id or latest pointer.", "latest")
|
|
192
|
+
.option("--cwd <path>", "Target project directory.", ".")
|
|
193
|
+
.option("--json", "Print a machine-readable JSON response.")
|
|
194
|
+
.action(async (options, command) => {
|
|
195
|
+
const result = await verifyRun(options.cwd, options.run);
|
|
196
|
+
writeResult(command, io, result, formatVerifyHuman);
|
|
197
|
+
io.setExitCode(result.ok ? 0 : 2);
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
function registerReviewCommand(parent, io) {
|
|
201
|
+
parent
|
|
202
|
+
.command("review")
|
|
203
|
+
.description("Build a review packet from verified run evidence.")
|
|
204
|
+
.option("--run <id>", "Run id or latest pointer.", "latest")
|
|
205
|
+
.option("--cwd <path>", "Target project directory.", ".")
|
|
206
|
+
.option("--json", "Print a machine-readable JSON response.")
|
|
207
|
+
.action(async (options, command) => {
|
|
208
|
+
const result = await readReview(options.cwd, options.run);
|
|
209
|
+
writeResult(command, io, result, (value) => `${JSON.stringify(value, null, 2)}\n`);
|
|
210
|
+
io.setExitCode("ok" in result && result.ok === false ? 2 : 0);
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
function registerRunsCommand(parent, io) {
|
|
214
|
+
parent
|
|
215
|
+
.command("runs")
|
|
216
|
+
.description("List local Mimetic runs and latest pointers.")
|
|
217
|
+
.option("--cwd <path>", "Target project directory.", ".")
|
|
218
|
+
.option("--json", "Print a machine-readable JSON response.")
|
|
219
|
+
.action(async (options, command) => {
|
|
220
|
+
const result = await listRuns(options.cwd);
|
|
221
|
+
writeResult(command, io, result, formatRunsHuman);
|
|
222
|
+
io.setExitCode(0);
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
function registerWatchCommand(parent, io) {
|
|
226
|
+
parent
|
|
227
|
+
.command("watch")
|
|
228
|
+
.description("Run sims, open the observer, and keep the shell attached.")
|
|
229
|
+
.option("--run <id>", "Watch an existing run id or latest pointer.")
|
|
230
|
+
.option("--sims <count>", "Start a fresh synthetic run with this many sims before rendering. Defaults to 4 when --run is omitted.")
|
|
231
|
+
.option("--run-id <id>", "Explicit run id for deterministic fixture tests.")
|
|
232
|
+
.option("--cwd <path>", "Target project directory.", ".")
|
|
233
|
+
.option("--open", "Open the observer in the default browser.")
|
|
234
|
+
.option("--no-open", "Render without opening a browser.")
|
|
235
|
+
.addOption(new Option("--follow", "Deprecated; human output follows by default.").hideHelp())
|
|
236
|
+
.option("--detach", "Render/open once and exit without attached watch server.")
|
|
237
|
+
.option("--port <port>", "Local observer server port when following.", "0")
|
|
238
|
+
.option("--json", "Print a machine-readable JSON response.")
|
|
239
|
+
.addHelpText("after", [
|
|
240
|
+
"",
|
|
241
|
+
"Happy path:",
|
|
242
|
+
" mimetic watch",
|
|
243
|
+
"",
|
|
244
|
+
"Agent/CI path:",
|
|
245
|
+
" mimetic watch --json --no-open",
|
|
246
|
+
"",
|
|
247
|
+
"Existing evidence:",
|
|
248
|
+
" mimetic watch --run latest --detach"
|
|
249
|
+
].join("\n"))
|
|
250
|
+
.action(async (options, command) => {
|
|
251
|
+
const runOptionSource = typeof command.getOptionValueSource === "function"
|
|
252
|
+
? command.getOptionValueSource("run")
|
|
253
|
+
: undefined;
|
|
254
|
+
const runWasOmitted = runOptionSource === undefined || runOptionSource === "default";
|
|
255
|
+
const simCount = options.sims === undefined ? undefined : parsePositiveInteger(options.sims);
|
|
256
|
+
const port = parseObserverPort(options.port);
|
|
257
|
+
if (options.sims !== undefined && simCount === null) {
|
|
258
|
+
const result = {
|
|
259
|
+
schema: "mimetic.run-result.v1",
|
|
260
|
+
ok: false,
|
|
261
|
+
cwd: options.cwd,
|
|
262
|
+
warnings: [],
|
|
263
|
+
error: {
|
|
264
|
+
code: "MIMETIC_INVALID_SIM_COUNT",
|
|
265
|
+
message: "--sims must be an integer between 1 and 64."
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
writeResult(command, io, result, formatRunHuman);
|
|
269
|
+
io.setExitCode(2);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
if (!runWasOmitted && options.sims !== undefined) {
|
|
273
|
+
const result = {
|
|
274
|
+
schema: "mimetic.run-result.v1",
|
|
275
|
+
ok: false,
|
|
276
|
+
cwd: options.cwd,
|
|
277
|
+
warnings: [],
|
|
278
|
+
error: {
|
|
279
|
+
code: "MIMETIC_WATCH_OPTION_CONFLICT",
|
|
280
|
+
message: "Use either --run to watch existing evidence or --sims to start a fresh run, not both."
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
writeResult(command, io, result, formatRunHuman);
|
|
284
|
+
io.setExitCode(2);
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
if (!runWasOmitted && options.runId !== undefined) {
|
|
288
|
+
const result = {
|
|
289
|
+
schema: "mimetic.run-result.v1",
|
|
290
|
+
ok: false,
|
|
291
|
+
cwd: options.cwd,
|
|
292
|
+
warnings: [],
|
|
293
|
+
error: {
|
|
294
|
+
code: "MIMETIC_WATCH_OPTION_CONFLICT",
|
|
295
|
+
message: "--run-id only applies to fresh watch runs; remove --run or remove --run-id."
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
writeResult(command, io, result, formatRunHuman);
|
|
299
|
+
io.setExitCode(2);
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
if (port === null) {
|
|
303
|
+
const result = {
|
|
304
|
+
schema: "mimetic.run-result.v1",
|
|
305
|
+
ok: false,
|
|
306
|
+
cwd: options.cwd,
|
|
307
|
+
warnings: [],
|
|
308
|
+
error: {
|
|
309
|
+
code: "MIMETIC_INVALID_PORT",
|
|
310
|
+
message: "--port must be an integer between 0 and 65535."
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
writeResult(command, io, result, formatRunHuman);
|
|
314
|
+
io.setExitCode(2);
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
const requestedSimCount = simCount ?? (runWasOmitted ? 4 : undefined);
|
|
318
|
+
let runInput = options.run ?? "latest";
|
|
319
|
+
if (requestedSimCount !== undefined && requestedSimCount !== null) {
|
|
320
|
+
const runResult = await runDryRun({
|
|
321
|
+
cwd: options.cwd,
|
|
322
|
+
dryRun: true,
|
|
323
|
+
simCount: requestedSimCount,
|
|
324
|
+
...(options.runId === undefined ? {} : { runId: options.runId })
|
|
325
|
+
});
|
|
326
|
+
if (!runResult.ok || !runResult.runId) {
|
|
327
|
+
writeResult(command, io, runResult, formatRunHuman);
|
|
328
|
+
io.setExitCode(2);
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
runInput = runResult.runId;
|
|
332
|
+
}
|
|
333
|
+
const wantsMachine = wantsJson(command);
|
|
334
|
+
const shouldOpen = options.open === false ? false : wantsMachine ? options.open === true : true;
|
|
335
|
+
const wantsFollow = !wantsMachine && options.detach !== true && (options.follow !== false);
|
|
336
|
+
const rendered = await renderObserver(options.cwd, runInput, { open: wantsFollow ? false : shouldOpen });
|
|
337
|
+
let server = null;
|
|
338
|
+
let result = rendered;
|
|
339
|
+
if (rendered.ok && wantsFollow) {
|
|
340
|
+
server = await serveObserver(rendered, { open: shouldOpen, port });
|
|
341
|
+
result = {
|
|
342
|
+
...rendered,
|
|
343
|
+
observerUrl: server.url,
|
|
344
|
+
serverUrl: server.url,
|
|
345
|
+
opened: server.opened,
|
|
346
|
+
...(server.openCommand ? { openCommand: server.openCommand } : {}),
|
|
347
|
+
warnings: [
|
|
348
|
+
...rendered.warnings,
|
|
349
|
+
"Live observer server is polling observer-data.json with no-store caching.",
|
|
350
|
+
...(server.warning ? [server.warning] : [])
|
|
351
|
+
]
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
writeResult(command, io, result, formatObserverHuman);
|
|
355
|
+
io.setExitCode(result.ok ? 0 : 2);
|
|
356
|
+
if (result.ok && server) {
|
|
357
|
+
await followObserver(io, result, server);
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
function registerFeedbackCommands(parent, io) {
|
|
362
|
+
const feedback = parent
|
|
363
|
+
.command("feedback")
|
|
364
|
+
.description("Create public-safe feedback drafts without GitHub API mutation.");
|
|
365
|
+
feedback
|
|
366
|
+
.command("list")
|
|
367
|
+
.description("List feedback draft state for a run.")
|
|
368
|
+
.option("--run <id>", "Run id or latest pointer.", "latest")
|
|
369
|
+
.option("--cwd <path>", "Target project directory.", ".")
|
|
370
|
+
.option("--json", "Print a machine-readable JSON response.")
|
|
371
|
+
.action(async (options, command) => {
|
|
372
|
+
const result = await listFeedback(options.cwd, options.run);
|
|
373
|
+
writeResult(command, io, result, formatFeedbackHuman);
|
|
374
|
+
io.setExitCode(result.ok ? 0 : 2);
|
|
375
|
+
});
|
|
376
|
+
feedback
|
|
377
|
+
.command("draft")
|
|
378
|
+
.description("Generate a public-safe feedback draft from verified evidence.")
|
|
379
|
+
.option("--run <id>", "Run id or latest pointer.", "latest")
|
|
380
|
+
.option("--cwd <path>", "Target project directory.", ".")
|
|
381
|
+
.option("--json", "Print a machine-readable JSON response.")
|
|
382
|
+
.action(async (options, command) => {
|
|
383
|
+
const result = await draftFeedback(options.cwd, options.run);
|
|
384
|
+
writeResult(command, io, result, formatFeedbackHuman);
|
|
385
|
+
io.setExitCode(result.ok ? 0 : 2);
|
|
386
|
+
});
|
|
387
|
+
feedback
|
|
388
|
+
.command("verify")
|
|
389
|
+
.description("Verify the feedback draft for public issue eligibility.")
|
|
390
|
+
.option("--run <id>", "Run id or latest pointer.", "latest")
|
|
391
|
+
.option("--cwd <path>", "Target project directory.", ".")
|
|
392
|
+
.option("--json", "Print a machine-readable JSON response.")
|
|
393
|
+
.action(async (options, command) => {
|
|
394
|
+
const result = await verifyFeedback(options.cwd, options.run);
|
|
395
|
+
writeResult(command, io, result, formatFeedbackHuman);
|
|
396
|
+
io.setExitCode(result.ok ? 0 : 2);
|
|
397
|
+
});
|
|
398
|
+
feedback
|
|
399
|
+
.command("issue")
|
|
400
|
+
.description("Print Markdown for a public GitHub issue. Does not mutate GitHub.")
|
|
401
|
+
.option("--run <id>", "Run id or latest pointer.", "latest")
|
|
402
|
+
.option("--cwd <path>", "Target project directory.", ".")
|
|
403
|
+
.requiredOption("--repo <owner/repo>", "Repository slug used in rendered filing instructions.")
|
|
404
|
+
.option("--format <format>", "Output format.", "markdown")
|
|
405
|
+
.option("--json", "Print a machine-readable JSON response.")
|
|
406
|
+
.action(async (options, command) => {
|
|
407
|
+
const result = await renderIssueMarkdown(options.cwd, options.run, options.repo);
|
|
408
|
+
if (wantsJson(command)) {
|
|
409
|
+
io.writeOut(`${JSON.stringify(result, null, 2)}\n`);
|
|
410
|
+
}
|
|
411
|
+
else if (options.format !== "markdown") {
|
|
412
|
+
io.writeErr("Only --format markdown is supported.\n");
|
|
413
|
+
io.setExitCode(2);
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
else if (result.ok && result.issueMarkdown) {
|
|
417
|
+
io.writeOut(result.issueMarkdown);
|
|
418
|
+
}
|
|
419
|
+
else {
|
|
420
|
+
io.writeErr(formatFeedbackHuman(result));
|
|
421
|
+
}
|
|
422
|
+
io.setExitCode(result.ok ? 0 : 2);
|
|
423
|
+
});
|
|
424
|
+
feedback
|
|
425
|
+
.command("issue-url")
|
|
426
|
+
.description("Print a prefilled public issue URL. Does not mutate GitHub.")
|
|
427
|
+
.option("--run <id>", "Run id or latest pointer.", "latest")
|
|
428
|
+
.option("--cwd <path>", "Target project directory.", ".")
|
|
429
|
+
.requiredOption("--repo <owner/repo>", "Repository slug used in the generated URL.")
|
|
430
|
+
.option("--json", "Print a machine-readable JSON response.")
|
|
431
|
+
.action(async (options, command) => {
|
|
432
|
+
const result = await renderIssueUrl(options.cwd, options.run, options.repo);
|
|
433
|
+
if (wantsJson(command)) {
|
|
434
|
+
io.writeOut(`${JSON.stringify(result, null, 2)}\n`);
|
|
435
|
+
}
|
|
436
|
+
else if (result.ok && result.issueUrl) {
|
|
437
|
+
io.writeOut(`${result.issueUrl}\n`);
|
|
438
|
+
}
|
|
439
|
+
else {
|
|
440
|
+
io.writeErr(formatFeedbackHuman(result));
|
|
441
|
+
}
|
|
442
|
+
io.setExitCode(result.ok ? 0 : 2);
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
function registerLabCommands(parent, io) {
|
|
446
|
+
const lab = parent
|
|
447
|
+
.command("lab")
|
|
448
|
+
.description("Run experimental Mimetic proof loops against disposable public targets.");
|
|
449
|
+
lab
|
|
450
|
+
.command("oss")
|
|
451
|
+
.description("Watch headed Codex/E2B OSS meta-sims setting up Mimetic inside public repos.")
|
|
452
|
+
.option("--repos <owner/repo,...>", "Comma-separated public GitHub repo slugs.")
|
|
453
|
+
.option("--repo <owner/repo>", "Public GitHub repo slug. Repeatable.", collectRepeated, [])
|
|
454
|
+
.option("--count <count>", "Number of headed desktop sims to assign.", String(DEFAULT_OSS_REPOS.length))
|
|
455
|
+
.option("--sims <count>", "Alias for --count.")
|
|
456
|
+
.option("--run-id <id>", "Explicit lab run id.")
|
|
457
|
+
.option("--cwd <path>", "Host directory for ignored .mimetic lab report.", ".")
|
|
458
|
+
.option("--dry-run", "Render the Observer-of-Observers contract without provider spend or live E2B launch.")
|
|
459
|
+
.option("--open", "Open the observer in the default browser.")
|
|
460
|
+
.option("--no-open", "Render without opening a browser.")
|
|
461
|
+
.option("--detach", "Render/open once and exit without attached watch server.")
|
|
462
|
+
.option("--port <port>", "Local observer server port when following.", "0")
|
|
463
|
+
.option("--smoke", "Run the disposable local clone smoke harness instead of headed meta-sims.")
|
|
464
|
+
.option("--limit <count>", "Smoke mode only: number of selected repos to trial.", String(DEFAULT_OSS_REPOS.length))
|
|
465
|
+
.option("--keep", "Smoke mode only: keep disposable clone sandbox for debugging.")
|
|
466
|
+
.option("--json", "Print a machine-readable JSON response.")
|
|
467
|
+
.addHelpText("after", [
|
|
468
|
+
"",
|
|
469
|
+
"Happy path:",
|
|
470
|
+
" mimetic lab oss",
|
|
471
|
+
"",
|
|
472
|
+
"Repo selection:",
|
|
473
|
+
" mimetic lab oss --repos developit/mitt,lukeed/clsx,sindresorhus/is-plain-obj,ai/nanoid",
|
|
474
|
+
" mimetic lab oss --repo developit/mitt --repo lukeed/clsx --count 4",
|
|
475
|
+
"",
|
|
476
|
+
"Agent/CI path:",
|
|
477
|
+
" mimetic lab oss --dry-run --json --no-open",
|
|
478
|
+
"",
|
|
479
|
+
"Disposable clone smoke:",
|
|
480
|
+
" mimetic lab oss-smoke --limit 1 --keep",
|
|
481
|
+
" mimetic lab oss --smoke --limit 1 --keep",
|
|
482
|
+
"",
|
|
483
|
+
"Shape:",
|
|
484
|
+
" The top-level Observer shows headed E2B desktop lanes. Each desktop is intended",
|
|
485
|
+
" to run Codex TUI, clone its assigned public OSS repo, set up Mimetic, and keep",
|
|
486
|
+
" that repo's nested Mimetic Observer visible in the E2B browser.",
|
|
487
|
+
"",
|
|
488
|
+
"Safety:",
|
|
489
|
+
" Only public GitHub owner/repo slugs are accepted. No keys or private artifacts",
|
|
490
|
+
" are written into committed Mimetic source."
|
|
491
|
+
].join("\n"))
|
|
492
|
+
.action(async (options, command) => {
|
|
493
|
+
if (options.smoke) {
|
|
494
|
+
await runOssSmokeAction({ command, io, options });
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
const countInput = options.sims ?? options.count;
|
|
498
|
+
const count = parsePositiveInteger(countInput);
|
|
499
|
+
const port = parseObserverPort(options.port);
|
|
500
|
+
if (port === null) {
|
|
501
|
+
const result = {
|
|
502
|
+
schema: "mimetic.oss-meta-lab-result.v1",
|
|
503
|
+
ok: false,
|
|
504
|
+
assignments: [],
|
|
505
|
+
cwd: options.cwd,
|
|
506
|
+
dryRun: options.dryRun === true,
|
|
507
|
+
error: {
|
|
508
|
+
code: "MIMETIC_META_RUN_FAILED",
|
|
509
|
+
message: "--port must be an integer between 0 and 65535."
|
|
510
|
+
},
|
|
511
|
+
liveRequested: options.dryRun !== true,
|
|
512
|
+
repos: [...options.repo, ...(options.repos ? [options.repos] : [])],
|
|
513
|
+
sandboxes: [],
|
|
514
|
+
warnings: []
|
|
515
|
+
};
|
|
516
|
+
writeResult(command, io, result, formatOssMetaLabHuman);
|
|
517
|
+
io.setExitCode(2);
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
const wantsMachine = wantsJson(command);
|
|
521
|
+
const shouldOpen = options.open === false ? false : wantsMachine ? options.open === true : true;
|
|
522
|
+
const wantsFollow = !wantsMachine && options.detach !== true && options.dryRun !== true;
|
|
523
|
+
const result = await runOssMetaLab({
|
|
524
|
+
cwd: options.cwd,
|
|
525
|
+
open: wantsFollow ? false : shouldOpen,
|
|
526
|
+
repos: [...options.repo, ...(options.repos ? [options.repos] : [])],
|
|
527
|
+
...(count === null ? { count: Number.NaN } : { count }),
|
|
528
|
+
...(options.dryRun === undefined ? {} : { dryRun: options.dryRun }),
|
|
529
|
+
...(options.runId === undefined ? {} : { runId: options.runId })
|
|
530
|
+
});
|
|
531
|
+
let server = null;
|
|
532
|
+
let output = result;
|
|
533
|
+
if (result.ok && wantsFollow && result.observer?.ok) {
|
|
534
|
+
server = await serveObserver(result.observer, { open: shouldOpen, port });
|
|
535
|
+
output = {
|
|
536
|
+
...result,
|
|
537
|
+
observer: {
|
|
538
|
+
...result.observer,
|
|
539
|
+
observerUrl: server.url,
|
|
540
|
+
serverUrl: server.url,
|
|
541
|
+
opened: server.opened,
|
|
542
|
+
...(server.openCommand ? { openCommand: server.openCommand } : {}),
|
|
543
|
+
warnings: [
|
|
544
|
+
...result.observer.warnings,
|
|
545
|
+
"Live OSS meta-lab server is polling observer-data.json with no-store caching.",
|
|
546
|
+
...(server.warning ? [server.warning] : [])
|
|
547
|
+
]
|
|
548
|
+
},
|
|
549
|
+
warnings: [
|
|
550
|
+
...result.warnings,
|
|
551
|
+
"Live OSS meta-lab server is polling observer-data.json with no-store caching.",
|
|
552
|
+
...(server.warning ? [server.warning] : [])
|
|
553
|
+
]
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
writeResult(command, io, output, formatOssMetaLabHuman);
|
|
557
|
+
io.setExitCode(output.ok ? 0 : 2);
|
|
558
|
+
if (output.ok && options.detach === true && output.sandboxes.some((sandbox) => sandbox.urlPresent)) {
|
|
559
|
+
// The E2B SDK keeps local handles open after stream URL creation. Detach should
|
|
560
|
+
// return the user's shell while the remote desktops continue on E2B.
|
|
561
|
+
setTimeout(() => process.exit(0), 50);
|
|
562
|
+
}
|
|
563
|
+
if (output.ok && server && output.observer?.ok) {
|
|
564
|
+
await followObserver(io, output.observer, server);
|
|
565
|
+
}
|
|
566
|
+
});
|
|
567
|
+
lab
|
|
568
|
+
.command("oss-smoke")
|
|
569
|
+
.description("Clone lightweight public OSS repos, try Mimetic setup/proof, then discard clones.")
|
|
570
|
+
.option("--repos <owner/repo,...>", "Comma-separated public GitHub repo slugs.")
|
|
571
|
+
.option("--repo <owner/repo>", "Public GitHub repo slug. Repeatable.", collectRepeated, [])
|
|
572
|
+
.option("--limit <count>", "Number of selected repos to trial.", String(DEFAULT_OSS_REPOS.length))
|
|
573
|
+
.option("--run-id <id>", "Explicit lab run id.")
|
|
574
|
+
.option("--cwd <path>", "Host directory for ignored .mimetic lab report.", ".")
|
|
575
|
+
.option("--keep", "Keep disposable clone sandbox for debugging.")
|
|
576
|
+
.option("--json", "Print a machine-readable JSON response.")
|
|
577
|
+
.addHelpText("after", [
|
|
578
|
+
"",
|
|
579
|
+
"Examples:",
|
|
580
|
+
" mimetic lab oss-smoke",
|
|
581
|
+
" mimetic lab oss-smoke --repos developit/mitt,lukeed/clsx",
|
|
582
|
+
" mimetic lab oss-smoke --limit 1 --keep --json",
|
|
583
|
+
"",
|
|
584
|
+
"Safety:",
|
|
585
|
+
" Only public GitHub owner/repo slugs are accepted. Clones live under ignored .mimetic/",
|
|
586
|
+
" runtime state and are removed by default."
|
|
587
|
+
].join("\n"))
|
|
588
|
+
.action(async (options, command) => {
|
|
589
|
+
await runOssSmokeAction({ command, io, options });
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
async function runOssSmokeAction(args) {
|
|
593
|
+
const limit = parsePositiveInteger(args.options.limit);
|
|
594
|
+
const labOptions = {
|
|
595
|
+
cwd: args.options.cwd,
|
|
596
|
+
limit: limit ?? Number.NaN,
|
|
597
|
+
repos: [...args.options.repo, ...(args.options.repos ? [args.options.repos] : [])],
|
|
598
|
+
...(args.options.keep === undefined ? {} : { keep: args.options.keep }),
|
|
599
|
+
...(args.options.runId === undefined ? {} : { runId: args.options.runId })
|
|
600
|
+
};
|
|
601
|
+
const result = await runOssLab(labOptions);
|
|
602
|
+
writeResult(args.command, args.io, result, formatOssLabHuman);
|
|
603
|
+
args.io.setExitCode(result.ok ? 0 : 2);
|
|
604
|
+
}
|
|
605
|
+
function writeResult(command, io, result, formatHuman) {
|
|
606
|
+
if (wantsJson(command)) {
|
|
607
|
+
io.writeOut(`${JSON.stringify(result, null, 2)}\n`);
|
|
608
|
+
}
|
|
609
|
+
else {
|
|
610
|
+
io.writeOut(formatHuman(result));
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
function formatDoctorHuman(result) {
|
|
614
|
+
return [
|
|
615
|
+
`mimetic doctor ${result.ok ? "ok" : "needs setup"}`,
|
|
616
|
+
`cwd: ${result.cwd}`,
|
|
617
|
+
...result.checks.map((check) => `- ${check.ok ? "ok" : "missing"} ${check.name}: ${check.message}`)
|
|
618
|
+
].join("\n") + "\n";
|
|
619
|
+
}
|
|
620
|
+
function formatRunHuman(result) {
|
|
621
|
+
if (!result.ok) {
|
|
622
|
+
return `${result.error?.code}: ${result.error?.message}\n`;
|
|
623
|
+
}
|
|
624
|
+
return [
|
|
625
|
+
`mimetic run ${result.mode}`,
|
|
626
|
+
`run: ${result.runId}`,
|
|
627
|
+
...(result.simCount === undefined ? [] : [`sims: ${result.simCount}`]),
|
|
628
|
+
`bundle: ${result.bundlePath}`,
|
|
629
|
+
`review: ${result.reviewPath}`,
|
|
630
|
+
...result.warnings.map((warning) => `warning: ${warning}`)
|
|
631
|
+
].join("\n") + "\n";
|
|
632
|
+
}
|
|
633
|
+
function formatVerifyHuman(result) {
|
|
634
|
+
return [
|
|
635
|
+
`mimetic verify ${result.ok ? "passed" : "failed"}`,
|
|
636
|
+
`run: ${result.run}`,
|
|
637
|
+
...result.checks.map((check) => `- ${check.ok ? "ok" : "fail"} ${check.name}: ${check.message}`)
|
|
638
|
+
].join("\n") + "\n";
|
|
639
|
+
}
|
|
640
|
+
function formatRunsHuman(result) {
|
|
641
|
+
if (result.runs.length === 0) {
|
|
642
|
+
return `No Mimetic runs found in ${result.cwd}\n`;
|
|
643
|
+
}
|
|
644
|
+
return [
|
|
645
|
+
`latest: ${result.latest ?? "none"}`,
|
|
646
|
+
...result.runs.map((run) => `- ${run.runId} ${run.mode ?? "unknown"} ${run.createdAt ?? "unknown"} ${run.path}`)
|
|
647
|
+
].join("\n") + "\n";
|
|
648
|
+
}
|
|
649
|
+
function formatObserverHuman(result) {
|
|
650
|
+
if (!result.ok) {
|
|
651
|
+
return `${result.error?.code}: ${result.error?.message}\n`;
|
|
652
|
+
}
|
|
653
|
+
return [
|
|
654
|
+
"mimetic observer rendered",
|
|
655
|
+
`run: ${result.run}`,
|
|
656
|
+
`observer: ${result.observerPath}`,
|
|
657
|
+
...(result.observerUrl ? [`url: ${result.observerUrl}`] : []),
|
|
658
|
+
...(result.opened === undefined ? [] : [`opened: ${result.opened ? "yes" : "no"}`]),
|
|
659
|
+
`bundle: ${result.bundlePath}`,
|
|
660
|
+
...result.warnings.map((warning) => `warning: ${warning}`)
|
|
661
|
+
].join("\n") + "\n";
|
|
662
|
+
}
|
|
663
|
+
function parsePositiveInteger(value) {
|
|
664
|
+
if (!/^\d+$/.test(value)) {
|
|
665
|
+
return null;
|
|
666
|
+
}
|
|
667
|
+
const parsed = Number.parseInt(value, 10);
|
|
668
|
+
return parsed >= 1 && parsed <= 64 ? parsed : null;
|
|
669
|
+
}
|
|
670
|
+
function parseObserverPort(value) {
|
|
671
|
+
if (!/^\d+$/.test(value)) {
|
|
672
|
+
return null;
|
|
673
|
+
}
|
|
674
|
+
const parsed = Number.parseInt(value, 10);
|
|
675
|
+
return parsed >= 0 && parsed <= 65535 ? parsed : null;
|
|
676
|
+
}
|
|
677
|
+
async function followObserver(io, result, server) {
|
|
678
|
+
io.writeOut(`watching: ${result.serverUrl ?? result.observerUrl ?? result.observerPath}\n`);
|
|
679
|
+
io.writeOut("watching: press Ctrl-C to stop\n");
|
|
680
|
+
await new Promise((resolve) => {
|
|
681
|
+
process.once("SIGINT", () => {
|
|
682
|
+
server.close()
|
|
683
|
+
.catch((error) => {
|
|
684
|
+
io.writeErr(`watch cleanup failed: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
685
|
+
})
|
|
686
|
+
.finally(() => {
|
|
687
|
+
io.writeOut("watch stopped\n");
|
|
688
|
+
resolve();
|
|
689
|
+
});
|
|
690
|
+
});
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
function formatFeedbackHuman(result) {
|
|
694
|
+
if (!result.ok) {
|
|
695
|
+
return `${result.error?.code}: ${result.error?.message}\n`;
|
|
696
|
+
}
|
|
697
|
+
return [
|
|
698
|
+
"mimetic feedback ready",
|
|
699
|
+
`run: ${result.run}`,
|
|
700
|
+
...(result.draftPath ? [`draft: ${result.draftPath}`] : []),
|
|
701
|
+
...(result.issuePath ? [`issue: ${result.issuePath}`] : [])
|
|
702
|
+
].join("\n") + "\n";
|
|
703
|
+
}
|
|
704
|
+
function formatOssLabHuman(result) {
|
|
705
|
+
if (!result.ok && result.error) {
|
|
706
|
+
return `${result.error.code}: ${result.error.message}\n`;
|
|
707
|
+
}
|
|
708
|
+
return [
|
|
709
|
+
`mimetic lab oss-smoke ${result.ok ? "passed" : "failed"}`,
|
|
710
|
+
`run: ${result.runId}`,
|
|
711
|
+
...(result.reportMarkdownPath ? [`report: ${result.reportMarkdownPath}`] : []),
|
|
712
|
+
`sandbox: ${result.cleanup.kept ? result.sandboxPath : "removed"}`,
|
|
713
|
+
...result.repos.map((repo) => {
|
|
714
|
+
const passed = repo.steps.filter((step) => step.ok).length;
|
|
715
|
+
return `- ${repo.ok ? "ok" : "fail"} ${repo.repo}: ${passed}/${repo.steps.length} steps, ${repo.changedFiles.length} changed files in disposable clone`;
|
|
716
|
+
}),
|
|
717
|
+
...result.warnings.map((warning) => `warning: ${warning}`)
|
|
718
|
+
].join("\n") + "\n";
|
|
719
|
+
}
|
|
720
|
+
function formatOssMetaLabHuman(result) {
|
|
721
|
+
if (!result.ok && result.error) {
|
|
722
|
+
return `${result.error.code}: ${result.error.message}\n`;
|
|
723
|
+
}
|
|
724
|
+
return [
|
|
725
|
+
`mimetic lab oss ${result.dryRun ? "dry-run" : "watch"}`,
|
|
726
|
+
`run: ${result.runId ?? "not-created"}`,
|
|
727
|
+
`repos: ${result.repos.join(", ")}`,
|
|
728
|
+
...(result.count === undefined ? [] : [`desktops: ${result.count}`]),
|
|
729
|
+
...(result.observer?.observerPath ? [`observer: ${result.observer.observerPath}`] : []),
|
|
730
|
+
...(result.observer?.observerUrl ? [`url: ${result.observer.observerUrl}`] : []),
|
|
731
|
+
...(result.observer?.opened === undefined ? [] : [`opened: ${result.observer.opened ? "yes" : "no"}`]),
|
|
732
|
+
...(result.observer?.bundlePath ? [`bundle: ${result.observer.bundlePath}`] : []),
|
|
733
|
+
...result.assignments.map((assignment) => `- ${String(assignment.index).padStart(2, "0")} ${assignment.repo}: top-level desktop lane -> nested Mimetic Observer`),
|
|
734
|
+
...result.sandboxes.map((sandbox) => {
|
|
735
|
+
const sandboxLabel = sandbox.sandboxId ? ` sandbox=${sandbox.sandboxId}` : "";
|
|
736
|
+
const bootstrapLabel = sandbox.bootstrapStatus ? ` bootstrap=${sandbox.bootstrapStatus}` : "";
|
|
737
|
+
return `sandbox ${sandbox.streamId}: ${sandbox.repo} stream=${sandbox.urlPresent ? "connected" : "missing"}${bootstrapLabel}${sandboxLabel}`;
|
|
738
|
+
}),
|
|
739
|
+
...result.warnings.map((warning) => `warning: ${warning}`)
|
|
740
|
+
].join("\n") + "\n";
|
|
741
|
+
}
|
|
742
|
+
function collectRepeated(value, previous) {
|
|
743
|
+
return [...previous, value];
|
|
744
|
+
}
|
|
745
|
+
function formatInitHuman(result) {
|
|
746
|
+
const title = result.ok
|
|
747
|
+
? `mimetic init ${result.mode}`
|
|
748
|
+
: `mimetic init ${result.mode} blocked`;
|
|
749
|
+
const lines = [
|
|
750
|
+
title,
|
|
751
|
+
`cwd: ${result.cwd}`,
|
|
752
|
+
"",
|
|
753
|
+
"changes:",
|
|
754
|
+
...result.changes.map(formatInitChange)
|
|
755
|
+
];
|
|
756
|
+
if (result.warnings.length > 0) {
|
|
757
|
+
lines.push("", "warnings:", ...result.warnings.map((warning) => `- ${warning}`));
|
|
758
|
+
}
|
|
759
|
+
if (result.error) {
|
|
760
|
+
lines.push("", `${result.error.code}: ${result.error.message}`);
|
|
761
|
+
}
|
|
762
|
+
if (result.mode === "needs-confirmation") {
|
|
763
|
+
lines.push("", "Run with --dry-run --json to inspect or --yes to apply.");
|
|
764
|
+
}
|
|
765
|
+
return `${lines.join("\n")}\n`;
|
|
766
|
+
}
|
|
767
|
+
function formatInitChange(change) {
|
|
768
|
+
return `- ${change.action.padEnd(6)} ${change.path} (${change.target}: ${change.reason})`;
|
|
769
|
+
}
|
|
770
|
+
function registerUnsupportedCommand(parent, plannedCommand, io, commandName = plannedCommand.name) {
|
|
771
|
+
const command = parent.command(commandName).description(plannedCommand.description);
|
|
772
|
+
command.option("--json", "Print a machine-readable JSON response.");
|
|
773
|
+
for (const option of plannedCommand.options ?? []) {
|
|
774
|
+
if (option.defaultValue === undefined) {
|
|
775
|
+
command.option(option.flags, option.description);
|
|
776
|
+
}
|
|
777
|
+
else {
|
|
778
|
+
command.option(option.flags, option.description, option.defaultValue);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
command.action((_options, actionCommand) => {
|
|
782
|
+
emitUnsupported(plannedCommand, actionCommand, io);
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
function emitUnsupported(plannedCommand, command, io) {
|
|
786
|
+
const envelope = {
|
|
787
|
+
schema: CLI_RESPONSE_SCHEMA,
|
|
788
|
+
ok: false,
|
|
789
|
+
command: plannedCommand.name,
|
|
790
|
+
error: {
|
|
791
|
+
code: "MIMETIC_UNIMPLEMENTED",
|
|
792
|
+
message: `${plannedCommand.name} is planned but not implemented in this scaffold slice.`
|
|
793
|
+
},
|
|
794
|
+
docs: plannedCommand.docs,
|
|
795
|
+
issue: plannedCommand.issue,
|
|
796
|
+
capabilities: {
|
|
797
|
+
githubMutation: false,
|
|
798
|
+
providerSpend: false,
|
|
799
|
+
productionData: false
|
|
800
|
+
}
|
|
801
|
+
};
|
|
802
|
+
if (wantsJson(command)) {
|
|
803
|
+
io.writeOut(`${JSON.stringify(envelope, null, 2)}\n`);
|
|
804
|
+
}
|
|
805
|
+
else {
|
|
806
|
+
io.writeErr([
|
|
807
|
+
`mimetic ${plannedCommand.name} is planned but not implemented yet.`,
|
|
808
|
+
`Track: ${plannedCommand.issue}`,
|
|
809
|
+
`Docs: ${plannedCommand.docs.join(", ")}`,
|
|
810
|
+
"This scaffold does not mutate GitHub, spend provider credits, or use production data."
|
|
811
|
+
].join("\n") + "\n");
|
|
812
|
+
}
|
|
813
|
+
io.setExitCode(2);
|
|
814
|
+
}
|
|
815
|
+
function wantsJson(command) {
|
|
816
|
+
let current = command;
|
|
817
|
+
while (current) {
|
|
818
|
+
if (current.opts().json === true) {
|
|
819
|
+
return true;
|
|
820
|
+
}
|
|
821
|
+
current = current.parent ?? null;
|
|
822
|
+
}
|
|
823
|
+
return false;
|
|
824
|
+
}
|
|
825
|
+
//# sourceMappingURL=program.js.map
|