@midhunxavier/code-flow 0.0.0-stage → 0.1.0-preview.1

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 (284) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +122 -2
  3. package/bin/codeflow.mjs +8 -0
  4. package/bin/launcher.mjs +127 -0
  5. package/dist/remote-host.json +1 -0
  6. package/licenses/inter.txt +92 -0
  7. package/licenses/preact.txt +21 -0
  8. package/licenses/signals-core.txt +21 -0
  9. package/licenses/signals.txt +21 -0
  10. package/licenses/typescript-notices.txt +193 -0
  11. package/licenses/typescript.txt +55 -0
  12. package/licenses/uqr.txt +22 -0
  13. package/licenses/xterm-fit.txt +19 -0
  14. package/licenses/xterm.txt +21 -0
  15. package/package.json +34 -4
  16. package/public/assets/browser-7XI52A6V.js +8 -0
  17. package/public/assets/changes-EYFPASEI.js +1 -0
  18. package/public/assets/chunk-7H3ZLTKA.js +2 -0
  19. package/public/assets/chunk-L7TUCIYF.js +1 -0
  20. package/public/assets/chunk-LLMQG23J.js +163 -0
  21. package/public/assets/main-KIRM2OEK.js +1 -0
  22. package/public/assets/settings-QJZVW22Y.js +9 -0
  23. package/public/assets/styles-615EBB20.css +32 -0
  24. package/public/assets/terminal-A2FI2NAX.js +62 -0
  25. package/public/assets.json +1 -0
  26. package/public/bundle-meta.json +1 -0
  27. package/public/docs/.nojekyll +0 -0
  28. package/public/docs/accessibility.html +19 -0
  29. package/public/docs/acp-agents.html +117 -0
  30. package/public/docs/agent-containers.html +79 -0
  31. package/public/docs/api.html +1130 -0
  32. package/public/docs/app.js +342 -0
  33. package/public/docs/application-improvements.html +275 -0
  34. package/public/docs/architecture-explorer.html +19 -0
  35. package/public/docs/architecture.html +149 -0
  36. package/public/docs/auto-team.html +142 -0
  37. package/public/docs/automations.html +109 -0
  38. package/public/docs/checkpoint-review.html +37 -0
  39. package/public/docs/code-hosts.html +35 -0
  40. package/public/docs/code-intelligence.html +35 -0
  41. package/public/docs/configuration.html +346 -0
  42. package/public/docs/context-inspection.html +23 -0
  43. package/public/docs/coordinated-changes.html +28 -0
  44. package/public/docs/deferred-runs.html +25 -0
  45. package/public/docs/dependency-discovery.html +19 -0
  46. package/public/docs/documentation.html +84 -0
  47. package/public/docs/dynamic-teams-evaluation.html +174 -0
  48. package/public/docs/dynamic-teams-implementation-plan.html +882 -0
  49. package/public/docs/dynamic-teams-implementation-review-1.html +141 -0
  50. package/public/docs/dynamic-teams-plan-review-0.html +204 -0
  51. package/public/docs/extensions.html +42 -0
  52. package/public/docs/external-sources.html +96 -0
  53. package/public/docs/filesystem-policy.html +26 -0
  54. package/public/docs/filtered-tasks.html +44 -0
  55. package/public/docs/font-license.txt +92 -0
  56. package/public/docs/getting-started.html +118 -0
  57. package/public/docs/github-review-threads.html +26 -0
  58. package/public/docs/github.html +157 -0
  59. package/public/docs/help.html +69 -0
  60. package/public/docs/index.html +77 -0
  61. package/public/docs/installation.html +74 -0
  62. package/public/docs/launch-profiles.html +48 -0
  63. package/public/docs/maintenance.html +60 -0
  64. package/public/docs/memory-claims.html +31 -0
  65. package/public/docs/memory-storage.html +58 -0
  66. package/public/docs/notifications.html +39 -0
  67. package/public/docs/patterns.html +195 -0
  68. package/public/docs/performance-results.html +210 -0
  69. package/public/docs/performance.html +166 -0
  70. package/public/docs/project-conventions.html +22 -0
  71. package/public/docs/project-first-navigation-plan.html +587 -0
  72. package/public/docs/project-teams-implementation-plan.html +772 -0
  73. package/public/docs/project-teams.html +99 -0
  74. package/public/docs/provider-compatibility.html +20 -0
  75. package/public/docs/releasing.html +61 -0
  76. package/public/docs/remote-workers.html +64 -0
  77. package/public/docs/review-inbox.html +26 -0
  78. package/public/docs/run-storage.html +23 -0
  79. package/public/docs/runs.html +141 -0
  80. package/public/docs/search.json +1 -0
  81. package/public/docs/security.html +133 -0
  82. package/public/docs/shared-projects.html +31 -0
  83. package/public/docs/ssh-projects.html +60 -0
  84. package/public/docs/style.css +768 -0
  85. package/public/docs/task-reruns.html +30 -0
  86. package/public/docs/team-collaboration.html +379 -0
  87. package/public/docs/verification.html +22 -0
  88. package/public/docs/vscode.html +53 -0
  89. package/public/docs/worker-permission-verification.html +122 -0
  90. package/public/index.html +17 -0
  91. package/scripts/check-node.mjs +7 -0
  92. package/scripts/codeflow.mjs +55 -0
  93. package/scripts/pty.py +273 -0
  94. package/src/acp-connections.js +110 -0
  95. package/src/adapters/acp-presets.js +37 -0
  96. package/src/adapters/acp-types.js +0 -0
  97. package/src/adapters/acp.js +328 -0
  98. package/src/adapters/agy-auth.js +67 -0
  99. package/src/adapters/agy-isolation.js +223 -0
  100. package/src/adapters/agy.js +297 -0
  101. package/src/adapters/claude.js +220 -0
  102. package/src/adapters/codex-app-server.js +518 -0
  103. package/src/adapters/codex.js +299 -0
  104. package/src/adapters/errors.js +57 -0
  105. package/src/adapters/index.js +91 -0
  106. package/src/adapters/managed-permissions.js +37 -0
  107. package/src/adapters/model-discovery.js +100 -0
  108. package/src/adapters/process.js +418 -0
  109. package/src/adapters/team-tools.js +102 -0
  110. package/src/adapters/types.js +0 -0
  111. package/src/agent-container.js +155 -0
  112. package/src/approvals.js +121 -0
  113. package/src/architecture.js +456 -0
  114. package/src/assets.js +44 -0
  115. package/src/automation.js +901 -0
  116. package/src/certificate-renewal.js +75 -0
  117. package/src/chat-handoff.js +55 -0
  118. package/src/checkpoint-review.js +72 -0
  119. package/src/checks.js +303 -0
  120. package/src/code-host-git.js +102 -0
  121. package/src/code-hosts-api.js +99 -0
  122. package/src/code-hosts.js +482 -0
  123. package/src/code-intelligence-worker.mjs +452 -0
  124. package/src/code-intelligence.js +141 -0
  125. package/src/collab/api.js +365 -0
  126. package/src/collab/checkpoints.js +198 -0
  127. package/src/collab/connections.js +139 -0
  128. package/src/collab/host-execution.js +347 -0
  129. package/src/collab/hub-client.js +210 -0
  130. package/src/collab/link-api.js +229 -0
  131. package/src/collab/link.js +595 -0
  132. package/src/collab/model.js +496 -0
  133. package/src/collab/runner.js +1014 -0
  134. package/src/collab/store.js +1452 -0
  135. package/src/collab/terminals.js +361 -0
  136. package/src/collaboration-reviews.js +116 -0
  137. package/src/config.js +48 -0
  138. package/src/context-budget.js +14 -0
  139. package/src/context-inspection.js +204 -0
  140. package/src/context.js +154 -0
  141. package/src/conventions.js +283 -0
  142. package/src/coordinated-api.js +247 -0
  143. package/src/deferred-launch.js +82 -0
  144. package/src/deferred-runs.js +515 -0
  145. package/src/dependency-health.js +35 -0
  146. package/src/diff.js +43 -0
  147. package/src/docs-help.js +316 -0
  148. package/src/doctor.js +232 -0
  149. package/src/engine.js +4366 -0
  150. package/src/env.js +44 -0
  151. package/src/experience.js +50 -0
  152. package/src/extensions.js +144 -0
  153. package/src/filesystem-access.js +107 -0
  154. package/src/filesystem-policy.js +238 -0
  155. package/src/filtered-task-limits.js +93 -0
  156. package/src/filtered-tasks-api.js +53 -0
  157. package/src/filtered-tasks.js +576 -0
  158. package/src/fs.js +57 -0
  159. package/src/git.js +626 -0
  160. package/src/github-review-threads.js +181 -0
  161. package/src/github.js +538 -0
  162. package/src/http.js +69 -0
  163. package/src/improve-settings.js +38 -0
  164. package/src/improve.js +187 -0
  165. package/src/integrations.js +373 -0
  166. package/src/integrity.js +259 -0
  167. package/src/journey-api.js +77 -0
  168. package/src/journey-replay.js +65 -0
  169. package/src/journey-store.js +97 -0
  170. package/src/journeys.js +60 -0
  171. package/src/json.js +81 -0
  172. package/src/launch-profiles.js +292 -0
  173. package/src/learnings.js +80 -0
  174. package/src/log.js +39 -0
  175. package/src/maintenance.js +335 -0
  176. package/src/memory-analysis.js +182 -0
  177. package/src/memory-claims.js +153 -0
  178. package/src/memory-sqlite.js +236 -0
  179. package/src/memory.js +552 -0
  180. package/src/merge-validation.js +58 -0
  181. package/src/model-catalog.js +238 -0
  182. package/src/model-research.js +91 -0
  183. package/src/notes.js +176 -0
  184. package/src/notifications.js +413 -0
  185. package/src/patterns/auto-team-model.js +212 -0
  186. package/src/patterns/auto-team.js +659 -0
  187. package/src/patterns/council.js +199 -0
  188. package/src/patterns/debate.js +188 -0
  189. package/src/patterns/findings.js +141 -0
  190. package/src/patterns/index.js +19 -0
  191. package/src/patterns/loop.js +158 -0
  192. package/src/patterns/pipeline.js +125 -0
  193. package/src/patterns/prompts.js +181 -0
  194. package/src/patterns/race.js +147 -0
  195. package/src/patterns/schemas.js +40 -0
  196. package/src/patterns/solo.js +49 -0
  197. package/src/patterns/supervisor.js +235 -0
  198. package/src/patterns/task-draft.js +29 -0
  199. package/src/patterns/tdd.js +231 -0
  200. package/src/patterns/team-strategies.js +67 -0
  201. package/src/performance.js +43 -0
  202. package/src/persistence-status.js +35 -0
  203. package/src/phone-sessions.js +96 -0
  204. package/src/preview-overlay.js +383 -0
  205. package/src/preview.js +219 -0
  206. package/src/privacy.js +23 -0
  207. package/src/project-report.js +22 -0
  208. package/src/project-team-model.js +178 -0
  209. package/src/project-team-policy.js +42 -0
  210. package/src/project-team-store.js +213 -0
  211. package/src/project-teams-api.js +36 -0
  212. package/src/project-teams.js +551 -0
  213. package/src/projects.js +286 -0
  214. package/src/provider-eligibility.js +9 -0
  215. package/src/readiness.js +98 -0
  216. package/src/remote-tls.js +66 -0
  217. package/src/remote-workers/bundles.js +198 -0
  218. package/src/remote-workers/client.js +332 -0
  219. package/src/remote-workers/durability.js +37 -0
  220. package/src/remote-workers/protocol.js +68 -0
  221. package/src/remote-workers/stdio.js +138 -0
  222. package/src/remote-workers/worker.js +304 -0
  223. package/src/remote.js +204 -0
  224. package/src/repo-config.js +201 -0
  225. package/src/repo-coordination.js +732 -0
  226. package/src/repo-tree.js +106 -0
  227. package/src/retention-policy.js +466 -0
  228. package/src/review-inbox.js +38 -0
  229. package/src/routing.js +49 -0
  230. package/src/run-analysis.js +111 -0
  231. package/src/run-cold-store.js +231 -0
  232. package/src/run-environment.js +15 -0
  233. package/src/run-history.js +91 -0
  234. package/src/run-log.js +160 -0
  235. package/src/run-permissions.js +110 -0
  236. package/src/run-projection.js +75 -0
  237. package/src/run-recovery.js +53 -0
  238. package/src/run-status.js +54 -0
  239. package/src/serve.js +138 -0
  240. package/src/server.js +2738 -0
  241. package/src/shared-access.js +103 -0
  242. package/src/shared-accounts.js +301 -0
  243. package/src/shared-api.js +239 -0
  244. package/src/shell.js +346 -0
  245. package/src/ship.js +344 -0
  246. package/src/slots.js +74 -0
  247. package/src/snapshot.js +388 -0
  248. package/src/source-context.js +17 -0
  249. package/src/sse-writer.js +70 -0
  250. package/src/ssh-projects/bootstrap.mjs +95 -0
  251. package/src/ssh-projects/config.js +45 -0
  252. package/src/ssh-projects/gateway.js +143 -0
  253. package/src/ssh-projects/manager.js +320 -0
  254. package/src/ssh-projects/team-autostart.mjs +93 -0
  255. package/src/ssh-projects/team-remove.mjs +69 -0
  256. package/src/ssh-projects/team-status.mjs +39 -0
  257. package/src/ssh-projects/transport.js +116 -0
  258. package/src/stats.js +139 -0
  259. package/src/store.js +544 -0
  260. package/src/stream.js +91 -0
  261. package/src/sweeps.js +241 -0
  262. package/src/tailscale.js +101 -0
  263. package/src/task-draft.js +23 -0
  264. package/src/team-evaluation.js +242 -0
  265. package/src/team-mailbox.js +160 -0
  266. package/src/team-memory.js +84 -0
  267. package/src/team-rerun-graph.js +15 -0
  268. package/src/team-reruns.js +130 -0
  269. package/src/team-scheduler.js +121 -0
  270. package/src/team-template-store.js +98 -0
  271. package/src/team-templates.js +117 -0
  272. package/src/team-tool-client.mjs +79 -0
  273. package/src/team-tools.js +176 -0
  274. package/src/teams.js +0 -0
  275. package/src/templates.js +62 -0
  276. package/src/transcript.js +134 -0
  277. package/src/verification-integrity.js +27 -0
  278. package/src/verification.js +183 -0
  279. package/src/watch.js +195 -0
  280. package/src/work-queue.js +47 -0
  281. package/src/workflow-control.js +83 -0
  282. package/src/workspace-api.js +207 -0
  283. package/src/workspace-changes.js +61 -0
  284. package/src/workspace.js +146 -0
package/src/shell.js ADDED
@@ -0,0 +1,346 @@
1
+ import { execFile, spawn } from "node:child_process";
2
+ import { randomBytes } from "node:crypto";
3
+ import { existsSync } from "node:fs";
4
+ import { basename } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { killGroup, trackGroup, untrackGroup } from "./adapters/process.js";
7
+ import { childEnv } from "./env.js";
8
+ const MARK = "";
9
+ const MARKER = new RegExp(`${MARK}(\\d+)${MARK}([^${MARK}]*)${MARK}\\n?`);
10
+ const HISTORY_BYTES = 256 * 1024;
11
+ const MAX_SHELLS = 12;
12
+ const PTY_SCRIPT = fileURLToPath(new URL("../scripts/pty.py", import.meta.url));
13
+ const PTY_CLOSE_MS = 3e3;
14
+ const clamp = (n, lo, hi, fallback) => Number.isFinite(Number(n)) ? Math.min(hi, Math.max(lo, Math.round(Number(n)))) : fallback;
15
+ class ShellLimitError extends Error {
16
+ }
17
+ function shellProgram(env = process.env) {
18
+ const own = env.SHELL ?? "";
19
+ if (["bash", "zsh", "sh", "dash", "ksh"].includes(basename(own)) && existsSync(own)) return own;
20
+ return existsSync("/bin/bash") ? "/bin/bash" : "/bin/sh";
21
+ }
22
+ function loginShell(env = process.env) {
23
+ const own = env.SHELL ?? "";
24
+ return own.startsWith("/") && existsSync(own) ? own : shellProgram(env);
25
+ }
26
+ const probes = /* @__PURE__ */ new Map();
27
+ function ptyPython(python = defaultPython()) {
28
+ if (process.platform === "win32") return Promise.resolve(void 0);
29
+ let probe = probes.get(python);
30
+ if (!probe) {
31
+ probe = new Promise((resolve) => {
32
+ const check = "import sys, pty, termios, fcntl, select\nif sys.version_info < (3, 6): sys.exit(1)\nprint(sys.executable)";
33
+ execFile(python, ["-I", "-c", check], { timeout: 1e4, env: childEnv() }, (err, stdout) => resolve(err ? void 0 : String(stdout).trim() || python));
34
+ });
35
+ probes.set(python, probe);
36
+ }
37
+ return probe;
38
+ }
39
+ function ptyAvailable(python = defaultPython(), options = {}) {
40
+ if (options.refresh) probes.delete(python);
41
+ return ptyPython(python).then(Boolean);
42
+ }
43
+ function defaultPython() {
44
+ return process.env.CODEFLOW_PYTHON || "python3";
45
+ }
46
+ class Shells {
47
+ shells = /* @__PURE__ */ new Map();
48
+ python;
49
+ constructor(opts = {}) {
50
+ this.python = opts.python ?? defaultPython();
51
+ }
52
+ /** Whether new terminals can be interactive (python3 is there to run the pseudo-terminal). */
53
+ interactive() {
54
+ return ptyAvailable(this.python);
55
+ }
56
+ list() {
57
+ return [...this.shells.values()].map(describe);
58
+ }
59
+ get(id) {
60
+ const s = this.shells.get(id);
61
+ return s && describe(s);
62
+ }
63
+ /** What a client needs to catch up: the kept output, in order. */
64
+ history(id) {
65
+ const s = this.shells.get(id);
66
+ if (!s) return [];
67
+ const events = [...s.history];
68
+ const first = events[0];
69
+ if (s.interactive && s.trimmed && first?.type === "output") {
70
+ const nl = first.text.indexOf("\n");
71
+ events[0] = { type: "output", text: nl < 0 ? "" : first.text.slice(nl + 1) };
72
+ }
73
+ return events;
74
+ }
75
+ /**
76
+ * A new shell in `cwd`. `pty` asks for a real terminal of `cols` × `rows`; without python3 it's
77
+ * a line-based one (`interactive: false`).
78
+ */
79
+ async open(cwd, opts = {}) {
80
+ const python = opts.pty ? await ptyPython(this.python) : void 0;
81
+ const pty = !!python;
82
+ while (this.shells.size >= MAX_SHELLS) {
83
+ const spare = await this.spare();
84
+ if (!spare) throw new ShellLimitError(`${MAX_SHELLS} terminals are open and each is running something: stop or close one first`);
85
+ this.close(spare.id);
86
+ }
87
+ const id = `sh${Date.now().toString(36)}${randomBytes(2).toString("hex")}`;
88
+ const shell = python ? this.startPty(id, python, cwd, clamp(opts.cols, 2, 1e3, 80), clamp(opts.rows, 1, 500, 24)) : this.startLines(id, cwd);
89
+ shell.child.stdin?.on("error", () => {
90
+ });
91
+ const ended = (code) => {
92
+ if (shell.status === "exited") return;
93
+ untrackGroup(shell.child.pid);
94
+ untrackGroup(shell.pid);
95
+ if (shell.pending) this.emit(shell, { type: "output", text: shell.pending });
96
+ const rest = shell.decoder?.decode();
97
+ if (rest) this.emit(shell, { type: "output", text: rest });
98
+ shell.pending = "";
99
+ shell.status = "exited";
100
+ shell.exitCode = code;
101
+ shell.busy = false;
102
+ shell.running = void 0;
103
+ this.emit(shell, { type: "exit", code });
104
+ };
105
+ shell.child.on(pty ? "close" : "exit", ended);
106
+ shell.child.on("error", () => shell.child.pid === void 0 && ended(null));
107
+ this.shells.set(id, shell);
108
+ return describe(shell);
109
+ }
110
+ /**
111
+ * The shell to close to make room for another: one that's done, else the oldest one waiting for
112
+ * a command. Never one that's running something, in the foreground or as a job in the
113
+ * background (`npm run dev &`, a stopped vim): closing the shell would end it.
114
+ */
115
+ async spare() {
116
+ const byAge = [...this.shells.values()].sort((a, b) => a.startedAt - b.startedAt);
117
+ const exited = byAge.find((s) => s.status === "exited");
118
+ if (exited) return exited;
119
+ if (!byAge.some((s) => !s.busy)) return void 0;
120
+ const procs = await processes();
121
+ return byAge.find((s) => s.status === "exited" || !s.busy && !hasJobs(s, procs));
122
+ }
123
+ /** Send a command to a line-based shell. Its exit code and the folder it left the shell in come back as `done`. */
124
+ write(id, text) {
125
+ const s = this.shells.get(id);
126
+ if (!s || s.status !== "running" || !s.child.stdin?.writable) return false;
127
+ if (s.interactive) return this.type(id, `${text.replace(/\r\n?|\n/g, "\r").replace(/\r$/, "")}\r`);
128
+ const command = text.replace(/\r\n?/g, "\n").replace(/\n$/, "");
129
+ this.emit(s, { type: "input", text: command });
130
+ s.busy = true;
131
+ s.child.stdin.write(`${command}
132
+ printf '\\036%s\\036%s\\036\\n' "$?" "$PWD"
133
+ `);
134
+ return true;
135
+ }
136
+ /**
137
+ * Keystrokes for an interactive shell, as typed (a paste, an escape sequence, Ctrl-C as \x03).
138
+ * `latin1`: bytes, one per character, sent as they are (xterm's mouse reports, which aren't UTF-8).
139
+ */
140
+ type(id, data, encoding = "utf8") {
141
+ const s = this.shells.get(id);
142
+ if (!s || !s.interactive || s.status !== "running" || !s.child.stdin?.writable) return false;
143
+ s.child.stdin.write(Buffer.from(data, encoding));
144
+ return true;
145
+ }
146
+ /** Buffered keystrokes, for bounded remote terminal input. */
147
+ pendingInputBytes(id) {
148
+ return this.shells.get(id)?.child.stdin?.writableLength ?? 0;
149
+ }
150
+ /** An interactive shell's window changed size: the programs in it get SIGWINCH. */
151
+ resize(id, cols, rows) {
152
+ const s = this.shells.get(id);
153
+ const control = s?.child.stdio[3];
154
+ if (!s || !s.interactive || s.status !== "running" || !control?.writable) return false;
155
+ s.cols = clamp(cols, 2, 1e3, s.cols ?? 80);
156
+ s.rows = clamp(rows, 1, 500, s.rows ?? 24);
157
+ control.write(`${JSON.stringify({ resize: [s.cols, s.rows] })}
158
+ `);
159
+ return true;
160
+ }
161
+ subscribe(id, client) {
162
+ const s = this.shells.get(id);
163
+ if (!s) return void 0;
164
+ s.clients.add(client);
165
+ return () => s.clients.delete(client);
166
+ }
167
+ close(id) {
168
+ const s = this.shells.get(id);
169
+ if (!s) return false;
170
+ if (s.status === "running") {
171
+ if (s.interactive) {
172
+ if (s.pid) signalGroup(s.pid, "SIGHUP");
173
+ killGroup(s.child.pid, PTY_CLOSE_MS);
174
+ } else killGroup(s.child.pid, 2e3);
175
+ }
176
+ untrackGroup(s.child.pid);
177
+ this.shells.delete(id);
178
+ return true;
179
+ }
180
+ /**
181
+ * Close every shell: code-flow is stopping. An interactive shell's helper is left to hang up its
182
+ * terminal on its own, after code-flow has exited (about a second), so it's no longer tracked:
183
+ * killed at exit, it couldn't, and jobs in process groups of their own (`nohup cmd &`, `disown`)
184
+ * would keep running with nothing left that knows about them.
185
+ */
186
+ closeAll() {
187
+ for (const s of [...this.shells.values()]) {
188
+ if (!s.interactive || s.status !== "running") {
189
+ this.close(s.id);
190
+ continue;
191
+ }
192
+ if (s.pid) signalGroup(s.pid, "SIGHUP");
193
+ if (s.child.pid) signalGroup(s.child.pid, "SIGTERM");
194
+ untrackGroup(s.child.pid);
195
+ untrackGroup(s.pid);
196
+ this.shells.delete(s.id);
197
+ }
198
+ }
199
+ startLines(id, cwd) {
200
+ const program = shellProgram();
201
+ const child = spawn(program, [], {
202
+ cwd,
203
+ detached: true,
204
+ // its own group: closing the shell closes whatever it started
205
+ stdio: ["pipe", "pipe", "pipe"],
206
+ env: childEnv({ TERM: "dumb", NO_COLOR: "1", FORCE_COLOR: "0", PAGER: "cat", GIT_PAGER: "cat", GIT_TERMINAL_PROMPT: "0", GCM_INTERACTIVE: "never" })
207
+ });
208
+ trackGroup(child.pid);
209
+ const shell = newShell(id, cwd, program, child, false);
210
+ child.stdout.on("data", (chunk) => this.onStdout(shell, chunk.toString()));
211
+ child.stderr.on("data", (chunk) => this.emit(shell, { type: "output", text: chunk.toString() }));
212
+ child.on("error", (err) => this.emit(shell, { type: "output", text: `Couldn't start ${program}: ${err.message}
213
+ ` }));
214
+ return shell;
215
+ }
216
+ startPty(id, python, cwd, cols, rows) {
217
+ const program = loginShell();
218
+ const env = childEnv({ TERM: "xterm-256color", COLORTERM: "truecolor" });
219
+ if (!env.LC_ALL && !env.LC_CTYPE && !env.LANG) env.LANG = process.platform === "darwin" ? "en_US.UTF-8" : "C.UTF-8";
220
+ const child = spawn(python, ["-I", PTY_SCRIPT, String(cols), String(rows), program, `-${basename(program)}`], {
221
+ cwd,
222
+ detached: true,
223
+ // the helper's own group; the shell gets a session of its own in the terminal
224
+ stdio: ["pipe", "pipe", "pipe", "pipe"],
225
+ env
226
+ });
227
+ trackGroup(child.pid);
228
+ const shell = newShell(id, cwd, program, child, true);
229
+ shell.cols = cols;
230
+ shell.rows = rows;
231
+ shell.decoder = new TextDecoder("utf-8");
232
+ child.stdout.on("data", (chunk) => {
233
+ const text = shell.decoder.decode(chunk, { stream: true });
234
+ if (text) this.emit(shell, { type: "output", text });
235
+ });
236
+ child.stderr.on("data", (chunk) => this.emit(shell, { type: "output", text: chunk.toString().replace(/\r?\n/g, "\r\n") }));
237
+ child.on("error", (err) => this.emit(shell, { type: "output", text: `Couldn't start the terminal (${python}): ${err.message}\r
238
+ ` }));
239
+ let control = "";
240
+ child.stdio[3]?.on("data", (chunk) => {
241
+ control += chunk.toString();
242
+ const lines = control.split("\n");
243
+ control = lines.pop();
244
+ for (const line of lines) {
245
+ let msg;
246
+ try {
247
+ msg = JSON.parse(line);
248
+ } catch {
249
+ continue;
250
+ }
251
+ if (typeof msg.pid === "number") {
252
+ shell.pid = msg.pid;
253
+ if (shell.status === "running") trackGroup(msg.pid);
254
+ }
255
+ if (typeof msg.tty === "string") shell.tty = msg.tty.replace(/^\/dev\//, "");
256
+ if ("fg" in msg) this.onForeground(shell, msg.fg ?? null);
257
+ }
258
+ });
259
+ child.stdio[3]?.on("error", () => {
260
+ });
261
+ return shell;
262
+ }
263
+ /** The terminal's foreground changed: busy unless it's the shell itself; name what's running. */
264
+ onForeground(s, fg) {
265
+ s.fg = fg;
266
+ const busy = fg !== null && s.pid !== void 0 && fg !== s.pid;
267
+ if (busy === s.busy && !busy) return;
268
+ s.busy = busy;
269
+ s.running = void 0;
270
+ this.emit(s, { type: "busy", busy });
271
+ if (!busy) return;
272
+ execFile("ps", ["-o", "comm=", "-p", String(fg)], { timeout: 5e3, env: { ...process.env, LC_ALL: "C" } }, (err, stdout) => {
273
+ const name = basename(String(stdout).trim()).replace(/^-/, "");
274
+ if (err || !name || s.fg !== fg || s.status !== "running") return;
275
+ s.running = name;
276
+ this.emit(s, { type: "busy", busy: true, running: name });
277
+ });
278
+ }
279
+ onStdout(s, chunk) {
280
+ s.pending += chunk;
281
+ for (; ; ) {
282
+ const m = MARKER.exec(s.pending);
283
+ if (!m) break;
284
+ const before = s.pending.slice(0, m.index);
285
+ if (before) this.emit(s, { type: "output", text: before });
286
+ s.pending = s.pending.slice(m.index + m[0].length);
287
+ s.busy = false;
288
+ s.cwd = m[2] || s.cwd;
289
+ this.emit(s, { type: "done", code: Number(m[1]), cwd: s.cwd });
290
+ }
291
+ const partial = s.pending.indexOf(MARK);
292
+ const ready = partial < 0 ? s.pending : s.pending.slice(0, partial);
293
+ if (ready) {
294
+ this.emit(s, { type: "output", text: ready });
295
+ s.pending = s.pending.slice(ready.length);
296
+ }
297
+ }
298
+ emit(s, event) {
299
+ if (event.type === "busy") {
300
+ for (const client of s.clients) client(event);
301
+ return;
302
+ }
303
+ const last = s.history.at(-1);
304
+ if (event.type === "output" && last?.type === "output" && last.text.length < 8192) last.text += event.text;
305
+ else s.history.push(event.type === "output" ? { ...event } : event);
306
+ s.historyBytes += event.type === "output" || event.type === "input" ? event.text.length : 16;
307
+ while (s.historyBytes > HISTORY_BYTES && s.history.length > 1) {
308
+ const dropped = s.history.shift();
309
+ s.historyBytes -= dropped.type === "output" || dropped.type === "input" ? dropped.text.length : 16;
310
+ s.trimmed = true;
311
+ }
312
+ for (const client of s.clients) client(event);
313
+ }
314
+ }
315
+ function newShell(id, cwd, program, child, interactive) {
316
+ return { id, cwd, shell: program, status: "running", startedAt: Date.now(), busy: false, interactive, child, history: [], historyBytes: 0, trimmed: false, pending: "", clients: /* @__PURE__ */ new Set() };
317
+ }
318
+ function processes() {
319
+ return new Promise((resolve) => {
320
+ execFile("ps", ["-A", "-o", "pid=,pgid=,tty="], { timeout: 5e3, maxBuffer: 16 * 1024 * 1024, env: { ...process.env, LC_ALL: "C" } }, (err, stdout) => {
321
+ if (err) return resolve([]);
322
+ const rows = String(stdout).split("\n").map((line) => line.trim().split(/\s+/));
323
+ resolve(rows.filter((r) => r.length >= 2).map(([pid, pgid, tty]) => ({ pid: Number(pid), pgid: Number(pgid), tty: tty ?? "" })));
324
+ });
325
+ });
326
+ }
327
+ function hasJobs(s, procs) {
328
+ if (s.interactive) return !!s.tty && procs.some((p) => p.tty === s.tty && p.pgid !== s.pid);
329
+ return procs.some((p) => p.pgid === s.child.pid && p.pid !== s.child.pid);
330
+ }
331
+ function signalGroup(pid, signal) {
332
+ try {
333
+ process.kill(-pid, signal);
334
+ } catch {
335
+ }
336
+ }
337
+ function describe(s) {
338
+ return { id: s.id, cwd: s.cwd, shell: s.shell, status: s.status, exitCode: s.exitCode, startedAt: s.startedAt, busy: s.busy, interactive: s.interactive, cols: s.cols, rows: s.rows, running: s.running };
339
+ }
340
+ export {
341
+ MAX_SHELLS,
342
+ ShellLimitError,
343
+ Shells,
344
+ ptyAvailable,
345
+ shellProgram
346
+ };