@ozzylabs/feedradar 0.1.3 → 0.1.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.
Files changed (144) hide show
  1. package/README.ja.md +31 -6
  2. package/README.md +31 -6
  3. package/dist/agents/claude-code.d.ts +12 -1
  4. package/dist/agents/claude-code.d.ts.map +1 -1
  5. package/dist/agents/claude-code.js +9 -5
  6. package/dist/agents/claude-code.js.map +1 -1
  7. package/dist/agents/codex-cli.d.ts +7 -1
  8. package/dist/agents/codex-cli.d.ts.map +1 -1
  9. package/dist/agents/codex-cli.js +9 -5
  10. package/dist/agents/codex-cli.js.map +1 -1
  11. package/dist/agents/copilot.d.ts +7 -1
  12. package/dist/agents/copilot.d.ts.map +1 -1
  13. package/dist/agents/copilot.js +9 -5
  14. package/dist/agents/copilot.js.map +1 -1
  15. package/dist/agents/gemini-cli.d.ts +7 -1
  16. package/dist/agents/gemini-cli.d.ts.map +1 -1
  17. package/dist/agents/gemini-cli.js +9 -5
  18. package/dist/agents/gemini-cli.js.map +1 -1
  19. package/dist/agents/index.d.ts +1 -1
  20. package/dist/agents/index.d.ts.map +1 -1
  21. package/dist/agents/types.d.ts +33 -0
  22. package/dist/agents/types.d.ts.map +1 -1
  23. package/dist/cli/_progress.d.ts +138 -0
  24. package/dist/cli/_progress.d.ts.map +1 -0
  25. package/dist/cli/_progress.js +176 -0
  26. package/dist/cli/_progress.js.map +1 -0
  27. package/dist/cli/doctor.d.ts +20 -0
  28. package/dist/cli/doctor.d.ts.map +1 -1
  29. package/dist/cli/doctor.js +291 -2
  30. package/dist/cli/doctor.js.map +1 -1
  31. package/dist/cli/index.d.ts.map +1 -1
  32. package/dist/cli/index.js +2 -0
  33. package/dist/cli/index.js.map +1 -1
  34. package/dist/cli/research.d.ts +18 -20
  35. package/dist/cli/research.d.ts.map +1 -1
  36. package/dist/cli/research.js +318 -203
  37. package/dist/cli/research.js.map +1 -1
  38. package/dist/cli/respawn.d.ts +53 -0
  39. package/dist/cli/respawn.d.ts.map +1 -0
  40. package/dist/cli/respawn.js +120 -0
  41. package/dist/cli/respawn.js.map +1 -0
  42. package/dist/cli/review.d.ts +7 -0
  43. package/dist/cli/review.d.ts.map +1 -1
  44. package/dist/cli/review.js +46 -1
  45. package/dist/cli/review.js.map +1 -1
  46. package/dist/cli/source.d.ts +23 -2
  47. package/dist/cli/source.d.ts.map +1 -1
  48. package/dist/cli/source.js +425 -7
  49. package/dist/cli/source.js.map +1 -1
  50. package/dist/cli/update.d.ts +7 -0
  51. package/dist/cli/update.d.ts.map +1 -1
  52. package/dist/cli/update.js +41 -1
  53. package/dist/cli/update.js.map +1 -1
  54. package/dist/cli/watch.d.ts.map +1 -1
  55. package/dist/cli/watch.js +65 -3
  56. package/dist/cli/watch.js.map +1 -1
  57. package/dist/cli/workflow/generate-combined.d.ts +100 -0
  58. package/dist/cli/workflow/generate-combined.d.ts.map +1 -0
  59. package/dist/cli/workflow/generate-combined.js +387 -0
  60. package/dist/cli/workflow/generate-combined.js.map +1 -0
  61. package/dist/cli/workflow/generate-watch.d.ts +142 -0
  62. package/dist/cli/workflow/generate-watch.d.ts.map +1 -0
  63. package/dist/cli/workflow/generate-watch.js +338 -0
  64. package/dist/cli/workflow/generate-watch.js.map +1 -0
  65. package/dist/cli/workflow.d.ts +29 -0
  66. package/dist/cli/workflow.d.ts.map +1 -0
  67. package/dist/cli/workflow.js +66 -0
  68. package/dist/cli/workflow.js.map +1 -0
  69. package/dist/core/feeds/_fetch.d.ts +103 -0
  70. package/dist/core/feeds/_fetch.d.ts.map +1 -0
  71. package/dist/core/feeds/_fetch.js +364 -0
  72. package/dist/core/feeds/_fetch.js.map +1 -0
  73. package/dist/core/feeds/_jsonpath.d.ts +57 -0
  74. package/dist/core/feeds/_jsonpath.d.ts.map +1 -0
  75. package/dist/core/feeds/_jsonpath.js +207 -0
  76. package/dist/core/feeds/_jsonpath.js.map +1 -0
  77. package/dist/core/feeds/github-api.d.ts.map +1 -1
  78. package/dist/core/feeds/github-api.js +2 -1
  79. package/dist/core/feeds/github-api.js.map +1 -1
  80. package/dist/core/feeds/html-js.d.ts +29 -0
  81. package/dist/core/feeds/html-js.d.ts.map +1 -1
  82. package/dist/core/feeds/html-js.js +86 -2
  83. package/dist/core/feeds/html-js.js.map +1 -1
  84. package/dist/core/feeds/html.d.ts.map +1 -1
  85. package/dist/core/feeds/html.js +2 -1
  86. package/dist/core/feeds/html.js.map +1 -1
  87. package/dist/core/feeds/index.d.ts +1 -1
  88. package/dist/core/feeds/index.d.ts.map +1 -1
  89. package/dist/core/feeds/index.js +4 -0
  90. package/dist/core/feeds/index.js.map +1 -1
  91. package/dist/core/feeds/json-api.d.ts +3 -0
  92. package/dist/core/feeds/json-api.d.ts.map +1 -0
  93. package/dist/core/feeds/json-api.js +723 -0
  94. package/dist/core/feeds/json-api.js.map +1 -0
  95. package/dist/core/feeds/json-feed.d.ts +11 -0
  96. package/dist/core/feeds/json-feed.d.ts.map +1 -0
  97. package/dist/core/feeds/json-feed.js +242 -0
  98. package/dist/core/feeds/json-feed.js.map +1 -0
  99. package/dist/core/feeds/npm-registry.d.ts.map +1 -1
  100. package/dist/core/feeds/npm-registry.js +2 -1
  101. package/dist/core/feeds/npm-registry.js.map +1 -1
  102. package/dist/core/feeds/rss.d.ts.map +1 -1
  103. package/dist/core/feeds/rss.js +2 -1
  104. package/dist/core/feeds/rss.js.map +1 -1
  105. package/dist/core/feeds/types.d.ts +123 -0
  106. package/dist/core/feeds/types.d.ts.map +1 -1
  107. package/dist/core/progress.d.ts +101 -0
  108. package/dist/core/progress.d.ts.map +1 -0
  109. package/dist/core/progress.js +212 -0
  110. package/dist/core/progress.js.map +1 -0
  111. package/dist/core/proxy.d.ts +87 -0
  112. package/dist/core/proxy.d.ts.map +1 -0
  113. package/dist/core/proxy.js +146 -0
  114. package/dist/core/proxy.js.map +1 -0
  115. package/dist/core/recipes.d.ts +138 -0
  116. package/dist/core/recipes.d.ts.map +1 -0
  117. package/dist/core/recipes.js +238 -0
  118. package/dist/core/recipes.js.map +1 -0
  119. package/dist/core/watcher.d.ts +61 -1
  120. package/dist/core/watcher.d.ts.map +1 -1
  121. package/dist/core/watcher.js +99 -2
  122. package/dist/core/watcher.js.map +1 -1
  123. package/dist/index.js +17 -4
  124. package/dist/index.js.map +1 -1
  125. package/dist/recipes/aws-whats-new.yaml +61 -0
  126. package/dist/recipes/dev-to.yaml +40 -0
  127. package/dist/schemas/index.d.ts +1 -0
  128. package/dist/schemas/index.d.ts.map +1 -1
  129. package/dist/schemas/index.js +1 -0
  130. package/dist/schemas/index.js.map +1 -1
  131. package/dist/schemas/recipe.d.ts +115 -0
  132. package/dist/schemas/recipe.d.ts.map +1 -0
  133. package/dist/schemas/recipe.js +54 -0
  134. package/dist/schemas/recipe.js.map +1 -0
  135. package/dist/schemas/source.d.ts +130 -0
  136. package/dist/schemas/source.d.ts.map +1 -1
  137. package/dist/schemas/source.js +130 -0
  138. package/dist/schemas/source.js.map +1 -1
  139. package/dist/templates/agents/AGENTS.md +31 -3
  140. package/dist/templates/feedradar.md +23 -8
  141. package/dist/templates/workflows/combined.template.yaml.tmpl +110 -0
  142. package/dist/templates/workflows/watch.template.yaml.tmpl +103 -0
  143. package/dist/templates/workflows/watch.yaml +5 -1
  144. package/package.json +2 -3
@@ -0,0 +1,101 @@
1
+ /**
2
+ * ProgressReporter — UX abstraction for long-running CLI operations.
3
+ *
4
+ * Implements [ADR-0015 Progress Reporting UX](../../docs/adr/0015-progress-reporting-ux.md):
5
+ *
6
+ * - 3-layer model (phase markers / heartbeat spinner / side metrics)
7
+ * - TTY auto-detection with env / flag overrides (`RADAR_NO_PROGRESS=1`,
8
+ * `--quiet`, `--verbose`)
9
+ * - CI / non-TTY safe degradation: spinner becomes plain text and `\r`
10
+ * same-line updates are disabled
11
+ * - Zero new runtime dependencies — the spinner frame set
12
+ * (`⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏`) is rotated by an internal `setInterval` and rendered via
13
+ * `process.stderr.write` + ANSI escape `\x1b[K\r`
14
+ *
15
+ * This is the **base** (#196) — only the interface, the factory, and the
16
+ * default reporter implementation. CLI integration (`research` / `review` /
17
+ * `update`) ships in #197, feed adapter integration in #198. Adapter call
18
+ * sites in `src/agents/*.ts` consume the optional `onProgress` callback
19
+ * defined alongside this module (see `src/agents/types.ts`).
20
+ */
21
+ export type ProgressLevel = "quiet" | "normal" | "verbose";
22
+ /**
23
+ * Public API surface used by CLI / adapter integrations. Six methods cover
24
+ * the three UX layers from ADR-0015:
25
+ *
26
+ * - `phase`: structured milestone, always rendered (TTY and non-TTY)
27
+ * - `start` / `succeed` / `fail`: spinner lifecycle for a single sub-task.
28
+ * On TTY, `start` begins same-line spinner animation; `succeed` / `fail`
29
+ * stop it and emit a single completion line.
30
+ * - `update`: side metrics (page x/N, stdout bytes, ...) — merged into the
31
+ * spinner row on TTY, suppressed or printed as plain text on non-TTY
32
+ * - `raw`: agent stdout / stderr pass-through. Only printed in `verbose`
33
+ * level. Default reporter pipes the chunk to `process.stderr` verbatim.
34
+ */
35
+ export interface ProgressReporter {
36
+ /** Structured milestone. e.g. `"Spawning claude-code"` */
37
+ phase(name: string, info?: string): void;
38
+ /** Begin a spinner-tracked sub-task. e.g. `"Agent running…"` */
39
+ start(label: string): void;
40
+ /** Update side metrics for the active spinner row. */
41
+ update(metrics: Record<string, string>): void;
42
+ /** Stop the spinner and emit a success line. `duration` is milliseconds. */
43
+ succeed(label: string, duration?: number): void;
44
+ /** Stop the spinner and emit a failure line. */
45
+ fail(label: string, reason: string): void;
46
+ /** Pass-through agent stdout / stderr text (verbose only). */
47
+ raw(text: string): void;
48
+ }
49
+ export interface CreateProgressReporterOptions {
50
+ /**
51
+ * TTY override. If unspecified, falls back to `process.stderr.isTTY`.
52
+ * Always overridable so tests can pin the value.
53
+ */
54
+ tty?: boolean;
55
+ /** Verbosity. `quiet` = phase markers off; `verbose` = `raw()` enabled. */
56
+ level: ProgressLevel;
57
+ /**
58
+ * Output stream. Defaults to `process.stderr` so progress does not pollute
59
+ * piped stdout (`radar research > out.md`). Tests pass an in-memory stream.
60
+ */
61
+ stream?: NodeJS.WritableStream;
62
+ /**
63
+ * Heartbeat interval in milliseconds (default 1000ms / 1s). Tests can
64
+ * shorten this to verify spinner rotation without burning real time.
65
+ * Negative or zero disables the heartbeat (still renders a static first
66
+ * frame on `start`).
67
+ */
68
+ heartbeatMs?: number;
69
+ /** `Date.now()` override for deterministic elapsed-time rendering. */
70
+ now?: () => number;
71
+ }
72
+ /**
73
+ * No-op reporter. Used by:
74
+ *
75
+ * - tests that do not care about progress output
76
+ * - `--quiet` / `RADAR_NO_PROGRESS=1` paths (see `createProgressReporter`)
77
+ * - adapter call sites where the caller did not opt into progress
78
+ *
79
+ * All six methods return immediately so wiring a `noopProgressReporter()` to
80
+ * an existing adapter is byte-equivalent to passing `undefined`.
81
+ */
82
+ export declare function noopProgressReporter(): ProgressReporter;
83
+ /**
84
+ * Construct a `ProgressReporter` honouring the ADR-0015 D2 priority table:
85
+ *
86
+ * env (`RADAR_NO_PROGRESS=1`) > flag (`level`) > TTY auto-detect
87
+ *
88
+ * - `level: "quiet"` → no-op (callers want zero output)
89
+ * - `RADAR_NO_PROGRESS=1` → no-op (CI escape hatch)
90
+ * - non-TTY + `level: "normal"` → plain text (phase markers + completion
91
+ * lines, NO spinner animation, NO `\r` overwrite)
92
+ * - TTY + `level: "normal"` → phase markers + spinner + same-line update
93
+ * - `level: "verbose"` → phase markers + spinner (if TTY) + `raw()` pass-
94
+ * through enabled (regardless of TTY)
95
+ *
96
+ * The reporter is self-contained: callers should not depend on internal
97
+ * state (e.g. the active spinner timer). The contract is the 6-method
98
+ * interface above.
99
+ */
100
+ export declare function createProgressReporter(opts: CreateProgressReporterOptions): ProgressReporter;
101
+ //# sourceMappingURL=progress.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"progress.d.ts","sourceRoot":"","sources":["../../src/core/progress.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE3D;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,gBAAgB;IAC/B,0DAA0D;IAC1D,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzC,gEAAgE;IAChE,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,sDAAsD;IACtD,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;IAC9C,4EAA4E;IAC5E,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChD,gDAAgD;IAChD,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1C,8DAA8D;IAC9D,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,MAAM,WAAW,6BAA6B;IAC5C;;;OAGG;IACH,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,2EAA2E;IAC3E,KAAK,EAAE,aAAa,CAAC;IACrB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC,cAAc,CAAC;IAC/B;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sEAAsE;IACtE,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAKD;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,IAAI,gBAAgB,CASvD;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,6BAA6B,GAAG,gBAAgB,CA8I5F"}
@@ -0,0 +1,212 @@
1
+ /**
2
+ * ProgressReporter — UX abstraction for long-running CLI operations.
3
+ *
4
+ * Implements [ADR-0015 Progress Reporting UX](../../docs/adr/0015-progress-reporting-ux.md):
5
+ *
6
+ * - 3-layer model (phase markers / heartbeat spinner / side metrics)
7
+ * - TTY auto-detection with env / flag overrides (`RADAR_NO_PROGRESS=1`,
8
+ * `--quiet`, `--verbose`)
9
+ * - CI / non-TTY safe degradation: spinner becomes plain text and `\r`
10
+ * same-line updates are disabled
11
+ * - Zero new runtime dependencies — the spinner frame set
12
+ * (`⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏`) is rotated by an internal `setInterval` and rendered via
13
+ * `process.stderr.write` + ANSI escape `\x1b[K\r`
14
+ *
15
+ * This is the **base** (#196) — only the interface, the factory, and the
16
+ * default reporter implementation. CLI integration (`research` / `review` /
17
+ * `update`) ships in #197, feed adapter integration in #198. Adapter call
18
+ * sites in `src/agents/*.ts` consume the optional `onProgress` callback
19
+ * defined alongside this module (see `src/agents/types.ts`).
20
+ */
21
+ const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
22
+ const ANSI_CLEAR_LINE = "\x1b[K";
23
+ /**
24
+ * No-op reporter. Used by:
25
+ *
26
+ * - tests that do not care about progress output
27
+ * - `--quiet` / `RADAR_NO_PROGRESS=1` paths (see `createProgressReporter`)
28
+ * - adapter call sites where the caller did not opt into progress
29
+ *
30
+ * All six methods return immediately so wiring a `noopProgressReporter()` to
31
+ * an existing adapter is byte-equivalent to passing `undefined`.
32
+ */
33
+ export function noopProgressReporter() {
34
+ return {
35
+ phase() { },
36
+ start() { },
37
+ update() { },
38
+ succeed() { },
39
+ fail() { },
40
+ raw() { },
41
+ };
42
+ }
43
+ /**
44
+ * Construct a `ProgressReporter` honouring the ADR-0015 D2 priority table:
45
+ *
46
+ * env (`RADAR_NO_PROGRESS=1`) > flag (`level`) > TTY auto-detect
47
+ *
48
+ * - `level: "quiet"` → no-op (callers want zero output)
49
+ * - `RADAR_NO_PROGRESS=1` → no-op (CI escape hatch)
50
+ * - non-TTY + `level: "normal"` → plain text (phase markers + completion
51
+ * lines, NO spinner animation, NO `\r` overwrite)
52
+ * - TTY + `level: "normal"` → phase markers + spinner + same-line update
53
+ * - `level: "verbose"` → phase markers + spinner (if TTY) + `raw()` pass-
54
+ * through enabled (regardless of TTY)
55
+ *
56
+ * The reporter is self-contained: callers should not depend on internal
57
+ * state (e.g. the active spinner timer). The contract is the 6-method
58
+ * interface above.
59
+ */
60
+ export function createProgressReporter(opts) {
61
+ // Env escape hatch (D2 table row 1). Honoured even at `level: "verbose"`
62
+ // — CI environments must be able to opt out unconditionally.
63
+ if (process.env.RADAR_NO_PROGRESS === "1") {
64
+ return noopProgressReporter();
65
+ }
66
+ if (opts.level === "quiet") {
67
+ return noopProgressReporter();
68
+ }
69
+ const tty = opts.tty ?? Boolean(process.stderr.isTTY);
70
+ const stream = opts.stream ?? process.stderr;
71
+ const heartbeatMs = opts.heartbeatMs ?? 1000;
72
+ const now = opts.now ?? Date.now;
73
+ const verbose = opts.level === "verbose";
74
+ // Active spinner state. `null` means no spinner is running.
75
+ let active = null;
76
+ function renderSpinnerRow() {
77
+ if (!active)
78
+ return "";
79
+ const elapsedSec = Math.max(0, Math.floor((now() - active.startedAt) / 1000));
80
+ const mm = String(Math.floor(elapsedSec / 60)).padStart(2, "0");
81
+ const ss = String(elapsedSec % 60).padStart(2, "0");
82
+ const frame = SPINNER_FRAMES[active.frame % SPINNER_FRAMES.length];
83
+ const metricEntries = Object.entries(active.metrics);
84
+ const metricsSuffix = metricEntries.length > 0 ? ` ${metricEntries.map(([k, v]) => `${k}: ${v}`).join(" ")}` : "";
85
+ return `${frame} ${active.label} [${mm}:${ss}]${metricsSuffix}`;
86
+ }
87
+ function repaint() {
88
+ if (!active)
89
+ return;
90
+ if (tty) {
91
+ stream.write(`\r${ANSI_CLEAR_LINE}${renderSpinnerRow()}`);
92
+ }
93
+ }
94
+ function clearSpinnerLine() {
95
+ if (tty && active) {
96
+ stream.write(`\r${ANSI_CLEAR_LINE}`);
97
+ }
98
+ }
99
+ function stopSpinner() {
100
+ if (active?.timer) {
101
+ clearInterval(active.timer);
102
+ }
103
+ active = null;
104
+ }
105
+ return {
106
+ phase(name, info) {
107
+ // Phase markers always claim their own line; clear any active spinner
108
+ // overwrite first so we don't leave a half-painted row in scrollback.
109
+ clearSpinnerLine();
110
+ const suffix = info ? ` (${info})` : "";
111
+ stream.write(`${name}${suffix}\n`);
112
+ // Re-paint the spinner row on the new line so it continues to update.
113
+ repaint();
114
+ },
115
+ start(label) {
116
+ // If a previous spinner was still active, drop it silently — the new
117
+ // start supersedes it (caller bug, but we don't want to crash).
118
+ clearSpinnerLine();
119
+ stopSpinner();
120
+ active = {
121
+ label,
122
+ startedAt: now(),
123
+ frame: 0,
124
+ metrics: {},
125
+ timer: null,
126
+ };
127
+ if (tty) {
128
+ // Paint frame 0 immediately so the user sees something before the
129
+ // first heartbeat tick fires.
130
+ repaint();
131
+ if (heartbeatMs > 0) {
132
+ const timer = setInterval(() => {
133
+ if (!active)
134
+ return;
135
+ active.frame += 1;
136
+ repaint();
137
+ }, heartbeatMs);
138
+ // Don't keep the event loop alive just for the spinner — if the
139
+ // host process is otherwise idle (e.g. awaiting a child), we still
140
+ // want it to exit when the work is done.
141
+ if (typeof timer.unref === "function") {
142
+ timer.unref();
143
+ }
144
+ active.timer = timer;
145
+ }
146
+ }
147
+ else {
148
+ // Non-TTY plain-text degrade: one line per state transition, no `\r`.
149
+ stream.write(`${label}\n`);
150
+ }
151
+ },
152
+ update(metrics) {
153
+ if (!active)
154
+ return;
155
+ // Merge so successive `update` calls only need to pass the changed
156
+ // metric (e.g. page index ticks per fetch).
157
+ active.metrics = { ...active.metrics, ...metrics };
158
+ if (tty) {
159
+ repaint();
160
+ }
161
+ // On non-TTY we intentionally drop tick updates to avoid spamming the
162
+ // log with one line per page. The next phase / succeed / fail line
163
+ // re-states the final metric set.
164
+ },
165
+ succeed(label, duration) {
166
+ const elapsedMs = duration ?? (active ? now() - active.startedAt : 0);
167
+ const formatted = formatDuration(elapsedMs);
168
+ clearSpinnerLine();
169
+ stopSpinner();
170
+ stream.write(`${label} (${formatted})\n`);
171
+ },
172
+ fail(label, reason) {
173
+ clearSpinnerLine();
174
+ stopSpinner();
175
+ stream.write(`${label} — ${reason}\n`);
176
+ },
177
+ raw(text) {
178
+ if (!verbose)
179
+ return;
180
+ // Clear the spinner row before flushing pass-through so the agent's
181
+ // stdout / stderr line doesn't end up appended to the spinner frame.
182
+ // On non-TTY, the spinner doesn't share a line so clearing is a no-op.
183
+ clearSpinnerLine();
184
+ stream.write(text);
185
+ // If the chunk does not end in a newline, the spinner repaint would
186
+ // overwrite the tail of the chunk. Insert a soft newline before
187
+ // re-rendering so the chunk is preserved verbatim in scrollback.
188
+ if (active && tty && !text.endsWith("\n")) {
189
+ stream.write("\n");
190
+ }
191
+ repaint();
192
+ },
193
+ };
194
+ }
195
+ /**
196
+ * Format an elapsed-time in milliseconds as either `Nms`, `N.Ns`, or
197
+ * `Nm Ns`. Used by `succeed()` so the completion line carries the actual
198
+ * sub-task duration without the caller having to compute it.
199
+ */
200
+ function formatDuration(ms) {
201
+ if (ms < 1000) {
202
+ return `${ms}ms`;
203
+ }
204
+ if (ms < 60_000) {
205
+ const seconds = (ms / 1000).toFixed(1);
206
+ return `${seconds}s`;
207
+ }
208
+ const minutes = Math.floor(ms / 60_000);
209
+ const seconds = Math.floor((ms % 60_000) / 1000);
210
+ return `${minutes}m ${seconds}s`;
211
+ }
212
+ //# sourceMappingURL=progress.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"progress.js","sourceRoot":"","sources":["../../src/core/progress.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Proxy environment detection & NODE_OPTIONS merging helpers.
3
+ *
4
+ * `radar` runs `fetch()` on Node's built-in `undici`. Honoring `HTTPS_PROXY` /
5
+ * `HTTP_PROXY` requires `NODE_OPTIONS=--use-env-proxy` (Node 22.21+ / 24.5+).
6
+ * Rather than asking users to set it manually, the `bin` self-respawns with
7
+ * the flag injected when a proxy env var is present.
8
+ *
9
+ * This module is intentionally pure (no I/O, no `process` access) so the
10
+ * detection / merge logic can be unit tested with arbitrary env objects.
11
+ */
12
+ /**
13
+ * Result of probing the environment for a proxy URL. `source` records which
14
+ * variable supplied the URL so the CLI can warn when only `ALL_PROXY` is set
15
+ * (`ALL_PROXY` does not engage `--use-env-proxy`; users must also set
16
+ * `HTTPS_PROXY` or `HTTP_PROXY`).
17
+ */
18
+ export interface ProxyDetection {
19
+ /** The proxy URL string as found in env (no normalization). */
20
+ url: string;
21
+ /** Variable name the URL was sourced from. */
22
+ source: "HTTPS_PROXY" | "HTTP_PROXY" | "ALL_PROXY";
23
+ /**
24
+ * True when the URL came **only** from `ALL_PROXY` and no `HTTPS_PROXY` /
25
+ * `HTTP_PROXY` is set. `--use-env-proxy` ignores `ALL_PROXY`, so the CLI
26
+ * should warn the user before respawning would otherwise be a no-op.
27
+ */
28
+ allProxyOnly: boolean;
29
+ }
30
+ /**
31
+ * Lookup order: HTTPS_PROXY → HTTP_PROXY → ALL_PROXY, each tried in upper-case
32
+ * then lower-case form (POSIX convention, matches what `undici` itself checks).
33
+ * Empty-string values are treated as unset, matching `curl` / `wget` behavior
34
+ * where `HTTPS_PROXY=""` disables the proxy rather than configures it.
35
+ */
36
+ export declare function detectProxyUrl(env: NodeJS.ProcessEnv): ProxyDetection | undefined;
37
+ /**
38
+ * Append `flag` to an existing `NODE_OPTIONS` string, preserving user-supplied
39
+ * options. Skips the append when the exact flag is already present (whitespace
40
+ * separated) so re-spawn chains don't keep growing the env var.
41
+ *
42
+ * `existing` may be `undefined` (env var unset) or empty string — both yield
43
+ * just `flag` with no leading whitespace.
44
+ */
45
+ export declare function mergeNodeOptions(existing: string | undefined, flag: string): string;
46
+ /**
47
+ * Convert a `NO_PROXY` env value (Node / curl / wget convention) to Playwright's
48
+ * `proxy.bypass` form.
49
+ *
50
+ * Differences between the two formats:
51
+ *
52
+ * | Concern | Node `NO_PROXY` | Playwright `bypass` |
53
+ * |----------------|------------------------------|---------------------------------|
54
+ * | Separator | `,` | `;` |
55
+ * | Domain suffix | `.example.com` (leading dot) | `*.example.com` (glob wildcard) |
56
+ * | Bare host | `example.com` | `example.com` |
57
+ *
58
+ * Empty / whitespace-only entries are dropped so trailing commas or accidental
59
+ * double commas don't produce empty rules (which Playwright would treat as
60
+ * "bypass nothing"). Returns `undefined` when the input is unset / empty so
61
+ * callers can pass through "no bypass list" to Playwright without an empty
62
+ * string (Playwright treats `""` as a valid empty bypass list — slightly
63
+ * different intent than "not specified").
64
+ */
65
+ export declare function noProxyToPlaywrightBypass(noProxy: string | undefined): string | undefined;
66
+ /**
67
+ * Redact basic-auth credentials from a proxy URL before printing.
68
+ *
69
+ * Proxy URLs like `http://user:pass@proxy:8080` are routinely set in env vars
70
+ * (`HTTPS_PROXY`, `HTTP_PROXY`) at corporate networks. `radar doctor` prints
71
+ * the detected URL so users can confirm radar is honoring the right env var,
72
+ * but the credentials must never reach stdout — terminal scrollback, CI
73
+ * artifact logs, and shared screenshots are all common leak vectors.
74
+ *
75
+ * Strategy:
76
+ * - Userinfo is replaced with `***:***` when **both** username and password
77
+ * are present. When only a username is set, it is still replaced (treating
78
+ * it as a credential because some proxies encode tokens in the userinfo
79
+ * slot without a separate password).
80
+ * - All other URL components (scheme / host / port / path / query) are
81
+ * preserved verbatim so the user can still verify the proxy address.
82
+ * - Invalid URLs that can't be parsed are returned as `***` rather than the
83
+ * original string — better to over-redact than risk leaking malformed
84
+ * credentials we couldn't classify.
85
+ */
86
+ export declare function maskProxyUrl(url: string): string;
87
+ //# sourceMappingURL=proxy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../../src/core/proxy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,+DAA+D;IAC/D,GAAG,EAAE,MAAM,CAAC;IACZ,8CAA8C;IAC9C,MAAM,EAAE,aAAa,GAAG,YAAY,GAAG,WAAW,CAAC;IACnD;;;;OAIG;IACH,YAAY,EAAE,OAAO,CAAC;CACvB;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,cAAc,GAAG,SAAS,CAmBjF;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAOnF;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAiBzF;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CA2BhD"}
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Proxy environment detection & NODE_OPTIONS merging helpers.
3
+ *
4
+ * `radar` runs `fetch()` on Node's built-in `undici`. Honoring `HTTPS_PROXY` /
5
+ * `HTTP_PROXY` requires `NODE_OPTIONS=--use-env-proxy` (Node 22.21+ / 24.5+).
6
+ * Rather than asking users to set it manually, the `bin` self-respawns with
7
+ * the flag injected when a proxy env var is present.
8
+ *
9
+ * This module is intentionally pure (no I/O, no `process` access) so the
10
+ * detection / merge logic can be unit tested with arbitrary env objects.
11
+ */
12
+ /**
13
+ * Lookup order: HTTPS_PROXY → HTTP_PROXY → ALL_PROXY, each tried in upper-case
14
+ * then lower-case form (POSIX convention, matches what `undici` itself checks).
15
+ * Empty-string values are treated as unset, matching `curl` / `wget` behavior
16
+ * where `HTTPS_PROXY=""` disables the proxy rather than configures it.
17
+ */
18
+ export function detectProxyUrl(env) {
19
+ const pick = (name) => {
20
+ const upper = env[name];
21
+ if (upper && upper.length > 0)
22
+ return upper;
23
+ const lower = env[name.toLowerCase()];
24
+ if (lower && lower.length > 0)
25
+ return lower;
26
+ return undefined;
27
+ };
28
+ const https = pick("HTTPS_PROXY");
29
+ if (https)
30
+ return { url: https, source: "HTTPS_PROXY", allProxyOnly: false };
31
+ const http = pick("HTTP_PROXY");
32
+ if (http)
33
+ return { url: http, source: "HTTP_PROXY", allProxyOnly: false };
34
+ const all = pick("ALL_PROXY");
35
+ if (all)
36
+ return { url: all, source: "ALL_PROXY", allProxyOnly: true };
37
+ return undefined;
38
+ }
39
+ /**
40
+ * Append `flag` to an existing `NODE_OPTIONS` string, preserving user-supplied
41
+ * options. Skips the append when the exact flag is already present (whitespace
42
+ * separated) so re-spawn chains don't keep growing the env var.
43
+ *
44
+ * `existing` may be `undefined` (env var unset) or empty string — both yield
45
+ * just `flag` with no leading whitespace.
46
+ */
47
+ export function mergeNodeOptions(existing, flag) {
48
+ if (!existing || existing.length === 0)
49
+ return flag;
50
+ // Whitespace-split avoids false positives like matching `--use-env-proxy`
51
+ // against an unrelated flag that contains the same substring.
52
+ const tokens = existing.split(/\s+/).filter((t) => t.length > 0);
53
+ if (tokens.includes(flag))
54
+ return existing;
55
+ return `${existing} ${flag}`;
56
+ }
57
+ /**
58
+ * Convert a `NO_PROXY` env value (Node / curl / wget convention) to Playwright's
59
+ * `proxy.bypass` form.
60
+ *
61
+ * Differences between the two formats:
62
+ *
63
+ * | Concern | Node `NO_PROXY` | Playwright `bypass` |
64
+ * |----------------|------------------------------|---------------------------------|
65
+ * | Separator | `,` | `;` |
66
+ * | Domain suffix | `.example.com` (leading dot) | `*.example.com` (glob wildcard) |
67
+ * | Bare host | `example.com` | `example.com` |
68
+ *
69
+ * Empty / whitespace-only entries are dropped so trailing commas or accidental
70
+ * double commas don't produce empty rules (which Playwright would treat as
71
+ * "bypass nothing"). Returns `undefined` when the input is unset / empty so
72
+ * callers can pass through "no bypass list" to Playwright without an empty
73
+ * string (Playwright treats `""` as a valid empty bypass list — slightly
74
+ * different intent than "not specified").
75
+ */
76
+ export function noProxyToPlaywrightBypass(noProxy) {
77
+ if (!noProxy)
78
+ return undefined;
79
+ const entries = noProxy
80
+ .split(",")
81
+ .map((s) => s.trim())
82
+ .filter((s) => s.length > 0)
83
+ .map((entry) => {
84
+ // Node convention: a leading dot means "match this domain and all
85
+ // subdomains" (`.example.com` matches `api.example.com`). Playwright
86
+ // expresses the same intent with a glob wildcard: `*.example.com`.
87
+ // Bare hosts (no leading dot) are passed through untouched — both
88
+ // formats agree on the exact-match semantics there.
89
+ if (entry.startsWith("."))
90
+ return `*${entry}`;
91
+ return entry;
92
+ });
93
+ if (entries.length === 0)
94
+ return undefined;
95
+ return entries.join(";");
96
+ }
97
+ /**
98
+ * Redact basic-auth credentials from a proxy URL before printing.
99
+ *
100
+ * Proxy URLs like `http://user:pass@proxy:8080` are routinely set in env vars
101
+ * (`HTTPS_PROXY`, `HTTP_PROXY`) at corporate networks. `radar doctor` prints
102
+ * the detected URL so users can confirm radar is honoring the right env var,
103
+ * but the credentials must never reach stdout — terminal scrollback, CI
104
+ * artifact logs, and shared screenshots are all common leak vectors.
105
+ *
106
+ * Strategy:
107
+ * - Userinfo is replaced with `***:***` when **both** username and password
108
+ * are present. When only a username is set, it is still replaced (treating
109
+ * it as a credential because some proxies encode tokens in the userinfo
110
+ * slot without a separate password).
111
+ * - All other URL components (scheme / host / port / path / query) are
112
+ * preserved verbatim so the user can still verify the proxy address.
113
+ * - Invalid URLs that can't be parsed are returned as `***` rather than the
114
+ * original string — better to over-redact than risk leaking malformed
115
+ * credentials we couldn't classify.
116
+ */
117
+ export function maskProxyUrl(url) {
118
+ let parsed;
119
+ try {
120
+ parsed = new URL(url);
121
+ }
122
+ catch {
123
+ // We can't tell where the credentials are if parsing failed; treat the
124
+ // whole string as sensitive. The doctor still tells the user *which*
125
+ // env var the URL came from, so total redaction is acceptable here.
126
+ return "***";
127
+ }
128
+ if (parsed.password) {
129
+ // Both halves present (or password-only — unusual but spec-valid). Mask
130
+ // both with `***` so neither leaks into terminal output. We intentionally
131
+ // don't keep the username "for debugging" because admins often use the
132
+ // username slot for tokens or service-account names that are themselves
133
+ // sensitive.
134
+ parsed.username = "***";
135
+ parsed.password = "***";
136
+ }
137
+ else if (parsed.username) {
138
+ // Userinfo with no password: some proxies encode service-account tokens
139
+ // or PATs in the username slot. Replace just the username; keep the URL
140
+ // shape (no spurious `:` separator) so the output matches the user's env
141
+ // var format.
142
+ parsed.username = "***";
143
+ }
144
+ return parsed.toString();
145
+ }
146
+ //# sourceMappingURL=proxy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy.js","sourceRoot":"","sources":["../../src/core/proxy.ts"],"names":[],"mappings":"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"}