@illuminis/comprism 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 (142) hide show
  1. package/README.md +61 -2
  2. package/out/agent/command.d.ts +75 -4
  3. package/out/agent/command.js +221 -26
  4. package/out/agent/render.d.ts +56 -8
  5. package/out/agent/render.js +441 -39
  6. package/out/agent/session.d.ts +156 -2
  7. package/out/agent/session.js +795 -154
  8. package/out/commands/agents.d.ts +2 -0
  9. package/out/commands/agents.js +79 -0
  10. package/out/commands/ask.d.ts +1 -1
  11. package/out/commands/ask.js +88 -12
  12. package/out/commands/commands-thin.js +79 -17
  13. package/out/commands/config.d.ts +1 -0
  14. package/out/commands/config.js +138 -0
  15. package/out/commands/cost.d.ts +1 -0
  16. package/out/commands/cost.js +167 -0
  17. package/out/commands/hooks.d.ts +1 -0
  18. package/out/commands/hooks.js +83 -0
  19. package/out/commands/install.d.ts +44 -1
  20. package/out/commands/install.js +198 -4
  21. package/out/commands/instructions.d.ts +1 -0
  22. package/out/commands/instructions.js +113 -0
  23. package/out/commands/integrations.d.ts +3 -0
  24. package/out/commands/integrations.js +215 -0
  25. package/out/commands/jobs.d.ts +5 -0
  26. package/out/commands/jobs.js +157 -0
  27. package/out/commands/login.js +249 -43
  28. package/out/commands/memory.d.ts +3 -0
  29. package/out/commands/memory.js +113 -0
  30. package/out/commands/permissions.d.ts +1 -0
  31. package/out/commands/permissions.js +94 -0
  32. package/out/commands/plugins.d.ts +4 -0
  33. package/out/commands/plugins.js +192 -0
  34. package/out/commands/privacy.d.ts +1 -0
  35. package/out/commands/privacy.js +57 -0
  36. package/out/commands/providerKey.d.ts +32 -0
  37. package/out/commands/providerKey.js +108 -0
  38. package/out/commands/repl.d.ts +12 -1
  39. package/out/commands/repl.js +1649 -122
  40. package/out/commands/report.d.ts +39 -0
  41. package/out/commands/report.js +115 -0
  42. package/out/commands/review.d.ts +5 -0
  43. package/out/commands/review.js +223 -0
  44. package/out/commands/sessions.d.ts +23 -0
  45. package/out/commands/sessions.js +115 -0
  46. package/out/commands/settings.d.ts +3 -1
  47. package/out/commands/settings.js +18 -16
  48. package/out/commands/skills.d.ts +21 -0
  49. package/out/commands/skills.js +207 -0
  50. package/out/commands/unattended.d.ts +7 -0
  51. package/out/commands/unattended.js +351 -0
  52. package/out/commands/update.d.ts +1 -0
  53. package/out/commands/update.js +123 -0
  54. package/out/commands/welcome.d.ts +25 -1
  55. package/out/commands/welcome.js +71 -35
  56. package/out/commands/worktrees.d.ts +5 -0
  57. package/out/commands/worktrees.js +186 -0
  58. package/out/executor/browser.d.ts +14 -0
  59. package/out/executor/browser.js +270 -0
  60. package/out/executor/diagnostics.d.ts +2 -0
  61. package/out/executor/diagnostics.js +181 -0
  62. package/out/executor/files.js +270 -40
  63. package/out/executor/git.js +42 -29
  64. package/out/executor/hooks.d.ts +42 -58
  65. package/out/executor/hooks.js +89 -182
  66. package/out/executor/index.d.ts +21 -5
  67. package/out/executor/index.js +160 -14
  68. package/out/executor/paths.d.ts +6 -1
  69. package/out/executor/paths.js +34 -6
  70. package/out/executor/sandbox.d.ts +40 -0
  71. package/out/executor/sandbox.js +299 -0
  72. package/out/executor/shell.d.ts +49 -4
  73. package/out/executor/shell.js +302 -56
  74. package/out/executor/toolservers.d.ts +20 -0
  75. package/out/executor/toolservers.js +189 -0
  76. package/out/executor/worktree.d.ts +9 -0
  77. package/out/executor/worktree.js +119 -0
  78. package/out/graph/read-python.js +2 -1
  79. package/out/lib/attach.d.ts +56 -12
  80. package/out/lib/attach.js +230 -63
  81. package/out/lib/clipboard.d.ts +23 -0
  82. package/out/lib/clipboard.js +182 -0
  83. package/out/lib/commandlist.d.ts +20 -0
  84. package/out/lib/commandlist.js +58 -0
  85. package/out/lib/decision.d.ts +22 -0
  86. package/out/lib/decision.js +50 -0
  87. package/out/lib/fingerprint.d.ts +25 -0
  88. package/out/lib/fingerprint.js +58 -0
  89. package/out/lib/gateway.d.ts +190 -2
  90. package/out/lib/gateway.js +59 -4
  91. package/out/lib/history.d.ts +24 -0
  92. package/out/lib/history.js +137 -0
  93. package/out/lib/ide.d.ts +19 -0
  94. package/out/lib/ide.js +131 -0
  95. package/out/lib/keyboard.d.ts +98 -0
  96. package/out/lib/keyboard.js +390 -0
  97. package/out/lib/lastlogin.d.ts +14 -0
  98. package/out/lib/lastlogin.js +105 -0
  99. package/out/lib/machine.d.ts +21 -0
  100. package/out/lib/machine.js +91 -0
  101. package/out/lib/notify.d.ts +4 -0
  102. package/out/lib/notify.js +52 -0
  103. package/out/lib/output.d.ts +48 -0
  104. package/out/lib/output.js +108 -0
  105. package/out/lib/picker.d.ts +1 -0
  106. package/out/lib/picker.js +158 -0
  107. package/out/lib/project-ops.d.ts +19 -0
  108. package/out/lib/project-ops.js +146 -0
  109. package/out/lib/project.d.ts +28 -0
  110. package/out/lib/project.js +114 -0
  111. package/out/lib/prompt.d.ts +3 -0
  112. package/out/lib/prompt.js +36 -2
  113. package/out/lib/queue.d.ts +13 -0
  114. package/out/lib/queue.js +116 -0
  115. package/out/lib/readiness.d.ts +19 -0
  116. package/out/lib/readiness.js +170 -1
  117. package/out/lib/self.d.ts +23 -0
  118. package/out/lib/self.js +124 -0
  119. package/out/lib/sessions.d.ts +19 -0
  120. package/out/lib/sessions.js +221 -0
  121. package/out/lib/startcache.d.ts +13 -0
  122. package/out/lib/startcache.js +78 -0
  123. package/out/lib/stdin.d.ts +32 -0
  124. package/out/lib/stdin.js +117 -0
  125. package/out/lib/store.d.ts +40 -0
  126. package/out/lib/store.js +138 -0
  127. package/out/lib/sync.d.ts +18 -0
  128. package/out/lib/sync.js +81 -0
  129. package/out/lib/ui.d.ts +10 -4
  130. package/out/lib/ui.js +65 -25
  131. package/out/lib/voice.d.ts +36 -0
  132. package/out/lib/voice.js +160 -1
  133. package/out/native/dictate +0 -0
  134. package/out/postinstall.js +42 -17
  135. package/out/providers/anthropic.d.ts +22 -0
  136. package/out/providers/anthropic.js +80 -0
  137. package/out/providers/index.d.ts +8 -0
  138. package/out/providers/index.js +83 -0
  139. package/out/providers/openai.d.ts +11 -0
  140. package/out/providers/openai.js +57 -0
  141. package/out/thin.js +595 -33
  142. package/package.json +11 -50
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.collectBatch = collectBatch;
4
+ exports.sync = sync;
5
+ /**
6
+ * Sync: the one thin contract between the Companion and the tenant.
7
+ *
8
+ * The Companion works standalone with no service to call - that is the wedge,
9
+ * and nothing here changes it. Sync is opt-in, additive, and one direction: the
10
+ * local stores are read, posted, and left exactly as they were. Nothing is
11
+ * deleted locally on success, because the local record is the primary evidence
12
+ * and a server round trip is not a reason to destroy it.
13
+ *
14
+ * Two properties make it safe to run on a cron:
15
+ *
16
+ * **Idempotent.** Every record is keyed by the id the Companion assigned it,
17
+ * and the server refuses a duplicate rather than inserting a second copy. A
18
+ * retry after a dropped connection cannot double-count anyone's spend.
19
+ *
20
+ * **Reconciled.** The response carries what the batch claimed and what the
21
+ * tenant now holds. If the two disagree, that is visible here, in the command
22
+ * the person just ran - not three weeks later in a figure someone is trying to
23
+ * defend to a CFO.
24
+ */
25
+ const chains_1 = require("../internal/chains");
26
+ const config_1 = require("./config");
27
+ const evidence_1 = require("../internal/evidence");
28
+ const links_1 = require("../internal/links");
29
+ const scan_1 = require("../internal/scan");
30
+ const sessions_1 = require("../commands/sessions");
31
+ const snapshot_1 = require("../internal/snapshot");
32
+ const store_1 = require("./store");
33
+ const version_1 = require("./version");
34
+ /** Everything the local stores hold, in the shape the tenant expects. */
35
+ function collectBatch() {
36
+ const sessions = (0, sessions_1.listSessions)();
37
+ const chains = (0, chains_1.buildChainsFromStore)(sessions);
38
+ return {
39
+ companion_version: version_1.COMPRISM_VERSION,
40
+ sessions,
41
+ ledger: (0, store_1.readRows)(store_1.LEDGER_FILE),
42
+ decisions: (0, snapshot_1.readSnapshots)(),
43
+ chains,
44
+ links: (0, links_1.readLinks)(),
45
+ executed_pairs: (0, evidence_1.readExecutedPairs)(),
46
+ policy_rejections: (0, evidence_1.readRejections)(),
47
+ exploration: (0, evidence_1.readExploration)(),
48
+ imported_ledger: (0, scan_1.readImportedLedger)(),
49
+ scan_findings: (0, scan_1.readFindings)(),
50
+ };
51
+ }
52
+ async function sync(target) {
53
+ const batch = collectBatch();
54
+ const cfg = (0, config_1.loadConfig)();
55
+ const headers = {
56
+ 'content-type': 'application/json',
57
+ authorization: `Bearer ${target.token}`,
58
+ };
59
+ // On localhost there is no subdomain, so the tenant travels in a header.
60
+ // Hyphenated in the header, underscored in the schema: the single most common
61
+ // cause of "it cannot find the tenant".
62
+ if (target.tenant)
63
+ headers['X-Tenant-ID'] = target.tenant;
64
+ const res = await fetch(`${target.url.replace(/\/+$/, '')}/api/v1/completion/sync`, {
65
+ method: 'POST',
66
+ headers,
67
+ body: JSON.stringify(batch),
68
+ });
69
+ if (!res.ok) {
70
+ // The status line, never the body: a server error body can echo the payload,
71
+ // and the payload is the customer's record.
72
+ throw new Error(`sync failed: ${res.status} ${res.statusText}`);
73
+ }
74
+ const result = (await res.json());
75
+ const sent = {};
76
+ for (const [key, value] of Object.entries(batch)) {
77
+ if (Array.isArray(value))
78
+ sent[key] = value.length;
79
+ }
80
+ return { ...result, sent, ...(cfg.mode ? {} : {}) };
81
+ }
package/out/lib/ui.d.ts CHANGED
@@ -35,6 +35,14 @@ export declare const c: {
35
35
  * and the brand is the one thing a customer notices before the engine.
36
36
  */
37
37
  export declare function spectrumAt(index: number, glyph: string): string;
38
+ /**
39
+ * A small picture, drawn two pixels to a character with the half block glyph:
40
+ * the top pixel is the glyph's color and the bottom one its background, which
41
+ * makes each pixel square in an ordinary terminal font. Each row of `pixels`
42
+ * is six hex digits per pixel, `......` for a clear one, and two rows make one
43
+ * line. Returns nothing without color, because the picture is only color.
44
+ */
45
+ export declare function pixelArt(pixels: string[]): string[];
38
46
  export declare function prismRule(w?: number): string;
39
47
  export declare function banner(subtitle?: string): string;
40
48
  export declare function money(v: number | null): string;
@@ -79,12 +87,10 @@ export declare function table(headers: string[], rows: string[][]): string;
79
87
  * out of the way, leaving the answer and a one-line receipt.
80
88
  */
81
89
  export declare class LiveIndicator {
82
- private timer;
83
- private frame;
84
- private label;
85
- private readonly frames;
90
+ private shown;
86
91
  start(label: string): void;
87
92
  update(label: string): void;
93
+ private draw;
88
94
  stop(): void;
89
95
  }
90
96
  export interface ReceiptData {
package/out/lib/ui.js CHANGED
@@ -19,6 +19,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
19
19
  exports.LiveIndicator = exports.c = void 0;
20
20
  exports.width = width;
21
21
  exports.spectrumAt = spectrumAt;
22
+ exports.pixelArt = pixelArt;
22
23
  exports.prismRule = prismRule;
23
24
  exports.banner = banner;
24
25
  exports.money = money;
@@ -35,15 +36,20 @@ exports.warn = warn;
35
36
  exports.fail = fail;
36
37
  exports.info = info;
37
38
  const isTty = process.stdout.isTTY === true;
38
- const noColor = !!process.env.NO_COLOR || process.env.TERM === 'dumb';
39
- const useColor = isTty && !noColor;
39
+ /** Decided when drawn, not when loaded, so `--plain`, `plain true` and
40
+ * `theme plain` applied at start up take effect (manual 6.10, 6.12). */
41
+ const noColor = () => !!process.env.NO_COLOR || process.env.TERM === 'dumb';
42
+ const useColorNow = () => isTty && !noColor();
43
+ /** Plain mode: no color and nothing redrawn; every update is a new line. */
44
+ const plainNow = () => process.env.COMPRISM_PLAIN === '1' || !isTty;
45
+ const light = () => process.env.COMPRISM_THEME === 'light';
40
46
  function width() {
41
47
  return Math.min(process.stdout.columns || 88, 100);
42
48
  }
43
49
  // ── palette ─────────────────────────────────────────────────────────────────
44
50
  // illuminis brand, from branding/brand.toml. Deep navy is unreadable on a dark
45
51
  // terminal, so the terminal palette leans on the two blues and the purple.
46
- const rgb = (r, g, b) => (s) => useColor ? `\x1b[38;2;${r};${g};${b}m${s}\x1b[0m` : s;
52
+ const rgb = (r, g, b) => (s) => useColorNow() ? `\x1b[38;2;${r};${g};${b}m${s}\x1b[0m` : s;
47
53
  exports.c = {
48
54
  blue: rgb(59, 111, 181), // primary_blue #3B6FB5
49
55
  deep: rgb(31, 77, 120), // heading_blue #1F4D78
@@ -52,9 +58,10 @@ exports.c = {
52
58
  amber: rgb(217, 119, 6), // warning #D97706
53
59
  red: rgb(220, 38, 38), // error #DC2626
54
60
  muted: rgb(158, 158, 158), // muted_text #9E9E9E
55
- text: rgb(214, 219, 228),
56
- bold: (s) => (useColor ? `\x1b[1m${s}\x1b[0m` : s),
57
- dim: (s) => (useColor ? `\x1b[2m${s}\x1b[0m` : s),
61
+ // Light on a dark terminal, dark on a light one (manual 6.10).
62
+ text: (s) => (light() ? rgb(40, 44, 52)(s) : rgb(214, 219, 228)(s)),
63
+ bold: (s) => (useColorNow() ? `\x1b[1m${s}\x1b[0m` : s),
64
+ dim: (s) => (useColorNow() ? `\x1b[2m${s}\x1b[0m` : s),
58
65
  };
59
66
  /** The prism: white light entering, a spectrum leaving. The brand in one line. */
60
67
  const SPECTRUM = [
@@ -75,13 +82,46 @@ const SPECTRUM = [
75
82
  * and the brand is the one thing a customer notices before the engine.
76
83
  */
77
84
  function spectrumAt(index, glyph) {
78
- if (!useColor)
85
+ if (!useColorNow())
79
86
  return glyph;
80
87
  const [r, g, b] = SPECTRUM[Math.abs(index) % SPECTRUM.length];
81
88
  return `\x1b[38;2;${r};${g};${b}m${glyph}\x1b[0m`;
82
89
  }
90
+ /**
91
+ * A small picture, drawn two pixels to a character with the half block glyph:
92
+ * the top pixel is the glyph's color and the bottom one its background, which
93
+ * makes each pixel square in an ordinary terminal font. Each row of `pixels`
94
+ * is six hex digits per pixel, `......` for a clear one, and two rows make one
95
+ * line. Returns nothing without color, because the picture is only color.
96
+ */
97
+ function pixelArt(pixels) {
98
+ if (!useColorNow())
99
+ return [];
100
+ const at = (row, x) => {
101
+ const hex = row?.slice(x * 6, x * 6 + 6);
102
+ return !hex || hex === '......' ? null
103
+ : [0, 2, 4].map((k) => parseInt(hex.slice(k, k + 2), 16)).join(';');
104
+ };
105
+ const lines = [];
106
+ for (let y = 0; y < pixels.length; y += 2) {
107
+ let line = '';
108
+ for (let x = 0; x < (pixels[y]?.length ?? 0) / 6; x++) {
109
+ const top = at(pixels[y], x), bottom = at(pixels[y + 1], x);
110
+ if (top && bottom)
111
+ line += `\x1b[38;2;${top}m\x1b[48;2;${bottom}m▀\x1b[0m`;
112
+ else if (top)
113
+ line += `\x1b[38;2;${top}m▀\x1b[0m`;
114
+ else if (bottom)
115
+ line += `\x1b[38;2;${bottom}m▄\x1b[0m`;
116
+ else
117
+ line += ' ';
118
+ }
119
+ lines.push(line);
120
+ }
121
+ return lines;
122
+ }
83
123
  function prismRule(w = width()) {
84
- if (!useColor)
124
+ if (!useColorNow())
85
125
  return '-'.repeat(w);
86
126
  const seg = Math.max(1, Math.floor(w / SPECTRUM.length));
87
127
  let out = '';
@@ -197,29 +237,29 @@ function table(headers, rows) {
197
237
  * out of the way, leaving the answer and a one-line receipt.
198
238
  */
199
239
  class LiveIndicator {
200
- timer = null;
201
- frame = 0;
202
- label = '';
203
- frames = ['◜', '◠', '◝', '◞', '◡', '◟'];
240
+ shown = false;
241
+ // No animation of its own. A glyph turning on a timer looks like work
242
+ // whether or not anything is happening; the line changes only when the
243
+ // caller has something new to say, and in plain mode each change is a new
244
+ // line rather than a redraw (manual 6.12).
204
245
  start(label) {
205
- if (!isTty)
206
- return;
207
- this.label = label;
208
- this.timer = setInterval(() => {
209
- const glyph = this.frames[this.frame % this.frames.length];
210
- this.frame += 1;
211
- process.stdout.write(`\r ${exports.c.purple(glyph)} ${exports.c.dim(this.label)} `);
212
- }, 90);
246
+ this.draw(label);
213
247
  }
214
248
  update(label) {
215
- this.label = label;
249
+ this.draw(label);
250
+ }
251
+ draw(label) {
252
+ if (plainNow()) {
253
+ process.stdout.write(` ${label}\n`);
254
+ return;
255
+ }
256
+ process.stdout.write(`\r ${exports.c.purple('◠')} ${exports.c.dim(label)} `);
257
+ this.shown = true;
216
258
  }
217
259
  stop() {
218
- if (this.timer)
219
- clearInterval(this.timer);
220
- this.timer = null;
221
- if (isTty)
260
+ if (this.shown && !plainNow())
222
261
  process.stdout.write(`\r${' '.repeat(width())}\r`);
262
+ this.shown = false;
223
263
  }
224
264
  }
225
265
  exports.LiveIndicator = LiveIndicator;
@@ -34,6 +34,42 @@ export interface Recording {
34
34
  * headed off with `missing` so the person gets a sentence rather than an error.
35
35
  */
36
36
  export declare function start(): Recording;
37
+ /** Live dictation in progress, on the Mac's own recogniser. */
38
+ export interface LiveDictation {
39
+ /** The loudness now, 0 to 1. */
40
+ level: () => number;
41
+ /** The words so far. */
42
+ words: () => string;
43
+ /** Stop listening and get the final words, or null when it could not listen. */
44
+ stop: () => Promise<string | null>;
45
+ /** Why it could not listen, when it could not. */
46
+ problem: () => string;
47
+ }
48
+ /**
49
+ * Words as they are spoken, the way the web app shows them.
50
+ *
51
+ * Nothing is recorded to a file and nothing leaves the machine: the Mac's own
52
+ * recogniser listens and reports what it hears, several times a second.
53
+ * Returns null where there is no helper, and the caller records and has the
54
+ * server write it down instead, as before.
55
+ */
56
+ export declare function listenLive(language?: string): LiveDictation | null;
57
+ /**
58
+ * How loud the last tenth of a second was, from 0 to 1.
59
+ *
60
+ * Read off the file as it is written, so somebody speaking sees the meter move
61
+ * and knows they are being heard, before a single word has come back. Sixteen
62
+ * bit mono at 16 kHz is 3,200 bytes a tenth of a second.
63
+ */
64
+ export declare function level(file: string): number;
65
+ /**
66
+ * What has been said so far, as a WAV file a transcriber can read.
67
+ *
68
+ * The recorder writes the header's lengths only when it stops, so a copy taken
69
+ * mid recording claims no audio at all. The copy gets the lengths of what is
70
+ * actually there. Returns null until there is at least half a second to send.
71
+ */
72
+ export declare function soFar(file: string): string | null;
37
73
  /** Delete the recording and the folder holding it. Always, and immediately. */
38
74
  export declare function discard(file: string): void;
39
75
  /**
package/out/lib/voice.js CHANGED
@@ -36,6 +36,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.recorder = recorder;
37
37
  exports.missing = missing;
38
38
  exports.start = start;
39
+ exports.listenLive = listenLive;
40
+ exports.level = level;
41
+ exports.soFar = soFar;
39
42
  exports.discard = discard;
40
43
  exports.transcribe = transcribe;
41
44
  /**
@@ -102,7 +105,9 @@ const RECORDERS = [
102
105
  ...(process.platform === 'darwin'
103
106
  ? ['-f', 'avfoundation', '-i', ':default']
104
107
  : ['-f', 'alsa', '-i', 'default']),
105
- '-ac', '1', '-ar', RATE, '-y', out,
108
+ // Written as it is heard, not held until the end, so the meter and the
109
+ // words so far have something to read while the person is speaking.
110
+ '-ac', '1', '-ar', RATE, '-flush_packets', '1', '-y', out,
106
111
  ],
107
112
  },
108
113
  // Linux's own, present on most desktop installs.
@@ -143,6 +148,30 @@ function start() {
143
148
  stdio: ['ignore', 'ignore', 'ignore'],
144
149
  });
145
150
  let stopped = null;
151
+ // The recorder never outlives the session. Closing the window or ending the
152
+ // process while it listens would otherwise leave a microphone recording to a
153
+ // file nobody knows about, the worst thing this tool could do.
154
+ const abandon = () => {
155
+ try {
156
+ child.kill('SIGKILL');
157
+ }
158
+ catch { /* already gone */ }
159
+ discard(file);
160
+ };
161
+ const onSignal = (signal) => {
162
+ abandon();
163
+ release();
164
+ process.kill(process.pid, signal);
165
+ };
166
+ const release = () => {
167
+ process.off('exit', abandon);
168
+ process.off('SIGHUP', onSignal);
169
+ process.off('SIGTERM', onSignal);
170
+ };
171
+ process.once('exit', abandon);
172
+ process.once('SIGHUP', onSignal);
173
+ process.once('SIGTERM', onSignal);
174
+ child.once('close', release);
146
175
  const stop = () => {
147
176
  if (stopped)
148
177
  return stopped;
@@ -182,6 +211,136 @@ function start() {
182
211
  };
183
212
  return { file, by: tool.name, stop };
184
213
  }
214
+ /** The helper built beside this file on a Mac, when there is one. */
215
+ function helper() {
216
+ if (process.platform !== 'darwin')
217
+ return null;
218
+ const p = path.join(__dirname, '..', 'native', 'dictate');
219
+ return fs.existsSync(p) ? p : null;
220
+ }
221
+ /**
222
+ * Words as they are spoken, the way the web app shows them.
223
+ *
224
+ * Nothing is recorded to a file and nothing leaves the machine: the Mac's own
225
+ * recogniser listens and reports what it hears, several times a second.
226
+ * Returns null where there is no helper, and the caller records and has the
227
+ * server write it down instead, as before.
228
+ */
229
+ function listenLive(language = 'en-US') {
230
+ const bin = helper();
231
+ if (!bin)
232
+ return null;
233
+ const child = (0, node_child_process_1.spawn)(bin, [language], { stdio: ['pipe', 'pipe', 'ignore'] });
234
+ let loud = 0;
235
+ let heard = '';
236
+ let final = null;
237
+ let why = '';
238
+ let rest = '';
239
+ const ended = new Promise((resolve) => child.once('close', () => resolve()));
240
+ child.stdout?.on('data', (chunk) => {
241
+ rest += chunk.toString('utf8');
242
+ const lines = rest.split('\n');
243
+ rest = lines.pop() ?? '';
244
+ for (const line of lines) {
245
+ const kind = line.slice(0, 1);
246
+ const text = line.slice(2);
247
+ if (kind === 'L')
248
+ loud = Number(text) || 0;
249
+ else if (kind === 'P')
250
+ heard = text;
251
+ else if (kind === 'F')
252
+ final = text;
253
+ else if (kind === 'E')
254
+ why = text;
255
+ }
256
+ });
257
+ // The helper never outlives the session.
258
+ const abandon = () => { try {
259
+ child.kill('SIGKILL');
260
+ }
261
+ catch { /* gone */ } };
262
+ process.once('exit', abandon);
263
+ return {
264
+ level: () => loud,
265
+ words: () => heard,
266
+ problem: () => why,
267
+ stop: async () => {
268
+ // Closing its input is "Enter was pressed": it says its final words and ends.
269
+ try {
270
+ child.stdin?.end();
271
+ }
272
+ catch { /* gone */ }
273
+ const timeout = new Promise((resolve) => setTimeout(resolve, 4000).unref());
274
+ await Promise.race([ended, timeout]);
275
+ abandon();
276
+ process.off('exit', abandon);
277
+ if (why && !final && !heard)
278
+ return null;
279
+ return (final ?? heard).trim();
280
+ },
281
+ };
282
+ }
283
+ /** Where the samples start in a WAV file this recorder wrote, or -1. */
284
+ function dataStart(buf) {
285
+ const at = buf.indexOf('data', 12, 'ascii');
286
+ return at < 0 ? -1 : at + 8;
287
+ }
288
+ /**
289
+ * How loud the last tenth of a second was, from 0 to 1.
290
+ *
291
+ * Read off the file as it is written, so somebody speaking sees the meter move
292
+ * and knows they are being heard, before a single word has come back. Sixteen
293
+ * bit mono at 16 kHz is 3,200 bytes a tenth of a second.
294
+ */
295
+ function level(file) {
296
+ try {
297
+ const size = fs.statSync(file).size;
298
+ const n = 3200;
299
+ if (size < 44 + n)
300
+ return 0;
301
+ const fd = fs.openSync(file, 'r');
302
+ const buf = Buffer.alloc(n);
303
+ fs.readSync(fd, buf, 0, n, size - n - (size - 44) % 2);
304
+ fs.closeSync(fd);
305
+ let sum = 0;
306
+ for (let i = 0; i + 1 < n; i += 2) {
307
+ const s = buf.readInt16LE(i) / 32768;
308
+ sum += s * s;
309
+ }
310
+ const rms = Math.sqrt(sum / (n / 2));
311
+ // Speech sits roughly between -50 dB (quiet room) and -10 dB (close and loud).
312
+ const db = 20 * Math.log10(rms || 1e-9);
313
+ return Math.max(0, Math.min(1, (db + 50) / 40));
314
+ }
315
+ catch {
316
+ return 0;
317
+ }
318
+ }
319
+ /**
320
+ * What has been said so far, as a WAV file a transcriber can read.
321
+ *
322
+ * The recorder writes the header's lengths only when it stops, so a copy taken
323
+ * mid recording claims no audio at all. The copy gets the lengths of what is
324
+ * actually there. Returns null until there is at least half a second to send.
325
+ */
326
+ function soFar(file) {
327
+ try {
328
+ const buf = fs.readFileSync(file);
329
+ const start = dataStart(buf);
330
+ if (start < 0 || buf.length - start < 16000)
331
+ return null;
332
+ const usable = buf.length - ((buf.length - start) % 2);
333
+ const copy = Buffer.from(buf.subarray(0, usable));
334
+ copy.writeUInt32LE(usable - 8, 4);
335
+ copy.writeUInt32LE(usable - start, start - 4);
336
+ const out = path.join(path.dirname(file), 'so-far.wav');
337
+ fs.writeFileSync(out, copy);
338
+ return out;
339
+ }
340
+ catch {
341
+ return null;
342
+ }
343
+ }
185
344
  /** Delete the recording and the folder holding it. Always, and immediately. */
186
345
  function discard(file) {
187
346
  try {
Binary file
@@ -1,5 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
3
6
  Object.defineProperty(exports, "__esModule", { value: true });
4
7
  /**
5
8
  * Runs on `npm install -g @illuminis/comprism`. Rule R1.
@@ -20,8 +23,26 @@ Object.defineProperty(exports, "__esModule", { value: true });
20
23
  * swallowing its own errors. A package that fails to install because its
21
24
  * telemetry could not configure itself deserves to be uninstalled.
22
25
  */
26
+ const node_fs_1 = __importDefault(require("node:fs"));
23
27
  const install_1 = require("./commands/install");
24
28
  const readiness_1 = require("./lib/readiness");
29
+ /**
30
+ * Written to the terminal itself, not to npm. npm hides everything an install
31
+ * script prints unless the install fails, so the next steps a person needs
32
+ * right now never reached the screen (owner, 27 September 2026). Falls back to
33
+ * standard output where there is no terminal, such as a build server's log.
34
+ */
35
+ function say(text) {
36
+ const tty = process.platform === 'win32' ? 'CONOUT$' : '/dev/tty';
37
+ try {
38
+ const fd = node_fs_1.default.openSync(tty, 'w');
39
+ node_fs_1.default.writeSync(fd, text);
40
+ node_fs_1.default.closeSync(fd);
41
+ }
42
+ catch {
43
+ process.stdout.write(text);
44
+ }
45
+ }
25
46
  async function main() {
26
47
  // Local development installs (`npm install` inside the repo) should not touch
27
48
  // the developer's shell profile. Only a global install means "set me up".
@@ -44,29 +65,33 @@ async function main() {
44
65
  if (report.desktop.staleRemoved) {
45
66
  desktopLines.push(' · removed an old CompletionPrism entry from Claude Desktop (it could not answer)');
46
67
  }
47
- if (report.desktop.note) {
68
+ // Only when Claude Desktop is on this machine: a line about an app somebody
69
+ // does not have is noise in a list of next steps.
70
+ if (report.desktop.note && report.desktop.present) {
48
71
  desktopLines.push(` · ${report.desktop.note}`);
49
72
  }
50
73
  else if (report.desktop.present && report.baseUrl) {
51
74
  desktopLines.push(` · Claude Desktop: add ${report.baseUrl}/mcp under Settings > Connectors`);
52
75
  }
53
- process.stdout.write([
76
+ // A short numbered list of what to do next, and nothing a person has to
77
+ // decode. The setup detail (which profile got a line) is one quiet line.
78
+ const signIn = !(report.enrolled && report.baseUrl);
79
+ const steps = [
80
+ 'Open a new terminal window.',
81
+ ...(signIn ? ['Sign in: comprism login'] : []),
82
+ 'Start working: comprism (or claude, already measured)',
83
+ ...(report.baseUrl ? [`See what it saved: ${report.baseUrl}`] : []),
84
+ ];
85
+ say([
54
86
  '',
55
- ' CompletionPrism is set up.',
56
- ` · interception configured against ${report.baseUrl || 'no tenant yet - run `comprism connect`'}`,
57
- ...report.profilesTouched.map((p) => ` · added one line to ${p}`),
58
- ...report.profilesAlreadyHad.map((p) => ` · already configured in ${p}`),
59
- ...desktopLines,
87
+ ' CompletionPrism is installed.',
60
88
  '',
61
- ' Open a new terminal and just work:',
62
- ' claude your tools, instrumented, unchanged',
63
- ' comprism our own session',
64
- ' comprism check what this machine still needs',
89
+ ' Next steps',
90
+ ...steps.map((line, i) => ` ${i + 1}. ${line}`),
91
+ ...desktopLines,
65
92
  '',
66
- ...(report.baseUrl
67
- ? [` What your work cost: ${report.baseUrl}`, '']
68
- : []),
69
- ' If our recorder is ever down your tools are unaffected. Undo with `comprism uninstall`.',
93
+ ...report.profilesTouched.map((p) => ` Set up in ${p}.`),
94
+ ' To remove it later, one command: comprism uninstall',
70
95
  '',
71
96
  ].join('\n'));
72
97
  // What this machine still needs, said at the only moment somebody is
@@ -76,7 +101,7 @@ async function main() {
76
101
  // already fine is noise on a screen somebody is about to close.
77
102
  const machine = (0, readiness_1.check)();
78
103
  if (!machine.ready) {
79
- process.stdout.write([
104
+ say([
80
105
  ' Two things the work itself may need on this machine:',
81
106
  ...machine.lines,
82
107
  '',
@@ -89,7 +114,7 @@ async function main() {
89
114
  }
90
115
  catch {
91
116
  // Never fail an install over setup. The user can run `comprism install`.
92
- process.stdout.write('\n CompletionPrism installed. Run `comprism install` to finish setup.\n\n');
117
+ say('\n CompletionPrism is installed. Next step: open a new terminal and run `comprism install`.\n\n');
93
118
  }
94
119
  }
95
120
  void main();
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Anthropic adapter.
3
+ *
4
+ * Deliberately dependency-free: the Messages API over `fetch`, nothing else.
5
+ * The Companion has to install into someone else's tool without dragging a
6
+ * dependency tree behind it, and a proxy that sits in front of a provider is a
7
+ * poor place to inherit somebody's transitive supply chain.
8
+ */
9
+ import type { Message, ProviderResult } from '../lib/types';
10
+ export interface AnthropicCall {
11
+ model: string;
12
+ messages: Message[];
13
+ maxTokens: number;
14
+ apiKey: string;
15
+ }
16
+ export declare function callAnthropic(call: AnthropicCall): Promise<ProviderResult>;
17
+ /** Carries the status code so the shared retry policy can read it. */
18
+ export declare class ProviderHttpError extends Error {
19
+ status: number;
20
+ latencyMs: number;
21
+ constructor(status: number, message: string);
22
+ }