@illuminis/comprism 0.1.3 → 0.1.4
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 +220 -25
- package/out/agent/render.d.ts +17 -2
- package/out/agent/render.js +157 -14
- package/out/agent/session.d.ts +141 -2
- package/out/agent/session.js +735 -149
- 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 +78 -11
- 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 +188 -36
- 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 +8 -1
- package/out/commands/repl.js +1207 -118
- 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/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 +186 -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 +95 -0
- package/out/lib/keyboard.js +383 -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/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.js +15 -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/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 +2 -4
- package/out/lib/ui.js +31 -25
- package/out/lib/voice.js +24 -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 +594 -32
- package/package.json +9 -49
package/out/lib/stdin.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.STDIN_NAME = void 0;
|
|
37
|
+
exports.readPiped = readPiped;
|
|
38
|
+
exports.attachPiped = attachPiped;
|
|
39
|
+
exports.openTerminal = openTerminal;
|
|
40
|
+
/**
|
|
41
|
+
* Text piped in, and the keyboard behind the pipe (manual 3.9).
|
|
42
|
+
*
|
|
43
|
+
* `git diff | comprism work "write a commit message"` hands the tool two
|
|
44
|
+
* things through two doors: the diff on standard input and the person at the
|
|
45
|
+
* terminal. They are never confused. Standard input is read to its end and
|
|
46
|
+
* attached as a file called `standard input`; it is material to read, never
|
|
47
|
+
* an answer. Approval questions go to the terminal itself (`/dev/tty`, or the
|
|
48
|
+
* Windows console), so a `y` in the piped text can never approve anything.
|
|
49
|
+
* With no terminal at all, the job is unattended (manual 10.1).
|
|
50
|
+
*/
|
|
51
|
+
const fs = __importStar(require("node:fs"));
|
|
52
|
+
const os = __importStar(require("node:os"));
|
|
53
|
+
const path = __importStar(require("node:path"));
|
|
54
|
+
const tty = __importStar(require("node:tty"));
|
|
55
|
+
const attach = __importStar(require("./attach"));
|
|
56
|
+
/** The name piped text is attached under. */
|
|
57
|
+
exports.STDIN_NAME = 'standard input';
|
|
58
|
+
/** How long a pipe may stay silent before the person is told it is waited on. */
|
|
59
|
+
const QUIET_MS = 2000;
|
|
60
|
+
/**
|
|
61
|
+
* Everything piped in, read to the end. Null when standard input is a
|
|
62
|
+
* terminal, and empty when the pipe closed with nothing on it.
|
|
63
|
+
*
|
|
64
|
+
* A pipe that stays open with nothing coming (a command still running, or one
|
|
65
|
+
* that never closes) is announced after two seconds on standard error, so the
|
|
66
|
+
* person is not left looking at a still screen. Ctrl+C ends it as usual.
|
|
67
|
+
*/
|
|
68
|
+
function readPiped(input = process.stdin, say = (l) => { process.stderr.write(`${l}\n`); }) {
|
|
69
|
+
if (input.isTTY)
|
|
70
|
+
return Promise.resolve(null);
|
|
71
|
+
return new Promise((resolve) => {
|
|
72
|
+
const chunks = [];
|
|
73
|
+
const quiet = setTimeout(() => say(' waiting for standard input (Ctrl+C to stop)'), QUIET_MS);
|
|
74
|
+
input.on('data', (c) => {
|
|
75
|
+
chunks.push(typeof c === 'string' ? Buffer.from(c) : c);
|
|
76
|
+
});
|
|
77
|
+
const done = () => {
|
|
78
|
+
clearTimeout(quiet);
|
|
79
|
+
resolve(Buffer.concat(chunks).toString('utf8'));
|
|
80
|
+
};
|
|
81
|
+
input.on('end', done);
|
|
82
|
+
input.on('error', done);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* The piped text, uploaded as `standard input`. Written to a temporary file so
|
|
87
|
+
* it travels exactly like any attachment, and the file removed straight after.
|
|
88
|
+
*/
|
|
89
|
+
async function attachPiped(text, baseUrl, credential) {
|
|
90
|
+
const folder = fs.mkdtempSync(path.join(os.tmpdir(), 'comprism-stdin-'));
|
|
91
|
+
const file = path.join(folder, exports.STDIN_NAME);
|
|
92
|
+
try {
|
|
93
|
+
fs.writeFileSync(file, text);
|
|
94
|
+
return await attach.upload(baseUrl, credential, [file]);
|
|
95
|
+
}
|
|
96
|
+
finally {
|
|
97
|
+
fs.rmSync(folder, { recursive: true, force: true });
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* The terminal the person is sitting at, when standard input is a pipe.
|
|
102
|
+
* Null when there is none (a script, a scheduler, a container), which makes
|
|
103
|
+
* the job unattended.
|
|
104
|
+
*/
|
|
105
|
+
function openTerminal(platform = process.platform) {
|
|
106
|
+
try {
|
|
107
|
+
const fd = fs.openSync(platform === 'win32' ? 'CONIN$' : '/dev/tty', 'r');
|
|
108
|
+
if (!tty.isatty(fd)) {
|
|
109
|
+
fs.closeSync(fd);
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
return new tty.ReadStream(fd);
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export declare const LEDGER_FILE = "ledger.jsonl";
|
|
2
|
+
export declare const DECISIONS_FILE = "decisions.jsonl";
|
|
3
|
+
export declare const SESSIONS_FILE = "sessions.json";
|
|
4
|
+
/**
|
|
5
|
+
* Files that may never carry a supersede row and may never be resolved through
|
|
6
|
+
* `latestByKey`. Adding a file here is a promise that its rows are frozen.
|
|
7
|
+
*/
|
|
8
|
+
export declare const IMMUTABLE_FILES: ReadonlySet<string>;
|
|
9
|
+
export declare function storePath(file: string): string;
|
|
10
|
+
/**
|
|
11
|
+
* Append to a supersedable store. Refuses the immutable ones by name.
|
|
12
|
+
*/
|
|
13
|
+
export declare function appendRow(file: string, row: object): void;
|
|
14
|
+
/**
|
|
15
|
+
* Append to an immutable store. The only write path to `decisions.jsonl`.
|
|
16
|
+
*
|
|
17
|
+
* Refuses any row carrying `supersedes`. A decision record that supersedes
|
|
18
|
+
* another is not a correction, it is a second decision, and it belongs in the
|
|
19
|
+
* file as its own row with its own id.
|
|
20
|
+
*/
|
|
21
|
+
export declare function appendImmutable(file: string, row: object): void;
|
|
22
|
+
/**
|
|
23
|
+
* Read every parseable row.
|
|
24
|
+
*
|
|
25
|
+
* A truncated final line - the process died mid-write on a machine without the
|
|
26
|
+
* fsync guarantee, or the file was copied while being written - is skipped
|
|
27
|
+
* rather than thrown. One lost row is a gap in the evidence; an exception here
|
|
28
|
+
* would make every row after it unreadable, which is a far worse failure.
|
|
29
|
+
*/
|
|
30
|
+
export declare function readRows<T>(file: string): T[];
|
|
31
|
+
/**
|
|
32
|
+
* Resolve supersede chains: the last row wins per key.
|
|
33
|
+
*
|
|
34
|
+
* Takes the file name rather than just the rows, so that calling it on an
|
|
35
|
+
* immutable store is an error at the call site rather than a review comment
|
|
36
|
+
* somebody has to catch.
|
|
37
|
+
*/
|
|
38
|
+
export declare function latestByKey<T>(file: string, rows: T[], key: (row: T) => string): Map<string, T>;
|
|
39
|
+
/** Row count without parsing, for reports and the gate. */
|
|
40
|
+
export declare function countRows(file: string): number;
|
package/out/lib/store.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.IMMUTABLE_FILES = exports.SESSIONS_FILE = exports.DECISIONS_FILE = exports.LEDGER_FILE = void 0;
|
|
7
|
+
exports.storePath = storePath;
|
|
8
|
+
exports.appendRow = appendRow;
|
|
9
|
+
exports.appendImmutable = appendImmutable;
|
|
10
|
+
exports.readRows = readRows;
|
|
11
|
+
exports.latestByKey = latestByKey;
|
|
12
|
+
exports.countRows = countRows;
|
|
13
|
+
/**
|
|
14
|
+
* The append-only store.
|
|
15
|
+
*
|
|
16
|
+
* One JSON object per line. A row is written once and never edited. There is no
|
|
17
|
+
* update path, no delete path and no rewrite path in this file, and that is the
|
|
18
|
+
* point: the discipline is structural rather than a convention someone has to
|
|
19
|
+
* remember at three in the morning.
|
|
20
|
+
*
|
|
21
|
+
* Two grades of immutability, and the difference matters:
|
|
22
|
+
*
|
|
23
|
+
* - Ordinary stores (the ledger) are append-only with supersession. A
|
|
24
|
+
* correction is a NEW row carrying `supersedes`, and `latestByKey` resolves
|
|
25
|
+
* the chain at read time. Nothing is lost; the earlier belief stays on the
|
|
26
|
+
* record.
|
|
27
|
+
* - `decisions.jsonl` is stricter. It is never superseded, for any reason,
|
|
28
|
+
* because it means exactly one thing forever: what the system knew and
|
|
29
|
+
* chose at decision time. A supersede row would make it a claim about the
|
|
30
|
+
* present instead. The fences below make that unenforceable by accident:
|
|
31
|
+
* `appendRow` refuses the file, `appendImmutable` refuses a `supersedes`
|
|
32
|
+
* field, and `latestByKey` refuses to run against it at all.
|
|
33
|
+
*/
|
|
34
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
35
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
36
|
+
const config_1 = require("./config");
|
|
37
|
+
exports.LEDGER_FILE = 'ledger.jsonl';
|
|
38
|
+
exports.DECISIONS_FILE = 'decisions.jsonl';
|
|
39
|
+
exports.SESSIONS_FILE = 'sessions.json';
|
|
40
|
+
/**
|
|
41
|
+
* Files that may never carry a supersede row and may never be resolved through
|
|
42
|
+
* `latestByKey`. Adding a file here is a promise that its rows are frozen.
|
|
43
|
+
*/
|
|
44
|
+
exports.IMMUTABLE_FILES = new Set([exports.DECISIONS_FILE]);
|
|
45
|
+
function storePath(file) {
|
|
46
|
+
return node_path_1.default.join((0, config_1.homeDir)(), file);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The single write primitive. Everything that appends goes through here.
|
|
50
|
+
*
|
|
51
|
+
* `fsync` after every row is the reason a kill mid-session leaves a parseable
|
|
52
|
+
* file: the bytes are on the disk, not in a buffer that dies with the process.
|
|
53
|
+
* It costs a syscall per call, against a network round trip to a model, so the
|
|
54
|
+
* cost is not measurable and the guarantee is absolute.
|
|
55
|
+
*/
|
|
56
|
+
function writeLine(file, row) {
|
|
57
|
+
(0, config_1.ensureHome)();
|
|
58
|
+
const line = `${JSON.stringify(row)}\n`;
|
|
59
|
+
const fd = node_fs_1.default.openSync(storePath(file), 'a', 0o600);
|
|
60
|
+
try {
|
|
61
|
+
node_fs_1.default.writeSync(fd, line, null, 'utf8');
|
|
62
|
+
node_fs_1.default.fsyncSync(fd);
|
|
63
|
+
}
|
|
64
|
+
finally {
|
|
65
|
+
node_fs_1.default.closeSync(fd);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Append to a supersedable store. Refuses the immutable ones by name.
|
|
70
|
+
*/
|
|
71
|
+
function appendRow(file, row) {
|
|
72
|
+
if (exports.IMMUTABLE_FILES.has(file)) {
|
|
73
|
+
throw new Error(`${file} is immutable: append through appendImmutable, which forbids supersession.`);
|
|
74
|
+
}
|
|
75
|
+
writeLine(file, row);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Append to an immutable store. The only write path to `decisions.jsonl`.
|
|
79
|
+
*
|
|
80
|
+
* Refuses any row carrying `supersedes`. A decision record that supersedes
|
|
81
|
+
* another is not a correction, it is a second decision, and it belongs in the
|
|
82
|
+
* file as its own row with its own id.
|
|
83
|
+
*/
|
|
84
|
+
function appendImmutable(file, row) {
|
|
85
|
+
if (!exports.IMMUTABLE_FILES.has(file)) {
|
|
86
|
+
throw new Error(`${file} is not an immutable store: append through appendRow.`);
|
|
87
|
+
}
|
|
88
|
+
if (Object.prototype.hasOwnProperty.call(row, 'supersedes')) {
|
|
89
|
+
throw new Error(`${file} rows are never superseded. A later decision is a new record, not a correction.`);
|
|
90
|
+
}
|
|
91
|
+
writeLine(file, row);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Read every parseable row.
|
|
95
|
+
*
|
|
96
|
+
* A truncated final line - the process died mid-write on a machine without the
|
|
97
|
+
* fsync guarantee, or the file was copied while being written - is skipped
|
|
98
|
+
* rather than thrown. One lost row is a gap in the evidence; an exception here
|
|
99
|
+
* would make every row after it unreadable, which is a far worse failure.
|
|
100
|
+
*/
|
|
101
|
+
function readRows(file) {
|
|
102
|
+
const p = storePath(file);
|
|
103
|
+
if (!node_fs_1.default.existsSync(p))
|
|
104
|
+
return [];
|
|
105
|
+
const out = [];
|
|
106
|
+
for (const line of node_fs_1.default.readFileSync(p, 'utf8').split('\n')) {
|
|
107
|
+
const trimmed = line.trim();
|
|
108
|
+
if (!trimmed)
|
|
109
|
+
continue;
|
|
110
|
+
try {
|
|
111
|
+
out.push(JSON.parse(trimmed));
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return out;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Resolve supersede chains: the last row wins per key.
|
|
121
|
+
*
|
|
122
|
+
* Takes the file name rather than just the rows, so that calling it on an
|
|
123
|
+
* immutable store is an error at the call site rather than a review comment
|
|
124
|
+
* somebody has to catch.
|
|
125
|
+
*/
|
|
126
|
+
function latestByKey(file, rows, key) {
|
|
127
|
+
if (exports.IMMUTABLE_FILES.has(file)) {
|
|
128
|
+
throw new Error(`${file} is never superseded, so resolving it through latestByKey would imply it can be.`);
|
|
129
|
+
}
|
|
130
|
+
const map = new Map();
|
|
131
|
+
for (const row of rows)
|
|
132
|
+
map.set(key(row), row);
|
|
133
|
+
return map;
|
|
134
|
+
}
|
|
135
|
+
/** Row count without parsing, for reports and the gate. */
|
|
136
|
+
function countRows(file) {
|
|
137
|
+
return readRows(file).length;
|
|
138
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface SyncTarget {
|
|
2
|
+
/** Tenant base URL, e.g. https://acme.completionprism.ai */
|
|
3
|
+
url: string;
|
|
4
|
+
/** A tenant session token. Never written to any file by this command. */
|
|
5
|
+
token: string;
|
|
6
|
+
/** Only needed on localhost, where there is no subdomain to resolve. */
|
|
7
|
+
tenant?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface SyncOutcome {
|
|
10
|
+
accepted: Record<string, number>;
|
|
11
|
+
duplicates: Record<string, number>;
|
|
12
|
+
rejected: Record<string, string[]>;
|
|
13
|
+
reconciliation: Record<string, unknown>;
|
|
14
|
+
sent: Record<string, number>;
|
|
15
|
+
}
|
|
16
|
+
/** Everything the local stores hold, in the shape the tenant expects. */
|
|
17
|
+
export declare function collectBatch(): Record<string, unknown>;
|
|
18
|
+
export declare function sync(target: SyncTarget): Promise<SyncOutcome>;
|
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
|
@@ -79,12 +79,10 @@ export declare function table(headers: string[], rows: string[][]): string;
|
|
|
79
79
|
* out of the way, leaving the answer and a one-line receipt.
|
|
80
80
|
*/
|
|
81
81
|
export declare class LiveIndicator {
|
|
82
|
-
private
|
|
83
|
-
private frame;
|
|
84
|
-
private label;
|
|
85
|
-
private readonly frames;
|
|
82
|
+
private shown;
|
|
86
83
|
start(label: string): void;
|
|
87
84
|
update(label: string): void;
|
|
85
|
+
private draw;
|
|
88
86
|
stop(): void;
|
|
89
87
|
}
|
|
90
88
|
export interface ReceiptData {
|
package/out/lib/ui.js
CHANGED
|
@@ -35,15 +35,20 @@ exports.warn = warn;
|
|
|
35
35
|
exports.fail = fail;
|
|
36
36
|
exports.info = info;
|
|
37
37
|
const isTty = process.stdout.isTTY === true;
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
/** Decided when drawn, not when loaded, so `--plain`, `plain true` and
|
|
39
|
+
* `theme plain` applied at start up take effect (manual 6.10, 6.12). */
|
|
40
|
+
const noColor = () => !!process.env.NO_COLOR || process.env.TERM === 'dumb';
|
|
41
|
+
const useColorNow = () => isTty && !noColor();
|
|
42
|
+
/** Plain mode: no color and nothing redrawn; every update is a new line. */
|
|
43
|
+
const plainNow = () => process.env.COMPRISM_PLAIN === '1' || !isTty;
|
|
44
|
+
const light = () => process.env.COMPRISM_THEME === 'light';
|
|
40
45
|
function width() {
|
|
41
46
|
return Math.min(process.stdout.columns || 88, 100);
|
|
42
47
|
}
|
|
43
48
|
// ── palette ─────────────────────────────────────────────────────────────────
|
|
44
49
|
// illuminis brand, from branding/brand.toml. Deep navy is unreadable on a dark
|
|
45
50
|
// terminal, so the terminal palette leans on the two blues and the purple.
|
|
46
|
-
const rgb = (r, g, b) => (s) =>
|
|
51
|
+
const rgb = (r, g, b) => (s) => useColorNow() ? `\x1b[38;2;${r};${g};${b}m${s}\x1b[0m` : s;
|
|
47
52
|
exports.c = {
|
|
48
53
|
blue: rgb(59, 111, 181), // primary_blue #3B6FB5
|
|
49
54
|
deep: rgb(31, 77, 120), // heading_blue #1F4D78
|
|
@@ -52,9 +57,10 @@ exports.c = {
|
|
|
52
57
|
amber: rgb(217, 119, 6), // warning #D97706
|
|
53
58
|
red: rgb(220, 38, 38), // error #DC2626
|
|
54
59
|
muted: rgb(158, 158, 158), // muted_text #9E9E9E
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
60
|
+
// Light on a dark terminal, dark on a light one (manual 6.10).
|
|
61
|
+
text: (s) => (light() ? rgb(40, 44, 52)(s) : rgb(214, 219, 228)(s)),
|
|
62
|
+
bold: (s) => (useColorNow() ? `\x1b[1m${s}\x1b[0m` : s),
|
|
63
|
+
dim: (s) => (useColorNow() ? `\x1b[2m${s}\x1b[0m` : s),
|
|
58
64
|
};
|
|
59
65
|
/** The prism: white light entering, a spectrum leaving. The brand in one line. */
|
|
60
66
|
const SPECTRUM = [
|
|
@@ -75,13 +81,13 @@ const SPECTRUM = [
|
|
|
75
81
|
* and the brand is the one thing a customer notices before the engine.
|
|
76
82
|
*/
|
|
77
83
|
function spectrumAt(index, glyph) {
|
|
78
|
-
if (!
|
|
84
|
+
if (!useColorNow())
|
|
79
85
|
return glyph;
|
|
80
86
|
const [r, g, b] = SPECTRUM[Math.abs(index) % SPECTRUM.length];
|
|
81
87
|
return `\x1b[38;2;${r};${g};${b}m${glyph}\x1b[0m`;
|
|
82
88
|
}
|
|
83
89
|
function prismRule(w = width()) {
|
|
84
|
-
if (!
|
|
90
|
+
if (!useColorNow())
|
|
85
91
|
return '-'.repeat(w);
|
|
86
92
|
const seg = Math.max(1, Math.floor(w / SPECTRUM.length));
|
|
87
93
|
let out = '';
|
|
@@ -197,29 +203,29 @@ function table(headers, rows) {
|
|
|
197
203
|
* out of the way, leaving the answer and a one-line receipt.
|
|
198
204
|
*/
|
|
199
205
|
class LiveIndicator {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
206
|
+
shown = false;
|
|
207
|
+
// No animation of its own. A glyph turning on a timer looks like work
|
|
208
|
+
// whether or not anything is happening; the line changes only when the
|
|
209
|
+
// caller has something new to say, and in plain mode each change is a new
|
|
210
|
+
// line rather than a redraw (manual 6.12).
|
|
204
211
|
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);
|
|
212
|
+
this.draw(label);
|
|
213
213
|
}
|
|
214
214
|
update(label) {
|
|
215
|
-
this.label
|
|
215
|
+
this.draw(label);
|
|
216
|
+
}
|
|
217
|
+
draw(label) {
|
|
218
|
+
if (plainNow()) {
|
|
219
|
+
process.stdout.write(` ${label}\n`);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
process.stdout.write(`\r ${exports.c.purple('◠')} ${exports.c.dim(label)} `);
|
|
223
|
+
this.shown = true;
|
|
216
224
|
}
|
|
217
225
|
stop() {
|
|
218
|
-
if (this.
|
|
219
|
-
clearInterval(this.timer);
|
|
220
|
-
this.timer = null;
|
|
221
|
-
if (isTty)
|
|
226
|
+
if (this.shown && !plainNow())
|
|
222
227
|
process.stdout.write(`\r${' '.repeat(width())}\r`);
|
|
228
|
+
this.shown = false;
|
|
223
229
|
}
|
|
224
230
|
}
|
|
225
231
|
exports.LiveIndicator = LiveIndicator;
|
package/out/lib/voice.js
CHANGED
|
@@ -143,6 +143,30 @@ function start() {
|
|
|
143
143
|
stdio: ['ignore', 'ignore', 'ignore'],
|
|
144
144
|
});
|
|
145
145
|
let stopped = null;
|
|
146
|
+
// The recorder never outlives the session. Closing the window or ending the
|
|
147
|
+
// process while it listens would otherwise leave a microphone recording to a
|
|
148
|
+
// file nobody knows about, the worst thing this tool could do.
|
|
149
|
+
const abandon = () => {
|
|
150
|
+
try {
|
|
151
|
+
child.kill('SIGKILL');
|
|
152
|
+
}
|
|
153
|
+
catch { /* already gone */ }
|
|
154
|
+
discard(file);
|
|
155
|
+
};
|
|
156
|
+
const onSignal = (signal) => {
|
|
157
|
+
abandon();
|
|
158
|
+
release();
|
|
159
|
+
process.kill(process.pid, signal);
|
|
160
|
+
};
|
|
161
|
+
const release = () => {
|
|
162
|
+
process.off('exit', abandon);
|
|
163
|
+
process.off('SIGHUP', onSignal);
|
|
164
|
+
process.off('SIGTERM', onSignal);
|
|
165
|
+
};
|
|
166
|
+
process.once('exit', abandon);
|
|
167
|
+
process.once('SIGHUP', onSignal);
|
|
168
|
+
process.once('SIGTERM', onSignal);
|
|
169
|
+
child.once('close', release);
|
|
146
170
|
const stop = () => {
|
|
147
171
|
if (stopped)
|
|
148
172
|
return stopped;
|
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
|
+
}
|