@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.
- package/README.md +61 -2
- package/out/agent/command.d.ts +75 -4
- package/out/agent/command.js +221 -26
- package/out/agent/render.d.ts +56 -8
- package/out/agent/render.js +441 -39
- package/out/agent/session.d.ts +156 -2
- package/out/agent/session.js +795 -154
- package/out/commands/agents.d.ts +2 -0
- package/out/commands/agents.js +79 -0
- package/out/commands/ask.d.ts +1 -1
- package/out/commands/ask.js +88 -12
- package/out/commands/commands-thin.js +79 -17
- package/out/commands/config.d.ts +1 -0
- package/out/commands/config.js +138 -0
- package/out/commands/cost.d.ts +1 -0
- package/out/commands/cost.js +167 -0
- package/out/commands/hooks.d.ts +1 -0
- package/out/commands/hooks.js +83 -0
- package/out/commands/install.d.ts +44 -1
- package/out/commands/install.js +198 -4
- package/out/commands/instructions.d.ts +1 -0
- package/out/commands/instructions.js +113 -0
- package/out/commands/integrations.d.ts +3 -0
- package/out/commands/integrations.js +215 -0
- package/out/commands/jobs.d.ts +5 -0
- package/out/commands/jobs.js +157 -0
- package/out/commands/login.js +249 -43
- package/out/commands/memory.d.ts +3 -0
- package/out/commands/memory.js +113 -0
- package/out/commands/permissions.d.ts +1 -0
- package/out/commands/permissions.js +94 -0
- package/out/commands/plugins.d.ts +4 -0
- package/out/commands/plugins.js +192 -0
- package/out/commands/privacy.d.ts +1 -0
- package/out/commands/privacy.js +57 -0
- package/out/commands/providerKey.d.ts +32 -0
- package/out/commands/providerKey.js +108 -0
- package/out/commands/repl.d.ts +12 -1
- package/out/commands/repl.js +1649 -122
- package/out/commands/report.d.ts +39 -0
- package/out/commands/report.js +115 -0
- package/out/commands/review.d.ts +5 -0
- package/out/commands/review.js +223 -0
- package/out/commands/sessions.d.ts +23 -0
- package/out/commands/sessions.js +115 -0
- package/out/commands/settings.d.ts +3 -1
- package/out/commands/settings.js +18 -16
- package/out/commands/skills.d.ts +21 -0
- package/out/commands/skills.js +207 -0
- package/out/commands/unattended.d.ts +7 -0
- package/out/commands/unattended.js +351 -0
- package/out/commands/update.d.ts +1 -0
- package/out/commands/update.js +123 -0
- package/out/commands/welcome.d.ts +25 -1
- package/out/commands/welcome.js +71 -35
- package/out/commands/worktrees.d.ts +5 -0
- package/out/commands/worktrees.js +186 -0
- package/out/executor/browser.d.ts +14 -0
- package/out/executor/browser.js +270 -0
- package/out/executor/diagnostics.d.ts +2 -0
- package/out/executor/diagnostics.js +181 -0
- package/out/executor/files.js +270 -40
- package/out/executor/git.js +42 -29
- package/out/executor/hooks.d.ts +42 -58
- package/out/executor/hooks.js +89 -182
- package/out/executor/index.d.ts +21 -5
- package/out/executor/index.js +160 -14
- package/out/executor/paths.d.ts +6 -1
- package/out/executor/paths.js +34 -6
- package/out/executor/sandbox.d.ts +40 -0
- package/out/executor/sandbox.js +299 -0
- package/out/executor/shell.d.ts +49 -4
- package/out/executor/shell.js +302 -56
- package/out/executor/toolservers.d.ts +20 -0
- package/out/executor/toolservers.js +189 -0
- package/out/executor/worktree.d.ts +9 -0
- package/out/executor/worktree.js +119 -0
- package/out/graph/read-python.js +2 -1
- package/out/lib/attach.d.ts +56 -12
- package/out/lib/attach.js +230 -63
- package/out/lib/clipboard.d.ts +23 -0
- package/out/lib/clipboard.js +182 -0
- package/out/lib/commandlist.d.ts +20 -0
- package/out/lib/commandlist.js +58 -0
- package/out/lib/decision.d.ts +22 -0
- package/out/lib/decision.js +50 -0
- package/out/lib/fingerprint.d.ts +25 -0
- package/out/lib/fingerprint.js +58 -0
- package/out/lib/gateway.d.ts +190 -2
- package/out/lib/gateway.js +59 -4
- package/out/lib/history.d.ts +24 -0
- package/out/lib/history.js +137 -0
- package/out/lib/ide.d.ts +19 -0
- package/out/lib/ide.js +131 -0
- package/out/lib/keyboard.d.ts +98 -0
- package/out/lib/keyboard.js +390 -0
- package/out/lib/lastlogin.d.ts +14 -0
- package/out/lib/lastlogin.js +105 -0
- package/out/lib/machine.d.ts +21 -0
- package/out/lib/machine.js +91 -0
- package/out/lib/notify.d.ts +4 -0
- package/out/lib/notify.js +52 -0
- package/out/lib/output.d.ts +48 -0
- package/out/lib/output.js +108 -0
- package/out/lib/picker.d.ts +1 -0
- package/out/lib/picker.js +158 -0
- package/out/lib/project-ops.d.ts +19 -0
- package/out/lib/project-ops.js +146 -0
- package/out/lib/project.d.ts +28 -0
- package/out/lib/project.js +114 -0
- package/out/lib/prompt.d.ts +3 -0
- package/out/lib/prompt.js +36 -2
- package/out/lib/queue.d.ts +13 -0
- package/out/lib/queue.js +116 -0
- package/out/lib/readiness.d.ts +19 -0
- package/out/lib/readiness.js +170 -1
- package/out/lib/self.d.ts +23 -0
- package/out/lib/self.js +124 -0
- package/out/lib/sessions.d.ts +19 -0
- package/out/lib/sessions.js +221 -0
- package/out/lib/startcache.d.ts +13 -0
- package/out/lib/startcache.js +78 -0
- package/out/lib/stdin.d.ts +32 -0
- package/out/lib/stdin.js +117 -0
- package/out/lib/store.d.ts +40 -0
- package/out/lib/store.js +138 -0
- package/out/lib/sync.d.ts +18 -0
- package/out/lib/sync.js +81 -0
- package/out/lib/ui.d.ts +10 -4
- package/out/lib/ui.js +65 -25
- package/out/lib/voice.d.ts +36 -0
- package/out/lib/voice.js +160 -1
- package/out/native/dictate +0 -0
- package/out/postinstall.js +42 -17
- package/out/providers/anthropic.d.ts +22 -0
- package/out/providers/anthropic.js +80 -0
- package/out/providers/index.d.ts +8 -0
- package/out/providers/index.js +83 -0
- package/out/providers/openai.d.ts +11 -0
- package/out/providers/openai.js +57 -0
- package/out/thin.js +595 -33
- package/package.json +11 -50
package/out/lib/sync.js
ADDED
|
@@ -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
|
|
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
|
-
|
|
39
|
-
|
|
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) =>
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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 (!
|
|
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 (!
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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;
|
package/out/lib/voice.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
package/out/postinstall.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
'
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
' comprism check what this machine still needs',
|
|
89
|
+
' Next steps',
|
|
90
|
+
...steps.map((line, i) => ` ${i + 1}. ${line}`),
|
|
91
|
+
...desktopLines,
|
|
65
92
|
'',
|
|
66
|
-
...
|
|
67
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|