dotdo 0.0.2 → 0.1.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 (313) hide show
  1. package/cli/README.md +238 -0
  2. package/cli/agent.ts +72 -0
  3. package/cli/bin.js +44 -0
  4. package/cli/bin.ts +38 -0
  5. package/cli/build.ts +157 -0
  6. package/cli/commands/auth/login.ts +14 -0
  7. package/cli/commands/auth/logout.ts +6 -0
  8. package/cli/commands/auth/whoami.ts +16 -0
  9. package/cli/commands/deploy-multi.ts +245 -0
  10. package/cli/commands/dev/deploy.ts +100 -0
  11. package/cli/commands/dev/dev.ts +95 -0
  12. package/cli/commands/dev/logs.ts +91 -0
  13. package/cli/commands/dev-local.ts +88 -0
  14. package/cli/commands/do-ops.ts +314 -0
  15. package/cli/commands/index.ts +100 -0
  16. package/cli/commands/init.ts +247 -0
  17. package/cli/commands/introspect/emitter.ts +315 -0
  18. package/cli/commands/introspect/index.ts +193 -0
  19. package/cli/commands/link.ts +598 -0
  20. package/cli/commands/snippets.ts +415 -0
  21. package/cli/commands/tunnel.ts +239 -0
  22. package/cli/device-auth.ts +289 -0
  23. package/cli/fallback.ts +12 -0
  24. package/cli/index.ts +121 -0
  25. package/cli/main.ts +246 -0
  26. package/cli/mcp-stdio.ts +790 -0
  27. package/cli/package.json +62 -0
  28. package/cli/runtime/do-registry.ts +193 -0
  29. package/cli/runtime/embedded-db.ts +344 -0
  30. package/cli/runtime/index.ts +9 -0
  31. package/cli/runtime/miniflare-adapter.ts +162 -0
  32. package/cli/sandbox.ts +82 -0
  33. package/cli/src/args.ts +174 -0
  34. package/cli/src/auth.ts +55 -0
  35. package/cli/src/commands/call.ts +84 -0
  36. package/cli/src/commands/charge.ts +96 -0
  37. package/cli/src/commands/config.ts +115 -0
  38. package/cli/src/commands/email.ts +112 -0
  39. package/cli/src/commands/llm.ts +115 -0
  40. package/cli/src/commands/queue.ts +134 -0
  41. package/cli/src/commands/text.ts +86 -0
  42. package/cli/src/config.ts +185 -0
  43. package/cli/src/output.ts +246 -0
  44. package/cli/src/rpc.ts +192 -0
  45. package/cli/utils/config.ts +282 -0
  46. package/cli/utils/detect.ts +73 -0
  47. package/cli/utils/index.ts +15 -0
  48. package/cli/utils/logger.ts +232 -0
  49. package/dist/ai/template-literals.js +2 -2
  50. package/dist/ai/template-literals.js.map +1 -1
  51. package/dist/api/middleware/auth.js +3 -2
  52. package/dist/api/middleware/auth.js.map +1 -1
  53. package/dist/db/iceberg/inverted-index.js +1 -1
  54. package/dist/db/iceberg/inverted-index.js.map +1 -1
  55. package/dist/db/iceberg/puffin.js.map +1 -1
  56. package/dist/db/json-indexes.js.map +1 -1
  57. package/dist/db/objects.js.map +1 -1
  58. package/dist/db/primitives/dag-scheduler/index.js +1 -1
  59. package/dist/db/primitives/dag-scheduler/index.js.map +1 -1
  60. package/dist/db/primitives/observability.js.map +1 -1
  61. package/dist/db/primitives/schema-evolution.js.map +1 -1
  62. package/dist/db/primitives/temporal-store.js.map +1 -1
  63. package/dist/db/primitives/typed-column-store.js.map +1 -1
  64. package/dist/db/primitives/utils/duration.js.map +1 -1
  65. package/dist/db/primitives/utils/murmur3.js +12 -14
  66. package/dist/db/primitives/utils/murmur3.js.map +1 -1
  67. package/dist/db/primitives/window-manager.js.map +1 -1
  68. package/dist/db/stores.js.map +1 -1
  69. package/dist/db/things.js.map +1 -1
  70. package/dist/lib/DODispatcher.js +2 -2
  71. package/dist/lib/DODispatcher.js.map +1 -1
  72. package/dist/lib/auto-wiring.js.map +1 -1
  73. package/dist/lib/channels/email.js +1 -1
  74. package/dist/lib/channels/email.js.map +1 -1
  75. package/dist/lib/channels/slack-blockkit.js.map +1 -1
  76. package/dist/lib/cloudflare/ai.js +1 -1
  77. package/dist/lib/cloudflare/ai.js.map +1 -1
  78. package/dist/lib/cloudflare/kv.js +1 -1
  79. package/dist/lib/cloudflare/kv.js.map +1 -1
  80. package/dist/lib/cloudflare/r2.js +3 -3
  81. package/dist/lib/cloudflare/r2.js.map +1 -1
  82. package/dist/lib/cloudflare/vectorize.js.map +1 -1
  83. package/dist/lib/cloudflare/workflows.js.map +1 -1
  84. package/dist/lib/executors/AgenticFunctionExecutor.js.map +1 -1
  85. package/dist/lib/executors/CodeFunctionExecutor.js.map +1 -1
  86. package/dist/lib/executors/GenerativeFunctionExecutor.js.map +1 -1
  87. package/dist/lib/executors/HumanFunctionExecutor.js +1 -1
  88. package/dist/lib/executors/HumanFunctionExecutor.js.map +1 -1
  89. package/dist/lib/executors/ParallelStepExecutor.js.map +1 -1
  90. package/dist/lib/experiments.js.map +1 -1
  91. package/dist/lib/flags/store.js.map +1 -1
  92. package/dist/lib/functions/FunctionComposition.js.map +1 -1
  93. package/dist/lib/functions/FunctionMiddleware.js.map +1 -1
  94. package/dist/lib/functions/FunctionRegistry.js.map +1 -1
  95. package/dist/lib/humans/templates.js.map +1 -1
  96. package/dist/lib/identity.js +2 -2
  97. package/dist/lib/identity.js.map +1 -1
  98. package/dist/lib/logging/index.js.map +1 -1
  99. package/dist/lib/mixins/bash.js +1 -73
  100. package/dist/lib/mixins/bash.js.map +1 -1
  101. package/dist/lib/mixins/git.js +0 -5
  102. package/dist/lib/mixins/git.js.map +1 -1
  103. package/dist/lib/mixins/npm.js.map +1 -1
  104. package/dist/lib/noun-id.js.map +1 -1
  105. package/dist/lib/rate-limit/sliding-window.js.map +1 -1
  106. package/dist/lib/rpc/bindings.js.map +1 -1
  107. package/dist/lib/safe-stringify.js.map +1 -1
  108. package/dist/lib/sandbox/miniflare-sandbox.js.map +1 -1
  109. package/dist/lib/sqids.js.map +1 -1
  110. package/dist/lib/sql/adapters/node-sql-parser.js.map +1 -1
  111. package/dist/lib/sql/adapters/pgsql-parser.js +19 -18
  112. package/dist/lib/sql/adapters/pgsql-parser.js.map +1 -1
  113. package/dist/metrics/hunch.js.map +1 -1
  114. package/dist/objects/API.js +1 -1
  115. package/dist/objects/API.js.map +1 -1
  116. package/dist/objects/Agent.js.map +1 -1
  117. package/dist/objects/Browser.js.map +1 -1
  118. package/dist/objects/CLI.js.map +1 -1
  119. package/dist/objects/DOBase.js.map +1 -1
  120. package/dist/objects/DOCache.js +153 -0
  121. package/dist/objects/DOCache.js.map +1 -0
  122. package/dist/objects/DOFull.js.map +1 -1
  123. package/dist/objects/Entity.js.map +1 -1
  124. package/dist/objects/Human.js.map +1 -1
  125. package/dist/objects/IcebergMetadataDO.js.map +1 -1
  126. package/dist/objects/IntegrationsDO.js.map +1 -1
  127. package/dist/objects/ObservabilityBroadcaster.js.map +1 -1
  128. package/dist/objects/Package.js.map +1 -1
  129. package/dist/objects/Product.js +1 -1
  130. package/dist/objects/Product.js.map +1 -1
  131. package/dist/objects/SaaS.js.map +1 -1
  132. package/dist/objects/SandboxDO.js.map +1 -1
  133. package/dist/objects/Service.js.map +1 -1
  134. package/dist/objects/VectorShardDO.js +9 -7
  135. package/dist/objects/VectorShardDO.js.map +1 -1
  136. package/dist/objects/Workflow.js.map +1 -1
  137. package/dist/objects/WorkflowFactory.js.map +1 -1
  138. package/dist/objects/WorkflowRuntime.js.map +1 -1
  139. package/dist/objects/lifecycle/Branch.js.map +1 -1
  140. package/dist/objects/lifecycle/Clone.js +1 -1
  141. package/dist/objects/lifecycle/Clone.js.map +1 -1
  142. package/dist/objects/lifecycle/Compact.js.map +1 -1
  143. package/dist/objects/lifecycle/Shard.js.map +1 -1
  144. package/dist/objects/persistence/checkpoint-manager.js.map +1 -1
  145. package/dist/objects/persistence/migration-runner.js.map +1 -1
  146. package/dist/objects/persistence/replication-manager.js +2 -2
  147. package/dist/objects/persistence/replication-manager.js.map +1 -1
  148. package/dist/objects/persistence/tiered-storage-manager.js.map +1 -1
  149. package/dist/objects/persistence/wal-manager.js.map +1 -1
  150. package/dist/objects/transport/auth-layer.js.map +1 -1
  151. package/dist/objects/transport/chain.js.map +1 -1
  152. package/dist/objects/transport/mcp-server.js +7 -6
  153. package/dist/objects/transport/mcp-server.js.map +1 -1
  154. package/dist/objects/transport/rest-autowire.js +3 -2
  155. package/dist/objects/transport/rest-autowire.js.map +1 -1
  156. package/dist/objects/transport/rest-router.js.map +1 -1
  157. package/dist/objects/transport/rpc-server.js +18 -15
  158. package/dist/objects/transport/rpc-server.js.map +1 -1
  159. package/dist/objects/transport/shared.js +2 -1
  160. package/dist/objects/transport/shared.js.map +1 -1
  161. package/dist/snippets/artifacts-ingest.js.map +1 -1
  162. package/dist/snippets/artifacts-serve.js.map +1 -1
  163. package/dist/snippets/search.js.map +1 -1
  164. package/dist/workflows/ScheduleManager.js.map +1 -1
  165. package/dist/workflows/StepResultStorage.js.map +1 -1
  166. package/dist/workflows/WaitForEventManager.js.map +1 -1
  167. package/dist/workflows/compat/backends/cloudflare-workflows.js.map +1 -1
  168. package/dist/workflows/compat/inngest/index.js.map +1 -1
  169. package/dist/workflows/compat/qstash/index.js.map +1 -1
  170. package/dist/workflows/compat/temporal/client.js.map +1 -1
  171. package/dist/workflows/compat/temporal/index.js.map +1 -1
  172. package/dist/workflows/compat/trigger/index.js.map +1 -1
  173. package/dist/workflows/compat/utils/index.js.map +1 -1
  174. package/dist/workflows/context/correlation.js +2 -2
  175. package/dist/workflows/context/correlation.js.map +1 -1
  176. package/dist/workflows/context/experiment.js +1 -1
  177. package/dist/workflows/context/experiment.js.map +1 -1
  178. package/dist/workflows/context/flag.js +1 -1
  179. package/dist/workflows/context/flag.js.map +1 -1
  180. package/dist/workflows/context/measure.js +1 -1
  181. package/dist/workflows/context/measure.js.map +1 -1
  182. package/dist/workflows/context/rate-limit.js.map +1 -1
  183. package/dist/workflows/data/entity-events/entity-events.js.map +1 -1
  184. package/dist/workflows/data/experiment/index.js.map +1 -1
  185. package/dist/workflows/data/goal/context.js +1 -1
  186. package/dist/workflows/data/goal/context.js.map +1 -1
  187. package/dist/workflows/data/measure/index.js +1 -1
  188. package/dist/workflows/data/measure/index.js.map +1 -1
  189. package/dist/workflows/data/stream/index.js +10 -76
  190. package/dist/workflows/data/stream/index.js.map +1 -1
  191. package/dist/workflows/data/track/context.js.map +1 -1
  192. package/dist/workflows/data/view/context.js.map +1 -1
  193. package/dist/workflows/domain.js.map +1 -1
  194. package/dist/workflows/flags.js +1 -1
  195. package/dist/workflows/flags.js.map +1 -1
  196. package/dist/workflows/hash.js.map +1 -1
  197. package/dist/workflows/on.js +1 -1
  198. package/dist/workflows/on.js.map +1 -1
  199. package/dist/workflows/schedule-builder.js.map +1 -1
  200. package/dist/workflows/visibility/index.js +0 -2
  201. package/dist/workflows/visibility/index.js.map +1 -1
  202. package/dist/workflows/visibility/query-parser.js.map +1 -1
  203. package/package.json +18 -3
  204. package/dist/api/analytics/router.js +0 -601
  205. package/dist/api/analytics/router.js.map +0 -1
  206. package/dist/api/index.js +0 -158
  207. package/dist/api/index.js.map +0 -1
  208. package/dist/api/middleware/error-handling.js +0 -176
  209. package/dist/api/middleware/error-handling.js.map +0 -1
  210. package/dist/api/middleware/request-id.js +0 -21
  211. package/dist/api/middleware/request-id.js.map +0 -1
  212. package/dist/api/pages.js +0 -1180
  213. package/dist/api/pages.js.map +0 -1
  214. package/dist/api/routes/api.js +0 -612
  215. package/dist/api/routes/api.js.map +0 -1
  216. package/dist/api/routes/browsers.js +0 -471
  217. package/dist/api/routes/browsers.js.map +0 -1
  218. package/dist/api/routes/do.js +0 -188
  219. package/dist/api/routes/do.js.map +0 -1
  220. package/dist/api/routes/mcp.js +0 -459
  221. package/dist/api/routes/mcp.js.map +0 -1
  222. package/dist/api/routes/obs.js +0 -445
  223. package/dist/api/routes/obs.js.map +0 -1
  224. package/dist/api/routes/openapi.js +0 -794
  225. package/dist/api/routes/openapi.js.map +0 -1
  226. package/dist/api/routes/rpc.js +0 -1103
  227. package/dist/api/routes/rpc.js.map +0 -1
  228. package/dist/api/routes/sandboxes.js +0 -389
  229. package/dist/api/routes/sandboxes.js.map +0 -1
  230. package/dist/api/test-do.js +0 -38
  231. package/dist/api/test-do.js.map +0 -1
  232. package/dist/api/types.js +0 -11
  233. package/dist/api/types.js.map +0 -1
  234. package/dist/cli/bin.js +0 -2
  235. package/dist/cli/main.js +0 -52342
  236. package/dist/do/bash.js +0 -35
  237. package/dist/do/bash.js.map +0 -1
  238. package/dist/do/fs.js +0 -25
  239. package/dist/do/fs.js.map +0 -1
  240. package/dist/do/full.js +0 -61
  241. package/dist/do/full.js.map +0 -1
  242. package/dist/do/git.js +0 -28
  243. package/dist/do/git.js.map +0 -1
  244. package/dist/do/index.js +0 -52
  245. package/dist/do/index.js.map +0 -1
  246. package/dist/lib/agent/tools/bash.js +0 -336
  247. package/dist/lib/agent/tools/bash.js.map +0 -1
  248. package/dist/lib/agent/tools/edit.js +0 -157
  249. package/dist/lib/agent/tools/edit.js.map +0 -1
  250. package/dist/lib/agent/tools/glob.js +0 -137
  251. package/dist/lib/agent/tools/glob.js.map +0 -1
  252. package/dist/lib/agent/tools/grep.js +0 -315
  253. package/dist/lib/agent/tools/grep.js.map +0 -1
  254. package/dist/lib/agent/tools/index.js +0 -71
  255. package/dist/lib/agent/tools/index.js.map +0 -1
  256. package/dist/lib/agent/tools/read.js +0 -212
  257. package/dist/lib/agent/tools/read.js.map +0 -1
  258. package/dist/lib/agent/tools/types.js +0 -197
  259. package/dist/lib/agent/tools/types.js.map +0 -1
  260. package/dist/lib/agent/tools/write.js +0 -159
  261. package/dist/lib/agent/tools/write.js.map +0 -1
  262. package/dist/lib/mixins/index.js +0 -29
  263. package/dist/lib/mixins/index.js.map +0 -1
  264. package/dist/primitives/bashx/src/ast/analyze.js +0 -1472
  265. package/dist/primitives/bashx/src/ast/analyze.js.map +0 -1
  266. package/dist/primitives/bashx/src/ast/parser.js +0 -1488
  267. package/dist/primitives/bashx/src/ast/parser.js.map +0 -1
  268. package/dist/primitives/bashx/src/do/commands/crypto.js +0 -1954
  269. package/dist/primitives/bashx/src/do/commands/crypto.js.map +0 -1
  270. package/dist/primitives/bashx/src/do/commands/data-processing.js +0 -1812
  271. package/dist/primitives/bashx/src/do/commands/data-processing.js.map +0 -1
  272. package/dist/primitives/bashx/src/do/commands/extended-utils.js +0 -804
  273. package/dist/primitives/bashx/src/do/commands/extended-utils.js.map +0 -1
  274. package/dist/primitives/bashx/src/do/commands/math-control.js +0 -1122
  275. package/dist/primitives/bashx/src/do/commands/math-control.js.map +0 -1
  276. package/dist/primitives/bashx/src/do/commands/posix-utils.js +0 -1015
  277. package/dist/primitives/bashx/src/do/commands/posix-utils.js.map +0 -1
  278. package/dist/primitives/bashx/src/do/commands/system-utils.js +0 -687
  279. package/dist/primitives/bashx/src/do/commands/system-utils.js.map +0 -1
  280. package/dist/primitives/bashx/src/do/commands/test-command.js +0 -523
  281. package/dist/primitives/bashx/src/do/commands/test-command.js.map +0 -1
  282. package/dist/primitives/bashx/src/do/commands/text-processing.js +0 -1550
  283. package/dist/primitives/bashx/src/do/commands/text-processing.js.map +0 -1
  284. package/dist/primitives/bashx/src/do/container-executor.js +0 -429
  285. package/dist/primitives/bashx/src/do/container-executor.js.map +0 -1
  286. package/dist/primitives/bashx/src/do/index.js +0 -668
  287. package/dist/primitives/bashx/src/do/index.js.map +0 -1
  288. package/dist/primitives/bashx/src/do/tiered-executor.js +0 -2647
  289. package/dist/primitives/bashx/src/do/tiered-executor.js.map +0 -1
  290. package/dist/primitives/bashx/src/do/worker.js +0 -352
  291. package/dist/primitives/bashx/src/do/worker.js.map +0 -1
  292. package/dist/primitives/bashx/src/types.js +0 -10
  293. package/dist/primitives/bashx/src/types.js.map +0 -1
  294. package/dist/primitives/fsx/core/backend.js +0 -480
  295. package/dist/primitives/fsx/core/backend.js.map +0 -1
  296. package/dist/primitives/fsx/core/constants.js +0 -140
  297. package/dist/primitives/fsx/core/constants.js.map +0 -1
  298. package/dist/primitives/fsx/core/fsx.js +0 -1184
  299. package/dist/primitives/fsx/core/fsx.js.map +0 -1
  300. package/dist/primitives/fsx/core/glob/glob.js +0 -438
  301. package/dist/primitives/fsx/core/glob/glob.js.map +0 -1
  302. package/dist/primitives/fsx/core/glob/index.js +0 -8
  303. package/dist/primitives/fsx/core/glob/index.js.map +0 -1
  304. package/dist/primitives/fsx/core/glob/match.js +0 -392
  305. package/dist/primitives/fsx/core/glob/match.js.map +0 -1
  306. package/dist/primitives/fsx/core/types.js +0 -307
  307. package/dist/primitives/fsx/core/types.js.map +0 -1
  308. package/dist/sdk/capnweb-compat.js +0 -42
  309. package/dist/sdk/capnweb-compat.js.map +0 -1
  310. package/dist/sdk/client.js +0 -20
  311. package/dist/sdk/client.js.map +0 -1
  312. package/dist/sdk/index.js +0 -17
  313. package/dist/sdk/index.js.map +0 -1
@@ -1,1472 +0,0 @@
1
- /**
2
- * AST Analysis
3
- *
4
- * Analyzes parsed AST to extract safety classification and intent.
5
- * Implements structural safety analysis without regex-based detection.
6
- */
7
- // ============================================================================
8
- // Command Classification Data
9
- // ============================================================================
10
- /**
11
- * Commands that only read data and have no side effects
12
- */
13
- const READ_ONLY_COMMANDS = new Set([
14
- 'ls', 'cat', 'head', 'tail', 'less', 'more', 'grep', 'awk', 'sed',
15
- 'find', 'which', 'whereis', 'type', 'file', 'stat', 'wc', 'sort',
16
- 'uniq', 'diff', 'cmp', 'pwd', 'echo', 'printf', 'date', 'cal',
17
- 'whoami', 'id', 'groups', 'uname', 'hostname', 'uptime', 'free',
18
- 'df', 'du', 'ps', 'top', 'htop', 'pgrep', 'env', 'printenv',
19
- 'test', '[', '[[', 'true', 'false', 'expr', 'bc', 'seq',
20
- 'basename', 'dirname', 'realpath', 'readlink', 'md5sum', 'sha256sum',
21
- 'git', 'npm', 'node', 'python', 'python3', 'ruby', 'perl',
22
- 'man', 'help', 'info', 'apropos', 'whatis',
23
- // Shell builtins that don't modify filesystem
24
- 'cd', 'pushd', 'popd', 'dirs', 'alias', 'unalias', 'export', 'set', 'unset',
25
- 'read', 'declare', 'local', 'typeset', 'readonly', 'shift', 'wait', 'jobs',
26
- 'fg', 'bg', 'disown', 'builtin', 'command', 'enable', 'hash', 'history',
27
- 'let', 'logout', 'mapfile', 'readarray', 'return', 'trap', 'ulimit', 'umask',
28
- ]);
29
- /**
30
- * Read-only git subcommands
31
- */
32
- const GIT_READ_ONLY_SUBCOMMANDS = new Set([
33
- 'status', 'log', 'diff', 'show', 'branch', 'tag', 'remote',
34
- 'stash', 'describe', 'rev-parse', 'ls-files', 'ls-tree',
35
- 'cat-file', 'shortlog', 'blame', 'bisect', 'config',
36
- ]);
37
- /**
38
- * Network write git subcommands
39
- */
40
- const GIT_NETWORK_SUBCOMMANDS = new Set([
41
- 'push', 'fetch', 'pull', 'clone',
42
- ]);
43
- /**
44
- * Commands that delete data
45
- */
46
- const DELETE_COMMANDS = new Set([
47
- 'rm', 'rmdir', 'unlink', 'shred',
48
- ]);
49
- /**
50
- * Commands that write/modify data
51
- */
52
- const WRITE_COMMANDS = new Set([
53
- 'cp', 'mv', 'touch', 'mkdir', 'ln',
54
- 'chmod', 'chown', 'chgrp', 'chattr',
55
- 'tar', 'zip', 'unzip', 'gzip', 'gunzip', 'bzip2', 'xz',
56
- 'tee', 'install', 'patch',
57
- ]);
58
- /**
59
- * Commands that perform network operations
60
- */
61
- const NETWORK_COMMANDS = new Set([
62
- 'curl', 'wget', 'nc', 'netcat', 'ssh', 'scp', 'sftp', 'rsync',
63
- 'ftp', 'telnet', 'ping', 'traceroute', 'nslookup', 'dig', 'host',
64
- 'nmap', 'netstat', 'ss', 'ip', 'ifconfig', 'route',
65
- ]);
66
- /**
67
- * Commands that execute other code
68
- */
69
- const EXECUTE_COMMANDS = new Set([
70
- 'exec', 'eval', 'source', '.', 'bash', 'sh', 'zsh', 'fish',
71
- 'xargs', 'parallel', 'nohup', 'timeout', 'time', 'watch',
72
- 'sudo', 'su', 'doas', 'runuser',
73
- ]);
74
- /**
75
- * Critical system commands that should always require confirmation
76
- */
77
- const CRITICAL_SYSTEM_COMMANDS = new Set([
78
- 'shutdown', 'reboot', 'poweroff', 'halt', 'init',
79
- 'dd', 'mkfs', 'mkfs.ext4', 'mkfs.xfs', 'mkfs.btrfs',
80
- 'fdisk', 'parted', 'gdisk', 'mkswap', 'swapon', 'swapoff',
81
- 'mount', 'umount', 'losetup',
82
- 'iptables', 'ip6tables', 'firewall-cmd', 'ufw',
83
- 'systemctl', 'service', 'chkconfig',
84
- 'useradd', 'userdel', 'usermod', 'groupadd', 'groupdel',
85
- 'passwd', 'chpasswd', 'visudo',
86
- ]);
87
- /**
88
- * Critical system paths that require elevated privileges
89
- */
90
- const SYSTEM_PATHS = [
91
- '/', '/etc', '/usr', '/bin', '/sbin', '/lib', '/lib64',
92
- '/boot', '/dev', '/sys', '/proc', '/var', '/root', '/home',
93
- ];
94
- /**
95
- * Paths that indicate device access
96
- */
97
- const DEVICE_PATHS = ['/dev/sd', '/dev/hd', '/dev/nvme', '/dev/vd', '/dev/loop'];
98
- // ============================================================================
99
- // Helper Functions
100
- // ============================================================================
101
- /**
102
- * Extract command name from a Word node
103
- */
104
- function getCommandName(nameWord) {
105
- return nameWord?.value ?? '';
106
- }
107
- /**
108
- * Extract argument values from Word array
109
- */
110
- function getArgs(args) {
111
- return args.map(arg => arg.value);
112
- }
113
- /**
114
- * Check if a path is a system/root path
115
- */
116
- function isSystemPath(path) {
117
- const normalized = path.replace(/\/+$/, ''); // Remove trailing slashes
118
- return SYSTEM_PATHS.some(sp => normalized === sp || normalized.startsWith(sp + '/'));
119
- }
120
- /**
121
- * Check if a path is a device path
122
- */
123
- function isDevicePath(path) {
124
- return DEVICE_PATHS.some(dp => path.startsWith(dp)) || path === '/dev/zero' || path === '/dev/null';
125
- }
126
- /**
127
- * Check if args contain recursive flag
128
- */
129
- function hasRecursiveFlag(args) {
130
- return args.some(arg => arg === '-r' || arg === '-R' || arg === '-rf' || arg === '-fr' || arg === '--recursive');
131
- }
132
- /**
133
- * Check if args contain force flag
134
- */
135
- function hasForceFlag(args) {
136
- return args.some(arg => arg === '-f' || arg === '-rf' || arg === '-fr' || arg === '--force');
137
- }
138
- /**
139
- * Extract target paths from arguments
140
- */
141
- function extractPaths(args) {
142
- return args.filter(arg => !arg.startsWith('-') && arg.length > 0);
143
- }
144
- /**
145
- * Check if any path is root or system-critical
146
- */
147
- function hasCriticalPath(paths) {
148
- return paths.some(p => {
149
- const normalized = p.replace(/\/+$/, '');
150
- // Handle root path (/ or empty after stripping trailing slashes)
151
- // Also handle /* and ~/
152
- return normalized === '' || normalized === '/' || normalized === '/*' ||
153
- normalized === '~' || normalized === '~/' ||
154
- p === '/' || // Direct check before normalization
155
- isDevicePath(p);
156
- });
157
- }
158
- /**
159
- * Check if command targets system paths
160
- */
161
- function targetsSystemPath(paths) {
162
- return paths.some(p => isSystemPath(p));
163
- }
164
- // ============================================================================
165
- // Classification Functions
166
- // ============================================================================
167
- /**
168
- * Classify a single command from AST
169
- */
170
- export function classifyCommand(commandName, args) {
171
- const name = commandName.toLowerCase();
172
- const paths = extractPaths(args);
173
- const recursive = hasRecursiveFlag(args);
174
- const force = hasForceFlag(args);
175
- // Handle empty command (assignment only)
176
- if (!name) {
177
- return {
178
- type: 'write',
179
- impact: 'low',
180
- reversible: true,
181
- reason: 'Variable assignment',
182
- };
183
- }
184
- // Critical system commands always critical
185
- if (CRITICAL_SYSTEM_COMMANDS.has(name)) {
186
- return {
187
- type: 'system',
188
- impact: 'critical',
189
- reversible: false,
190
- reason: `${name} is a critical system command`,
191
- };
192
- }
193
- // Elevated commands (sudo, su, doas)
194
- if (name === 'sudo' || name === 'su' || name === 'doas') {
195
- // Re-classify the actual command being run
196
- if (args.length > 0) {
197
- const subCommand = args[0];
198
- const subArgs = args.slice(1);
199
- const subClassification = classifyCommand(subCommand, subArgs);
200
- // Elevate impact for sudo commands
201
- const impact = subClassification.impact === 'none' ? 'low' :
202
- subClassification.impact === 'low' ? 'medium' :
203
- subClassification.impact === 'medium' ? 'high' : 'critical';
204
- return {
205
- ...subClassification,
206
- impact,
207
- reason: `Elevated: ${subClassification.reason}`,
208
- };
209
- }
210
- return {
211
- type: 'execute',
212
- impact: 'high',
213
- reversible: false,
214
- reason: 'Elevated privilege command',
215
- };
216
- }
217
- // Git command classification
218
- if (name === 'git' && args.length > 0) {
219
- const subCommand = args[0];
220
- if (GIT_READ_ONLY_SUBCOMMANDS.has(subCommand)) {
221
- return {
222
- type: 'read',
223
- impact: 'none',
224
- reversible: true,
225
- reason: 'Read-only git command',
226
- };
227
- }
228
- if (GIT_NETWORK_SUBCOMMANDS.has(subCommand)) {
229
- return {
230
- type: 'network',
231
- impact: 'medium',
232
- reversible: true,
233
- reason: 'Git network operation',
234
- };
235
- }
236
- // Git write commands (commit, add, etc.)
237
- return {
238
- type: 'write',
239
- impact: 'low',
240
- reversible: true,
241
- reason: 'Git local write operation',
242
- };
243
- }
244
- // npm command classification
245
- if (name === 'npm' && args.length > 0) {
246
- const subCommand = args[0];
247
- if (subCommand === 'publish') {
248
- return {
249
- type: 'network',
250
- impact: 'high',
251
- reversible: false,
252
- reason: 'Publishes package to public registry',
253
- };
254
- }
255
- if (['list', 'ls', 'view', 'info', 'search', 'audit', 'outdated'].includes(subCommand)) {
256
- return {
257
- type: 'read',
258
- impact: 'none',
259
- reversible: true,
260
- reason: 'Read-only npm command',
261
- };
262
- }
263
- return {
264
- type: 'write',
265
- impact: 'low',
266
- reversible: true,
267
- reason: 'npm local operation',
268
- };
269
- }
270
- // Delete commands (rm, rmdir)
271
- if (DELETE_COMMANDS.has(name)) {
272
- // Critical: rm -rf / or rm -rf /*
273
- if (hasCriticalPath(paths) && (recursive || force)) {
274
- return {
275
- type: 'delete',
276
- impact: 'critical',
277
- reversible: false,
278
- reason: recursive ? 'Recursively deletes critical path' : 'Deletes critical path',
279
- };
280
- }
281
- // High: recursive delete
282
- if (recursive) {
283
- return {
284
- type: 'delete',
285
- impact: 'high',
286
- reversible: false,
287
- reason: 'Recursively deletes directory and contents',
288
- };
289
- }
290
- // Medium: single file delete
291
- return {
292
- type: 'delete',
293
- impact: 'medium',
294
- reversible: false,
295
- reason: 'Deletes file permanently',
296
- };
297
- }
298
- // Write commands (chmod, chown, etc.) with recursive and system paths
299
- if (name === 'chmod' || name === 'chown' || name === 'chgrp') {
300
- if (recursive && hasCriticalPath(paths)) {
301
- return {
302
- type: 'system',
303
- impact: 'critical',
304
- reversible: false,
305
- reason: `Recursively modifies permissions/ownership on critical path`,
306
- };
307
- }
308
- if (targetsSystemPath(paths)) {
309
- return {
310
- type: 'write',
311
- impact: 'high',
312
- reversible: false,
313
- reason: 'Modifies system file permissions/ownership',
314
- };
315
- }
316
- return {
317
- type: 'write',
318
- impact: 'medium',
319
- reversible: true,
320
- reason: `Modifies file ${name === 'chmod' ? 'permissions' : 'ownership'}`,
321
- };
322
- }
323
- // dd command - always dangerous for device targets
324
- if (name === 'dd') {
325
- const ofArg = args.find(a => a.startsWith('of='));
326
- const target = ofArg?.slice(3) ?? '';
327
- if (isDevicePath(target) || target.startsWith('/dev/')) {
328
- return {
329
- type: 'system',
330
- impact: 'critical',
331
- reversible: false,
332
- reason: 'Overwrites disk device',
333
- };
334
- }
335
- return {
336
- type: 'write',
337
- impact: 'high',
338
- reversible: false,
339
- reason: 'Low-level data copy operation',
340
- };
341
- }
342
- // Network commands
343
- if (NETWORK_COMMANDS.has(name)) {
344
- // POST/PUT requests are higher impact
345
- if (name === 'curl' && (args.includes('-X') || args.includes('--request'))) {
346
- const methodIndex = args.indexOf('-X') !== -1 ? args.indexOf('-X') : args.indexOf('--request');
347
- const method = args[methodIndex + 1]?.toUpperCase();
348
- if (method === 'POST' || method === 'PUT' || method === 'DELETE' || method === 'PATCH') {
349
- return {
350
- type: 'network',
351
- impact: 'medium',
352
- reversible: false,
353
- reason: `Sends ${method} request to remote server`,
354
- };
355
- }
356
- }
357
- return {
358
- type: 'network',
359
- impact: 'low',
360
- reversible: true,
361
- reason: 'Network operation',
362
- };
363
- }
364
- // Execute commands
365
- if (EXECUTE_COMMANDS.has(name)) {
366
- return {
367
- type: 'execute',
368
- impact: 'medium',
369
- reversible: false,
370
- reason: 'Executes external code',
371
- };
372
- }
373
- // Write commands
374
- if (WRITE_COMMANDS.has(name)) {
375
- if (targetsSystemPath(paths)) {
376
- return {
377
- type: 'write',
378
- impact: 'high',
379
- reversible: false,
380
- reason: 'Writes to system path',
381
- };
382
- }
383
- return {
384
- type: 'write',
385
- impact: 'low',
386
- reversible: true,
387
- reason: 'File write operation',
388
- };
389
- }
390
- // Kill command
391
- if (name === 'kill' || name === 'killall' || name === 'pkill') {
392
- const hasSignal9 = args.includes('-9') || args.includes('-KILL') || args.includes('SIGKILL');
393
- return {
394
- type: 'system',
395
- impact: hasSignal9 ? 'high' : 'medium',
396
- reversible: false,
397
- reason: hasSignal9 ? 'Force terminates a running process' : 'Terminates a process',
398
- };
399
- }
400
- // Read-only commands
401
- if (READ_ONLY_COMMANDS.has(name)) {
402
- return {
403
- type: 'read',
404
- impact: 'none',
405
- reversible: true,
406
- reason: 'Read-only command',
407
- };
408
- }
409
- // Unknown command - be conservative
410
- return {
411
- type: 'execute',
412
- impact: 'low',
413
- reversible: false,
414
- reason: 'Unknown command - classified conservatively',
415
- };
416
- }
417
- /**
418
- * Classify redirect operations
419
- */
420
- function classifyRedirect(redirect) {
421
- const target = redirect.target.value;
422
- const isWrite = redirect.op === '>' || redirect.op === '>>' || redirect.op === '>|';
423
- if (!isWrite) {
424
- return {
425
- type: 'read',
426
- impact: 'none',
427
- reversible: true,
428
- reason: 'Input redirection',
429
- };
430
- }
431
- if (isSystemPath(target)) {
432
- return {
433
- type: 'write',
434
- impact: 'high',
435
- reversible: false,
436
- reason: 'Writes to system configuration file',
437
- };
438
- }
439
- return {
440
- type: 'write',
441
- impact: 'low',
442
- reversible: redirect.op === '>>' ? true : false, // Append is somewhat reversible
443
- reason: redirect.op === '>>' ? 'Appends to file' : 'Overwrites file',
444
- };
445
- }
446
- /**
447
- * Extract commands from AST nodes recursively
448
- */
449
- function extractAllCommands(node) {
450
- const commands = [];
451
- switch (node.type) {
452
- case 'Program':
453
- for (const child of node.body) {
454
- commands.push(...extractAllCommands(child));
455
- }
456
- break;
457
- case 'Command':
458
- commands.push(node);
459
- break;
460
- case 'Pipeline':
461
- for (const cmd of node.commands) {
462
- commands.push(...extractAllCommands(cmd));
463
- }
464
- break;
465
- case 'List':
466
- commands.push(...extractAllCommands(node.left));
467
- commands.push(...extractAllCommands(node.right));
468
- break;
469
- case 'Subshell':
470
- for (const child of node.body) {
471
- commands.push(...extractAllCommands(child));
472
- }
473
- break;
474
- case 'CompoundCommand':
475
- for (const child of node.body) {
476
- commands.push(...extractAllCommands(child));
477
- }
478
- break;
479
- case 'FunctionDef':
480
- commands.push(...extractAllCommands(node.body));
481
- break;
482
- }
483
- return commands;
484
- }
485
- /**
486
- * Combine classifications, taking the most dangerous
487
- */
488
- function combineClassifications(classifications) {
489
- if (classifications.length === 0) {
490
- return {
491
- type: 'read',
492
- impact: 'none',
493
- reversible: true,
494
- reason: 'Empty command',
495
- };
496
- }
497
- const impactOrder = ['none', 'low', 'medium', 'high', 'critical'];
498
- const typeOrder = ['read', 'write', 'delete', 'execute', 'network', 'system', 'mixed'];
499
- let maxImpact = 'none';
500
- let mainType = classifications[0].type;
501
- let mainReason = classifications[0].reason;
502
- let anyIrreversible = false;
503
- // Collect all non-read types to determine if truly mixed
504
- const nonReadTypes = new Set();
505
- for (const classification of classifications) {
506
- // Track maximum impact
507
- if (impactOrder.indexOf(classification.impact) > impactOrder.indexOf(maxImpact)) {
508
- maxImpact = classification.impact;
509
- mainReason = classification.reason;
510
- }
511
- // Track non-read types
512
- if (classification.type !== 'read') {
513
- nonReadTypes.add(classification.type);
514
- }
515
- // Update main type if this one is more severe
516
- if (typeOrder.indexOf(classification.type) > typeOrder.indexOf(mainType)) {
517
- mainType = classification.type;
518
- }
519
- // Track reversibility
520
- if (!classification.reversible) {
521
- anyIrreversible = true;
522
- }
523
- }
524
- // Only return 'mixed' if there are truly multiple different non-read types
525
- // (e.g., write + network, delete + system)
526
- // Don't count read as it's semantically included in other operations
527
- const hasMultipleTypes = nonReadTypes.size > 1;
528
- return {
529
- type: hasMultipleTypes ? 'mixed' : mainType,
530
- impact: maxImpact,
531
- reversible: !anyIrreversible,
532
- reason: mainReason,
533
- };
534
- }
535
- /**
536
- * Maps file extensions to human-readable descriptions
537
- */
538
- const FILE_TYPE_DESCRIPTIONS = {
539
- '.js': 'JavaScript files',
540
- '.ts': 'TypeScript files',
541
- '.py': 'Python files',
542
- '.md': 'Markdown files',
543
- '.json': 'JSON files',
544
- '.log': 'log files',
545
- '.txt': 'text files',
546
- '.tmp': 'temporary files',
547
- '.sh': 'shell scripts',
548
- '.yaml': 'YAML files',
549
- '.yml': 'YAML files',
550
- '.css': 'CSS files',
551
- '.html': 'HTML files',
552
- '.xml': 'XML files',
553
- };
554
- /**
555
- * Extract file type description from a pattern like "*.js"
556
- */
557
- function getFileTypeDescription(pattern) {
558
- // Extract extension from pattern like "*.js" or "*.log"
559
- const extMatch = pattern.match(/\*(\.[a-zA-Z0-9]+)$/);
560
- if (extMatch) {
561
- const ext = extMatch[1];
562
- return FILE_TYPE_DESCRIPTIONS[ext] || `${ext.slice(1)} files`;
563
- }
564
- return 'files';
565
- }
566
- /**
567
- * Extract modifiers from command flags
568
- */
569
- function extractModifiers(args) {
570
- const modifiers = [];
571
- for (const arg of args) {
572
- if (!arg.startsWith('-'))
573
- continue;
574
- // Handle combined flags like -rfv
575
- const flags = arg.startsWith('--') ? [arg] : arg.slice(1).split('');
576
- for (const flag of flags) {
577
- switch (flag) {
578
- case 'r':
579
- case 'R':
580
- case 'recursive':
581
- case '-recursive':
582
- modifiers.push('recursive');
583
- break;
584
- case 'f':
585
- case '-force':
586
- modifiers.push('force');
587
- break;
588
- case 'v':
589
- case '-verbose':
590
- modifiers.push('verbose');
591
- break;
592
- case 'a':
593
- modifiers.push('all');
594
- break;
595
- case 'l':
596
- modifiers.push('long');
597
- break;
598
- case 'i':
599
- case '-ignore-case':
600
- modifiers.push('case-insensitive');
601
- break;
602
- case 'n':
603
- case '-dry-run':
604
- if (arg === '-n' || arg === '--dry-run') {
605
- modifiers.push('dry-run');
606
- }
607
- break;
608
- }
609
- }
610
- }
611
- return [...new Set(modifiers)]; // Remove duplicates
612
- }
613
- /**
614
- * Get action type classification
615
- */
616
- function getActionType(commandName, args) {
617
- const name = commandName.toLowerCase();
618
- if (DELETE_COMMANDS.has(name))
619
- return 'delete';
620
- if (NETWORK_COMMANDS.has(name))
621
- return 'network';
622
- if (name === 'git' && args.length > 0 && GIT_NETWORK_SUBCOMMANDS.has(args[0]))
623
- return 'network';
624
- if (WRITE_COMMANDS.has(name) || name === 'chmod' || name === 'chown' || name === 'touch' || name === 'mkdir')
625
- return 'write';
626
- if (READ_ONLY_COMMANDS.has(name))
627
- return 'read';
628
- if (EXECUTE_COMMANDS.has(name))
629
- return 'execute';
630
- if (CRITICAL_SYSTEM_COMMANDS.has(name) || name === 'kill' || name === 'killall' || name === 'pkill')
631
- return 'system';
632
- return 'execute';
633
- }
634
- /**
635
- * Extract intent from a single command
636
- */
637
- function extractCommandIntent(cmd) {
638
- const name = getCommandName(cmd.name);
639
- const args = getArgs(cmd.args);
640
- const paths = extractPaths(args);
641
- const modifiers = extractModifiers(args);
642
- const intent = {
643
- modifiers,
644
- actionType: getActionType(name, args),
645
- };
646
- // Basic file operations
647
- switch (name.toLowerCase()) {
648
- case 'ls': {
649
- intent.action = 'list';
650
- intent.object = 'files';
651
- intent.objectType = 'directory';
652
- if (paths.length > 0) {
653
- intent.target = paths[0];
654
- intent.description = `list files in ${paths[0]}`;
655
- }
656
- else {
657
- intent.description = 'list files';
658
- }
659
- if (modifiers.includes('all') && modifiers.includes('long')) {
660
- intent.description = 'list files: all with details';
661
- }
662
- break;
663
- }
664
- case 'cat': {
665
- intent.action = 'read';
666
- intent.objectType = 'file';
667
- if (paths.length === 1) {
668
- intent.object = 'file';
669
- intent.target = paths[0];
670
- intent.description = `read file ${paths[0]}`;
671
- }
672
- else if (paths.length > 1) {
673
- intent.object = 'files';
674
- intent.targets = paths;
675
- intent.description = `read files ${paths.join(', ')}`;
676
- }
677
- else {
678
- intent.object = 'stdin';
679
- intent.description = 'read from stdin';
680
- }
681
- break;
682
- }
683
- case 'head':
684
- case 'tail':
685
- case 'less':
686
- case 'more': {
687
- intent.action = 'read';
688
- intent.object = 'file';
689
- intent.objectType = 'file';
690
- if (paths.length > 0) {
691
- intent.target = paths[0];
692
- intent.description = `read file ${paths[0]}`;
693
- }
694
- break;
695
- }
696
- case 'pwd': {
697
- intent.action = 'show';
698
- intent.object = 'working directory';
699
- intent.description = 'show working directory';
700
- break;
701
- }
702
- case 'echo':
703
- case 'printf': {
704
- intent.action = 'print';
705
- intent.object = 'text';
706
- intent.description = 'print text';
707
- break;
708
- }
709
- case 'rm': {
710
- intent.action = 'delete';
711
- intent.objectType = 'file';
712
- if (paths.length > 0) {
713
- intent.target = paths[0];
714
- // Detect directory from -r flag or trailing slash
715
- if (modifiers.includes('recursive') || paths[0].endsWith('/')) {
716
- intent.object = 'directory';
717
- if (modifiers.includes('recursive') && modifiers.includes('force')) {
718
- intent.description = `recursively delete directory ${paths[0]}`;
719
- }
720
- else {
721
- intent.description = `delete directory ${paths[0]}`;
722
- }
723
- }
724
- else {
725
- intent.object = 'file';
726
- intent.description = `delete file ${paths[0]}`;
727
- }
728
- }
729
- break;
730
- }
731
- case 'rmdir': {
732
- intent.action = 'delete';
733
- intent.object = 'directory';
734
- intent.objectType = 'directory';
735
- if (paths.length > 0) {
736
- intent.target = paths[0];
737
- intent.description = `delete directory ${paths[0]}`;
738
- }
739
- break;
740
- }
741
- case 'mkdir': {
742
- intent.action = 'create';
743
- intent.object = 'directory';
744
- intent.objectType = 'directory';
745
- if (paths.length > 0) {
746
- intent.target = paths[0];
747
- intent.description = `create directory ${paths[0]}`;
748
- }
749
- break;
750
- }
751
- case 'touch': {
752
- intent.action = 'create';
753
- intent.object = 'file';
754
- intent.objectType = 'file';
755
- if (paths.length > 0) {
756
- intent.target = paths[0];
757
- intent.description = `create file ${paths[0]}`;
758
- }
759
- break;
760
- }
761
- case 'cp': {
762
- intent.action = 'copy';
763
- intent.object = 'file';
764
- intent.objectType = 'file';
765
- if (paths.length >= 2) {
766
- intent.source = paths[0];
767
- intent.target = paths[paths.length - 1];
768
- intent.description = `copy file from ${paths[0]} to ${paths[paths.length - 1]}`;
769
- }
770
- break;
771
- }
772
- case 'mv': {
773
- intent.action = 'move';
774
- intent.object = 'file';
775
- intent.objectType = 'file';
776
- if (paths.length >= 2) {
777
- intent.source = paths[0];
778
- intent.target = paths[paths.length - 1];
779
- intent.description = `move file from ${paths[0]} to ${paths[paths.length - 1]}`;
780
- }
781
- break;
782
- }
783
- case 'chmod': {
784
- intent.action = 'modify';
785
- intent.object = 'permissions';
786
- intent.objectType = 'file';
787
- if (paths.length > 0) {
788
- intent.target = paths[paths.length - 1];
789
- intent.description = `modify permissions of ${paths[paths.length - 1]}`;
790
- }
791
- break;
792
- }
793
- case 'chown':
794
- case 'chgrp': {
795
- intent.action = 'modify';
796
- intent.object = 'ownership';
797
- intent.objectType = 'file';
798
- if (paths.length > 0) {
799
- intent.target = paths[paths.length - 1];
800
- intent.description = `modify ownership of ${paths[paths.length - 1]}`;
801
- }
802
- break;
803
- }
804
- case 'find': {
805
- intent.action = 'find';
806
- intent.objectType = 'file';
807
- // Extract search path (first non-flag argument)
808
- intent.searchPath = paths[0] || '.';
809
- // Extract -name pattern
810
- const nameIdx = args.indexOf('-name');
811
- if (nameIdx !== -1 && args[nameIdx + 1]) {
812
- const pattern = args[nameIdx + 1].replace(/^["']|["']$/g, '');
813
- intent.pattern = pattern;
814
- intent.object = getFileTypeDescription(pattern);
815
- }
816
- else {
817
- intent.object = 'files';
818
- }
819
- // Check for -exec with delete
820
- const execIdx = args.indexOf('-exec');
821
- if (execIdx !== -1) {
822
- const execCmd = args[execIdx + 1];
823
- if (execCmd === 'rm') {
824
- intent.action = 'find and delete';
825
- intent.description = `find and delete ${intent.object}`;
826
- }
827
- }
828
- // Check for -delete flag
829
- if (args.includes('-delete')) {
830
- intent.action = 'delete';
831
- intent.actionType = 'delete';
832
- }
833
- // Check for -mtime
834
- const mtimeIdx = args.indexOf('-mtime');
835
- if (mtimeIdx !== -1 && args[mtimeIdx + 1]) {
836
- const mtimeVal = args[mtimeIdx + 1];
837
- if (mtimeVal.startsWith('+')) {
838
- const days = mtimeVal.slice(1);
839
- if (intent.modifiers) {
840
- intent.modifiers.push(`older than ${days} days`);
841
- }
842
- intent.object = 'old files';
843
- }
844
- }
845
- if (!intent.description) {
846
- if (intent.searchPath !== '.' && intent.searchPath !== './') {
847
- intent.description = `find ${intent.object} in ${intent.searchPath}`;
848
- }
849
- else {
850
- intent.description = `find ${intent.object}`;
851
- }
852
- }
853
- if (intent.action === 'delete' && intent.object === 'old files') {
854
- intent.description = `delete old files`;
855
- }
856
- break;
857
- }
858
- case 'grep': {
859
- intent.action = 'search';
860
- intent.object = 'pattern';
861
- intent.objectType = 'pattern';
862
- // Extract pattern (first non-flag argument)
863
- const nonFlags = args.filter(a => !a.startsWith('-'));
864
- if (nonFlags.length > 0) {
865
- intent.pattern = nonFlags[0].replace(/^["']|["']$/g, '');
866
- }
867
- if (nonFlags.length > 1) {
868
- intent.target = nonFlags[1];
869
- }
870
- if (modifiers.includes('recursive')) {
871
- intent.description = `recursively search for "${intent.pattern}" in ${intent.target || '.'}`;
872
- }
873
- else if (modifiers.includes('case-insensitive')) {
874
- intent.description = `case-insensitive search for "${intent.pattern}" in ${intent.target || 'files'}`;
875
- }
876
- else if (intent.target) {
877
- intent.description = `search for "${intent.pattern}" in ${intent.target}`;
878
- }
879
- else {
880
- intent.description = `search for "${intent.pattern}"`;
881
- }
882
- break;
883
- }
884
- case 'git': {
885
- const subCommand = args[0]?.toLowerCase();
886
- switch (subCommand) {
887
- case 'status': {
888
- intent.action = 'show';
889
- intent.object = 'repository status';
890
- intent.description = 'show repository status';
891
- break;
892
- }
893
- case 'commit': {
894
- intent.action = 'create';
895
- intent.object = 'commit';
896
- const mIdx = args.indexOf('-m');
897
- if (mIdx !== -1 && args[mIdx + 1]) {
898
- intent.message = args[mIdx + 1].replace(/^["']|["']$/g, '');
899
- intent.description = `create commit with message "${intent.message}"`;
900
- }
901
- else {
902
- intent.description = 'create commit';
903
- }
904
- break;
905
- }
906
- case 'push': {
907
- intent.action = 'push';
908
- intent.object = 'commits';
909
- intent.actionType = 'network';
910
- if (args[1])
911
- intent.remote = args[1];
912
- if (args[2])
913
- intent.branch = args[2];
914
- if (intent.remote && intent.branch) {
915
- intent.description = `push commits to ${intent.remote}/${intent.branch}`;
916
- }
917
- else {
918
- intent.description = 'push commits';
919
- }
920
- break;
921
- }
922
- case 'pull': {
923
- intent.action = 'pull';
924
- intent.object = 'changes';
925
- intent.actionType = 'network';
926
- if (args[1])
927
- intent.remote = args[1];
928
- if (args[2])
929
- intent.branch = args[2];
930
- if (intent.remote && intent.branch) {
931
- intent.description = `pull changes from ${intent.remote}/${intent.branch}`;
932
- }
933
- else {
934
- intent.description = 'pull changes';
935
- }
936
- break;
937
- }
938
- case 'checkout': {
939
- intent.action = 'switch';
940
- intent.object = 'branch';
941
- if (args[1]) {
942
- intent.target = args[1];
943
- intent.description = `switch to branch ${args[1]}`;
944
- }
945
- else {
946
- intent.description = 'switch branch';
947
- }
948
- break;
949
- }
950
- default: {
951
- intent.action = subCommand || 'git';
952
- intent.object = 'repository';
953
- intent.description = `git ${subCommand || ''}`;
954
- }
955
- }
956
- break;
957
- }
958
- case 'curl': {
959
- intent.action = 'fetch';
960
- intent.object = 'URL';
961
- intent.objectType = 'url';
962
- intent.method = 'GET';
963
- // Check for explicit method
964
- const xIdx = args.findIndex(a => a === '-X' || a === '--request');
965
- if (xIdx !== -1 && args[xIdx + 1]) {
966
- intent.method = args[xIdx + 1].toUpperCase();
967
- if (intent.method === 'POST' || intent.method === 'PUT' || intent.method === 'PATCH') {
968
- intent.action = 'send';
969
- }
970
- }
971
- // Extract URL (first non-flag argument that looks like a URL)
972
- for (const arg of args) {
973
- if (arg.startsWith('http://') || arg.startsWith('https://')) {
974
- intent.url = arg;
975
- break;
976
- }
977
- }
978
- if (intent.action === 'send') {
979
- intent.description = `send ${intent.method} request to ${intent.url || 'URL'}`;
980
- }
981
- else {
982
- intent.description = `fetch data from ${intent.url || 'URL'}`;
983
- }
984
- break;
985
- }
986
- case 'wget': {
987
- intent.action = 'download';
988
- intent.object = 'file';
989
- intent.objectType = 'url';
990
- // Extract URL
991
- for (const arg of args) {
992
- if (arg.startsWith('http://') || arg.startsWith('https://')) {
993
- intent.url = arg;
994
- break;
995
- }
996
- }
997
- intent.description = `download file from ${intent.url || 'URL'}`;
998
- break;
999
- }
1000
- case 'ssh': {
1001
- intent.action = 'connect';
1002
- intent.object = 'remote server';
1003
- intent.objectType = 'url';
1004
- // Get target (user@host)
1005
- const target = paths[0];
1006
- if (target) {
1007
- intent.target = target;
1008
- intent.description = `connect to remote server ${target}`;
1009
- }
1010
- else {
1011
- intent.description = 'connect to remote server';
1012
- }
1013
- break;
1014
- }
1015
- case 'scp': {
1016
- intent.action = 'copy';
1017
- intent.object = 'file';
1018
- intent.objectType = 'file';
1019
- intent.actionType = 'network';
1020
- intent.description = 'copy file over SSH';
1021
- break;
1022
- }
1023
- case 'rsync': {
1024
- intent.action = 'sync';
1025
- intent.object = 'files';
1026
- intent.objectType = 'file';
1027
- if (paths.length >= 2) {
1028
- intent.source = paths[0];
1029
- intent.target = paths[1];
1030
- intent.description = `sync files from ${paths[0]} to ${paths[1]}`;
1031
- }
1032
- break;
1033
- }
1034
- case 'kill':
1035
- case 'killall':
1036
- case 'pkill': {
1037
- intent.action = 'terminate';
1038
- intent.object = 'process';
1039
- intent.objectType = 'process';
1040
- if (paths.length > 0) {
1041
- intent.target = paths[0];
1042
- }
1043
- intent.description = `terminate process`;
1044
- break;
1045
- }
1046
- case 'cd': {
1047
- intent.action = 'change';
1048
- intent.object = 'directory';
1049
- intent.objectType = 'directory';
1050
- if (paths.length > 0) {
1051
- intent.target = paths[0];
1052
- intent.description = `change to directory ${paths[0]}`;
1053
- }
1054
- break;
1055
- }
1056
- case 'test': {
1057
- intent.action = 'test';
1058
- intent.object = 'condition';
1059
- intent.description = 'test condition';
1060
- break;
1061
- }
1062
- case 'sort': {
1063
- intent.action = 'sort';
1064
- intent.object = 'lines';
1065
- intent.description = 'sort lines';
1066
- break;
1067
- }
1068
- case 'uniq': {
1069
- intent.action = 'filter';
1070
- intent.object = 'unique lines';
1071
- intent.description = 'filter unique lines';
1072
- break;
1073
- }
1074
- case 'wc': {
1075
- intent.action = 'count';
1076
- intent.object = 'lines';
1077
- intent.description = 'count lines';
1078
- break;
1079
- }
1080
- case 'jq': {
1081
- intent.action = 'parse';
1082
- intent.object = 'JSON';
1083
- intent.description = 'parse JSON data';
1084
- break;
1085
- }
1086
- default: {
1087
- intent.action = name;
1088
- intent.object = paths[0] || 'output';
1089
- intent.description = `${name}${paths.length > 0 ? ' ' + paths[0] : ''}`;
1090
- }
1091
- }
1092
- return intent;
1093
- }
1094
- /**
1095
- * Extract intent from pipeline
1096
- */
1097
- function extractPipelineIntent(pipeline) {
1098
- const commands = pipeline.commands.map(cmd => ({
1099
- name: getCommandName(cmd.name),
1100
- args: getArgs(cmd.args),
1101
- intent: extractCommandIntent(cmd),
1102
- }));
1103
- const commandNames = commands.map(c => c.name);
1104
- const firstCmd = commands[0];
1105
- const lastCmd = commands[commands.length - 1];
1106
- // Determine overall action based on pipeline structure
1107
- let action = firstCmd.intent.action || firstCmd.name;
1108
- let description = '';
1109
- // Common patterns
1110
- if (commandNames.includes('grep')) {
1111
- const grepIdx = commandNames.indexOf('grep');
1112
- const grepCmd = commands[grepIdx];
1113
- const pattern = grepCmd.intent.pattern;
1114
- if (commandNames.includes('wc')) {
1115
- action = 'count';
1116
- description = `count lines matching "${pattern}"`;
1117
- if (firstCmd.name === 'cat' && firstCmd.intent.target) {
1118
- description = `count "${pattern}" lines in ${firstCmd.intent.target}`;
1119
- }
1120
- }
1121
- else {
1122
- action = 'filter';
1123
- if (firstCmd.name === 'cat' && firstCmd.intent.target) {
1124
- description = `filter "${pattern}" from ${firstCmd.intent.target}`;
1125
- }
1126
- else {
1127
- description = `filter for pattern "${pattern}"`;
1128
- }
1129
- }
1130
- }
1131
- else if (commandNames.includes('sort') && commandNames.includes('uniq')) {
1132
- action = 'sort and deduplicate';
1133
- description = 'sort and get unique lines';
1134
- if (firstCmd.intent.target) {
1135
- description = `sort ${firstCmd.intent.target} and get unique lines`;
1136
- }
1137
- }
1138
- // Get reads/writes from pipeline
1139
- const reads = [];
1140
- const writes = [];
1141
- // First command reads
1142
- if (firstCmd.intent.target) {
1143
- reads.push(firstCmd.intent.target);
1144
- }
1145
- // Last command redirects write
1146
- const lastCmdAst = pipeline.commands[pipeline.commands.length - 1];
1147
- for (const redirect of lastCmdAst.redirects) {
1148
- if (redirect.op === '>' || redirect.op === '>>') {
1149
- writes.push(redirect.target.value);
1150
- }
1151
- }
1152
- return {
1153
- action,
1154
- description: description || `${action} via pipeline`,
1155
- commands: commandNames,
1156
- modifiers: firstCmd.intent.modifiers || [],
1157
- actionType: lastCmd.intent.actionType || 'read',
1158
- reads,
1159
- writes,
1160
- };
1161
- }
1162
- /**
1163
- * Extract intent from List (&&, ||)
1164
- */
1165
- function extractListIntent(list) {
1166
- const leftIntent = extractNodeIntent(list.left);
1167
- const rightIntent = extractNodeIntent(list.right);
1168
- const commands = [
1169
- ...(leftIntent.commands || []),
1170
- ...(rightIntent.commands || []),
1171
- ];
1172
- let description = '';
1173
- if (list.operator === '&&') {
1174
- // Sequential execution
1175
- if (leftIntent.action === 'create' && rightIntent.action === 'change') {
1176
- description = `create directory and change into it`;
1177
- }
1178
- else if (leftIntent.action === 'test' && rightIntent.action === 'create') {
1179
- description = `create ${rightIntent.object} if not exists`;
1180
- }
1181
- else {
1182
- description = `${leftIntent.description || leftIntent.action} then ${rightIntent.description || rightIntent.action}`;
1183
- }
1184
- }
1185
- else if (list.operator === '||') {
1186
- // Conditional execution
1187
- if (leftIntent.action === 'test') {
1188
- description = `${rightIntent.description || rightIntent.action} if not exists`;
1189
- }
1190
- else {
1191
- description = `${leftIntent.description || leftIntent.action} or ${rightIntent.description || rightIntent.action}`;
1192
- }
1193
- }
1194
- return {
1195
- action: leftIntent.action,
1196
- object: leftIntent.object,
1197
- description,
1198
- commands,
1199
- reads: [...(leftIntent.reads || []), ...(rightIntent.reads || [])],
1200
- writes: [...(leftIntent.writes || []), ...(rightIntent.writes || [])],
1201
- deletes: [...(leftIntent.deletes || []), ...(rightIntent.deletes || [])],
1202
- modifiers: [...(leftIntent.modifiers || []), ...(rightIntent.modifiers || [])],
1203
- actionType: rightIntent.actionType || leftIntent.actionType || 'execute',
1204
- network: leftIntent.network || rightIntent.network,
1205
- elevated: leftIntent.elevated || rightIntent.elevated,
1206
- };
1207
- }
1208
- /**
1209
- * Extract intent from any AST node
1210
- */
1211
- function extractNodeIntent(node) {
1212
- switch (node.type) {
1213
- case 'Command':
1214
- return extractCommandIntent(node);
1215
- case 'Pipeline':
1216
- return extractPipelineIntent(node);
1217
- case 'List':
1218
- return extractListIntent(node);
1219
- default:
1220
- return {
1221
- action: 'execute',
1222
- object: 'command',
1223
- description: 'execute command',
1224
- modifiers: [],
1225
- actionType: 'execute',
1226
- };
1227
- }
1228
- }
1229
- /**
1230
- * Extract intent from a parsed AST Program
1231
- *
1232
- * @param ast - The parsed Program AST
1233
- * @returns Extended intent with semantic information
1234
- */
1235
- export function extractIntentFromAST(ast) {
1236
- // Get all commands for basic intent
1237
- const allCommands = extractAllCommands(ast);
1238
- const basicIntent = extractIntent(allCommands);
1239
- // Get semantic intent from first statement
1240
- const firstNode = ast.body[0];
1241
- let semanticIntent = {
1242
- action: 'execute',
1243
- object: 'command',
1244
- description: 'execute command',
1245
- modifiers: [],
1246
- actionType: 'read',
1247
- };
1248
- if (firstNode) {
1249
- semanticIntent = extractNodeIntent(firstNode);
1250
- // Aggregate from all nodes for compound commands
1251
- if (ast.body.length > 1) {
1252
- for (let i = 1; i < ast.body.length; i++) {
1253
- const nodeIntent = extractNodeIntent(ast.body[i]);
1254
- semanticIntent.commands = [
1255
- ...(semanticIntent.commands || []),
1256
- ...(nodeIntent.commands || []),
1257
- ];
1258
- }
1259
- }
1260
- }
1261
- // Merge basic and semantic intent
1262
- return {
1263
- ...basicIntent,
1264
- action: semanticIntent.action || 'execute',
1265
- object: semanticIntent.object || 'command',
1266
- description: semanticIntent.description || 'execute command',
1267
- target: semanticIntent.target,
1268
- targets: semanticIntent.targets,
1269
- source: semanticIntent.source,
1270
- searchPath: semanticIntent.searchPath,
1271
- pattern: semanticIntent.pattern,
1272
- message: semanticIntent.message,
1273
- url: semanticIntent.url,
1274
- method: semanticIntent.method,
1275
- remote: semanticIntent.remote,
1276
- branch: semanticIntent.branch,
1277
- modifiers: semanticIntent.modifiers || [],
1278
- actionType: semanticIntent.actionType || 'read',
1279
- objectType: semanticIntent.objectType,
1280
- // Override with aggregated values from semantic analysis
1281
- commands: semanticIntent.commands?.length ? semanticIntent.commands : basicIntent.commands,
1282
- reads: semanticIntent.reads?.length ? semanticIntent.reads : basicIntent.reads,
1283
- writes: semanticIntent.writes?.length ? semanticIntent.writes : basicIntent.writes,
1284
- };
1285
- }
1286
- /**
1287
- * Generate a human-readable description from an Intent object
1288
- *
1289
- * @param intent - The Intent to describe
1290
- * @returns Human-readable description string
1291
- */
1292
- export function describeIntent(intent) {
1293
- const parts = [];
1294
- // Describe elevated operations
1295
- if (intent.elevated) {
1296
- parts.push('(elevated/privileged)');
1297
- }
1298
- // Detect move operation: when reads, writes, and deletes overlap (source is read, deleted, dest is written)
1299
- const isMoveOperation = intent.commands.includes('mv') ||
1300
- (intent.writes.length > 0 && intent.reads.length > 0 &&
1301
- intent.deletes.length > 0 && intent.reads.some(r => intent.deletes.includes(r)));
1302
- // Describe main operation
1303
- if (isMoveOperation && intent.reads.length > 0 && intent.writes.length > 0) {
1304
- // Move operation
1305
- parts.push(`move ${intent.reads[0]} to ${intent.writes[0]}`);
1306
- }
1307
- else if (intent.deletes.length > 0) {
1308
- parts.push(`delete ${intent.deletes.join(', ')}`);
1309
- }
1310
- else if (intent.writes.length > 0) {
1311
- parts.push(`write to ${intent.writes.join(', ')}`);
1312
- }
1313
- else if (intent.reads.length > 0) {
1314
- parts.push(`read ${intent.reads.join(', ')}`);
1315
- }
1316
- // Describe network operations
1317
- if (intent.network) {
1318
- if (intent.commands.includes('curl')) {
1319
- parts.push('fetch from network');
1320
- }
1321
- else if (intent.commands.includes('wget')) {
1322
- parts.push('download from network');
1323
- }
1324
- else {
1325
- parts.push('perform network operation');
1326
- }
1327
- }
1328
- // Fallback to command names
1329
- if (parts.length === 0 && intent.commands.length > 0) {
1330
- parts.push(`execute ${intent.commands.join(', ')}`);
1331
- }
1332
- return parts.join('; ');
1333
- }
1334
- /**
1335
- * Extract intent from commands
1336
- */
1337
- export function extractIntent(commands) {
1338
- const intent = {
1339
- commands: [],
1340
- reads: [],
1341
- writes: [],
1342
- deletes: [],
1343
- network: false,
1344
- elevated: false,
1345
- };
1346
- for (const cmd of commands) {
1347
- const name = getCommandName(cmd.name);
1348
- const args = getArgs(cmd.args);
1349
- const paths = extractPaths(args);
1350
- if (name) {
1351
- intent.commands.push(name);
1352
- }
1353
- // Check for elevated
1354
- if (name === 'sudo' || name === 'su' || name === 'doas') {
1355
- intent.elevated = true;
1356
- if (args.length > 0) {
1357
- intent.commands.push(args[0]);
1358
- }
1359
- }
1360
- // Check for network
1361
- if (NETWORK_COMMANDS.has(name) || GIT_NETWORK_SUBCOMMANDS.has(args[0])) {
1362
- intent.network = true;
1363
- }
1364
- // Categorize paths
1365
- if (DELETE_COMMANDS.has(name)) {
1366
- intent.deletes.push(...paths);
1367
- }
1368
- else if (name === 'mv') {
1369
- // mv reads source, writes to destination, and deletes source
1370
- if (paths.length >= 2) {
1371
- // All but last are sources (reads and deletes)
1372
- const sources = paths.slice(0, -1);
1373
- const dest = paths[paths.length - 1];
1374
- intent.reads.push(...sources);
1375
- intent.writes.push(dest);
1376
- intent.deletes.push(...sources); // mv removes from source
1377
- }
1378
- else if (paths.length === 1) {
1379
- // Single arg mv (unusual, but handle it)
1380
- intent.reads.push(paths[0]);
1381
- intent.deletes.push(paths[0]);
1382
- }
1383
- }
1384
- else if (WRITE_COMMANDS.has(name) || name === 'chmod' || name === 'chown') {
1385
- intent.writes.push(...paths);
1386
- }
1387
- else if (READ_ONLY_COMMANDS.has(name)) {
1388
- if (paths.length > 0) {
1389
- intent.reads.push(...paths);
1390
- }
1391
- else if (name === 'ls' || name === 'pwd') {
1392
- intent.reads.push('.');
1393
- }
1394
- }
1395
- // Handle redirects
1396
- for (const redirect of cmd.redirects) {
1397
- const target = redirect.target.value;
1398
- if (redirect.op === '>' || redirect.op === '>>' || redirect.op === '>|') {
1399
- intent.writes.push(target);
1400
- if (isSystemPath(target)) {
1401
- intent.elevated = true;
1402
- }
1403
- }
1404
- else if (redirect.op === '<') {
1405
- intent.reads.push(target);
1406
- }
1407
- }
1408
- // Check if targeting system paths elevates privileges
1409
- if (hasCriticalPath(paths) || targetsSystemPath(paths)) {
1410
- if (DELETE_COMMANDS.has(name) || WRITE_COMMANDS.has(name) ||
1411
- name === 'chmod' || name === 'chown') {
1412
- intent.elevated = true;
1413
- }
1414
- }
1415
- }
1416
- return intent;
1417
- }
1418
- // ============================================================================
1419
- // Main Analysis Functions
1420
- // ============================================================================
1421
- /**
1422
- * Analyze an AST for safety classification
1423
- */
1424
- export function analyze(ast) {
1425
- const commands = extractAllCommands(ast);
1426
- if (commands.length === 0) {
1427
- return {
1428
- classification: {
1429
- type: 'read',
1430
- impact: 'none',
1431
- reversible: true,
1432
- reason: 'Empty command or no executable commands',
1433
- },
1434
- intent: {
1435
- commands: [],
1436
- reads: [],
1437
- writes: [],
1438
- deletes: [],
1439
- network: false,
1440
- elevated: false,
1441
- },
1442
- };
1443
- }
1444
- const classifications = [];
1445
- for (const cmd of commands) {
1446
- const name = getCommandName(cmd.name);
1447
- const args = getArgs(cmd.args);
1448
- // Classify the command itself
1449
- classifications.push(classifyCommand(name, args));
1450
- // Classify redirects
1451
- for (const redirect of cmd.redirects) {
1452
- classifications.push(classifyRedirect(redirect));
1453
- }
1454
- }
1455
- const classification = combineClassifications(classifications);
1456
- const intent = extractIntent(commands);
1457
- return { classification, intent };
1458
- }
1459
- /**
1460
- * Check if command is dangerous based on AST structure
1461
- */
1462
- export function isDangerous(ast) {
1463
- const { classification } = analyze(ast);
1464
- if (classification.impact === 'critical') {
1465
- return { dangerous: true, reason: classification.reason };
1466
- }
1467
- if (classification.impact === 'high' && !classification.reversible) {
1468
- return { dangerous: true, reason: classification.reason };
1469
- }
1470
- return { dangerous: false };
1471
- }
1472
- //# sourceMappingURL=analyze.js.map