@namzu/sdk 51.0.0 → 52.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +110 -0
- package/dist/agents/QueryAgent.d.ts.map +1 -1
- package/dist/agents/QueryAgent.js +2 -0
- package/dist/agents/QueryAgent.js.map +1 -1
- package/dist/agents/SupervisorAgent.d.ts +1 -1
- package/dist/agents/SupervisorAgent.d.ts.map +1 -1
- package/dist/agents/SupervisorAgent.js.map +1 -1
- package/dist/agents/examples/SupervisorAgent.d.ts +42 -0
- package/dist/agents/examples/SupervisorAgent.d.ts.map +1 -1
- package/dist/agents/examples/SupervisorAgent.js +80 -4
- package/dist/agents/examples/SupervisorAgent.js.map +1 -1
- package/dist/agents/index.d.ts +1 -1
- package/dist/agents/index.d.ts.map +1 -1
- package/dist/agents/index.js.map +1 -1
- package/dist/authorization/protected-paths.d.ts +36 -0
- package/dist/authorization/protected-paths.d.ts.map +1 -0
- package/dist/authorization/protected-paths.js +107 -0
- package/dist/authorization/protected-paths.js.map +1 -0
- package/dist/authorization/readonly-commands.d.ts +39 -0
- package/dist/authorization/readonly-commands.d.ts.map +1 -0
- package/dist/authorization/readonly-commands.js +797 -0
- package/dist/authorization/readonly-commands.js.map +1 -0
- package/dist/authorization/readonly-flags.d.ts +19 -0
- package/dist/authorization/readonly-flags.d.ts.map +1 -0
- package/dist/authorization/readonly-flags.js +178 -0
- package/dist/authorization/readonly-flags.js.map +1 -0
- package/dist/authorization/removal.d.ts +52 -0
- package/dist/authorization/removal.d.ts.map +1 -0
- package/dist/authorization/removal.js +283 -0
- package/dist/authorization/removal.js.map +1 -0
- package/dist/authorization/shell-paths.d.ts +66 -0
- package/dist/authorization/shell-paths.d.ts.map +1 -0
- package/dist/authorization/shell-paths.js +697 -0
- package/dist/authorization/shell-paths.js.map +1 -0
- package/dist/bridge/acp/update.d.ts.map +1 -1
- package/dist/bridge/acp/update.js +1 -0
- package/dist/bridge/acp/update.js.map +1 -1
- package/dist/bridge/sse/mapper.d.ts.map +1 -1
- package/dist/bridge/sse/mapper.js +1 -0
- package/dist/bridge/sse/mapper.js.map +1 -1
- package/dist/compaction/context-window.d.ts +24 -0
- package/dist/compaction/context-window.d.ts.map +1 -1
- package/dist/compaction/context-window.js +34 -0
- package/dist/compaction/context-window.js.map +1 -1
- package/dist/compaction/manual.d.ts.map +1 -1
- package/dist/compaction/manual.js +10 -2
- package/dist/compaction/manual.js.map +1 -1
- package/dist/compaction/plan.d.ts +4 -0
- package/dist/compaction/plan.d.ts.map +1 -1
- package/dist/compaction/plan.js +6 -2
- package/dist/compaction/plan.js.map +1 -1
- package/dist/compaction/verifier.d.ts +11 -1
- package/dist/compaction/verifier.d.ts.map +1 -1
- package/dist/compaction/verifier.js +19 -7
- package/dist/compaction/verifier.js.map +1 -1
- package/dist/config/runtime.d.ts +55 -7
- package/dist/config/runtime.d.ts.map +1 -1
- package/dist/config/runtime.js +23 -2
- package/dist/config/runtime.js.map +1 -1
- package/dist/connector/web/guarded-fetch.d.ts.map +1 -1
- package/dist/connector/web/guarded-fetch.js +24 -0
- package/dist/connector/web/guarded-fetch.js.map +1 -1
- package/dist/connector/web/index.d.ts +1 -1
- package/dist/connector/web/index.d.ts.map +1 -1
- package/dist/connector/web/page.d.ts +59 -0
- package/dist/connector/web/page.d.ts.map +1 -0
- package/dist/connector/web/page.js +112 -0
- package/dist/connector/web/page.js.map +1 -0
- package/dist/connector/web/types.d.ts +47 -0
- package/dist/connector/web/types.d.ts.map +1 -1
- package/dist/connector/web/types.js.map +1 -1
- package/dist/constants/continuation.d.ts +8 -0
- package/dist/constants/continuation.d.ts.map +1 -1
- package/dist/constants/continuation.js +8 -0
- package/dist/constants/continuation.js.map +1 -1
- package/dist/constants/plugin/index.d.ts +1 -1
- package/dist/constants/plugin/index.d.ts.map +1 -1
- package/dist/constants/plugin/index.js +1 -1
- package/dist/constants/plugin/index.js.map +1 -1
- package/dist/manager/agent/lifecycle.d.ts +7 -0
- package/dist/manager/agent/lifecycle.d.ts.map +1 -1
- package/dist/manager/agent/lifecycle.js +11 -2
- package/dist/manager/agent/lifecycle.js.map +1 -1
- package/dist/manager/session/turn-recorder.d.ts.map +1 -1
- package/dist/manager/session/turn-recorder.js +4 -2
- package/dist/manager/session/turn-recorder.js.map +1 -1
- package/dist/plugin/index.d.ts +1 -1
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +1 -1
- package/dist/plugin/index.js.map +1 -1
- package/dist/plugin/lifecycle.d.ts +6 -0
- package/dist/plugin/lifecycle.d.ts.map +1 -1
- package/dist/plugin/lifecycle.js +98 -114
- package/dist/plugin/lifecycle.js.map +1 -1
- package/dist/plugin/shell-hook.d.ts +90 -22
- package/dist/plugin/shell-hook.d.ts.map +1 -1
- package/dist/plugin/shell-hook.js +434 -41
- package/dist/plugin/shell-hook.js.map +1 -1
- package/dist/provider/errors.d.ts +10 -0
- package/dist/provider/errors.d.ts.map +1 -1
- package/dist/provider/errors.js +16 -0
- package/dist/provider/errors.js.map +1 -1
- package/dist/provider/retry.d.ts +27 -0
- package/dist/provider/retry.d.ts.map +1 -1
- package/dist/provider/retry.js +19 -3
- package/dist/provider/retry.js.map +1 -1
- package/dist/public-runtime.d.ts +4 -1
- package/dist/public-runtime.d.ts.map +1 -1
- package/dist/public-runtime.js +4 -1
- package/dist/public-runtime.js.map +1 -1
- package/dist/public-types.d.ts +2 -2
- package/dist/public-types.d.ts.map +1 -1
- package/dist/runtime/jobs/registry.d.ts +21 -0
- package/dist/runtime/jobs/registry.d.ts.map +1 -1
- package/dist/runtime/jobs/registry.js +15 -2
- package/dist/runtime/jobs/registry.js.map +1 -1
- package/dist/runtime/query/executor/tool-call-admission.d.ts +10 -0
- package/dist/runtime/query/executor/tool-call-admission.d.ts.map +1 -1
- package/dist/runtime/query/executor/tool-call-admission.js +74 -13
- package/dist/runtime/query/executor/tool-call-admission.js.map +1 -1
- package/dist/runtime/query/executor.d.ts +41 -2
- package/dist/runtime/query/executor.d.ts.map +1 -1
- package/dist/runtime/query/executor.js +190 -6
- package/dist/runtime/query/executor.js.map +1 -1
- package/dist/runtime/query/finalize-turn.d.ts +6 -0
- package/dist/runtime/query/finalize-turn.d.ts.map +1 -1
- package/dist/runtime/query/finalize-turn.js +1 -1
- package/dist/runtime/query/finalize-turn.js.map +1 -1
- package/dist/runtime/query/index.d.ts +29 -0
- package/dist/runtime/query/index.d.ts.map +1 -1
- package/dist/runtime/query/index.js +15 -3
- package/dist/runtime/query/index.js.map +1 -1
- package/dist/runtime/query/iteration/index.d.ts +31 -0
- package/dist/runtime/query/iteration/index.d.ts.map +1 -1
- package/dist/runtime/query/iteration/index.js +216 -7
- package/dist/runtime/query/iteration/index.js.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction-breaker.d.ts +58 -0
- package/dist/runtime/query/iteration/phases/compaction-breaker.d.ts.map +1 -0
- package/dist/runtime/query/iteration/phases/compaction-breaker.js +91 -0
- package/dist/runtime/query/iteration/phases/compaction-breaker.js.map +1 -0
- package/dist/runtime/query/iteration/phases/compaction.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction.js +89 -7
- package/dist/runtime/query/iteration/phases/compaction.js.map +1 -1
- package/dist/runtime/query/iteration/phases/context.d.ts +10 -0
- package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/context.js.map +1 -1
- package/dist/runtime/query/iteration/phases/tool-review.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/tool-review.js +165 -2
- package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
- package/dist/runtime/query/iteration/stream-turn.d.ts.map +1 -1
- package/dist/runtime/query/iteration/stream-turn.js +24 -1
- package/dist/runtime/query/iteration/stream-turn.js.map +1 -1
- package/dist/runtime/query/path-escalation.d.ts +36 -0
- package/dist/runtime/query/path-escalation.d.ts.map +1 -0
- package/dist/runtime/query/path-escalation.js +184 -0
- package/dist/runtime/query/path-escalation.js.map +1 -0
- package/dist/runtime/query/plugin-hooks.d.ts.map +1 -1
- package/dist/runtime/query/plugin-hooks.js +8 -0
- package/dist/runtime/query/plugin-hooks.js.map +1 -1
- package/dist/runtime/query/reminders.d.ts +35 -0
- package/dist/runtime/query/reminders.d.ts.map +1 -0
- package/dist/runtime/query/reminders.js +7 -0
- package/dist/runtime/query/reminders.js.map +1 -0
- package/dist/runtime/query/resume-pending.d.ts.map +1 -1
- package/dist/runtime/query/resume-pending.js +26 -0
- package/dist/runtime/query/resume-pending.js.map +1 -1
- package/dist/runtime/query/review-policy.d.ts +26 -0
- package/dist/runtime/query/review-policy.d.ts.map +1 -1
- package/dist/runtime/query/review-policy.js +79 -5
- package/dist/runtime/query/review-policy.js.map +1 -1
- package/dist/runtime/query/tool-output-budget.d.ts +46 -0
- package/dist/runtime/query/tool-output-budget.d.ts.map +1 -1
- package/dist/runtime/query/tool-output-budget.js +85 -1
- package/dist/runtime/query/tool-output-budget.js.map +1 -1
- package/dist/runtime/query/tooling.d.ts +1 -0
- package/dist/runtime/query/tooling.d.ts.map +1 -1
- package/dist/runtime/query/tooling.js +5 -0
- package/dist/runtime/query/tooling.js.map +1 -1
- package/dist/session/paths.d.ts +5 -1
- package/dist/session/paths.d.ts.map +1 -1
- package/dist/session/paths.js +8 -0
- package/dist/session/paths.js.map +1 -1
- package/dist/store/memory/disk.d.ts.map +1 -1
- package/dist/store/memory/disk.js +2 -2
- package/dist/store/memory/disk.js.map +1 -1
- package/dist/store/memory/markdown.d.ts.map +1 -1
- package/dist/store/memory/markdown.js +2 -2
- package/dist/store/memory/markdown.js.map +1 -1
- package/dist/store/memory/naming.d.ts +9 -0
- package/dist/store/memory/naming.d.ts.map +1 -1
- package/dist/store/memory/naming.js +12 -0
- package/dist/store/memory/naming.js.map +1 -1
- package/dist/tools/builtins/bash.d.ts +30 -1
- package/dist/tools/builtins/bash.d.ts.map +1 -1
- package/dist/tools/builtins/bash.js +170 -26
- package/dist/tools/builtins/bash.js.map +1 -1
- package/dist/tools/builtins/content-fingerprint.d.ts +2 -0
- package/dist/tools/builtins/content-fingerprint.d.ts.map +1 -1
- package/dist/tools/builtins/content-fingerprint.js +4 -0
- package/dist/tools/builtins/content-fingerprint.js.map +1 -1
- package/dist/tools/builtins/edit.d.ts.map +1 -1
- package/dist/tools/builtins/edit.js +60 -18
- package/dist/tools/builtins/edit.js.map +1 -1
- package/dist/tools/builtins/grep.d.ts +8 -1
- package/dist/tools/builtins/grep.d.ts.map +1 -1
- package/dist/tools/builtins/grep.js +152 -33
- package/dist/tools/builtins/grep.js.map +1 -1
- package/dist/tools/builtins/read-file.d.ts +1 -0
- package/dist/tools/builtins/read-file.d.ts.map +1 -1
- package/dist/tools/builtins/read-file.js +44 -5
- package/dist/tools/builtins/read-file.js.map +1 -1
- package/dist/tools/builtins/read-render.d.ts +52 -0
- package/dist/tools/builtins/read-render.d.ts.map +1 -1
- package/dist/tools/builtins/read-render.js +143 -0
- package/dist/tools/builtins/read-render.js.map +1 -1
- package/dist/tools/builtins/web.d.ts +13 -2
- package/dist/tools/builtins/web.d.ts.map +1 -1
- package/dist/tools/builtins/web.js +225 -32
- package/dist/tools/builtins/web.js.map +1 -1
- package/dist/tools/coordinator/ask-user-question.d.ts +2 -0
- package/dist/tools/coordinator/ask-user-question.d.ts.map +1 -1
- package/dist/tools/coordinator/ask-user-question.js +256 -92
- package/dist/tools/coordinator/ask-user-question.js.map +1 -1
- package/dist/tools/coordinator/question-options.d.ts +1 -0
- package/dist/tools/coordinator/question-options.d.ts.map +1 -1
- package/dist/tools/coordinator/question-options.js +6 -1
- package/dist/tools/coordinator/question-options.js.map +1 -1
- package/dist/tools/defineTool.d.ts +4 -1
- package/dist/tools/defineTool.d.ts.map +1 -1
- package/dist/tools/defineTool.js +6 -1
- package/dist/tools/defineTool.js.map +1 -1
- package/dist/tools/paths.d.ts +6 -0
- package/dist/tools/paths.d.ts.map +1 -1
- package/dist/tools/paths.js +1 -1
- package/dist/tools/paths.js.map +1 -1
- package/dist/types/agent/query.d.ts +3 -0
- package/dist/types/agent/query.d.ts.map +1 -1
- package/dist/types/errors/catalog.js +1 -1
- package/dist/types/errors/catalog.js.map +1 -1
- package/dist/types/hitl/index.d.ts +104 -0
- package/dist/types/hitl/index.d.ts.map +1 -1
- package/dist/types/hitl/index.js.map +1 -1
- package/dist/types/message/index.d.ts +1 -1
- package/dist/types/message/index.d.ts.map +1 -1
- package/dist/types/message/index.js +2 -0
- package/dist/types/message/index.js.map +1 -1
- package/dist/types/plugin/index.d.ts +93 -19
- package/dist/types/plugin/index.d.ts.map +1 -1
- package/dist/types/plugin/index.js +3 -0
- package/dist/types/plugin/index.js.map +1 -1
- package/dist/types/provider/error.d.ts +6 -0
- package/dist/types/provider/error.d.ts.map +1 -1
- package/dist/types/provider/errors.d.ts +19 -0
- package/dist/types/provider/errors.d.ts.map +1 -1
- package/dist/types/provider/errors.js +39 -1
- package/dist/types/provider/errors.js.map +1 -1
- package/dist/types/provider/stream.d.ts +6 -0
- package/dist/types/provider/stream.d.ts.map +1 -1
- package/dist/types/session/events.d.ts +15 -2
- package/dist/types/session/events.d.ts.map +1 -1
- package/dist/types/session/events.js.map +1 -1
- package/dist/types/session/records.d.ts +9 -9
- package/dist/types/session/records.d.ts.map +1 -1
- package/dist/types/session/records.js +1 -0
- package/dist/types/session/records.js.map +1 -1
- package/dist/types/session/stop-reason.d.ts +5 -0
- package/dist/types/session/stop-reason.d.ts.map +1 -1
- package/dist/types/tool/index.d.ts +52 -0
- package/dist/types/tool/index.d.ts.map +1 -1
- package/dist/types/tool/index.js.map +1 -1
- package/dist/utils/backoff.d.ts +12 -0
- package/dist/utils/backoff.d.ts.map +1 -1
- package/dist/utils/backoff.js +15 -0
- package/dist/utils/backoff.js.map +1 -1
- package/package.json +4 -2
- package/src/agents/QueryAgent.ts +5 -1
- package/src/agents/SupervisorAgent.ts +1 -1
- package/src/agents/examples/SupervisorAgent.ts +217 -106
- package/src/agents/index.ts +1 -1
- package/src/authorization/protected-paths.ts +110 -0
- package/src/authorization/readonly-commands.ts +771 -0
- package/src/authorization/readonly-flags.ts +196 -0
- package/src/authorization/removal.ts +326 -0
- package/src/authorization/shell-paths.ts +927 -0
- package/src/bridge/acp/update.ts +1 -0
- package/src/bridge/sse/mapper.ts +1 -0
- package/src/compaction/context-window.ts +41 -0
- package/src/compaction/manual.ts +10 -2
- package/src/compaction/plan.ts +11 -2
- package/src/compaction/verifier.ts +28 -3
- package/src/config/runtime.ts +23 -2
- package/src/connector/web/guarded-fetch.ts +26 -0
- package/src/connector/web/index.ts +3 -0
- package/src/connector/web/page.ts +130 -0
- package/src/connector/web/types.ts +47 -0
- package/src/constants/continuation.ts +10 -0
- package/src/constants/plugin/index.ts +1 -1
- package/src/manager/agent/lifecycle.ts +12 -4
- package/src/manager/session/turn-recorder.ts +10 -2
- package/src/plugin/index.ts +1 -0
- package/src/plugin/lifecycle.ts +107 -119
- package/src/plugin/shell-hook.ts +477 -43
- package/src/provider/errors.ts +18 -0
- package/src/provider/retry.ts +53 -3
- package/src/public-runtime.ts +8 -0
- package/src/public-types.ts +4 -0
- package/src/runtime/jobs/registry.ts +39 -2
- package/src/runtime/query/executor/tool-call-admission.ts +79 -13
- package/src/runtime/query/executor.ts +242 -8
- package/src/runtime/query/finalize-turn.ts +7 -1
- package/src/runtime/query/index.ts +45 -3
- package/src/runtime/query/iteration/index.ts +251 -12
- package/src/runtime/query/iteration/phases/compaction-breaker.ts +106 -0
- package/src/runtime/query/iteration/phases/compaction.ts +99 -12
- package/src/runtime/query/iteration/phases/context.ts +11 -0
- package/src/runtime/query/iteration/phases/tool-review.ts +177 -2
- package/src/runtime/query/iteration/stream-turn.ts +24 -1
- package/src/runtime/query/path-escalation.ts +204 -0
- package/src/runtime/query/plugin-hooks.ts +8 -0
- package/src/runtime/query/reminders.ts +45 -0
- package/src/runtime/query/resume-pending.ts +27 -0
- package/src/runtime/query/review-policy.ts +106 -5
- package/src/runtime/query/tool-output-budget.ts +115 -1
- package/src/runtime/query/tooling.ts +6 -0
- package/src/session/paths.ts +11 -1
- package/src/store/memory/disk.ts +2 -1
- package/src/store/memory/markdown.ts +2 -1
- package/src/store/memory/naming.ts +13 -0
- package/src/tools/builtins/bash.ts +203 -34
- package/src/tools/builtins/content-fingerprint.ts +5 -0
- package/src/tools/builtins/edit.ts +69 -19
- package/src/tools/builtins/grep.ts +159 -38
- package/src/tools/builtins/read-file.ts +48 -5
- package/src/tools/builtins/read-render.ts +193 -0
- package/src/tools/builtins/web.ts +258 -33
- package/src/tools/coordinator/ask-user-question.ts +309 -103
- package/src/tools/coordinator/question-options.ts +8 -1
- package/src/tools/defineTool.ts +11 -2
- package/src/tools/paths.ts +1 -1
- package/src/types/agent/query.ts +3 -0
- package/src/types/errors/catalog.ts +1 -1
- package/src/types/hitl/index.ts +104 -0
- package/src/types/message/index.ts +2 -0
- package/src/types/plugin/index.ts +101 -18
- package/src/types/provider/error.ts +6 -0
- package/src/types/provider/errors.ts +49 -1
- package/src/types/provider/stream.ts +6 -0
- package/src/types/session/events.ts +15 -2
- package/src/types/session/records.ts +1 -0
- package/src/types/session/stop-reason.ts +5 -0
- package/src/types/tool/index.ts +53 -2
- package/src/utils/backoff.ts +21 -0
|
@@ -0,0 +1,927 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The files a shell command line names, read before it runs.
|
|
3
|
+
*
|
|
4
|
+
* The file tools declare a `pathArgument`, so the kernel can see which path a
|
|
5
|
+
* call reaches. A shell command line has no such argument: `cat /etc/passwd`
|
|
6
|
+
* and `echo x > ~/.profile` carry their paths inside one string, and a
|
|
7
|
+
* boundary that only reads declared arguments let both through.
|
|
8
|
+
*
|
|
9
|
+
* This is the reference terminal's answer, ported: a table of the commands
|
|
10
|
+
* whose positional arguments are paths (its path-command table and
|
|
11
|
+
* per-command extractors, version 2.1.302), the kind of access each makes (read,
|
|
12
|
+
* write or create), and the output redirections of the line. It is a
|
|
13
|
+
* reading of the text, not a model of the shell:
|
|
14
|
+
*
|
|
15
|
+
* - a word that expands at runtime (`$VAR`, `$(...)`) has no path to read;
|
|
16
|
+
* it is reported with `unresolved` so the caller decides what an unreadable
|
|
17
|
+
* write target is worth (the reference asks);
|
|
18
|
+
* - a `cd` earlier in the line moves every relative path after it, which the
|
|
19
|
+
* reading does not follow, so the caller is told one happened;
|
|
20
|
+
* - a command that is not in the table contributes nothing.
|
|
21
|
+
*
|
|
22
|
+
* Nothing here touches the file system. The caller resolves the paths
|
|
23
|
+
* against the turn's roots and decides what is outside them or protected.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { unwrapReexecChain } from './reexec-wrapper.js'
|
|
27
|
+
import {
|
|
28
|
+
type ShellDialect,
|
|
29
|
+
type ShellRedirection,
|
|
30
|
+
type ShellWord,
|
|
31
|
+
basename,
|
|
32
|
+
lexShellCommandLine,
|
|
33
|
+
} from './shell-lexer.js'
|
|
34
|
+
|
|
35
|
+
/** One program invocation found in a line, wrappers unwrapped. */
|
|
36
|
+
export interface ShellInvocation {
|
|
37
|
+
/** The program's base name (`/bin/rm` reads as `rm`). */
|
|
38
|
+
readonly program: string
|
|
39
|
+
/** Its arguments, as the shell passes them. */
|
|
40
|
+
readonly args: readonly ShellWord[]
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** What the lexer found, in the shape this module and the removal check share. */
|
|
44
|
+
export interface ShellReading {
|
|
45
|
+
readonly invocations: readonly ShellInvocation[]
|
|
46
|
+
readonly redirections: readonly ShellRedirection[]
|
|
47
|
+
/** False when the line does not parse to the end. */
|
|
48
|
+
readonly complete: boolean
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const EXEC_FLAGS = new Set(['-exec', '-execdir', '-ok', '-okdir'])
|
|
52
|
+
|
|
53
|
+
function invocationOf(words: readonly ShellWord[]): ShellInvocation | undefined {
|
|
54
|
+
if (words.length === 0) return undefined
|
|
55
|
+
const step = unwrapReexecChain(words)
|
|
56
|
+
if (!('rest' in step)) return undefined
|
|
57
|
+
const [head, ...args] = step.rest
|
|
58
|
+
if (head === undefined || head.expands) return undefined
|
|
59
|
+
return { program: basename(head.value), args }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Every program a line runs, and every redirection it carries. */
|
|
63
|
+
export function readShellLine(line: string, dialect: ShellDialect): ShellReading {
|
|
64
|
+
const lexed = lexShellCommandLine(line, { dialect })
|
|
65
|
+
const invocations: ShellInvocation[] = []
|
|
66
|
+
for (const command of lexed.commands) {
|
|
67
|
+
const own = invocationOf(command.words.slice(command.assignments))
|
|
68
|
+
if (own === undefined) continue
|
|
69
|
+
invocations.push(own)
|
|
70
|
+
if (own.program !== 'find') continue
|
|
71
|
+
// `find … -exec CMD … ;` runs CMD on each match; its arguments up to the
|
|
72
|
+
// terminator are CMD's.
|
|
73
|
+
for (let i = 0; i < own.args.length; i += 1) {
|
|
74
|
+
const flag = own.args[i] as ShellWord
|
|
75
|
+
if (flag.expands || !EXEC_FLAGS.has(flag.value)) continue
|
|
76
|
+
const clause: ShellWord[] = []
|
|
77
|
+
for (let j = i + 1; j < own.args.length; j += 1) {
|
|
78
|
+
const word = own.args[j] as ShellWord
|
|
79
|
+
if (!word.expands && (word.value === ';' || word.value === '+')) break
|
|
80
|
+
clause.push(word)
|
|
81
|
+
}
|
|
82
|
+
const nested = invocationOf(clause)
|
|
83
|
+
if (nested !== undefined) invocations.push(nested)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
invocations,
|
|
88
|
+
redirections: lexed.redirections,
|
|
89
|
+
complete: lexed.complete,
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** The kind of access a command makes to a path argument. */
|
|
94
|
+
export type PathAccess = 'read' | 'write'
|
|
95
|
+
|
|
96
|
+
/** A path a line names, as written. */
|
|
97
|
+
export interface ShellPathUse {
|
|
98
|
+
/** The path with quotes removed; `~` and `$HOME` are left for the caller. */
|
|
99
|
+
readonly path: string
|
|
100
|
+
readonly access: PathAccess
|
|
101
|
+
/** Which program or redirection named it. */
|
|
102
|
+
readonly via: string
|
|
103
|
+
/** True when the word is decided at runtime and `path` is not what it will be. */
|
|
104
|
+
readonly unresolved: boolean
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export interface ShellPathAccess {
|
|
108
|
+
readonly uses: readonly ShellPathUse[]
|
|
109
|
+
/** A `cd`, `pushd`, `popd` or `chdir` runs somewhere in the line. */
|
|
110
|
+
readonly changesDirectory: boolean
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Devices a command may name without it being a file. */
|
|
114
|
+
const HARMLESS_TARGETS = new Set([
|
|
115
|
+
'/dev/null',
|
|
116
|
+
'/dev/stdin',
|
|
117
|
+
'/dev/stdout',
|
|
118
|
+
'/dev/stderr',
|
|
119
|
+
'/dev/tty',
|
|
120
|
+
])
|
|
121
|
+
|
|
122
|
+
function isHarmlessTarget(path: string): boolean {
|
|
123
|
+
return (
|
|
124
|
+
HARMLESS_TARGETS.has(path) ||
|
|
125
|
+
/^\/dev\/fd\/\d+$/.test(path) ||
|
|
126
|
+
/^\/proc\/self\/fd\/[0-2]$/.test(path)
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Whether a word's value still holds an expansion the shell will perform. */
|
|
131
|
+
export function expandsAtRuntime(word: ShellWord): boolean {
|
|
132
|
+
return word.expands && (/[$`]/.test(word.value) || word.substitutes)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
type Args = readonly ShellWord[]
|
|
136
|
+
|
|
137
|
+
/** Arguments that are not flags: everything after `--`, and everything from the first operand on. */
|
|
138
|
+
function operands(args: Args): ShellWord[] {
|
|
139
|
+
const out: ShellWord[] = []
|
|
140
|
+
let afterDashes = false
|
|
141
|
+
let started = false
|
|
142
|
+
for (const arg of args) {
|
|
143
|
+
if (afterDashes || started) out.push(arg)
|
|
144
|
+
else if (arg.value === '--') afterDashes = true
|
|
145
|
+
else if (arg.value === '-' || !arg.value.startsWith('-')) {
|
|
146
|
+
out.push(arg)
|
|
147
|
+
started = true
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return out
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
interface FlagTable {
|
|
154
|
+
readonly booleanShort: string
|
|
155
|
+
readonly valuedShort: string
|
|
156
|
+
readonly attachedShort?: string
|
|
157
|
+
readonly booleanLong: ReadonlySet<string>
|
|
158
|
+
readonly attachedLong?: ReadonlySet<string>
|
|
159
|
+
readonly valuedLong: ReadonlySet<string>
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Operands of a command whose flags are known: an unknown flag stops flag parsing, as in the reference. */
|
|
163
|
+
function operandsByTable(table: FlagTable): (args: Args) => ShellWord[] {
|
|
164
|
+
const kind = (flag: string): 'boolean' | 'valued' | 'attached' | 'unknown' => {
|
|
165
|
+
if (flag.startsWith('--')) {
|
|
166
|
+
if (flag.includes('=')) return 'attached'
|
|
167
|
+
if (table.booleanLong.has(flag)) return 'boolean'
|
|
168
|
+
if (table.attachedLong?.has(flag)) return 'attached'
|
|
169
|
+
return table.valuedLong.has(flag) ? 'valued' : 'unknown'
|
|
170
|
+
}
|
|
171
|
+
if (/^-\d+$/.test(flag)) return 'attached'
|
|
172
|
+
for (let i = 1; i < flag.length; i += 1) {
|
|
173
|
+
const letter = flag[i] as string
|
|
174
|
+
if (table.booleanShort.includes(letter)) continue
|
|
175
|
+
if (table.attachedShort?.includes(letter)) return 'attached'
|
|
176
|
+
if (!table.valuedShort.includes(letter)) return 'unknown'
|
|
177
|
+
return i === flag.length - 1 ? 'valued' : 'attached'
|
|
178
|
+
}
|
|
179
|
+
return 'boolean'
|
|
180
|
+
}
|
|
181
|
+
return (args) => {
|
|
182
|
+
const out: ShellWord[] = []
|
|
183
|
+
let afterDashes = false
|
|
184
|
+
let started = false
|
|
185
|
+
let parsing = true
|
|
186
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
187
|
+
const arg = args[i] as ShellWord
|
|
188
|
+
if (afterDashes || started) out.push(arg)
|
|
189
|
+
else if (arg.value === '--') afterDashes = true
|
|
190
|
+
else if (arg.value !== '-' && arg.value.startsWith('-')) {
|
|
191
|
+
if (!parsing) continue
|
|
192
|
+
const k = kind(arg.value)
|
|
193
|
+
if (k === 'valued') i += 1
|
|
194
|
+
else if (k === 'unknown') parsing = false
|
|
195
|
+
} else {
|
|
196
|
+
out.push(arg)
|
|
197
|
+
started = true
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return out
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const set = (...names: string[]): ReadonlySet<string> => new Set(names)
|
|
205
|
+
|
|
206
|
+
/** Operands of `grep`-family and `rg`: the first operand is the pattern unless `-e`/`-f` gave one. */
|
|
207
|
+
function patternThenFiles(
|
|
208
|
+
valued: ReadonlySet<string>,
|
|
209
|
+
fallback: string[] = [],
|
|
210
|
+
): (args: Args) => ShellWord[] {
|
|
211
|
+
const patternFlags = ['-e', '--regexp', '-f', '--file']
|
|
212
|
+
const fileFlags = ['-f', '--file']
|
|
213
|
+
const attachedValue = (flag: string, names: readonly string[]): string | undefined => {
|
|
214
|
+
if (!flag.startsWith('-')) return undefined
|
|
215
|
+
const eq = flag.indexOf('=')
|
|
216
|
+
if (eq >= 0) return names.includes(flag.slice(0, eq)) ? flag.slice(eq + 1) : undefined
|
|
217
|
+
for (const name of names) {
|
|
218
|
+
if (name.length === 2 && name[0] === '-' && flag.startsWith(name) && flag !== name) {
|
|
219
|
+
return flag.slice(2)
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return undefined
|
|
223
|
+
}
|
|
224
|
+
return (args) => {
|
|
225
|
+
const out: ShellWord[] = []
|
|
226
|
+
let patternGiven = false
|
|
227
|
+
let afterDashes = false
|
|
228
|
+
let started = false
|
|
229
|
+
const take = (value: string | undefined): void => {
|
|
230
|
+
if (value) out.push(word(value))
|
|
231
|
+
}
|
|
232
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
233
|
+
const arg = args[i] as ShellWord
|
|
234
|
+
const text = arg.value
|
|
235
|
+
if (!afterDashes && !started && text === '--') {
|
|
236
|
+
afterDashes = true
|
|
237
|
+
continue
|
|
238
|
+
}
|
|
239
|
+
if (!afterDashes && !started && text !== '-' && text.startsWith('-')) {
|
|
240
|
+
const eq = text.indexOf('=')
|
|
241
|
+
const name = eq >= 0 ? text.slice(0, eq) : text
|
|
242
|
+
if (patternFlags.includes(name)) {
|
|
243
|
+
patternGiven = true
|
|
244
|
+
if (name === '-f' || name === '--file') {
|
|
245
|
+
take(eq >= 0 ? text.slice(eq + 1) : args[i + 1]?.value)
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
if (/^-[a-zA-Z]*f$/.test(name) && name !== '-f' && eq < 0 && args[i + 1] !== undefined) {
|
|
249
|
+
patternGiven = true
|
|
250
|
+
take(args[i + 1]?.value)
|
|
251
|
+
i += 1
|
|
252
|
+
continue
|
|
253
|
+
}
|
|
254
|
+
if (['--exclude-from', '--include-from', '--ignore-file'].includes(name)) {
|
|
255
|
+
take(eq >= 0 ? text.slice(eq + 1) : args[i + 1]?.value)
|
|
256
|
+
if (eq < 0) i += 1
|
|
257
|
+
continue
|
|
258
|
+
}
|
|
259
|
+
if (eq < 0) {
|
|
260
|
+
const attachedFile = attachedValue(text, fileFlags)
|
|
261
|
+
if (attachedFile !== undefined) {
|
|
262
|
+
patternGiven = true
|
|
263
|
+
take(attachedFile)
|
|
264
|
+
continue
|
|
265
|
+
}
|
|
266
|
+
if (text.length > 2 && text.startsWith('-e')) {
|
|
267
|
+
patternGiven = true
|
|
268
|
+
continue
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
if (valued.has(name) && eq < 0) i += 1
|
|
272
|
+
continue
|
|
273
|
+
}
|
|
274
|
+
if (started && !afterDashes) {
|
|
275
|
+
const attachedFile = attachedValue(text, fileFlags)
|
|
276
|
+
if (attachedFile !== undefined) take(attachedFile)
|
|
277
|
+
}
|
|
278
|
+
started = true
|
|
279
|
+
if (!patternGiven) {
|
|
280
|
+
patternGiven = true
|
|
281
|
+
continue
|
|
282
|
+
}
|
|
283
|
+
out.push(arg)
|
|
284
|
+
}
|
|
285
|
+
return out.length > 0 ? out : fallback.map(word)
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/** A synthetic word for a path the extractor derived rather than read. */
|
|
290
|
+
function word(value: string): ShellWord {
|
|
291
|
+
return {
|
|
292
|
+
text: value,
|
|
293
|
+
value,
|
|
294
|
+
expands: false,
|
|
295
|
+
quoted: false,
|
|
296
|
+
substitutes: false,
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const grepOperands = (args: Args): ShellWord[] => {
|
|
301
|
+
const found = patternThenFiles(
|
|
302
|
+
set(
|
|
303
|
+
'-e',
|
|
304
|
+
'--regexp',
|
|
305
|
+
'-f',
|
|
306
|
+
'--file',
|
|
307
|
+
'--exclude',
|
|
308
|
+
'--include',
|
|
309
|
+
'--exclude-dir',
|
|
310
|
+
'--include-dir',
|
|
311
|
+
'-m',
|
|
312
|
+
'--max-count',
|
|
313
|
+
'-A',
|
|
314
|
+
'--after-context',
|
|
315
|
+
'-B',
|
|
316
|
+
'--before-context',
|
|
317
|
+
'-C',
|
|
318
|
+
'--context',
|
|
319
|
+
),
|
|
320
|
+
)(args)
|
|
321
|
+
if (found.length === 0 && args.some((a) => ['-r', '-R', '--recursive'].includes(a.value))) {
|
|
322
|
+
return [word('.')]
|
|
323
|
+
}
|
|
324
|
+
return found
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const rgOperands = patternThenFiles(
|
|
328
|
+
set(
|
|
329
|
+
'-e',
|
|
330
|
+
'--regexp',
|
|
331
|
+
'-f',
|
|
332
|
+
'--file',
|
|
333
|
+
'-t',
|
|
334
|
+
'--type',
|
|
335
|
+
'-T',
|
|
336
|
+
'--type-not',
|
|
337
|
+
'-g',
|
|
338
|
+
'--glob',
|
|
339
|
+
'-m',
|
|
340
|
+
'--max-count',
|
|
341
|
+
'--max-depth',
|
|
342
|
+
'-r',
|
|
343
|
+
'--replace',
|
|
344
|
+
'-A',
|
|
345
|
+
'--after-context',
|
|
346
|
+
'-B',
|
|
347
|
+
'--before-context',
|
|
348
|
+
'-C',
|
|
349
|
+
'--context',
|
|
350
|
+
),
|
|
351
|
+
['.'],
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
function awkOperands(args: Args): ShellWord[] {
|
|
355
|
+
const valued = set('-F', '--field-separator', '-v', '--assign', '-e', '--source')
|
|
356
|
+
const files = ['-f', '--file', '-E', '--exec']
|
|
357
|
+
const out: ShellWord[] = []
|
|
358
|
+
let afterDashes = false
|
|
359
|
+
let started = false
|
|
360
|
+
let programGiven = false
|
|
361
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
362
|
+
const arg = args[i] as ShellWord
|
|
363
|
+
const text = arg.value
|
|
364
|
+
if (!afterDashes && !started && text === '--') {
|
|
365
|
+
afterDashes = true
|
|
366
|
+
continue
|
|
367
|
+
}
|
|
368
|
+
if (!afterDashes && !started && text !== '-' && text.startsWith('-')) {
|
|
369
|
+
const eq = text.indexOf('=')
|
|
370
|
+
const name = eq >= 0 ? text.slice(0, eq) : text
|
|
371
|
+
if (valued.has(name)) {
|
|
372
|
+
if (name === '-e' || name === '--source') programGiven = true
|
|
373
|
+
if (eq < 0) i += 1
|
|
374
|
+
continue
|
|
375
|
+
}
|
|
376
|
+
if (files.includes(name)) {
|
|
377
|
+
programGiven = true
|
|
378
|
+
if (eq >= 0) out.push(word(text.slice(eq + 1)))
|
|
379
|
+
else if (args[i + 1] !== undefined) {
|
|
380
|
+
out.push(args[i + 1] as ShellWord)
|
|
381
|
+
i += 1
|
|
382
|
+
}
|
|
383
|
+
continue
|
|
384
|
+
}
|
|
385
|
+
continue
|
|
386
|
+
}
|
|
387
|
+
started = true
|
|
388
|
+
if (!programGiven) {
|
|
389
|
+
programGiven = true
|
|
390
|
+
continue
|
|
391
|
+
}
|
|
392
|
+
out.push(arg)
|
|
393
|
+
}
|
|
394
|
+
return out
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function sedOperands(args: Args): ShellWord[] {
|
|
398
|
+
const out: ShellWord[] = []
|
|
399
|
+
let skipNext = false
|
|
400
|
+
let scriptGiven = false
|
|
401
|
+
let afterDashes = false
|
|
402
|
+
let started = false
|
|
403
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
404
|
+
if (skipNext) {
|
|
405
|
+
skipNext = false
|
|
406
|
+
continue
|
|
407
|
+
}
|
|
408
|
+
const arg = args[i] as ShellWord
|
|
409
|
+
const text = arg.value
|
|
410
|
+
if (!afterDashes && !started && text === '--') {
|
|
411
|
+
afterDashes = true
|
|
412
|
+
continue
|
|
413
|
+
}
|
|
414
|
+
if (!afterDashes && !started && text !== '-' && text.startsWith('-')) {
|
|
415
|
+
if (text === '-f' || text === '--file') {
|
|
416
|
+
const next = args[i + 1]
|
|
417
|
+
if (next !== undefined) out.push(next)
|
|
418
|
+
skipNext = true
|
|
419
|
+
scriptGiven = true
|
|
420
|
+
} else if (text === '-e' || text === '--expression') {
|
|
421
|
+
skipNext = true
|
|
422
|
+
scriptGiven = true
|
|
423
|
+
} else if (!text.startsWith('--') && (text.includes('e') || text.includes('f'))) {
|
|
424
|
+
scriptGiven = true
|
|
425
|
+
}
|
|
426
|
+
continue
|
|
427
|
+
}
|
|
428
|
+
started = true
|
|
429
|
+
if (!scriptGiven) {
|
|
430
|
+
scriptGiven = true
|
|
431
|
+
continue
|
|
432
|
+
}
|
|
433
|
+
out.push(arg)
|
|
434
|
+
}
|
|
435
|
+
return out
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function findOperands(args: Args): ShellWord[] {
|
|
439
|
+
const out: ShellWord[] = []
|
|
440
|
+
const takesPath = new Set([
|
|
441
|
+
'-newer',
|
|
442
|
+
'-anewer',
|
|
443
|
+
'-cnewer',
|
|
444
|
+
'-mnewer',
|
|
445
|
+
'-samefile',
|
|
446
|
+
'-path',
|
|
447
|
+
'-wholename',
|
|
448
|
+
'-ilname',
|
|
449
|
+
'-lname',
|
|
450
|
+
'-ipath',
|
|
451
|
+
'-iwholename',
|
|
452
|
+
])
|
|
453
|
+
let expressionStarted = false
|
|
454
|
+
let afterDashes = false
|
|
455
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
456
|
+
const arg = args[i] as ShellWord
|
|
457
|
+
if (!arg.value) continue
|
|
458
|
+
if (afterDashes) {
|
|
459
|
+
out.push(arg)
|
|
460
|
+
continue
|
|
461
|
+
}
|
|
462
|
+
if (arg.value === '--') {
|
|
463
|
+
afterDashes = true
|
|
464
|
+
continue
|
|
465
|
+
}
|
|
466
|
+
if (arg.value.startsWith('-')) {
|
|
467
|
+
if (['-H', '-L', '-P'].includes(arg.value)) continue
|
|
468
|
+
expressionStarted = true
|
|
469
|
+
if (takesPath.has(arg.value) || /^-newer[acmBt][acmtB]$/.test(arg.value)) {
|
|
470
|
+
const next = args[i + 1]
|
|
471
|
+
if (next?.value) {
|
|
472
|
+
out.push(next)
|
|
473
|
+
i += 1
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
continue
|
|
477
|
+
}
|
|
478
|
+
if (!expressionStarted) out.push(arg)
|
|
479
|
+
}
|
|
480
|
+
return out.length > 0 ? out : [word('.')]
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function gitOperands(args: Args): ShellWord[] {
|
|
484
|
+
const valuedGlobals = new Set([
|
|
485
|
+
'-C',
|
|
486
|
+
'-c',
|
|
487
|
+
'--git-dir',
|
|
488
|
+
'--work-tree',
|
|
489
|
+
'--namespace',
|
|
490
|
+
'--super-prefix',
|
|
491
|
+
'--config-env',
|
|
492
|
+
'--attr-source',
|
|
493
|
+
'--shallow-file',
|
|
494
|
+
])
|
|
495
|
+
let index = 0
|
|
496
|
+
let base = ''
|
|
497
|
+
const join = (value: string): string =>
|
|
498
|
+
base === '' ? value : `${base.replace(/\/+$/, '')}/${value}`
|
|
499
|
+
const absolute = (value: string): boolean => value.startsWith('/') || value.startsWith('~')
|
|
500
|
+
const setBase = (value: string): void => {
|
|
501
|
+
base = absolute(value) ? value : join(value)
|
|
502
|
+
}
|
|
503
|
+
while (index < args.length && (args[index] as ShellWord).value.startsWith('-')) {
|
|
504
|
+
const flag = (args[index] as ShellWord).value
|
|
505
|
+
index += 1
|
|
506
|
+
if (valuedGlobals.has(flag) && index < args.length) {
|
|
507
|
+
const value = (args[index] as ShellWord).value
|
|
508
|
+
if (flag === '-C' || flag === '--work-tree') setBase(value)
|
|
509
|
+
index += 1
|
|
510
|
+
} else if (flag.startsWith('--work-tree=')) {
|
|
511
|
+
setBase(flag.slice('--work-tree='.length))
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
const sub = args[index]?.value
|
|
515
|
+
const rest = args.slice(index + 1)
|
|
516
|
+
const place = (value: string): string => (base === '' || absolute(value) ? value : join(value))
|
|
517
|
+
if (sub === 'grep') {
|
|
518
|
+
const files = patternThenFiles(
|
|
519
|
+
set(
|
|
520
|
+
'-e',
|
|
521
|
+
'--regexp',
|
|
522
|
+
'-f',
|
|
523
|
+
'--file',
|
|
524
|
+
'-A',
|
|
525
|
+
'--after-context',
|
|
526
|
+
'-B',
|
|
527
|
+
'--before-context',
|
|
528
|
+
'-C',
|
|
529
|
+
'--context',
|
|
530
|
+
'-m',
|
|
531
|
+
'--max-count',
|
|
532
|
+
'--max-depth',
|
|
533
|
+
'--threads',
|
|
534
|
+
),
|
|
535
|
+
)(rest)
|
|
536
|
+
return files.length > 0
|
|
537
|
+
? files.map((f) => word(place(f.value)))
|
|
538
|
+
: [word(base === '' ? '.' : base)]
|
|
539
|
+
}
|
|
540
|
+
if (sub !== 'diff') return []
|
|
541
|
+
const out: ShellWord[] = []
|
|
542
|
+
let afterDashes = false
|
|
543
|
+
for (const arg of rest) {
|
|
544
|
+
if (!afterDashes && arg.value === '--') afterDashes = true
|
|
545
|
+
else if (afterDashes || arg.value === '-' || !arg.value.startsWith('-')) {
|
|
546
|
+
const path = place(arg.value)
|
|
547
|
+
if (!isHarmlessTarget(path)) out.push(word(path))
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
return out
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function manOperands(args: Args): ShellWord[] {
|
|
554
|
+
const local = args.some(
|
|
555
|
+
(a) => a.value === '--local-file' || (/^-[^-]/.test(a.value) && a.value.includes('l')),
|
|
556
|
+
)
|
|
557
|
+
const out: ShellWord[] = []
|
|
558
|
+
args.forEach((arg, i) => {
|
|
559
|
+
const previous = args[i - 1]?.value
|
|
560
|
+
if (i > 0 && (previous === '-C' || previous === '--config-file')) {
|
|
561
|
+
out.push(arg)
|
|
562
|
+
return
|
|
563
|
+
}
|
|
564
|
+
const text = arg.value
|
|
565
|
+
const found: string[] = []
|
|
566
|
+
if (!text.startsWith('-')) found.push(text)
|
|
567
|
+
else {
|
|
568
|
+
if (text.includes('=')) found.push(text.slice(text.indexOf('=') + 1))
|
|
569
|
+
if (text.length > 2 && !text.startsWith('--')) found.push(text.slice(2))
|
|
570
|
+
const cut = text.search(/[\\/~]/)
|
|
571
|
+
if (cut > 0) found.push(text.slice(cut))
|
|
572
|
+
}
|
|
573
|
+
for (const value of new Set(found)) {
|
|
574
|
+
if (
|
|
575
|
+
value !== '' &&
|
|
576
|
+
(local || /[\\/]/.test(value) || value.startsWith('~') || value.startsWith('.'))
|
|
577
|
+
) {
|
|
578
|
+
out.push(word(value))
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
})
|
|
582
|
+
return out
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
function jqOperands(args: Args): ShellWord[] {
|
|
586
|
+
const valued = set(
|
|
587
|
+
'-e',
|
|
588
|
+
'--expression',
|
|
589
|
+
'--arg',
|
|
590
|
+
'--argjson',
|
|
591
|
+
'--args',
|
|
592
|
+
'--jsonargs',
|
|
593
|
+
'-L',
|
|
594
|
+
'--library-path',
|
|
595
|
+
'--indent',
|
|
596
|
+
'--tab',
|
|
597
|
+
)
|
|
598
|
+
const out: ShellWord[] = []
|
|
599
|
+
let afterDashes = false
|
|
600
|
+
let filterGiven = false
|
|
601
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
602
|
+
const arg = args[i] as ShellWord
|
|
603
|
+
const text = arg.value
|
|
604
|
+
if (!afterDashes && text === '--') {
|
|
605
|
+
afterDashes = true
|
|
606
|
+
continue
|
|
607
|
+
}
|
|
608
|
+
if (!afterDashes && text.startsWith('-')) {
|
|
609
|
+
const eq = text.indexOf('=')
|
|
610
|
+
const name = eq >= 0 ? text.slice(0, eq) : text
|
|
611
|
+
if (name === '-e' || name === '--expression') filterGiven = true
|
|
612
|
+
if (name === '-f' || name === '--from-file') {
|
|
613
|
+
filterGiven = true
|
|
614
|
+
if (eq >= 0) out.push(word(text.slice(eq + 1)))
|
|
615
|
+
else if (args[i + 1] !== undefined) {
|
|
616
|
+
out.push(args[i + 1] as ShellWord)
|
|
617
|
+
i += 1
|
|
618
|
+
}
|
|
619
|
+
continue
|
|
620
|
+
}
|
|
621
|
+
if (name === '--slurpfile' || name === '--rawfile') {
|
|
622
|
+
const file = args[i + 2]
|
|
623
|
+
if (file !== undefined) out.push(file)
|
|
624
|
+
i += 2
|
|
625
|
+
continue
|
|
626
|
+
}
|
|
627
|
+
if (valued.has(name) && eq < 0) i += 1
|
|
628
|
+
continue
|
|
629
|
+
}
|
|
630
|
+
if (!filterGiven) {
|
|
631
|
+
filterGiven = true
|
|
632
|
+
continue
|
|
633
|
+
}
|
|
634
|
+
out.push(arg)
|
|
635
|
+
}
|
|
636
|
+
return out
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
const trOperands = (args: Args): ShellWord[] => {
|
|
640
|
+
const deleting = args.some(
|
|
641
|
+
(a) =>
|
|
642
|
+
a.value === '-d' ||
|
|
643
|
+
a.value === '--delete' ||
|
|
644
|
+
(a.value.startsWith('-') && a.value.includes('d')),
|
|
645
|
+
)
|
|
646
|
+
return operands(args).slice(deleting ? 1 : 2)
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
const teeOperands = (args: Args): ShellWord[] =>
|
|
650
|
+
operands(args).filter((a) => !isHarmlessTarget(a.value))
|
|
651
|
+
|
|
652
|
+
const cdOperands = (args: Args): ShellWord[] => {
|
|
653
|
+
const found = operands(args)
|
|
654
|
+
if (found.length === 0) return args[args.length - 1]?.value === '-' ? [word('-')] : [word('~')]
|
|
655
|
+
return [found[0] as ShellWord]
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
const lsOperands = (args: Args): ShellWord[] => {
|
|
659
|
+
const found = operands(args)
|
|
660
|
+
return found.length > 0 ? found : [word('.')]
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
const table = (
|
|
664
|
+
booleanShort: string,
|
|
665
|
+
valuedShort: string,
|
|
666
|
+
booleanLong: ReadonlySet<string>,
|
|
667
|
+
valuedLong: ReadonlySet<string>,
|
|
668
|
+
attachedShort?: string,
|
|
669
|
+
attachedLong?: ReadonlySet<string>,
|
|
670
|
+
): FlagTable => ({
|
|
671
|
+
booleanShort,
|
|
672
|
+
valuedShort,
|
|
673
|
+
booleanLong,
|
|
674
|
+
valuedLong,
|
|
675
|
+
attachedShort,
|
|
676
|
+
attachedLong,
|
|
677
|
+
})
|
|
678
|
+
|
|
679
|
+
const EXTRACTORS: Readonly<Record<string, (args: Args) => ShellWord[]>> = {
|
|
680
|
+
cd: cdOperands,
|
|
681
|
+
ls: lsOperands,
|
|
682
|
+
find: findOperands,
|
|
683
|
+
mkdir: operands,
|
|
684
|
+
touch: operands,
|
|
685
|
+
rm: operands,
|
|
686
|
+
rmdir: operands,
|
|
687
|
+
mv: operands,
|
|
688
|
+
cp: operands,
|
|
689
|
+
cat: operands,
|
|
690
|
+
head: operands,
|
|
691
|
+
tail: operands,
|
|
692
|
+
sort: operands,
|
|
693
|
+
uniq: operands,
|
|
694
|
+
wc: operands,
|
|
695
|
+
cut: operandsByTable(
|
|
696
|
+
table(
|
|
697
|
+
'nswz',
|
|
698
|
+
'bcdf',
|
|
699
|
+
set('--complement', '--only-delimited', '--zero-terminated'),
|
|
700
|
+
set('--bytes', '--characters', '--delimiter', '--fields', '--output-delimiter'),
|
|
701
|
+
),
|
|
702
|
+
),
|
|
703
|
+
paste: operandsByTable(
|
|
704
|
+
table('sz', 'd', set('--serial', '--zero-terminated'), set('--delimiters')),
|
|
705
|
+
),
|
|
706
|
+
column: operandsByTable(
|
|
707
|
+
table(
|
|
708
|
+
'txdemLJhV',
|
|
709
|
+
'cosNOClEHRTWrip',
|
|
710
|
+
set(
|
|
711
|
+
'--table',
|
|
712
|
+
'--fillrows',
|
|
713
|
+
'--table-noheadings',
|
|
714
|
+
'--table-header-repeat',
|
|
715
|
+
'--table-maxout',
|
|
716
|
+
'--keep-empty-lines',
|
|
717
|
+
'--json',
|
|
718
|
+
),
|
|
719
|
+
set(
|
|
720
|
+
'--separator',
|
|
721
|
+
'--output-separator',
|
|
722
|
+
'--output-width',
|
|
723
|
+
'--table-name',
|
|
724
|
+
'--table-order',
|
|
725
|
+
'--table-column',
|
|
726
|
+
'--table-columns',
|
|
727
|
+
'--table-columns-limit',
|
|
728
|
+
'--table-noextreme',
|
|
729
|
+
'--table-hide',
|
|
730
|
+
'--table-right',
|
|
731
|
+
'--table-truncate',
|
|
732
|
+
'--table-wrap',
|
|
733
|
+
'--tree',
|
|
734
|
+
'--tree-id',
|
|
735
|
+
'--tree-parent',
|
|
736
|
+
),
|
|
737
|
+
),
|
|
738
|
+
),
|
|
739
|
+
file: operands,
|
|
740
|
+
stat: operands,
|
|
741
|
+
diff: operands,
|
|
742
|
+
awk: awkOperands,
|
|
743
|
+
gawk: awkOperands,
|
|
744
|
+
mawk: awkOperands,
|
|
745
|
+
nawk: awkOperands,
|
|
746
|
+
strings: operands,
|
|
747
|
+
hexdump: operands,
|
|
748
|
+
od: operands,
|
|
749
|
+
base64: operands,
|
|
750
|
+
nl: operands,
|
|
751
|
+
sha256sum: operands,
|
|
752
|
+
sha1sum: operands,
|
|
753
|
+
md5sum: operands,
|
|
754
|
+
tee: teeOperands,
|
|
755
|
+
tr: trOperands,
|
|
756
|
+
grep: grepOperands,
|
|
757
|
+
egrep: grepOperands,
|
|
758
|
+
fgrep: grepOperands,
|
|
759
|
+
tac: operandsByTable(table('br', 's', set('--before', '--regex'), set('--separator'))),
|
|
760
|
+
rev: operands,
|
|
761
|
+
fold: operandsByTable(table('bs', 'w', set('--bytes', '--spaces'), set('--width'))),
|
|
762
|
+
expand: operandsByTable(table('i', 't', set('--initial'), set('--tabs'))),
|
|
763
|
+
unexpand: operandsByTable(table('af', 't', set('--all', '--first-only'), set('--tabs'))),
|
|
764
|
+
fmt: operandsByTable(
|
|
765
|
+
table(
|
|
766
|
+
'csumn',
|
|
767
|
+
'wgdl',
|
|
768
|
+
set('--crown-margin', '--split-only', '--tagged-paragraph', '--uniform-spacing'),
|
|
769
|
+
set('--width', '--goal', '--prefix'),
|
|
770
|
+
),
|
|
771
|
+
),
|
|
772
|
+
comm: operandsByTable(
|
|
773
|
+
table(
|
|
774
|
+
'123iz',
|
|
775
|
+
'',
|
|
776
|
+
set('--check-order', '--nocheck-order', '--total', '--zero-terminated'),
|
|
777
|
+
set('--output-delimiter'),
|
|
778
|
+
),
|
|
779
|
+
),
|
|
780
|
+
cmp: operandsByTable(
|
|
781
|
+
table(
|
|
782
|
+
'blsvxzh',
|
|
783
|
+
'',
|
|
784
|
+
set('--print-bytes', '--verbose', '--quiet', '--silent'),
|
|
785
|
+
set('--ignore-initial', '--bytes'),
|
|
786
|
+
),
|
|
787
|
+
),
|
|
788
|
+
pr: operandsByTable(
|
|
789
|
+
table(
|
|
790
|
+
'acdFfJmprtTv',
|
|
791
|
+
'hlwWoND',
|
|
792
|
+
set(
|
|
793
|
+
'--across',
|
|
794
|
+
'--show-control-chars',
|
|
795
|
+
'--double-space',
|
|
796
|
+
'--form-feed',
|
|
797
|
+
'--join-lines',
|
|
798
|
+
'--merge',
|
|
799
|
+
'--no-file-warnings',
|
|
800
|
+
'--omit-header',
|
|
801
|
+
'--omit-pagination',
|
|
802
|
+
'--show-nonprinting',
|
|
803
|
+
),
|
|
804
|
+
set(
|
|
805
|
+
'--header',
|
|
806
|
+
'--length',
|
|
807
|
+
'--width',
|
|
808
|
+
'--page-width',
|
|
809
|
+
'--indent',
|
|
810
|
+
'--first-line-number',
|
|
811
|
+
'--date-format',
|
|
812
|
+
'--columns',
|
|
813
|
+
),
|
|
814
|
+
'einsS',
|
|
815
|
+
set('--expand-tabs', '--output-tabs', '--number-lines', '--separator', '--sep-string'),
|
|
816
|
+
),
|
|
817
|
+
),
|
|
818
|
+
numfmt: operandsByTable(
|
|
819
|
+
table(
|
|
820
|
+
'z',
|
|
821
|
+
'd',
|
|
822
|
+
set('--grouping', '--zero-terminated', '--debug'),
|
|
823
|
+
set(
|
|
824
|
+
'--from',
|
|
825
|
+
'--to',
|
|
826
|
+
'--from-unit',
|
|
827
|
+
'--to-unit',
|
|
828
|
+
'--format',
|
|
829
|
+
'--padding',
|
|
830
|
+
'--delimiter',
|
|
831
|
+
'--field',
|
|
832
|
+
'--round',
|
|
833
|
+
'--suffix',
|
|
834
|
+
'--invalid',
|
|
835
|
+
),
|
|
836
|
+
undefined,
|
|
837
|
+
set('--header'),
|
|
838
|
+
),
|
|
839
|
+
),
|
|
840
|
+
tsort: operands,
|
|
841
|
+
man: manOperands,
|
|
842
|
+
rg: rgOperands,
|
|
843
|
+
sed: sedOperands,
|
|
844
|
+
jq: jqOperands,
|
|
845
|
+
git: gitOperands,
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
/** Commands that create or change what their operands name. `sed` is added per call when it edits in place. */
|
|
849
|
+
const WRITERS = new Set(['mkdir', 'touch', 'rm', 'rmdir', 'mv', 'cp', 'tee'])
|
|
850
|
+
|
|
851
|
+
const CD_PROGRAMS = new Set(['cd', 'pushd', 'popd', 'chdir'])
|
|
852
|
+
|
|
853
|
+
/** `sed -i`, `-i.bak`, `-ni`, `--in-place[=SUFFIX]`: the script's output replaces the file. */
|
|
854
|
+
function editsInPlace(args: Args): boolean {
|
|
855
|
+
return args.some((a) => {
|
|
856
|
+
const text = a.value
|
|
857
|
+
if (text === '--') return false
|
|
858
|
+
if (text.startsWith('--')) return text === '--in-place' || text.startsWith('--in-place=')
|
|
859
|
+
return /^-[A-Za-z]*i/.test(text)
|
|
860
|
+
})
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
/**
|
|
864
|
+
* The paths `reading` names: positional arguments of the commands in the
|
|
865
|
+
* table, and the targets of its redirections.
|
|
866
|
+
*/
|
|
867
|
+
export function shellPathAccess(reading: ShellReading): ShellPathAccess {
|
|
868
|
+
const uses: ShellPathUse[] = []
|
|
869
|
+
let changesDirectory = false
|
|
870
|
+
for (const { program, args } of reading.invocations) {
|
|
871
|
+
if (CD_PROGRAMS.has(program)) changesDirectory = true
|
|
872
|
+
const extract = EXTRACTORS[program]
|
|
873
|
+
if (extract === undefined) continue
|
|
874
|
+
const access: PathAccess =
|
|
875
|
+
WRITERS.has(program) || (program === 'sed' && editsInPlace(args)) ? 'write' : 'read'
|
|
876
|
+
for (const arg of extract(args)) {
|
|
877
|
+
if (arg.value === '' || isHarmlessTarget(arg.value)) continue
|
|
878
|
+
uses.push({
|
|
879
|
+
path: arg.value,
|
|
880
|
+
access,
|
|
881
|
+
via: program,
|
|
882
|
+
unresolved: expandsAtRuntime(arg),
|
|
883
|
+
})
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
for (const redirection of reading.redirections) {
|
|
887
|
+
const target = redirection.target
|
|
888
|
+
if (target.value === '' || isHarmlessTarget(target.value)) continue
|
|
889
|
+
switch (redirection.operator) {
|
|
890
|
+
case '>':
|
|
891
|
+
case '>>':
|
|
892
|
+
case '>|':
|
|
893
|
+
case '&>':
|
|
894
|
+
case '&>>':
|
|
895
|
+
case '<>':
|
|
896
|
+
uses.push({
|
|
897
|
+
path: target.value,
|
|
898
|
+
access: 'write',
|
|
899
|
+
via: 'redirect',
|
|
900
|
+
unresolved: expandsAtRuntime(target),
|
|
901
|
+
})
|
|
902
|
+
break
|
|
903
|
+
case '>&':
|
|
904
|
+
// `>&2` and `>&-` duplicate or close a descriptor; any other word is a file.
|
|
905
|
+
if (!/^(?:\d+-?|-)$/.test(target.value)) {
|
|
906
|
+
uses.push({
|
|
907
|
+
path: target.value,
|
|
908
|
+
access: 'write',
|
|
909
|
+
via: 'redirect',
|
|
910
|
+
unresolved: expandsAtRuntime(target),
|
|
911
|
+
})
|
|
912
|
+
}
|
|
913
|
+
break
|
|
914
|
+
case '<':
|
|
915
|
+
uses.push({
|
|
916
|
+
path: target.value,
|
|
917
|
+
access: 'read',
|
|
918
|
+
via: 'redirect',
|
|
919
|
+
unresolved: expandsAtRuntime(target),
|
|
920
|
+
})
|
|
921
|
+
break
|
|
922
|
+
default:
|
|
923
|
+
break
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
return { uses, changesDirectory }
|
|
927
|
+
}
|