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.
- package/cli/README.md +238 -0
- package/cli/agent.ts +72 -0
- package/cli/bin.js +44 -0
- package/cli/bin.ts +38 -0
- package/cli/build.ts +157 -0
- package/cli/commands/auth/login.ts +14 -0
- package/cli/commands/auth/logout.ts +6 -0
- package/cli/commands/auth/whoami.ts +16 -0
- package/cli/commands/deploy-multi.ts +245 -0
- package/cli/commands/dev/deploy.ts +100 -0
- package/cli/commands/dev/dev.ts +95 -0
- package/cli/commands/dev/logs.ts +91 -0
- package/cli/commands/dev-local.ts +88 -0
- package/cli/commands/do-ops.ts +314 -0
- package/cli/commands/index.ts +100 -0
- package/cli/commands/init.ts +247 -0
- package/cli/commands/introspect/emitter.ts +315 -0
- package/cli/commands/introspect/index.ts +193 -0
- package/cli/commands/link.ts +598 -0
- package/cli/commands/snippets.ts +415 -0
- package/cli/commands/tunnel.ts +239 -0
- package/cli/device-auth.ts +289 -0
- package/cli/fallback.ts +12 -0
- package/cli/index.ts +121 -0
- package/cli/main.ts +246 -0
- package/cli/mcp-stdio.ts +790 -0
- package/cli/package.json +62 -0
- package/cli/runtime/do-registry.ts +193 -0
- package/cli/runtime/embedded-db.ts +344 -0
- package/cli/runtime/index.ts +9 -0
- package/cli/runtime/miniflare-adapter.ts +162 -0
- package/cli/sandbox.ts +82 -0
- package/cli/src/args.ts +174 -0
- package/cli/src/auth.ts +55 -0
- package/cli/src/commands/call.ts +84 -0
- package/cli/src/commands/charge.ts +96 -0
- package/cli/src/commands/config.ts +115 -0
- package/cli/src/commands/email.ts +112 -0
- package/cli/src/commands/llm.ts +115 -0
- package/cli/src/commands/queue.ts +134 -0
- package/cli/src/commands/text.ts +86 -0
- package/cli/src/config.ts +185 -0
- package/cli/src/output.ts +246 -0
- package/cli/src/rpc.ts +192 -0
- package/cli/utils/config.ts +282 -0
- package/cli/utils/detect.ts +73 -0
- package/cli/utils/index.ts +15 -0
- package/cli/utils/logger.ts +232 -0
- package/dist/ai/template-literals.js +2 -2
- package/dist/ai/template-literals.js.map +1 -1
- package/dist/api/middleware/auth.js +3 -2
- package/dist/api/middleware/auth.js.map +1 -1
- package/dist/db/iceberg/inverted-index.js +1 -1
- package/dist/db/iceberg/inverted-index.js.map +1 -1
- package/dist/db/iceberg/puffin.js.map +1 -1
- package/dist/db/json-indexes.js.map +1 -1
- package/dist/db/objects.js.map +1 -1
- package/dist/db/primitives/dag-scheduler/index.js +1 -1
- package/dist/db/primitives/dag-scheduler/index.js.map +1 -1
- package/dist/db/primitives/observability.js.map +1 -1
- package/dist/db/primitives/schema-evolution.js.map +1 -1
- package/dist/db/primitives/temporal-store.js.map +1 -1
- package/dist/db/primitives/typed-column-store.js.map +1 -1
- package/dist/db/primitives/utils/duration.js.map +1 -1
- package/dist/db/primitives/utils/murmur3.js +12 -14
- package/dist/db/primitives/utils/murmur3.js.map +1 -1
- package/dist/db/primitives/window-manager.js.map +1 -1
- package/dist/db/stores.js.map +1 -1
- package/dist/db/things.js.map +1 -1
- package/dist/lib/DODispatcher.js +2 -2
- package/dist/lib/DODispatcher.js.map +1 -1
- package/dist/lib/auto-wiring.js.map +1 -1
- package/dist/lib/channels/email.js +1 -1
- package/dist/lib/channels/email.js.map +1 -1
- package/dist/lib/channels/slack-blockkit.js.map +1 -1
- package/dist/lib/cloudflare/ai.js +1 -1
- package/dist/lib/cloudflare/ai.js.map +1 -1
- package/dist/lib/cloudflare/kv.js +1 -1
- package/dist/lib/cloudflare/kv.js.map +1 -1
- package/dist/lib/cloudflare/r2.js +3 -3
- package/dist/lib/cloudflare/r2.js.map +1 -1
- package/dist/lib/cloudflare/vectorize.js.map +1 -1
- package/dist/lib/cloudflare/workflows.js.map +1 -1
- package/dist/lib/executors/AgenticFunctionExecutor.js.map +1 -1
- package/dist/lib/executors/CodeFunctionExecutor.js.map +1 -1
- package/dist/lib/executors/GenerativeFunctionExecutor.js.map +1 -1
- package/dist/lib/executors/HumanFunctionExecutor.js +1 -1
- package/dist/lib/executors/HumanFunctionExecutor.js.map +1 -1
- package/dist/lib/executors/ParallelStepExecutor.js.map +1 -1
- package/dist/lib/experiments.js.map +1 -1
- package/dist/lib/flags/store.js.map +1 -1
- package/dist/lib/functions/FunctionComposition.js.map +1 -1
- package/dist/lib/functions/FunctionMiddleware.js.map +1 -1
- package/dist/lib/functions/FunctionRegistry.js.map +1 -1
- package/dist/lib/humans/templates.js.map +1 -1
- package/dist/lib/identity.js +2 -2
- package/dist/lib/identity.js.map +1 -1
- package/dist/lib/logging/index.js.map +1 -1
- package/dist/lib/mixins/bash.js +1 -73
- package/dist/lib/mixins/bash.js.map +1 -1
- package/dist/lib/mixins/git.js +0 -5
- package/dist/lib/mixins/git.js.map +1 -1
- package/dist/lib/mixins/npm.js.map +1 -1
- package/dist/lib/noun-id.js.map +1 -1
- package/dist/lib/rate-limit/sliding-window.js.map +1 -1
- package/dist/lib/rpc/bindings.js.map +1 -1
- package/dist/lib/safe-stringify.js.map +1 -1
- package/dist/lib/sandbox/miniflare-sandbox.js.map +1 -1
- package/dist/lib/sqids.js.map +1 -1
- package/dist/lib/sql/adapters/node-sql-parser.js.map +1 -1
- package/dist/lib/sql/adapters/pgsql-parser.js +19 -18
- package/dist/lib/sql/adapters/pgsql-parser.js.map +1 -1
- package/dist/metrics/hunch.js.map +1 -1
- package/dist/objects/API.js +1 -1
- package/dist/objects/API.js.map +1 -1
- package/dist/objects/Agent.js.map +1 -1
- package/dist/objects/Browser.js.map +1 -1
- package/dist/objects/CLI.js.map +1 -1
- package/dist/objects/DOBase.js.map +1 -1
- package/dist/objects/DOCache.js +153 -0
- package/dist/objects/DOCache.js.map +1 -0
- package/dist/objects/DOFull.js.map +1 -1
- package/dist/objects/Entity.js.map +1 -1
- package/dist/objects/Human.js.map +1 -1
- package/dist/objects/IcebergMetadataDO.js.map +1 -1
- package/dist/objects/IntegrationsDO.js.map +1 -1
- package/dist/objects/ObservabilityBroadcaster.js.map +1 -1
- package/dist/objects/Package.js.map +1 -1
- package/dist/objects/Product.js +1 -1
- package/dist/objects/Product.js.map +1 -1
- package/dist/objects/SaaS.js.map +1 -1
- package/dist/objects/SandboxDO.js.map +1 -1
- package/dist/objects/Service.js.map +1 -1
- package/dist/objects/VectorShardDO.js +9 -7
- package/dist/objects/VectorShardDO.js.map +1 -1
- package/dist/objects/Workflow.js.map +1 -1
- package/dist/objects/WorkflowFactory.js.map +1 -1
- package/dist/objects/WorkflowRuntime.js.map +1 -1
- package/dist/objects/lifecycle/Branch.js.map +1 -1
- package/dist/objects/lifecycle/Clone.js +1 -1
- package/dist/objects/lifecycle/Clone.js.map +1 -1
- package/dist/objects/lifecycle/Compact.js.map +1 -1
- package/dist/objects/lifecycle/Shard.js.map +1 -1
- package/dist/objects/persistence/checkpoint-manager.js.map +1 -1
- package/dist/objects/persistence/migration-runner.js.map +1 -1
- package/dist/objects/persistence/replication-manager.js +2 -2
- package/dist/objects/persistence/replication-manager.js.map +1 -1
- package/dist/objects/persistence/tiered-storage-manager.js.map +1 -1
- package/dist/objects/persistence/wal-manager.js.map +1 -1
- package/dist/objects/transport/auth-layer.js.map +1 -1
- package/dist/objects/transport/chain.js.map +1 -1
- package/dist/objects/transport/mcp-server.js +7 -6
- package/dist/objects/transport/mcp-server.js.map +1 -1
- package/dist/objects/transport/rest-autowire.js +3 -2
- package/dist/objects/transport/rest-autowire.js.map +1 -1
- package/dist/objects/transport/rest-router.js.map +1 -1
- package/dist/objects/transport/rpc-server.js +18 -15
- package/dist/objects/transport/rpc-server.js.map +1 -1
- package/dist/objects/transport/shared.js +2 -1
- package/dist/objects/transport/shared.js.map +1 -1
- package/dist/snippets/artifacts-ingest.js.map +1 -1
- package/dist/snippets/artifacts-serve.js.map +1 -1
- package/dist/snippets/search.js.map +1 -1
- package/dist/workflows/ScheduleManager.js.map +1 -1
- package/dist/workflows/StepResultStorage.js.map +1 -1
- package/dist/workflows/WaitForEventManager.js.map +1 -1
- package/dist/workflows/compat/backends/cloudflare-workflows.js.map +1 -1
- package/dist/workflows/compat/inngest/index.js.map +1 -1
- package/dist/workflows/compat/qstash/index.js.map +1 -1
- package/dist/workflows/compat/temporal/client.js.map +1 -1
- package/dist/workflows/compat/temporal/index.js.map +1 -1
- package/dist/workflows/compat/trigger/index.js.map +1 -1
- package/dist/workflows/compat/utils/index.js.map +1 -1
- package/dist/workflows/context/correlation.js +2 -2
- package/dist/workflows/context/correlation.js.map +1 -1
- package/dist/workflows/context/experiment.js +1 -1
- package/dist/workflows/context/experiment.js.map +1 -1
- package/dist/workflows/context/flag.js +1 -1
- package/dist/workflows/context/flag.js.map +1 -1
- package/dist/workflows/context/measure.js +1 -1
- package/dist/workflows/context/measure.js.map +1 -1
- package/dist/workflows/context/rate-limit.js.map +1 -1
- package/dist/workflows/data/entity-events/entity-events.js.map +1 -1
- package/dist/workflows/data/experiment/index.js.map +1 -1
- package/dist/workflows/data/goal/context.js +1 -1
- package/dist/workflows/data/goal/context.js.map +1 -1
- package/dist/workflows/data/measure/index.js +1 -1
- package/dist/workflows/data/measure/index.js.map +1 -1
- package/dist/workflows/data/stream/index.js +10 -76
- package/dist/workflows/data/stream/index.js.map +1 -1
- package/dist/workflows/data/track/context.js.map +1 -1
- package/dist/workflows/data/view/context.js.map +1 -1
- package/dist/workflows/domain.js.map +1 -1
- package/dist/workflows/flags.js +1 -1
- package/dist/workflows/flags.js.map +1 -1
- package/dist/workflows/hash.js.map +1 -1
- package/dist/workflows/on.js +1 -1
- package/dist/workflows/on.js.map +1 -1
- package/dist/workflows/schedule-builder.js.map +1 -1
- package/dist/workflows/visibility/index.js +0 -2
- package/dist/workflows/visibility/index.js.map +1 -1
- package/dist/workflows/visibility/query-parser.js.map +1 -1
- package/package.json +18 -3
- package/dist/api/analytics/router.js +0 -601
- package/dist/api/analytics/router.js.map +0 -1
- package/dist/api/index.js +0 -158
- package/dist/api/index.js.map +0 -1
- package/dist/api/middleware/error-handling.js +0 -176
- package/dist/api/middleware/error-handling.js.map +0 -1
- package/dist/api/middleware/request-id.js +0 -21
- package/dist/api/middleware/request-id.js.map +0 -1
- package/dist/api/pages.js +0 -1180
- package/dist/api/pages.js.map +0 -1
- package/dist/api/routes/api.js +0 -612
- package/dist/api/routes/api.js.map +0 -1
- package/dist/api/routes/browsers.js +0 -471
- package/dist/api/routes/browsers.js.map +0 -1
- package/dist/api/routes/do.js +0 -188
- package/dist/api/routes/do.js.map +0 -1
- package/dist/api/routes/mcp.js +0 -459
- package/dist/api/routes/mcp.js.map +0 -1
- package/dist/api/routes/obs.js +0 -445
- package/dist/api/routes/obs.js.map +0 -1
- package/dist/api/routes/openapi.js +0 -794
- package/dist/api/routes/openapi.js.map +0 -1
- package/dist/api/routes/rpc.js +0 -1103
- package/dist/api/routes/rpc.js.map +0 -1
- package/dist/api/routes/sandboxes.js +0 -389
- package/dist/api/routes/sandboxes.js.map +0 -1
- package/dist/api/test-do.js +0 -38
- package/dist/api/test-do.js.map +0 -1
- package/dist/api/types.js +0 -11
- package/dist/api/types.js.map +0 -1
- package/dist/cli/bin.js +0 -2
- package/dist/cli/main.js +0 -52342
- package/dist/do/bash.js +0 -35
- package/dist/do/bash.js.map +0 -1
- package/dist/do/fs.js +0 -25
- package/dist/do/fs.js.map +0 -1
- package/dist/do/full.js +0 -61
- package/dist/do/full.js.map +0 -1
- package/dist/do/git.js +0 -28
- package/dist/do/git.js.map +0 -1
- package/dist/do/index.js +0 -52
- package/dist/do/index.js.map +0 -1
- package/dist/lib/agent/tools/bash.js +0 -336
- package/dist/lib/agent/tools/bash.js.map +0 -1
- package/dist/lib/agent/tools/edit.js +0 -157
- package/dist/lib/agent/tools/edit.js.map +0 -1
- package/dist/lib/agent/tools/glob.js +0 -137
- package/dist/lib/agent/tools/glob.js.map +0 -1
- package/dist/lib/agent/tools/grep.js +0 -315
- package/dist/lib/agent/tools/grep.js.map +0 -1
- package/dist/lib/agent/tools/index.js +0 -71
- package/dist/lib/agent/tools/index.js.map +0 -1
- package/dist/lib/agent/tools/read.js +0 -212
- package/dist/lib/agent/tools/read.js.map +0 -1
- package/dist/lib/agent/tools/types.js +0 -197
- package/dist/lib/agent/tools/types.js.map +0 -1
- package/dist/lib/agent/tools/write.js +0 -159
- package/dist/lib/agent/tools/write.js.map +0 -1
- package/dist/lib/mixins/index.js +0 -29
- package/dist/lib/mixins/index.js.map +0 -1
- package/dist/primitives/bashx/src/ast/analyze.js +0 -1472
- package/dist/primitives/bashx/src/ast/analyze.js.map +0 -1
- package/dist/primitives/bashx/src/ast/parser.js +0 -1488
- package/dist/primitives/bashx/src/ast/parser.js.map +0 -1
- package/dist/primitives/bashx/src/do/commands/crypto.js +0 -1954
- package/dist/primitives/bashx/src/do/commands/crypto.js.map +0 -1
- package/dist/primitives/bashx/src/do/commands/data-processing.js +0 -1812
- package/dist/primitives/bashx/src/do/commands/data-processing.js.map +0 -1
- package/dist/primitives/bashx/src/do/commands/extended-utils.js +0 -804
- package/dist/primitives/bashx/src/do/commands/extended-utils.js.map +0 -1
- package/dist/primitives/bashx/src/do/commands/math-control.js +0 -1122
- package/dist/primitives/bashx/src/do/commands/math-control.js.map +0 -1
- package/dist/primitives/bashx/src/do/commands/posix-utils.js +0 -1015
- package/dist/primitives/bashx/src/do/commands/posix-utils.js.map +0 -1
- package/dist/primitives/bashx/src/do/commands/system-utils.js +0 -687
- package/dist/primitives/bashx/src/do/commands/system-utils.js.map +0 -1
- package/dist/primitives/bashx/src/do/commands/test-command.js +0 -523
- package/dist/primitives/bashx/src/do/commands/test-command.js.map +0 -1
- package/dist/primitives/bashx/src/do/commands/text-processing.js +0 -1550
- package/dist/primitives/bashx/src/do/commands/text-processing.js.map +0 -1
- package/dist/primitives/bashx/src/do/container-executor.js +0 -429
- package/dist/primitives/bashx/src/do/container-executor.js.map +0 -1
- package/dist/primitives/bashx/src/do/index.js +0 -668
- package/dist/primitives/bashx/src/do/index.js.map +0 -1
- package/dist/primitives/bashx/src/do/tiered-executor.js +0 -2647
- package/dist/primitives/bashx/src/do/tiered-executor.js.map +0 -1
- package/dist/primitives/bashx/src/do/worker.js +0 -352
- package/dist/primitives/bashx/src/do/worker.js.map +0 -1
- package/dist/primitives/bashx/src/types.js +0 -10
- package/dist/primitives/bashx/src/types.js.map +0 -1
- package/dist/primitives/fsx/core/backend.js +0 -480
- package/dist/primitives/fsx/core/backend.js.map +0 -1
- package/dist/primitives/fsx/core/constants.js +0 -140
- package/dist/primitives/fsx/core/constants.js.map +0 -1
- package/dist/primitives/fsx/core/fsx.js +0 -1184
- package/dist/primitives/fsx/core/fsx.js.map +0 -1
- package/dist/primitives/fsx/core/glob/glob.js +0 -438
- package/dist/primitives/fsx/core/glob/glob.js.map +0 -1
- package/dist/primitives/fsx/core/glob/index.js +0 -8
- package/dist/primitives/fsx/core/glob/index.js.map +0 -1
- package/dist/primitives/fsx/core/glob/match.js +0 -392
- package/dist/primitives/fsx/core/glob/match.js.map +0 -1
- package/dist/primitives/fsx/core/types.js +0 -307
- package/dist/primitives/fsx/core/types.js.map +0 -1
- package/dist/sdk/capnweb-compat.js +0 -42
- package/dist/sdk/capnweb-compat.js.map +0 -1
- package/dist/sdk/client.js +0 -20
- package/dist/sdk/client.js.map +0 -1
- package/dist/sdk/index.js +0 -17
- 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
|