@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,167 @@
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.cmdCost = cmdCost;
37
+ /**
38
+ * `comprism cost` — what a session cost, and what drove it.
39
+ *
40
+ * ── This file draws. It decides nothing ───────────────────────────────────
41
+ *
42
+ * Every word and every number below is written by `spend_report` on our
43
+ * server: which figures belong on the screen, what each one is called, what
44
+ * order they read in, and how each is formatted. This file receives that and
45
+ * puts it on a terminal.
46
+ *
47
+ * It was not always so. The first cut of this command held the list of cost
48
+ * factors, their labels and their formatting, which meant adding a factor or
49
+ * renaming one obliged every customer to install a new version of the tool to
50
+ * see a figure we had already begun measuring. A published package is the one
51
+ * thing in this product we cannot change, so as little as possible is decided
52
+ * inside it.
53
+ *
54
+ * What genuinely has to stay here is drawing: the server does not know the
55
+ * width of somebody's window or whether their terminal renders color, and
56
+ * finished text sent from a server breaks on a narrow window.
57
+ *
58
+ * A dash is how "nobody measured this" reaches the screen. The server sends
59
+ * null for it, never zero, because a zero in a cost column reads as a
60
+ * measurement.
61
+ */
62
+ const gateway_1 = require("../lib/gateway");
63
+ const connection_1 = require("../lib/connection");
64
+ const ui = __importStar(require("../lib/ui"));
65
+ const out = (s = '') => process.stdout.write(`${s}\n`);
66
+ /** A cell. Null and undefined are a figure nobody measured, so they print as a dash. */
67
+ const cell = (v) => v === null || v === undefined || v === '' ? ui.c.dim('—') : String(v);
68
+ async function cmdCost(args) {
69
+ const conn = (0, connection_1.readConnection)();
70
+ if (!conn?.url || !conn.workspaceKey) {
71
+ out();
72
+ out(ui.warn('This machine is not signed in, so there is no work to read.'));
73
+ out(` ${ui.c.dim('$')} comprism login`);
74
+ out();
75
+ return 1;
76
+ }
77
+ // The first bare word is a session, when one is given. No flag, because
78
+ // "which session" is the only thing this command takes.
79
+ const wanted = args.find((a) => a && !a.startsWith('-'));
80
+ const reply = await (0, gateway_1.ask)({
81
+ intent: 'spend',
82
+ prompt: '',
83
+ ...(wanted ? { spendSession: wanted } : {}),
84
+ });
85
+ // A workspace whose build does not carry this question says so, and that is a
86
+ // complete answer rather than an absence. Reading a refusal as "no spend"
87
+ // would tell somebody their work cost nothing.
88
+ if (reply.served === false) {
89
+ out();
90
+ out(ui.warn('This workspace is running a build that cannot answer this yet.'));
91
+ out(` ${ui.c.dim(reply.message || '')}`);
92
+ out();
93
+ return 1;
94
+ }
95
+ const report = reply.spend;
96
+ if (!report || !report.session) {
97
+ out();
98
+ out(ui.info(reply.message || 'No work has been recorded in this workspace yet.'));
99
+ out();
100
+ return 0;
101
+ }
102
+ const s = report.session;
103
+ // An older workspace answers without a presentation block. Saying so beats
104
+ // drawing an empty screen, and beats this file inventing the layout again.
105
+ const p = report.presentation;
106
+ if (!p) {
107
+ out();
108
+ out(ui.warn('This workspace answered without the breakdown this version draws.'));
109
+ out(` ${ui.c.dim('Deploy the workspace, or run `comprism status` to see which one you are on.')}`);
110
+ out();
111
+ return 1;
112
+ }
113
+ out();
114
+ out(ui.banner(String(p.heading || 'What this session cost')));
115
+ out();
116
+ out(` ${ui.c.bold(String(s.title || s.session_id))}`);
117
+ out(ui.c.dim(` ${String(s.started_at || '').slice(0, 16).replace('T', ' ')}`
118
+ + ` · ${String(s.turns ?? 0)} turn(s)`
119
+ + ` · ${String(s.surface || 'unknown surface')}`));
120
+ out();
121
+ const headline = (p.headline || []);
122
+ // One line per figure, label, value and the service's note in full, so a
123
+ // word like "estimated" is never cut to fit a box (manual 10.11). A figure
124
+ // nobody measured is blank.
125
+ if (headline.length > 0) {
126
+ for (const h of headline) {
127
+ const value = h.value === null || h.value === undefined ? 'blank' : String(h.value);
128
+ out(` ${String(h.label || '').padEnd(18)} ${ui.c.bold(value.padEnd(9))} ${ui.c.dim(h.note ? String(h.note) : '')}`);
129
+ }
130
+ out();
131
+ }
132
+ const factorRows = (p.factor_rows || []);
133
+ out(ui.c.muted(` ${String(p.factor_heading || '')}`));
134
+ out();
135
+ if (factorRows.length > 0) {
136
+ out(ui.table(p.factor_columns || ['factor', 'cost'], factorRows.map((r) => [String(r.label ?? ''), cell(r.value)])));
137
+ out();
138
+ if (p.driver) {
139
+ out(ui.c.dim(` ${String(p.driver)}`));
140
+ out();
141
+ }
142
+ }
143
+ else if (p.factor_empty) {
144
+ out(ui.c.dim(` ${String(p.factor_empty)}`));
145
+ out();
146
+ }
147
+ const callRows = (p.call_rows || []);
148
+ const callColumns = (p.call_columns || []);
149
+ if (callRows.length > 0 && callColumns.length > 0) {
150
+ out(ui.c.muted(` ${String(p.call_heading || '')}`));
151
+ out();
152
+ out(ui.table(callColumns,
153
+ // Read by the column names the server sent, so a column added there
154
+ // appears here without this file being told about it.
155
+ callRows.map((r) => callColumns.map((k) => cell(r[k])))));
156
+ out();
157
+ }
158
+ if (p.footnote) {
159
+ out(ui.c.dim(` ${String(p.footnote)}`));
160
+ out();
161
+ }
162
+ if (reply.details_url) {
163
+ out(ui.c.dim(` Every figure here traces to its records: ${reply.details_url}`));
164
+ out();
165
+ }
166
+ return 0;
167
+ }
@@ -0,0 +1 @@
1
+ export declare function cmdHooks(args: string[]): Promise<number>;
@@ -0,0 +1,83 @@
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.cmdHooks = cmdHooks;
37
+ /**
38
+ * `comprism hooks` and `comprism hooks test` (manual 10.10).
39
+ *
40
+ * This reads the project's settings files and sends them; the service says
41
+ * which hooks are in force here, where each came from, and for a test which
42
+ * ones run, in order. This then runs exactly those and shows what they did.
43
+ */
44
+ const ui = __importStar(require("../lib/ui"));
45
+ const project_ops_1 = require("../lib/project-ops");
46
+ const hooks_1 = require("../executor/hooks");
47
+ const out = (s = "") => process.stdout.write(`${s}\n`);
48
+ async function cmdHooks(args) {
49
+ const facts = await (0, project_ops_1.currentProject)();
50
+ const files = (0, hooks_1.hookFiles)(facts.root);
51
+ if (args[0] === "test") {
52
+ const [, when, action, target] = args;
53
+ if (!when) {
54
+ out(ui.fail("Say which moment: comprism hooks test <when> [action] [file], "
55
+ + "for example comprism hooks test after edit_file calc.py"));
56
+ return 2;
57
+ }
58
+ if ((0, hooks_1.insideHook)()) {
59
+ out(ui.fail("Hooks do not run from inside a hook, so nothing was run."));
60
+ return 1;
61
+ }
62
+ const reply = await (0, project_ops_1.call)("hooks", "test", { files, when, action: action ?? "" }, facts);
63
+ if (!reply)
64
+ return 1;
65
+ const chosen = (reply.data?.hooks) ?? [];
66
+ const done = await (0, hooks_1.runHooks)(facts.root, chosen, {
67
+ when, action, args: target ? { path: target } : {},
68
+ });
69
+ for (const line of (0, hooks_1.reportLines)(done))
70
+ out(` ${line}`);
71
+ if (done.refusedBy)
72
+ out(` ${done.detail}`);
73
+ out();
74
+ return done.refusedBy || done.failed.length ? 1 : 0;
75
+ }
76
+ if (args[0] && args[0] !== "list") {
77
+ out(ui.fail(`\`hooks ${args[0]}\` is not a command. Use comprism hooks, or comprism hooks test.`));
78
+ return 2;
79
+ }
80
+ const reply = await (0, project_ops_1.call)("hooks", "list", { files }, facts);
81
+ out();
82
+ return reply ? 0 : 1;
83
+ }
@@ -1,6 +1,17 @@
1
+ import { type InstallMethod } from '../lib/self';
1
2
  /** The header the tenant reads to decide whether this machine may be served. */
2
3
  export declare const WORKSPACE_KEY_HEADER = "x-comprism-key";
3
4
  export declare function envScriptPath(): string;
5
+ /** The PowerShell form of the setup script, for Windows (manual 1.2). */
6
+ export declare function envScriptPathPs(): string;
7
+ /**
8
+ * The PowerShell profiles to set up on Windows: Windows PowerShell's and,
9
+ * where it exists, PowerShell 7's. Windows PowerShell's is created when there
10
+ * is none, because it is the shell every Windows machine has.
11
+ */
12
+ export declare function powershellProfiles(): string[];
13
+ /** The PowerShell script: the same rule as the POSIX one, in PowerShell. */
14
+ export declare function envScriptPs(baseUrl: string | null, workspaceKey?: string | null): string;
4
15
  export declare function pidFilePath(): string;
5
16
  export declare function proxyLogPath(): string;
6
17
  /** Every profile we should add the line to, that actually exists. */
@@ -118,9 +129,41 @@ export declare function install(options?: {
118
129
  binary?: LaunchCommand;
119
130
  workspaceKey?: string | null;
120
131
  }): InstallReport;
121
- /** Take the line back out. An install you cannot cleanly undo is a liability. */
132
+ /**
133
+ * Rewrite the generated script from the connection as it now stands.
134
+ *
135
+ * Sign in and sign out call this. Before, only the install wrote it, and the
136
+ * install runs before anybody has signed in, so the script said "not enrolled"
137
+ * forever and signing in never switched measurement on. After sign out it kept
138
+ * presenting a revoked key. Touches no shell profile: a machine that never had
139
+ * the line added is left exactly as it was.
140
+ */
141
+ export declare function refreshEnvScript(): void;
142
+ /**
143
+ * What sign in calls: refresh the setup script, and on a Python install that
144
+ * was never set up, do the setup now (manual 1.1, Option 3).
145
+ *
146
+ * npm runs the setup as it installs and the install script runs it itself, but
147
+ * pip has no step of its own, so the first sign in is the first moment this
148
+ * tool runs on a machine that asked for it. Returns the profiles it changed,
149
+ * for the caller to say so. Every other install is left to `refreshEnvScript`.
150
+ */
151
+ export declare function finishSetup(): string[];
152
+ /** Does this profile carry our fenced line. */
153
+ export declare function profileHasSetup(profile: string): boolean;
154
+ /**
155
+ * Take the line back out. An install you cannot cleanly undo is a liability.
156
+ *
157
+ * Removes only what the install wrote: the fenced profile block, the generated
158
+ * `env.sh` and our own Claude Desktop entry. The sign in and settings stay, and
159
+ * the caller says so, because deleting a live credential file is not revoking it.
160
+ */
122
161
  export declare function uninstall(): {
123
162
  profilesCleaned: string[];
163
+ scriptRemoved: boolean;
164
+ programRemoved: boolean;
165
+ method: InstallMethod;
166
+ kept: string;
124
167
  };
125
168
  /**
126
169
  * The command that starts this package's CLI, resolved from the package itself.
@@ -5,6 +5,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.WORKSPACE_KEY_HEADER = void 0;
7
7
  exports.envScriptPath = envScriptPath;
8
+ exports.envScriptPathPs = envScriptPathPs;
9
+ exports.powershellProfiles = powershellProfiles;
10
+ exports.envScriptPs = envScriptPs;
8
11
  exports.pidFilePath = pidFilePath;
9
12
  exports.proxyLogPath = proxyLogPath;
10
13
  exports.shellProfiles = shellProfiles;
@@ -15,6 +18,9 @@ exports.installDesktopConnector = installDesktopConnector;
15
18
  exports.probeDesktopConnector = probeDesktopConnector;
16
19
  exports.uninstallDesktopConnector = uninstallDesktopConnector;
17
20
  exports.install = install;
21
+ exports.refreshEnvScript = refreshEnvScript;
22
+ exports.finishSetup = finishSetup;
23
+ exports.profileHasSetup = profileHasSetup;
18
24
  exports.uninstall = uninstall;
19
25
  exports.launchShellString = launchShellString;
20
26
  exports.proxyHealthy = proxyHealthy;
@@ -72,6 +78,7 @@ const node_path_1 = __importDefault(require("node:path"));
72
78
  const node_child_process_1 = require("node:child_process");
73
79
  const config_1 = require("../lib/config");
74
80
  const connection_1 = require("../lib/connection");
81
+ const self_1 = require("../lib/self");
75
82
  /** The header the tenant reads to decide whether this machine may be served. */
76
83
  exports.WORKSPACE_KEY_HEADER = 'x-comprism-key';
77
84
  const MARKER_START = '# >>> comprism >>>';
@@ -79,6 +86,53 @@ const MARKER_END = '# <<< comprism <<<';
79
86
  function envScriptPath() {
80
87
  return node_path_1.default.join((0, config_1.homeDir)(), 'env.sh');
81
88
  }
89
+ /** The PowerShell form of the setup script, for Windows (manual 1.2). */
90
+ function envScriptPathPs() {
91
+ return node_path_1.default.join((0, config_1.homeDir)(), 'env.ps1');
92
+ }
93
+ /**
94
+ * The PowerShell profiles to set up on Windows: Windows PowerShell's and,
95
+ * where it exists, PowerShell 7's. Windows PowerShell's is created when there
96
+ * is none, because it is the shell every Windows machine has.
97
+ */
98
+ function powershellProfiles() {
99
+ if (process.platform !== 'win32' && !process.env.COMPRISM_TEST_POWERSHELL)
100
+ return [];
101
+ const docs = node_path_1.default.join(process.env.USERPROFILE || node_os_1.default.homedir(), 'Documents');
102
+ const classic = node_path_1.default.join(docs, 'WindowsPowerShell', 'Microsoft.PowerShell_profile.ps1');
103
+ const modern = node_path_1.default.join(docs, 'PowerShell', 'Microsoft.PowerShell_profile.ps1');
104
+ const out = [classic];
105
+ if (node_fs_1.default.existsSync(node_path_1.default.dirname(modern)))
106
+ out.push(modern);
107
+ return out;
108
+ }
109
+ /** The PowerShell script: the same rule as the POSIX one, in PowerShell. */
110
+ function envScriptPs(baseUrl, workspaceKey) {
111
+ const bin = (0, self_1.standaloneBinDir)();
112
+ const pathLine = node_fs_1.default.existsSync(node_path_1.default.join(bin, 'comprism.exe'))
113
+ ? `if ($env:Path -notlike '*${bin.replace(/'/g, "''")}*') { $env:Path = '${bin.replace(/'/g, "''")};' + $env:Path }\n`
114
+ : '';
115
+ const head = '# Managed by comprism. Edit nothing here; run `comprism install` to regenerate.\n' + pathLine;
116
+ if (!baseUrl || !workspaceKey) {
117
+ return `${head}#\n# This machine is not signed in, so this sets nothing and your tools are untouched.\n`;
118
+ }
119
+ const q = (v) => v.replace(/'/g, "''");
120
+ return `${head}#
121
+ # Points AI clients at your CompletionPrism tenant so your work is measured.
122
+ # If the tenant is not reachable, this sets NOTHING and your tools talk
123
+ # straight to the provider exactly as they would without us.
124
+ try {
125
+ Invoke-WebRequest -Uri '${q(baseUrl)}/health' -UseBasicParsing -TimeoutSec 2 | Out-Null
126
+ $env:ANTHROPIC_BASE_URL = '${q(baseUrl)}'
127
+ $__comprism = '${exports.WORKSPACE_KEY_HEADER}: ${q(workspaceKey)}'
128
+ if (-not $env:ANTHROPIC_CUSTOM_HEADERS) { $env:ANTHROPIC_CUSTOM_HEADERS = $__comprism }
129
+ elseif ($env:ANTHROPIC_CUSTOM_HEADERS -notlike '*${exports.WORKSPACE_KEY_HEADER}*') {
130
+ $env:ANTHROPIC_CUSTOM_HEADERS = "$($env:ANTHROPIC_CUSTOM_HEADERS)\`n$__comprism"
131
+ }
132
+ Remove-Variable __comprism -ErrorAction SilentlyContinue
133
+ } catch { }
134
+ `;
135
+ }
82
136
  function pidFilePath() {
83
137
  return node_path_1.default.join((0, config_1.homeDir)(), 'proxy.pid');
84
138
  }
@@ -92,6 +146,11 @@ function shellProfiles() {
92
146
  const found = candidates
93
147
  .map((f) => node_path_1.default.join(home, f))
94
148
  .filter((p) => node_fs_1.default.existsSync(p));
149
+ // Windows shells read none of these, and the generated script is POSIX. An
150
+ // existing Git Bash profile is still set up; nothing is created, because a
151
+ // new .zshrc on Windows is litter that measures nothing.
152
+ if (process.platform === 'win32')
153
+ return found;
95
154
  // A machine with none of them: create the one matching the login shell rather
96
155
  // than silently doing nothing.
97
156
  if (found.length === 0) {
@@ -135,6 +194,21 @@ function tenantBaseUrl(explicit) {
135
194
  * the day there is an OpenAI-shaped endpoint to point at.
136
195
  */
137
196
  function envScript(baseUrl, workspaceKey) {
197
+ return envScriptBody(baseUrl, workspaceKey).replace(/^#!\/bin\/sh\n/, `#!/bin/sh\n${pathBlock()}`);
198
+ }
199
+ /**
200
+ * The install script puts the program in `~/.comprism/bin` (manual 1.1,
201
+ * Option 1), so the setup script puts that folder on the PATH, once. It is
202
+ * written only when the program is there, so an npm or Python install is
203
+ * unchanged.
204
+ */
205
+ function pathBlock() {
206
+ const bin = (0, self_1.standaloneBinDir)();
207
+ if (!node_fs_1.default.existsSync(node_path_1.default.join(bin, 'comprism')))
208
+ return '';
209
+ return `case ":$PATH:" in *":${bin}:"*) : ;; *) PATH="${bin}:$PATH"; export PATH ;; esac\n`;
210
+ }
211
+ function envScriptBody(baseUrl, workspaceKey) {
138
212
  if (!baseUrl) {
139
213
  return `#!/bin/sh
140
214
  # Managed by comprism. Edit nothing here; run \`comprism install\` to regenerate.
@@ -388,6 +462,27 @@ function install(options = {}) {
388
462
  const line = `${MARKER_START}\n[ -f "${scriptPath}" ] && . "${scriptPath}"\n${MARKER_END}`;
389
463
  const touched = [];
390
464
  const already = [];
465
+ // Windows: the same fenced line, in PowerShell's own profile (manual 1.2).
466
+ const psProfiles = powershellProfiles();
467
+ if (psProfiles.length) {
468
+ const ps = envScriptPathPs();
469
+ node_fs_1.default.writeFileSync(ps, envScriptPs(baseUrl, workspaceKey));
470
+ const psLine = `${MARKER_START}\nif (Test-Path '${ps.replace(/'/g, "''")}') { . '${ps.replace(/'/g, "''")}' }\n${MARKER_END}`;
471
+ for (const profile of psProfiles) {
472
+ node_fs_1.default.mkdirSync(node_path_1.default.dirname(profile), { recursive: true });
473
+ const contents = node_fs_1.default.existsSync(profile) ? node_fs_1.default.readFileSync(profile, 'utf8') : '';
474
+ if (contents.includes(MARKER_START)) {
475
+ const next = contents.replace(new RegExp(`${MARKER_START}[\\s\\S]*?${MARKER_END}`), psLine);
476
+ if (next !== contents)
477
+ node_fs_1.default.writeFileSync(profile, next);
478
+ already.push(profile);
479
+ }
480
+ else {
481
+ node_fs_1.default.appendFileSync(profile, `\n${psLine}\n`);
482
+ touched.push(profile);
483
+ }
484
+ }
485
+ }
391
486
  for (const profile of shellProfiles()) {
392
487
  let contents = '';
393
488
  try {
@@ -419,10 +514,60 @@ function install(options = {}) {
419
514
  desktop: installDesktopConnector(resolveDesktopBinary()),
420
515
  };
421
516
  }
422
- /** Take the line back out. An install you cannot cleanly undo is a liability. */
517
+ /**
518
+ * Rewrite the generated script from the connection as it now stands.
519
+ *
520
+ * Sign in and sign out call this. Before, only the install wrote it, and the
521
+ * install runs before anybody has signed in, so the script said "not enrolled"
522
+ * forever and signing in never switched measurement on. After sign out it kept
523
+ * presenting a revoked key. Touches no shell profile: a machine that never had
524
+ * the line added is left exactly as it was.
525
+ */
526
+ function refreshEnvScript() {
527
+ const script = envScriptPath();
528
+ if (!node_fs_1.default.existsSync(script))
529
+ return;
530
+ node_fs_1.default.writeFileSync(script, envScript(tenantBaseUrl(), (0, connection_1.readConnection)()?.workspaceKey ?? null), { mode: 0o700 });
531
+ const ps = envScriptPathPs();
532
+ if (node_fs_1.default.existsSync(ps))
533
+ node_fs_1.default.writeFileSync(ps, envScriptPs(tenantBaseUrl(), (0, connection_1.readConnection)()?.workspaceKey ?? null));
534
+ }
535
+ /**
536
+ * What sign in calls: refresh the setup script, and on a Python install that
537
+ * was never set up, do the setup now (manual 1.1, Option 3).
538
+ *
539
+ * npm runs the setup as it installs and the install script runs it itself, but
540
+ * pip has no step of its own, so the first sign in is the first moment this
541
+ * tool runs on a machine that asked for it. Returns the profiles it changed,
542
+ * for the caller to say so. Every other install is left to `refreshEnvScript`.
543
+ */
544
+ function finishSetup() {
545
+ if ((0, self_1.installMethod)() === 'python' && !node_fs_1.default.existsSync(envScriptPath()))
546
+ return install().profilesTouched;
547
+ refreshEnvScript();
548
+ return [];
549
+ }
550
+ /** Does this profile carry our fenced line. */
551
+ function profileHasSetup(profile) {
552
+ try {
553
+ return node_fs_1.default.readFileSync(profile, 'utf8').includes(MARKER_START);
554
+ }
555
+ catch {
556
+ return false;
557
+ }
558
+ }
559
+ /**
560
+ * Take the line back out. An install you cannot cleanly undo is a liability.
561
+ *
562
+ * Removes only what the install wrote: the fenced profile block, the generated
563
+ * `env.sh` and our own Claude Desktop entry. The sign in and settings stay, and
564
+ * the caller says so, because deleting a live credential file is not revoking it.
565
+ */
423
566
  function uninstall() {
567
+ // Read first: once the program's folder is gone it can no longer be told.
568
+ const method = (0, self_1.installMethod)();
424
569
  const cleaned = [];
425
- for (const profile of shellProfiles()) {
570
+ for (const profile of [...powershellProfiles(), ...shellProfiles()]) {
426
571
  let contents = '';
427
572
  try {
428
573
  contents = node_fs_1.default.readFileSync(profile, 'utf8');
@@ -439,7 +584,54 @@ function uninstall() {
439
584
  cleaned.push(profile);
440
585
  }
441
586
  uninstallDesktopConnector();
442
- return { profilesCleaned: cleaned };
587
+ const script = envScriptPath();
588
+ const scriptRemoved = node_fs_1.default.existsSync(script) || node_fs_1.default.existsSync(envScriptPathPs());
589
+ node_fs_1.default.rmSync(script, { force: true });
590
+ node_fs_1.default.rmSync(envScriptPathPs(), { force: true });
591
+ const programRemoved = method === 'script' ? removeStandaloneProgram() : false;
592
+ return { profilesCleaned: cleaned, scriptRemoved, programRemoved, method, kept: (0, config_1.homeDir)() };
593
+ }
594
+ /**
595
+ * The install script's program and its folder, and on Windows the PATH entry
596
+ * the installer added (manual 1.8). Only `~/.comprism/bin`: the sign in and
597
+ * settings beside it stay.
598
+ *
599
+ * A running program can be deleted on macOS and Linux. Windows refuses to
600
+ * delete it but allows it to be moved, so there the program is moved to the
601
+ * temporary folder and the folder removed at once; the moved copy is deleted a
602
+ * moment after this process exits. Everything is done before returning,
603
+ * rather than left to a background process: a background process can be
604
+ * stopped with its parent, and the first version, which worked that way, left
605
+ * the folder and the PATH entry behind on Windows Server.
606
+ */
607
+ function removeStandaloneProgram() {
608
+ const bin = (0, self_1.standaloneBinDir)();
609
+ if (process.platform !== 'win32') {
610
+ node_fs_1.default.rmSync(bin, { recursive: true, force: true });
611
+ return true;
612
+ }
613
+ const q = (v) => v.replace(/'/g, "''");
614
+ (0, node_child_process_1.spawnSync)('powershell.exe', ['-NoProfile', '-NonInteractive', '-Command',
615
+ `$b='${q(bin)}'; $p=[Environment]::GetEnvironmentVariable('Path','User'); `
616
+ + `if ($p) { [Environment]::SetEnvironmentVariable('Path', (($p -split ';') | Where-Object { $_ -and $_ -ne $b }) -join ';', 'User') }`,
617
+ ], { stdio: 'ignore', windowsHide: true });
618
+ const parked = node_path_1.default.join(node_os_1.default.tmpdir(), `comprism-removed-${process.pid}.exe`);
619
+ try {
620
+ node_fs_1.default.renameSync(node_path_1.default.join(bin, 'comprism.exe'), parked);
621
+ }
622
+ catch {
623
+ // Not there, or on another drive: the folder removal below says which.
624
+ }
625
+ try {
626
+ node_fs_1.default.rmSync(bin, { recursive: true, force: true });
627
+ }
628
+ catch {
629
+ return false;
630
+ }
631
+ (0, node_child_process_1.spawn)('cmd.exe', ['/d', '/c', `ping -n 3 127.0.0.1 >nul & del /f /q "${parked}"`], {
632
+ detached: true, stdio: 'ignore', windowsHide: true,
633
+ }).unref();
634
+ return true;
443
635
  }
444
636
  /** The shell form, quoted, for the profile script that must be a shell string. */
445
637
  function launchShellString(launch) {
@@ -486,6 +678,8 @@ function resolveBinary() {
486
678
  * genuinely better because it survives the package moving.
487
679
  */
488
680
  function resolveDesktopBinary() {
681
+ if ((0, self_1.isStandalone)())
682
+ return { command: process.execPath, args: [] };
489
683
  const compiled = node_path_1.default.join(__dirname, 'cli.js');
490
684
  if (!node_fs_1.default.existsSync(compiled))
491
685
  return resolveBinary();
@@ -514,7 +708,7 @@ async function startDaemon(port) {
514
708
  return true;
515
709
  (0, config_1.ensureHome)();
516
710
  const out = node_fs_1.default.openSync(proxyLogPath(), 'a');
517
- const child = (0, node_child_process_1.spawn)(process.execPath, [process.argv[1], 'proxy', '--port', String(port)], {
711
+ const child = (0, node_child_process_1.spawn)(...(0, self_1.selfLaunch)(['proxy', '--port', String(port)]), {
518
712
  detached: true,
519
713
  stdio: ['ignore', out, out],
520
714
  });
@@ -0,0 +1 @@
1
+ export declare function cmdInstructions(args: string[]): Promise<number>;