@arnilo/prism-tools 0.0.0-stage → 0.13.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 (331) hide show
  1. package/CHANGELOG.md +418 -0
  2. package/LICENSE +21 -0
  3. package/README.md +44 -2
  4. package/browser/README.md +98 -0
  5. package/browser/skills/browser-automation/SKILL.md +14 -0
  6. package/browser/src/actions/cdp.ts +131 -0
  7. package/browser/src/actions/dialog.ts +25 -0
  8. package/browser/src/actions/download_release.ts +22 -0
  9. package/browser/src/actions/index.ts +37 -0
  10. package/browser/src/actions/locator.ts +70 -0
  11. package/browser/src/actions/navigate.ts +23 -0
  12. package/browser/src/actions/screenshot.ts +27 -0
  13. package/browser/src/actions/scroll.ts +24 -0
  14. package/browser/src/actions/select_page.ts +21 -0
  15. package/browser/src/actions/types.ts +114 -0
  16. package/browser/src/actions/upload.ts +44 -0
  17. package/browser/src/actions/wait.ts +45 -0
  18. package/browser/src/browser.ts +197 -0
  19. package/browser/src/cdp.ts +178 -0
  20. package/browser/src/checkpoint.ts +189 -0
  21. package/browser/src/config.ts +72 -0
  22. package/browser/src/downloads.ts +253 -0
  23. package/browser/src/errors.ts +31 -0
  24. package/browser/src/evaluate.ts +101 -0
  25. package/browser/src/index.ts +129 -0
  26. package/browser/src/limits.ts +271 -0
  27. package/browser/src/manager.ts +730 -0
  28. package/browser/src/network.ts +263 -0
  29. package/browser/src/observe.ts +178 -0
  30. package/browser/src/policy.ts +133 -0
  31. package/browser/src/screenshot.ts +107 -0
  32. package/browser/src/shared-sandbox.ts +53 -0
  33. package/browser/src/snapshot.ts +99 -0
  34. package/browser/src/targets.ts +165 -0
  35. package/browser/src/tools.ts +480 -0
  36. package/browser/src/types.ts +316 -0
  37. package/browser/src/uploads.ts +174 -0
  38. package/coding/README.md +337 -0
  39. package/coding/src/agent/acp-operations.ts +146 -0
  40. package/coding/src/agent/artifacts.ts +38 -0
  41. package/coding/src/agent/ask-user-decision.ts +623 -0
  42. package/coding/src/agent/atomic-write.ts +23 -0
  43. package/coding/src/agent/bounded-file.ts +21 -0
  44. package/coding/src/agent/checks.ts +301 -0
  45. package/coding/src/agent/coding-checkpoint.ts +828 -0
  46. package/coding/src/agent/delete.ts +237 -0
  47. package/coding/src/agent/diagnostics-tool.ts +163 -0
  48. package/coding/src/agent/diagnostics.ts +262 -0
  49. package/coding/src/agent/edit-diff.ts +382 -0
  50. package/coding/src/agent/edit.ts +322 -0
  51. package/coding/src/agent/effects.ts +102 -0
  52. package/coding/src/agent/env.ts +28 -0
  53. package/coding/src/agent/execution-policy.ts +89 -0
  54. package/coding/src/agent/file-mutation-queue.ts +72 -0
  55. package/coding/src/agent/forge/github.ts +603 -0
  56. package/coding/src/agent/forge/index.ts +16 -0
  57. package/coding/src/agent/forge/types.ts +185 -0
  58. package/coding/src/agent/git-aware-repository.ts +305 -0
  59. package/coding/src/agent/git-exec.ts +326 -0
  60. package/coding/src/agent/git-status.ts +177 -0
  61. package/coding/src/agent/git-tools.ts +360 -0
  62. package/coding/src/agent/git.ts +1074 -0
  63. package/coding/src/agent/glob-match.ts +146 -0
  64. package/coding/src/agent/glob.ts +182 -0
  65. package/coding/src/agent/goal-verify.ts +385 -0
  66. package/coding/src/agent/grep-worker.ts +74 -0
  67. package/coding/src/agent/grep.ts +459 -0
  68. package/coding/src/agent/identity.ts +48 -0
  69. package/coding/src/agent/index.ts +676 -0
  70. package/coding/src/agent/language/client.ts +429 -0
  71. package/coding/src/agent/language/framing.ts +190 -0
  72. package/coding/src/agent/language/index.ts +23 -0
  73. package/coding/src/agent/language/intelligence.ts +682 -0
  74. package/coding/src/agent/language/types.ts +187 -0
  75. package/coding/src/agent/lifecycle-events.ts +121 -0
  76. package/coding/src/agent/lifecycle.ts +166 -0
  77. package/coding/src/agent/limits.ts +214 -0
  78. package/coding/src/agent/move.ts +178 -0
  79. package/coding/src/agent/mutation-path.ts +52 -0
  80. package/coding/src/agent/output-accumulator.ts +304 -0
  81. package/coding/src/agent/path-utils.ts +120 -0
  82. package/coding/src/agent/process/index.ts +58 -0
  83. package/coding/src/agent/process/recovery.ts +508 -0
  84. package/coding/src/agent/process/sessions-host.ts +222 -0
  85. package/coding/src/agent/process/sessions-monitor.ts +141 -0
  86. package/coding/src/agent/process/sessions-recovery.ts +241 -0
  87. package/coding/src/agent/process/sessions-spawn.ts +613 -0
  88. package/coding/src/agent/process/sessions-teardown.ts +281 -0
  89. package/coding/src/agent/process/sessions.ts +30 -0
  90. package/coding/src/agent/process/types.ts +358 -0
  91. package/coding/src/agent/read-path-set.ts +126 -0
  92. package/coding/src/agent/read.ts +713 -0
  93. package/coding/src/agent/repository/glob.ts +147 -0
  94. package/coding/src/agent/repository/indexed-search.ts +480 -0
  95. package/coding/src/agent/repository/list.ts +131 -0
  96. package/coding/src/agent/repository/operations.ts +22 -0
  97. package/coding/src/agent/repository/path.ts +98 -0
  98. package/coding/src/agent/repository/search.ts +277 -0
  99. package/coding/src/agent/repository/types.ts +243 -0
  100. package/coding/src/agent/repository/walk.ts +126 -0
  101. package/coding/src/agent/repository.ts +12 -0
  102. package/coding/src/agent/shell-background.ts +264 -0
  103. package/coding/src/agent/shell-output.ts +75 -0
  104. package/coding/src/agent/shell.ts +526 -0
  105. package/coding/src/agent/todo.ts +399 -0
  106. package/coding/src/agent/truncate.ts +251 -0
  107. package/coding/src/agent/write.ts +170 -0
  108. package/coding/src/ask-user-host.ts +48 -0
  109. package/coding/src/config.ts +278 -0
  110. package/coding/src/hard-deny.ts +97 -0
  111. package/coding/src/index.ts +318 -0
  112. package/coding/src/principal-guard.ts +84 -0
  113. package/coding/src/sandbox.ts +92 -0
  114. package/coding/src/security/approval.ts +144 -0
  115. package/coding/src/security/command-rules.ts +57 -0
  116. package/coding/src/security/docker-cli.ts +222 -0
  117. package/coding/src/security/docker-sandbox.ts +1205 -0
  118. package/coding/src/security/e2b-sandbox.ts +853 -0
  119. package/coding/src/security/egress/dns-pin.ts +88 -0
  120. package/coding/src/security/egress/index.ts +32 -0
  121. package/coding/src/security/egress/limits.ts +57 -0
  122. package/coding/src/security/egress/policy.ts +137 -0
  123. package/coding/src/security/egress/proxy.ts +599 -0
  124. package/coding/src/security/egress/types.ts +30 -0
  125. package/coding/src/security/index.ts +199 -0
  126. package/coding/src/security/native-sandbox.ts +639 -0
  127. package/coding/src/security/path-containment.ts +55 -0
  128. package/coding/src/security/sandbox-coding-operations.ts +392 -0
  129. package/coding/src/security/sandbox-fs-operations.ts +644 -0
  130. package/coding/src/security/sandbox-limits.ts +147 -0
  131. package/coding/src/security/sandbox-tar.ts +308 -0
  132. package/coding/src/security/sandbox.ts +179 -0
  133. package/coding/src/security/semaphore.ts +55 -0
  134. package/coding/src/targets.ts +110 -0
  135. package/coding/src/workspace/diff.ts +78 -0
  136. package/coding/src/workspace/errors.ts +29 -0
  137. package/coding/src/workspace/index.ts +5 -0
  138. package/coding/src/workspace/merge.ts +151 -0
  139. package/coding/src/workspace/routing.ts +76 -0
  140. package/coding/src/workspace/service.ts +584 -0
  141. package/computer-use/README.md +105 -0
  142. package/computer-use/skills/computer-use-linux/SKILL.md +17 -0
  143. package/computer-use/src/classify.ts +53 -0
  144. package/computer-use/src/command.ts +29 -0
  145. package/computer-use/src/config.ts +69 -0
  146. package/computer-use/src/create.ts +170 -0
  147. package/computer-use/src/devices.ts +247 -0
  148. package/computer-use/src/env.ts +28 -0
  149. package/computer-use/src/index.ts +157 -0
  150. package/computer-use/src/skill.ts +21 -0
  151. package/openapi/README.md +97 -0
  152. package/openapi/src/compile.ts +386 -0
  153. package/openapi/src/config.ts +73 -0
  154. package/openapi/src/errors.ts +16 -0
  155. package/openapi/src/execute.ts +241 -0
  156. package/openapi/src/index.ts +124 -0
  157. package/openapi/src/limits.ts +63 -0
  158. package/openapi/src/specs.ts +86 -0
  159. package/openapi/src/tools.ts +60 -0
  160. package/openapi/src/types.ts +61 -0
  161. package/package.json +171 -4
  162. package/research/README.md +72 -0
  163. package/research/skills/research-report/SKILL.md +12 -0
  164. package/research/skills/research-scoping/SKILL.md +10 -0
  165. package/research/skills/research-synthesis/SKILL.md +10 -0
  166. package/research/skills/source-triage/SKILL.md +10 -0
  167. package/research/src/check.ts +71 -0
  168. package/research/src/config.ts +41 -0
  169. package/research/src/index.ts +147 -0
  170. package/research/src/ledger.ts +157 -0
  171. package/research/src/urls.ts +21 -0
  172. package/web/README.md +287 -0
  173. package/web/skills/web-research/SKILL.md +17 -0
  174. package/web/src/brave.ts +66 -0
  175. package/web/src/cache.ts +151 -0
  176. package/web/src/config.ts +210 -0
  177. package/web/src/controls.ts +128 -0
  178. package/web/src/domains.ts +114 -0
  179. package/web/src/exa.ts +60 -0
  180. package/web/src/firecrawl.ts +160 -0
  181. package/web/src/index.ts +387 -0
  182. package/web/src/kagi.ts +73 -0
  183. package/web/src/limits.ts +55 -0
  184. package/web/src/native/convert.ts +84 -0
  185. package/web/src/native/fetch.ts +226 -0
  186. package/web/src/normalize.ts +131 -0
  187. package/web/src/obscura/cdp.ts +201 -0
  188. package/web/src/obscura/classify.ts +35 -0
  189. package/web/src/obscura/cli.ts +254 -0
  190. package/web/src/obscura/errors.ts +8 -0
  191. package/web/src/obscura/index.ts +59 -0
  192. package/web/src/obscura/limits.ts +92 -0
  193. package/web/src/obscura/mcp.ts +104 -0
  194. package/web/src/obscura/playwright.ts +133 -0
  195. package/web/src/obscura/process.ts +235 -0
  196. package/web/src/obscura/search-profile.ts +73 -0
  197. package/web/src/obscura/types.ts +48 -0
  198. package/web/src/obscura/web.ts +419 -0
  199. package/web/src/prompt-fetch.ts +88 -0
  200. package/web/src/render.ts +75 -0
  201. package/web/src/searxng.ts +78 -0
  202. package/web/src/slice.ts +139 -0
  203. package/web/src/tavily.ts +77 -0
  204. package/web/src/tools.ts +182 -0
  205. package/web/src/transport.ts +310 -0
  206. package/web/src/types.ts +199 -0
  207. package/work/README.md +77 -0
  208. package/work/THIRD_PARTY_NOTICES +24 -0
  209. package/work/docling/ocr.py +150 -0
  210. package/work/sandbox/Dockerfile +36 -0
  211. package/work/sandbox/soffice.sh +12 -0
  212. package/work/src/command.ts +29 -0
  213. package/work/src/config.ts +138 -0
  214. package/work/src/connectors/cli.ts +343 -0
  215. package/work/src/connectors/drafts.ts +647 -0
  216. package/work/src/connectors/errors.ts +8 -0
  217. package/work/src/connectors/file-bytes.ts +149 -0
  218. package/work/src/connectors/google-workspace-http.ts +399 -0
  219. package/work/src/connectors/google-workspace.ts +465 -0
  220. package/work/src/connectors/gws-tools.ts +378 -0
  221. package/work/src/connectors/http.ts +123 -0
  222. package/work/src/connectors/idempotency.ts +235 -0
  223. package/work/src/connectors/identity.ts +34 -0
  224. package/work/src/connectors/index.ts +93 -0
  225. package/work/src/connectors/limits.ts +50 -0
  226. package/work/src/connectors/m365-tools.ts +308 -0
  227. package/work/src/connectors/microsoft365-http.ts +428 -0
  228. package/work/src/connectors/microsoft365.ts +402 -0
  229. package/work/src/connectors/normalize.ts +120 -0
  230. package/work/src/connectors/tool-helpers.ts +398 -0
  231. package/work/src/connectors/tools.ts +42 -0
  232. package/work/src/connectors/types.ts +399 -0
  233. package/work/src/diagrams/canonicalize.ts +166 -0
  234. package/work/src/diagrams/caps.ts +52 -0
  235. package/work/src/diagrams/embed.ts +340 -0
  236. package/work/src/diagrams/errors.ts +76 -0
  237. package/work/src/diagrams/index.ts +81 -0
  238. package/work/src/diagrams/messages.ts +150 -0
  239. package/work/src/diagrams/origin.ts +52 -0
  240. package/work/src/diagrams/telemetry.ts +34 -0
  241. package/work/src/diagrams/xml.ts +267 -0
  242. package/work/src/document-extraction/index.ts +563 -0
  243. package/work/src/document-reader/ambient.d.ts +2 -0
  244. package/work/src/document-reader/errors.ts +7 -0
  245. package/work/src/document-reader/index.ts +181 -0
  246. package/work/src/document-reader/mistral-ocr.ts +318 -0
  247. package/work/src/document-reader/parsers.ts +171 -0
  248. package/work/src/documents/caps.ts +135 -0
  249. package/work/src/documents/diff.ts +269 -0
  250. package/work/src/documents/errors.ts +44 -0
  251. package/work/src/documents/fidelity.ts +98 -0
  252. package/work/src/documents/generate.ts +96 -0
  253. package/work/src/documents/hash.ts +8 -0
  254. package/work/src/documents/index.ts +14 -0
  255. package/work/src/documents/model-schema.ts +639 -0
  256. package/work/src/documents/parse.ts +252 -0
  257. package/work/src/documents/patch-history.ts +81 -0
  258. package/work/src/documents/patch.ts +476 -0
  259. package/work/src/documents/preview-html.ts +245 -0
  260. package/work/src/documents/preview.ts +227 -0
  261. package/work/src/documents/telemetry.ts +36 -0
  262. package/work/src/documents/translate/docx.ts +292 -0
  263. package/work/src/documents/translate/pptx.ts +304 -0
  264. package/work/src/documents/translate/xlsx.ts +137 -0
  265. package/work/src/documents/types.ts +188 -0
  266. package/work/src/index.ts +311 -0
  267. package/work/src/runtime/worker-pool-entry.ts +102 -0
  268. package/work/src/runtime/worker-pool.ts +267 -0
  269. package/work/src/sandbox/composition.ts +212 -0
  270. package/work/src/sandbox/image.ts +6 -0
  271. package/work/src/sandbox/index.ts +14 -0
  272. package/work/src/sheets/caps.ts +68 -0
  273. package/work/src/sheets/csv.ts +351 -0
  274. package/work/src/sheets/decimal.ts +388 -0
  275. package/work/src/sheets/errors.ts +52 -0
  276. package/work/src/sheets/index.ts +62 -0
  277. package/work/src/sheets/inference.ts +378 -0
  278. package/work/src/sheets/telemetry.ts +34 -0
  279. package/work/src/sheets/types.ts +162 -0
  280. package/work/src/sheets/xlsx.ts +388 -0
  281. package/work/src/skills/index.ts +8 -0
  282. package/work/src/skills/load.ts +43 -0
  283. package/work/src/tools/filesystem.ts +21 -0
  284. package/work/src/tools/index.ts +1 -0
  285. package/work/src/tools/local-filesystem.ts +33 -0
  286. package/work/src/tools/office.ts +311 -0
  287. package/work/vendor/hermes-agent/LICENSE +21 -0
  288. package/work/vendor/hermes-agent/skills/productivity/docx/LICENSE +21 -0
  289. package/work/vendor/hermes-agent/skills/productivity/docx/SKILL.md +196 -0
  290. package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_comments.py +289 -0
  291. package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_common.py +94 -0
  292. package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_create.py +177 -0
  293. package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_edit.py +250 -0
  294. package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_read.py +149 -0
  295. package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_revisions.py +147 -0
  296. package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_template.py +70 -0
  297. package/work/vendor/hermes-agent/skills/productivity/docx/scripts/docx_validate.py +156 -0
  298. package/work/vendor/hermes-agent/skills/productivity/pdf/LICENSE +21 -0
  299. package/work/vendor/hermes-agent/skills/productivity/pdf/SKILL.md +125 -0
  300. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/_raster.py +76 -0
  301. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/extract_marker.py +88 -0
  302. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/extract_pymupdf.py +101 -0
  303. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_create.py +130 -0
  304. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_fill_form.py +97 -0
  305. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_form_layout.py +168 -0
  306. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_make_form.py +145 -0
  307. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_merge.py +50 -0
  308. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_meta.py +115 -0
  309. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_page_image.py +99 -0
  310. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_read.py +153 -0
  311. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_secure.py +71 -0
  312. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_split.py +84 -0
  313. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_stamp.py +143 -0
  314. package/work/vendor/hermes-agent/skills/productivity/pdf/scripts/pdf_watermark.py +51 -0
  315. package/work/vendor/hermes-agent/skills/productivity/powerpoint/LICENSE +21 -0
  316. package/work/vendor/hermes-agent/skills/productivity/powerpoint/SKILL.md +220 -0
  317. package/work/vendor/hermes-agent/skills/productivity/powerpoint/scripts/pptx_create.py +214 -0
  318. package/work/vendor/hermes-agent/skills/productivity/powerpoint/scripts/pptx_edit.py +436 -0
  319. package/work/vendor/hermes-agent/skills/productivity/powerpoint/scripts/pptx_from_template.py +88 -0
  320. package/work/vendor/hermes-agent/skills/productivity/powerpoint/scripts/pptx_read.py +131 -0
  321. package/work/vendor/hermes-agent/skills/productivity/powerpoint/scripts/pptx_render.py +93 -0
  322. package/work/vendor/hermes-agent/skills/productivity/xlsx/LICENSE +21 -0
  323. package/work/vendor/hermes-agent/skills/productivity/xlsx/SKILL.md +196 -0
  324. package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/csv_to_xlsx.py +104 -0
  325. package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_create.py +259 -0
  326. package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_edit.py +263 -0
  327. package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_read.py +160 -0
  328. package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_recalc.py +111 -0
  329. package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_restructure.py +337 -0
  330. package/work/vendor/hermes-agent/skills/productivity/xlsx/scripts/xlsx_to_csv.py +69 -0
  331. package/work/vendor-lock.json +11 -0
@@ -0,0 +1,326 @@
1
+ /**
2
+ * Typed Git executable runner.
3
+ *
4
+ * Always uses argument arrays (never a shell), a noninteractive pager-safe
5
+ * environment, and finite stdout/stderr retention. Hosts may inject a custom
6
+ * runner (for example a sandbox `execFile` adapter) without changing tool code.
7
+ */
8
+ import { type ChildProcessWithoutNullStreams, spawn } from "node:child_process";
9
+ import { constants as fsConstants } from "node:fs";
10
+ import { access } from "node:fs/promises";
11
+ import { isAbsolute } from "node:path";
12
+ import {
13
+ DEFAULT_GIT_TIMEOUT_MS,
14
+ DEFAULT_MAX_GIT_OUTPUT_BYTES,
15
+ HARD_GIT_TIMEOUT_MS,
16
+ HARD_MAX_GIT_OUTPUT_BYTES,
17
+ validateCodingLimit,
18
+ } from "./limits.js";
19
+
20
+ export class GitError extends Error {
21
+ readonly code = "ERR_PRISM_GIT";
22
+ readonly exitCode: number | null;
23
+ constructor(message: string, exitCode: number | null = null) {
24
+ super(message);
25
+ this.name = "GitError";
26
+ this.exitCode = exitCode;
27
+ }
28
+ }
29
+
30
+ export interface GitExecRequest {
31
+ readonly args: readonly string[];
32
+ readonly cwd: string;
33
+ readonly env?: Readonly<Record<string, string>>;
34
+ readonly stdin?: Buffer | string;
35
+ readonly signal?: AbortSignal;
36
+ readonly timeoutMs?: number;
37
+ readonly maxOutputBytes?: number;
38
+ }
39
+
40
+ export interface GitExecResult {
41
+ readonly exitCode: number | null;
42
+ readonly stdout: Buffer;
43
+ readonly stderr: Buffer;
44
+ readonly timedOut: boolean;
45
+ readonly aborted: boolean;
46
+ readonly outputBytes: number;
47
+ }
48
+
49
+ export type GitRunner = (request: GitExecRequest & { gitPath: string }) => Promise<GitExecResult>;
50
+
51
+ /** Noninteractive, pager-safe, credential-prompt-free baseline for Git child processes. */
52
+ export const SAFE_GIT_ENV: Readonly<Record<string, string>> = Object.freeze({
53
+ PATH: "/usr/bin:/bin",
54
+ LANG: "C",
55
+ LC_ALL: "C",
56
+ GIT_TERMINAL_PROMPT: "0",
57
+ GIT_OPTIONAL_LOCKS: "0",
58
+ GIT_PAGER: "cat",
59
+ PAGER: "cat",
60
+ GCM_INTERACTIVE: "never",
61
+ GIT_CONFIG_NOSYSTEM: "1",
62
+ });
63
+
64
+ /** Config flags prepended to every git invocation to disable hooks/external helpers. */
65
+ export const SAFE_GIT_CONFIG_ARGS = Object.freeze([
66
+ "-c",
67
+ "core.hooksPath=/dev/null",
68
+ "-c",
69
+ "core.pager=cat",
70
+ "-c",
71
+ "sequence.editor=true",
72
+ "-c",
73
+ "credential.helper=",
74
+ "-c",
75
+ "advice.detachedHead=false",
76
+ ] as const);
77
+
78
+ export async function assertAbsoluteGit(path: string): Promise<string> {
79
+ if (!isAbsolute(path)) {
80
+ throw new GitError("gitPath must be an absolute executable path");
81
+ }
82
+ try {
83
+ await access(path, fsConstants.X_OK);
84
+ } catch {
85
+ throw new GitError(`git executable is missing or not executable: ${path}`);
86
+ }
87
+ return path;
88
+ }
89
+
90
+ function mergeEnv(extra?: Readonly<Record<string, string>>): Record<string, string> {
91
+ return { ...SAFE_GIT_ENV, ...(extra ?? {}) };
92
+ }
93
+
94
+ /** Local spawn-based Git runner. Never invokes a shell. */
95
+ export async function runGitCli(request: GitExecRequest & { gitPath: string }): Promise<GitExecResult> {
96
+ if (request.signal?.aborted) {
97
+ throw new GitError("Git operation aborted before start");
98
+ }
99
+ for (const arg of request.args) {
100
+ if (typeof arg !== "string" || arg.includes("\0")) {
101
+ throw new GitError("Git args must be strings without NUL");
102
+ }
103
+ }
104
+
105
+ const maxOutputBytes = validateCodingLimit(
106
+ "maxOutputBytes",
107
+ request.maxOutputBytes ?? DEFAULT_MAX_GIT_OUTPUT_BYTES,
108
+ HARD_MAX_GIT_OUTPUT_BYTES,
109
+ );
110
+ const timeoutMs = validateCodingLimit("timeoutMs", request.timeoutMs ?? DEFAULT_GIT_TIMEOUT_MS, HARD_GIT_TIMEOUT_MS);
111
+
112
+ return await new Promise<GitExecResult>((resolve, reject) => {
113
+ let settled = false;
114
+ let timedOut = false;
115
+ let aborted = false;
116
+ let outputBytes = 0;
117
+ const stdoutChunks: Buffer[] = [];
118
+ const stderrChunks: Buffer[] = [];
119
+ let child: ChildProcessWithoutNullStreams;
120
+ let timer: NodeJS.Timeout | undefined;
121
+
122
+ const cleanup = () => {
123
+ if (timer) clearTimeout(timer);
124
+ request.signal?.removeEventListener("abort", onAbort);
125
+ };
126
+
127
+ const killTree = () => {
128
+ try {
129
+ if (child.pid) process.kill(-child.pid, "SIGKILL");
130
+ } catch {
131
+ try {
132
+ child.kill("SIGKILL");
133
+ } catch {
134
+ /* ignore */
135
+ }
136
+ }
137
+ };
138
+
139
+ const finalize = (exitCode: number | null) => {
140
+ if (settled) return;
141
+ settled = true;
142
+ cleanup();
143
+ resolve({
144
+ exitCode,
145
+ stdout: Buffer.concat(stdoutChunks),
146
+ stderr: Buffer.concat(stderrChunks),
147
+ timedOut,
148
+ aborted,
149
+ outputBytes,
150
+ });
151
+ };
152
+
153
+ const fail = (error: Error) => {
154
+ if (settled) return;
155
+ settled = true;
156
+ cleanup();
157
+ reject(error);
158
+ };
159
+
160
+ const onAbort = () => {
161
+ aborted = true;
162
+ killTree();
163
+ };
164
+
165
+ try {
166
+ child = spawn(request.gitPath, [...SAFE_GIT_CONFIG_ARGS, ...request.args], {
167
+ cwd: request.cwd,
168
+ env: mergeEnv(request.env),
169
+ stdio: ["pipe", "pipe", "pipe"],
170
+ detached: process.platform !== "win32",
171
+ windowsHide: true,
172
+ }) as ChildProcessWithoutNullStreams;
173
+ } catch (error) {
174
+ const message = error instanceof Error ? error.message : String(error);
175
+ reject(new GitError(message));
176
+ return;
177
+ }
178
+
179
+ const track = (chunk: Buffer, target: Buffer[]) => {
180
+ outputBytes += chunk.length;
181
+ if (outputBytes > maxOutputBytes) {
182
+ killTree();
183
+ fail(new GitError(`Git output exceeded ${maxOutputBytes} byte limit`));
184
+ return;
185
+ }
186
+ target.push(chunk);
187
+ };
188
+
189
+ child.stdout.on("data", (chunk: Buffer) => track(chunk, stdoutChunks));
190
+ child.stderr.on("data", (chunk: Buffer) => track(chunk, stderrChunks));
191
+ child.on("error", (error) => fail(new GitError(error.message)));
192
+ child.on("close", (code) => finalize(code));
193
+
194
+ if (request.stdin !== undefined) {
195
+ const payload = typeof request.stdin === "string" ? Buffer.from(request.stdin) : request.stdin;
196
+ child.stdin.end(payload);
197
+ } else {
198
+ child.stdin.end();
199
+ }
200
+
201
+ timer = setTimeout(() => {
202
+ timedOut = true;
203
+ killTree();
204
+ }, timeoutMs);
205
+
206
+ request.signal?.addEventListener("abort", onAbort, { once: true });
207
+ });
208
+ }
209
+
210
+ export interface CreateGitRunnerOptions {
211
+ readonly gitPath?: string;
212
+ readonly runner?: GitRunner;
213
+ /** Optional sandbox-style execFile adapter. Collects streamed onData into stdout. */
214
+ readonly execFile?: (request: {
215
+ file: string;
216
+ args: readonly string[];
217
+ cwd?: string;
218
+ env?: Readonly<Record<string, string>>;
219
+ onData?: (data: Buffer) => void;
220
+ signal?: AbortSignal;
221
+ timeout?: number;
222
+ }) => Promise<{ exitCode: number | null }>;
223
+ readonly maxOutputBytes?: number;
224
+ readonly timeoutMs?: number;
225
+ }
226
+
227
+ export interface BoundGitRunner {
228
+ readonly gitPath: string;
229
+ exec(request: GitExecRequest): Promise<GitExecResult>;
230
+ }
231
+
232
+ /**
233
+ * Resolve a bound Git runner from an absolute git path, custom runner, or sandbox execFile.
234
+ */
235
+ export async function createBoundGitRunner(options?: CreateGitRunnerOptions): Promise<BoundGitRunner> {
236
+ const gitPath = await assertAbsoluteGit(options?.gitPath ?? "/usr/bin/git");
237
+ const maxOutputBytes = options?.maxOutputBytes;
238
+ const timeoutMs = options?.timeoutMs;
239
+
240
+ if (options?.runner) {
241
+ const custom = options.runner;
242
+ return {
243
+ gitPath,
244
+ exec: (request) =>
245
+ custom({
246
+ ...request,
247
+ gitPath,
248
+ maxOutputBytes: request.maxOutputBytes ?? maxOutputBytes,
249
+ timeoutMs: request.timeoutMs ?? timeoutMs,
250
+ }),
251
+ };
252
+ }
253
+
254
+ if (options?.execFile) {
255
+ const execFile = options.execFile;
256
+ return {
257
+ gitPath,
258
+ exec: async (request) => {
259
+ if (request.signal?.aborted) throw new GitError("Git operation aborted before start");
260
+ const chunks: Buffer[] = [];
261
+ let outputBytes = 0;
262
+ const limit = validateCodingLimit(
263
+ "maxOutputBytes",
264
+ request.maxOutputBytes ?? maxOutputBytes ?? DEFAULT_MAX_GIT_OUTPUT_BYTES,
265
+ HARD_MAX_GIT_OUTPUT_BYTES,
266
+ );
267
+ const args = [...SAFE_GIT_CONFIG_ARGS, ...request.args];
268
+ try {
269
+ const { exitCode } = await execFile({
270
+ file: gitPath,
271
+ args,
272
+ cwd: request.cwd,
273
+ env: mergeEnv(request.env),
274
+ signal: request.signal,
275
+ timeout: request.timeoutMs ?? timeoutMs ?? DEFAULT_GIT_TIMEOUT_MS,
276
+ onData: (data) => {
277
+ outputBytes += data.length;
278
+ if (outputBytes > limit) {
279
+ throw new GitError(`Git output exceeded ${limit} byte limit`);
280
+ }
281
+ chunks.push(data);
282
+ },
283
+ });
284
+ return {
285
+ exitCode,
286
+ stdout: Buffer.concat(chunks),
287
+ stderr: Buffer.alloc(0),
288
+ timedOut: false,
289
+ aborted: false,
290
+ outputBytes,
291
+ };
292
+ } catch (error) {
293
+ if (error instanceof GitError) throw error;
294
+ const message = error instanceof Error ? error.message : String(error);
295
+ throw new GitError(message);
296
+ }
297
+ },
298
+ };
299
+ }
300
+
301
+ return {
302
+ gitPath,
303
+ exec: (request) =>
304
+ runGitCli({
305
+ ...request,
306
+ gitPath,
307
+ maxOutputBytes: request.maxOutputBytes ?? maxOutputBytes,
308
+ timeoutMs: request.timeoutMs ?? timeoutMs,
309
+ }),
310
+ };
311
+ }
312
+
313
+ export function gitText(result: GitExecResult, stream: "stdout" | "stderr" = "stdout"): string {
314
+ return (stream === "stdout" ? result.stdout : result.stderr).toString("utf8");
315
+ }
316
+
317
+ export async function gitRequireOk(runner: BoundGitRunner, request: GitExecRequest, label: string): Promise<GitExecResult> {
318
+ const result = await runner.exec(request);
319
+ if (result.timedOut) throw new GitError(`${label} timed out`, result.exitCode);
320
+ if (result.aborted) throw new GitError(`${label} aborted`, result.exitCode);
321
+ if (result.exitCode !== 0) {
322
+ const err = gitText(result, "stderr").trim() || gitText(result).trim() || `exit ${result.exitCode}`;
323
+ throw new GitError(`${label} failed: ${err}`, result.exitCode);
324
+ }
325
+ return result;
326
+ }
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Parse `git status --porcelain=v2 -z --branch` output into structured records.
3
+ *
4
+ * Paths remain repository-relative as emitted by Git. Leading-dash and control
5
+ * characters are preserved as data; callers must pass paths after `--`.
6
+ */
7
+ import { GitError } from "./git-exec.js";
8
+
9
+ export type GitStatusEntryKind = "ordinary" | "rename" | "copy" | "unmerged" | "untracked" | "ignored";
10
+
11
+ export interface GitStatusBranch {
12
+ readonly oid: string | null;
13
+ readonly head: string | null;
14
+ readonly detached: boolean;
15
+ readonly upstream: string | null;
16
+ readonly ahead: number | null;
17
+ readonly behind: number | null;
18
+ readonly initial: boolean;
19
+ }
20
+
21
+ export interface GitStatusEntry {
22
+ readonly kind: GitStatusEntryKind;
23
+ readonly xy: string;
24
+ readonly path: string;
25
+ readonly origPath?: string;
26
+ readonly score?: string;
27
+ }
28
+
29
+ export interface GitStatusResult {
30
+ readonly branch: GitStatusBranch;
31
+ readonly entries: readonly GitStatusEntry[];
32
+ readonly dirty: boolean;
33
+ readonly truncated: boolean;
34
+ }
35
+
36
+ function splitNulRecords(buffer: Buffer): string[] {
37
+ const text = buffer.toString("utf8");
38
+ if (text.length === 0) return [];
39
+ const parts = text.split("\0");
40
+ if (parts.length > 0 && parts[parts.length - 1] === "") parts.pop();
41
+ return parts;
42
+ }
43
+
44
+ function parseAheadBehind(token: string | undefined): { ahead: number | null; behind: number | null } {
45
+ if (!token) return { ahead: null, behind: null };
46
+ // Format: +<ahead> -<behind>
47
+ const match = /^\+(\d+) -(\d+)$/.exec(token);
48
+ if (!match) return { ahead: null, behind: null };
49
+ return { ahead: Number(match[1]), behind: Number(match[2]) };
50
+ }
51
+
52
+ /**
53
+ * Parse porcelain v2 NUL-delimited status. `maxEntries` truncates retained
54
+ * entries without failing; callers surface truncation in tool metadata.
55
+ */
56
+ export function parsePorcelainV2(stdout: Buffer, options?: { maxEntries?: number }): GitStatusResult {
57
+ const records = splitNulRecords(stdout);
58
+ const branch: {
59
+ oid: string | null;
60
+ head: string | null;
61
+ detached: boolean;
62
+ upstream: string | null;
63
+ ahead: number | null;
64
+ behind: number | null;
65
+ initial: boolean;
66
+ } = {
67
+ oid: null,
68
+ head: null,
69
+ detached: false,
70
+ upstream: null,
71
+ ahead: null,
72
+ behind: null,
73
+ initial: false,
74
+ };
75
+
76
+ const entries: GitStatusEntry[] = [];
77
+ const maxEntries = options?.maxEntries;
78
+ let truncated = false;
79
+ let i = 0;
80
+
81
+ while (i < records.length) {
82
+ const record = records[i];
83
+ i++;
84
+
85
+ if (record.startsWith("# ")) {
86
+ const body = record.slice(2);
87
+ if (body.startsWith("branch.oid ")) {
88
+ const oid = body.slice("branch.oid ".length);
89
+ if (oid === "(initial)") {
90
+ branch.initial = true;
91
+ branch.oid = null;
92
+ } else {
93
+ branch.oid = oid;
94
+ }
95
+ } else if (body.startsWith("branch.head ")) {
96
+ const head = body.slice("branch.head ".length);
97
+ if (head === "(detached)") {
98
+ branch.detached = true;
99
+ branch.head = null;
100
+ } else {
101
+ branch.head = head;
102
+ }
103
+ } else if (body.startsWith("branch.upstream ")) {
104
+ branch.upstream = body.slice("branch.upstream ".length);
105
+ } else if (body.startsWith("branch.ab ")) {
106
+ const ab = parseAheadBehind(body.slice("branch.ab ".length));
107
+ branch.ahead = ab.ahead;
108
+ branch.behind = ab.behind;
109
+ }
110
+ continue;
111
+ }
112
+
113
+ if (maxEntries !== undefined && entries.length >= maxEntries) {
114
+ truncated = true;
115
+ continue;
116
+ }
117
+
118
+ if (record.startsWith("? ")) {
119
+ entries.push({ kind: "untracked", xy: "??", path: record.slice(2) });
120
+ continue;
121
+ }
122
+ if (record.startsWith("! ")) {
123
+ entries.push({ kind: "ignored", xy: "!!", path: record.slice(2) });
124
+ continue;
125
+ }
126
+ if (record.startsWith("1 ")) {
127
+ // 1 <XY> <sub> <mH> <mI> <mW> <hH> <hI> <path>
128
+ const parts = record.split(" ");
129
+ if (parts.length < 9) throw new GitError(`malformed ordinary status record: ${record}`);
130
+ const xy = parts[1];
131
+ const path = parts.slice(8).join(" ");
132
+ entries.push({ kind: "ordinary", xy, path });
133
+ continue;
134
+ }
135
+ if (record.startsWith("2 ")) {
136
+ // 2 <XY> <sub> <mH> <mI> <mW> <hH> <hI> <X><score> <path>\0<origPath>
137
+ const parts = record.split(" ");
138
+ if (parts.length < 10) throw new GitError(`malformed rename/copy status record: ${record}`);
139
+ const xy = parts[1];
140
+ const scoreToken = parts[8];
141
+ const path = parts.slice(9).join(" ");
142
+ const origPath = records[i];
143
+ if (origPath === undefined) throw new GitError("rename/copy status missing origPath");
144
+ i++;
145
+ const kind: GitStatusEntryKind = scoreToken.startsWith("C") ? "copy" : "rename";
146
+ entries.push({ kind, xy, path, origPath, score: scoreToken });
147
+ continue;
148
+ }
149
+ if (record.startsWith("u ")) {
150
+ // u <XY> <sub> <m1> <m2> <m3> <mW> <h1> <h2> <h3> <path>
151
+ const parts = record.split(" ");
152
+ if (parts.length < 11) throw new GitError(`malformed unmerged status record: ${record}`);
153
+ const xy = parts[1];
154
+ const path = parts.slice(10).join(" ");
155
+ entries.push({ kind: "unmerged", xy, path });
156
+ continue;
157
+ }
158
+
159
+ throw new GitError(`unrecognized porcelain v2 record: ${record.slice(0, 80)}`);
160
+ }
161
+
162
+ const dirty = entries.some((entry) => entry.kind !== "ignored");
163
+ return {
164
+ branch: {
165
+ oid: branch.oid,
166
+ head: branch.head,
167
+ detached: branch.detached,
168
+ upstream: branch.upstream,
169
+ ahead: branch.ahead,
170
+ behind: branch.behind,
171
+ initial: branch.initial,
172
+ },
173
+ entries,
174
+ dirty,
175
+ truncated,
176
+ };
177
+ }