@linzumi/cli 1.0.18 → 1.0.20

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.
@@ -0,0 +1,198 @@
1
+ /*
2
+ - Date: 2026-06-29
3
+ Spec: plans/2026-06-29-agent-backend-replay-orchestrator-spec.md
4
+ Relationship: One-command local browser proof wrapper for the real backend
5
+ replay path. It runs replay-agent-backend first, then points Playwright at the
6
+ real frontend thread route and backend-produced summary artifact.
7
+ */
8
+ import { spawn } from 'node:child_process';
9
+ import { existsSync } from 'node:fs';
10
+ import { mkdir } from 'node:fs/promises';
11
+ import { dirname, join, resolve } from 'node:path';
12
+ import { fileURLToPath } from 'node:url';
13
+
14
+ const scriptDir = dirname(fileURLToPath(import.meta.url));
15
+ const packageRoot = resolve(scriptDir, '..');
16
+ const repoRoot = resolve(packageRoot, '..', '..');
17
+ const webRoot = join(repoRoot, 'kandan', 'server_v2', 'web');
18
+
19
+ type BrowserReplayOptions = {
20
+ readonly backendArgs: readonly string[];
21
+ readonly backendUrl: string;
22
+ readonly outDir: string;
23
+ readonly summaryPath: string;
24
+ readonly browserAuthPath: string;
25
+ readonly explicitBrowserAuthPath: boolean;
26
+ };
27
+
28
+ async function main(): Promise<void> {
29
+ const args = stripLeadingSeparator(process.argv.slice(2));
30
+
31
+ if (args.includes('--help')) {
32
+ printHelp();
33
+ return;
34
+ }
35
+
36
+ const options = parseArgs(args);
37
+ await mkdir(options.outDir, { recursive: true });
38
+ await runBackendReplay(options);
39
+ await assertReplayArtifacts(options);
40
+ await runBrowserProof(options);
41
+ }
42
+
43
+ function stripLeadingSeparator(args: readonly string[]): readonly string[] {
44
+ return args[0] === '--' ? args.slice(1) : args;
45
+ }
46
+
47
+ function parseArgs(args: readonly string[]): BrowserReplayOptions {
48
+ const backendUrl = argValue(args, '--backend-url');
49
+
50
+ if (backendUrl === undefined) {
51
+ throw new Error('provide --backend-url <url>');
52
+ }
53
+
54
+ if (args.includes('--dry-run')) {
55
+ throw new Error(
56
+ 'browser proof requires a real backend replay; remove --dry-run'
57
+ );
58
+ }
59
+
60
+ if (args.includes('--probe-backend')) {
61
+ throw new Error(
62
+ 'browser proof requires a real backend replay; remove --probe-backend'
63
+ );
64
+ }
65
+
66
+ const outDir = resolve(
67
+ argValue(args, '--out-dir') ?? 'agent-backend-browser-replay-output'
68
+ );
69
+ const explicitBrowserAuth = argValue(args, '--browser-auth-file');
70
+ const browserAuthPath = resolve(
71
+ explicitBrowserAuth ?? join(outDir, 'browser-auth.json')
72
+ );
73
+ const backendArgs =
74
+ explicitBrowserAuth === undefined
75
+ ? [...args, '--browser-auth-file', browserAuthPath]
76
+ : [...args];
77
+
78
+ return {
79
+ backendArgs,
80
+ backendUrl,
81
+ outDir,
82
+ summaryPath: join(outDir, 'summary.json'),
83
+ browserAuthPath,
84
+ explicitBrowserAuthPath: explicitBrowserAuth !== undefined,
85
+ };
86
+ }
87
+
88
+ async function runBackendReplay(options: BrowserReplayOptions): Promise<void> {
89
+ await runCommand(
90
+ 'pnpm',
91
+ [
92
+ '--filter',
93
+ '@linzumi/cli',
94
+ 'run',
95
+ 'replay:agent-backend',
96
+ '--',
97
+ ...options.backendArgs,
98
+ ],
99
+ repoRoot,
100
+ process.env
101
+ );
102
+ }
103
+
104
+ async function assertReplayArtifacts(
105
+ options: BrowserReplayOptions
106
+ ): Promise<void> {
107
+ if (!existsSync(options.summaryPath)) {
108
+ throw new Error(
109
+ `backend replay did not write summary artifact: ${options.summaryPath}`
110
+ );
111
+ }
112
+
113
+ if (!existsSync(options.browserAuthPath)) {
114
+ const hint = options.explicitBrowserAuthPath
115
+ ? 'check --browser-auth-file path'
116
+ : 'the wrapper should have passed --browser-auth-file automatically';
117
+ throw new Error(
118
+ `backend replay did not write browser auth artifact: ${options.browserAuthPath}; ${hint}`
119
+ );
120
+ }
121
+ }
122
+
123
+ async function runBrowserProof(options: BrowserReplayOptions): Promise<void> {
124
+ await runCommand(
125
+ 'bun',
126
+ ['run', 'replay-harness:agent-backend-browser'],
127
+ webRoot,
128
+ {
129
+ ...process.env,
130
+ KANDAN_REPLAY_BASE_URL: options.backendUrl,
131
+ KANDAN_AGENT_BACKEND_REPLAY_SUMMARY: options.summaryPath,
132
+ KANDAN_AGENT_BACKEND_REPLAY_BROWSER_AUTH: options.browserAuthPath,
133
+ }
134
+ );
135
+ }
136
+
137
+ function runCommand(
138
+ command: string,
139
+ args: readonly string[],
140
+ cwd: string,
141
+ env: NodeJS.ProcessEnv
142
+ ): Promise<void> {
143
+ return new Promise((resolvePromise, reject) => {
144
+ const child = spawn(command, args, {
145
+ cwd,
146
+ env,
147
+ stdio: 'inherit',
148
+ });
149
+
150
+ child.on('error', reject);
151
+ child.on('exit', (code, signal) => {
152
+ if (code === 0) {
153
+ resolvePromise();
154
+ return;
155
+ }
156
+
157
+ reject(
158
+ new Error(
159
+ `${command} ${args.join(' ')} failed with ${
160
+ signal === null
161
+ ? `exit code ${code ?? 'unknown'}`
162
+ : `signal ${signal}`
163
+ }`
164
+ )
165
+ );
166
+ });
167
+ });
168
+ }
169
+
170
+ function argValue(args: readonly string[], name: string): string | undefined {
171
+ const index = args.indexOf(name);
172
+
173
+ if (index === -1) {
174
+ return undefined;
175
+ }
176
+
177
+ const value = args[index + 1];
178
+
179
+ if (value === undefined) {
180
+ throw new Error(`${name} requires a value`);
181
+ }
182
+
183
+ return value;
184
+ }
185
+
186
+ function printHelp(): void {
187
+ process.stdout.write(
188
+ `Usage: pnpm --filter @linzumi/cli run replay:agent-backend-browser -- --fixture <path> --backend-url <url> [backend replay options]\n\nThis runs the real backend replay first, then runs the Kandan web replay harness\nPlaywright browser proof against the generated summary.json and browser auth\nartifact. It forwards backend replay options to replay:agent-backend.\n\nRequired:\n --fixture <path> Agent replay fixture\n --backend-url <url> Running local Phoenix backend URL\n\nCommon options forwarded to replay:agent-backend:\n --provider auto|codex|claude-code\n --setup-fixture\n --out-dir <path> Default: agent-backend-browser-replay-output\n --speed immediate|realtime|<n>x|<n>ms\n --require-feature <featureId> Forwarded per-feature manifest/backend oracle gate
189
+ --oracle-feature <featureId> Candidate proof oracle without readiness gating\n --browser-auth-file <path> Optional; default: <out-dir>/browser-auth.json\n\nExample:\n pnpm --filter @linzumi/cli run replay:agent-backend-browser -- \\\n --fixture packages/linzumi-cli/test/fixtures/agent-raw-replay/real-probe/claude.ndjson \\\n --provider claude-code \\\n --backend-url http://127.0.0.1:4140 \\\n --setup-fixture \\\n --out-dir /tmp/agent-backend-replay-browser\n`
190
+ );
191
+ }
192
+
193
+ main().catch((error) => {
194
+ process.stderr.write(
195
+ `${error instanceof Error ? (error.stack ?? error.message) : String(error)}\n`
196
+ );
197
+ process.exitCode = 1;
198
+ });