gitx.do 0.1.0 → 0.1.2

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 (344) hide show
  1. package/README.md +40 -353
  2. package/dist/do/logger.d.ts +50 -0
  3. package/dist/do/logger.d.ts.map +1 -0
  4. package/dist/do/logger.js +122 -0
  5. package/dist/do/logger.js.map +1 -0
  6. package/dist/{durable-object → do}/schema.d.ts +3 -3
  7. package/dist/do/schema.d.ts.map +1 -0
  8. package/dist/{durable-object → do}/schema.js +4 -3
  9. package/dist/do/schema.js.map +1 -0
  10. package/dist/do/types.d.ts +267 -0
  11. package/dist/do/types.d.ts.map +1 -0
  12. package/dist/do/types.js +62 -0
  13. package/dist/do/types.js.map +1 -0
  14. package/dist/index.d.ts +15 -415
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +31 -483
  17. package/dist/index.js.map +1 -1
  18. package/package.json +13 -21
  19. package/dist/cli/commands/add.d.ts +0 -174
  20. package/dist/cli/commands/add.d.ts.map +0 -1
  21. package/dist/cli/commands/add.js +0 -131
  22. package/dist/cli/commands/add.js.map +0 -1
  23. package/dist/cli/commands/blame.d.ts +0 -259
  24. package/dist/cli/commands/blame.d.ts.map +0 -1
  25. package/dist/cli/commands/blame.js +0 -609
  26. package/dist/cli/commands/blame.js.map +0 -1
  27. package/dist/cli/commands/branch.d.ts +0 -249
  28. package/dist/cli/commands/branch.d.ts.map +0 -1
  29. package/dist/cli/commands/branch.js +0 -693
  30. package/dist/cli/commands/branch.js.map +0 -1
  31. package/dist/cli/commands/commit.d.ts +0 -182
  32. package/dist/cli/commands/commit.d.ts.map +0 -1
  33. package/dist/cli/commands/commit.js +0 -437
  34. package/dist/cli/commands/commit.js.map +0 -1
  35. package/dist/cli/commands/diff.d.ts +0 -464
  36. package/dist/cli/commands/diff.d.ts.map +0 -1
  37. package/dist/cli/commands/diff.js +0 -958
  38. package/dist/cli/commands/diff.js.map +0 -1
  39. package/dist/cli/commands/log.d.ts +0 -239
  40. package/dist/cli/commands/log.d.ts.map +0 -1
  41. package/dist/cli/commands/log.js +0 -535
  42. package/dist/cli/commands/log.js.map +0 -1
  43. package/dist/cli/commands/merge.d.ts +0 -106
  44. package/dist/cli/commands/merge.d.ts.map +0 -1
  45. package/dist/cli/commands/merge.js +0 -55
  46. package/dist/cli/commands/merge.js.map +0 -1
  47. package/dist/cli/commands/review.d.ts +0 -457
  48. package/dist/cli/commands/review.d.ts.map +0 -1
  49. package/dist/cli/commands/review.js +0 -533
  50. package/dist/cli/commands/review.js.map +0 -1
  51. package/dist/cli/commands/status.d.ts +0 -269
  52. package/dist/cli/commands/status.d.ts.map +0 -1
  53. package/dist/cli/commands/status.js +0 -493
  54. package/dist/cli/commands/status.js.map +0 -1
  55. package/dist/cli/commands/web.d.ts +0 -199
  56. package/dist/cli/commands/web.d.ts.map +0 -1
  57. package/dist/cli/commands/web.js +0 -696
  58. package/dist/cli/commands/web.js.map +0 -1
  59. package/dist/cli/fs-adapter.d.ts +0 -656
  60. package/dist/cli/fs-adapter.d.ts.map +0 -1
  61. package/dist/cli/fs-adapter.js +0 -1179
  62. package/dist/cli/fs-adapter.js.map +0 -1
  63. package/dist/cli/fsx-cli-adapter.d.ts +0 -359
  64. package/dist/cli/fsx-cli-adapter.d.ts.map +0 -1
  65. package/dist/cli/fsx-cli-adapter.js +0 -619
  66. package/dist/cli/fsx-cli-adapter.js.map +0 -1
  67. package/dist/cli/index.d.ts +0 -387
  68. package/dist/cli/index.d.ts.map +0 -1
  69. package/dist/cli/index.js +0 -523
  70. package/dist/cli/index.js.map +0 -1
  71. package/dist/cli/ui/components/DiffView.d.ts +0 -7
  72. package/dist/cli/ui/components/DiffView.d.ts.map +0 -1
  73. package/dist/cli/ui/components/DiffView.js +0 -11
  74. package/dist/cli/ui/components/DiffView.js.map +0 -1
  75. package/dist/cli/ui/components/ErrorDisplay.d.ts +0 -6
  76. package/dist/cli/ui/components/ErrorDisplay.d.ts.map +0 -1
  77. package/dist/cli/ui/components/ErrorDisplay.js +0 -11
  78. package/dist/cli/ui/components/ErrorDisplay.js.map +0 -1
  79. package/dist/cli/ui/components/FuzzySearch.d.ts +0 -9
  80. package/dist/cli/ui/components/FuzzySearch.d.ts.map +0 -1
  81. package/dist/cli/ui/components/FuzzySearch.js +0 -12
  82. package/dist/cli/ui/components/FuzzySearch.js.map +0 -1
  83. package/dist/cli/ui/components/LoadingSpinner.d.ts +0 -6
  84. package/dist/cli/ui/components/LoadingSpinner.d.ts.map +0 -1
  85. package/dist/cli/ui/components/LoadingSpinner.js +0 -10
  86. package/dist/cli/ui/components/LoadingSpinner.js.map +0 -1
  87. package/dist/cli/ui/components/NavigationList.d.ts +0 -9
  88. package/dist/cli/ui/components/NavigationList.d.ts.map +0 -1
  89. package/dist/cli/ui/components/NavigationList.js +0 -11
  90. package/dist/cli/ui/components/NavigationList.js.map +0 -1
  91. package/dist/cli/ui/components/ScrollableContent.d.ts +0 -8
  92. package/dist/cli/ui/components/ScrollableContent.d.ts.map +0 -1
  93. package/dist/cli/ui/components/ScrollableContent.js +0 -11
  94. package/dist/cli/ui/components/ScrollableContent.js.map +0 -1
  95. package/dist/cli/ui/components/index.d.ts +0 -7
  96. package/dist/cli/ui/components/index.d.ts.map +0 -1
  97. package/dist/cli/ui/components/index.js +0 -9
  98. package/dist/cli/ui/components/index.js.map +0 -1
  99. package/dist/cli/ui/terminal-ui.d.ts +0 -52
  100. package/dist/cli/ui/terminal-ui.d.ts.map +0 -1
  101. package/dist/cli/ui/terminal-ui.js +0 -121
  102. package/dist/cli/ui/terminal-ui.js.map +0 -1
  103. package/dist/do/BashModule.d.ts +0 -871
  104. package/dist/do/BashModule.d.ts.map +0 -1
  105. package/dist/do/BashModule.js +0 -1143
  106. package/dist/do/BashModule.js.map +0 -1
  107. package/dist/do/FsModule.d.ts +0 -601
  108. package/dist/do/FsModule.d.ts.map +0 -1
  109. package/dist/do/FsModule.js +0 -1120
  110. package/dist/do/FsModule.js.map +0 -1
  111. package/dist/do/GitModule.d.ts +0 -635
  112. package/dist/do/GitModule.d.ts.map +0 -1
  113. package/dist/do/GitModule.js +0 -781
  114. package/dist/do/GitModule.js.map +0 -1
  115. package/dist/do/GitRepoDO.d.ts +0 -281
  116. package/dist/do/GitRepoDO.d.ts.map +0 -1
  117. package/dist/do/GitRepoDO.js +0 -479
  118. package/dist/do/GitRepoDO.js.map +0 -1
  119. package/dist/do/bash-ast.d.ts +0 -246
  120. package/dist/do/bash-ast.d.ts.map +0 -1
  121. package/dist/do/bash-ast.js +0 -888
  122. package/dist/do/bash-ast.js.map +0 -1
  123. package/dist/do/container-executor.d.ts +0 -491
  124. package/dist/do/container-executor.d.ts.map +0 -1
  125. package/dist/do/container-executor.js +0 -730
  126. package/dist/do/container-executor.js.map +0 -1
  127. package/dist/do/index.d.ts +0 -53
  128. package/dist/do/index.d.ts.map +0 -1
  129. package/dist/do/index.js +0 -91
  130. package/dist/do/index.js.map +0 -1
  131. package/dist/do/tiered-storage.d.ts +0 -403
  132. package/dist/do/tiered-storage.d.ts.map +0 -1
  133. package/dist/do/tiered-storage.js +0 -689
  134. package/dist/do/tiered-storage.js.map +0 -1
  135. package/dist/do/withBash.d.ts +0 -231
  136. package/dist/do/withBash.d.ts.map +0 -1
  137. package/dist/do/withBash.js +0 -244
  138. package/dist/do/withBash.js.map +0 -1
  139. package/dist/do/withFs.d.ts +0 -237
  140. package/dist/do/withFs.d.ts.map +0 -1
  141. package/dist/do/withFs.js +0 -387
  142. package/dist/do/withFs.js.map +0 -1
  143. package/dist/do/withGit.d.ts +0 -180
  144. package/dist/do/withGit.d.ts.map +0 -1
  145. package/dist/do/withGit.js +0 -271
  146. package/dist/do/withGit.js.map +0 -1
  147. package/dist/durable-object/object-store.d.ts +0 -633
  148. package/dist/durable-object/object-store.d.ts.map +0 -1
  149. package/dist/durable-object/object-store.js +0 -1161
  150. package/dist/durable-object/object-store.js.map +0 -1
  151. package/dist/durable-object/schema.d.ts.map +0 -1
  152. package/dist/durable-object/schema.js.map +0 -1
  153. package/dist/durable-object/wal.d.ts +0 -416
  154. package/dist/durable-object/wal.d.ts.map +0 -1
  155. package/dist/durable-object/wal.js +0 -445
  156. package/dist/durable-object/wal.js.map +0 -1
  157. package/dist/mcp/adapter.d.ts +0 -772
  158. package/dist/mcp/adapter.d.ts.map +0 -1
  159. package/dist/mcp/adapter.js +0 -895
  160. package/dist/mcp/adapter.js.map +0 -1
  161. package/dist/mcp/sandbox/miniflare-evaluator.d.ts +0 -22
  162. package/dist/mcp/sandbox/miniflare-evaluator.d.ts.map +0 -1
  163. package/dist/mcp/sandbox/miniflare-evaluator.js +0 -140
  164. package/dist/mcp/sandbox/miniflare-evaluator.js.map +0 -1
  165. package/dist/mcp/sandbox/object-store-proxy.d.ts +0 -32
  166. package/dist/mcp/sandbox/object-store-proxy.d.ts.map +0 -1
  167. package/dist/mcp/sandbox/object-store-proxy.js +0 -30
  168. package/dist/mcp/sandbox/object-store-proxy.js.map +0 -1
  169. package/dist/mcp/sandbox/template.d.ts +0 -17
  170. package/dist/mcp/sandbox/template.d.ts.map +0 -1
  171. package/dist/mcp/sandbox/template.js +0 -71
  172. package/dist/mcp/sandbox/template.js.map +0 -1
  173. package/dist/mcp/sandbox.d.ts +0 -764
  174. package/dist/mcp/sandbox.d.ts.map +0 -1
  175. package/dist/mcp/sandbox.js +0 -1362
  176. package/dist/mcp/sandbox.js.map +0 -1
  177. package/dist/mcp/sdk-adapter.d.ts +0 -835
  178. package/dist/mcp/sdk-adapter.d.ts.map +0 -1
  179. package/dist/mcp/sdk-adapter.js +0 -974
  180. package/dist/mcp/sdk-adapter.js.map +0 -1
  181. package/dist/mcp/tools/do.d.ts +0 -32
  182. package/dist/mcp/tools/do.d.ts.map +0 -1
  183. package/dist/mcp/tools/do.js +0 -115
  184. package/dist/mcp/tools/do.js.map +0 -1
  185. package/dist/mcp/tools.d.ts +0 -548
  186. package/dist/mcp/tools.d.ts.map +0 -1
  187. package/dist/mcp/tools.js +0 -1934
  188. package/dist/mcp/tools.js.map +0 -1
  189. package/dist/ops/blame.d.ts +0 -551
  190. package/dist/ops/blame.d.ts.map +0 -1
  191. package/dist/ops/blame.js +0 -1037
  192. package/dist/ops/blame.js.map +0 -1
  193. package/dist/ops/branch.d.ts +0 -766
  194. package/dist/ops/branch.d.ts.map +0 -1
  195. package/dist/ops/branch.js +0 -950
  196. package/dist/ops/branch.js.map +0 -1
  197. package/dist/ops/commit-traversal.d.ts +0 -349
  198. package/dist/ops/commit-traversal.d.ts.map +0 -1
  199. package/dist/ops/commit-traversal.js +0 -821
  200. package/dist/ops/commit-traversal.js.map +0 -1
  201. package/dist/ops/commit.d.ts +0 -555
  202. package/dist/ops/commit.d.ts.map +0 -1
  203. package/dist/ops/commit.js +0 -826
  204. package/dist/ops/commit.js.map +0 -1
  205. package/dist/ops/merge-base.d.ts +0 -397
  206. package/dist/ops/merge-base.d.ts.map +0 -1
  207. package/dist/ops/merge-base.js +0 -691
  208. package/dist/ops/merge-base.js.map +0 -1
  209. package/dist/ops/merge.d.ts +0 -855
  210. package/dist/ops/merge.d.ts.map +0 -1
  211. package/dist/ops/merge.js +0 -1551
  212. package/dist/ops/merge.js.map +0 -1
  213. package/dist/ops/tag.d.ts +0 -247
  214. package/dist/ops/tag.d.ts.map +0 -1
  215. package/dist/ops/tag.js +0 -649
  216. package/dist/ops/tag.js.map +0 -1
  217. package/dist/ops/tree-builder.d.ts +0 -178
  218. package/dist/ops/tree-builder.d.ts.map +0 -1
  219. package/dist/ops/tree-builder.js +0 -271
  220. package/dist/ops/tree-builder.js.map +0 -1
  221. package/dist/ops/tree-diff.d.ts +0 -291
  222. package/dist/ops/tree-diff.d.ts.map +0 -1
  223. package/dist/ops/tree-diff.js +0 -705
  224. package/dist/ops/tree-diff.js.map +0 -1
  225. package/dist/pack/delta.d.ts +0 -248
  226. package/dist/pack/delta.d.ts.map +0 -1
  227. package/dist/pack/delta.js +0 -736
  228. package/dist/pack/delta.js.map +0 -1
  229. package/dist/pack/format.d.ts +0 -446
  230. package/dist/pack/format.d.ts.map +0 -1
  231. package/dist/pack/format.js +0 -572
  232. package/dist/pack/format.js.map +0 -1
  233. package/dist/pack/full-generation.d.ts +0 -612
  234. package/dist/pack/full-generation.d.ts.map +0 -1
  235. package/dist/pack/full-generation.js +0 -1378
  236. package/dist/pack/full-generation.js.map +0 -1
  237. package/dist/pack/generation.d.ts +0 -441
  238. package/dist/pack/generation.d.ts.map +0 -1
  239. package/dist/pack/generation.js +0 -707
  240. package/dist/pack/generation.js.map +0 -1
  241. package/dist/pack/index.d.ts +0 -502
  242. package/dist/pack/index.d.ts.map +0 -1
  243. package/dist/pack/index.js +0 -833
  244. package/dist/pack/index.js.map +0 -1
  245. package/dist/refs/branch.d.ts +0 -668
  246. package/dist/refs/branch.d.ts.map +0 -1
  247. package/dist/refs/branch.js +0 -897
  248. package/dist/refs/branch.js.map +0 -1
  249. package/dist/refs/storage.d.ts +0 -833
  250. package/dist/refs/storage.d.ts.map +0 -1
  251. package/dist/refs/storage.js +0 -1023
  252. package/dist/refs/storage.js.map +0 -1
  253. package/dist/refs/tag.d.ts +0 -860
  254. package/dist/refs/tag.d.ts.map +0 -1
  255. package/dist/refs/tag.js +0 -996
  256. package/dist/refs/tag.js.map +0 -1
  257. package/dist/storage/backend.d.ts +0 -425
  258. package/dist/storage/backend.d.ts.map +0 -1
  259. package/dist/storage/backend.js +0 -41
  260. package/dist/storage/backend.js.map +0 -1
  261. package/dist/storage/fsx-adapter.d.ts +0 -204
  262. package/dist/storage/fsx-adapter.d.ts.map +0 -1
  263. package/dist/storage/fsx-adapter.js +0 -470
  264. package/dist/storage/fsx-adapter.js.map +0 -1
  265. package/dist/storage/lru-cache.d.ts +0 -691
  266. package/dist/storage/lru-cache.d.ts.map +0 -1
  267. package/dist/storage/lru-cache.js +0 -813
  268. package/dist/storage/lru-cache.js.map +0 -1
  269. package/dist/storage/object-index.d.ts +0 -585
  270. package/dist/storage/object-index.d.ts.map +0 -1
  271. package/dist/storage/object-index.js +0 -532
  272. package/dist/storage/object-index.js.map +0 -1
  273. package/dist/storage/r2-pack.d.ts +0 -1257
  274. package/dist/storage/r2-pack.d.ts.map +0 -1
  275. package/dist/storage/r2-pack.js +0 -1770
  276. package/dist/storage/r2-pack.js.map +0 -1
  277. package/dist/tiered/cdc-pipeline.d.ts +0 -1888
  278. package/dist/tiered/cdc-pipeline.d.ts.map +0 -1
  279. package/dist/tiered/cdc-pipeline.js +0 -1880
  280. package/dist/tiered/cdc-pipeline.js.map +0 -1
  281. package/dist/tiered/migration.d.ts +0 -1104
  282. package/dist/tiered/migration.d.ts.map +0 -1
  283. package/dist/tiered/migration.js +0 -1214
  284. package/dist/tiered/migration.js.map +0 -1
  285. package/dist/tiered/parquet-writer.d.ts +0 -1145
  286. package/dist/tiered/parquet-writer.d.ts.map +0 -1
  287. package/dist/tiered/parquet-writer.js +0 -1183
  288. package/dist/tiered/parquet-writer.js.map +0 -1
  289. package/dist/tiered/read-path.d.ts +0 -835
  290. package/dist/tiered/read-path.d.ts.map +0 -1
  291. package/dist/tiered/read-path.js +0 -487
  292. package/dist/tiered/read-path.js.map +0 -1
  293. package/dist/types/capability.d.ts +0 -1385
  294. package/dist/types/capability.d.ts.map +0 -1
  295. package/dist/types/capability.js +0 -36
  296. package/dist/types/capability.js.map +0 -1
  297. package/dist/types/index.d.ts +0 -13
  298. package/dist/types/index.d.ts.map +0 -1
  299. package/dist/types/index.js +0 -18
  300. package/dist/types/index.js.map +0 -1
  301. package/dist/types/objects.d.ts +0 -692
  302. package/dist/types/objects.d.ts.map +0 -1
  303. package/dist/types/objects.js +0 -837
  304. package/dist/types/objects.js.map +0 -1
  305. package/dist/types/storage.d.ts +0 -603
  306. package/dist/types/storage.d.ts.map +0 -1
  307. package/dist/types/storage.js +0 -191
  308. package/dist/types/storage.js.map +0 -1
  309. package/dist/types/worker-loader.d.ts +0 -60
  310. package/dist/types/worker-loader.d.ts.map +0 -1
  311. package/dist/types/worker-loader.js +0 -62
  312. package/dist/types/worker-loader.js.map +0 -1
  313. package/dist/utils/hash.d.ts +0 -197
  314. package/dist/utils/hash.d.ts.map +0 -1
  315. package/dist/utils/hash.js +0 -268
  316. package/dist/utils/hash.js.map +0 -1
  317. package/dist/utils/sha1.d.ts +0 -290
  318. package/dist/utils/sha1.d.ts.map +0 -1
  319. package/dist/utils/sha1.js +0 -582
  320. package/dist/utils/sha1.js.map +0 -1
  321. package/dist/wire/capabilities.d.ts +0 -1044
  322. package/dist/wire/capabilities.d.ts.map +0 -1
  323. package/dist/wire/capabilities.js +0 -941
  324. package/dist/wire/capabilities.js.map +0 -1
  325. package/dist/wire/path-security.d.ts +0 -157
  326. package/dist/wire/path-security.d.ts.map +0 -1
  327. package/dist/wire/path-security.js +0 -307
  328. package/dist/wire/path-security.js.map +0 -1
  329. package/dist/wire/pkt-line.d.ts +0 -345
  330. package/dist/wire/pkt-line.d.ts.map +0 -1
  331. package/dist/wire/pkt-line.js +0 -381
  332. package/dist/wire/pkt-line.js.map +0 -1
  333. package/dist/wire/receive-pack.d.ts +0 -1059
  334. package/dist/wire/receive-pack.d.ts.map +0 -1
  335. package/dist/wire/receive-pack.js +0 -1414
  336. package/dist/wire/receive-pack.js.map +0 -1
  337. package/dist/wire/smart-http.d.ts +0 -799
  338. package/dist/wire/smart-http.d.ts.map +0 -1
  339. package/dist/wire/smart-http.js +0 -945
  340. package/dist/wire/smart-http.js.map +0 -1
  341. package/dist/wire/upload-pack.d.ts +0 -727
  342. package/dist/wire/upload-pack.d.ts.map +0 -1
  343. package/dist/wire/upload-pack.js +0 -1138
  344. package/dist/wire/upload-pack.js.map +0 -1
@@ -1,1143 +0,0 @@
1
- /**
2
- * @fileoverview BashModule for Durable Object Integration
3
- *
4
- * This module provides a BashModule class that integrates with dotdo's $ WorkflowContext,
5
- * providing $.bash.exec(), $.bash.run(), and bash execution functionality.
6
- *
7
- * The module depends on FsModule for file system operations during command execution,
8
- * enabling sandboxed bash operations within the DO's virtual filesystem.
9
- *
10
- * Features:
11
- * - AST-based safety analysis for command parsing
12
- * - Configurable command blocking and confirmation requirements
13
- * - Support for database-backed execution policies
14
- *
15
- * @module do/BashModule
16
- *
17
- * @example
18
- * ```typescript
19
- * import { BashModule } from 'gitx.do/do'
20
- *
21
- * class MyDO extends DO {
22
- * bash = new BashModule({
23
- * executor: myExecutor,
24
- * fs: this.$.fs
25
- * })
26
- *
27
- * async buildProject() {
28
- * const result = await this.bash.exec('npm', ['run', 'build'])
29
- * if (result.exitCode !== 0) {
30
- * throw new Error(`Build failed: ${result.stderr}`)
31
- * }
32
- * }
33
- * }
34
- * ```
35
- */
36
- // Import AST-based safety analysis
37
- import { parseBashCommand, analyzeASTSafety, } from './bash-ast';
38
- // ============================================================================
39
- // BashModule Class
40
- // ============================================================================
41
- /**
42
- * BashModule class for integration with dotdo's $ WorkflowContext.
43
- *
44
- * @description
45
- * Provides bash execution functionality as a capability module that integrates
46
- * with dotdo's Durable Object framework. The module:
47
- *
48
- * - Depends on FsModule for file system operations during execution
49
- * - Delegates actual command execution to a configurable executor
50
- * - Provides safety analysis and command blocking
51
- * - Supports both exec (wait for completion) and spawn (streaming) modes
52
- *
53
- * @example
54
- * ```typescript
55
- * // In a Durable Object
56
- * class MyDO extends DO {
57
- * private bash: BashModule
58
- *
59
- * constructor(state: DurableObjectState, env: Env) {
60
- * super(state, env)
61
- * this.bash = new BashModule({
62
- * executor: containerExecutor,
63
- * fs: this.$.fs,
64
- * cwd: '/app'
65
- * })
66
- * }
67
- *
68
- * async fetch(request: Request) {
69
- * // Execute a command
70
- * const result = await this.bash.exec('npm', ['install'])
71
- *
72
- * // Run a script
73
- * await this.bash.run(`
74
- * set -e
75
- * npm run build
76
- * npm run test
77
- * `)
78
- *
79
- * return new Response('OK')
80
- * }
81
- * }
82
- * ```
83
- */
84
- export class BashModule {
85
- /**
86
- * Capability module name for identification.
87
- */
88
- name = 'bash';
89
- /**
90
- * The executor used for running commands.
91
- */
92
- executor;
93
- /**
94
- * Filesystem capability for file operations.
95
- */
96
- fs;
97
- /**
98
- * Default working directory.
99
- */
100
- defaultCwd;
101
- /**
102
- * Default timeout in milliseconds.
103
- */
104
- defaultTimeout;
105
- /**
106
- * List of blocked commands.
107
- */
108
- blockedCommands;
109
- /**
110
- * Whether to require confirmation for dangerous commands.
111
- */
112
- requireConfirmation;
113
- /**
114
- * Database storage for persistence.
115
- */
116
- storage;
117
- /**
118
- * Policy name for database operations.
119
- */
120
- policyName;
121
- /**
122
- * Database row ID for this policy.
123
- */
124
- policyId;
125
- /**
126
- * Allowed command patterns (regex).
127
- */
128
- allowedPatterns = [];
129
- /**
130
- * Denied command patterns (regex).
131
- */
132
- deniedPatterns = [];
133
- /**
134
- * Maximum concurrent executions.
135
- */
136
- maxConcurrent = 5;
137
- /**
138
- * Whether the policy is enabled.
139
- */
140
- enabled = true;
141
- /**
142
- * Whether to use AST-based safety analysis.
143
- */
144
- useAST;
145
- /**
146
- * Commands considered dangerous and requiring confirmation.
147
- */
148
- static DANGEROUS_COMMANDS = new Set([
149
- 'rm',
150
- 'rmdir',
151
- 'dd',
152
- 'mkfs',
153
- 'fdisk',
154
- 'format',
155
- 'shutdown',
156
- 'reboot',
157
- 'halt',
158
- 'poweroff',
159
- 'init',
160
- 'kill',
161
- 'killall',
162
- 'pkill',
163
- 'chmod',
164
- 'chown',
165
- 'chgrp',
166
- 'mount',
167
- 'umount',
168
- 'mkswap',
169
- 'swapon',
170
- 'swapoff',
171
- ]);
172
- /**
173
- * Critical patterns that should ALWAYS be blocked, regardless of confirmation.
174
- * These patterns represent commands that could cause catastrophic, irreversible damage.
175
- */
176
- static CRITICAL_PATTERNS = [
177
- /\brm\s+(-[rfvI]+\s+)*\/\s*$/, // rm -rf /
178
- /\brm\s+(-[rfvI]+\s+)*\/\*/, // rm -rf /*
179
- /\brm\s+.*--no-preserve-root/, // rm with --no-preserve-root
180
- /:\(\)\s*\{\s*:\s*\|\s*:\s*&\s*\}\s*;\s*:/, // fork bomb
181
- /\.\(\)\s*\{\s*\.\s*\|\s*\.\s*&\s*\}\s*;\s*\./, // fork bomb variant
182
- /\bdd\s+.*if=\/dev\/(u?random|zero)\s+.*of=\/dev\/[hs]d[a-z]/, // dd to disk
183
- /\bdd\s+.*of=\/dev\/[hs]d[a-z].*if=\/dev\/(u?random|zero)/, // dd to disk (reversed)
184
- /\bmkfs(\.\w+)?\s+(-[a-zA-Z]+\s+)*\/dev\/[hs]d[a-z]/, // mkfs on disk
185
- />\s*\/dev\/[hs]d[a-z]/, // redirect to disk device
186
- /echo\s+[cso]\s*>\s*\/proc\/sysrq-trigger/, // kernel panic trigger
187
- /\bcurl\s+.*\|\s*(ba)?sh\b/, // curl piped to shell
188
- /\bwget\s+.*\|\s*(ba)?sh\b/, // wget piped to shell
189
- ];
190
- /**
191
- * Dangerous flag patterns (require confirmation but can be executed with confirm).
192
- */
193
- static DANGEROUS_PATTERNS = [
194
- /rm\s+(-[rf]+\s+)*\/\w/, // rm with path starting from root (but not root itself)
195
- /rm\s+(-[rf]+\s+)*\*/, // rm with wildcard
196
- />\s*\/dev\//, // redirect to device
197
- /dd\s+.*of=\/dev/, // dd to device
198
- /chmod\s+777/, // overly permissive chmod
199
- ];
200
- /**
201
- * Create a new BashModule instance.
202
- *
203
- * @param options - Configuration options
204
- *
205
- * @example
206
- * ```typescript
207
- * const bash = new BashModule({
208
- * executor: containerExecutor,
209
- * fs: workflowContext.fs,
210
- * cwd: '/app'
211
- * })
212
- * ```
213
- */
214
- constructor(options = {}) {
215
- this.executor = options.executor;
216
- this.fs = options.fs;
217
- this.defaultCwd = options.cwd ?? '/';
218
- this.defaultTimeout = options.defaultTimeout ?? 30000;
219
- this.blockedCommands = new Set(options.blockedCommands ?? []);
220
- this.requireConfirmation = options.requireConfirmation ?? true;
221
- this.storage = options.storage;
222
- this.policyName = options.policyName ?? 'default';
223
- this.useAST = options.useAST ?? true;
224
- }
225
- /**
226
- * Optional initialization hook.
227
- * Called when the module is first loaded.
228
- * When storage is provided, loads or creates the execution policy from the database.
229
- */
230
- async initialize() {
231
- if (!this.storage)
232
- return;
233
- // Try to load existing policy
234
- const existingRows = this.storage.sql.exec('SELECT * FROM exec WHERE name = ?', this.policyName).toArray();
235
- if (existingRows.length > 0) {
236
- // Load existing settings
237
- const row = existingRows[0];
238
- this.policyId = row.id;
239
- this.loadFromRow(row);
240
- }
241
- else {
242
- // Create new policy with current settings
243
- await this.persistPolicy();
244
- }
245
- }
246
- /**
247
- * Load settings from a database row.
248
- */
249
- loadFromRow(row) {
250
- // Parse blocked commands from JSON string
251
- if (row.blocked_commands) {
252
- try {
253
- const commands = JSON.parse(row.blocked_commands);
254
- this.blockedCommands = new Set(commands);
255
- }
256
- catch {
257
- this.blockedCommands = new Set();
258
- }
259
- }
260
- this.requireConfirmation = row.require_confirmation === 1;
261
- this.defaultTimeout = row.default_timeout;
262
- this.defaultCwd = row.default_cwd;
263
- this.maxConcurrent = row.max_concurrent;
264
- this.enabled = row.enabled === 1;
265
- // Parse allowed patterns
266
- if (row.allowed_patterns) {
267
- try {
268
- const patterns = JSON.parse(row.allowed_patterns);
269
- this.allowedPatterns = patterns.map(p => new RegExp(p));
270
- }
271
- catch {
272
- this.allowedPatterns = [];
273
- }
274
- }
275
- // Parse denied patterns
276
- if (row.denied_patterns) {
277
- try {
278
- const patterns = JSON.parse(row.denied_patterns);
279
- this.deniedPatterns = patterns.map(p => new RegExp(p));
280
- }
281
- catch {
282
- this.deniedPatterns = [];
283
- }
284
- }
285
- }
286
- /**
287
- * Persist current policy settings to the database.
288
- */
289
- async persistPolicy() {
290
- if (!this.storage)
291
- return;
292
- const now = Date.now();
293
- const blockedCommandsJson = JSON.stringify(Array.from(this.blockedCommands));
294
- const allowedPatternsJson = this.allowedPatterns.length > 0
295
- ? JSON.stringify(this.allowedPatterns.map(p => p.source))
296
- : null;
297
- const deniedPatternsJson = this.deniedPatterns.length > 0
298
- ? JSON.stringify(this.deniedPatterns.map(p => p.source))
299
- : null;
300
- if (this.policyId) {
301
- // Update existing policy
302
- this.storage.sql.exec(`UPDATE exec SET
303
- blocked_commands = ?,
304
- require_confirmation = ?,
305
- default_timeout = ?,
306
- default_cwd = ?,
307
- allowed_patterns = ?,
308
- denied_patterns = ?,
309
- max_concurrent = ?,
310
- enabled = ?,
311
- updated_at = ?
312
- WHERE id = ?`, blockedCommandsJson, this.requireConfirmation ? 1 : 0, this.defaultTimeout, this.defaultCwd, allowedPatternsJson, deniedPatternsJson, this.maxConcurrent, this.enabled ? 1 : 0, now, this.policyId);
313
- }
314
- else {
315
- // Insert new policy
316
- this.storage.sql.exec(`INSERT INTO exec (
317
- name, blocked_commands, require_confirmation, default_timeout,
318
- default_cwd, allowed_patterns, denied_patterns, max_concurrent,
319
- enabled, created_at, updated_at
320
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, this.policyName, blockedCommandsJson, this.requireConfirmation ? 1 : 0, this.defaultTimeout, this.defaultCwd, allowedPatternsJson, deniedPatternsJson, this.maxConcurrent, this.enabled ? 1 : 0, now, now);
321
- // Get the inserted row ID
322
- const insertedRows = this.storage.sql.exec('SELECT id FROM exec WHERE name = ?', this.policyName).toArray();
323
- if (insertedRows.length > 0) {
324
- this.policyId = insertedRows[0].id;
325
- }
326
- }
327
- }
328
- /**
329
- * Optional cleanup hook.
330
- * Called when the capability is unloaded.
331
- */
332
- async dispose() {
333
- // Cleanup logic if needed
334
- }
335
- /**
336
- * Check if FsCapability is available.
337
- *
338
- * @returns True if FsCapability is configured
339
- */
340
- get hasFsCapability() {
341
- return this.fs !== undefined;
342
- }
343
- /**
344
- * Check if an executor is available.
345
- *
346
- * @returns True if an executor is configured
347
- */
348
- get hasExecutor() {
349
- return this.executor !== undefined;
350
- }
351
- /**
352
- * Execute a command and wait for completion.
353
- *
354
- * @param command - The command to execute (e.g., 'git', 'npm', 'ls')
355
- * @param args - Optional array of command arguments
356
- * @param options - Optional execution options
357
- * @returns Promise resolving to the execution result
358
- *
359
- * @example
360
- * ```typescript
361
- * // Simple command
362
- * const result = await bash.exec('ls')
363
- *
364
- * // With arguments
365
- * const result = await bash.exec('git', ['status', '--short'])
366
- *
367
- * // With options
368
- * const result = await bash.exec('npm', ['install'], {
369
- * cwd: '/app',
370
- * timeout: 60000
371
- * })
372
- * ```
373
- */
374
- async exec(command, args, options) {
375
- // Build full command string
376
- const fullCommand = args && args.length > 0
377
- ? `${command} ${args.map(a => this.escapeArg(a)).join(' ')}`
378
- : command;
379
- // Check if command is blocked
380
- const baseCommand = this.extractBaseCommand(command);
381
- if (this.blockedCommands.has(baseCommand)) {
382
- return {
383
- command: fullCommand,
384
- stdout: '',
385
- stderr: `Command '${baseCommand}' is blocked`,
386
- exitCode: 1,
387
- blocked: true,
388
- blockReason: `Command '${baseCommand}' is in the blocked list`
389
- };
390
- }
391
- // Check safety
392
- const safety = this.analyze(fullCommand);
393
- // Critical commands are ALWAYS blocked, regardless of confirmation
394
- if (safety.safetyLevel === 'critical') {
395
- return {
396
- command: fullCommand,
397
- stdout: '',
398
- stderr: safety.reason ?? 'Critical command is always blocked',
399
- exitCode: 1,
400
- blocked: true,
401
- blockReason: safety.reason ?? 'Critical command cannot be executed even with confirmation'
402
- };
403
- }
404
- // Dangerous commands require confirmation
405
- if (safety.dangerous && this.requireConfirmation && !options?.confirm) {
406
- return {
407
- command: fullCommand,
408
- stdout: '',
409
- stderr: safety.reason ?? 'Command requires confirmation',
410
- exitCode: 1,
411
- blocked: true,
412
- blockReason: safety.reason ?? 'Dangerous command requires confirmation'
413
- };
414
- }
415
- // Dry run mode
416
- if (options?.dryRun) {
417
- return {
418
- command: fullCommand,
419
- stdout: `[dry-run] Would execute: ${fullCommand}`,
420
- stderr: '',
421
- exitCode: 0
422
- };
423
- }
424
- // Check for executor
425
- if (!this.executor) {
426
- return {
427
- command: fullCommand,
428
- stdout: '',
429
- stderr: 'No executor configured',
430
- exitCode: 1
431
- };
432
- }
433
- // Merge options with defaults
434
- const execOptions = {
435
- timeout: this.defaultTimeout,
436
- cwd: this.defaultCwd,
437
- ...options
438
- };
439
- // Execute the command
440
- try {
441
- return await this.executor.execute(fullCommand, execOptions);
442
- }
443
- catch (error) {
444
- const errorMessage = error instanceof Error ? error.message : String(error);
445
- return {
446
- command: fullCommand,
447
- stdout: '',
448
- stderr: errorMessage,
449
- exitCode: 1
450
- };
451
- }
452
- }
453
- /**
454
- * Spawn a command for streaming execution.
455
- *
456
- * @param command - The command to spawn
457
- * @param args - Optional array of command arguments
458
- * @param options - Optional spawn options including stream callbacks
459
- * @returns Promise resolving to a spawn handle
460
- *
461
- * @example
462
- * ```typescript
463
- * const handle = await bash.spawn('tail', ['-f', '/var/log/app.log'], {
464
- * onStdout: (chunk) => console.log(chunk),
465
- * onStderr: (chunk) => console.error(chunk)
466
- * })
467
- *
468
- * // Later, stop the process
469
- * handle.kill()
470
- *
471
- * // Wait for it to finish
472
- * const result = await handle.done
473
- * ```
474
- */
475
- async spawn(command, args, options) {
476
- if (!this.executor?.spawn) {
477
- throw new Error('Spawn not supported by this executor');
478
- }
479
- // Check if command is blocked
480
- const baseCommand = this.extractBaseCommand(command);
481
- if (this.blockedCommands.has(baseCommand)) {
482
- throw new Error(`Command '${baseCommand}' is blocked`);
483
- }
484
- // Check safety
485
- const fullCommand = args && args.length > 0
486
- ? `${command} ${args.join(' ')}`
487
- : command;
488
- const safety = this.analyze(fullCommand);
489
- // Critical commands are ALWAYS blocked, regardless of confirmation
490
- if (safety.safetyLevel === 'critical') {
491
- throw new Error(safety.reason ?? 'Critical command cannot be executed even with confirmation');
492
- }
493
- // Dangerous commands require confirmation
494
- if (safety.dangerous && this.requireConfirmation && !options?.confirm) {
495
- throw new Error(safety.reason ?? 'Dangerous command requires confirmation');
496
- }
497
- return this.executor.spawn(command, args, options);
498
- }
499
- /**
500
- * Run a shell script.
501
- *
502
- * @param script - The bash script to execute
503
- * @param options - Optional execution options
504
- * @returns Promise resolving to the execution result
505
- *
506
- * @example
507
- * ```typescript
508
- * const result = await bash.run(`
509
- * set -e
510
- * cd /app
511
- * npm install
512
- * npm run build
513
- * `)
514
- * ```
515
- */
516
- async run(script, options) {
517
- // Analyze script safety first (before dry-run check)
518
- const safety = this.analyze(script);
519
- // Critical commands are ALWAYS blocked, regardless of confirmation or dry-run
520
- if (safety.safetyLevel === 'critical') {
521
- return {
522
- command: script,
523
- stdout: '',
524
- stderr: safety.reason ?? 'Critical command is always blocked',
525
- exitCode: 1,
526
- blocked: true,
527
- blockReason: safety.reason ?? 'Critical command cannot be executed even with confirmation'
528
- };
529
- }
530
- // Dry run mode
531
- if (options?.dryRun) {
532
- return {
533
- command: script,
534
- stdout: `[dry-run] Would execute script:\n${script}`,
535
- stderr: '',
536
- exitCode: 0
537
- };
538
- }
539
- // Check for executor
540
- if (!this.executor) {
541
- return {
542
- command: script,
543
- stdout: '',
544
- stderr: 'No executor configured',
545
- exitCode: 1
546
- };
547
- }
548
- // Dangerous commands require confirmation
549
- if (safety.dangerous && this.requireConfirmation && !options?.confirm) {
550
- return {
551
- command: script,
552
- stdout: '',
553
- stderr: safety.reason ?? 'Script requires confirmation',
554
- exitCode: 1,
555
- blocked: true,
556
- blockReason: safety.reason ?? 'Dangerous script requires confirmation'
557
- };
558
- }
559
- // Execute the script
560
- const execOptions = {
561
- timeout: this.defaultTimeout,
562
- cwd: this.defaultCwd,
563
- ...options
564
- };
565
- try {
566
- return await this.executor.execute(script, execOptions);
567
- }
568
- catch (error) {
569
- const errorMessage = error instanceof Error ? error.message : String(error);
570
- return {
571
- command: script,
572
- stdout: '',
573
- stderr: errorMessage,
574
- exitCode: 1
575
- };
576
- }
577
- }
578
- /**
579
- * Analyze a command for safety.
580
- *
581
- * Uses AST-based analysis by default for more accurate command parsing
582
- * and safety classification. Falls back to regex-based analysis if
583
- * useAST is disabled.
584
- *
585
- * @param input - The command or script to analyze
586
- * @returns Safety analysis result
587
- *
588
- * @example
589
- * ```typescript
590
- * const analysis = bash.analyze('rm -rf /')
591
- * if (analysis.dangerous) {
592
- * console.warn(analysis.reason)
593
- * }
594
- * ```
595
- */
596
- analyze(input) {
597
- // Use AST-based analysis if enabled
598
- if (this.useAST) {
599
- return this.analyzeWithAST(input);
600
- }
601
- // Fall back to regex-based analysis
602
- return this.analyzeWithRegex(input);
603
- }
604
- /**
605
- * Analyze a command using AST-based parsing.
606
- *
607
- * Parses the command into an AST and inspects nodes for safety issues.
608
- * This provides more accurate analysis than regex patterns because it
609
- * understands command structure, arguments, and pipelines.
610
- *
611
- * @param input - The command or script to analyze
612
- * @returns Safety analysis result with AST details
613
- * @internal
614
- */
615
- analyzeWithAST(input) {
616
- try {
617
- const ast = parseBashCommand(input);
618
- const astAnalysis = analyzeASTSafety(ast, this.blockedCommands, input);
619
- return {
620
- dangerous: astAnalysis.dangerous,
621
- safetyLevel: astAnalysis.safetyLevel,
622
- reason: astAnalysis.reason,
623
- commands: astAnalysis.commands,
624
- impact: astAnalysis.impact,
625
- issues: astAnalysis.issues,
626
- usedAST: true,
627
- };
628
- }
629
- catch {
630
- // If AST parsing fails, fall back to regex analysis
631
- return this.analyzeWithRegex(input);
632
- }
633
- }
634
- /**
635
- * Analyze a command using regex patterns.
636
- *
637
- * This is the fallback analysis method when AST parsing is disabled
638
- * or fails. It uses simple pattern matching.
639
- *
640
- * @param input - The command or script to analyze
641
- * @returns Safety analysis result
642
- * @internal
643
- */
644
- analyzeWithRegex(input) {
645
- const commands = this.extractCommands(input);
646
- let dangerous = false;
647
- let reason;
648
- let impact = 'none';
649
- let safetyLevel = 'safe';
650
- // Check for critical patterns first (these are ALWAYS blocked)
651
- for (const pattern of BashModule.CRITICAL_PATTERNS) {
652
- if (pattern.test(input)) {
653
- dangerous = true;
654
- reason = `Critical command pattern detected - cannot be executed`;
655
- impact = 'critical';
656
- safetyLevel = 'critical';
657
- break;
658
- }
659
- }
660
- // Check for blocked commands (highest priority after critical)
661
- if (!dangerous) {
662
- for (const cmd of commands) {
663
- if (this.blockedCommands.has(cmd)) {
664
- dangerous = true;
665
- reason = `Command '${cmd}' is blocked`;
666
- impact = 'critical';
667
- safetyLevel = 'dangerous';
668
- break;
669
- }
670
- }
671
- }
672
- // Check for dangerous patterns (critical impact - check before DANGEROUS_COMMANDS)
673
- if (!dangerous) {
674
- for (const pattern of BashModule.DANGEROUS_PATTERNS) {
675
- if (pattern.test(input)) {
676
- dangerous = true;
677
- reason = `Command matches dangerous pattern: ${pattern.source}`;
678
- impact = 'critical';
679
- safetyLevel = 'dangerous';
680
- break;
681
- }
682
- }
683
- }
684
- // Check for dangerous commands (high impact)
685
- if (!dangerous) {
686
- for (const cmd of commands) {
687
- if (BashModule.DANGEROUS_COMMANDS.has(cmd)) {
688
- dangerous = true;
689
- reason = `Command '${cmd}' is potentially dangerous`;
690
- impact = 'high';
691
- safetyLevel = 'dangerous';
692
- break;
693
- }
694
- }
695
- }
696
- // Determine impact based on commands
697
- if (!dangerous) {
698
- if (commands.some(c => ['cat', 'ls', 'pwd', 'echo', 'head', 'tail', 'wc'].includes(c))) {
699
- impact = 'none';
700
- }
701
- else if (commands.some(c => ['touch', 'mkdir', 'cp'].includes(c))) {
702
- impact = 'low';
703
- }
704
- else if (commands.some(c => ['mv', 'sed', 'awk'].includes(c))) {
705
- impact = 'medium';
706
- }
707
- }
708
- return {
709
- dangerous,
710
- safetyLevel,
711
- reason,
712
- commands,
713
- impact,
714
- usedAST: false,
715
- };
716
- }
717
- /**
718
- * Check if a command is dangerous.
719
- *
720
- * @param input - The command to check
721
- * @returns Object indicating if dangerous and why
722
- *
723
- * @example
724
- * ```typescript
725
- * const check = bash.isDangerous('rm -rf /')
726
- * if (check.dangerous) {
727
- * console.warn(check.reason)
728
- * }
729
- * ```
730
- */
731
- isDangerous(input) {
732
- const analysis = this.analyze(input);
733
- return {
734
- dangerous: analysis.dangerous,
735
- reason: analysis.reason
736
- };
737
- }
738
- /**
739
- * Add a command to the blocked list.
740
- * Persists the change to the database if storage is configured.
741
- *
742
- * @param command - Command to block
743
- */
744
- block(command) {
745
- this.blockedCommands.add(command);
746
- // Persist to database asynchronously
747
- this.persistPolicy().catch(() => {
748
- // Silently ignore persistence errors
749
- });
750
- }
751
- /**
752
- * Remove a command from the blocked list.
753
- * Persists the change to the database if storage is configured.
754
- *
755
- * @param command - Command to unblock
756
- */
757
- unblock(command) {
758
- this.blockedCommands.delete(command);
759
- // Persist to database asynchronously
760
- this.persistPolicy().catch(() => {
761
- // Silently ignore persistence errors
762
- });
763
- }
764
- /**
765
- * Get the list of blocked commands.
766
- *
767
- * @returns Array of blocked command names
768
- */
769
- getBlockedCommands() {
770
- return Array.from(this.blockedCommands);
771
- }
772
- /**
773
- * Get the current execution policy.
774
- *
775
- * @returns Current policy configuration
776
- */
777
- getPolicy() {
778
- return {
779
- name: this.policyName,
780
- blockedCommands: Array.from(this.blockedCommands),
781
- requireConfirmation: this.requireConfirmation,
782
- defaultTimeout: this.defaultTimeout,
783
- defaultCwd: this.defaultCwd,
784
- allowedPatterns: this.allowedPatterns.map(p => p.source),
785
- deniedPatterns: this.deniedPatterns.map(p => p.source),
786
- maxConcurrent: this.maxConcurrent,
787
- enabled: this.enabled
788
- };
789
- }
790
- /**
791
- * Update the execution policy.
792
- * Persists the changes to the database if storage is configured.
793
- *
794
- * @param policy - Partial policy configuration to update
795
- */
796
- async updatePolicy(policy) {
797
- if (policy.blockedCommands !== undefined) {
798
- this.blockedCommands = new Set(policy.blockedCommands);
799
- }
800
- if (policy.requireConfirmation !== undefined) {
801
- this.requireConfirmation = policy.requireConfirmation;
802
- }
803
- if (policy.defaultTimeout !== undefined) {
804
- this.defaultTimeout = policy.defaultTimeout;
805
- }
806
- if (policy.defaultCwd !== undefined) {
807
- this.defaultCwd = policy.defaultCwd;
808
- }
809
- if (policy.allowedPatterns !== undefined) {
810
- this.allowedPatterns = policy.allowedPatterns.map(p => new RegExp(p));
811
- }
812
- if (policy.deniedPatterns !== undefined) {
813
- this.deniedPatterns = policy.deniedPatterns.map(p => new RegExp(p));
814
- }
815
- if (policy.maxConcurrent !== undefined) {
816
- this.maxConcurrent = policy.maxConcurrent;
817
- }
818
- if (policy.enabled !== undefined) {
819
- this.enabled = policy.enabled;
820
- }
821
- await this.persistPolicy();
822
- }
823
- /**
824
- * Check if the policy is enabled.
825
- *
826
- * @returns True if the policy is enabled
827
- */
828
- isEnabled() {
829
- return this.enabled;
830
- }
831
- /**
832
- * Check if database storage is available.
833
- *
834
- * @returns True if storage is configured
835
- */
836
- hasStorage() {
837
- return this.storage !== undefined;
838
- }
839
- // ═══════════════════════════════════════════════════════════════════════════
840
- // Private Helper Methods
841
- // ═══════════════════════════════════════════════════════════════════════════
842
- /**
843
- * Extract the base command name from a command string.
844
- */
845
- extractBaseCommand(command) {
846
- const parts = command.trim().split(/\s+/);
847
- const first = parts[0] ?? '';
848
- // Handle paths like /usr/bin/rm
849
- const name = first.split('/').pop() ?? first;
850
- return name;
851
- }
852
- /**
853
- * Extract all command names from a script.
854
- */
855
- extractCommands(input) {
856
- const commands = [];
857
- // Split by common separators
858
- const segments = input.split(/[;&|]+/);
859
- for (const segment of segments) {
860
- const trimmed = segment.trim();
861
- if (!trimmed)
862
- continue;
863
- // Skip comments
864
- if (trimmed.startsWith('#'))
865
- continue;
866
- // Get the first word
867
- const match = trimmed.match(/^(\S+)/);
868
- if (match) {
869
- const cmd = match[1];
870
- // Handle paths
871
- const name = cmd.split('/').pop() ?? cmd;
872
- // Skip shell keywords
873
- if (!['if', 'then', 'else', 'fi', 'for', 'do', 'done', 'while', 'until', 'case', 'esac', 'function'].includes(name)) {
874
- commands.push(name);
875
- }
876
- }
877
- }
878
- return commands;
879
- }
880
- /**
881
- * Escape an argument for safe shell use.
882
- */
883
- escapeArg(arg) {
884
- // If the argument contains no special characters, return as-is
885
- if (/^[a-zA-Z0-9._\-/=]+$/.test(arg)) {
886
- return arg;
887
- }
888
- // Otherwise, single-quote escape
889
- return `'${arg.replace(/'/g, "'\\''")}'`;
890
- }
891
- // ═══════════════════════════════════════════════════════════════════════════
892
- // Tagged Template Literal Support
893
- // ═══════════════════════════════════════════════════════════════════════════
894
- /**
895
- * Tagged template literal for safe bash command execution.
896
- *
897
- * This method allows using template literal syntax for bash commands with
898
- * automatic variable interpolation and escaping. Variables are safely
899
- * escaped to prevent shell injection attacks.
900
- *
901
- * @param strings - Template literal string parts
902
- * @param values - Interpolated values
903
- * @returns Promise resolving to the execution result
904
- *
905
- * @example
906
- * ```typescript
907
- * // Simple usage
908
- * const result = await this.$.bash`ls -la`
909
- *
910
- * // With interpolation
911
- * const dir = '/tmp/my folder'
912
- * const result = await this.$.bash`ls -la ${dir}`
913
- *
914
- * // With multiple variables
915
- * const src = 'file.txt'
916
- * const dest = 'backup/file.txt'
917
- * const result = await this.$.bash`cp ${src} ${dest}`
918
- * ```
919
- */
920
- tag(strings, ...values) {
921
- const command = this.buildCommandFromTemplate(strings, values);
922
- return this.run(command);
923
- }
924
- /**
925
- * Build a command string from template literal parts with safe escaping.
926
- *
927
- * @param strings - Template literal string parts
928
- * @param values - Interpolated values
929
- * @returns The constructed command string with escaped values
930
- * @internal
931
- */
932
- buildCommandFromTemplate(strings, values) {
933
- let result = '';
934
- for (let i = 0; i < strings.length; i++) {
935
- result += strings[i];
936
- if (i < values.length) {
937
- const value = values[i];
938
- result += this.escapeTemplateValue(value);
939
- }
940
- }
941
- return result;
942
- }
943
- /**
944
- * Escape a template literal value for safe shell interpolation.
945
- *
946
- * Handles various types of values:
947
- * - null/undefined: empty string
948
- * - string: escaped with single quotes if needed
949
- * - number/boolean: converted to string directly
950
- * - array: each element escaped and joined with spaces
951
- * - object: JSON stringified and escaped
952
- *
953
- * @param value - The value to escape
954
- * @returns The escaped string representation
955
- * @internal
956
- */
957
- escapeTemplateValue(value) {
958
- // Handle null/undefined
959
- if (value === null || value === undefined) {
960
- return '';
961
- }
962
- // Handle numbers and booleans - safe to use directly
963
- if (typeof value === 'number' || typeof value === 'boolean') {
964
- return String(value);
965
- }
966
- // Handle arrays - escape each element and join
967
- if (Array.isArray(value)) {
968
- return value.map(v => this.escapeTemplateValue(v)).join(' ');
969
- }
970
- // Handle objects (except arrays) - JSON stringify and escape
971
- if (typeof value === 'object') {
972
- return this.escapeShellString(JSON.stringify(value));
973
- }
974
- // Handle strings
975
- return this.escapeShellString(String(value));
976
- }
977
- /**
978
- * Escape a string for safe shell use.
979
- *
980
- * Uses single-quote escaping which is the safest form of escaping
981
- * for bash. Single quotes prevent all special character interpretation
982
- * except for the single quote itself.
983
- *
984
- * @param str - The string to escape
985
- * @returns The escaped string
986
- * @internal
987
- */
988
- escapeShellString(str) {
989
- // If the string is empty, return empty quoted string
990
- if (str === '') {
991
- return "''";
992
- }
993
- // If the string contains no special characters, return as-is
994
- // This is more readable for simple cases like file paths without spaces
995
- if (/^[a-zA-Z0-9._\-/=@:]+$/.test(str)) {
996
- return str;
997
- }
998
- // Otherwise, use single-quote escaping
999
- // Single quotes prevent all interpretation except ' itself
1000
- // To include a single quote, we end the quoted string, add an escaped quote, and start a new quoted string
1001
- // 'It'\''s' -> It's
1002
- return `'${str.replace(/'/g, "'\\''")}'`;
1003
- }
1004
- }
1005
- // ============================================================================
1006
- // Factory Functions
1007
- // ============================================================================
1008
- /**
1009
- * Create a BashModule instance with the given options.
1010
- *
1011
- * @param options - Configuration options for the module
1012
- * @returns A new BashModule instance
1013
- *
1014
- * @example
1015
- * ```typescript
1016
- * import { createBashModule } from 'gitx.do/do'
1017
- *
1018
- * const bash = createBashModule({
1019
- * executor: containerExecutor,
1020
- * fs: workflowContext.fs,
1021
- * cwd: '/app'
1022
- * })
1023
- * ```
1024
- */
1025
- export function createBashModule(options = {}) {
1026
- return new BashModule(options);
1027
- }
1028
- /**
1029
- * Create a callable BashModule instance that supports tagged template literals.
1030
- *
1031
- * This factory creates a BashModule wrapped in a Proxy that allows both:
1032
- * - Standard method calls: `bash.exec('ls', ['-la'])`
1033
- * - Tagged template syntax: `bash\`ls -la ${dir}\``
1034
- *
1035
- * The tagged template syntax automatically escapes interpolated values
1036
- * to prevent shell injection attacks.
1037
- *
1038
- * @param options - Configuration options for the module
1039
- * @returns A callable BashModule instance
1040
- *
1041
- * @example
1042
- * ```typescript
1043
- * import { createCallableBashModule } from 'gitx.do/do'
1044
- *
1045
- * // In a Durable Object
1046
- * class MyDO extends DO {
1047
- * bash = createCallableBashModule({
1048
- * executor: containerExecutor,
1049
- * fs: this.$.fs,
1050
- * cwd: '/app'
1051
- * })
1052
- *
1053
- * async listFiles(dir: string) {
1054
- * // Use tagged template syntax
1055
- * const result = await this.bash`ls -la ${dir}`
1056
- * return result.stdout
1057
- * }
1058
- *
1059
- * async runWithArgs() {
1060
- * // Or use regular methods
1061
- * const result = await this.bash.exec('npm', ['install'])
1062
- * return result
1063
- * }
1064
- * }
1065
- * ```
1066
- *
1067
- * @example
1068
- * ```typescript
1069
- * // Handle special characters safely
1070
- * const filename = "file with 'quotes' and spaces"
1071
- * const result = await bash`cat ${filename}`
1072
- * // Executes: cat 'file with '\''quotes'\'' and spaces'
1073
- * ```
1074
- */
1075
- export function createCallableBashModule(options = {}) {
1076
- const module = new BashModule(options);
1077
- // Create a function that calls the tag method
1078
- const tagFn = (strings, ...values) => {
1079
- return module.tag(strings, ...values);
1080
- };
1081
- // Create a Proxy that makes the module callable
1082
- return new Proxy(tagFn, {
1083
- // Forward property access to the module
1084
- get(target, prop, receiver) {
1085
- if (prop in module) {
1086
- const value = module[prop];
1087
- // Bind methods to the module
1088
- if (typeof value === 'function') {
1089
- return value.bind(module);
1090
- }
1091
- return value;
1092
- }
1093
- return Reflect.get(target, prop, receiver);
1094
- },
1095
- // Forward property setting to the module
1096
- set(target, prop, value) {
1097
- if (prop in module) {
1098
- module[prop] = value;
1099
- return true;
1100
- }
1101
- return Reflect.set(target, prop, value);
1102
- },
1103
- // Forward has checks to the module
1104
- has(target, prop) {
1105
- return prop in module || Reflect.has(target, prop);
1106
- },
1107
- // Make instanceof work
1108
- getPrototypeOf() {
1109
- return BashModule.prototype;
1110
- },
1111
- // Forward apply to the tag function
1112
- apply(target, _thisArg, args) {
1113
- return target(...args);
1114
- },
1115
- });
1116
- }
1117
- // ============================================================================
1118
- // Type Guards
1119
- // ============================================================================
1120
- /**
1121
- * Check if a value is a BashModule instance.
1122
- *
1123
- * @param value - Value to check
1124
- * @returns True if value is a BashModule
1125
- */
1126
- export function isBashModule(value) {
1127
- return value instanceof BashModule;
1128
- }
1129
- /**
1130
- * Check if a value is a CallableBashModule.
1131
- *
1132
- * @param value - Value to check
1133
- * @returns True if value is a CallableBashModule
1134
- */
1135
- export function isCallableBashModule(value) {
1136
- if (typeof value !== 'function')
1137
- return false;
1138
- if (!('name' in value))
1139
- return false;
1140
- const maybeBash = value;
1141
- return maybeBash.name === 'bash' && 'exec' in value && typeof maybeBash.exec === 'function';
1142
- }
1143
- //# sourceMappingURL=BashModule.js.map