switchboard-cli 0.1.0-alpha.4 → 0.1.0-alpha.5
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/CHANGELOG.md +19 -0
- package/README.md +121 -65
- package/bin/switchboard.js +75 -0
- package/dist/index.cjs +14371 -0
- package/package.json +45 -8
- package/bin/switchboard.mjs +0 -49
- package/src/autoagent/boundary-check.spec.ts +0 -77
- package/src/autoagent/boundary-check.ts +0 -102
- package/src/autoagent/loop.ts +0 -327
- package/src/autoagent/results.spec.ts +0 -73
- package/src/autoagent/results.ts +0 -68
- package/src/autoagent/runner.spec.ts +0 -20
- package/src/autoagent/runner.ts +0 -92
- package/src/autoagent/types.ts +0 -64
- package/src/commands/audit-codex.ts +0 -266
- package/src/commands/autoagent.ts +0 -108
- package/src/commands/calibrate.ts +0 -70
- package/src/commands/compile.ts +0 -117
- package/src/commands/evaluate.ts +0 -103
- package/src/commands/ingest.ts +0 -250
- package/src/commands/init.ts +0 -133
- package/src/commands/packet.ts +0 -408
- package/src/commands/receipt.ts +0 -336
- package/src/commands/run-claude.ts +0 -355
- package/src/index.ts +0 -47
- package/src/lib/draft-return.ts +0 -278
- package/src/lib/drift-guard.ts +0 -105
- package/src/lib/errors.ts +0 -61
- package/src/lib/output.ts +0 -43
- package/src/lib/paths.ts +0 -125
- package/src/lib/proof.ts +0 -262
- package/src/lib/transport.ts +0 -276
- package/src/lib/yaml-io.ts +0 -62
- package/src/store/filesystem-store.ts +0 -326
package/src/commands/packet.ts
DELETED
|
@@ -1,408 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* switchboard packet <surface>
|
|
3
|
-
*
|
|
4
|
-
* Compiles an execution harness from canonical state, runs the meta-harness
|
|
5
|
-
* compile pass automatically when eligible, validates packet integrity,
|
|
6
|
-
* and writes the formatted dispatch packet to .switchboard/packets/.
|
|
7
|
-
*
|
|
8
|
-
* Supports all four V1 surfaces: claude, chatgpt, cursor, codex.
|
|
9
|
-
*
|
|
10
|
-
* Uses the best existing output shape for each surface:
|
|
11
|
-
* - claude-code: sdk_prompt packet (written as markdown dispatch file)
|
|
12
|
-
* - chatgpt: sdk_prompt packet (written as markdown dispatch file)
|
|
13
|
-
* - cursor: clipboard packet (paste-ready markdown with operating notes)
|
|
14
|
-
* - codex: sdk_prompt packet (written as markdown dispatch file)
|
|
15
|
-
*
|
|
16
|
-
* Additionally generates a Claude runtime projection when targeting claude-code,
|
|
17
|
-
* and an SBX bundle for cursor.
|
|
18
|
-
*
|
|
19
|
-
* Does not require the hosted web app, Supabase, or a running server.
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
import { randomBytes } from "crypto";
|
|
23
|
-
import { existsSync, writeFileSync, mkdirSync } from "fs";
|
|
24
|
-
import { join } from "path";
|
|
25
|
-
import type { Command } from "commander";
|
|
26
|
-
import {
|
|
27
|
-
compileExecutionHarness,
|
|
28
|
-
compileMetaHarness,
|
|
29
|
-
augmentPromptWithHarnessContext,
|
|
30
|
-
validateDispatchPacket,
|
|
31
|
-
formatFromHarness,
|
|
32
|
-
compileLoopContract,
|
|
33
|
-
getSurfacePlaybook,
|
|
34
|
-
handoffRecordSchema,
|
|
35
|
-
routingResultSchema,
|
|
36
|
-
type Surface,
|
|
37
|
-
type LoopContract,
|
|
38
|
-
type RoutingResult,
|
|
39
|
-
type DispatchPacket,
|
|
40
|
-
type PacketIntegrityResult,
|
|
41
|
-
type HandoffRecord,
|
|
42
|
-
} from "@switchboard/core";
|
|
43
|
-
import { generateSbxBundle } from "@switchboard/projections";
|
|
44
|
-
import { generateClaudeRuntimeProjection } from "@switchboard/projections";
|
|
45
|
-
import { requireRepoRoot, packetsDir } from "../lib/paths";
|
|
46
|
-
import {
|
|
47
|
-
loadContract,
|
|
48
|
-
loadCurrentState,
|
|
49
|
-
loadSpec,
|
|
50
|
-
specExists as checkSpecExists,
|
|
51
|
-
writePacket,
|
|
52
|
-
} from "../store/filesystem-store";
|
|
53
|
-
import * as out from "../lib/output";
|
|
54
|
-
import { withCliErrors } from "../lib/errors";
|
|
55
|
-
import { enforceDriftGuard } from "../lib/drift-guard";
|
|
56
|
-
|
|
57
|
-
// ---------------------------------------------------------------------------
|
|
58
|
-
// Surface alias normalization
|
|
59
|
-
// ---------------------------------------------------------------------------
|
|
60
|
-
|
|
61
|
-
const SURFACE_ALIASES: Record<string, Surface> = {
|
|
62
|
-
claude: "claude-code",
|
|
63
|
-
"claude-code": "claude-code",
|
|
64
|
-
chatgpt: "chatgpt",
|
|
65
|
-
gpt: "chatgpt",
|
|
66
|
-
cursor: "cursor",
|
|
67
|
-
codex: "codex",
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
function resolveSurface(raw: string): Surface | null {
|
|
71
|
-
return SURFACE_ALIASES[raw.toLowerCase()] ?? null;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// ---------------------------------------------------------------------------
|
|
75
|
-
// Forced-surface route builder
|
|
76
|
-
// ---------------------------------------------------------------------------
|
|
77
|
-
|
|
78
|
-
/** Surface-specific defaults for fallback, rationale, and return contract. */
|
|
79
|
-
const SURFACE_ROUTE_DEFAULTS: Record<Surface, {
|
|
80
|
-
fallback: Surface;
|
|
81
|
-
rationale: string;
|
|
82
|
-
return_condition: string;
|
|
83
|
-
}> = {
|
|
84
|
-
"claude-code": {
|
|
85
|
-
fallback: "codex",
|
|
86
|
-
rationale: "Operator-selected surface: Claude Code. Repo-native implementation with structured return.",
|
|
87
|
-
return_condition: "Return when the requested files are changed, relevant checks are run, and the implementation status is explicit.",
|
|
88
|
-
},
|
|
89
|
-
chatgpt: {
|
|
90
|
-
fallback: "claude-code",
|
|
91
|
-
rationale: "Operator-selected surface: ChatGPT. Structured clarification or compile pass.",
|
|
92
|
-
return_condition: "Return with one clear decision, one exact next objective, and any narrowed scope boundaries.",
|
|
93
|
-
},
|
|
94
|
-
cursor: {
|
|
95
|
-
fallback: "claude-code",
|
|
96
|
-
rationale: "Operator-selected surface: Cursor. Close-control, visual, or taste-heavy editing.",
|
|
97
|
-
return_condition: "Return when the UI changes are coherent, the changed files are listed, and any taste-sensitive decisions are captured.",
|
|
98
|
-
},
|
|
99
|
-
codex: {
|
|
100
|
-
fallback: "claude-code",
|
|
101
|
-
rationale: "Operator-selected surface: Codex. Bounded, sandboxed implementation slice.",
|
|
102
|
-
return_condition: "Return when the bounded code change is complete, tests pass in the sandbox, and any broader repo dependencies are listed.",
|
|
103
|
-
},
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Build a complete, internally coherent route for a forced surface.
|
|
108
|
-
* Every field is surface-appropriate — no leftover metadata from a
|
|
109
|
-
* different surface's routing decision.
|
|
110
|
-
*/
|
|
111
|
-
export function buildForcedSurfaceRoute(surface: Surface, objective: string): RoutingResult {
|
|
112
|
-
const defaults = SURFACE_ROUTE_DEFAULTS[surface];
|
|
113
|
-
return routingResultSchema.parse({
|
|
114
|
-
primary_recommendation: surface,
|
|
115
|
-
fallback_surface: defaults.fallback,
|
|
116
|
-
rationale: defaults.rationale,
|
|
117
|
-
next_objective: objective,
|
|
118
|
-
return_condition: defaults.return_condition,
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// ---------------------------------------------------------------------------
|
|
123
|
-
// Prompt generation from spec + loop
|
|
124
|
-
// ---------------------------------------------------------------------------
|
|
125
|
-
|
|
126
|
-
function buildDispatchPrompt(
|
|
127
|
-
specMarkdown: string,
|
|
128
|
-
loop: LoopContract,
|
|
129
|
-
surface: Surface,
|
|
130
|
-
): string {
|
|
131
|
-
const sections: string[] = [];
|
|
132
|
-
|
|
133
|
-
sections.push("# Execution Brief\n");
|
|
134
|
-
sections.push(specMarkdown.trim());
|
|
135
|
-
sections.push("\n---\n");
|
|
136
|
-
sections.push("## Objective\n");
|
|
137
|
-
sections.push(loop.objective);
|
|
138
|
-
sections.push("\n## Scope In\n");
|
|
139
|
-
sections.push(loop.scope_in.map(s => `- ${s}`).join("\n"));
|
|
140
|
-
sections.push("\n## Scope Out\n");
|
|
141
|
-
sections.push(loop.scope_out.map(s => `- ${s}`).join("\n"));
|
|
142
|
-
sections.push("\n## Done When\n");
|
|
143
|
-
sections.push(loop.done_when.map(s => `- ${s}`).join("\n"));
|
|
144
|
-
sections.push("\n## Success Criteria\n");
|
|
145
|
-
sections.push(loop.success_criteria.map(s => `- ${s}`).join("\n"));
|
|
146
|
-
sections.push("\n## Verify With\n");
|
|
147
|
-
sections.push(loop.verify_with.map(s => `- ${s}`).join("\n"));
|
|
148
|
-
sections.push("\n## Return When\n");
|
|
149
|
-
sections.push(loop.return_when);
|
|
150
|
-
sections.push("\n## Blocker Escalation\n");
|
|
151
|
-
sections.push(loop.blocker_escalation.map(s => `- ${s}`).join("\n"));
|
|
152
|
-
|
|
153
|
-
return sections.join("\n");
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// ---------------------------------------------------------------------------
|
|
157
|
-
// Packet file rendering
|
|
158
|
-
// ---------------------------------------------------------------------------
|
|
159
|
-
|
|
160
|
-
function renderPacketFile(packet: DispatchPacket, integrityResult: PacketIntegrityResult): string {
|
|
161
|
-
const sections: string[] = [];
|
|
162
|
-
|
|
163
|
-
sections.push("---");
|
|
164
|
-
sections.push(`dispatch_id: ${packet.metadata.dispatch_id}`);
|
|
165
|
-
sections.push(`surface: ${packet.surface}`);
|
|
166
|
-
sections.push(`format: ${packet.format}`);
|
|
167
|
-
sections.push(`integrity: ${integrityResult.outcome}`);
|
|
168
|
-
sections.push(`generated_at: ${new Date().toISOString()}`);
|
|
169
|
-
sections.push("---\n");
|
|
170
|
-
|
|
171
|
-
if (packet.format === "clipboard") {
|
|
172
|
-
sections.push(packet.preamble);
|
|
173
|
-
sections.push("\n---\n");
|
|
174
|
-
sections.push(packet.body);
|
|
175
|
-
sections.push("\n---\n");
|
|
176
|
-
sections.push(packet.return_instructions);
|
|
177
|
-
} else if (packet.format === "sdk_prompt") {
|
|
178
|
-
sections.push(`# Switchboard Dispatch — ${packet.metadata.contract_title}\n`);
|
|
179
|
-
sections.push(`**Surface:** ${packet.surface}`);
|
|
180
|
-
sections.push(`**Dispatch ID:** ${packet.metadata.dispatch_id}`);
|
|
181
|
-
sections.push(`**Objective:** ${packet.metadata.objective}\n`);
|
|
182
|
-
if (packet.working_directory) {
|
|
183
|
-
sections.push(`**Working directory:** ${packet.working_directory}\n`);
|
|
184
|
-
}
|
|
185
|
-
sections.push("---\n");
|
|
186
|
-
sections.push(packet.prompt);
|
|
187
|
-
} else if (packet.format === "file_drop") {
|
|
188
|
-
sections.push(packet.content);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// Integrity findings
|
|
192
|
-
if (integrityResult.findings.length > 0) {
|
|
193
|
-
sections.push("\n---\n");
|
|
194
|
-
sections.push("## Packet Integrity Findings\n");
|
|
195
|
-
for (const f of integrityResult.findings) {
|
|
196
|
-
const icon = f.severity === "blocking" ? "BLOCKED" : "CAUTION";
|
|
197
|
-
sections.push(`- [${icon}] ${f.field}: ${f.detail}`);
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
return sections.join("\n");
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// ---------------------------------------------------------------------------
|
|
205
|
-
// Command registration
|
|
206
|
-
// ---------------------------------------------------------------------------
|
|
207
|
-
|
|
208
|
-
export function registerPacketCommand(program: Command): void {
|
|
209
|
-
program
|
|
210
|
-
.command("packet")
|
|
211
|
-
.argument("<surface>", "Target surface: claude, chatgpt, cursor, codex")
|
|
212
|
-
.description("Generate a governed dispatch packet for a target surface")
|
|
213
|
-
.option("--force", "Generate even if integrity check has blocking findings")
|
|
214
|
-
.action(withCliErrors(async (surfaceArg: string, opts: { force?: boolean }) => {
|
|
215
|
-
const repoRoot = requireRepoRoot();
|
|
216
|
-
const surface = resolveSurface(surfaceArg);
|
|
217
|
-
|
|
218
|
-
if (!surface) {
|
|
219
|
-
out.fail(`Unknown surface: "${surfaceArg}". Use: claude, chatgpt, cursor, codex`);
|
|
220
|
-
process.exitCode = 1;
|
|
221
|
-
return;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
out.heading(`switchboard packet ${surface}`);
|
|
225
|
-
|
|
226
|
-
// 1. Load canonical state
|
|
227
|
-
const contract = loadContract(repoRoot);
|
|
228
|
-
const current = loadCurrentState(repoRoot);
|
|
229
|
-
const spec = loadSpec(repoRoot) ?? "";
|
|
230
|
-
|
|
231
|
-
out.success(`Loaded contract: ${contract.title}`);
|
|
232
|
-
|
|
233
|
-
// 1b. Drift guard: detect loop.yaml / current.yaml objective mismatch
|
|
234
|
-
if (!enforceDriftGuard(repoRoot, current, !!opts.force)) {
|
|
235
|
-
process.exitCode = 1;
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// 2. Build a complete, surface-coherent route for the requested surface.
|
|
240
|
-
// Always recompile the loop rather than reusing a cached loop.yaml,
|
|
241
|
-
// because loop defaults (done_when, verify_with, success_criteria,
|
|
242
|
-
// return_when, fallback_surface) are all surface-sensitive.
|
|
243
|
-
const route = buildForcedSurfaceRoute(surface, current.next_objective);
|
|
244
|
-
|
|
245
|
-
const loop = compileLoopContract({ contract, current, route });
|
|
246
|
-
out.section("Objective", loop.objective);
|
|
247
|
-
out.section("Surface", surface);
|
|
248
|
-
|
|
249
|
-
// 3. Generate dispatch ID
|
|
250
|
-
const dispatchId = `dsp-${randomBytes(8).toString("hex")}`;
|
|
251
|
-
|
|
252
|
-
const harness = compileExecutionHarness({
|
|
253
|
-
dispatch_id: dispatchId,
|
|
254
|
-
contract,
|
|
255
|
-
loop,
|
|
256
|
-
route,
|
|
257
|
-
working_directory: repoRoot,
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
out.success("Compiled execution harness");
|
|
261
|
-
out.section("Dispatch mode", harness.dispatch_mode);
|
|
262
|
-
out.section("Packet format", harness.packet_format);
|
|
263
|
-
|
|
264
|
-
// 5. Run meta-harness compile pass (backstage, automatic)
|
|
265
|
-
const metaResult = compileMetaHarness({
|
|
266
|
-
contract,
|
|
267
|
-
loop,
|
|
268
|
-
dispatchId,
|
|
269
|
-
specMarkdown: spec,
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
if (!metaResult.summary.selection.was_no_op) {
|
|
273
|
-
out.success(`Meta-harness: +${metaResult.summary.context_chars_added} chars context`);
|
|
274
|
-
} else {
|
|
275
|
-
out.info(`Meta-harness: no-op (${metaResult.summary.selection.no_op_reason})`);
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
// 6. Build dispatch prompt (spec + loop scope + meta-harness augmentation)
|
|
279
|
-
let prompt = buildDispatchPrompt(spec, loop, surface);
|
|
280
|
-
prompt = augmentPromptWithHarnessContext(prompt, metaResult.contextSections);
|
|
281
|
-
|
|
282
|
-
// 7. Validate packet integrity
|
|
283
|
-
const integrity = validateDispatchPacket({
|
|
284
|
-
contract,
|
|
285
|
-
loop,
|
|
286
|
-
prompt,
|
|
287
|
-
specMarkdown: spec,
|
|
288
|
-
specExists: checkSpecExists(repoRoot),
|
|
289
|
-
repoExists: contract.repo_status === "repo-present",
|
|
290
|
-
hasActiveHandoff: !!current.active_handoff_id,
|
|
291
|
-
});
|
|
292
|
-
|
|
293
|
-
if (integrity.outcome === "blocked" && !opts.force) {
|
|
294
|
-
out.fail(`Packet integrity: BLOCKED (${integrity.blocking_findings_count} blocking finding(s))`);
|
|
295
|
-
for (const f of integrity.findings.filter(f => f.severity === "blocking")) {
|
|
296
|
-
out.bullet(`${f.field}: ${f.detail}`);
|
|
297
|
-
}
|
|
298
|
-
out.info("Fix the blocking findings or use --force to generate anyway.");
|
|
299
|
-
process.exitCode = 1;
|
|
300
|
-
return;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
if (integrity.outcome === "ready_with_cautions") {
|
|
304
|
-
out.warn(`Packet integrity: ready with ${integrity.caution_findings_count} caution(s)`);
|
|
305
|
-
for (const f of integrity.findings.filter(f => f.severity === "caution")) {
|
|
306
|
-
out.bullet(`${f.field}: ${f.detail}`);
|
|
307
|
-
}
|
|
308
|
-
} else if (integrity.outcome === "ready") {
|
|
309
|
-
out.success("Packet integrity: ready");
|
|
310
|
-
} else if (opts.force) {
|
|
311
|
-
out.warn(`Packet integrity: BLOCKED (forced)`);
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// 8. Format the packet using existing dispatch formatter
|
|
315
|
-
const packet = formatFromHarness(harness, prompt);
|
|
316
|
-
|
|
317
|
-
// 9. Write packet file
|
|
318
|
-
const timestamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
319
|
-
const packetFilename = `${surface}-${timestamp}-${dispatchId.slice(4, 12)}.md`;
|
|
320
|
-
const packetContent = renderPacketFile(packet, integrity);
|
|
321
|
-
const packetPath = writePacket(repoRoot, packetFilename, packetContent);
|
|
322
|
-
|
|
323
|
-
out.fileWritten("Dispatch packet", packetPath);
|
|
324
|
-
|
|
325
|
-
// 10. Build a HandoffRecord for projection generators
|
|
326
|
-
const handoff: HandoffRecord = handoffRecordSchema.parse({
|
|
327
|
-
handoff_id: dispatchId,
|
|
328
|
-
surface,
|
|
329
|
-
objective: loop.objective,
|
|
330
|
-
included_scope: loop.scope_in,
|
|
331
|
-
excluded_scope: loop.scope_out,
|
|
332
|
-
done_when: loop.done_when,
|
|
333
|
-
return_condition: loop.return_when,
|
|
334
|
-
loop,
|
|
335
|
-
readiness: { status: "ready" as const, issues: [] },
|
|
336
|
-
bundle_version: "SBX-v1" as const,
|
|
337
|
-
launched_at: new Date().toISOString(),
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
// 11. Surface-specific extras
|
|
341
|
-
if (surface === "claude-code") {
|
|
342
|
-
// Generate Claude runtime projection (CLAUDE.md + rules)
|
|
343
|
-
try {
|
|
344
|
-
const playbook = getSurfacePlaybook(surface);
|
|
345
|
-
const projection = generateClaudeRuntimeProjection({
|
|
346
|
-
contract,
|
|
347
|
-
current,
|
|
348
|
-
spec_markdown: spec,
|
|
349
|
-
handoff,
|
|
350
|
-
playbook,
|
|
351
|
-
});
|
|
352
|
-
|
|
353
|
-
const projDir = join(packetsDir(repoRoot), `claude-projection-${dispatchId.slice(4, 12)}`);
|
|
354
|
-
if (!existsSync(projDir)) mkdirSync(projDir, { recursive: true });
|
|
355
|
-
|
|
356
|
-
for (const file of projection.files) {
|
|
357
|
-
const filePath = join(projDir, file.path);
|
|
358
|
-
const fileDir = join(projDir, file.path.split("/").slice(0, -1).join("/"));
|
|
359
|
-
if (fileDir !== projDir && !existsSync(fileDir)) {
|
|
360
|
-
mkdirSync(fileDir, { recursive: true });
|
|
361
|
-
}
|
|
362
|
-
writeFileSync(filePath, file.content, "utf-8");
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
out.fileWritten("Claude runtime projection", projDir);
|
|
366
|
-
} catch (e) {
|
|
367
|
-
out.warn(`Could not generate Claude projection: ${e instanceof Error ? e.message : String(e)}`);
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
if (surface === "cursor") {
|
|
372
|
-
// Generate SBX bundle files (paste-ready bundle structure)
|
|
373
|
-
try {
|
|
374
|
-
const bundle = generateSbxBundle({
|
|
375
|
-
contract,
|
|
376
|
-
current,
|
|
377
|
-
spec_markdown: spec,
|
|
378
|
-
handoff,
|
|
379
|
-
});
|
|
380
|
-
|
|
381
|
-
const bundleDir = join(packetsDir(repoRoot), `sbx-bundle-${dispatchId.slice(4, 12)}`);
|
|
382
|
-
if (!existsSync(bundleDir)) mkdirSync(bundleDir, { recursive: true });
|
|
383
|
-
|
|
384
|
-
for (const file of bundle.files) {
|
|
385
|
-
writeFileSync(join(bundleDir, file.path), file.content, "utf-8");
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
out.fileWritten("SBX bundle", bundleDir);
|
|
389
|
-
} catch (e) {
|
|
390
|
-
out.warn(`Could not generate SBX bundle: ${e instanceof Error ? e.message : String(e)}`);
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
// Summary
|
|
395
|
-
out.heading("Packet ready");
|
|
396
|
-
out.section("Dispatch ID", dispatchId);
|
|
397
|
-
out.section("Surface", surface);
|
|
398
|
-
out.section("Integrity", integrity.outcome);
|
|
399
|
-
|
|
400
|
-
if (surface === "cursor") {
|
|
401
|
-
out.bullet("Copy the packet contents into Cursor or use the SBX bundle files");
|
|
402
|
-
} else if (surface === "claude-code") {
|
|
403
|
-
out.bullet("Use the Claude runtime projection or paste the packet into Claude Code");
|
|
404
|
-
} else {
|
|
405
|
-
out.bullet("Copy the packet contents into the target surface");
|
|
406
|
-
}
|
|
407
|
-
}));
|
|
408
|
-
}
|