@namzu/sdk 51.1.0 → 52.0.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 (323) hide show
  1. package/CHANGELOG.md +100 -0
  2. package/dist/agents/QueryAgent.d.ts.map +1 -1
  3. package/dist/agents/QueryAgent.js +2 -0
  4. package/dist/agents/QueryAgent.js.map +1 -1
  5. package/dist/agents/SupervisorAgent.d.ts +1 -1
  6. package/dist/agents/SupervisorAgent.d.ts.map +1 -1
  7. package/dist/agents/SupervisorAgent.js.map +1 -1
  8. package/dist/agents/examples/SupervisorAgent.d.ts +42 -0
  9. package/dist/agents/examples/SupervisorAgent.d.ts.map +1 -1
  10. package/dist/agents/examples/SupervisorAgent.js +80 -4
  11. package/dist/agents/examples/SupervisorAgent.js.map +1 -1
  12. package/dist/agents/index.d.ts +1 -1
  13. package/dist/agents/index.d.ts.map +1 -1
  14. package/dist/agents/index.js.map +1 -1
  15. package/dist/authorization/protected-paths.d.ts +36 -0
  16. package/dist/authorization/protected-paths.d.ts.map +1 -0
  17. package/dist/authorization/protected-paths.js +107 -0
  18. package/dist/authorization/protected-paths.js.map +1 -0
  19. package/dist/authorization/readonly-commands.d.ts +39 -0
  20. package/dist/authorization/readonly-commands.d.ts.map +1 -0
  21. package/dist/authorization/readonly-commands.js +797 -0
  22. package/dist/authorization/readonly-commands.js.map +1 -0
  23. package/dist/authorization/readonly-flags.d.ts +19 -0
  24. package/dist/authorization/readonly-flags.d.ts.map +1 -0
  25. package/dist/authorization/readonly-flags.js +178 -0
  26. package/dist/authorization/readonly-flags.js.map +1 -0
  27. package/dist/authorization/removal.d.ts +52 -0
  28. package/dist/authorization/removal.d.ts.map +1 -0
  29. package/dist/authorization/removal.js +283 -0
  30. package/dist/authorization/removal.js.map +1 -0
  31. package/dist/authorization/shell-paths.d.ts +66 -0
  32. package/dist/authorization/shell-paths.d.ts.map +1 -0
  33. package/dist/authorization/shell-paths.js +697 -0
  34. package/dist/authorization/shell-paths.js.map +1 -0
  35. package/dist/bridge/acp/update.d.ts.map +1 -1
  36. package/dist/bridge/acp/update.js +1 -0
  37. package/dist/bridge/acp/update.js.map +1 -1
  38. package/dist/compaction/context-window.d.ts +24 -0
  39. package/dist/compaction/context-window.d.ts.map +1 -1
  40. package/dist/compaction/context-window.js +34 -0
  41. package/dist/compaction/context-window.js.map +1 -1
  42. package/dist/compaction/manual.d.ts.map +1 -1
  43. package/dist/compaction/manual.js +10 -2
  44. package/dist/compaction/manual.js.map +1 -1
  45. package/dist/compaction/plan.d.ts +4 -0
  46. package/dist/compaction/plan.d.ts.map +1 -1
  47. package/dist/compaction/plan.js +6 -2
  48. package/dist/compaction/plan.js.map +1 -1
  49. package/dist/compaction/verifier.d.ts +11 -1
  50. package/dist/compaction/verifier.d.ts.map +1 -1
  51. package/dist/compaction/verifier.js +19 -7
  52. package/dist/compaction/verifier.js.map +1 -1
  53. package/dist/config/runtime.d.ts +55 -7
  54. package/dist/config/runtime.d.ts.map +1 -1
  55. package/dist/config/runtime.js +23 -2
  56. package/dist/config/runtime.js.map +1 -1
  57. package/dist/connector/web/guarded-fetch.d.ts.map +1 -1
  58. package/dist/connector/web/guarded-fetch.js +24 -0
  59. package/dist/connector/web/guarded-fetch.js.map +1 -1
  60. package/dist/connector/web/index.d.ts +1 -1
  61. package/dist/connector/web/index.d.ts.map +1 -1
  62. package/dist/connector/web/page.d.ts +59 -0
  63. package/dist/connector/web/page.d.ts.map +1 -0
  64. package/dist/connector/web/page.js +112 -0
  65. package/dist/connector/web/page.js.map +1 -0
  66. package/dist/connector/web/types.d.ts +47 -0
  67. package/dist/connector/web/types.d.ts.map +1 -1
  68. package/dist/connector/web/types.js.map +1 -1
  69. package/dist/constants/continuation.d.ts +8 -0
  70. package/dist/constants/continuation.d.ts.map +1 -1
  71. package/dist/constants/continuation.js +8 -0
  72. package/dist/constants/continuation.js.map +1 -1
  73. package/dist/constants/plugin/index.d.ts +1 -1
  74. package/dist/constants/plugin/index.d.ts.map +1 -1
  75. package/dist/constants/plugin/index.js +1 -1
  76. package/dist/constants/plugin/index.js.map +1 -1
  77. package/dist/manager/agent/lifecycle.d.ts +7 -0
  78. package/dist/manager/agent/lifecycle.d.ts.map +1 -1
  79. package/dist/manager/agent/lifecycle.js +11 -2
  80. package/dist/manager/agent/lifecycle.js.map +1 -1
  81. package/dist/manager/session/turn-recorder.d.ts.map +1 -1
  82. package/dist/manager/session/turn-recorder.js +4 -2
  83. package/dist/manager/session/turn-recorder.js.map +1 -1
  84. package/dist/plugin/index.d.ts +1 -1
  85. package/dist/plugin/index.d.ts.map +1 -1
  86. package/dist/plugin/index.js +1 -1
  87. package/dist/plugin/index.js.map +1 -1
  88. package/dist/plugin/lifecycle.d.ts +6 -0
  89. package/dist/plugin/lifecycle.d.ts.map +1 -1
  90. package/dist/plugin/lifecycle.js +98 -114
  91. package/dist/plugin/lifecycle.js.map +1 -1
  92. package/dist/plugin/shell-hook.d.ts +90 -22
  93. package/dist/plugin/shell-hook.d.ts.map +1 -1
  94. package/dist/plugin/shell-hook.js +434 -41
  95. package/dist/plugin/shell-hook.js.map +1 -1
  96. package/dist/public-runtime.d.ts +4 -1
  97. package/dist/public-runtime.d.ts.map +1 -1
  98. package/dist/public-runtime.js +4 -1
  99. package/dist/public-runtime.js.map +1 -1
  100. package/dist/public-types.d.ts +2 -2
  101. package/dist/public-types.d.ts.map +1 -1
  102. package/dist/runtime/jobs/registry.d.ts +21 -0
  103. package/dist/runtime/jobs/registry.d.ts.map +1 -1
  104. package/dist/runtime/jobs/registry.js +15 -2
  105. package/dist/runtime/jobs/registry.js.map +1 -1
  106. package/dist/runtime/query/executor/tool-call-admission.d.ts +10 -0
  107. package/dist/runtime/query/executor/tool-call-admission.d.ts.map +1 -1
  108. package/dist/runtime/query/executor/tool-call-admission.js +74 -13
  109. package/dist/runtime/query/executor/tool-call-admission.js.map +1 -1
  110. package/dist/runtime/query/executor.d.ts +41 -2
  111. package/dist/runtime/query/executor.d.ts.map +1 -1
  112. package/dist/runtime/query/executor.js +190 -6
  113. package/dist/runtime/query/executor.js.map +1 -1
  114. package/dist/runtime/query/finalize-turn.d.ts +6 -0
  115. package/dist/runtime/query/finalize-turn.d.ts.map +1 -1
  116. package/dist/runtime/query/finalize-turn.js +1 -1
  117. package/dist/runtime/query/finalize-turn.js.map +1 -1
  118. package/dist/runtime/query/index.d.ts +29 -0
  119. package/dist/runtime/query/index.d.ts.map +1 -1
  120. package/dist/runtime/query/index.js +15 -3
  121. package/dist/runtime/query/index.js.map +1 -1
  122. package/dist/runtime/query/iteration/index.d.ts +31 -0
  123. package/dist/runtime/query/iteration/index.d.ts.map +1 -1
  124. package/dist/runtime/query/iteration/index.js +216 -7
  125. package/dist/runtime/query/iteration/index.js.map +1 -1
  126. package/dist/runtime/query/iteration/phases/compaction-breaker.d.ts +58 -0
  127. package/dist/runtime/query/iteration/phases/compaction-breaker.d.ts.map +1 -0
  128. package/dist/runtime/query/iteration/phases/compaction-breaker.js +91 -0
  129. package/dist/runtime/query/iteration/phases/compaction-breaker.js.map +1 -0
  130. package/dist/runtime/query/iteration/phases/compaction.d.ts.map +1 -1
  131. package/dist/runtime/query/iteration/phases/compaction.js +89 -7
  132. package/dist/runtime/query/iteration/phases/compaction.js.map +1 -1
  133. package/dist/runtime/query/iteration/phases/context.d.ts +10 -0
  134. package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
  135. package/dist/runtime/query/iteration/phases/context.js.map +1 -1
  136. package/dist/runtime/query/iteration/phases/tool-review.d.ts.map +1 -1
  137. package/dist/runtime/query/iteration/phases/tool-review.js +165 -2
  138. package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
  139. package/dist/runtime/query/path-escalation.d.ts +36 -0
  140. package/dist/runtime/query/path-escalation.d.ts.map +1 -0
  141. package/dist/runtime/query/path-escalation.js +184 -0
  142. package/dist/runtime/query/path-escalation.js.map +1 -0
  143. package/dist/runtime/query/plugin-hooks.d.ts.map +1 -1
  144. package/dist/runtime/query/plugin-hooks.js +8 -0
  145. package/dist/runtime/query/plugin-hooks.js.map +1 -1
  146. package/dist/runtime/query/reminders.d.ts +35 -0
  147. package/dist/runtime/query/reminders.d.ts.map +1 -0
  148. package/dist/runtime/query/reminders.js +7 -0
  149. package/dist/runtime/query/reminders.js.map +1 -0
  150. package/dist/runtime/query/resume-pending.d.ts.map +1 -1
  151. package/dist/runtime/query/resume-pending.js +26 -0
  152. package/dist/runtime/query/resume-pending.js.map +1 -1
  153. package/dist/runtime/query/review-policy.d.ts +26 -0
  154. package/dist/runtime/query/review-policy.d.ts.map +1 -1
  155. package/dist/runtime/query/review-policy.js +79 -5
  156. package/dist/runtime/query/review-policy.js.map +1 -1
  157. package/dist/runtime/query/tool-output-budget.d.ts +46 -0
  158. package/dist/runtime/query/tool-output-budget.d.ts.map +1 -1
  159. package/dist/runtime/query/tool-output-budget.js +85 -1
  160. package/dist/runtime/query/tool-output-budget.js.map +1 -1
  161. package/dist/runtime/query/tooling.d.ts +1 -0
  162. package/dist/runtime/query/tooling.d.ts.map +1 -1
  163. package/dist/runtime/query/tooling.js +5 -0
  164. package/dist/runtime/query/tooling.js.map +1 -1
  165. package/dist/session/paths.d.ts +5 -1
  166. package/dist/session/paths.d.ts.map +1 -1
  167. package/dist/session/paths.js +8 -0
  168. package/dist/session/paths.js.map +1 -1
  169. package/dist/store/memory/disk.d.ts.map +1 -1
  170. package/dist/store/memory/disk.js +2 -2
  171. package/dist/store/memory/disk.js.map +1 -1
  172. package/dist/store/memory/markdown.d.ts.map +1 -1
  173. package/dist/store/memory/markdown.js +2 -2
  174. package/dist/store/memory/markdown.js.map +1 -1
  175. package/dist/store/memory/naming.d.ts +9 -0
  176. package/dist/store/memory/naming.d.ts.map +1 -1
  177. package/dist/store/memory/naming.js +12 -0
  178. package/dist/store/memory/naming.js.map +1 -1
  179. package/dist/tools/builtins/bash.d.ts +30 -1
  180. package/dist/tools/builtins/bash.d.ts.map +1 -1
  181. package/dist/tools/builtins/bash.js +170 -26
  182. package/dist/tools/builtins/bash.js.map +1 -1
  183. package/dist/tools/builtins/content-fingerprint.d.ts +2 -0
  184. package/dist/tools/builtins/content-fingerprint.d.ts.map +1 -1
  185. package/dist/tools/builtins/content-fingerprint.js +4 -0
  186. package/dist/tools/builtins/content-fingerprint.js.map +1 -1
  187. package/dist/tools/builtins/edit.d.ts.map +1 -1
  188. package/dist/tools/builtins/edit.js +60 -18
  189. package/dist/tools/builtins/edit.js.map +1 -1
  190. package/dist/tools/builtins/grep.d.ts +8 -1
  191. package/dist/tools/builtins/grep.d.ts.map +1 -1
  192. package/dist/tools/builtins/grep.js +152 -33
  193. package/dist/tools/builtins/grep.js.map +1 -1
  194. package/dist/tools/builtins/read-file.d.ts +1 -0
  195. package/dist/tools/builtins/read-file.d.ts.map +1 -1
  196. package/dist/tools/builtins/read-file.js +44 -5
  197. package/dist/tools/builtins/read-file.js.map +1 -1
  198. package/dist/tools/builtins/read-render.d.ts +52 -0
  199. package/dist/tools/builtins/read-render.d.ts.map +1 -1
  200. package/dist/tools/builtins/read-render.js +143 -0
  201. package/dist/tools/builtins/read-render.js.map +1 -1
  202. package/dist/tools/builtins/web.d.ts +13 -2
  203. package/dist/tools/builtins/web.d.ts.map +1 -1
  204. package/dist/tools/builtins/web.js +225 -32
  205. package/dist/tools/builtins/web.js.map +1 -1
  206. package/dist/tools/coordinator/ask-user-question.d.ts +2 -0
  207. package/dist/tools/coordinator/ask-user-question.d.ts.map +1 -1
  208. package/dist/tools/coordinator/ask-user-question.js +256 -92
  209. package/dist/tools/coordinator/ask-user-question.js.map +1 -1
  210. package/dist/tools/coordinator/question-options.d.ts +1 -0
  211. package/dist/tools/coordinator/question-options.d.ts.map +1 -1
  212. package/dist/tools/coordinator/question-options.js +6 -1
  213. package/dist/tools/coordinator/question-options.js.map +1 -1
  214. package/dist/tools/defineTool.d.ts +4 -1
  215. package/dist/tools/defineTool.d.ts.map +1 -1
  216. package/dist/tools/defineTool.js +6 -1
  217. package/dist/tools/defineTool.js.map +1 -1
  218. package/dist/tools/paths.d.ts +6 -0
  219. package/dist/tools/paths.d.ts.map +1 -1
  220. package/dist/tools/paths.js +1 -1
  221. package/dist/tools/paths.js.map +1 -1
  222. package/dist/types/agent/query.d.ts +3 -0
  223. package/dist/types/agent/query.d.ts.map +1 -1
  224. package/dist/types/errors/catalog.js +1 -1
  225. package/dist/types/errors/catalog.js.map +1 -1
  226. package/dist/types/hitl/index.d.ts +104 -0
  227. package/dist/types/hitl/index.d.ts.map +1 -1
  228. package/dist/types/hitl/index.js.map +1 -1
  229. package/dist/types/message/index.d.ts +1 -1
  230. package/dist/types/message/index.d.ts.map +1 -1
  231. package/dist/types/message/index.js +2 -0
  232. package/dist/types/message/index.js.map +1 -1
  233. package/dist/types/plugin/index.d.ts +93 -19
  234. package/dist/types/plugin/index.d.ts.map +1 -1
  235. package/dist/types/plugin/index.js +3 -0
  236. package/dist/types/plugin/index.js.map +1 -1
  237. package/dist/types/provider/errors.d.ts +19 -0
  238. package/dist/types/provider/errors.d.ts.map +1 -1
  239. package/dist/types/provider/errors.js +24 -0
  240. package/dist/types/provider/errors.js.map +1 -1
  241. package/dist/types/session/events.d.ts +10 -2
  242. package/dist/types/session/events.d.ts.map +1 -1
  243. package/dist/types/session/events.js.map +1 -1
  244. package/dist/types/session/records.d.ts +9 -9
  245. package/dist/types/session/records.d.ts.map +1 -1
  246. package/dist/types/session/records.js +1 -0
  247. package/dist/types/session/records.js.map +1 -1
  248. package/dist/types/session/stop-reason.d.ts +5 -0
  249. package/dist/types/session/stop-reason.d.ts.map +1 -1
  250. package/dist/types/tool/index.d.ts +52 -0
  251. package/dist/types/tool/index.d.ts.map +1 -1
  252. package/dist/types/tool/index.js.map +1 -1
  253. package/package.json +4 -2
  254. package/src/agents/QueryAgent.ts +5 -1
  255. package/src/agents/SupervisorAgent.ts +1 -1
  256. package/src/agents/examples/SupervisorAgent.ts +217 -106
  257. package/src/agents/index.ts +1 -1
  258. package/src/authorization/protected-paths.ts +110 -0
  259. package/src/authorization/readonly-commands.ts +771 -0
  260. package/src/authorization/readonly-flags.ts +196 -0
  261. package/src/authorization/removal.ts +326 -0
  262. package/src/authorization/shell-paths.ts +927 -0
  263. package/src/bridge/acp/update.ts +1 -0
  264. package/src/compaction/context-window.ts +41 -0
  265. package/src/compaction/manual.ts +10 -2
  266. package/src/compaction/plan.ts +11 -2
  267. package/src/compaction/verifier.ts +28 -3
  268. package/src/config/runtime.ts +23 -2
  269. package/src/connector/web/guarded-fetch.ts +26 -0
  270. package/src/connector/web/index.ts +3 -0
  271. package/src/connector/web/page.ts +130 -0
  272. package/src/connector/web/types.ts +47 -0
  273. package/src/constants/continuation.ts +10 -0
  274. package/src/constants/plugin/index.ts +1 -1
  275. package/src/manager/agent/lifecycle.ts +12 -4
  276. package/src/manager/session/turn-recorder.ts +10 -2
  277. package/src/plugin/index.ts +1 -0
  278. package/src/plugin/lifecycle.ts +107 -119
  279. package/src/plugin/shell-hook.ts +477 -43
  280. package/src/public-runtime.ts +8 -0
  281. package/src/public-types.ts +4 -0
  282. package/src/runtime/jobs/registry.ts +39 -2
  283. package/src/runtime/query/executor/tool-call-admission.ts +79 -13
  284. package/src/runtime/query/executor.ts +242 -8
  285. package/src/runtime/query/finalize-turn.ts +7 -1
  286. package/src/runtime/query/index.ts +45 -3
  287. package/src/runtime/query/iteration/index.ts +251 -12
  288. package/src/runtime/query/iteration/phases/compaction-breaker.ts +106 -0
  289. package/src/runtime/query/iteration/phases/compaction.ts +99 -12
  290. package/src/runtime/query/iteration/phases/context.ts +11 -0
  291. package/src/runtime/query/iteration/phases/tool-review.ts +177 -2
  292. package/src/runtime/query/path-escalation.ts +204 -0
  293. package/src/runtime/query/plugin-hooks.ts +8 -0
  294. package/src/runtime/query/reminders.ts +45 -0
  295. package/src/runtime/query/resume-pending.ts +27 -0
  296. package/src/runtime/query/review-policy.ts +106 -5
  297. package/src/runtime/query/tool-output-budget.ts +115 -1
  298. package/src/runtime/query/tooling.ts +6 -0
  299. package/src/session/paths.ts +11 -1
  300. package/src/store/memory/disk.ts +2 -1
  301. package/src/store/memory/markdown.ts +2 -1
  302. package/src/store/memory/naming.ts +13 -0
  303. package/src/tools/builtins/bash.ts +203 -34
  304. package/src/tools/builtins/content-fingerprint.ts +5 -0
  305. package/src/tools/builtins/edit.ts +69 -19
  306. package/src/tools/builtins/grep.ts +159 -38
  307. package/src/tools/builtins/read-file.ts +48 -5
  308. package/src/tools/builtins/read-render.ts +193 -0
  309. package/src/tools/builtins/web.ts +258 -33
  310. package/src/tools/coordinator/ask-user-question.ts +309 -103
  311. package/src/tools/coordinator/question-options.ts +8 -1
  312. package/src/tools/defineTool.ts +11 -2
  313. package/src/tools/paths.ts +1 -1
  314. package/src/types/agent/query.ts +3 -0
  315. package/src/types/errors/catalog.ts +1 -1
  316. package/src/types/hitl/index.ts +104 -0
  317. package/src/types/message/index.ts +2 -0
  318. package/src/types/plugin/index.ts +101 -18
  319. package/src/types/provider/errors.ts +33 -0
  320. package/src/types/session/events.ts +10 -2
  321. package/src/types/session/records.ts +1 -0
  322. package/src/types/session/stop-reason.ts +5 -0
  323. package/src/types/tool/index.ts +53 -2
@@ -0,0 +1,797 @@
1
+ /**
2
+ * Which shell lines only read, so that they run without a prompt.
3
+ *
4
+ * The reference terminal runs `ls`, `cat`, `git status` and `git diff` without
5
+ * asking, because each is on a vetted list and nothing in the line is decided
6
+ * at run time. This is that list and those conditions (version 2.1.302):
7
+ *
8
+ * - every command of the line is on the list, the line parses completely, and
9
+ * nothing in it is read at run time: no `$var` outside single quotes, no
10
+ * substitution, no subshell or group, no background `&`, no here-document,
11
+ * no unquoted glob except for a short set of commands that only list or show;
12
+ * - a redirection is allowed only to `/dev/null`, as a descriptor copy, or as
13
+ * an input from a plain file; a `~user` path and a UNC path are refused;
14
+ * - a leading assignment is allowed only for a variable that cannot change
15
+ * what a command does (`LANG`, `TZ`, `NO_COLOR`, ...);
16
+ * - `git` must match a vetted subcommand and every flag must be one the table
17
+ * names, with the kind of value it takes. `git diff --output=f`, `git -c`,
18
+ * `--exec-path` and `--config-env` are in no table, so they are refused;
19
+ * `git fetch` is deliberately absent. A `cd` in the same line as `git`
20
+ * refuses the line, because the directory it enters may hold a repository
21
+ * the person never looked at.
22
+ *
23
+ * Not ported, and so never read-only here: `sed`, `ps`, `lsof`, `ss`, `date`,
24
+ * `man`, `xargs`, `fd`, `gh`, `docker logs|inspect`, `printf`, `[[`, `test`
25
+ * and the shell builtins other than `cd`. The reference's check of the working
26
+ * directory for a planted bare repository needs the directory, which the tool
27
+ * predicate does not get; the trusted-project gate is what stands in for it.
28
+ *
29
+ * This decides only "does not need a prompt for being a command". The path
30
+ * floor still applies: a read-only command that reads outside the roots is
31
+ * asked about, and so is anything the protected-path and removal floors see.
32
+ */
33
+ import { FLAG_TABLES } from './readonly-flags.js';
34
+ import { lexShellCommandLine } from './shell-lexer.js';
35
+ /** Lines longer than this are not analysed (the reference's budget). */
36
+ const MAX_LINE_LENGTH = 20_000;
37
+ /** Variables whose value cannot change what a command does. */
38
+ const SAFE_ENVIRONMENT = new Set([
39
+ 'GOEXPERIMENT',
40
+ 'GOOS',
41
+ 'GOARCH',
42
+ 'CGO_ENABLED',
43
+ 'GO111MODULE',
44
+ 'RUST_BACKTRACE',
45
+ 'RUST_LOG',
46
+ 'NODE_ENV',
47
+ 'PYTHONUNBUFFERED',
48
+ 'PYTHONDONTWRITEBYTECODE',
49
+ 'PYTEST_DISABLE_PLUGIN_AUTOLOAD',
50
+ 'PYTEST_DEBUG',
51
+ 'LANG',
52
+ 'LANGUAGE',
53
+ 'LC_ALL',
54
+ 'LC_CTYPE',
55
+ 'LC_TIME',
56
+ 'CHARSET',
57
+ 'TERM',
58
+ 'COLORTERM',
59
+ 'NO_COLOR',
60
+ 'FORCE_COLOR',
61
+ 'TZ',
62
+ 'LS_COLORS',
63
+ 'LSCOLORS',
64
+ 'GREP_COLOR',
65
+ 'GREP_COLORS',
66
+ 'GCC_COLORS',
67
+ 'TIME_STYLE',
68
+ 'BLOCK_SIZE',
69
+ 'BLOCKSIZE',
70
+ 'COLUMNS',
71
+ 'LINES',
72
+ 'CLICOLOR',
73
+ 'CLICOLOR_FORCE',
74
+ 'CI',
75
+ 'DEBIAN_FRONTEND',
76
+ 'GIT_TERMINAL_PROMPT',
77
+ ]);
78
+ /** Commands that read and print, with any arguments (the reference's bare-safe list). */
79
+ const ANY_ARGUMENTS = new Set([
80
+ 'cal',
81
+ 'uptime',
82
+ 'cat',
83
+ 'head',
84
+ 'tail',
85
+ 'wc',
86
+ 'stat',
87
+ 'strings',
88
+ 'hexdump',
89
+ 'od',
90
+ 'nl',
91
+ 'id',
92
+ 'uname',
93
+ 'free',
94
+ 'df',
95
+ 'du',
96
+ 'locale',
97
+ 'groups',
98
+ 'nproc',
99
+ 'basename',
100
+ 'dirname',
101
+ 'realpath',
102
+ 'cut',
103
+ 'paste',
104
+ 'tr',
105
+ 'column',
106
+ 'tac',
107
+ 'rev',
108
+ 'fold',
109
+ 'expand',
110
+ 'unexpand',
111
+ 'fmt',
112
+ 'comm',
113
+ 'cmp',
114
+ 'numfmt',
115
+ 'readlink',
116
+ 'diff',
117
+ 'true',
118
+ 'false',
119
+ 'sleep',
120
+ 'which',
121
+ 'type',
122
+ 'expr',
123
+ 'seq',
124
+ 'tsort',
125
+ 'pr',
126
+ 'echo',
127
+ 'ls',
128
+ ]);
129
+ /** The commands whose unquoted glob arguments are still read-only. */
130
+ const GLOB_OK = new Set([
131
+ 'ls',
132
+ 'cat',
133
+ 'head',
134
+ 'tail',
135
+ 'wc',
136
+ 'stat',
137
+ 'grep',
138
+ 'egrep',
139
+ 'fgrep',
140
+ 'diff',
141
+ 'du',
142
+ 'df',
143
+ 'echo',
144
+ 'strings',
145
+ 'hexdump',
146
+ 'od',
147
+ 'nl',
148
+ 'cut',
149
+ 'column',
150
+ 'tr',
151
+ 'tac',
152
+ 'rev',
153
+ 'cmp',
154
+ 'basename',
155
+ 'dirname',
156
+ 'realpath',
157
+ 'readlink',
158
+ 'sha256sum',
159
+ 'sha1sum',
160
+ 'md5sum',
161
+ 'cd',
162
+ ]);
163
+ const FIND_WRITES = new Set([
164
+ '-delete',
165
+ '-rm',
166
+ '-exec',
167
+ '-execdir',
168
+ '-ok',
169
+ '-okdir',
170
+ '-fprint',
171
+ '-fprint0',
172
+ '-fls',
173
+ '-fprintf',
174
+ '-files0-from',
175
+ ]);
176
+ /** Options that point `docker` at another daemon, context or config. */
177
+ const DOCKER_REMOTE = [
178
+ '-H',
179
+ '-c',
180
+ '-r',
181
+ '--host',
182
+ '--context',
183
+ '--config',
184
+ '--tlscacert',
185
+ '--tlscert',
186
+ '--tlskey',
187
+ '--url',
188
+ '--connection',
189
+ '--identity',
190
+ '--remote',
191
+ '--module',
192
+ '--out',
193
+ ];
194
+ const DOCKER_REMOTE_SHORT = new Set(['H', 'c', 'r']);
195
+ const GREP_FAMILY = new Set(['grep', 'egrep', 'fgrep']);
196
+ // ---------------------------------------------------------------------------
197
+ // the line as text
198
+ // ---------------------------------------------------------------------------
199
+ /**
200
+ * Whether the line carries syntax the word-level reading cannot vouch for:
201
+ * a variable expansion anywhere but single quotes, a substitution, a subshell, or a `&` that is not `&&` or a descriptor copy.
202
+ */
203
+ function hasDynamicSyntax(line) {
204
+ let single = false;
205
+ let double = false;
206
+ for (let i = 0; i < line.length; i += 1) {
207
+ const c = line[i];
208
+ if (c === '\\' && !single) {
209
+ i += 1;
210
+ continue;
211
+ }
212
+ if (c === "'" && !double) {
213
+ single = !single;
214
+ continue;
215
+ }
216
+ if (c === '"' && !single) {
217
+ double = !double;
218
+ continue;
219
+ }
220
+ if (single)
221
+ continue;
222
+ if (c === '$') {
223
+ const next = line[i + 1];
224
+ if (next !== undefined && /[A-Za-z_@*#?!$0-9{(-]/.test(next))
225
+ return true;
226
+ }
227
+ if (c === '`')
228
+ return true;
229
+ if (double)
230
+ continue;
231
+ if (c === '(' || c === ')')
232
+ return true;
233
+ // `{ ls; }` is a group; `HEAD@{1}` and `{a,b}` are words (the latter is
234
+ // refused with the other expansions).
235
+ if (c === '{') {
236
+ const previous = line[i - 1];
237
+ const next = line[i + 1];
238
+ if ((previous === undefined || /[\s;&|]/.test(previous)) &&
239
+ (next === undefined || /\s/.test(next)))
240
+ return true;
241
+ }
242
+ if (c === '&') {
243
+ const previous = line[i - 1];
244
+ const next = line[i + 1];
245
+ if (next === '&' || previous === '&') {
246
+ i += next === '&' ? 1 : 0;
247
+ continue;
248
+ }
249
+ // `2>&1`, `>&2`, `<&3`: a descriptor copy.
250
+ if (previous === '>' || previous === '<')
251
+ continue;
252
+ return true;
253
+ }
254
+ }
255
+ return false;
256
+ }
257
+ /** An unquoted `*`, `?` or `[` in a word's source text. */
258
+ function hasUnquotedGlob(text) {
259
+ let single = false;
260
+ let double = false;
261
+ for (let i = 0; i < text.length; i += 1) {
262
+ const c = text[i];
263
+ if (c === '\\' && !single) {
264
+ i += 1;
265
+ continue;
266
+ }
267
+ if (c === "'" && !double) {
268
+ single = !single;
269
+ continue;
270
+ }
271
+ if (c === '"' && !single) {
272
+ double = !double;
273
+ continue;
274
+ }
275
+ if (!single && !double && (c === '*' || c === '?' || c === '['))
276
+ return true;
277
+ }
278
+ return false;
279
+ }
280
+ /** `\\host\share` and `//host/share`: a path the OS may fetch over the network. */
281
+ function looksLikeNetworkPath(value) {
282
+ return /^(?:\\\\|\/\/)[^\\/]/.test(value);
283
+ }
284
+ /** A word whose value is not what the shell will pass, other than by a plain glob or `~`. */
285
+ function isDynamic(word) {
286
+ // `expands` is false for a single-quoted `$`, which is only text.
287
+ if (word.substitutes || (word.expands && /[$`]/.test(word.value)))
288
+ return true;
289
+ if (/\{[^{}]*(?:,|\.\.)[^{}]*\}/.test(word.text))
290
+ return true;
291
+ // `~user` expands to another account's home; `~` and `~/x` are this one's.
292
+ return /^~(?!\/|$)/.test(word.text);
293
+ }
294
+ // ---------------------------------------------------------------------------
295
+ // a command's flags
296
+ // ---------------------------------------------------------------------------
297
+ const FLAG_LIKE = /^-[A-Za-z0-9_-]/;
298
+ function isFlag(word) {
299
+ return word.startsWith('-') && word.length > 1 && FLAG_LIKE.test(word);
300
+ }
301
+ function valueFits(value, kind) {
302
+ if (kind === 'number')
303
+ return /^\d+$/.test(value);
304
+ return kind === 'string';
305
+ }
306
+ /** A flag's value that is dynamic or names a network path. */
307
+ function dangerousValue(value) {
308
+ return /[$`]/.test(value) || looksLikeNetworkPath(value);
309
+ }
310
+ /**
311
+ * Check `args[start..]` against a flag table: every flag is listed, takes the
312
+ * value its kind says, and (for `string`) that value is not itself a flag.
313
+ */
314
+ function flagsAreVetted(args, start, flagTable, command) {
315
+ let i = start;
316
+ while (i < args.length) {
317
+ const word = args[i];
318
+ if (word === '') {
319
+ i += 1;
320
+ continue;
321
+ }
322
+ if (word === '--') {
323
+ i += 1;
324
+ if (flagTable.doubleDash)
325
+ break;
326
+ continue;
327
+ }
328
+ if (!isFlag(word)) {
329
+ if (looksLikeNetworkPath(word))
330
+ return false;
331
+ i += 1;
332
+ continue;
333
+ }
334
+ const equals = word.indexOf('=');
335
+ const name = equals === -1 ? word : word.slice(0, equals);
336
+ const attached = equals === -1 ? undefined : word.slice(equals + 1);
337
+ const kind = flagTable.flags[name];
338
+ if (kind === undefined) {
339
+ // `git log -5`
340
+ if (command === 'git' && /^-\d+$/.test(name)) {
341
+ i += 1;
342
+ continue;
343
+ }
344
+ const searching = GREP_FAMILY.has(command) || command === 'rg';
345
+ // `grep -A5`: a count glued to its flag.
346
+ if (searching && name.startsWith('-') && !name.startsWith('--') && name.length > 2) {
347
+ const head = flagTable.flags[name.slice(0, 2)];
348
+ if ((head === 'number' || head === 'string') && /^\d+$/.test(name.slice(2))) {
349
+ i += 1;
350
+ continue;
351
+ }
352
+ }
353
+ // `-rn`: a cluster of switches, each of which must be a switch.
354
+ if (name.startsWith('-') && !name.startsWith('--') && name.length > 2) {
355
+ for (const letter of name.slice(1))
356
+ if (flagTable.flags[`-${letter}`] !== 'none')
357
+ return false;
358
+ i += 1;
359
+ continue;
360
+ }
361
+ return false;
362
+ }
363
+ if (kind === 'none') {
364
+ if (attached !== undefined)
365
+ return false;
366
+ i += 1;
367
+ continue;
368
+ }
369
+ let value;
370
+ if (attached !== undefined) {
371
+ value = attached;
372
+ i += 1;
373
+ }
374
+ else {
375
+ const next = args[i + 1];
376
+ if (next === undefined || isFlag(next))
377
+ return false;
378
+ value = next;
379
+ i += 2;
380
+ }
381
+ if (dangerousValue(value))
382
+ return false;
383
+ if (kind === 'string' && value.startsWith('-')) {
384
+ // `git for-each-ref --sort=-committerdate`, but not `--format -x`.
385
+ if (!(name === '--sort' && command === 'git' && /^-[a-zA-Z]/.test(value)))
386
+ return false;
387
+ }
388
+ if (!valueFits(value, kind))
389
+ return false;
390
+ }
391
+ return true;
392
+ }
393
+ // -- `git` hardening -------------------------------------------------------
394
+ const SIGNATURE = /%[-+ ]?G|%\(\*?signature/;
395
+ const PRETTY_PRESETS = new Set(['oneline', 'short', 'medium', 'full', 'fuller', 'email', 'raw']);
396
+ function mentionsHomeDirectory(args) {
397
+ return args.some((a) => /(?:^|[=:])~/.test(a) || (a.includes('~') && a.includes('{')));
398
+ }
399
+ /** A `--format`/`--pretty`/`--sort`/`--group` value that runs a program or reads a signature. */
400
+ function formatIsDangerous(args) {
401
+ const bad = (value) => mentionsHomeDirectory([value]) ||
402
+ looksLikeNetworkPath(value) ||
403
+ SIGNATURE.test(value) ||
404
+ value.includes('signature');
405
+ for (let i = 0; i < args.length; i += 1) {
406
+ const word = args[i];
407
+ if (SIGNATURE.test(word))
408
+ return true;
409
+ for (const option of ['--format', '--pretty', '--sort', '--group']) {
410
+ let value;
411
+ if (word === option && i + 1 < args.length)
412
+ value = args[i + 1];
413
+ else if (word.startsWith(`${option}=`))
414
+ value = word.slice(option.length + 1);
415
+ if (value === undefined)
416
+ continue;
417
+ if (bad(value))
418
+ return true;
419
+ // A named pretty format that is not a built-in is read from the config.
420
+ if ((option === '--format' || option === '--pretty') &&
421
+ value !== '' &&
422
+ !value.includes('%') &&
423
+ !value.startsWith('format:') &&
424
+ !value.startsWith('tformat:') &&
425
+ !PRETTY_PRESETS.has(value))
426
+ return true;
427
+ }
428
+ }
429
+ return false;
430
+ }
431
+ const GIT_CHECKS = {
432
+ 'git log': formatIsDangerous,
433
+ 'git show': formatIsDangerous,
434
+ 'git shortlog': formatIsDangerous,
435
+ 'git rev-list': formatIsDangerous,
436
+ 'git for-each-ref': formatIsDangerous,
437
+ 'git reflog': (args) => {
438
+ const first = args[0];
439
+ if (first !== undefined && !first.startsWith('-') && first !== 'show' && first !== 'list')
440
+ return true;
441
+ return args.some((a) => ['expire', 'delete', 'exists', 'drop', 'write'].includes(a));
442
+ },
443
+ 'git ls-remote': (args) => {
444
+ if (formatIsDangerous(args))
445
+ return true;
446
+ let afterDashes = false;
447
+ for (let i = 0; i < args.length; i += 1) {
448
+ const word = args[i];
449
+ if (!afterDashes && word === '--') {
450
+ afterDashes = true;
451
+ continue;
452
+ }
453
+ if (!afterDashes && (word === '' || word.startsWith('-'))) {
454
+ if (word === '--sort')
455
+ i += 1;
456
+ continue;
457
+ }
458
+ return true;
459
+ }
460
+ return false;
461
+ },
462
+ 'git remote show': (args) => {
463
+ const dashes = args.indexOf('--');
464
+ const options = dashes === -1 ? args : args.slice(0, dashes);
465
+ const operands = dashes === -1 ? [] : args.slice(dashes + 1);
466
+ const rest = options.filter((a) => a !== '-n').concat(operands);
467
+ if (rest.length !== 1)
468
+ return true;
469
+ if (!options.includes('-n'))
470
+ return true;
471
+ return !/^[a-zA-Z0-9_][a-zA-Z0-9_-]*$/.test(rest[0]);
472
+ },
473
+ 'git remote': (args) => args.some((a) => a !== '-v' && a !== '--verbose'),
474
+ 'git tag': (args) => {
475
+ if (formatIsDangerous(args))
476
+ return true;
477
+ const takesValue = new Set([
478
+ '--contains',
479
+ '--no-contains',
480
+ '--merged',
481
+ '--no-merged',
482
+ '--points-at',
483
+ '--sort',
484
+ '--format',
485
+ '-n',
486
+ ]);
487
+ let listing = false;
488
+ let afterDashes = false;
489
+ let i = 0;
490
+ while (i < args.length) {
491
+ const word = args[i];
492
+ if (word === '') {
493
+ i += 1;
494
+ continue;
495
+ }
496
+ if (word === '--' && !afterDashes) {
497
+ afterDashes = true;
498
+ i += 1;
499
+ continue;
500
+ }
501
+ if (!afterDashes && word.startsWith('-')) {
502
+ if (word === '--list' || word === '-l')
503
+ listing = true;
504
+ else if (word[1] !== '-' &&
505
+ word.length > 2 &&
506
+ !word.includes('=') &&
507
+ word.slice(1).includes('l'))
508
+ listing = true;
509
+ if (word.includes('='))
510
+ i += 1;
511
+ else if (takesValue.has(word))
512
+ i += 2;
513
+ else
514
+ i += 1;
515
+ }
516
+ else {
517
+ // A bare word creates a tag unless the list flag came with it.
518
+ if (!listing)
519
+ return true;
520
+ i += 1;
521
+ }
522
+ }
523
+ return false;
524
+ },
525
+ 'git branch': (args) => {
526
+ if (formatIsDangerous(args))
527
+ return true;
528
+ const takesValue = new Set(['--contains', '--no-contains', '--points-at', '--sort']);
529
+ const optionalCommit = new Set(['--merged', '--no-merged']);
530
+ let listing = false;
531
+ let afterDashes = false;
532
+ let previous = '';
533
+ let i = 0;
534
+ while (i < args.length) {
535
+ const word = args[i];
536
+ if (word === '') {
537
+ i += 1;
538
+ continue;
539
+ }
540
+ if (word === '--' && !afterDashes) {
541
+ afterDashes = true;
542
+ previous = '';
543
+ i += 1;
544
+ continue;
545
+ }
546
+ if (!afterDashes && word.startsWith('-')) {
547
+ if (word === '--list' || word === '-l')
548
+ listing = true;
549
+ else if (word[1] !== '-' && word.length > 2 && !word.includes('=') && word.includes('l'))
550
+ listing = true;
551
+ if (word.includes('=')) {
552
+ previous = word.slice(0, word.indexOf('='));
553
+ i += 1;
554
+ }
555
+ else if (takesValue.has(word)) {
556
+ previous = word;
557
+ i += 2;
558
+ }
559
+ else {
560
+ previous = word;
561
+ i += 1;
562
+ }
563
+ }
564
+ else {
565
+ // A bare word creates a branch unless listing or it is the commit of --merged.
566
+ if (!listing && !optionalCommit.has(previous))
567
+ return true;
568
+ i += 1;
569
+ }
570
+ }
571
+ return false;
572
+ },
573
+ };
574
+ // -- the commands with a regex of their own in the reference ----------------
575
+ function uniqIsReadOnly(args) {
576
+ for (let i = 0; i < args.length; i += 1) {
577
+ const word = args[i];
578
+ if (/^-[a-zA-Z]+$/.test(word) || /^--[a-zA-Z-]+(?:=\S+)?$/.test(word))
579
+ continue;
580
+ // `-f N`, `-s N`, `-w N`
581
+ if (/^-[fsw]$/.test(word))
582
+ continue;
583
+ if (/^\d+$/.test(word) && /^-[fsw]$/.test(args[i - 1] ?? ''))
584
+ continue;
585
+ return false;
586
+ }
587
+ return true;
588
+ }
589
+ function jqIsReadOnly(args) {
590
+ const text = args.join(' ');
591
+ if (/\b(?:env|include|import)\b|\$ENV/.test(text))
592
+ return false;
593
+ let filters = 0;
594
+ for (const word of args) {
595
+ if (/^-[a-zA-Z]*[fL]/.test(word) && !word.startsWith('--'))
596
+ return false;
597
+ if (['--from-file', '--rawfile', '--slurpfile', '--run-tests', '--library-path'].some((x) => word === x || word.startsWith(`${x}=`)))
598
+ return false;
599
+ if (!word.startsWith('-'))
600
+ filters += 1;
601
+ }
602
+ return filters > 0;
603
+ }
604
+ function findIsReadOnly(args) {
605
+ return !args.some((word) => FIND_WRITES.has(word));
606
+ }
607
+ function dockerIsReadOnly(args) {
608
+ if (args[0] !== 'ps' && args[0] !== 'images')
609
+ return false;
610
+ for (const word of args) {
611
+ if (mentionsHomeDirectory([word]) || looksLikeNetworkPath(word))
612
+ return false;
613
+ if (DOCKER_REMOTE.some((o) => word === o || word.startsWith(`${o}=`)))
614
+ return false;
615
+ if (word.length > 2 && word.startsWith('-') && !word.startsWith('--')) {
616
+ if (word.startsWith('-H') || word.startsWith('-c') || word.startsWith('-r'))
617
+ return false;
618
+ for (const letter of word.slice(1))
619
+ if (DOCKER_REMOTE_SHORT.has(letter))
620
+ return false;
621
+ }
622
+ }
623
+ return true;
624
+ }
625
+ /** Whether one simple command, as its argument vector, is on the list. */
626
+ function commandIsReadOnly(argv, text) {
627
+ const [head, ...rest] = argv;
628
+ if (head === undefined)
629
+ return false;
630
+ if (ANY_ARGUMENTS.has(head))
631
+ return true;
632
+ switch (head) {
633
+ case 'pwd':
634
+ case 'whoami':
635
+ case 'alias':
636
+ return rest.length === 0;
637
+ case 'cd':
638
+ return rest.length <= 1;
639
+ case 'history':
640
+ return rest.length === 0 || (rest.length === 1 && /^\d+$/.test(rest[0]));
641
+ case 'arch':
642
+ return rest.length === 0 || (rest.length === 1 && (rest[0] === '-h' || rest[0] === '--help'));
643
+ case 'ifconfig':
644
+ return (rest.length === 0 ||
645
+ (rest.length === 1 && /^[a-zA-Z][a-zA-Z0-9_-]*$/.test(rest[0])));
646
+ case 'ip':
647
+ return rest.length === 1 && rest[0] === 'addr';
648
+ case 'node':
649
+ return rest.length === 1 && (rest[0] === '-v' || rest[0] === '--version');
650
+ case 'python':
651
+ case 'python3':
652
+ return rest.length === 1 && rest[0] === '--version';
653
+ case 'claude':
654
+ return rest.length === 1 && (rest[0] === '-h' || rest[0] === '--help');
655
+ case 'uniq':
656
+ return uniqIsReadOnly(rest);
657
+ case 'jq':
658
+ return jqIsReadOnly(rest);
659
+ case 'find':
660
+ return findIsReadOnly(rest);
661
+ case 'docker':
662
+ return dockerIsReadOnly(rest);
663
+ default:
664
+ break;
665
+ }
666
+ // Longest table key that starts the vector: `git config --get` before `git`.
667
+ let selected;
668
+ for (const [key, flagTable] of Object.entries(FLAG_TABLES)) {
669
+ const words = key.split(' ');
670
+ if (argv.length < words.length)
671
+ continue;
672
+ if (!words.every((w, i) => argv[i] === w))
673
+ continue;
674
+ if (selected === undefined || words.length > selected.words)
675
+ selected = { key, flagTable, words: words.length };
676
+ }
677
+ if (selected === undefined)
678
+ return false;
679
+ const { key, flagTable, words } = selected;
680
+ const rest2 = argv.slice(words);
681
+ for (const word of rest2) {
682
+ if (word.includes('$'))
683
+ return false;
684
+ if (word.includes('{') && (word.includes(',') || word.includes('..')))
685
+ return false;
686
+ }
687
+ if (head === 'git' && argv[1] === 'ls-remote') {
688
+ if (argv.some((a) => a === '-o' || a === '--server-option' || a.startsWith('--server-option=')))
689
+ return false;
690
+ }
691
+ if (!flagsAreVetted(argv, words, flagTable, head))
692
+ return false;
693
+ if ((GREP_FAMILY.has(head) || head === 'rg') && /[\n\r]/.test(text))
694
+ return false;
695
+ const check = GIT_CHECKS[key];
696
+ if (check?.(rest2))
697
+ return false;
698
+ return true;
699
+ }
700
+ // ---------------------------------------------------------------------------
701
+ // redirections
702
+ // ---------------------------------------------------------------------------
703
+ function redirectionsAreHarmless(redirections) {
704
+ for (const { operator, target } of redirections) {
705
+ const value = target.value;
706
+ if (isDynamic(target))
707
+ return false;
708
+ if (/^\/dev\/(?:tcp|udp)\//.test(value))
709
+ return false;
710
+ if (operator === '>&' || operator === '<&') {
711
+ if (/^\d+$/.test(value) || value === '-')
712
+ continue;
713
+ if (value === '/dev/null')
714
+ continue;
715
+ return false;
716
+ }
717
+ if (operator === '<') {
718
+ if (looksLikeNetworkPath(value))
719
+ return false;
720
+ if (/^~(?!\/|$)/.test(target.text))
721
+ return false;
722
+ continue;
723
+ }
724
+ // Any writing operator: only the null device.
725
+ if (value === '/dev/null' && !target.expands)
726
+ continue;
727
+ return false;
728
+ }
729
+ return true;
730
+ }
731
+ // ---------------------------------------------------------------------------
732
+ // the entry point
733
+ // ---------------------------------------------------------------------------
734
+ /**
735
+ * Whether `line` only reads: every command in it is vetted and nothing in it
736
+ * is decided at run time. False for anything this module cannot vouch for.
737
+ */
738
+ export function isReadOnlyCommandLine(line, dialect = 'bash') {
739
+ if (dialect === 'cmd')
740
+ return false;
741
+ if (line.trim() === '' || line.length > MAX_LINE_LENGTH)
742
+ return false;
743
+ if (hasDynamicSyntax(line))
744
+ return false;
745
+ if (looksLikeNetworkPath(line.trim()))
746
+ return false;
747
+ const lexed = lexShellCommandLine(line, { dialect });
748
+ if (!lexed.complete || lexed.opaque || lexed.commands.length === 0)
749
+ return false;
750
+ if (lexed.compoundWords.length > 0)
751
+ return false;
752
+ if (!redirectionsAreHarmless(lexed.redirections))
753
+ return false;
754
+ let runsGit = false;
755
+ let changesDirectory = false;
756
+ for (const command of lexed.commands) {
757
+ if (command.origin !== 'line')
758
+ return false;
759
+ if (!redirectionsAreHarmless(command.redirections))
760
+ return false;
761
+ for (const word of command.words.slice(0, command.assignments)) {
762
+ const name = word.text.slice(0, Math.max(0, word.text.indexOf('=')));
763
+ if (!SAFE_ENVIRONMENT.has(name) || isDynamic(word) || word.expands)
764
+ return false;
765
+ }
766
+ const words = command.words.slice(command.assignments);
767
+ const head = words[0];
768
+ if (head === undefined || head.expands || head.quoted)
769
+ return false;
770
+ if (head.value.includes('/'))
771
+ return false;
772
+ // A word may differ from what the shell passes only by a plain glob or a
773
+ // leading `~`; anything else (a substitution, a brace list, `~user`, a
774
+ // locale escape) is decided at run time.
775
+ if (words.some(isDynamic))
776
+ return false;
777
+ const plainExpansion = (word) => hasUnquotedGlob(word.text) || /^~(?:\/|$)/.test(word.text);
778
+ if (words.some((word) => word.expands && !plainExpansion(word)))
779
+ return false;
780
+ const glob = words.some((word) => word.expands && hasUnquotedGlob(word.text));
781
+ if (glob && !GLOB_OK.has(head.value))
782
+ return false;
783
+ if (words.some((word) => looksLikeNetworkPath(word.value)))
784
+ return false;
785
+ const argv = words.map((word) => word.value);
786
+ if (!commandIsReadOnly(argv, command.text))
787
+ return false;
788
+ if (head.value === 'git')
789
+ runsGit = true;
790
+ if (head.value === 'cd')
791
+ changesDirectory = true;
792
+ }
793
+ if (runsGit && changesDirectory)
794
+ return false;
795
+ return true;
796
+ }
797
+ //# sourceMappingURL=readonly-commands.js.map