@mafdet/cli 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/LICENSE +21 -0
  3. package/README.md +173 -0
  4. package/README.zh-CN.md +148 -0
  5. package/dist/agent/approvals.d.ts +38 -0
  6. package/dist/agent/approvals.d.ts.map +1 -0
  7. package/dist/agent/approvals.js +53 -0
  8. package/dist/agent/command-policy.d.ts +36 -0
  9. package/dist/agent/command-policy.d.ts.map +1 -0
  10. package/dist/agent/command-policy.js +133 -0
  11. package/dist/agent/loop.d.ts +107 -0
  12. package/dist/agent/loop.d.ts.map +1 -0
  13. package/dist/agent/loop.js +242 -0
  14. package/dist/agent/session.d.ts +62 -0
  15. package/dist/agent/session.d.ts.map +1 -0
  16. package/dist/agent/session.js +290 -0
  17. package/dist/agent/tools/read-only.d.ts +15 -0
  18. package/dist/agent/tools/read-only.d.ts.map +1 -0
  19. package/dist/agent/tools/read-only.js +254 -0
  20. package/dist/agent/tools/run-command.d.ts +20 -0
  21. package/dist/agent/tools/run-command.d.ts.map +1 -0
  22. package/dist/agent/tools/run-command.js +116 -0
  23. package/dist/agent/tools/types.d.ts +46 -0
  24. package/dist/agent/tools/types.d.ts.map +1 -0
  25. package/dist/agent/tools/types.js +16 -0
  26. package/dist/agent/tools/write.d.ts +3 -0
  27. package/dist/agent/tools/write.d.ts.map +1 -0
  28. package/dist/agent/tools/write.js +68 -0
  29. package/dist/api/account.d.ts +39 -0
  30. package/dist/api/account.d.ts.map +1 -0
  31. package/dist/api/account.js +39 -0
  32. package/dist/api/errors.d.ts +38 -0
  33. package/dist/api/errors.d.ts.map +1 -0
  34. package/dist/api/errors.js +149 -0
  35. package/dist/api/http.d.ts +63 -0
  36. package/dist/api/http.d.ts.map +1 -0
  37. package/dist/api/http.js +207 -0
  38. package/dist/api/models.d.ts +68 -0
  39. package/dist/api/models.d.ts.map +1 -0
  40. package/dist/api/models.js +91 -0
  41. package/dist/api/sse-parser.d.ts +72 -0
  42. package/dist/api/sse-parser.d.ts.map +1 -0
  43. package/dist/api/sse-parser.js +163 -0
  44. package/dist/commands/account.d.ts +4 -0
  45. package/dist/commands/account.d.ts.map +1 -0
  46. package/dist/commands/account.js +103 -0
  47. package/dist/commands/agent.d.ts +10 -0
  48. package/dist/commands/agent.d.ts.map +1 -0
  49. package/dist/commands/agent.js +268 -0
  50. package/dist/commands/chat.d.ts +13 -0
  51. package/dist/commands/chat.d.ts.map +1 -0
  52. package/dist/commands/chat.js +191 -0
  53. package/dist/commands/config.d.ts +15 -0
  54. package/dist/commands/config.d.ts.map +1 -0
  55. package/dist/commands/config.js +74 -0
  56. package/dist/commands/doctor.d.ts +15 -0
  57. package/dist/commands/doctor.d.ts.map +1 -0
  58. package/dist/commands/doctor.js +157 -0
  59. package/dist/commands/embed.d.ts +7 -0
  60. package/dist/commands/embed.d.ts.map +1 -0
  61. package/dist/commands/embed.js +105 -0
  62. package/dist/commands/models.d.ts +4 -0
  63. package/dist/commands/models.d.ts.map +1 -0
  64. package/dist/commands/models.js +151 -0
  65. package/dist/commands/runtime.d.ts +29 -0
  66. package/dist/commands/runtime.d.ts.map +1 -0
  67. package/dist/commands/runtime.js +56 -0
  68. package/dist/commands/speech.d.ts +7 -0
  69. package/dist/commands/speech.d.ts.map +1 -0
  70. package/dist/commands/speech.js +111 -0
  71. package/dist/config/config.d.ts +57 -0
  72. package/dist/config/config.d.ts.map +1 -0
  73. package/dist/config/config.js +129 -0
  74. package/dist/config/load.d.ts +20 -0
  75. package/dist/config/load.d.ts.map +1 -0
  76. package/dist/config/load.js +48 -0
  77. package/dist/config/paths.d.ts +11 -0
  78. package/dist/config/paths.d.ts.map +1 -0
  79. package/dist/config/paths.js +26 -0
  80. package/dist/filesystem/diff.d.ts +12 -0
  81. package/dist/filesystem/diff.d.ts.map +1 -0
  82. package/dist/filesystem/diff.js +109 -0
  83. package/dist/filesystem/sensitive-files.d.ts +19 -0
  84. package/dist/filesystem/sensitive-files.d.ts.map +1 -0
  85. package/dist/filesystem/sensitive-files.js +46 -0
  86. package/dist/filesystem/workspace.d.ts +92 -0
  87. package/dist/filesystem/workspace.d.ts.map +1 -0
  88. package/dist/filesystem/workspace.js +264 -0
  89. package/dist/index.d.ts +4 -0
  90. package/dist/index.d.ts.map +1 -0
  91. package/dist/index.js +109 -0
  92. package/dist/output/exit-codes.d.ts +25 -0
  93. package/dist/output/exit-codes.d.ts.map +1 -0
  94. package/dist/output/exit-codes.js +23 -0
  95. package/dist/output/format.d.ts +28 -0
  96. package/dist/output/format.d.ts.map +1 -0
  97. package/dist/output/format.js +56 -0
  98. package/dist/output/money.d.ts +16 -0
  99. package/dist/output/money.d.ts.map +1 -0
  100. package/dist/output/money.js +35 -0
  101. package/dist/output/write-file.d.ts +11 -0
  102. package/dist/output/write-file.d.ts.map +1 -0
  103. package/dist/output/write-file.js +38 -0
  104. package/dist/security/redact.d.ts +16 -0
  105. package/dist/security/redact.d.ts.map +1 -0
  106. package/dist/security/redact.js +61 -0
  107. package/package.json +51 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,86 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@mafdet/cli`.
4
+
5
+ The version is the contract for the JSON output shapes and the exit codes.
6
+ Being 0.x, semver still permits a break in a minor bump; the intent is not to
7
+ use that latitude, and the promise becomes binding at 1.0.0.
8
+
9
+ ## 0.3.0 — 2026-08-07
10
+
11
+ First published release. It contains everything through the plan's 0.3 scope,
12
+ which is why it is numbered 0.3 rather than 0.1: a user installing it gets the
13
+ write and shell tools, and the version should say so.
14
+
15
+ ### Model and account
16
+
17
+ - `mafdet models list` shows what this key can actually call, joining the data
18
+ plane's access list with the catalogue's metadata. Metadata always comes from
19
+ the catalogue — the data plane reports a 1M context window for a 128K model.
20
+ - `mafdet models show <id>` distinguishes "not allowed for this key", "not in
21
+ the catalogue" and "no such model", which the server's 403 cannot.
22
+ - `mafdet account balance | usage | subscription`, needing the `account:read`
23
+ scope. A key without it is told about the scope, not that the key is invalid.
24
+ - Money is integer micro-USD end to end. `--json` emits the exact strings the
25
+ API returned; the human view renders with integer division, and a sub-cent
26
+ charge stays visible rather than rounding to `$0.00`.
27
+
28
+ ### Calling models
29
+
30
+ - `mafdet chat` streams by default, prints the answer on stdout and everything
31
+ else on stderr, and reports tokens plus an estimated charge computed with the
32
+ gateway's own arithmetic.
33
+ - `mafdet embed` and `mafdet speech`. Output files are written atomically and
34
+ an existing file is never overwritten without `--force`.
35
+ - The model's API surface is checked locally before a request is spent.
36
+
37
+ ### Agent
38
+
39
+ - `mafdet agent "<goal>"` — read-only by default: list, search, read, and git
40
+ status/diff.
41
+ - `--allow-write` adds `edit_file` and `create_file`. Every change is shown as
42
+ a diff and applied only after approval. A non-interactive run cannot write at
43
+ all, and the write tools are not even offered to the model.
44
+ - `--allow-command "<prefix>"` adds `run_command` for exactly those commands.
45
+ There is no shell: the command is split into argv, so pipes, redirects,
46
+ chaining and substitution are refused rather than sanitised.
47
+ - `mafdet agent` with no goal and a terminal starts an interactive session with
48
+ `/model`, `/permissions`, `/diff`, `/cost`, `/compact` and `/exit`.
49
+ - Ceilings on turns, wall clock and cost stop a run with a limit status and
50
+ exit 7 instead of reporting success.
51
+
52
+ ### Diagnostics
53
+
54
+ - `mafdet doctor` checks Node, both endpoints, the key, the workspace, git,
55
+ both API planes, the selected model, its tool-calling status and the balance.
56
+ Only a genuine blocker fails; a missing optional scope is a warning.
57
+ - `mafdet config list` shows the resolved settings and which layer each came
58
+ from.
59
+
60
+ ### Safety
61
+
62
+ - The key is read only from `MAFDET_API_KEY`. There is no `--api-key` flag, no
63
+ key in any config file, and no full key in any output — only a fingerprint.
64
+ - A project's `.mafdet.json` may set `model`, `approvalMode` and `testCommands`.
65
+ Credentials and endpoints there are a hard error: cloning a repository must
66
+ not change where a key is sent.
67
+ - Workspace containment is decided on real paths, so symlinks and junctions
68
+ cannot escape it, and a file that does not exist yet is checked against its
69
+ nearest existing ancestor.
70
+ - `.env*`, private keys, `id_rsa*`, `kubeconfig`, `credentials*`, `.npmrc` and
71
+ the whole of `.ssh`, `.aws`, `.gnupg`, `.docker` and `.kube` are refused, with
72
+ no blanket override.
73
+ - Tool output is stripped of terminal control, redacted and capped before it
74
+ reaches the model or the terminal.
75
+ - Only an explicit 429 is retried. A timeout or a 5xx is never replayed,
76
+ because the model may already have generated — and billed for — an answer.
77
+
78
+ ### Known limitations
79
+
80
+ - macOS and Windows are not verified. On Windows specifically, the timeout kill
81
+ relies on POSIX process groups, so only the direct child is terminated and a
82
+ grandchild can survive.
83
+ - `/compact` truncates the conversation rather than summarising it.
84
+ - Cancelling a stream produced no charge in testing, but that is an observation
85
+ rather than a guarantee: if the upstream has already finished generating, a
86
+ charge can still land.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mafdet AI
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,173 @@
1
+ # @mafdet/cli
2
+
3
+ The official Mafdet AI command line: model calls, account queries, and a
4
+ permissioned local agent.
5
+
6
+ > **0.3.0.** 中文版见 [README.zh-CN.md](./README.zh-CN.md). The CLI never
7
+ > advertises a command it cannot serve, so `mafdet help` always lists exactly
8
+ > what works.
9
+ >
10
+ > macOS and Windows are not yet verified. On Windows the command timeout
11
+ > relies on POSIX process groups, so only the direct child is terminated and a
12
+ > grandchild can survive.
13
+
14
+ ## Requirements
15
+
16
+ Node.js 20 or newer, and a Mafdet API key.
17
+
18
+ ## Install
19
+
20
+ ```bash
21
+ npm install -g @mafdet/cli
22
+ ```
23
+
24
+ Or build it from this repository:
25
+
26
+ ```bash
27
+ pnpm --filter @mafdet/cli build
28
+ cd packages/cli && npm pack # -> mafdet-cli-0.3.0.tgz
29
+ npm install -g ./mafdet-cli-0.3.0.tgz
30
+ ```
31
+
32
+ ## Quickstart
33
+
34
+ ```bash
35
+ export MAFDET_API_KEY="sk-mafdet-live-..." # never passed as a flag
36
+ export MAFDET_MODEL="deepseek-v4-flash"
37
+
38
+ mafdet doctor # check key, endpoints, model, balance
39
+ mafdet models list # what this key can actually call
40
+ mafdet chat "Explain TypeScript decorators"
41
+ mafdet account balance
42
+ ```
43
+
44
+ ### PowerShell
45
+
46
+ ```powershell
47
+ $env:MAFDET_API_KEY = "sk-mafdet-live-..."
48
+ $env:MAFDET_MODEL = "deepseek-v4-flash"
49
+
50
+ mafdet doctor
51
+ mafdet chat "Explain TypeScript decorators"
52
+ ```
53
+
54
+ Everything the CLI prints is ASCII, so output stays readable on a console
55
+ running a non-UTF-8 code page (the Windows PowerShell 5.1 default). Piping a
56
+ prompt works too, but PowerShell pipes UTF-16 by default — prefer passing the
57
+ prompt as an argument, or use `--input <file>`.
58
+
59
+ ## Commands
60
+
61
+ | Command | What it does |
62
+ | ------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
63
+ | `mafdet doctor` | One pass over key, endpoints, model, tool-calling, balance, workspace, git |
64
+ | `mafdet models list [--surface chat\|embeddings\|speech] [--agent]` | Models this key can call, joined with catalogue metadata |
65
+ | `mafdet models show <id>` | One model in detail, including whether the agent may drive it |
66
+ | `mafdet chat [prompt] [--model] [--system <file>] [--input <file>] [--no-stream] [--quiet]` | Send a prompt; streams by default |
67
+ | `mafdet agent "<goal>" [--allow-write] [--allow-command "<prefix>"]` | Local agent; read-only unless told otherwise |
68
+ | `mafdet agent` (no goal, needs a terminal) | Interactive session |
69
+ | `mafdet embed [text\|<file>] --model <id> [--output <file>]` | Text to vector |
70
+ | `mafdet speech [text\|<file>] --model <id> --output <file> --voice <name>` | Text to an audio file |
71
+ | `mafdet account balance\|usage\|subscription` | Wallet, spend and plan (needs the `account:read` scope) |
72
+ | `mafdet config list` | The resolved settings and where each came from |
73
+
74
+ `--json` on any query command emits one complete JSON document; `--plain`
75
+ emits tab-separated rows with no header, for `cut` and `awk`.
76
+
77
+ ## What the agent may do
78
+
79
+ **Read-only by default**: list, search, read files, and `git status` / `git diff`.
80
+ It cannot change anything.
81
+
82
+ `--allow-write` adds `edit_file` and `create_file`, and then:
83
+
84
+ - every change is shown as a diff and confirmed first, with Enter meaning no;
85
+ - a non-interactive run cannot write at all, even with the flag - the write
86
+ tools are not offered to the model in the first place;
87
+ - there is no flag that approves everything in advance.
88
+
89
+ `--allow-command "<prefix>"` adds `run_command`, unprompted for exactly those
90
+ prefixes: `pnpm test` permits `pnpm test --filter web` but never `pnpm publish`.
91
+ **There is no shell** - the command is split into argv, so pipes, redirects,
92
+ `;`, `&&`, backticks and `$( )` are refused with an explanation rather than
93
+ sanitised.
94
+
95
+ In a session: `/model`, `/permissions`, `/diff`, `/cost`, `/compact`, `/exit`.
96
+
97
+ ## Configuration
98
+
99
+ Resolution order: `--model` > `MAFDET_MODEL` > project config > user config >
100
+ default. `mafdet config list` shows which one won.
101
+
102
+ | Variable | Default |
103
+ | -------------------- | --------------------------- |
104
+ | `MAFDET_API_KEY` | (required) |
105
+ | `MAFDET_MODEL` | none |
106
+ | `MAFDET_BASE_URL` | `https://api.mafdet.ai/v1` |
107
+ | `MAFDET_BACKEND_URL` | `https://backend.mafdet.ai` |
108
+
109
+ User config lives at `~/.config/mafdet/config.json` (Linux),
110
+ `~/Library/Application Support/mafdet/config.json` (macOS) or
111
+ `%APPDATA%\mafdet\config.json` (Windows).
112
+
113
+ A project may carry `.mafdet.json` with `model`, `approvalMode` and
114
+ `testCommands` — and nothing else. Credentials and endpoints in a project file
115
+ are rejected outright: cloning a repository must never change where your key
116
+ is sent.
117
+
118
+ ## Exit codes
119
+
120
+ Scripts may branch on these; a value never changes meaning once released.
121
+
122
+ | Code | Meaning |
123
+ | ---: | ------------------------------------------------------------- |
124
+ | 0 | Success |
125
+ | 1 | General failure (including `doctor` reporting a failed check) |
126
+ | 2 | Bad command line or configuration |
127
+ | 3 | Authentication or permission failure |
128
+ | 4 | Model or API unavailable |
129
+ | 5 | Local permission denied |
130
+ | 6 | Cancelled by the user |
131
+ | 7 | Cost, time, or loop ceiling reached |
132
+
133
+ ## Compatibility
134
+
135
+ If you build a script on this, here is what will and will not move.
136
+
137
+ Within a major version:
138
+
139
+ - an exit code never changes meaning, and a code is never reused for something
140
+ else;
141
+ - a field in `--json` output keeps its name, type and meaning; new fields may
142
+ be added, so parse by name and ignore what you do not know;
143
+ - `--plain` keeps its column order and stays tab-separated with no header.
144
+
145
+ This is a 0.x release, so semver still permits a break in a minor bump. The
146
+ intent is not to use that latitude, and any change will be listed in
147
+ [CHANGELOG.md](./CHANGELOG.md); the promise becomes binding at 1.0.0.
148
+
149
+ Money is always an integer count of micro-USD, sent as a string in `--json` so
150
+ that no parser rounds it. Do not read the human-formatted dollar figure.
151
+
152
+ ## Output streams
153
+
154
+ Results and machine-readable JSON go to stdout. Progress, warnings, summaries
155
+ and diagnostics go to stderr, so `mafdet chat ... > answer.txt` captures the
156
+ answer alone and `mafdet models list --json | jq` always parses.
157
+
158
+ ## Credentials
159
+
160
+ The key is read from `MAFDET_API_KEY`. There is deliberately no `--api-key`
161
+ flag: it would put the key into shell history and the process list. Keys are
162
+ never written to a config file and never printed in full — only as a
163
+ fingerprint like `sk-mafdet-live-<redacted>`.
164
+
165
+ ## Cost and cancellation
166
+
167
+ `chat` prints tokens and an estimated charge to stderr, computed with the same
168
+ arithmetic the gateway bills with. The wallet remains the authority.
169
+
170
+ Cancelling with Ctrl+C closes the connection. In testing against production a
171
+ cancelled stream produced no charge, but that is an observation, not a
172
+ guarantee: if the upstream has already finished generating, a charge can still
173
+ land. The summary after a cancel is labelled `partial:` for that reason.
@@ -0,0 +1,148 @@
1
+ # @mafdet/cli
2
+
3
+ Mafdet AI 官方命令行:模型调用、账户查询,以及一个受权限约束的本地 Agent。
4
+
5
+ > **0.3.0。** 英文版见 [README.md](./README.md)。
6
+
7
+ ## 环境要求
8
+
9
+ Node.js 20 或更高,以及一把 Mafdet API Key。
10
+
11
+ ## 安装
12
+
13
+ ```bash
14
+ npm install -g @mafdet/cli
15
+ ```
16
+
17
+ ## 快速开始
18
+
19
+ ```bash
20
+ export MAFDET_API_KEY="sk-mafdet-live-..." # 只从环境变量读,没有 --api-key 参数
21
+ export MAFDET_MODEL="deepseek-v4-flash"
22
+
23
+ mafdet doctor # 一次查完 Key、端点、模型、余额、工作区、git
24
+ mafdet models list # 这把 Key 实际能调用的模型
25
+ mafdet chat "解释一下 TypeScript 装饰器"
26
+ mafdet account balance
27
+ ```
28
+
29
+ ### PowerShell
30
+
31
+ ```powershell
32
+ $env:MAFDET_API_KEY = "sk-mafdet-live-..."
33
+ $env:MAFDET_MODEL = "deepseek-v4-flash"
34
+
35
+ mafdet doctor
36
+ ```
37
+
38
+ 所有用户可见输出都是纯 ASCII,因此在非 UTF-8 代码页的控制台(Windows PowerShell 5.1
39
+ 的默认状态,中文系统为 cp936)也不会变成乱码。管道传入 prompt 也可以,但 PowerShell
40
+ 默认按 UTF-16 走管道——建议直接把 prompt 作为参数,或用 `--input <文件>`。
41
+
42
+ ⚠️ **macOS 与 Windows 尚未验收**。Windows 上有一个已知差异:命令超时终止依赖 POSIX 的
43
+ 进程组语义,Windows 只会终止直接子进程,孙子进程可能存活。
44
+
45
+ ## 命令
46
+
47
+ | 命令 | 作用 |
48
+ | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- |
49
+ | `mafdet doctor` | 一次检查 Node、两个端点、Key、工作区、git、模型与工具调用能力、余额 |
50
+ | `mafdet models list [--surface chat\|embeddings\|speech] [--agent]` | 这把 Key 能调用的模型,并附上目录元数据 |
51
+ | `mafdet models show <id>` | 单个模型详情,含 Agent 是否可以驱动它 |
52
+ | `mafdet chat [提示词] [--model] [--system <文件>] [--input <文件>] [--no-stream] [--quiet]` | 发一次对话,默认流式 |
53
+ | `mafdet embed [文本\|<文件>] --model <id> [--output <文件>]` | 文本转向量 |
54
+ | `mafdet speech [文本\|<文件>] --model <id> --output <文件> --voice <音色>` | 文本转音频 |
55
+ | `mafdet agent "<目标>" [--allow-write] [--allow-command "<前缀>"]` | 本地 Agent,默认只读 |
56
+ | `mafdet agent`(不带目标,需终端) | 交互会话 |
57
+ | `mafdet account balance\|usage\|subscription` | 钱包、用量、套餐(需 `account:read` scope) |
58
+ | `mafdet config list` | 当前生效的配置及其来源 |
59
+
60
+ 查询类命令都支持 `--json`(输出一份完整 JSON 文档)与 `--plain`(无表头的 TSV,方便
61
+ `cut`、`awk` 处理)。
62
+
63
+ ## Agent 的权限模型
64
+
65
+ **默认只读**:可以列目录、搜索、读文件、看 git status/diff,**不能**改任何东西。
66
+
67
+ `--allow-write` 才会加上 `edit_file` 与 `create_file`,并且:
68
+
69
+ - 每次改动都**先展示 diff 再询问**,回车即拒绝;
70
+ - **非交互环境(无终端)一律不给写权限**,即使显式传了 `--allow-write`——此时写工具
71
+ 根本不会提供给模型;
72
+ - **没有任何"全部同意"的开关**(无 `--yes`、无 `--force`、无 `--allow-secrets`)。
73
+
74
+ `--allow-command "<前缀>"` 才会加上 `run_command`,且只对**这些命令前缀**免提示。
75
+ `pnpm test` 放行 `pnpm test --filter web`,但不放行 `pnpm publish`。
76
+ **没有 shell**:命令被切成 argv 直接执行,所以管道、重定向、`;`、`&&`、反引号、`$( )`
77
+ 一律被拒绝并说明原因,而不是尝试"消毒"。
78
+
79
+ 会话内命令:`/model`、`/permissions`、`/diff`、`/cost`、`/compact`、`/exit`。
80
+
81
+ ## 配置
82
+
83
+ 优先级:`--model` > `MAFDET_MODEL` > 项目配置 > 用户配置 > 默认值。
84
+ `mafdet config list` 会告诉你最终是哪一层生效的。
85
+
86
+ | 环境变量 | 默认值 |
87
+ | -------------------- | --------------------------- |
88
+ | `MAFDET_API_KEY` | (必填) |
89
+ | `MAFDET_MODEL` | 无 |
90
+ | `MAFDET_BASE_URL` | `https://api.mafdet.ai/v1` |
91
+ | `MAFDET_BACKEND_URL` | `https://backend.mafdet.ai` |
92
+
93
+ 用户配置位置:Linux `~/.config/mafdet/config.json`、macOS
94
+ `~/Library/Application Support/mafdet/config.json`、Windows `%APPDATA%\mafdet\config.json`。
95
+
96
+ 项目可以放一个 `.mafdet.json`,但**只允许** `model`、`approvalMode`、`testCommands` 三项。
97
+ 里面出现凭据或端点会**直接报错**:克隆一个仓库绝不该改变你的 Key 发往哪里。
98
+
99
+ ## 退出码
100
+
101
+ 脚本可以据此分支;一旦发布,某个值的含义就不再改变。
102
+
103
+ | 码 | 含义 |
104
+ | --: | -------------------------------------- |
105
+ | 0 | 成功 |
106
+ | 1 | 一般失败(含 `doctor` 有检查项未通过) |
107
+ | 2 | 命令行或配置错误 |
108
+ | 3 | 认证或权限失败 |
109
+ | 4 | 模型或 API 不可用 |
110
+ | 5 | 本地权限被拒绝 |
111
+ | 6 | 用户取消 |
112
+ | 7 | 达到费用、时间或轮次上限 |
113
+
114
+ ## 兼容性承诺
115
+
116
+ 如果你要在脚本里依赖它,这里说明什么会变、什么不会。
117
+
118
+ 同一大版本内:
119
+
120
+ - **退出码的含义不变**,某个码也不会被挪作他用;
121
+ - `--json` 里的字段**保持名称、类型与含义**;只会新增字段——所以请按字段名解析,
122
+ 忽略不认识的键;
123
+ - `--plain` 的**列顺序固定**,始终是无表头的 TSV。
124
+
125
+ 这是 0.x 版本,按 semver 次版本号仍**允许**破坏性变更。我们不打算动用这个余地,
126
+ 任何调整都会写进 [CHANGELOG.md](./CHANGELOG.md);**1.0.0 起正式生效**。
127
+
128
+ 金额一律是**整数微美元**,`--json` 里以字符串给出,避免任何解析器把它变成浮点。
129
+ 请不要去解析给人看的美元数字。
130
+
131
+ ## 输出约定
132
+
133
+ 结果与机器可读的 JSON 走 **stdout**;进度、警告、摘要、审批提示走 **stderr**。
134
+ 所以 `mafdet chat ... > answer.txt` 只会拿到答案本身,而
135
+ `mafdet models list --json | jq` 永远能解析。
136
+
137
+ ## 凭据
138
+
139
+ Key 只从 `MAFDET_API_KEY` 读取。**刻意不提供 `--api-key` 参数**——那会把 Key 写进
140
+ shell 历史和进程列表。Key 不会被写入任何配置文件,也不会完整出现在任何输出里,
141
+ 只以 `sk-mafdet-live-<redacted>` 这样的指纹形式展示。
142
+
143
+ ## 费用与取消
144
+
145
+ `chat` 会在 stderr 打印 token 数与预估费用,算法与网关计费一致,但**以钱包为准**。
146
+
147
+ Ctrl+C 会关闭连接。实测中被取消的流式请求没有产生费用,但这是一次观察结果、**不是保证**:
148
+ 如果上游已经生成完毕,仍可能产生扣费。因此取消后的摘要标注为 `partial:`。
@@ -0,0 +1,38 @@
1
+ export interface ApprovalRequest {
2
+ /** What the tool wants to do, e.g. "edit src/app.ts". */
3
+ summary: string;
4
+ /** The diff or command the user is being asked to approve. */
5
+ preview: string;
6
+ }
7
+ export interface Approver {
8
+ approve(request: ApprovalRequest): Promise<boolean>;
9
+ }
10
+ /**
11
+ * `read-only`, and every non-interactive run that has not been given explicit
12
+ * permission. Plan §8.1: a CI job must not acquire write access by accident,
13
+ * and there is no global `--yes` that could hand it over in one flag.
14
+ */
15
+ export declare const denyAll: Approver;
16
+ /** `auto-edit`: the user pre-approved changes inside the workspace. */
17
+ export declare const approveAll: Approver;
18
+ /**
19
+ * `suggest`, the default: show the diff, ask, default to no.
20
+ *
21
+ * The prompt goes to stderr so it never lands in redirected output, and a bare
22
+ * Enter means no — the safe answer must be the one you get by not thinking.
23
+ */
24
+ export declare function interactiveApprover(io: {
25
+ stderr: (text: string) => void;
26
+ input?: NodeJS.ReadableStream;
27
+ output?: NodeJS.WritableStream;
28
+ }): Approver;
29
+ export type ApprovalMode = "read-only" | "suggest" | "auto-edit";
30
+ /**
31
+ * Pick the approver for a mode, refusing to be interactive when there is no
32
+ * terminal: prompting a pipe would either hang or read the next line of data
33
+ * as an answer.
34
+ */
35
+ export declare function approverFor(mode: ApprovalMode, io: {
36
+ stderr: (text: string) => void;
37
+ }, isInteractive?: boolean): Approver;
38
+ //# sourceMappingURL=approvals.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"approvals.d.ts","sourceRoot":"","sources":["../../src/agent/approvals.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,eAAe;IAC9B,yDAAyD;IACzD,OAAO,EAAE,MAAM,CAAC;IAChB,8DAA8D;IAC9D,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,QAAQ;IACvB,OAAO,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACrD;AAED;;;;GAIG;AACH,eAAO,MAAM,OAAO,EAAE,QAIrB,CAAC;AAEF,uEAAuE;AACvE,eAAO,MAAM,UAAU,EAAE,QAIxB,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,EAAE;IACtC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC,cAAc,CAAC;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC,cAAc,CAAC;CAChC,GAAG,QAAQ,CAgBX;AAED,MAAM,MAAM,YAAY,GAAG,WAAW,GAAG,SAAS,GAAG,WAAW,CAAC;AAEjE;;;;GAIG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,YAAY,EAClB,EAAE,EAAE;IAAE,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,EACtC,aAAa,GAAE,OAAsC,GACpD,QAAQ,CAIV"}
@@ -0,0 +1,53 @@
1
+ import { createInterface } from "node:readline/promises";
2
+ /**
3
+ * `read-only`, and every non-interactive run that has not been given explicit
4
+ * permission. Plan §8.1: a CI job must not acquire write access by accident,
5
+ * and there is no global `--yes` that could hand it over in one flag.
6
+ */
7
+ export const denyAll = {
8
+ async approve() {
9
+ return false;
10
+ },
11
+ };
12
+ /** `auto-edit`: the user pre-approved changes inside the workspace. */
13
+ export const approveAll = {
14
+ async approve() {
15
+ return true;
16
+ },
17
+ };
18
+ /**
19
+ * `suggest`, the default: show the diff, ask, default to no.
20
+ *
21
+ * The prompt goes to stderr so it never lands in redirected output, and a bare
22
+ * Enter means no — the safe answer must be the one you get by not thinking.
23
+ */
24
+ export function interactiveApprover(io) {
25
+ return {
26
+ async approve(request) {
27
+ io.stderr(`\n${request.summary}\n${request.preview}\n`);
28
+ const rl = createInterface({
29
+ input: io.input ?? process.stdin,
30
+ output: io.output ?? process.stderr,
31
+ });
32
+ try {
33
+ const answer = await rl.question("apply this change? [y/N] ");
34
+ return /^y(es)?$/i.test(answer.trim());
35
+ }
36
+ finally {
37
+ rl.close();
38
+ }
39
+ },
40
+ };
41
+ }
42
+ /**
43
+ * Pick the approver for a mode, refusing to be interactive when there is no
44
+ * terminal: prompting a pipe would either hang or read the next line of data
45
+ * as an answer.
46
+ */
47
+ export function approverFor(mode, io, isInteractive = process.stdin.isTTY === true) {
48
+ if (mode === "read-only")
49
+ return denyAll;
50
+ if (mode === "auto-edit")
51
+ return approveAll;
52
+ return isInteractive ? interactiveApprover(io) : denyAll;
53
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * What a model is allowed to ask the machine to run.
3
+ *
4
+ * The central decision: there is no shell. A command is tokenised into argv
5
+ * and handed to execFile, so `;`, `&&`, backticks, `$(...)`, pipes and
6
+ * redirects have no meaning to anything downstream — there is nothing to
7
+ * inject into. Rather than accept them and try to sanitise, the policy refuses
8
+ * them and says why, because a sanitiser is a thing that can be wrong once.
9
+ */
10
+ export interface CommandVerdict {
11
+ ok: boolean;
12
+ /** Present when ok: the argv to execute, already split. */
13
+ argv?: string[];
14
+ /**
15
+ * True when an --allow-command entry matched, which is the only way a
16
+ * command runs without a prompt (plan §8.1: CI declares its capabilities
17
+ * up front rather than being asked).
18
+ */
19
+ preApproved?: boolean;
20
+ reason?: string;
21
+ }
22
+ /**
23
+ * Split a command line the way a shell would for simple cases: whitespace
24
+ * separates, quotes group. Anything more exotic is refused above, so this
25
+ * never has to be clever.
26
+ */
27
+ export declare function tokenize(command: string): string[];
28
+ /**
29
+ * @param allowlist entries from `--allow-command`. An entry matches when it is
30
+ * the whole command or a leading prefix of its tokens, so "pnpm test"
31
+ * permits "pnpm test --filter x" but never "pnpm publish".
32
+ */
33
+ export declare function assessCommand(command: string, allowlist?: readonly string[]): CommandVerdict;
34
+ /** Terminal control sequences, stripped before any output is shown or sent. */
35
+ export declare function stripAnsi(text: string): string;
36
+ //# sourceMappingURL=command-policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command-policy.d.ts","sourceRoot":"","sources":["../../src/agent/command-policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAeH,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,OAAO,CAAC;IACZ,2DAA2D;IAC3D,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB;;;;OAIG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CA4BlD;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,EACf,SAAS,GAAE,SAAS,MAAM,EAAO,GAChC,cAAc,CAwChB;AAmBD,+EAA+E;AAC/E,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAe9C"}