@floh-solutions/pharos-cli 0.29.0 → 0.30.0

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 (45) hide show
  1. package/README.md +33 -0
  2. package/dist/capabilities.d.ts +7 -0
  3. package/dist/capabilities.d.ts.map +1 -1
  4. package/dist/capabilities.js +25 -0
  5. package/dist/capabilities.js.map +1 -1
  6. package/dist/cli.d.ts +18 -1
  7. package/dist/cli.d.ts.map +1 -1
  8. package/dist/cli.js +178 -86
  9. package/dist/cli.js.map +1 -1
  10. package/dist/commands/delegate.d.ts +143 -0
  11. package/dist/commands/delegate.d.ts.map +1 -0
  12. package/dist/commands/delegate.js +364 -0
  13. package/dist/commands/delegate.js.map +1 -0
  14. package/dist/commands/doctor.d.ts +8 -0
  15. package/dist/commands/doctor.d.ts.map +1 -1
  16. package/dist/commands/doctor.js +179 -4
  17. package/dist/commands/doctor.js.map +1 -1
  18. package/dist/commands/setup.d.ts.map +1 -1
  19. package/dist/commands/setup.js +7 -3
  20. package/dist/commands/setup.js.map +1 -1
  21. package/dist/delegate/hosts.d.ts +83 -0
  22. package/dist/delegate/hosts.d.ts.map +1 -0
  23. package/dist/delegate/hosts.js +154 -0
  24. package/dist/delegate/hosts.js.map +1 -0
  25. package/dist/delegate/quote.d.ts +78 -0
  26. package/dist/delegate/quote.d.ts.map +1 -0
  27. package/dist/delegate/quote.js +134 -0
  28. package/dist/delegate/quote.js.map +1 -0
  29. package/dist/delegate/sessions.d.ts +113 -0
  30. package/dist/delegate/sessions.d.ts.map +1 -0
  31. package/dist/delegate/sessions.js +220 -0
  32. package/dist/delegate/sessions.js.map +1 -0
  33. package/dist/delegate/vsix.d.ts +122 -0
  34. package/dist/delegate/vsix.d.ts.map +1 -0
  35. package/dist/delegate/vsix.js +329 -0
  36. package/dist/delegate/vsix.js.map +1 -0
  37. package/package.json +3 -2
  38. package/skill/SKILL.md +41 -0
  39. package/vscode/README.md +30 -0
  40. package/vscode/pharos-bridge.json +10 -0
  41. package/vscode/pharos-bridge.vsix +0 -0
  42. package/dist/commands/pr.d.ts +0 -56
  43. package/dist/commands/pr.d.ts.map +0 -1
  44. package/dist/commands/pr.js +0 -202
  45. package/dist/commands/pr.js.map +0 -1
@@ -0,0 +1,220 @@
1
+ import { execFile } from "node:child_process";
2
+ import { realpath } from "node:fs/promises";
3
+ import { basename } from "node:path";
4
+ import { promisify } from "node:util";
5
+ import { hostOfCommand } from "./hosts.js";
6
+ const run = promisify(execFile);
7
+ /**
8
+ * `ps -axo pid,ppid,tty,comm`, parsed.
9
+ *
10
+ * The command column is taken as everything after the third column because
11
+ * it contains spaces — `/Applications/ChatGPT.app/…/Codex (Service)` is one
12
+ * command. The header line does not match the numeric shape and is dropped.
13
+ */
14
+ export function parsePs(text) {
15
+ const rows = [];
16
+ for (const line of text.split("\n")) {
17
+ const match = /^\s*(\d+)\s+(\d+)\s+(\S+)\s+(.+?)\s*$/.exec(line);
18
+ if (match === null)
19
+ continue;
20
+ const tty = match[3];
21
+ rows.push({
22
+ pid: Number(match[1]),
23
+ ppid: Number(match[2]),
24
+ tty: tty === "??" || tty === "-" ? null : tty.startsWith("/dev/") ? tty : `/dev/${tty}`,
25
+ comm: match[4],
26
+ });
27
+ }
28
+ return rows;
29
+ }
30
+ /**
31
+ * `claude agents --json`, parsed tolerantly: an array of objects with a numeric
32
+ * `pid` and a string `cwd` is all that is required, so a field Claude Code
33
+ * adds or drops in a later release changes nothing here.
34
+ */
35
+ export function parseClaudeAgents(json) {
36
+ let parsed;
37
+ try {
38
+ parsed = JSON.parse(json);
39
+ }
40
+ catch {
41
+ return [];
42
+ }
43
+ if (!Array.isArray(parsed))
44
+ return [];
45
+ const agents = [];
46
+ for (const entry of parsed) {
47
+ if (typeof entry !== "object" || entry === null)
48
+ continue;
49
+ const record = entry;
50
+ if (typeof record["pid"] !== "number" || typeof record["cwd"] !== "string")
51
+ continue;
52
+ agents.push({
53
+ pid: record["pid"],
54
+ cwd: record["cwd"],
55
+ ...(typeof record["kind"] === "string" ? { kind: record["kind"] } : {}),
56
+ ...(typeof record["sessionId"] === "string" ? { sessionId: record["sessionId"] } : {}),
57
+ ...(typeof record["name"] === "string" ? { name: record["name"] } : {}),
58
+ ...(typeof record["status"] === "string" ? { status: record["status"] } : {}),
59
+ ...(typeof record["startedAt"] === "number" ? { startedAt: record["startedAt"] } : {}),
60
+ });
61
+ }
62
+ return agents;
63
+ }
64
+ /** `lsof -a -p PID -d cwd -Fn` prints `p<pid>`, `fcwd`, `n<path>`; the path is what we want. */
65
+ export function parseLsofCwd(text) {
66
+ for (const line of text.split("\n")) {
67
+ if (line.startsWith("n"))
68
+ return line.slice(1).trim() || null;
69
+ }
70
+ return null;
71
+ }
72
+ /** The ancestors of a pid, nearest first, stopping at launchd or a cycle. */
73
+ export function ancestry(pid, rows) {
74
+ const byPid = new Map(rows.map((row) => [row.pid, row]));
75
+ const chain = [];
76
+ const seen = new Set();
77
+ let current = byPid.get(pid)?.ppid;
78
+ while (current !== undefined && current > 1 && !seen.has(current)) {
79
+ seen.add(current);
80
+ const row = byPid.get(current);
81
+ if (row === undefined)
82
+ break;
83
+ chain.push(row);
84
+ current = row.ppid;
85
+ }
86
+ return chain;
87
+ }
88
+ /** The first ancestor that is a known host, or null. */
89
+ export function hostOfPid(pid, rows) {
90
+ for (const ancestor of ancestry(pid, rows)) {
91
+ const host = hostOfCommand(ancestor.comm);
92
+ if (host !== null)
93
+ return host;
94
+ }
95
+ return null;
96
+ }
97
+ export function systemProbes(env, claudePath) {
98
+ return {
99
+ ps: async () => (await run("/bin/ps", ["-axo", "pid,ppid,tty,comm"], { timeout: 10_000, maxBuffer: 8 * 1024 * 1024 })).stdout,
100
+ claudeAgents: async () => {
101
+ if (claudePath === null)
102
+ return "[]";
103
+ // Bounded and given the caller's env: the same rule as every probe in
104
+ // `doctor` — it must ask the machine the report describes.
105
+ const { stdout } = await run(claudePath, ["agents", "--json"], {
106
+ timeout: 20_000,
107
+ maxBuffer: 4 * 1024 * 1024,
108
+ env,
109
+ });
110
+ return stdout;
111
+ },
112
+ lsofCwd: async (pid) => (await run("/usr/sbin/lsof", ["-a", "-p", String(pid), "-d", "cwd", "-Fn"], { timeout: 10_000 })).stdout,
113
+ };
114
+ }
115
+ /**
116
+ * Every session of `agent` whose working directory is `folder`, with its host.
117
+ *
118
+ * Working directories are compared after `realpath` on both sides, so a
119
+ * symlinked checkout and its target read as one folder. A session whose cwd
120
+ * cannot be resolved is compared as printed.
121
+ */
122
+ export async function findSessions(agent, folder, probes) {
123
+ const rows = parsePs(await probes.ps());
124
+ const target = await canonical(folder);
125
+ const sessions = [];
126
+ if (agent === "claude") {
127
+ let agents = [];
128
+ try {
129
+ agents = parseClaudeAgents(await probes.claudeAgents());
130
+ }
131
+ catch (error) {
132
+ // A `claude` that cannot list its sessions leaves us unable to tell an
133
+ // idle session from none at all — the caller launches, and says so.
134
+ return { sessions, incomplete: true, detail: `\`claude agents --json\` could not be read: ${messageOf(error)}` };
135
+ }
136
+ for (const found of agents) {
137
+ if ((await canonical(found.cwd)) !== target)
138
+ continue;
139
+ sessions.push({
140
+ agent,
141
+ pid: found.pid,
142
+ cwd: found.cwd,
143
+ name: found.name ?? null,
144
+ status: found.status === "idle" || found.status === "busy" ? found.status : null,
145
+ tty: rows.find((row) => row.pid === found.pid)?.tty ?? null,
146
+ host: hostOfPid(found.pid, rows),
147
+ });
148
+ }
149
+ return { sessions, incomplete: false, detail: null };
150
+ }
151
+ // Codex: the TUI process is `codex`; `codex-code-mode-host` and the ChatGPT
152
+ // app's bundled helpers are not sessions, and the host walk drops the app's
153
+ // own `codex` because ChatGPT.app is not a host anybody can delegate to.
154
+ let lsofFailed = 0;
155
+ for (const row of rows) {
156
+ if (basename(row.comm) !== "codex")
157
+ continue;
158
+ let cwd = null;
159
+ try {
160
+ cwd = parseLsofCwd(await probes.lsofCwd(row.pid));
161
+ }
162
+ catch {
163
+ // One codex whose cwd could not be read is one that might be on this
164
+ // folder and go unseen. Counted so the caller can say detection was partial.
165
+ lsofFailed += 1;
166
+ continue;
167
+ }
168
+ if (cwd === null || (await canonical(cwd)) !== target)
169
+ continue;
170
+ sessions.push({
171
+ agent,
172
+ pid: row.pid,
173
+ cwd,
174
+ name: null,
175
+ // Codex does not say. `null` is "not known", never a guess at idle.
176
+ status: null,
177
+ tty: row.tty,
178
+ host: hostOfPid(row.pid, rows),
179
+ });
180
+ }
181
+ return {
182
+ sessions,
183
+ incomplete: lsofFailed > 0,
184
+ detail: lsofFailed > 0 ? `the working directory of ${lsofFailed} codex process(es) could not be read (\`lsof\`)` : null,
185
+ };
186
+ }
187
+ /**
188
+ * Which session to send to, among those in the requested host.
189
+ *
190
+ * Idle beats busy: a prompt typed into a busy session queues behind the turn
191
+ * in progress, which works but is a worse experience than a session that is
192
+ * waiting for exactly this. Among equals the lowest pid — the longest-lived,
193
+ * which is usually the one the person thinks of as "the" session.
194
+ */
195
+ export function chooseSession(sessions, host) {
196
+ // **A controlling tty is required to type into a session, so a tty-less one
197
+ // is not a send target.** `claude agents --json` lists sessions with no tty,
198
+ // and the Codex VS Code extension's own app-server is a tty-less `codex`;
199
+ // choosing one makes a host answer `sent` while the route can only launch.
200
+ // Navarch is the exception — its workers reach the app over the socket, not a
201
+ // tty — but delegation to Navarch is refused upstream, so this is defensive.
202
+ const mine = sessions.filter((session) => session.host === host && (host === "navarch" || session.tty !== null));
203
+ if (mine.length === 0)
204
+ return null;
205
+ const rank = (session) => session.status === "idle" ? 0 : session.status === null ? 1 : 2;
206
+ return [...mine].sort((a, b) => rank(a) - rank(b) || a.pid - b.pid)[0];
207
+ }
208
+ function messageOf(error) {
209
+ return error instanceof Error ? error.message : String(error);
210
+ }
211
+ async function canonical(path) {
212
+ const trimmed = path.length > 1 ? path.replace(/\/+$/, "") : path;
213
+ try {
214
+ return await realpath(trimmed);
215
+ }
216
+ catch {
217
+ return trimmed;
218
+ }
219
+ }
220
+ //# sourceMappingURL=sessions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessions.js","sourceRoot":"","sources":["../../src/delegate/sessions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,122 @@
1
+ import { type HostId } from "./hosts.js";
2
+ /**
3
+ * The Pharos bridge for VS Code — what this CLI ships, and what a machine has.
4
+ *
5
+ * ## Why the CLI carries a `.vsix` at all
6
+ *
7
+ * Nothing outside VS Code can put a prompt into a session (measured against
8
+ * the Claude Code and Codex extensions: no URI handler takes text, and the
9
+ * `code` CLI runs no terminal command). The bridge — `packages/pharos-vscode`,
10
+ * publisher `floh-solutions`, name `pharos` — is the thing that can, and it
11
+ * ships INSIDE the npm package the way `skill/` does, so `pharos setup
12
+ * --install vscode-bridge` needs no network and no Marketplace listing. A
13
+ * listing is a later, separate decision and a fourth release route.
14
+ *
15
+ * ## Two files, one source of truth
16
+ *
17
+ * The bridge's build writes `vscode/pharos-bridge.vsix` and, beside it,
18
+ * `vscode/pharos-bridge.json` — `{ id, version, vsix, sha256, bytes }`,
19
+ * derived from the extension's own `package.json`. The JSON is what `doctor`
20
+ * reads to say which bridge this CLI ships, and the sha256 in it is what the
21
+ * installer checks the `.vsix` against before handing it to VS Code: the
22
+ * `convert.ts` rule, that bytes are verified against something already in the
23
+ * package rather than trusted because they are there. When the JSON is absent
24
+ * the `.vsix` is unzipped for its `extension/package.json` instead, so an
25
+ * older build still reports a version.
26
+ */
27
+ export declare const BRIDGE_EXTENSION_ID = "floh-solutions.pharos";
28
+ /** `anthropic.claude-code` and `openai.chatgpt` — the ids the extension folders and manifest carry. */
29
+ export declare const CLAUDE_EXTENSION_ID = "anthropic.claude-code";
30
+ export declare const CODEX_EXTENSION_ID = "openai.chatgpt";
31
+ /**
32
+ * Where the bundled `.vsix` is.
33
+ *
34
+ * `PHAROS_BRIDGE_VSIX` overrides it — for a developer installing a local
35
+ * build, and for the tests, which must never install a real extension into
36
+ * somebody's editor to prove a refusal.
37
+ */
38
+ export declare function bundledVsixPath(env?: NodeJS.ProcessEnv): string;
39
+ /** The manifest the bridge's build writes beside the `.vsix`. */
40
+ export declare function bundledManifestPath(env?: NodeJS.ProcessEnv): string;
41
+ export interface VsixManifest {
42
+ publisher: string | null;
43
+ name: string | null;
44
+ version: string | null;
45
+ }
46
+ export interface BundledBridge {
47
+ path: string;
48
+ bytes: number;
49
+ /** Publisher, name and version — from the JSON manifest, else from the zip. */
50
+ manifest: VsixManifest;
51
+ /** What the JSON manifest says the bytes should hash to; null without one. */
52
+ sha256: string | null;
53
+ /** Where the version came from. */
54
+ via: "manifest" | "vsix";
55
+ }
56
+ /** The bridge this CLI ships, or null when the `.vsix` is not there. Never throws. */
57
+ export declare function bundledBridge(env?: NodeJS.ProcessEnv): Promise<BundledBridge | null>;
58
+ /**
59
+ * `extension/package.json` out of a `.vsix`, which is a zip.
60
+ *
61
+ * A zip reader in forty lines rather than a dependency: the package has none
62
+ * today, and one to read three fields out of a file we produce ourselves
63
+ * would be a poor trade. Handles the two methods `vsce` writes (stored and
64
+ * deflate) and nothing else; zip64 is not needed for a bridge this size.
65
+ */
66
+ export declare function vsixManifest(bytes: Buffer): VsixManifest;
67
+ /** One file's contents out of a zip, by exact name, or null. */
68
+ export declare function zipEntry(bytes: Buffer, name: string): Buffer | null;
69
+ export interface InstalledExtension {
70
+ id: string;
71
+ version: string;
72
+ path: string;
73
+ }
74
+ /** `~/.vscode[-insiders]/extensions` for a VS Code flavour; null for any other host. */
75
+ export declare function extensionsDirectory(host: HostId, env?: NodeJS.ProcessEnv): string | null;
76
+ /**
77
+ * What a flavour has installed. `null` when the directory does not exist —
78
+ * which is "this VS Code has never run", a different fact from "no extensions".
79
+ *
80
+ * `extensions.json` is read first because it is what VS Code itself reads:
81
+ * the reference machine held two `anthropic.claude-code-*` folders side by
82
+ * side (an update leaves the old one behind), and only the manifest says which
83
+ * is live. A missing or unreadable manifest falls back to the folder names,
84
+ * `<publisher>.<name>-<version>[-<platform>]`.
85
+ */
86
+ export declare function installedExtensions(host: HostId, env?: NodeJS.ProcessEnv): Promise<InstalledExtension[] | null>;
87
+ /** `anthropic.claude-code-2.1.228-darwin-arm64` → id and version. Null for anything else. */
88
+ export declare function parseExtensionFolder(name: string): {
89
+ id: string;
90
+ version: string;
91
+ } | null;
92
+ export interface BridgeInstallOutcome {
93
+ ok: boolean;
94
+ /** The last command run, so a transcript shows what happened. */
95
+ command: string | null;
96
+ detail: string;
97
+ /** Per flavour: which got it, which did not, and why. */
98
+ flavours: {
99
+ host: HostId;
100
+ ok: boolean;
101
+ detail: string;
102
+ }[];
103
+ }
104
+ /**
105
+ * Install the bundled bridge into every VS Code flavour on this machine.
106
+ *
107
+ * Through the bundle's OWN `bin/code`, because the `code` command is usually
108
+ * not on PATH (see {@link bundleCli}). `--force` replaces an older bridge
109
+ * without a prompt; a headless install that stops to ask is a wizard that
110
+ * hangs.
111
+ *
112
+ * A missing `.vsix` is a refusal with the path, never a stack trace: the
113
+ * package can legitimately ship without one until the bridge's build has
114
+ * written it, and `doctor` names that state too. A `.vsix` that does not
115
+ * match its manifest's checksum is refused the same way — nothing is handed
116
+ * to VS Code that the package did not vouch for.
117
+ */
118
+ export declare function installBridge(options?: {
119
+ env?: NodeJS.ProcessEnv;
120
+ onCommand?: (command: string) => void;
121
+ }): Promise<BridgeInstallOutcome>;
122
+ //# sourceMappingURL=vsix.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vsix.d.ts","sourceRoot":"","sources":["../../src/delegate/vsix.ts"],"names":[],"mappings":"AASA,OAAO,EAA6B,KAAK,MAAM,EAAE,MAAM,YAAY,CAAC;AAIpE;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,eAAO,MAAM,mBAAmB,0BAA0B,CAAC;AAE3D,uGAAuG;AACvG,eAAO,MAAM,mBAAmB,0BAA0B,CAAC;AAC3D,eAAO,MAAM,kBAAkB,mBAAmB,CAAC;AAEnD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAM5E;AAED,iEAAiE;AACjE,wBAAgB,mBAAmB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAEhF;AAED,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,+EAA+E;IAC/E,QAAQ,EAAE,YAAY,CAAC;IACvB,8EAA8E;IAC9E,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,mCAAmC;IACnC,GAAG,EAAE,UAAU,GAAG,MAAM,CAAC;CAC1B;AAED,sFAAsF;AACtF,wBAAsB,aAAa,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CA8BvG;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,CAcxD;AAMD,gEAAgE;AAChE,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA6CnE;AAED,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,wFAAwF;AACxF,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,GAAG,IAAI,CAIrG;AAED;;;;;;;;;GASG;AACH,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,MAAM,EACZ,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,OAAO,CAAC,kBAAkB,EAAE,GAAG,IAAI,CAAC,CAmCtC;AAED,6FAA6F;AAC7F,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAOzF;AAED,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,OAAO,CAAC;IACZ,iEAAiE;IACjE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,yDAAyD;IACzD,QAAQ,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC3D;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,aAAa,CACjC,OAAO,GAAE;IACP,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CAClC,GACL,OAAO,CAAC,oBAAoB,CAAC,CAoF/B"}