@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,221 @@
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.sessions = sessions;
37
+ exports.failure = failure;
38
+ exports.exitFor = exitFor;
39
+ exports.pick = pick;
40
+ exports.cmdSessions = cmdSessions;
41
+ /**
42
+ * A person's sessions, as the service keeps them (manual chapter 8).
43
+ *
44
+ * Nothing about a session is decided or stored here. The list, the numbers,
45
+ * the names, the costs, whether the conversation was kept and the words of
46
+ * every line come from the service's `sessions` intent; this file sends the
47
+ * question, draws the answer, and runs the arrow key list for `--resume`.
48
+ */
49
+ const ui = __importStar(require("./ui"));
50
+ const gateway_1 = require("./gateway");
51
+ const out = (s = '') => process.stdout.write(s + '\n');
52
+ /** Ask the service one sessions question, with where this machine is. */
53
+ async function sessions(op, extra = {}, folder) {
54
+ const { resolveProject, asWire } = await Promise.resolve().then(() => __importStar(require('./project')));
55
+ const { currentWorkspace } = await Promise.resolve().then(() => __importStar(require('../commands/settings')));
56
+ const facts = resolveProject(folder ?? currentWorkspace().path, []);
57
+ return (0, gateway_1.ask)({
58
+ intent: 'sessions',
59
+ prompt: '',
60
+ sessionsPayload: {
61
+ op,
62
+ project: asWire(facts),
63
+ // The service writes the times, in this machine's own clock.
64
+ tz_minutes: -new Date().getTimezoneOffset(),
65
+ ...extra,
66
+ },
67
+ });
68
+ }
69
+ /** Why a sessions question got no answer, in the service's words when it has them. */
70
+ function failure(reply) {
71
+ if (reply.served === false) {
72
+ return 'Your workspace is running a build without sessions yet.';
73
+ }
74
+ return reply.message || 'Your workspace could not be reached.';
75
+ }
76
+ /** The exit number for a sessions question that got no answer (Appendix B). */
77
+ function exitFor(reply) {
78
+ if (reply.message === 'this machine is not signed in')
79
+ return 3;
80
+ if (!reply.ok && reply.message === 'the service could not be reached')
81
+ return 6;
82
+ return 1;
83
+ }
84
+ /**
85
+ * Choose one line with the arrow keys; Enter picks, Esc or Ctrl+C leaves.
86
+ * Resolves with the index chosen, or null. Without a terminal it asks for a
87
+ * number instead, so a script is never left waiting on keys it cannot send.
88
+ */
89
+ async function pick(lines) {
90
+ if (lines.length === 0)
91
+ return null;
92
+ if (!process.stdin.isTTY) {
93
+ lines.forEach((l, i) => out(`${String(i + 1).padStart(3)}. ${l.trimStart()}`));
94
+ const { ask: askLine, closePrompt } = await Promise.resolve().then(() => __importStar(require('./prompt')));
95
+ const said = (await askLine(' which one? ')).trim();
96
+ closePrompt();
97
+ const n = Number(said);
98
+ return Number.isInteger(n) && n >= 1 && n <= lines.length ? n - 1 : null;
99
+ }
100
+ const stdin = process.stdin;
101
+ let at = 0;
102
+ const draw = (first) => {
103
+ if (!first)
104
+ process.stdout.write(`\x1b[${lines.length}A`);
105
+ lines.forEach((l, i) => {
106
+ const mark = i === at ? ui.c.bold('> ') : ' ';
107
+ process.stdout.write(`\x1b[2K${mark}${i === at ? ui.c.bold(l.trimStart()) : l.trimStart()}\n`);
108
+ });
109
+ };
110
+ out(ui.c.dim(' Up and Down to choose, Enter to resume, Esc to leave.'));
111
+ draw(true);
112
+ const wasRaw = stdin.isRaw;
113
+ stdin.setRawMode(true);
114
+ stdin.resume();
115
+ return new Promise((resolve) => {
116
+ const done = (chosen) => {
117
+ stdin.off('data', onKey);
118
+ stdin.setRawMode(Boolean(wasRaw));
119
+ stdin.pause();
120
+ resolve(chosen);
121
+ };
122
+ const onKey = (buf) => {
123
+ const key = buf.toString();
124
+ if (key === '\x1b[A' || key === 'k') {
125
+ at = (at + lines.length - 1) % lines.length;
126
+ draw(false);
127
+ }
128
+ else if (key === '\x1b[B' || key === 'j') {
129
+ at = (at + 1) % lines.length;
130
+ draw(false);
131
+ }
132
+ else if (key === '\r' || key === '\n')
133
+ done(at);
134
+ else if (key === '\x1b' || key === '\x03' || key === 'q')
135
+ done(null);
136
+ };
137
+ stdin.on('data', onKey);
138
+ });
139
+ }
140
+ /** Print what the service sent, or why it sent nothing. */
141
+ function answered(reply) {
142
+ out();
143
+ if (!reply.ok || !reply.sessions) {
144
+ out(ui.fail(failure(reply)));
145
+ out();
146
+ return exitFor(reply);
147
+ }
148
+ for (const line of reply.sessions.lines)
149
+ out(line);
150
+ out();
151
+ return 0;
152
+ }
153
+ const USAGE = [
154
+ 'comprism sessions [--search <words>] [--pr <number>]',
155
+ 'comprism sessions rename <number> "<name>"',
156
+ 'comprism sessions branch <number>',
157
+ 'comprism sessions export <number> [--format json]',
158
+ ];
159
+ /**
160
+ * `comprism sessions` and its words (manual 8.5 to 8.7, 8.10). Every answer,
161
+ * every line and every refusal is the service's; the export is written to
162
+ * standard output exactly as it came, so `> session.md` holds the file.
163
+ */
164
+ async function cmdSessions(args) {
165
+ const [word, key, ...rest] = args;
166
+ if (word === 'rename') {
167
+ const name = rest.join(' ').trim();
168
+ if (!key || !name) {
169
+ out(ui.fail(`Say which session and the new name: ${USAGE[1]}`));
170
+ return 2;
171
+ }
172
+ return answered(await sessions('rename', { key, name }));
173
+ }
174
+ if (word === 'branch') {
175
+ if (!key) {
176
+ out(ui.fail(`Say which session to branch: ${USAGE[2]}`));
177
+ return 2;
178
+ }
179
+ return answered(await sessions('branch', { key }));
180
+ }
181
+ if (word === 'export') {
182
+ const at = rest.indexOf('--format');
183
+ const format = at === -1 ? 'markdown' : rest[at + 1];
184
+ if (!key || (format !== 'markdown' && format !== 'json' && format !== 'md')) {
185
+ process.stderr.write(ui.fail(`Say which session, and json or markdown: ${USAGE[3]}`) + '\n');
186
+ return 2;
187
+ }
188
+ const reply = await sessions('export', { key, format: format === 'json' ? 'json' : 'markdown' });
189
+ const got = reply.sessions;
190
+ if (!reply.ok || !got) {
191
+ process.stderr.write(ui.fail(failure(reply)) + '\n');
192
+ return exitFor(reply);
193
+ }
194
+ process.stdout.write(got.format === 'json'
195
+ ? `${JSON.stringify(got.document, null, 2)}\n`
196
+ : `${String(got.text ?? '')}\n`);
197
+ return 0;
198
+ }
199
+ const prAt = args.indexOf('--pr');
200
+ if (prAt !== -1) {
201
+ const pr = args[prAt + 1];
202
+ if (!pr) {
203
+ out(ui.fail('Say which pull request: comprism sessions --pr 42'));
204
+ return 2;
205
+ }
206
+ return answered(await sessions('by_pr', { pr }));
207
+ }
208
+ const at = args.indexOf('--search');
209
+ const search = at === -1 ? '' : args.slice(at + 1).join(' ').trim();
210
+ if (at !== -1 && !search) {
211
+ out(ui.fail('Say what to search for: comprism sessions --search parser'));
212
+ return 2;
213
+ }
214
+ if (word && !word.startsWith('-')) {
215
+ out(ui.fail(`There is no sessions command called "${word}".`));
216
+ for (const u of USAGE)
217
+ out(` ${ui.c.dim(u)}`);
218
+ return 2;
219
+ }
220
+ return answered(await sessions('list', { search, limit: 20 }));
221
+ }
@@ -0,0 +1,13 @@
1
+ import type { SessionOut } from './gateway';
2
+ import type { CommandList } from './commandlist';
3
+ export interface StartCache {
4
+ /** Which workspace these answers came from; another workspace's are never used. */
5
+ url?: string;
6
+ me?: SessionOut | null;
7
+ hasKey?: boolean;
8
+ /** The project line, by project folder. */
9
+ lines?: Record<string, string>;
10
+ list?: CommandList | null;
11
+ }
12
+ export declare function readStart(url: string): StartCache;
13
+ export declare function saveStart(url: string, change: Partial<StartCache>): void;
@@ -0,0 +1,78 @@
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.readStart = readStart;
37
+ exports.saveStart = saveStart;
38
+ /**
39
+ * What the service said the last time a session opened, kept so the next one
40
+ * can open at once.
41
+ *
42
+ * Opening a session asks the service four things (who you are, whether a key
43
+ * is stored, the project line, the command list). Asked from a laptop whose
44
+ * database is a region away, each takes seconds, and the prompt waited for all
45
+ * of them. Now the last answers are drawn immediately and the same questions
46
+ * are asked again in the background; the fresh answers are kept for next time,
47
+ * and a change that matters (no key any more) is said as soon as it is known.
48
+ *
49
+ * Only the service's own answers are kept, exactly as sent. Nothing here is
50
+ * decided on this machine, and a missing or unreadable file simply means the
51
+ * session waits for the service, as it always did.
52
+ */
53
+ const fs = __importStar(require("node:fs"));
54
+ const path = __importStar(require("node:path"));
55
+ const config_1 = require("./config");
56
+ function file() {
57
+ return path.join((0, config_1.ensureHome)(), 'start-cache.json');
58
+ }
59
+ function readStart(url) {
60
+ try {
61
+ const got = JSON.parse(fs.readFileSync(file(), 'utf8'));
62
+ return got.url === url ? got : { url };
63
+ }
64
+ catch {
65
+ return { url };
66
+ }
67
+ }
68
+ function saveStart(url, change) {
69
+ try {
70
+ const now = readStart(url);
71
+ const next = {
72
+ ...now, ...change, url,
73
+ lines: { ...(now.lines ?? {}), ...(change.lines ?? {}) },
74
+ };
75
+ fs.writeFileSync(file(), JSON.stringify(next), { mode: 0o600 });
76
+ }
77
+ catch { /* a cache that cannot be written only means the next start waits */ }
78
+ }
@@ -0,0 +1,32 @@
1
+ import * as tty from 'node:tty';
2
+ import * as attach from './attach';
3
+ /** The name piped text is attached under. */
4
+ export declare const STDIN_NAME = "standard input";
5
+ /**
6
+ * Everything piped in, read to the end. Null when standard input is a
7
+ * terminal, and empty when the pipe closed with nothing on it.
8
+ *
9
+ * A pipe that stays open with nothing coming (a command still running, or one
10
+ * that never closes) is announced after two seconds on standard error, so the
11
+ * person is not left looking at a still screen. Ctrl+C ends it as usual.
12
+ */
13
+ export declare function readPiped(input?: NodeJS.ReadableStream & {
14
+ isTTY?: boolean;
15
+ }, say?: (line: string) => void): Promise<string | null>;
16
+ /**
17
+ * The piped text, uploaded as `standard input`. Written to a temporary file so
18
+ * it travels exactly like any attachment, and the file removed straight after.
19
+ */
20
+ export declare function attachPiped(text: string, baseUrl: string, credential: string): Promise<{
21
+ attached: attach.Attached[];
22
+ refused: {
23
+ file: string;
24
+ why: string;
25
+ }[];
26
+ }>;
27
+ /**
28
+ * The terminal the person is sitting at, when standard input is a pipe.
29
+ * Null when there is none (a script, a scheduler, a container), which makes
30
+ * the job unattended.
31
+ */
32
+ export declare function openTerminal(platform?: string): tty.ReadStream | null;
@@ -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;
@@ -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>;