@illuminis/comprism 0.1.4 → 0.1.6
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/out/agent/command.js +5 -5
- package/out/agent/render.d.ts +39 -6
- package/out/agent/render.js +287 -28
- package/out/agent/session.d.ts +21 -1
- package/out/agent/session.js +81 -17
- package/out/commands/agents.js +3 -3
- package/out/commands/ask.js +20 -11
- package/out/commands/codemap.d.ts +1 -1
- package/out/commands/codemap.js +3 -3
- package/out/commands/commands-thin.js +4 -4
- package/out/commands/config.js +1 -1
- package/out/commands/cost.js +4 -4
- package/out/commands/hooks.js +1 -1
- package/out/commands/install.d.ts +1 -1
- package/out/commands/install.js +3 -2
- package/out/commands/instructions.js +1 -1
- package/out/commands/integrations.js +4 -4
- package/out/commands/keys.js +6 -6
- package/out/commands/login.js +97 -27
- package/out/commands/permissions.js +2 -2
- package/out/commands/plugins.js +3 -3
- package/out/commands/privacy.js +2 -2
- package/out/commands/repl.d.ts +4 -0
- package/out/commands/repl.js +563 -101
- package/out/commands/review.js +6 -6
- package/out/commands/settings.js +30 -123
- package/out/commands/skills.js +2 -2
- package/out/commands/unattended.js +6 -6
- package/out/commands/update.js +1 -1
- package/out/commands/welcome.d.ts +25 -1
- package/out/commands/welcome.js +65 -38
- package/out/commands/worktrees.d.ts +1 -1
- package/out/graph/sync.d.ts +1 -1
- package/out/graph/sync.js +6 -6
- package/out/lib/attach.js +4 -3
- package/out/lib/commandlist.d.ts +1 -1
- package/out/lib/commandlist.js +2 -2
- package/out/lib/config.d.ts +3 -2
- package/out/lib/config.js +7 -8
- package/out/lib/connection.js +1 -1
- package/out/lib/gateway.d.ts +9 -5
- package/out/lib/keyboard.d.ts +3 -0
- package/out/lib/keyboard.js +7 -0
- package/out/lib/lastlogin.d.ts +14 -0
- package/out/lib/lastlogin.js +105 -0
- package/out/lib/machine.js +1 -1
- package/out/lib/picker.d.ts +1 -0
- package/out/lib/picker.js +158 -0
- package/out/lib/project-ops.d.ts +1 -1
- package/out/lib/project-ops.js +4 -4
- package/out/lib/prompt.d.ts +3 -0
- package/out/lib/prompt.js +21 -0
- package/out/lib/queue.js +1 -1
- package/out/lib/readiness.d.ts +3 -30
- package/out/lib/readiness.js +13 -82
- package/out/lib/servicecommand.d.ts +14 -0
- package/out/lib/servicecommand.js +74 -0
- package/out/lib/sessions.js +3 -3
- package/out/lib/startcache.d.ts +13 -0
- package/out/lib/startcache.js +78 -0
- package/out/lib/ui.d.ts +11 -0
- package/out/lib/ui.js +28 -0
- package/out/lib/voice.d.ts +36 -0
- package/out/lib/voice.js +137 -2
- package/out/lib/words.d.ts +20 -0
- package/out/lib/words.js +27 -0
- package/out/machine/codemap/build.d.ts +45 -0
- package/out/machine/codemap/build.js +91 -0
- package/out/machine/codemap/facts.d.ts +47 -0
- package/out/machine/codemap/facts.js +12 -0
- package/out/machine/codemap/files.d.ts +45 -0
- package/out/machine/codemap/files.js +207 -0
- package/out/machine/codemap/read-locales.d.ts +29 -0
- package/out/machine/codemap/read-locales.js +246 -0
- package/out/machine/codemap/read-python.d.ts +11 -0
- package/out/machine/codemap/read-python.js +116 -0
- package/out/machine/codemap/read-typescript.d.ts +16 -0
- package/out/machine/codemap/read-typescript.js +292 -0
- package/out/machine/executor/browser.d.ts +14 -0
- package/out/machine/executor/browser.js +270 -0
- package/out/machine/executor/diagnostics.d.ts +2 -0
- package/out/machine/executor/diagnostics.js +181 -0
- package/out/machine/executor/documents.d.ts +40 -0
- package/out/machine/executor/documents.js +170 -0
- package/out/machine/executor/files.d.ts +2 -0
- package/out/machine/executor/files.js +590 -0
- package/out/machine/executor/git.d.ts +48 -0
- package/out/machine/executor/git.js +145 -0
- package/out/machine/executor/hooks.d.ts +51 -0
- package/out/machine/executor/hooks.js +154 -0
- package/out/machine/executor/index.d.ts +45 -0
- package/out/machine/executor/index.js +367 -0
- package/out/machine/executor/notebook.d.ts +2 -0
- package/out/machine/executor/notebook.js +147 -0
- package/out/machine/executor/paths.d.ts +20 -0
- package/out/machine/executor/paths.js +154 -0
- package/out/machine/executor/sandbox.d.ts +40 -0
- package/out/machine/executor/sandbox.js +299 -0
- package/out/machine/executor/shell.d.ts +86 -0
- package/out/machine/executor/shell.js +582 -0
- package/out/machine/executor/toolservers.d.ts +20 -0
- package/out/machine/executor/toolservers.js +189 -0
- package/out/machine/executor/worktree.d.ts +9 -0
- package/out/machine/executor/worktree.js +119 -0
- package/out/machine/folder/project.d.ts +28 -0
- package/out/machine/folder/project.js +114 -0
- package/out/machine/runtime/home.d.ts +2 -0
- package/out/machine/runtime/home.js +48 -0
- package/out/machine/runtime/needs.d.ts +30 -0
- package/out/machine/runtime/needs.js +89 -0
- package/out/machine/runtime/self.d.ts +23 -0
- package/out/machine/runtime/self.js +124 -0
- package/out/native/dictate +0 -0
- package/out/providers/index.js +2 -1
- package/out/thin.js +13 -12
- package/package.json +5 -4
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ToolServers = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Tool servers on this machine (manual 9.10): started when a job needs one,
|
|
6
|
+
* spoken to over standard input and output, stopped when the job ends.
|
|
7
|
+
*
|
|
8
|
+
* A local helper and nothing more. Which servers exist, whether one may run
|
|
9
|
+
* in this project, whether a call is allowed and what a result means are all
|
|
10
|
+
* the service's. This starts the command the person approved, asks for the
|
|
11
|
+
* tool list, carries one call, and reports what came back or why nothing did.
|
|
12
|
+
*
|
|
13
|
+
* Only the two methods an agent needs: list the tools, call a tool.
|
|
14
|
+
*/
|
|
15
|
+
const child_process_1 = require("child_process");
|
|
16
|
+
/** How long one request may take before it is reported as not answered. */
|
|
17
|
+
const timeoutMs = () => Number(process.env.COMPRISM_TOOL_SERVER_TIMEOUT_MS || 30_000);
|
|
18
|
+
/** What one result may carry back. The service cuts it again. */
|
|
19
|
+
const MAX_RESULT_CHARS = 20_000;
|
|
20
|
+
class Server {
|
|
21
|
+
name;
|
|
22
|
+
command;
|
|
23
|
+
proc;
|
|
24
|
+
buffer = "";
|
|
25
|
+
nextId = 1;
|
|
26
|
+
pending = new Map();
|
|
27
|
+
gone = null;
|
|
28
|
+
ready;
|
|
29
|
+
constructor(name, command, cwd) {
|
|
30
|
+
this.name = name;
|
|
31
|
+
this.command = command;
|
|
32
|
+
// The command exactly as the person approved it, through their shell, the
|
|
33
|
+
// way they would type it themselves.
|
|
34
|
+
// nosemgrep: javascript.lang.security.detect-child-process.detect-child-process -- the tool server command exactly as the person approved it
|
|
35
|
+
this.proc = (0, child_process_1.spawn)(this.command, { cwd, shell: true, stdio: ["pipe", "pipe", "pipe"],
|
|
36
|
+
windowsHide: true });
|
|
37
|
+
this.proc.stdout.setEncoding("utf8");
|
|
38
|
+
this.proc.stdout.on("data", (chunk) => this.read(chunk));
|
|
39
|
+
this.proc.stderr.on("data", () => { });
|
|
40
|
+
this.proc.stdin.on("error", () => { });
|
|
41
|
+
this.proc.on("error", (e) => this.fail(`could not be started: ${e.message}`));
|
|
42
|
+
this.proc.on("exit", (code) => this.fail(`stopped${code === null ? "" : ` (exit ${code})`}`));
|
|
43
|
+
this.ready = this.request("initialize", {
|
|
44
|
+
protocolVersion: "2025-06-18", capabilities: {},
|
|
45
|
+
clientInfo: { name: "comprism", version: "1" },
|
|
46
|
+
}).then(() => { this.notify("notifications/initialized"); });
|
|
47
|
+
this.ready.catch(() => { });
|
|
48
|
+
}
|
|
49
|
+
fail(why) {
|
|
50
|
+
if (this.gone)
|
|
51
|
+
return;
|
|
52
|
+
this.gone = `The tool server ${this.name} ${why}.`;
|
|
53
|
+
for (const [, p] of this.pending) {
|
|
54
|
+
clearTimeout(p.timer);
|
|
55
|
+
p.reject(new Error(this.gone));
|
|
56
|
+
}
|
|
57
|
+
this.pending.clear();
|
|
58
|
+
}
|
|
59
|
+
read(chunk) {
|
|
60
|
+
this.buffer += chunk;
|
|
61
|
+
let at;
|
|
62
|
+
while ((at = this.buffer.indexOf("\n")) >= 0) {
|
|
63
|
+
const line = this.buffer.slice(0, at).trim();
|
|
64
|
+
this.buffer = this.buffer.slice(at + 1);
|
|
65
|
+
if (!line)
|
|
66
|
+
continue;
|
|
67
|
+
let msg;
|
|
68
|
+
try {
|
|
69
|
+
msg = JSON.parse(line);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
// A reply that is not a reply: every waiting request is told so.
|
|
73
|
+
for (const [id, p] of this.pending) {
|
|
74
|
+
clearTimeout(p.timer);
|
|
75
|
+
p.reject(new Error(`The tool server ${this.name} sent something that is not a reply.`));
|
|
76
|
+
this.pending.delete(id);
|
|
77
|
+
}
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
const id = typeof msg.id === "number" ? msg.id : Number(msg.id);
|
|
81
|
+
const waiting = this.pending.get(id);
|
|
82
|
+
if (!waiting)
|
|
83
|
+
continue;
|
|
84
|
+
clearTimeout(waiting.timer);
|
|
85
|
+
this.pending.delete(id);
|
|
86
|
+
if (msg.error) {
|
|
87
|
+
const e = msg.error;
|
|
88
|
+
waiting.reject(new Error(`The tool server ${this.name} refused: ${e.message ?? "an error"}.`));
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
waiting.resolve((msg.result ?? {}));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
notify(method) {
|
|
96
|
+
if (this.gone)
|
|
97
|
+
return;
|
|
98
|
+
this.proc.stdin.write(JSON.stringify({ jsonrpc: "2.0", method }) + "\n");
|
|
99
|
+
}
|
|
100
|
+
request(method, params) {
|
|
101
|
+
if (this.gone)
|
|
102
|
+
return Promise.reject(new Error(this.gone));
|
|
103
|
+
const id = this.nextId++;
|
|
104
|
+
return new Promise((resolve, reject) => {
|
|
105
|
+
const timer = setTimeout(() => {
|
|
106
|
+
this.pending.delete(id);
|
|
107
|
+
reject(new Error(`The tool server ${this.name} did not answer within ${Math.max(1, Math.round(timeoutMs() / 1000))} seconds.`));
|
|
108
|
+
}, timeoutMs());
|
|
109
|
+
this.pending.set(id, { resolve, reject, timer });
|
|
110
|
+
this.proc.stdin.write(JSON.stringify({ jsonrpc: "2.0", id, method, params }) + "\n");
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
stop() {
|
|
114
|
+
this.fail("was stopped");
|
|
115
|
+
try {
|
|
116
|
+
this.proc.stdin.end();
|
|
117
|
+
}
|
|
118
|
+
catch { /* already closed */ }
|
|
119
|
+
// On Windows the command runs under cmd.exe, and ending cmd.exe leaves
|
|
120
|
+
// the server itself running. The whole tree is ended instead, so nothing
|
|
121
|
+
// outlives the job (manual 9.10).
|
|
122
|
+
if (process.platform === "win32" && this.proc.pid && this.proc.exitCode === null) {
|
|
123
|
+
try {
|
|
124
|
+
(0, child_process_1.spawnSync)("taskkill", ["/pid", String(this.proc.pid), "/T", "/F"], { windowsHide: true, stdio: "ignore" });
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
catch { /* fall through to the ordinary stop */ }
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
this.proc.kill();
|
|
131
|
+
}
|
|
132
|
+
catch { /* already gone */ }
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/** The tool servers one job started, by name. */
|
|
136
|
+
class ToolServers {
|
|
137
|
+
cwd;
|
|
138
|
+
running = new Map();
|
|
139
|
+
constructor(cwd) {
|
|
140
|
+
this.cwd = cwd;
|
|
141
|
+
}
|
|
142
|
+
async server(name, command) {
|
|
143
|
+
let s = this.running.get(name);
|
|
144
|
+
if (!s || s.command !== command) {
|
|
145
|
+
s?.stop();
|
|
146
|
+
s = new Server(name, command, this.cwd);
|
|
147
|
+
this.running.set(name, s);
|
|
148
|
+
}
|
|
149
|
+
await s.ready;
|
|
150
|
+
return s;
|
|
151
|
+
}
|
|
152
|
+
/** The server's tools, as it described them, as JSON text. */
|
|
153
|
+
async list(name, command) {
|
|
154
|
+
try {
|
|
155
|
+
const s = await this.server(name, command);
|
|
156
|
+
const r = await s.request("tools/list", {});
|
|
157
|
+
if (!Array.isArray(r.tools)) {
|
|
158
|
+
return { isError: true, content: `The tool server ${name} sent something that is not a tool list.` };
|
|
159
|
+
}
|
|
160
|
+
return { isError: false, content: JSON.stringify(r.tools) };
|
|
161
|
+
}
|
|
162
|
+
catch (e) {
|
|
163
|
+
this.running.get(name)?.stop();
|
|
164
|
+
this.running.delete(name);
|
|
165
|
+
return { isError: true, content: e.message };
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/** One call, and the text the server returned. */
|
|
169
|
+
async call(name, command, tool, args) {
|
|
170
|
+
try {
|
|
171
|
+
const s = await this.server(name, command);
|
|
172
|
+
const r = await s.request("tools/call", { name: tool, arguments: args ?? {} });
|
|
173
|
+
const blocks = Array.isArray(r.content) ? r.content : [];
|
|
174
|
+
const text = blocks.filter((b) => b.type === "text").map((b) => String(b.text ?? "")).join("\n").trim();
|
|
175
|
+
return { isError: r.isError === true, content: (text || "(the tool returned nothing)").slice(0, MAX_RESULT_CHARS) };
|
|
176
|
+
}
|
|
177
|
+
catch (e) {
|
|
178
|
+
return { isError: true, content: e.message };
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/** Every server this job started, stopped. Called when the job ends. */
|
|
182
|
+
stopAll() {
|
|
183
|
+
for (const [, s] of this.running)
|
|
184
|
+
s.stop();
|
|
185
|
+
this.running.clear();
|
|
186
|
+
}
|
|
187
|
+
get count() { return this.running.size; }
|
|
188
|
+
}
|
|
189
|
+
exports.ToolServers = ToolServers;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { RunResult, ShellOptions } from "./shell";
|
|
2
|
+
/** Where a copy is, by its id, or undefined. */
|
|
3
|
+
export declare function worktreePath(id: string): string | undefined;
|
|
4
|
+
export declare function openWorktree(id: string, opts: ShellOptions): Promise<RunResult>;
|
|
5
|
+
/** What the helper changed in its copy, as one diff. */
|
|
6
|
+
export declare function diffWorktree(id: string, opts: ShellOptions): Promise<RunResult>;
|
|
7
|
+
export declare function closeWorktree(id: string, opts: ShellOptions): Promise<RunResult>;
|
|
8
|
+
/** Every copy this process made, removed; for the end of a session. */
|
|
9
|
+
export declare function closeAllWorktrees(root: string): Promise<void>;
|
|
@@ -0,0 +1,119 @@
|
|
|
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.worktreePath = worktreePath;
|
|
37
|
+
exports.openWorktree = openWorktree;
|
|
38
|
+
exports.diffWorktree = diffWorktree;
|
|
39
|
+
exports.closeWorktree = closeWorktree;
|
|
40
|
+
exports.closeAllWorktrees = closeAllWorktrees;
|
|
41
|
+
/**
|
|
42
|
+
* A helper's own copy of the project (manual 5.26).
|
|
43
|
+
*
|
|
44
|
+
* A git worktree in the temporary folder, started from the project as it is
|
|
45
|
+
* now: the last commit plus the person's uncommitted changes to tracked files,
|
|
46
|
+
* committed inside the copy as its starting point. The helper's edits land
|
|
47
|
+
* there and nowhere else; what comes back is a diff against that starting
|
|
48
|
+
* point, which the service applies to the real folder with the usual approval.
|
|
49
|
+
* Nothing here decides anything: it makes the copy, reads its diff, and
|
|
50
|
+
* removes it.
|
|
51
|
+
*/
|
|
52
|
+
const fs = __importStar(require("fs"));
|
|
53
|
+
const os = __importStar(require("os"));
|
|
54
|
+
const path = __importStar(require("path"));
|
|
55
|
+
const shell_1 = require("./shell");
|
|
56
|
+
const copies = new Map();
|
|
57
|
+
const git = (args, opts) => (0, shell_1.runProgram)("git", args, opts);
|
|
58
|
+
const okExit = (r) => !r.isError && /^exit 0/.test(r.content);
|
|
59
|
+
const body = (r) => r.content.replace(/^exit -?\d+\n\n/, "");
|
|
60
|
+
/** Where a copy is, by its id, or undefined. */
|
|
61
|
+
function worktreePath(id) {
|
|
62
|
+
return copies.get(id);
|
|
63
|
+
}
|
|
64
|
+
async function openWorktree(id, opts) {
|
|
65
|
+
if (!/^[\w-]{1,40}$/.test(id))
|
|
66
|
+
return { isError: true, content: "Refused: not a usable copy name." };
|
|
67
|
+
if (copies.has(id))
|
|
68
|
+
return { content: copies.get(id), summary: `copy ${id}` };
|
|
69
|
+
const where = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), `comprism-helper-${id}-`));
|
|
70
|
+
fs.rmdirSync(where);
|
|
71
|
+
const made = await git(["worktree", "add", "--detach", "-q", where, "HEAD"], opts);
|
|
72
|
+
if (!okExit(made))
|
|
73
|
+
return { isError: true, content: `A separate copy could not be made: ${body(made)}` };
|
|
74
|
+
const inCopy = { ...opts, root: where, cwd: undefined };
|
|
75
|
+
// The person's uncommitted changes, so the helper starts from what they see.
|
|
76
|
+
const pending = await git(["diff", "--binary", "HEAD"], opts);
|
|
77
|
+
const patch = body(pending);
|
|
78
|
+
if (okExit(pending) && patch.trim() && patch.trim() !== "(no output)") {
|
|
79
|
+
const file = path.join(where, ".comprism-start.patch");
|
|
80
|
+
fs.writeFileSync(file, patch.endsWith("\n") ? patch : `${patch}\n`);
|
|
81
|
+
await git(["apply", "--whitespace=nowarn", file], inCopy);
|
|
82
|
+
fs.unlinkSync(file);
|
|
83
|
+
}
|
|
84
|
+
await git(["add", "-A"], inCopy);
|
|
85
|
+
await git(["-c", "user.name=comprism", "-c", "user.email=helper@illuminis.ai",
|
|
86
|
+
"commit", "-q", "--allow-empty", "--no-verify", "-m", "start"], inCopy);
|
|
87
|
+
copies.set(id, fs.realpathSync(where));
|
|
88
|
+
return { content: copies.get(id), summary: `copy ${id}` };
|
|
89
|
+
}
|
|
90
|
+
/** What the helper changed in its copy, as one diff. */
|
|
91
|
+
async function diffWorktree(id, opts) {
|
|
92
|
+
const where = copies.get(id);
|
|
93
|
+
if (!where)
|
|
94
|
+
return { isError: true, content: `There is no copy called ${id}.` };
|
|
95
|
+
const inCopy = { ...opts, root: where, cwd: undefined };
|
|
96
|
+
await git(["add", "-A"], inCopy);
|
|
97
|
+
const d = await git(["--no-pager", "diff", "--cached", "--binary", "--no-color", "HEAD"], inCopy);
|
|
98
|
+
if (!okExit(d))
|
|
99
|
+
return { isError: true, content: body(d) };
|
|
100
|
+
const text = body(d).trim() === "(no output)" ? "" : body(d);
|
|
101
|
+
return { content: text, summary: text ? "changes" : "no changes", facts: { changed: text ? 1 : 0 } };
|
|
102
|
+
}
|
|
103
|
+
async function closeWorktree(id, opts) {
|
|
104
|
+
const where = copies.get(id);
|
|
105
|
+
if (!where)
|
|
106
|
+
return { content: `${id} is already gone.` };
|
|
107
|
+
await git(["worktree", "remove", "--force", where], opts);
|
|
108
|
+
try {
|
|
109
|
+
fs.rmSync(where, { recursive: true, force: true });
|
|
110
|
+
}
|
|
111
|
+
catch { /* removed above */ }
|
|
112
|
+
copies.delete(id);
|
|
113
|
+
return { content: `Removed ${id}.` };
|
|
114
|
+
}
|
|
115
|
+
/** Every copy this process made, removed; for the end of a session. */
|
|
116
|
+
async function closeAllWorktrees(root) {
|
|
117
|
+
for (const id of [...copies.keys()])
|
|
118
|
+
await closeWorktree(id, { root });
|
|
119
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface ProjectFacts {
|
|
2
|
+
/** The repository root, or the folder itself outside a repository. Real path. */
|
|
3
|
+
root: string;
|
|
4
|
+
/** Where the person was standing, relative to `root`. Empty at the root. */
|
|
5
|
+
start: string;
|
|
6
|
+
/** Null outside a repository, or when HEAD is not on a branch. */
|
|
7
|
+
branch: string | null;
|
|
8
|
+
/** Folders the person named, real paths, never the root itself. */
|
|
9
|
+
added: string[];
|
|
10
|
+
home: string;
|
|
11
|
+
/** `owner/name` of the GitHub repository `origin` points at, or null. The
|
|
12
|
+
* service needs it for pull requests, issues and CI (manual 5.18 to 5.21). */
|
|
13
|
+
repository?: string | null;
|
|
14
|
+
}
|
|
15
|
+
/** `owner/name` from a GitHub remote address, in any of its spellings. */
|
|
16
|
+
export declare function githubRepository(remote: string | null): string | null;
|
|
17
|
+
/** A folder the person named, or the reason it cannot be used. */
|
|
18
|
+
export declare function checkFolder(raw: string): {
|
|
19
|
+
ok: true;
|
|
20
|
+
path: string;
|
|
21
|
+
} | {
|
|
22
|
+
ok: false;
|
|
23
|
+
why: string;
|
|
24
|
+
};
|
|
25
|
+
/** The project for a job started in `startDir`, with any added folders. */
|
|
26
|
+
export declare function resolveProject(startDir: string, added?: string[]): ProjectFacts;
|
|
27
|
+
/** The facts as the service reads them, on the job and on the session line. */
|
|
28
|
+
export declare function asWire(p: ProjectFacts, mode?: string): Record<string, unknown>;
|
|
@@ -0,0 +1,114 @@
|
|
|
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.githubRepository = githubRepository;
|
|
37
|
+
exports.checkFolder = checkFolder;
|
|
38
|
+
exports.resolveProject = resolveProject;
|
|
39
|
+
exports.asWire = asWire;
|
|
40
|
+
/**
|
|
41
|
+
* Where a job works, as this machine sees it (manual 2.1 to 2.3).
|
|
42
|
+
*
|
|
43
|
+
* A local helper and nothing more: it observes the folder, the Git repository
|
|
44
|
+
* around it and the branch, because only this machine can. The service decides
|
|
45
|
+
* what those facts mean, stores them on the job and writes the line a person
|
|
46
|
+
* reads. Nothing here decides whether a path may be touched; the executor's
|
|
47
|
+
* check in `executor/paths.ts` does that immediately before each action.
|
|
48
|
+
*/
|
|
49
|
+
const node_child_process_1 = require("node:child_process");
|
|
50
|
+
const fs = __importStar(require("node:fs"));
|
|
51
|
+
const os = __importStar(require("node:os"));
|
|
52
|
+
const path = __importStar(require("node:path"));
|
|
53
|
+
/** `owner/name` from a GitHub remote address, in any of its spellings. */
|
|
54
|
+
function githubRepository(remote) {
|
|
55
|
+
// `github.com`, or an SSH alias such as `github-work` set up for a second account.
|
|
56
|
+
const m = /(?:^|[@/])github[\w.-]*[:/]([\w.-]+)\/([\w.-]+?)(?:\.git)?\/?$/.exec((remote ?? "").trim());
|
|
57
|
+
return m ? `${m[1]}/${m[2]}` : null;
|
|
58
|
+
}
|
|
59
|
+
/** A folder the person named, or the reason it cannot be used. */
|
|
60
|
+
function checkFolder(raw) {
|
|
61
|
+
const expanded = raw.replace(/^~(?=$|[\\/])/, os.homedir());
|
|
62
|
+
const resolved = path.resolve(expanded);
|
|
63
|
+
if (!fs.existsSync(resolved))
|
|
64
|
+
return { ok: false, why: `There is no folder at ${resolved}.` };
|
|
65
|
+
if (!fs.statSync(resolved).isDirectory())
|
|
66
|
+
return { ok: false, why: `${resolved} is a file, not a folder.` };
|
|
67
|
+
return { ok: true, path: fs.realpathSync(resolved) };
|
|
68
|
+
}
|
|
69
|
+
/** Git's output, or null when git failed or this is not a repository. */
|
|
70
|
+
function git(args, cwd) {
|
|
71
|
+
try {
|
|
72
|
+
// An argument list, never a shell string, so it behaves the same on Windows.
|
|
73
|
+
return (0, node_child_process_1.execFileSync)("git", args, {
|
|
74
|
+
cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 5000,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/** The project for a job started in `startDir`, with any added folders. */
|
|
82
|
+
function resolveProject(startDir, added = []) {
|
|
83
|
+
const start = fs.realpathSync(startDir);
|
|
84
|
+
// The way up to the repository root and the way back down, relative, rather
|
|
85
|
+
// than the root's absolute name. Git spells an absolute path its own way (on
|
|
86
|
+
// Windows, long names where the temp folder has short ones), and comparing two
|
|
87
|
+
// spellings of one folder reads as two folders.
|
|
88
|
+
const where = git(["rev-parse", "--show-cdup", "--show-prefix"], start);
|
|
89
|
+
const [up = "", down = ""] = where === null ? [] : where.split(/\r?\n/);
|
|
90
|
+
const root = where === null ? start : fs.realpathSync(path.resolve(start, up || "."));
|
|
91
|
+
const branch = where === null
|
|
92
|
+
? null : (git(["symbolic-ref", "--short", "-q", "HEAD"], root) ?? "").trim() || null;
|
|
93
|
+
const extra = [...new Set(added.map((a) => fs.realpathSync(a)))].filter((a) => a !== root);
|
|
94
|
+
return {
|
|
95
|
+
root,
|
|
96
|
+
start: down.replace(/\/+$/, ""),
|
|
97
|
+
branch,
|
|
98
|
+
added: extra,
|
|
99
|
+
home: os.homedir(),
|
|
100
|
+
repository: where === null ? null : githubRepository(git(["remote", "get-url", "origin"], root)),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/** The facts as the service reads them, on the job and on the session line. */
|
|
104
|
+
function asWire(p, mode) {
|
|
105
|
+
return {
|
|
106
|
+
project_root: p.root,
|
|
107
|
+
start_folder: p.start,
|
|
108
|
+
branch: p.branch,
|
|
109
|
+
added_folders: p.added,
|
|
110
|
+
home: p.home,
|
|
111
|
+
...(p.repository ? { repository: p.repository } : {}),
|
|
112
|
+
...(mode ? { permission_mode: mode } : {}),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
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.HOME_DIR_NAME = void 0;
|
|
37
|
+
exports.homeDir = homeDir;
|
|
38
|
+
/**
|
|
39
|
+
* The tool's own folder on this computer, where it keeps its settings, its
|
|
40
|
+
* copies of shipped files and its programs. One answer for every client that
|
|
41
|
+
* carries the machine services.
|
|
42
|
+
*/
|
|
43
|
+
const os = __importStar(require("os"));
|
|
44
|
+
const path = __importStar(require("path"));
|
|
45
|
+
exports.HOME_DIR_NAME = ".comprism";
|
|
46
|
+
function homeDir() {
|
|
47
|
+
return process.env.COMPRISM_HOME || path.join(os.homedir(), exports.HOME_DIR_NAME);
|
|
48
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface Requirement {
|
|
2
|
+
/** What it is for, said as the work it makes possible. */
|
|
3
|
+
purpose: string;
|
|
4
|
+
/** Any one of these being present is enough. */
|
|
5
|
+
anyOf: string[];
|
|
6
|
+
/** Work that cannot be done without it, for the message. */
|
|
7
|
+
without: string;
|
|
8
|
+
/** True when the tool cannot function at all without this. */
|
|
9
|
+
essential: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare const REQUIREMENTS: Requirement[];
|
|
12
|
+
/** Is this program on the machine, by name, without running anything of theirs. */
|
|
13
|
+
export declare function present(program: string): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* The one command that installs what is missing, for the machine it is on.
|
|
16
|
+
*
|
|
17
|
+
* Named per platform rather than guessed: telling somebody on Ubuntu to run a
|
|
18
|
+
* Homebrew command is worse than saying nothing, because they now have to work
|
|
19
|
+
* out that the instruction was wrong before they can work out what is right.
|
|
20
|
+
*/
|
|
21
|
+
export declare function howToInstall(missing: string[]): string;
|
|
22
|
+
export interface Readiness {
|
|
23
|
+
ready: boolean;
|
|
24
|
+
/** One line per requirement, ready to print. */
|
|
25
|
+
lines: string[];
|
|
26
|
+
/** The command to run, empty when nothing is missing. */
|
|
27
|
+
fix: string;
|
|
28
|
+
}
|
|
29
|
+
/** What this machine can and cannot do, and how to close the gap. */
|
|
30
|
+
export declare function check(): Readiness;
|
|
@@ -0,0 +1,89 @@
|
|
|
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.REQUIREMENTS = void 0;
|
|
7
|
+
exports.present = present;
|
|
8
|
+
exports.howToInstall = howToInstall;
|
|
9
|
+
exports.check = check;
|
|
10
|
+
/**
|
|
11
|
+
* What the machine services need on a computer before they can do their work:
|
|
12
|
+
* git for the git actions, a PDF reader for PDFs (service catalog, machine
|
|
13
|
+
* services). Kept with the engine, not with one client, because the terminal,
|
|
14
|
+
* VS Code and desktop all carry the same engine. Checked on the computer itself,
|
|
15
|
+
* and at install time, before anyone has signed in.
|
|
16
|
+
*/
|
|
17
|
+
const node_child_process_1 = require("node:child_process");
|
|
18
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
19
|
+
exports.REQUIREMENTS = [
|
|
20
|
+
{
|
|
21
|
+
purpose: 'working with a repository',
|
|
22
|
+
anyOf: ['git'],
|
|
23
|
+
without: 'reading, branching and committing a project',
|
|
24
|
+
essential: false,
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
purpose: 'reading a PDF you already have',
|
|
28
|
+
// Three ways to do the same job. The first keeps the layout, the second is
|
|
29
|
+
// close, the third is usually there on any machine with Python at all.
|
|
30
|
+
anyOf: ['pdftotext', 'mutool', 'python3'],
|
|
31
|
+
without: 'reading a specification or a contract that arrived as a PDF',
|
|
32
|
+
essential: false,
|
|
33
|
+
},
|
|
34
|
+
];
|
|
35
|
+
/** Is this program on the machine, by name, without running anything of theirs. */
|
|
36
|
+
function present(program) {
|
|
37
|
+
try {
|
|
38
|
+
// Windows has no /usr/bin/env, so every program read as missing there.
|
|
39
|
+
if (node_os_1.default.platform() === 'win32')
|
|
40
|
+
(0, node_child_process_1.execFileSync)('where', [program], { stdio: 'ignore' });
|
|
41
|
+
else
|
|
42
|
+
(0, node_child_process_1.execFileSync)('/usr/bin/env', ['which', program], { stdio: 'ignore' });
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The one command that installs what is missing, for the machine it is on.
|
|
51
|
+
*
|
|
52
|
+
* Named per platform rather than guessed: telling somebody on Ubuntu to run a
|
|
53
|
+
* Homebrew command is worse than saying nothing, because they now have to work
|
|
54
|
+
* out that the instruction was wrong before they can work out what is right.
|
|
55
|
+
*/
|
|
56
|
+
function howToInstall(missing) {
|
|
57
|
+
if (missing.length === 0)
|
|
58
|
+
return '';
|
|
59
|
+
const platform = node_os_1.default.platform();
|
|
60
|
+
const parts = missing.map((m) => (m === 'pdftotext' ? 'poppler' : m));
|
|
61
|
+
if (platform === 'darwin')
|
|
62
|
+
return `brew install ${parts.join(' ')}`;
|
|
63
|
+
if (platform === 'linux') {
|
|
64
|
+
const apt = missing.map((m) => (m === 'pdftotext' ? 'poppler-utils' : m));
|
|
65
|
+
return `sudo apt-get install -y ${apt.join(' ')}`;
|
|
66
|
+
}
|
|
67
|
+
if (platform === 'win32')
|
|
68
|
+
return `winget install ${parts.join(' ')}`;
|
|
69
|
+
return `install: ${parts.join(', ')}`;
|
|
70
|
+
}
|
|
71
|
+
/** What this machine can and cannot do, and how to close the gap. */
|
|
72
|
+
function check() {
|
|
73
|
+
const lines = [];
|
|
74
|
+
const missing = [];
|
|
75
|
+
for (const req of exports.REQUIREMENTS) {
|
|
76
|
+
const found = req.anyOf.find(present);
|
|
77
|
+
if (found) {
|
|
78
|
+
lines.push(` ready ${req.purpose}`);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
// The FIRST of the alternatives is the one named, because a list of three
|
|
82
|
+
// equivalent programs is a decision nobody wants to make about a thing
|
|
83
|
+
// they did not know they needed.
|
|
84
|
+
missing.push(req.anyOf[0]);
|
|
85
|
+
lines.push(` missing ${req.purpose}, so ${req.without} will not work`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return { ready: missing.length === 0, lines, fix: howToInstall(missing) };
|
|
89
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** True when running as the ready to run program rather than under Node. */
|
|
2
|
+
export declare function isStandalone(): boolean;
|
|
3
|
+
/** Where the install script keeps the program: `~/.comprism/bin`. */
|
|
4
|
+
export declare function standaloneBinDir(): string;
|
|
5
|
+
export type InstallMethod = "script" | "npm" | "python";
|
|
6
|
+
/**
|
|
7
|
+
* Which of the three install options put this copy here.
|
|
8
|
+
*
|
|
9
|
+
* The program is only ever distributed two ways, so where it sits decides it:
|
|
10
|
+
* in `~/.comprism/bin` it came from the install script, anywhere else pip put
|
|
11
|
+
* it. Under Node it came from npm.
|
|
12
|
+
*/
|
|
13
|
+
export declare function installMethod(): InstallMethod;
|
|
14
|
+
/** The command and arguments that start another copy of this tool. */
|
|
15
|
+
export declare function selfLaunch(args: string[], nodeFlags?: string[]): [string, string[]];
|
|
16
|
+
/**
|
|
17
|
+
* A file shipped beside the code, as a path on disk.
|
|
18
|
+
*
|
|
19
|
+
* Under npm it sits in the package. Inside the program it is carried as an
|
|
20
|
+
* asset and written once to `~/.comprism/assets`, because the programs it is
|
|
21
|
+
* handed to (Python, for the reader script) need a real file.
|
|
22
|
+
*/
|
|
23
|
+
export declare function shippedFile(name: string, packagePath: string): string;
|