@refrainai/cli 0.4.1

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.
Files changed (69) hide show
  1. package/dist/ai-model-FM6GWCID.js +37 -0
  2. package/dist/ai-model-FM6GWCID.js.map +1 -0
  3. package/dist/chunk-2BVDAJZT.js +236 -0
  4. package/dist/chunk-2BVDAJZT.js.map +1 -0
  5. package/dist/chunk-2H7UOFLK.js +11 -0
  6. package/dist/chunk-2H7UOFLK.js.map +1 -0
  7. package/dist/chunk-7UCVPKD4.js +902 -0
  8. package/dist/chunk-7UCVPKD4.js.map +1 -0
  9. package/dist/chunk-AG3CFMYU.js +36 -0
  10. package/dist/chunk-AG3CFMYU.js.map +1 -0
  11. package/dist/chunk-CLYJHKPY.js +1131 -0
  12. package/dist/chunk-CLYJHKPY.js.map +1 -0
  13. package/dist/chunk-D5SI2PHK.js +74 -0
  14. package/dist/chunk-D5SI2PHK.js.map +1 -0
  15. package/dist/chunk-DJVUITRB.js +9084 -0
  16. package/dist/chunk-DJVUITRB.js.map +1 -0
  17. package/dist/chunk-H47NWH7N.js +4427 -0
  18. package/dist/chunk-H47NWH7N.js.map +1 -0
  19. package/dist/chunk-HQDXLWAY.js +109 -0
  20. package/dist/chunk-HQDXLWAY.js.map +1 -0
  21. package/dist/chunk-IGFCYKHC.js +1974 -0
  22. package/dist/chunk-IGFCYKHC.js.map +1 -0
  23. package/dist/chunk-RT664YIO.js +245 -0
  24. package/dist/chunk-RT664YIO.js.map +1 -0
  25. package/dist/chunk-RYIJPYM3.js +164 -0
  26. package/dist/chunk-RYIJPYM3.js.map +1 -0
  27. package/dist/chunk-TDSM3UXI.js +40 -0
  28. package/dist/chunk-TDSM3UXI.js.map +1 -0
  29. package/dist/chunk-UGPXCQY3.js +778 -0
  30. package/dist/chunk-UGPXCQY3.js.map +1 -0
  31. package/dist/chunk-VPK2MQAZ.js +589 -0
  32. package/dist/chunk-VPK2MQAZ.js.map +1 -0
  33. package/dist/chunk-WEYR56ZN.js +953 -0
  34. package/dist/chunk-WEYR56ZN.js.map +1 -0
  35. package/dist/chunk-XMFCXPYU.js +275 -0
  36. package/dist/chunk-XMFCXPYU.js.map +1 -0
  37. package/dist/chunk-Z33FCOTZ.js +251 -0
  38. package/dist/chunk-Z33FCOTZ.js.map +1 -0
  39. package/dist/cli.js +59 -0
  40. package/dist/cli.js.map +1 -0
  41. package/dist/compose-MTSIJY5D.js +547 -0
  42. package/dist/compose-MTSIJY5D.js.map +1 -0
  43. package/dist/config-ZSUNCFXR.js +9 -0
  44. package/dist/config-ZSUNCFXR.js.map +1 -0
  45. package/dist/fix-runbook-ZSBOTLC2.js +294 -0
  46. package/dist/fix-runbook-ZSBOTLC2.js.map +1 -0
  47. package/dist/google-sheets-DRWIVEVC.js +482 -0
  48. package/dist/google-sheets-DRWIVEVC.js.map +1 -0
  49. package/dist/registry-LZLYTNDJ.js +17 -0
  50. package/dist/registry-LZLYTNDJ.js.map +1 -0
  51. package/dist/runbook-data-helpers-KRR2SH76.js +16 -0
  52. package/dist/runbook-data-helpers-KRR2SH76.js.map +1 -0
  53. package/dist/runbook-executor-K7T6RJWJ.js +1480 -0
  54. package/dist/runbook-executor-K7T6RJWJ.js.map +1 -0
  55. package/dist/runbook-generator-MPXJBQ5N.js +800 -0
  56. package/dist/runbook-generator-MPXJBQ5N.js.map +1 -0
  57. package/dist/runbook-schema-3T6TP3JJ.js +35 -0
  58. package/dist/runbook-schema-3T6TP3JJ.js.map +1 -0
  59. package/dist/runbook-store-G5GUOWRR.js +11 -0
  60. package/dist/runbook-store-G5GUOWRR.js.map +1 -0
  61. package/dist/schema-5G6UQSPT.js +91 -0
  62. package/dist/schema-5G6UQSPT.js.map +1 -0
  63. package/dist/server-AG3LXQBI.js +8778 -0
  64. package/dist/server-AG3LXQBI.js.map +1 -0
  65. package/dist/tenant-ai-config-QPFEJUVJ.js +14 -0
  66. package/dist/tenant-ai-config-QPFEJUVJ.js.map +1 -0
  67. package/dist/yaml-patcher-VGUS2JGH.js +15 -0
  68. package/dist/yaml-patcher-VGUS2JGH.js.map +1 -0
  69. package/package.json +37 -0
@@ -0,0 +1,800 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ buildRunbookYaml,
4
+ createReviewPrompt,
5
+ explore,
6
+ reviewResponseSchema
7
+ } from "./chunk-IGFCYKHC.js";
8
+ import {
9
+ formatAiMetricsSection,
10
+ formatDebugLogSections,
11
+ formatFailureCategoryDistribution,
12
+ formatPerformanceBottlenecks,
13
+ formatRuntimeEnvironment,
14
+ readDebugLog,
15
+ renderOptionsMarkdown,
16
+ serializeGeneratorOptions
17
+ } from "./chunk-VPK2MQAZ.js";
18
+ import {
19
+ checkHelpFlag,
20
+ getRawArgs,
21
+ parseModelConfig,
22
+ readContextFile
23
+ } from "./chunk-2BVDAJZT.js";
24
+ import {
25
+ createDebugLogger,
26
+ formatDuration,
27
+ loadSecrets
28
+ } from "./chunk-DJVUITRB.js";
29
+ import "./chunk-XMFCXPYU.js";
30
+ import "./chunk-AG3CFMYU.js";
31
+ import {
32
+ cancel,
33
+ getLocale,
34
+ initLocale,
35
+ intro,
36
+ log,
37
+ note,
38
+ outro,
39
+ promptMultiselect,
40
+ promptSelect,
41
+ promptText,
42
+ t,
43
+ tf
44
+ } from "./chunk-7UCVPKD4.js";
45
+ import {
46
+ getModel,
47
+ globalMetrics,
48
+ initModel,
49
+ trackedGenerateObject
50
+ } from "./chunk-UGPXCQY3.js";
51
+ import "./chunk-RT664YIO.js";
52
+ import "./chunk-2H7UOFLK.js";
53
+
54
+ // src/runbook-generator/index.ts
55
+ import { writeFile, mkdir } from "fs/promises";
56
+ import { dirname } from "path";
57
+
58
+ // src/runbook-generator/config.ts
59
+ import { parseArgs as nodeParseArgs } from "util";
60
+ async function parseArgs() {
61
+ checkHelpFlag("generate");
62
+ const args = getRawArgs();
63
+ const { values } = nodeParseArgs({
64
+ args,
65
+ options: {
66
+ url: { type: "string" },
67
+ goal: { type: "string" },
68
+ output: { type: "string" },
69
+ screenshots: { type: "string" },
70
+ context: { type: "string" },
71
+ secrets: { type: "string" },
72
+ "max-iterations": { type: "string" },
73
+ "step-delay": { type: "string" },
74
+ "stall-check-interval": { type: "string" },
75
+ "history-window": { type: "string" },
76
+ "max-failures": { type: "string" },
77
+ "no-snapshot-filter": { type: "boolean" },
78
+ "debug-log": { type: "string" },
79
+ debug: { type: "boolean" },
80
+ headless: { type: "string" },
81
+ model: { type: "string" },
82
+ "model-provider": { type: "string" },
83
+ "model-base-url": { type: "string" },
84
+ "model-selector": { type: "string" },
85
+ "model-extraction": { type: "string" },
86
+ "model-exploration": { type: "string" },
87
+ "model-review": { type: "string" },
88
+ "model-fallback": { type: "string" },
89
+ "model-exploration-light": { type: "string" },
90
+ "enable-multi-model": { type: "boolean" },
91
+ video: { type: "string" },
92
+ locale: { type: "string" },
93
+ stealth: { type: "boolean" },
94
+ proxy: { type: "string" },
95
+ skill: { type: "string" }
96
+ },
97
+ strict: true
98
+ });
99
+ initLocale(values.locale);
100
+ if (!values.url) {
101
+ cancel(t("cli.urlRequired"));
102
+ process.exit(1);
103
+ }
104
+ if (!values.goal) {
105
+ cancel(t("cli.goalRequired"));
106
+ process.exit(1);
107
+ }
108
+ if (!values.output) {
109
+ cancel(t("cli.outputRequired"));
110
+ process.exit(1);
111
+ }
112
+ if (!values.context) {
113
+ cancel(t("cli.contextRequired"));
114
+ process.exit(1);
115
+ }
116
+ const contextMarkdown = await readContextFile(values.context);
117
+ const secrets = await loadSecrets(values.secrets);
118
+ const aiModelConfig = parseModelConfig(values);
119
+ return {
120
+ url: values.url,
121
+ goal: values.goal,
122
+ locale: getLocale(),
123
+ output: values.output,
124
+ screenshotDir: values.screenshots,
125
+ contextMarkdown,
126
+ secrets,
127
+ maxIterations: values["max-iterations"] ? Number.parseInt(values["max-iterations"], 10) : 20,
128
+ stepDelay: values["step-delay"] ? Number.parseInt(values["step-delay"], 10) : 500,
129
+ headless: values.headless !== "false",
130
+ stallCheckInterval: values["stall-check-interval"] ? Number.parseInt(values["stall-check-interval"], 10) : 3,
131
+ historyWindow: values["history-window"] ? Number.parseInt(values["history-window"], 10) : 10,
132
+ maxConsecutiveFailures: values["max-failures"] ? Number.parseInt(values["max-failures"], 10) : 3,
133
+ snapshotFilter: !values["no-snapshot-filter"],
134
+ debugLogPath: values["debug-log"],
135
+ debugConsole: values.debug ?? false,
136
+ videoDir: values.video,
137
+ aiModelConfig,
138
+ enableMultiModel: values["enable-multi-model"] ?? false,
139
+ stealth: values.stealth ?? false,
140
+ proxy: values.proxy,
141
+ skills: values.skill?.split(",").map((s) => s.trim()).filter(Boolean)
142
+ };
143
+ }
144
+
145
+ // src/runbook-generator/review-editor.ts
146
+ function buildSummaryText(reviewResult, recordedSteps, humanGuidance) {
147
+ const lines = [];
148
+ const keptSteps = reviewResult.reviewedSteps.filter((s) => s.keep);
149
+ if (keptSteps.length > 0) {
150
+ lines.push(t("review.keptSteps"));
151
+ for (const step of keptSteps) {
152
+ const recorded = recordedSteps.find(
153
+ (r) => r.ordinal === step.originalOrdinal
154
+ );
155
+ const desc = recorded?.action.description ?? t("common.unknown");
156
+ lines.push(
157
+ ` #${step.originalOrdinal}: ${desc} [risk: ${step.riskLevel}]`
158
+ );
159
+ }
160
+ }
161
+ const confirmSteps = keptSteps.filter((s) => s.requiresConfirmation);
162
+ lines.push("");
163
+ lines.push(t("review.confirmationRequired"));
164
+ if (confirmSteps.length > 0) {
165
+ for (const step of confirmSteps) {
166
+ const recorded = recordedSteps.find(
167
+ (r) => r.ordinal === step.originalOrdinal
168
+ );
169
+ const desc = recorded?.action.description ?? t("common.unknown");
170
+ const reason = step.confirmationReason ? ` \u2014 ${step.confirmationReason}` : "";
171
+ lines.push(
172
+ ` #${step.originalOrdinal}: ${desc} [risk: ${step.riskLevel}]${reason}`
173
+ );
174
+ }
175
+ } else {
176
+ lines.push(" " + t("common.none"));
177
+ }
178
+ const removedSteps = reviewResult.reviewedSteps.filter((s) => !s.keep);
179
+ if (removedSteps.length > 0) {
180
+ lines.push("");
181
+ lines.push(t("review.removedSteps"));
182
+ for (const step of removedSteps) {
183
+ const recorded = recordedSteps.find(
184
+ (r) => r.ordinal === step.originalOrdinal
185
+ );
186
+ const desc = recorded?.action.description ?? t("common.unknown");
187
+ const reason = step.removalReason ?? "";
188
+ lines.push(` #${step.originalOrdinal}: ${desc} (\u7406\u7531: ${reason})`);
189
+ }
190
+ }
191
+ const captureSteps = recordedSteps.filter(
192
+ (r) => r.action.suggestedCaptures && r.action.suggestedCaptures.length > 0
193
+ );
194
+ if (captureSteps.length > 0) {
195
+ lines.push("");
196
+ lines.push(t("review.capturedValues"));
197
+ for (const r of captureSteps) {
198
+ for (const c of r.action.suggestedCaptures) {
199
+ lines.push(
200
+ ` Step #${r.ordinal} \u2192 ${c.name} (${c.strategy}${c.pattern ? `: ${c.pattern}` : ""})`
201
+ );
202
+ }
203
+ }
204
+ }
205
+ lines.push("");
206
+ lines.push(t("review.notes"));
207
+ if (humanGuidance.length > 0) {
208
+ for (const [i, n] of humanGuidance.entries()) {
209
+ lines.push(` ${tf("review.guidancePrefix", { index: i + 1 })} ${n}`);
210
+ }
211
+ } else {
212
+ lines.push(" " + t("common.none"));
213
+ }
214
+ lines.push("");
215
+ lines.push(tf("review.summaryLabel", { summary: reviewResult.summary }));
216
+ return lines.join("\n");
217
+ }
218
+ function cloneReviewResult(r) {
219
+ return {
220
+ reviewedSteps: r.reviewedSteps.map((s) => ({ ...s })),
221
+ summary: r.summary
222
+ };
223
+ }
224
+ function deriveSuggestedNotes(reviewResult, recordedSteps) {
225
+ const suggestions = [];
226
+ const highRiskSteps = reviewResult.reviewedSteps.filter(
227
+ (s) => s.keep && s.riskLevel === "high"
228
+ );
229
+ if (highRiskSteps.length > 0) {
230
+ const ordinals = highRiskSteps.map((s) => `#${s.originalOrdinal}`).join(", ");
231
+ suggestions.push(tf("review.highRiskNote", { ordinals }));
232
+ }
233
+ const credentialSteps = recordedSteps.filter(
234
+ (r) => r.action.inputCategory === "credential"
235
+ );
236
+ if (credentialSteps.length > 0) {
237
+ suggestions.push(t("review.credentialNote"));
238
+ }
239
+ const removedCount = reviewResult.reviewedSteps.filter(
240
+ (s) => !s.keep
241
+ ).length;
242
+ if (removedCount >= 3) {
243
+ suggestions.push(tf("review.removedStepsNote", { count: removedCount }));
244
+ }
245
+ if (reviewResult.summary) {
246
+ suggestions.push(`AI\u30EC\u30D3\u30E5\u30FC\u8981\u7D04: ${reviewResult.summary}`);
247
+ }
248
+ return suggestions;
249
+ }
250
+ async function handleToggleConfirmation(reviewResult, recordedSteps) {
251
+ const keptSteps = reviewResult.reviewedSteps.filter((s) => s.keep);
252
+ if (keptSteps.length === 0) {
253
+ log.warn("\u4FDD\u6301\u3055\u308C\u308B\u30B9\u30C6\u30C3\u30D7\u304C\u3042\u308A\u307E\u305B\u3093\u3002");
254
+ return;
255
+ }
256
+ const initialValues = keptSteps.filter((s) => s.requiresConfirmation).map((s) => s.originalOrdinal);
257
+ const selected = await promptMultiselect(
258
+ t("review.selectConfirmSteps"),
259
+ keptSteps.map((step) => {
260
+ const recorded = recordedSteps.find(
261
+ (r) => r.ordinal === step.originalOrdinal
262
+ );
263
+ const desc = recorded?.action.description ?? t("common.unknown");
264
+ return {
265
+ value: step.originalOrdinal,
266
+ label: `#${step.originalOrdinal}: ${desc}`,
267
+ hint: `risk: ${step.riskLevel}`
268
+ };
269
+ }),
270
+ { initialValues }
271
+ );
272
+ for (const step of keptSteps) {
273
+ step.requiresConfirmation = selected.includes(step.originalOrdinal);
274
+ }
275
+ }
276
+ async function handleEditNotes(humanGuidance, reviewResult, recordedSteps) {
277
+ const suggestions = deriveSuggestedNotes(reviewResult, recordedSteps);
278
+ const options = [
279
+ { value: "add_custom", label: t("review.addCustomNote") }
280
+ ];
281
+ if (suggestions.length > 0) {
282
+ options.push({
283
+ value: "add_suggested",
284
+ label: t("review.addSuggestedNote"),
285
+ hint: `${suggestions.length} ${t("common.items")}`
286
+ });
287
+ }
288
+ if (humanGuidance.length > 0) {
289
+ options.push({
290
+ value: "delete",
291
+ label: t("review.deleteNote"),
292
+ hint: `${humanGuidance.length} ${t("common.items")}`
293
+ });
294
+ }
295
+ const action = await promptSelect(
296
+ t("review.selectNotesAction"),
297
+ options
298
+ );
299
+ switch (action) {
300
+ case "add_custom": {
301
+ const content = await promptText(t("review.noteContent"), {
302
+ validate: (v) => !v?.trim() ? t("review.noteContentRequired") : void 0
303
+ });
304
+ humanGuidance.push(content.trim());
305
+ log.success(tf("review.notesAdded", { count: 1 }));
306
+ break;
307
+ }
308
+ case "add_suggested": {
309
+ const selected = await promptMultiselect(
310
+ "\u8FFD\u52A0\u3059\u308B\u30CE\u30FC\u30C8\u3092\u9078\u629E",
311
+ suggestions.map((s, i) => ({
312
+ value: i,
313
+ label: s
314
+ }))
315
+ );
316
+ for (const idx of selected) {
317
+ humanGuidance.push(suggestions[idx]);
318
+ }
319
+ log.success(tf("review.notesAdded", { count: selected.length }));
320
+ break;
321
+ }
322
+ case "delete": {
323
+ const toDelete = await promptMultiselect(
324
+ "\u524A\u9664\u3059\u308B\u30CE\u30FC\u30C8\u3092\u9078\u629E",
325
+ humanGuidance.map((g, i) => ({
326
+ value: i,
327
+ label: `${tf("review.guidancePrefix", { index: i + 1 })} ${g}`
328
+ }))
329
+ );
330
+ for (const idx of [...toDelete].sort((a, b) => b - a)) {
331
+ humanGuidance.splice(idx, 1);
332
+ }
333
+ log.success(tf("review.notesDeleted", { count: toDelete.length }));
334
+ break;
335
+ }
336
+ }
337
+ }
338
+ async function handleEditCaptures(recordedSteps) {
339
+ const action = await promptSelect(
340
+ t("review.selectCaptureAction"),
341
+ [
342
+ { value: "accept_all", label: t("review.acceptAllCaptures") },
343
+ { value: "reject", label: t("review.rejectCaptures") },
344
+ { value: "add", label: t("review.addCapture") }
345
+ ]
346
+ );
347
+ switch (action) {
348
+ case "accept_all": {
349
+ log.success(t("review.allCapturesAccepted"));
350
+ break;
351
+ }
352
+ case "reject": {
353
+ const captureSteps = recordedSteps.filter(
354
+ (r) => r.action.suggestedCaptures && r.action.suggestedCaptures.length > 0
355
+ );
356
+ if (captureSteps.length === 0) {
357
+ log.warn(t("review.noCapturesAvailable"));
358
+ break;
359
+ }
360
+ const allCaptures = [];
361
+ for (const r of captureSteps) {
362
+ for (const c of r.action.suggestedCaptures) {
363
+ allCaptures.push({
364
+ value: `${r.ordinal}:${c.name}`,
365
+ label: `Step #${r.ordinal} \u2192 ${c.name} (${c.strategy})`
366
+ });
367
+ }
368
+ }
369
+ const toReject = await promptMultiselect(
370
+ t("review.selectCapturesToReject"),
371
+ allCaptures
372
+ );
373
+ for (const key of toReject) {
374
+ const [ordStr, name] = key.split(":");
375
+ const step = recordedSteps.find((r) => r.ordinal === Number(ordStr));
376
+ if (step?.action.suggestedCaptures) {
377
+ step.action.suggestedCaptures = step.action.suggestedCaptures.filter(
378
+ (c) => c.name !== name
379
+ );
380
+ }
381
+ }
382
+ log.success(tf("review.capturesRejected", { count: toReject.length }));
383
+ break;
384
+ }
385
+ case "add": {
386
+ const ordinalStr = await promptText(t("review.captureStepNumber"), {
387
+ validate: (v) => {
388
+ if (!v?.trim()) return t("review.captureStepRequired");
389
+ if (Number.isNaN(Number(v))) return t("review.captureStepNumeric");
390
+ return void 0;
391
+ }
392
+ });
393
+ const ordinal = Number(ordinalStr);
394
+ const step = recordedSteps.find((r) => r.ordinal === ordinal);
395
+ if (!step) {
396
+ log.warn(tf("review.stepNotFound", { ordinal }));
397
+ break;
398
+ }
399
+ const name = await promptText(t("review.captureVarName"), {
400
+ validate: (v) => !v?.trim() ? t("review.captureVarRequired") : void 0
401
+ });
402
+ const strategy = await promptSelect(
403
+ t("review.captureStrategy"),
404
+ [
405
+ { value: "snapshot", label: t("review.strategySnapshot") },
406
+ { value: "url", label: t("review.strategyUrl") },
407
+ { value: "ai", label: t("review.strategyAi") },
408
+ { value: "expression", label: t("review.strategyExpression") }
409
+ ]
410
+ );
411
+ const newCapture = { name, strategy };
412
+ if (strategy === "snapshot" || strategy === "url") {
413
+ newCapture.pattern = await promptText(t("review.regexPattern"));
414
+ } else if (strategy === "ai") {
415
+ newCapture.prompt = await promptText(t("review.aiExtractionPrompt"));
416
+ } else if (strategy === "expression") {
417
+ newCapture.expression = await promptText(t("review.templateExpression"));
418
+ }
419
+ if (!step.action.suggestedCaptures) {
420
+ step.action.suggestedCaptures = [];
421
+ }
422
+ step.action.suggestedCaptures.push(newCapture);
423
+ log.success(tf("review.captureAdded", { ordinal, name }));
424
+ break;
425
+ }
426
+ }
427
+ }
428
+ async function promptReviewEdits(reviewResult, recordedSteps, humanGuidance) {
429
+ const edited = cloneReviewResult(reviewResult);
430
+ const editedGuidance = [...humanGuidance];
431
+ while (true) {
432
+ const summaryText = buildSummaryText(edited, recordedSteps, editedGuidance);
433
+ note(summaryText, t("review.reviewResult"));
434
+ const hasCaptureSteps = recordedSteps.some(
435
+ (r) => r.action.suggestedCaptures && r.action.suggestedCaptures.length > 0
436
+ );
437
+ const menuOptions = [
438
+ { value: "confirm", label: t("review.confirm"), hint: t("review.confirmHint") },
439
+ {
440
+ value: "toggle_confirmation",
441
+ label: t("review.toggleConfirmation"),
442
+ hint: "multiselect"
443
+ },
444
+ ...hasCaptureSteps ? [
445
+ {
446
+ value: "edit_captures",
447
+ label: t("review.editCaptures"),
448
+ hint: t("review.editCapturesHint")
449
+ }
450
+ ] : [],
451
+ { value: "edit_notes", label: t("review.editNotes") }
452
+ ];
453
+ const action = await promptSelect(
454
+ t("review.selectAction"),
455
+ menuOptions
456
+ );
457
+ if (action === "confirm") {
458
+ break;
459
+ }
460
+ switch (action) {
461
+ case "toggle_confirmation":
462
+ await handleToggleConfirmation(edited, recordedSteps);
463
+ break;
464
+ case "edit_captures":
465
+ await handleEditCaptures(recordedSteps);
466
+ break;
467
+ case "edit_notes":
468
+ await handleEditNotes(editedGuidance, edited, recordedSteps);
469
+ break;
470
+ }
471
+ }
472
+ return {
473
+ reviewResult: edited,
474
+ humanGuidance: editedGuidance
475
+ };
476
+ }
477
+
478
+ // src/runbook-generator/report-generator.ts
479
+ function formatOutcome(outcome) {
480
+ switch (outcome) {
481
+ case "cancelled":
482
+ return "Cancelled by user";
483
+ case "goal_achieved":
484
+ return "Goal achieved";
485
+ case "goal_not_achieved":
486
+ return "Goal not achieved";
487
+ case "error":
488
+ return "Error";
489
+ }
490
+ }
491
+ function toReportableSteps(steps) {
492
+ return steps.map((s) => ({
493
+ ordinal: s.ordinal + 1,
494
+ description: `${s.action.action}: ${s.action.description}`,
495
+ status: s.success ? "success" : "failed",
496
+ durationMs: s.durationMs ?? 0,
497
+ error: s.error,
498
+ failureCategory: s.failureCategory
499
+ }));
500
+ }
501
+ async function generateExplorationReport(input) {
502
+ const lines = [];
503
+ lines.push("# Exploration Report");
504
+ lines.push("");
505
+ lines.push("## Session Info");
506
+ lines.push(`- **Date**: ${(/* @__PURE__ */ new Date()).toISOString()}`);
507
+ lines.push(`- **Goal**: ${input.goal}`);
508
+ lines.push(`- **Start URL**: ${input.startUrl}`);
509
+ lines.push(`- **Outcome**: ${formatOutcome(input.outcome)}`);
510
+ lines.push("");
511
+ if (input.cliOptions && input.cliOptions.length > 0) {
512
+ lines.push("## CLI Options");
513
+ lines.push(...renderOptionsMarkdown(input.cliOptions));
514
+ lines.push("");
515
+ }
516
+ lines.push("## User Feedback");
517
+ if (input.userFeedback) {
518
+ lines.push(input.userFeedback);
519
+ } else {
520
+ lines.push("(No feedback provided)");
521
+ }
522
+ lines.push("");
523
+ if (input.errorMessage) {
524
+ lines.push("## Error Details");
525
+ lines.push("```");
526
+ lines.push(input.errorMessage);
527
+ lines.push("```");
528
+ lines.push("");
529
+ }
530
+ const successCount = input.recordedSteps.filter((s) => s.success).length;
531
+ const failedCount = input.recordedSteps.filter((s) => !s.success).length;
532
+ lines.push("## Execution Summary");
533
+ lines.push(`- **Total Steps**: ${input.recordedSteps.length}`);
534
+ lines.push(`- **Successful**: ${successCount}`);
535
+ lines.push(`- **Failed**: ${failedCount}`);
536
+ lines.push(`- **Interventions**: ${input.interventions.length}`);
537
+ if (input.totalDurationMs !== void 0) {
538
+ lines.push(`- **Duration**: ${input.totalDurationMs}ms`);
539
+ }
540
+ lines.push("");
541
+ if (input.recordedSteps.length > 0) {
542
+ lines.push("## Recorded Steps");
543
+ for (const step of input.recordedSteps) {
544
+ const tag = step.success ? "DONE" : "FAILED";
545
+ const errorInfo = !step.success && step.error ? ` \u2014 Error: ${step.error}` : "";
546
+ const category = step.failureCategory ? ` [${step.failureCategory}]` : "";
547
+ const duration = step.durationMs !== void 0 ? ` (${step.durationMs}ms)` : "";
548
+ lines.push(
549
+ `${step.ordinal + 1}. [${tag}] ${step.action.action}: ${step.action.description} (URL: ${step.url})${duration}${errorInfo}${category}`
550
+ );
551
+ }
552
+ lines.push("");
553
+ }
554
+ if (input.interventions.length > 0) {
555
+ lines.push("## Interventions");
556
+ for (const iv of input.interventions) {
557
+ lines.push(
558
+ `- **Step ${iv.stepIndex}** (${iv.timestamp}): "${iv.userInstruction}" (URL: ${iv.url})`
559
+ );
560
+ }
561
+ lines.push("");
562
+ }
563
+ const debugEntries = input.debugLogPath ? await readDebugLog(input.debugLogPath) : [];
564
+ const reportableSteps = toReportableSteps(input.recordedSteps);
565
+ const hasInsights = input.aiMetrics && input.aiMetrics.totalCalls > 0 || reportableSteps.some((s) => s.status === "failed") || reportableSteps.some((s) => s.durationMs > 3e3) || debugEntries.length > 0;
566
+ if (hasInsights) {
567
+ lines.push("## System Insights");
568
+ lines.push("");
569
+ lines.push("> refrain \u306E\u6A5F\u80FD\u6539\u5584\u30FB\u30D0\u30B0\u4FEE\u6B63\u306E\u305F\u3081\u306E\u5206\u6790\u60C5\u5831");
570
+ lines.push("");
571
+ lines.push(...formatRuntimeEnvironment());
572
+ if (input.aiMetrics && input.aiMetrics.totalCalls > 0) {
573
+ lines.push(...formatAiMetricsSection(input.aiMetrics));
574
+ }
575
+ const failedSteps = reportableSteps.filter((s) => s.status === "failed");
576
+ lines.push(...formatFailureCategoryDistribution(failedSteps));
577
+ lines.push(...formatPerformanceBottlenecks(reportableSteps));
578
+ }
579
+ lines.push(...formatDebugLogSections(debugEntries));
580
+ return lines.join("\n");
581
+ }
582
+
583
+ // src/runbook-generator/index.ts
584
+ async function main() {
585
+ const totalStart = performance.now();
586
+ const config = await parseArgs();
587
+ await initModel(config.aiModelConfig);
588
+ const cliOptions = serializeGeneratorOptions(config);
589
+ intro("runbook-generator");
590
+ log.info(`URL: ${config.url}`);
591
+ log.info(`Goal: ${config.goal}`);
592
+ log.info(`Output: ${config.output}`);
593
+ log.info(`Max iterations: ${config.maxIterations}`);
594
+ log.info(`Step delay: ${config.stepDelay}ms`);
595
+ log.info(`Headless: ${config.headless}`);
596
+ if (config.skills && config.skills.length > 0) {
597
+ log.info(`Skills: ${config.skills.join(", ")}`);
598
+ }
599
+ log.info(`Context: loaded (${config.contextMarkdown.split("\n").length} lines)`);
600
+ log.info("--- Context Markdown ---");
601
+ log.info(config.contextMarkdown);
602
+ log.info("--- End Context ---");
603
+ if (config.secrets && Object.keys(config.secrets.values).length > 0) {
604
+ log.info(`Secrets: ${Object.keys(config.secrets.values).length} keys loaded`);
605
+ }
606
+ const exploreStart = performance.now();
607
+ const { recordedSteps, goalAchieved, additionalGuidance, interventions, cancelled, error: exploreError } = await explore(config);
608
+ log.info(`Steps recorded: ${recordedSteps.length}`);
609
+ log.info(`Goal achieved: ${goalAchieved}`);
610
+ if (cancelled) {
611
+ log.warn("Exploration was cancelled by user.");
612
+ }
613
+ if (interventions.length > 0) {
614
+ log.info(`User interventions: ${interventions.length}`);
615
+ }
616
+ log.info(`Exploration completed (${formatDuration(performance.now() - exploreStart)})`);
617
+ if (exploreError) {
618
+ log.error(`Exploration failed: ${exploreError.message}`);
619
+ const report = await generateExplorationReport({
620
+ goal: config.goal,
621
+ startUrl: config.url,
622
+ outcome: "error",
623
+ userFeedback: "",
624
+ recordedSteps,
625
+ interventions,
626
+ debugLogPath: config.debugLogPath,
627
+ errorMessage: exploreError.stack ?? exploreError.message,
628
+ cliOptions,
629
+ aiMetrics: globalMetrics.getSummary(),
630
+ totalDurationMs: Math.round(performance.now() - totalStart)
631
+ });
632
+ const reportPath = config.output.replace(/\.\w+$/, "-report.md");
633
+ await mkdir(dirname(reportPath), { recursive: true });
634
+ await writeFile(reportPath, report, "utf-8");
635
+ log.info(`Error report written to: ${reportPath}`);
636
+ outro(`Exploration failed. Report: ${reportPath}`);
637
+ process.exit(1);
638
+ }
639
+ if (cancelled || !goalAchieved) {
640
+ const userFeedback = await promptText(
641
+ t("generator.feedbackPrompt"),
642
+ { validate: () => void 0 }
643
+ );
644
+ const report = await generateExplorationReport({
645
+ goal: config.goal,
646
+ startUrl: config.url,
647
+ outcome: cancelled ? "cancelled" : "goal_not_achieved",
648
+ userFeedback: userFeedback?.trim() ?? "",
649
+ recordedSteps,
650
+ interventions,
651
+ debugLogPath: config.debugLogPath,
652
+ cliOptions,
653
+ aiMetrics: globalMetrics.getSummary(),
654
+ totalDurationMs: Math.round(performance.now() - totalStart)
655
+ });
656
+ const reportPath = config.output.replace(/\.\w+$/, "-report.md");
657
+ await mkdir(dirname(reportPath), { recursive: true });
658
+ await writeFile(reportPath, report, "utf-8");
659
+ log.info(`Report written to: ${reportPath}`);
660
+ if (cancelled) {
661
+ outro(t("generator.cancelled"));
662
+ process.exit(0);
663
+ }
664
+ }
665
+ if (recordedSteps.length === 0) {
666
+ log.error("No steps recorded. Cannot generate runbook.");
667
+ process.exit(1);
668
+ }
669
+ try {
670
+ const debugLogger = createDebugLogger({
671
+ filePath: config.debugLogPath,
672
+ console: config.debugConsole
673
+ });
674
+ let reviewResult;
675
+ try {
676
+ const reviewStart = performance.now();
677
+ log.step(t("generator.aiReviewing"));
678
+ const { system: reviewSystem, userPrompt: reviewUserPrompt } = createReviewPrompt(
679
+ config.goal,
680
+ recordedSteps,
681
+ goalAchieved,
682
+ interventions,
683
+ config.locale
684
+ );
685
+ debugLogger.log({
686
+ phase: "generator",
687
+ event: "ai_review",
688
+ data: { prompt: `${reviewSystem}
689
+ ---
690
+ ${reviewUserPrompt}`.slice(0, 500) }
691
+ });
692
+ const { object } = await trackedGenerateObject("review", {
693
+ model: getModel("review"),
694
+ messages: [
695
+ {
696
+ role: "system",
697
+ content: reviewSystem,
698
+ providerOptions: {
699
+ anthropic: { cacheControl: { type: "ephemeral" } }
700
+ }
701
+ },
702
+ { role: "user", content: reviewUserPrompt }
703
+ ],
704
+ schema: reviewResponseSchema,
705
+ temperature: 0
706
+ });
707
+ reviewResult = object;
708
+ debugLogger.log({
709
+ phase: "generator",
710
+ event: "ai_review",
711
+ data: {
712
+ parsedResult: reviewResult
713
+ }
714
+ });
715
+ const kept = reviewResult.reviewedSteps.filter((s) => s.keep).length;
716
+ const removed = reviewResult.reviewedSteps.filter((s) => !s.keep).length;
717
+ log.info(`AI review done: ${kept} kept, ${removed} removed (${formatDuration(performance.now() - reviewStart)})`);
718
+ log.info(`Summary: ${reviewResult.summary}`);
719
+ } catch (error) {
720
+ log.warn(tf("generator.aiReviewFailed", { error: error instanceof Error ? error.message : String(error) }));
721
+ reviewResult = void 0;
722
+ }
723
+ let finalGuidance = additionalGuidance;
724
+ if (reviewResult) {
725
+ const edited = await promptReviewEdits(
726
+ reviewResult,
727
+ recordedSteps,
728
+ additionalGuidance
729
+ );
730
+ reviewResult = edited.reviewResult;
731
+ finalGuidance = edited.humanGuidance;
732
+ }
733
+ if (reviewResult) {
734
+ const keptWithConfirm = reviewResult.reviewedSteps.filter((s) => s.keep && s.requiresConfirmation).map((s) => `#${s.originalOrdinal}`);
735
+ log.info(`Confirmation required for steps: ${keptWithConfirm.join(", ") || t("common.none")}`);
736
+ }
737
+ const yamlStart = performance.now();
738
+ const yaml = buildRunbookYaml({
739
+ goal: config.goal,
740
+ startUrl: config.url,
741
+ recordedSteps,
742
+ goalAchieved,
743
+ stepDelay: config.stepDelay,
744
+ contextMarkdown: config.contextMarkdown,
745
+ reviewResult,
746
+ humanGuidance: finalGuidance,
747
+ interventions,
748
+ skills: config.skills
749
+ });
750
+ await mkdir(dirname(config.output), { recursive: true });
751
+ await writeFile(config.output, yaml, "utf-8");
752
+ log.info(`YAML generation + write (${formatDuration(performance.now() - yamlStart)})`);
753
+ await debugLogger.flush();
754
+ log.success(`Runbook written to: ${config.output}`);
755
+ if (!goalAchieved) {
756
+ log.warn("Goal was NOT fully achieved. Runbook may be incomplete.");
757
+ }
758
+ const metrics = globalMetrics.getSummary();
759
+ if (metrics.totalCalls > 0) {
760
+ const purposeSummary = Object.entries(metrics.byPurpose).map(([p, b]) => `${p}(${b.calls})`).join(" ");
761
+ log.info(
762
+ `AI Metrics: ${metrics.totalCalls} calls, ${metrics.totalInputTokens.toLocaleString()} in / ${metrics.totalOutputTokens.toLocaleString()} out tokens, cache ${(metrics.cacheHitRate * 100).toFixed(1)}%, $${metrics.estimatedCostUsd.toFixed(4)}, ${(metrics.totalDurationMs / 1e3).toFixed(1)}s \u2014 ${purposeSummary}`
763
+ );
764
+ if (metrics.byModel && Object.keys(metrics.byModel).length > 1) {
765
+ const modelSummary = Object.entries(metrics.byModel).map(([id, b]) => `${id}($${b.estimatedCostUsd.toFixed(4)})`).join(" ");
766
+ log.info(`Models: ${modelSummary}`);
767
+ }
768
+ }
769
+ outro(`Total: ${formatDuration(performance.now() - totalStart)}`);
770
+ } catch (postExploreError) {
771
+ const errorMessage = postExploreError instanceof Error ? postExploreError.stack ?? postExploreError.message : String(postExploreError);
772
+ log.error(`Post-exploration phase failed: ${errorMessage}`);
773
+ const report = await generateExplorationReport({
774
+ goal: config.goal,
775
+ startUrl: config.url,
776
+ outcome: "error",
777
+ userFeedback: "",
778
+ recordedSteps,
779
+ interventions,
780
+ debugLogPath: config.debugLogPath,
781
+ errorMessage,
782
+ cliOptions,
783
+ aiMetrics: globalMetrics.getSummary(),
784
+ totalDurationMs: Math.round(performance.now() - totalStart)
785
+ });
786
+ const reportPath = config.output.replace(/\.\w+$/, "-report.md");
787
+ await mkdir(dirname(reportPath), { recursive: true });
788
+ await writeFile(reportPath, report, "utf-8");
789
+ log.info(`Error report written to: ${reportPath}`);
790
+ process.exit(1);
791
+ }
792
+ }
793
+ main().catch((error) => {
794
+ cancel(`Fatal error: ${error instanceof Error ? error.message : String(error)}`);
795
+ if (error instanceof Error && error.stack) {
796
+ console.error(error.stack);
797
+ }
798
+ process.exit(1);
799
+ });
800
+ //# sourceMappingURL=runbook-generator-MPXJBQ5N.js.map