@armadra/agent 0.0.0-stage → 0.2.1
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 +22 -0
- package/LICENSE +21 -0
- package/README.md +426 -2
- package/dist/agent/agent.d.ts +56 -0
- package/dist/agent/agent.js +129 -0
- package/dist/agent/loop.d.ts +51 -0
- package/dist/agent/loop.js +158 -0
- package/dist/agent/queue.d.ts +25 -0
- package/dist/agent/queue.js +55 -0
- package/dist/agent/retry.d.ts +22 -0
- package/dist/agent/retry.js +92 -0
- package/dist/agent/schema.d.ts +27 -0
- package/dist/agent/schema.js +186 -0
- package/dist/agent/session-cache.d.ts +100 -0
- package/dist/agent/session-cache.js +511 -0
- package/dist/agent/session-compaction.d.ts +49 -0
- package/dist/agent/session-compaction.js +251 -0
- package/dist/agent/session-core.d.ts +103 -0
- package/dist/agent/session-core.js +7 -0
- package/dist/agent/session-run.d.ts +55 -0
- package/dist/agent/session-run.js +246 -0
- package/dist/agent/session-state.d.ts +39 -0
- package/dist/agent/session-state.js +118 -0
- package/dist/agent/session-subagent.d.ts +54 -0
- package/dist/agent/session-subagent.js +157 -0
- package/dist/agent/session-sync.d.ts +20 -0
- package/dist/agent/session-sync.js +104 -0
- package/dist/agent/session-tools.d.ts +24 -0
- package/dist/agent/session-tools.js +277 -0
- package/dist/agent/session.d.ts +117 -0
- package/dist/agent/session.js +484 -0
- package/dist/agent/system-prompt.d.ts +49 -0
- package/dist/agent/system-prompt.js +121 -0
- package/dist/agent/tool-runner.d.ts +64 -0
- package/dist/agent/tool-runner.js +309 -0
- package/dist/agent/transform.d.ts +26 -0
- package/dist/agent/transform.js +171 -0
- package/dist/agent/types.d.ts +378 -0
- package/dist/agent/types.js +21 -0
- package/dist/ai/apis/anthropic-messages.d.ts +26 -0
- package/dist/ai/apis/anthropic-messages.js +214 -0
- package/dist/ai/apis/anthropic-request.d.ts +42 -0
- package/dist/ai/apis/anthropic-request.js +282 -0
- package/dist/ai/apis/api.d.ts +28 -0
- package/dist/ai/apis/api.js +102 -0
- package/dist/ai/apis/cache-params.d.ts +48 -0
- package/dist/ai/apis/cache-params.js +163 -0
- package/dist/ai/apis/google-generative-ai.d.ts +32 -0
- package/dist/ai/apis/google-generative-ai.js +249 -0
- package/dist/ai/apis/google-request.d.ts +40 -0
- package/dist/ai/apis/google-request.js +255 -0
- package/dist/ai/apis/openai-compat.d.ts +35 -0
- package/dist/ai/apis/openai-compat.js +142 -0
- package/dist/ai/apis/openai-completions.d.ts +28 -0
- package/dist/ai/apis/openai-completions.js +246 -0
- package/dist/ai/apis/openai-request.d.ts +35 -0
- package/dist/ai/apis/openai-request.js +303 -0
- package/dist/ai/apis/openai-responses-request.d.ts +52 -0
- package/dist/ai/apis/openai-responses-request.js +266 -0
- package/dist/ai/apis/openai-responses.d.ts +28 -0
- package/dist/ai/apis/openai-responses.js +303 -0
- package/dist/ai/apis/shared.d.ts +56 -0
- package/dist/ai/apis/shared.js +179 -0
- package/dist/ai/cache/economics.d.ts +24 -0
- package/dist/ai/cache/economics.js +69 -0
- package/dist/ai/cache/fingerprint.d.ts +17 -0
- package/dist/ai/cache/fingerprint.js +39 -0
- package/dist/ai/cache/miss.d.ts +49 -0
- package/dist/ai/cache/miss.js +96 -0
- package/dist/ai/cache/reporting.d.ts +39 -0
- package/dist/ai/cache/reporting.js +102 -0
- package/dist/ai/cache/types.d.ts +94 -0
- package/dist/ai/cache/types.js +19 -0
- package/dist/ai/cache/warmer.d.ts +94 -0
- package/dist/ai/cache/warmer.js +224 -0
- package/dist/ai/context.d.ts +35 -0
- package/dist/ai/context.js +114 -0
- package/dist/ai/cost.d.ts +21 -0
- package/dist/ai/cost.js +60 -0
- package/dist/ai/event-stream.d.ts +33 -0
- package/dist/ai/event-stream.js +101 -0
- package/dist/ai/fake/fake-provider.d.ts +68 -0
- package/dist/ai/fake/fake-provider.js +278 -0
- package/dist/ai/fake/fake-script.d.ts +69 -0
- package/dist/ai/fake/fake-script.js +142 -0
- package/dist/ai/http.d.ts +54 -0
- package/dist/ai/http.js +212 -0
- package/dist/ai/json-partial.d.ts +17 -0
- package/dist/ai/json-partial.js +243 -0
- package/dist/ai/overflow.d.ts +22 -0
- package/dist/ai/overflow.js +61 -0
- package/dist/ai/providers/auth.d.ts +71 -0
- package/dist/ai/providers/auth.js +202 -0
- package/dist/ai/providers/builtin.d.ts +23 -0
- package/dist/ai/providers/builtin.js +148 -0
- package/dist/ai/providers/catalog-data.d.ts +5 -0
- package/dist/ai/providers/catalog-data.js +19 -0
- package/dist/ai/providers/catalog.d.ts +28 -0
- package/dist/ai/providers/catalog.js +166 -0
- package/dist/ai/providers/registry.d.ts +83 -0
- package/dist/ai/providers/registry.js +305 -0
- package/dist/ai/sse.d.ts +32 -0
- package/dist/ai/sse.js +140 -0
- package/dist/ai/thinking.d.ts +42 -0
- package/dist/ai/thinking.js +101 -0
- package/dist/ai/types.d.ts +428 -0
- package/dist/ai/types.js +19 -0
- package/dist/bundle/ama-sandbox.cjs +324 -0
- package/dist/bundle/ama.cjs +25770 -0
- package/dist/bundle.d.ts +9 -0
- package/dist/bundle.js +15 -0
- package/dist/cli/args.d.ts +82 -0
- package/dist/cli/args.js +381 -0
- package/dist/cli/bootstrap.d.ts +18 -0
- package/dist/cli/bootstrap.js +398 -0
- package/dist/cli/compose-providers.d.ts +37 -0
- package/dist/cli/compose-providers.js +104 -0
- package/dist/cli/compose-session.d.ts +69 -0
- package/dist/cli/compose-session.js +370 -0
- package/dist/cli/compose-store.d.ts +24 -0
- package/dist/cli/compose-store.js +125 -0
- package/dist/cli/compose.d.ts +106 -0
- package/dist/cli/compose.js +206 -0
- package/dist/cli/default-model.d.ts +16 -0
- package/dist/cli/default-model.js +24 -0
- package/dist/cli/deps.d.ts +205 -0
- package/dist/cli/deps.js +12 -0
- package/dist/cli/exit-codes.d.ts +29 -0
- package/dist/cli/exit-codes.js +40 -0
- package/dist/cli/main.d.ts +26 -0
- package/dist/cli/main.js +165 -0
- package/dist/cli/runtime.d.ts +90 -0
- package/dist/cli/runtime.js +8 -0
- package/dist/cli/startup-screen.d.ts +15 -0
- package/dist/cli/startup-screen.js +56 -0
- package/dist/cli/startup-steps.d.ts +35 -0
- package/dist/cli/startup-steps.js +197 -0
- package/dist/cli/subcommands/auth.d.ts +12 -0
- package/dist/cli/subcommands/auth.js +91 -0
- package/dist/cli/subcommands/config.d.ts +44 -0
- package/dist/cli/subcommands/config.js +201 -0
- package/dist/cli/subcommands/context.d.ts +21 -0
- package/dist/cli/subcommands/context.js +41 -0
- package/dist/cli/subcommands/doctor.d.ts +11 -0
- package/dist/cli/subcommands/doctor.js +246 -0
- package/dist/cli/subcommands/models-cache-probe.d.ts +34 -0
- package/dist/cli/subcommands/models-cache-probe.js +190 -0
- package/dist/cli/subcommands/models-discover.d.ts +40 -0
- package/dist/cli/subcommands/models-discover.js +204 -0
- package/dist/cli/subcommands/models.d.ts +40 -0
- package/dist/cli/subcommands/models.js +144 -0
- package/dist/cli/subcommands/sessions.d.ts +11 -0
- package/dist/cli/subcommands/sessions.js +100 -0
- package/dist/codemode/capability.d.ts +37 -0
- package/dist/codemode/capability.js +55 -0
- package/dist/codemode/declarations.d.ts +44 -0
- package/dist/codemode/declarations.js +111 -0
- package/dist/codemode/host-side.d.ts +59 -0
- package/dist/codemode/host-side.js +253 -0
- package/dist/codemode/modes.d.ts +23 -0
- package/dist/codemode/modes.js +40 -0
- package/dist/codemode/protocol.d.ts +91 -0
- package/dist/codemode/protocol.js +114 -0
- package/dist/codemode/sandbox-entry.d.ts +25 -0
- package/dist/codemode/sandbox-entry.js +323 -0
- package/dist/codemode/store.d.ts +26 -0
- package/dist/codemode/store.js +53 -0
- package/dist/codemode/tool.d.ts +105 -0
- package/dist/codemode/tool.js +283 -0
- package/dist/compaction/branch-summary.d.ts +30 -0
- package/dist/compaction/branch-summary.js +79 -0
- package/dist/compaction/breaker.d.ts +32 -0
- package/dist/compaction/breaker.js +65 -0
- package/dist/compaction/cut-point.d.ts +23 -0
- package/dist/compaction/cut-point.js +70 -0
- package/dist/compaction/estimate.d.ts +30 -0
- package/dist/compaction/estimate.js +114 -0
- package/dist/compaction/prune-tier.d.ts +31 -0
- package/dist/compaction/prune-tier.js +83 -0
- package/dist/compaction/serialize.d.ts +20 -0
- package/dist/compaction/serialize.js +115 -0
- package/dist/compaction/summarize-tier.d.ts +111 -0
- package/dist/compaction/summarize-tier.js +305 -0
- package/dist/config/auth-file.d.ts +39 -0
- package/dist/config/auth-file.js +92 -0
- package/dist/config/context-files.d.ts +30 -0
- package/dist/config/context-files.js +77 -0
- package/dist/config/load.d.ts +45 -0
- package/dist/config/load.js +97 -0
- package/dist/config/merge.d.ts +73 -0
- package/dist/config/merge.js +248 -0
- package/dist/config/paths.d.ts +38 -0
- package/dist/config/paths.js +108 -0
- package/dist/config/profile.d.ts +27 -0
- package/dist/config/profile.js +65 -0
- package/dist/config/schema.d.ts +34 -0
- package/dist/config/schema.js +413 -0
- package/dist/config/trust.d.ts +42 -0
- package/dist/config/trust.js +122 -0
- package/dist/config/types.d.ts +172 -0
- package/dist/config/types.js +27 -0
- package/dist/config/write.d.ts +10 -0
- package/dist/config/write.js +25 -0
- package/dist/errors.d.ts +28 -0
- package/dist/errors.js +30 -0
- package/dist/hooks/config.d.ts +49 -0
- package/dist/hooks/config.js +102 -0
- package/dist/hooks/dispatcher.d.ts +41 -0
- package/dist/hooks/dispatcher.js +104 -0
- package/dist/hooks/matcher.d.ts +22 -0
- package/dist/hooks/matcher.js +108 -0
- package/dist/hooks/protocol.d.ts +33 -0
- package/dist/hooks/protocol.js +240 -0
- package/dist/hooks/runner.d.ts +30 -0
- package/dist/hooks/runner.js +143 -0
- package/dist/hooks/types.d.ts +147 -0
- package/dist/hooks/types.js +20 -0
- package/dist/host/api-impl.d.ts +75 -0
- package/dist/host/api-impl.js +173 -0
- package/dist/host/loader.d.ts +31 -0
- package/dist/host/loader.js +147 -0
- package/dist/host/types.d.ts +169 -0
- package/dist/host/types.js +14 -0
- package/dist/host.d.ts +5 -0
- package/dist/host.js +4 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.js +28 -0
- package/dist/modes/commands-core.d.ts +44 -0
- package/dist/modes/commands-core.js +171 -0
- package/dist/modes/interactive/approval-dialog.d.ts +48 -0
- package/dist/modes/interactive/approval-dialog.js +202 -0
- package/dist/modes/interactive/commands.d.ts +41 -0
- package/dist/modes/interactive/commands.js +202 -0
- package/dist/modes/interactive/completion.d.ts +53 -0
- package/dist/modes/interactive/completion.js +157 -0
- package/dist/modes/interactive/interactive-mode.d.ts +46 -0
- package/dist/modes/interactive/interactive-mode.js +487 -0
- package/dist/modes/interactive/key-dispatch.d.ts +29 -0
- package/dist/modes/interactive/key-dispatch.js +107 -0
- package/dist/modes/interactive/line/line-editor.d.ts +50 -0
- package/dist/modes/interactive/line/line-editor.js +232 -0
- package/dist/modes/interactive/line/line-mode.d.ts +20 -0
- package/dist/modes/interactive/line/line-mode.js +216 -0
- package/dist/modes/interactive/line/line-render.d.ts +35 -0
- package/dist/modes/interactive/line/line-render.js +171 -0
- package/dist/modes/interactive/line/paste-state.d.ts +26 -0
- package/dist/modes/interactive/line/paste-state.js +72 -0
- package/dist/modes/interactive/message-view.d.ts +77 -0
- package/dist/modes/interactive/message-view.js +269 -0
- package/dist/modes/interactive/pickers.d.ts +42 -0
- package/dist/modes/interactive/pickers.js +115 -0
- package/dist/modes/interactive/startup-ui.d.ts +28 -0
- package/dist/modes/interactive/startup-ui.js +238 -0
- package/dist/modes/interactive/status-bar.d.ts +48 -0
- package/dist/modes/interactive/status-bar.js +139 -0
- package/dist/modes/interactive/tool-view.d.ts +95 -0
- package/dist/modes/interactive/tool-view.js +344 -0
- package/dist/modes/print/json-event.d.ts +15 -0
- package/dist/modes/print/json-event.js +39 -0
- package/dist/modes/print/print-mode.d.ts +15 -0
- package/dist/modes/print/print-mode.js +96 -0
- package/dist/modes/rpc/commands.d.ts +40 -0
- package/dist/modes/rpc/commands.js +258 -0
- package/dist/modes/rpc/jsonl.d.ts +16 -0
- package/dist/modes/rpc/jsonl.js +55 -0
- package/dist/modes/rpc/rpc-mode.d.ts +22 -0
- package/dist/modes/rpc/rpc-mode.js +153 -0
- package/dist/modes/session-report.d.ts +55 -0
- package/dist/modes/session-report.js +278 -0
- package/dist/modes/shared.d.ts +13 -0
- package/dist/modes/shared.js +52 -0
- package/dist/modes/startup-ui-text.d.ts +16 -0
- package/dist/modes/startup-ui-text.js +103 -0
- package/dist/permissions/broker.d.ts +47 -0
- package/dist/permissions/broker.js +107 -0
- package/dist/permissions/dangerous.d.ts +47 -0
- package/dist/permissions/dangerous.js +370 -0
- package/dist/permissions/pipeline.d.ts +51 -0
- package/dist/permissions/pipeline.js +181 -0
- package/dist/permissions/preview.d.ts +43 -0
- package/dist/permissions/preview.js +463 -0
- package/dist/permissions/rules.d.ts +58 -0
- package/dist/permissions/rules.js +249 -0
- package/dist/permissions/types.d.ts +100 -0
- package/dist/permissions/types.js +18 -0
- package/dist/rpc.d.ts +171 -0
- package/dist/rpc.js +13 -0
- package/dist/sdk.d.ts +98 -0
- package/dist/sdk.js +195 -0
- package/dist/session/manager.d.ts +70 -0
- package/dist/session/manager.js +278 -0
- package/dist/session/migrate.d.ts +19 -0
- package/dist/session/migrate.js +53 -0
- package/dist/session/projection.d.ts +50 -0
- package/dist/session/projection.js +180 -0
- package/dist/session/store.d.ts +47 -0
- package/dist/session/store.js +210 -0
- package/dist/session/tree.d.ts +21 -0
- package/dist/session/tree.js +88 -0
- package/dist/session/types.d.ts +199 -0
- package/dist/session/types.js +18 -0
- package/dist/skills/discover.d.ts +60 -0
- package/dist/skills/discover.js +160 -0
- package/dist/skills/expand.d.ts +35 -0
- package/dist/skills/expand.js +46 -0
- package/dist/skills/frontmatter.d.ts +27 -0
- package/dist/skills/frontmatter.js +157 -0
- package/dist/skills/index-prompt.d.ts +13 -0
- package/dist/skills/index-prompt.js +42 -0
- package/dist/skills/templates.d.ts +52 -0
- package/dist/skills/templates.js +177 -0
- package/dist/tools/bash.d.ts +49 -0
- package/dist/tools/bash.js +222 -0
- package/dist/tools/edit-fuzzy.d.ts +36 -0
- package/dist/tools/edit-fuzzy.js +129 -0
- package/dist/tools/edit.d.ts +38 -0
- package/dist/tools/edit.js +241 -0
- package/dist/tools/file-mutex.d.ts +8 -0
- package/dist/tools/file-mutex.js +29 -0
- package/dist/tools/glob.d.ts +44 -0
- package/dist/tools/glob.js +217 -0
- package/dist/tools/grep.d.ts +39 -0
- package/dist/tools/grep.js +179 -0
- package/dist/tools/ignore.d.ts +40 -0
- package/dist/tools/ignore.js +174 -0
- package/dist/tools/ls.d.ts +21 -0
- package/dist/tools/ls.js +96 -0
- package/dist/tools/output-accumulator.d.ts +48 -0
- package/dist/tools/output-accumulator.js +106 -0
- package/dist/tools/paths.d.ts +20 -0
- package/dist/tools/paths.js +58 -0
- package/dist/tools/presets.d.ts +71 -0
- package/dist/tools/presets.js +128 -0
- package/dist/tools/process-tree.d.ts +35 -0
- package/dist/tools/process-tree.js +125 -0
- package/dist/tools/read.d.ts +28 -0
- package/dist/tools/read.js +181 -0
- package/dist/tools/registry.d.ts +43 -0
- package/dist/tools/registry.js +106 -0
- package/dist/tools/shell.d.ts +38 -0
- package/dist/tools/shell.js +91 -0
- package/dist/tools/task.d.ts +29 -0
- package/dist/tools/task.js +107 -0
- package/dist/tools/todo.d.ts +28 -0
- package/dist/tools/todo.js +107 -0
- package/dist/tools/truncate.d.ts +44 -0
- package/dist/tools/truncate.js +175 -0
- package/dist/tools/types.d.ts +135 -0
- package/dist/tools/types.js +19 -0
- package/dist/tools/write.d.ts +20 -0
- package/dist/tools/write.js +84 -0
- package/dist/tui/ansi.d.ts +29 -0
- package/dist/tui/ansi.js +420 -0
- package/dist/tui/component.d.ts +38 -0
- package/dist/tui/component.js +13 -0
- package/dist/tui/components/box.d.ts +26 -0
- package/dist/tui/components/box.js +65 -0
- package/dist/tui/components/container.d.ts +14 -0
- package/dist/tui/components/container.js +39 -0
- package/dist/tui/components/editor-buffer.d.ts +67 -0
- package/dist/tui/components/editor-buffer.js +300 -0
- package/dist/tui/components/editor-paste.d.ts +29 -0
- package/dist/tui/components/editor-paste.js +64 -0
- package/dist/tui/components/editor.d.ts +114 -0
- package/dist/tui/components/editor.js +530 -0
- package/dist/tui/components/key-value.d.ts +29 -0
- package/dist/tui/components/key-value.js +48 -0
- package/dist/tui/components/loader.d.ts +35 -0
- package/dist/tui/components/loader.js +71 -0
- package/dist/tui/components/markdown.d.ts +90 -0
- package/dist/tui/components/markdown.js +462 -0
- package/dist/tui/components/meter.d.ts +29 -0
- package/dist/tui/components/meter.js +47 -0
- package/dist/tui/components/overlay.d.ts +24 -0
- package/dist/tui/components/overlay.js +52 -0
- package/dist/tui/components/select-list.d.ts +57 -0
- package/dist/tui/components/select-list.js +179 -0
- package/dist/tui/components/spacer.d.ts +11 -0
- package/dist/tui/components/spacer.js +16 -0
- package/dist/tui/components/text.d.ts +30 -0
- package/dist/tui/components/text.js +65 -0
- package/dist/tui/keybindings.d.ts +66 -0
- package/dist/tui/keybindings.js +120 -0
- package/dist/tui/keys.d.ts +38 -0
- package/dist/tui/keys.js +275 -0
- package/dist/tui/stdin-buffer.d.ts +41 -0
- package/dist/tui/stdin-buffer.js +206 -0
- package/dist/tui/terminal.d.ts +79 -0
- package/dist/tui/terminal.js +153 -0
- package/dist/tui/theme.d.ts +31 -0
- package/dist/tui/theme.js +199 -0
- package/dist/tui/tui.d.ts +103 -0
- package/dist/tui/tui.js +363 -0
- package/dist/tui/vt-screen.d.ts +46 -0
- package/dist/tui/vt-screen.js +251 -0
- package/dist/tui.d.ts +30 -0
- package/dist/tui.js +31 -0
- package/dist/version.d.ts +8 -0
- package/dist/version.js +24 -0
- package/docs/codemode.md +102 -0
- package/docs/hooks.md +196 -0
- package/docs/host-api.md +161 -0
- package/docs/providers.md +316 -0
- package/docs/rpc.md +253 -0
- package/docs/session-format.md +119 -0
- package/docs/tui.md +168 -0
- package/package.json +101 -4
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ama auth set|list|remove <provider>`(设计 §3.5、§16.2 B5 验收)。[B5]
|
|
3
|
+
*
|
|
4
|
+
* - set:key 从 stdin 读取(TTY 下不回显),不经命令行参数,避免进 shell 历史;写 auth.json 0600。
|
|
5
|
+
* - list:只列供应商与 key 形态(字面量 / `!command` / `$ENV` 引用),从不输出 key。
|
|
6
|
+
* - `--auth-file <path>` 改变目标文件(缺省 `<configDir>/auth.json`)。
|
|
7
|
+
*/
|
|
8
|
+
import { resolve } from "node:path";
|
|
9
|
+
import { PROVIDER_ID_PATTERN, defaultAuthFilePath, describeAuthFile, readAuthFile, removeAuthKey, setAuthKey, } from "../../config/auth-file.js";
|
|
10
|
+
import { resolveConfigDir } from "../../config/paths.js";
|
|
11
|
+
import { parseSubArgs, UsageError } from "../args.js";
|
|
12
|
+
import { ExitCode } from "../exit-codes.js";
|
|
13
|
+
export const AUTH_USAGE = `用法:ama auth set <provider> [--auth-file <文件>] 从 stdin 读取 key
|
|
14
|
+
ama auth list [--auth-file <文件>]
|
|
15
|
+
ama auth remove <provider> [--auth-file <文件>]
|
|
16
|
+
`;
|
|
17
|
+
const KIND_TEXT = { literal: "key", command: "!命令", "env-ref": "环境变量引用" };
|
|
18
|
+
function providerArg(positionals, action) {
|
|
19
|
+
const provider = positionals[1];
|
|
20
|
+
if (provider === undefined)
|
|
21
|
+
throw new UsageError(`ama auth ${action} 需要 <provider>`);
|
|
22
|
+
if (positionals.length > 2)
|
|
23
|
+
throw new UsageError(`多余的参数:${positionals.slice(2).join(" ")}`);
|
|
24
|
+
if (!PROVIDER_ID_PATTERN.test(provider))
|
|
25
|
+
throw new UsageError(`供应商 id 不合法:${provider}`);
|
|
26
|
+
return provider;
|
|
27
|
+
}
|
|
28
|
+
/** 取第一行并去掉首尾空白;stdin 为空返回 ""。 */
|
|
29
|
+
export function extractKey(raw) {
|
|
30
|
+
const line = raw.split(/\r?\n/).find((l) => l.trim() !== "") ?? "";
|
|
31
|
+
return line.trim();
|
|
32
|
+
}
|
|
33
|
+
export async function runAuth(argv, io) {
|
|
34
|
+
const { positionals, values, flags } = parseSubArgs(argv, ["auth-file"]);
|
|
35
|
+
const action = positionals[0];
|
|
36
|
+
if (flags.has("help") || action === undefined) {
|
|
37
|
+
io.stdout(AUTH_USAGE);
|
|
38
|
+
return flags.has("help") ? ExitCode.Ok : ExitCode.Usage;
|
|
39
|
+
}
|
|
40
|
+
const explicit = values.get("auth-file");
|
|
41
|
+
const path = explicit !== undefined
|
|
42
|
+
? resolve(io.cwd, explicit)
|
|
43
|
+
: defaultAuthFilePath(resolveConfigDir({ env: io.env }));
|
|
44
|
+
switch (action) {
|
|
45
|
+
case "set": {
|
|
46
|
+
const provider = providerArg(positionals, "set");
|
|
47
|
+
if (io.stdinIsTTY)
|
|
48
|
+
io.stderr(`输入 ${provider} 的 API key(不回显),回车结束:`);
|
|
49
|
+
const key = extractKey(await io.readStdin());
|
|
50
|
+
if (io.stdinIsTTY)
|
|
51
|
+
io.stderr("\n");
|
|
52
|
+
if (key === "")
|
|
53
|
+
throw new UsageError("没有从 stdin 读到 key");
|
|
54
|
+
setAuthKey(path, provider, key);
|
|
55
|
+
io.stdout(`已保存 ${provider} 的 key → ${path}(0600)\n`);
|
|
56
|
+
return ExitCode.Ok;
|
|
57
|
+
}
|
|
58
|
+
case "list": {
|
|
59
|
+
if (positionals.length > 1)
|
|
60
|
+
throw new UsageError(`多余的参数:${positionals.slice(1).join(" ")}`);
|
|
61
|
+
const result = readAuthFile(path);
|
|
62
|
+
for (const warning of result.warnings)
|
|
63
|
+
io.stderr(`ama: 警告:${warning}\n`);
|
|
64
|
+
const entries = describeAuthFile(result.file);
|
|
65
|
+
if (entries.length === 0) {
|
|
66
|
+
io.stdout(`${path}:没有保存的 key\n`);
|
|
67
|
+
return ExitCode.Ok;
|
|
68
|
+
}
|
|
69
|
+
io.stdout(`${path}\n`);
|
|
70
|
+
for (const entry of entries) {
|
|
71
|
+
const extra = [
|
|
72
|
+
entry.hasBaseUrl ? "baseUrl" : undefined,
|
|
73
|
+
entry.envNames.length > 0 ? `env ${entry.envNames.join(",")}` : undefined,
|
|
74
|
+
].filter((x) => x !== undefined);
|
|
75
|
+
io.stdout(` ${entry.provider.padEnd(20)} ${KIND_TEXT[entry.kind]}${extra.length > 0 ? ` · ${extra.join(" · ")}` : ""}\n`);
|
|
76
|
+
}
|
|
77
|
+
return ExitCode.Ok;
|
|
78
|
+
}
|
|
79
|
+
case "remove": {
|
|
80
|
+
const provider = providerArg(positionals, "remove");
|
|
81
|
+
if (removeAuthKey(path, provider)) {
|
|
82
|
+
io.stdout(`已删除 ${provider} 的 key(${path})\n`);
|
|
83
|
+
return ExitCode.Ok;
|
|
84
|
+
}
|
|
85
|
+
io.stderr(`ama: ${path} 中没有 ${provider}\n`);
|
|
86
|
+
return ExitCode.RuntimeError;
|
|
87
|
+
}
|
|
88
|
+
default:
|
|
89
|
+
throw new UsageError(`未知的 auth 子命令:${action}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ama config show [--json] [--profile <文件>] [--auth-file <文件>]`(设计 §10.0):打印生效配置,
|
|
3
|
+
* 每一项标出来自哪一层(default / user / profile / project),并给出将使用的模型与原因
|
|
4
|
+
* (`config.defaultModel` 或零配置选择)。只读;config 里的字面量 apiKey 只显示种类。[B6]
|
|
5
|
+
*
|
|
6
|
+
* [W3-B12] 「供应商」节:config 里出现的供应商与 baseUrl 来自环境变量(`OPENAI_BASE_URL` /
|
|
7
|
+
* `ANTHROPIC_BASE_URL`)的内置供应商,列出协议、生效 baseUrl 与来源、config 里每个模型的协议
|
|
8
|
+
* (模型级 `api` 生效后的值)。
|
|
9
|
+
*/
|
|
10
|
+
import type { AmaConfig } from "../../config/types.js";
|
|
11
|
+
import type { Api, ProviderRegistryApi } from "../../ai/types.js";
|
|
12
|
+
import type { CliIo, RuntimeDeps } from "../deps.js";
|
|
13
|
+
export declare const CONFIG_USAGE = "\u7528\u6CD5\uFF1Aama config show [--json] [--profile <\u6587\u4EF6>] [--auth-file <\u6587\u4EF6>]\n";
|
|
14
|
+
type Layer = {
|
|
15
|
+
name: "default" | "user" | "profile" | "project";
|
|
16
|
+
label: string;
|
|
17
|
+
value: unknown;
|
|
18
|
+
};
|
|
19
|
+
/** 叶子路径 → 值(数组与标量是叶子)。 */
|
|
20
|
+
export declare function flatten(value: unknown, prefix?: string, out?: Map<string, unknown>): Map<string, unknown>;
|
|
21
|
+
export interface ModelDescription {
|
|
22
|
+
ref: string | undefined;
|
|
23
|
+
reason: string;
|
|
24
|
+
}
|
|
25
|
+
/** 将使用的模型:`defaultModel` → 零配置(第一个有 key / 本地可达的供应商)→ 无。 */
|
|
26
|
+
export declare function describeModel(config: AmaConfig, registry: ProviderRegistryApi | undefined): Promise<ModelDescription>;
|
|
27
|
+
export interface ProviderDescription {
|
|
28
|
+
id: string;
|
|
29
|
+
api: Api;
|
|
30
|
+
baseUrl: string;
|
|
31
|
+
/** baseUrl 来自的环境变量。 */
|
|
32
|
+
baseUrlEnv?: string;
|
|
33
|
+
/** config 里列出的模型及其生效协议。 */
|
|
34
|
+
models: {
|
|
35
|
+
id: string;
|
|
36
|
+
api: Api;
|
|
37
|
+
}[];
|
|
38
|
+
}
|
|
39
|
+
/** config 里出现的供应商 + baseUrl 来自环境变量的内置供应商。 */
|
|
40
|
+
export declare function describeProviders(config: AmaConfig, registry: ProviderRegistryApi | undefined): ProviderDescription[];
|
|
41
|
+
/** 每个生效叶子的来源:累加型列表列出所有贡献层,其余取最后设置它的层。 */
|
|
42
|
+
export declare function sourcesOf(effective: AmaConfig, layers: readonly Layer[]): Map<string, string>;
|
|
43
|
+
export declare function runConfig(argv: readonly string[], io: CliIo, deps: Pick<RuntimeDeps, "providers"> | undefined): Promise<number>;
|
|
44
|
+
export {};
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ama config show [--json] [--profile <文件>] [--auth-file <文件>]`(设计 §10.0):打印生效配置,
|
|
3
|
+
* 每一项标出来自哪一层(default / user / profile / project),并给出将使用的模型与原因
|
|
4
|
+
* (`config.defaultModel` 或零配置选择)。只读;config 里的字面量 apiKey 只显示种类。[B6]
|
|
5
|
+
*
|
|
6
|
+
* [W3-B12] 「供应商」节:config 里出现的供应商与 baseUrl 来自环境变量(`OPENAI_BASE_URL` /
|
|
7
|
+
* `ANTHROPIC_BASE_URL`)的内置供应商,列出协议、生效 baseUrl 与来源、config 里每个模型的协议
|
|
8
|
+
* (模型级 `api` 生效后的值)。
|
|
9
|
+
*/
|
|
10
|
+
import { loadConfigFile } from "../../config/load.js";
|
|
11
|
+
import { DEFAULT_CONFIG, PROFILE_DEFAULTS, mergeProjectAndCli } from "../../config/merge.js";
|
|
12
|
+
import { CONFIG_FILE, projectFile } from "../../config/paths.js";
|
|
13
|
+
import { baseUrlEnvOf } from "../../ai/providers/registry.js";
|
|
14
|
+
import { classifyKeyValue } from "../../config/auth-file.js";
|
|
15
|
+
import { effectiveCodemodeMode, resolvePreset } from "../../tools/presets.js";
|
|
16
|
+
import { builtinTools } from "../../tools/registry.js";
|
|
17
|
+
import { parseSubArgs, UsageError } from "../args.js";
|
|
18
|
+
import { pickDefaultModel } from "../default-model.js";
|
|
19
|
+
import { ExitCode } from "../exit-codes.js";
|
|
20
|
+
import { buildRegistry, loadUserLevel } from "./context.js";
|
|
21
|
+
export const CONFIG_USAGE = `用法:ama config show [--json] [--profile <文件>] [--auth-file <文件>]
|
|
22
|
+
`;
|
|
23
|
+
function isObject(value) {
|
|
24
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
25
|
+
}
|
|
26
|
+
/** 叶子路径 → 值(数组与标量是叶子)。 */
|
|
27
|
+
export function flatten(value, prefix = "", out = new Map()) {
|
|
28
|
+
if (!isObject(value)) {
|
|
29
|
+
if (prefix !== "")
|
|
30
|
+
out.set(prefix, value);
|
|
31
|
+
return out;
|
|
32
|
+
}
|
|
33
|
+
for (const [key, child] of Object.entries(value)) {
|
|
34
|
+
if (key === "$schema" || (prefix === "" && key === "version"))
|
|
35
|
+
continue;
|
|
36
|
+
flatten(child, prefix === "" ? key : `${prefix}.${key}`, out);
|
|
37
|
+
}
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
40
|
+
const ACCUMULATING = new Set([
|
|
41
|
+
"permission.allow",
|
|
42
|
+
"permission.deny",
|
|
43
|
+
"tools.disabled",
|
|
44
|
+
"skills.dirs",
|
|
45
|
+
]);
|
|
46
|
+
function display(path, value) {
|
|
47
|
+
if (path.endsWith(".apiKey") && typeof value === "string") {
|
|
48
|
+
const kind = classifyKeyValue(value);
|
|
49
|
+
return kind === "literal" ? "<literal key>" : JSON.stringify(value);
|
|
50
|
+
}
|
|
51
|
+
return JSON.stringify(value);
|
|
52
|
+
}
|
|
53
|
+
/** 将使用的模型:`defaultModel` → 零配置(第一个有 key / 本地可达的供应商)→ 无。 */
|
|
54
|
+
export async function describeModel(config, registry) {
|
|
55
|
+
if (config.defaultModel !== undefined)
|
|
56
|
+
return { ref: config.defaultModel, reason: "config.defaultModel" };
|
|
57
|
+
if (registry === undefined)
|
|
58
|
+
return { ref: undefined, reason: "注册表未装配" };
|
|
59
|
+
const picked = await pickDefaultModel(registry);
|
|
60
|
+
if (picked === undefined)
|
|
61
|
+
return { ref: undefined, reason: "没有可用模型:ama auth set <provider> 或设置对应环境变量" };
|
|
62
|
+
const ref = `${picked.provider.id}/${picked.model.id}`;
|
|
63
|
+
if (picked.via === "local")
|
|
64
|
+
return { ref, reason: `零配置:本地 ${picked.provider.id} 可达` };
|
|
65
|
+
const key = await registry.resolveApiKey(picked.provider.id);
|
|
66
|
+
const origin = key.origin !== undefined ? ` ${key.origin}` : "";
|
|
67
|
+
return { ref, reason: `零配置:${picked.provider.id} 有 key(${key.source}${origin})` };
|
|
68
|
+
}
|
|
69
|
+
/** config 里出现的供应商 + baseUrl 来自环境变量的内置供应商。 */
|
|
70
|
+
export function describeProviders(config, registry) {
|
|
71
|
+
if (registry === undefined)
|
|
72
|
+
return [];
|
|
73
|
+
const out = [];
|
|
74
|
+
for (const provider of registry.list()) {
|
|
75
|
+
const configured = config.providers?.[provider.id];
|
|
76
|
+
const env = baseUrlEnvOf(registry, provider.id);
|
|
77
|
+
if (configured === undefined && env === undefined)
|
|
78
|
+
continue;
|
|
79
|
+
const ids = new Set((configured?.models ?? []).map((m) => m.id));
|
|
80
|
+
out.push({
|
|
81
|
+
id: provider.id,
|
|
82
|
+
api: provider.api,
|
|
83
|
+
baseUrl: provider.baseUrl,
|
|
84
|
+
...(env !== undefined ? { baseUrlEnv: env } : {}),
|
|
85
|
+
models: provider.models.filter((m) => ids.has(m.id)).map((m) => ({ id: m.id, api: m.api })),
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
return out;
|
|
89
|
+
}
|
|
90
|
+
function providerLines(providers) {
|
|
91
|
+
if (providers.length === 0)
|
|
92
|
+
return [];
|
|
93
|
+
const lines = ["", "供应商:"];
|
|
94
|
+
for (const p of providers) {
|
|
95
|
+
const env = p.baseUrlEnv !== undefined ? `(baseUrl 来自环境变量 ${p.baseUrlEnv})` : "";
|
|
96
|
+
lines.push(` ${p.id} ${p.api} ${p.baseUrl}${env}`);
|
|
97
|
+
for (const m of p.models)
|
|
98
|
+
lines.push(` ${p.id}/${m.id} ${m.api}`);
|
|
99
|
+
}
|
|
100
|
+
return lines;
|
|
101
|
+
}
|
|
102
|
+
function layersOf(level, project, effective) {
|
|
103
|
+
const layers = [{ name: "default", label: "内置缺省", value: DEFAULT_CONFIG }];
|
|
104
|
+
const user = loadConfigFile("config", level.userConfigPath)?.value;
|
|
105
|
+
if (user !== undefined)
|
|
106
|
+
layers.push({ name: "user", label: level.userConfigPath, value: user });
|
|
107
|
+
if (level.profile !== undefined) {
|
|
108
|
+
layers.push({ name: "profile", label: level.profile.path, value: PROFILE_DEFAULTS });
|
|
109
|
+
if (level.profile.config !== undefined)
|
|
110
|
+
layers.push({
|
|
111
|
+
name: "profile",
|
|
112
|
+
label: level.profile.configFile ?? level.profile.path,
|
|
113
|
+
value: level.profile.config,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
if (project !== undefined) {
|
|
117
|
+
// 只记被接受的项:生效值与项目级相同的路径才算项目级来源。
|
|
118
|
+
const flat = flatten(effective);
|
|
119
|
+
const accepted = {};
|
|
120
|
+
for (const [path, value] of flatten(project)) {
|
|
121
|
+
if (JSON.stringify(flat.get(path)) === JSON.stringify(value) || ACCUMULATING.has(path))
|
|
122
|
+
accepted[path] = value;
|
|
123
|
+
}
|
|
124
|
+
layers.push({ name: "project", label: ".ama/config.json", value: unflatten(accepted) });
|
|
125
|
+
}
|
|
126
|
+
return layers;
|
|
127
|
+
}
|
|
128
|
+
function unflatten(flat) {
|
|
129
|
+
const out = {};
|
|
130
|
+
for (const [path, value] of Object.entries(flat)) {
|
|
131
|
+
const keys = path.split(".");
|
|
132
|
+
let node = out;
|
|
133
|
+
for (const key of keys.slice(0, -1))
|
|
134
|
+
node = (node[key] ??= {});
|
|
135
|
+
node[keys.at(-1)] = value;
|
|
136
|
+
}
|
|
137
|
+
return out;
|
|
138
|
+
}
|
|
139
|
+
/** 每个生效叶子的来源:累加型列表列出所有贡献层,其余取最后设置它的层。 */
|
|
140
|
+
export function sourcesOf(effective, layers) {
|
|
141
|
+
const flats = layers.map((l) => ({ name: l.name, flat: flatten(l.value) }));
|
|
142
|
+
const sources = new Map();
|
|
143
|
+
for (const path of flatten(effective).keys()) {
|
|
144
|
+
const setBy = flats.filter((l) => l.flat.has(path)).map((l) => l.name);
|
|
145
|
+
const names = ACCUMULATING.has(path)
|
|
146
|
+
? [...new Set(setBy.filter((n) => n !== "default"))]
|
|
147
|
+
: setBy.slice(-1);
|
|
148
|
+
sources.set(path, names.length > 0 ? names.join("+") : "default");
|
|
149
|
+
}
|
|
150
|
+
return sources;
|
|
151
|
+
}
|
|
152
|
+
export async function runConfig(argv, io, deps) {
|
|
153
|
+
const { positionals, values, flags } = parseSubArgs(argv, ["profile", "auth-file"], ["json"]);
|
|
154
|
+
const action = positionals[0] ?? "show";
|
|
155
|
+
if (flags.has("help")) {
|
|
156
|
+
io.stdout(CONFIG_USAGE);
|
|
157
|
+
return ExitCode.Ok;
|
|
158
|
+
}
|
|
159
|
+
if (action !== "show")
|
|
160
|
+
throw new UsageError(`未知的 config 子命令:${action}`);
|
|
161
|
+
const level = loadUserLevel(io, {
|
|
162
|
+
profile: values.get("profile"),
|
|
163
|
+
authFile: values.get("auth-file"),
|
|
164
|
+
});
|
|
165
|
+
const project = loadConfigFile("config", projectFile(io.cwd, CONFIG_FILE))?.value;
|
|
166
|
+
const merged = mergeProjectAndCli(level.merged, project, undefined);
|
|
167
|
+
const config = merged.config;
|
|
168
|
+
const layers = layersOf(level, project, config);
|
|
169
|
+
const sources = sourcesOf(config, layers);
|
|
170
|
+
const registry = deps === undefined ? undefined : await buildRegistry(level, io, deps);
|
|
171
|
+
const model = await describeModel(config, registry);
|
|
172
|
+
const providers = describeProviders(config, registry);
|
|
173
|
+
const builtin = new Set(builtinTools().map((tool) => tool.name));
|
|
174
|
+
const preset = resolvePreset({ config, available: (name) => builtin.has(name) });
|
|
175
|
+
const warnings = [...level.warnings, ...merged.warnings];
|
|
176
|
+
if (flags.has("json")) {
|
|
177
|
+
const entries = [...flatten(config)].map(([path, value]) => ({
|
|
178
|
+
path,
|
|
179
|
+
value: path.endsWith(".apiKey") ? display(path, value) : value,
|
|
180
|
+
source: sources.get(path),
|
|
181
|
+
}));
|
|
182
|
+
io.stdout(`${JSON.stringify({ model, providers, tools: preset.builtin, codemode: effectiveCodemodeMode(config), entries, layers: layers.map((l) => ({ name: l.name, file: l.label })), warnings }, null, 2)}\n`);
|
|
183
|
+
return ExitCode.Ok;
|
|
184
|
+
}
|
|
185
|
+
const lines = ["生效配置(来源:default 内置 ← user ← profile ← project 只能收紧)"];
|
|
186
|
+
for (const layer of layers.slice(1))
|
|
187
|
+
lines.push(` ${layer.name}:${layer.label}`);
|
|
188
|
+
lines.push("");
|
|
189
|
+
const rows = [...flatten(config)].map(([path, value]) => [`${path} = ${display(path, value)}`, sources.get(path) ?? "default"]);
|
|
190
|
+
const width = Math.min(60, Math.max(...rows.map(([text]) => text.length)));
|
|
191
|
+
for (const [text, source] of rows)
|
|
192
|
+
lines.push(` ${text.padEnd(width)} ${source}`);
|
|
193
|
+
lines.push(...providerLines(providers));
|
|
194
|
+
lines.push("");
|
|
195
|
+
lines.push(`模型:${model.ref ?? "(无)"} ${model.reason}`);
|
|
196
|
+
lines.push(`工具:${preset.builtin.join(", ")}(预设 ${preset.preset},codemode ${preset.codemode})`);
|
|
197
|
+
for (const warning of [...warnings, ...preset.warnings])
|
|
198
|
+
lines.push(`警告:${warning}`);
|
|
199
|
+
io.stdout(`${lines.join("\n")}\n`);
|
|
200
|
+
return ExitCode.Ok;
|
|
201
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 子命令共用:读用户级配置(+ 可选 profile)并构造供应商注册表。[B5]
|
|
3
|
+
*/
|
|
4
|
+
import type { ProviderRegistryApi } from "../../ai/types.js";
|
|
5
|
+
import { type MergeResult } from "../../config/merge.js";
|
|
6
|
+
import { type ProfileOptions } from "../../config/profile.js";
|
|
7
|
+
import type { CliIo, RuntimeDeps } from "../deps.js";
|
|
8
|
+
export interface UserLevel {
|
|
9
|
+
configDir: string;
|
|
10
|
+
dataDir: string;
|
|
11
|
+
profile?: ProfileOptions;
|
|
12
|
+
merged: MergeResult;
|
|
13
|
+
userConfigPath: string;
|
|
14
|
+
authFile: string;
|
|
15
|
+
warnings: string[];
|
|
16
|
+
}
|
|
17
|
+
export declare function loadUserLevel(io: CliIo, options?: {
|
|
18
|
+
profile?: string | undefined;
|
|
19
|
+
authFile?: string | undefined;
|
|
20
|
+
}): UserLevel;
|
|
21
|
+
export declare function buildRegistry(level: UserLevel, io: CliIo, deps: Pick<RuntimeDeps, "providers">): Promise<ProviderRegistryApi>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 子命令共用:读用户级配置(+ 可选 profile)并构造供应商注册表。[B5]
|
|
3
|
+
*/
|
|
4
|
+
import { resolve } from "node:path";
|
|
5
|
+
import { defaultAuthFilePath } from "../../config/auth-file.js";
|
|
6
|
+
import { loadConfigFile } from "../../config/load.js";
|
|
7
|
+
import { mergeBaseLayers } from "../../config/merge.js";
|
|
8
|
+
import { CONFIG_FILE, resolveConfigDir, resolveDataDir, userFile } from "../../config/paths.js";
|
|
9
|
+
import { loadProfile } from "../../config/profile.js";
|
|
10
|
+
export function loadUserLevel(io, options = {}) {
|
|
11
|
+
const configDir = resolveConfigDir({ env: io.env });
|
|
12
|
+
const dataDir = resolveDataDir({ env: io.env });
|
|
13
|
+
const warnings = [];
|
|
14
|
+
const profile = options.profile === undefined ? undefined : loadProfile(options.profile, io.cwd);
|
|
15
|
+
if (profile !== undefined)
|
|
16
|
+
warnings.push(...profile.warnings);
|
|
17
|
+
const userConfigPath = userFile({ configDir }, CONFIG_FILE);
|
|
18
|
+
const user = loadConfigFile("config", userConfigPath);
|
|
19
|
+
if (user !== undefined)
|
|
20
|
+
warnings.push(...user.warnings);
|
|
21
|
+
const merged = mergeBaseLayers({
|
|
22
|
+
user: user?.value,
|
|
23
|
+
profile: profile?.config,
|
|
24
|
+
hasProfile: profile !== undefined,
|
|
25
|
+
});
|
|
26
|
+
const authFile = options.authFile !== undefined
|
|
27
|
+
? resolve(io.cwd, options.authFile)
|
|
28
|
+
: (profile?.authFile ?? defaultAuthFilePath(configDir));
|
|
29
|
+
const result = { configDir, dataDir, merged, userConfigPath, authFile, warnings };
|
|
30
|
+
if (profile !== undefined)
|
|
31
|
+
result.profile = profile;
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
export async function buildRegistry(level, io, deps) {
|
|
35
|
+
return deps.providers.create({
|
|
36
|
+
config: level.merged.config,
|
|
37
|
+
cwd: io.cwd,
|
|
38
|
+
authFile: level.authFile,
|
|
39
|
+
authEnv: level.profile?.authEnv ?? true,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ama doctor`(设计 §1.2、§6.1、R4、R8)。[B5]
|
|
3
|
+
*
|
|
4
|
+
* 输出:目录、配置层级(每层文件存在 / 有效 / 警告)、信任状态与需信任的资源、AGENTS.md、
|
|
5
|
+
* key 来源(只给来源不给值)、将执行的 Hook 命令(含因未信任被跳过的项目级)、终端能力。
|
|
6
|
+
* 只读:不询问信任、不写任何文件;配置错误照常列出并以退出码 3 结束。
|
|
7
|
+
* [W3-B12] baseUrl 来自 `OPENAI_BASE_URL` / `ANTHROPIC_BASE_URL` 时在 key 行后标出变量与地址。
|
|
8
|
+
*/
|
|
9
|
+
import type { CliIo, RuntimeDeps } from "../deps.js";
|
|
10
|
+
export declare const DOCTOR_USAGE = "\u7528\u6CD5\uFF1Aama doctor [--profile <\u6587\u4EF6>] [--auth-file <\u6587\u4EF6>] [--trust | --no-trust]\n";
|
|
11
|
+
export declare function runDoctor(argv: readonly string[], io: CliIo, deps: RuntimeDeps | undefined): Promise<number>;
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ama doctor`(设计 §1.2、§6.1、R4、R8)。[B5]
|
|
3
|
+
*
|
|
4
|
+
* 输出:目录、配置层级(每层文件存在 / 有效 / 警告)、信任状态与需信任的资源、AGENTS.md、
|
|
5
|
+
* key 来源(只给来源不给值)、将执行的 Hook 命令(含因未信任被跳过的项目级)、终端能力。
|
|
6
|
+
* 只读:不询问信任、不写任何文件;配置错误照常列出并以退出码 3 结束。
|
|
7
|
+
* [W3-B12] baseUrl 来自 `OPENAI_BASE_URL` / `ANTHROPIC_BASE_URL` 时在 key 行后标出变量与地址。
|
|
8
|
+
*/
|
|
9
|
+
import { existsSync } from "node:fs";
|
|
10
|
+
import { baseUrlEnvOf } from "../../ai/providers/registry.js";
|
|
11
|
+
import { classifyKeyValue, isModeTooOpen, fileMode, readAuthFile } from "../../config/auth-file.js";
|
|
12
|
+
import { findContextFiles } from "../../config/context-files.js";
|
|
13
|
+
import { loadConfigFile, probeConfigFile } from "../../config/load.js";
|
|
14
|
+
import { mergeProjectAndCli } from "../../config/merge.js";
|
|
15
|
+
import { CONFIG_FILE, HOOKS_FILE, projectFile, userFile } from "../../config/paths.js";
|
|
16
|
+
import { findTrustEntry, readTrustFile, trustGatedResources } from "../../config/trust.js";
|
|
17
|
+
import { loadHookConfigs } from "../../hooks/config.js";
|
|
18
|
+
import { AMA_VERSION } from "../../version.js";
|
|
19
|
+
import { parseSubArgs } from "../args.js";
|
|
20
|
+
import { ExitCode } from "../exit-codes.js";
|
|
21
|
+
import { describeModel } from "./config.js";
|
|
22
|
+
import { buildRegistry, loadUserLevel } from "./context.js";
|
|
23
|
+
export const DOCTOR_USAGE = `用法:ama doctor [--profile <文件>] [--auth-file <文件>] [--trust | --no-trust]
|
|
24
|
+
`;
|
|
25
|
+
class Report {
|
|
26
|
+
lines = [];
|
|
27
|
+
problems = 0;
|
|
28
|
+
section(title) {
|
|
29
|
+
if (this.lines.length > 0)
|
|
30
|
+
this.lines.push("");
|
|
31
|
+
this.lines.push(title);
|
|
32
|
+
}
|
|
33
|
+
item(text) {
|
|
34
|
+
this.lines.push(` ${text}`);
|
|
35
|
+
}
|
|
36
|
+
problem(text) {
|
|
37
|
+
this.problems++;
|
|
38
|
+
this.lines.push(` ✗ ${text}`);
|
|
39
|
+
}
|
|
40
|
+
text() {
|
|
41
|
+
return `${this.lines.join("\n")}\n`;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function probeLine(report, label, kind, path) {
|
|
45
|
+
const probe = probeConfigFile(kind, path);
|
|
46
|
+
if (!probe.exists) {
|
|
47
|
+
report.item(`${label}:${path}(不存在)`);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (!probe.ok) {
|
|
51
|
+
report.problem(`${label}:${probe.messages.join("; ")}`);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
report.item(`${label}:${path}`);
|
|
55
|
+
for (const message of probe.messages)
|
|
56
|
+
report.item(` 警告:${message}`);
|
|
57
|
+
}
|
|
58
|
+
function trustSection(report, io, level, flag) {
|
|
59
|
+
report.section("信任");
|
|
60
|
+
let trusted = false;
|
|
61
|
+
let from;
|
|
62
|
+
if (flag !== undefined) {
|
|
63
|
+
trusted = flag;
|
|
64
|
+
from = "命令行";
|
|
65
|
+
}
|
|
66
|
+
else if (level.profile?.trustProject === true) {
|
|
67
|
+
trusted = true;
|
|
68
|
+
from = "profile.trustProject";
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
try {
|
|
72
|
+
const entry = findTrustEntry(readTrustFile(level.configDir).entries, io.cwd);
|
|
73
|
+
trusted = entry?.trusted ?? false;
|
|
74
|
+
from = entry === undefined ? "缺省:无记录,交互模式会询问" : `trust.json ${entry.path}`;
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
report.problem(error.message);
|
|
78
|
+
from = "trust.json 无效";
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
report.item(`${io.cwd}:${trusted ? "已信任" : "未信任"}(${from})`);
|
|
82
|
+
const gated = trustGatedResources(io.cwd);
|
|
83
|
+
if (gated.length > 0) {
|
|
84
|
+
report.item(`需要信任的资源${trusted ? "" : "(当前不加载)"}:`);
|
|
85
|
+
for (const path of gated)
|
|
86
|
+
report.item(` ${path}`);
|
|
87
|
+
}
|
|
88
|
+
return trusted;
|
|
89
|
+
}
|
|
90
|
+
async function keySection(report, io, level, deps) {
|
|
91
|
+
report.section("Key 来源(只显示来源,不显示值)");
|
|
92
|
+
try {
|
|
93
|
+
const auth = readAuthFile(level.authFile);
|
|
94
|
+
const mode = fileMode(level.authFile);
|
|
95
|
+
if (!auth.exists)
|
|
96
|
+
report.item(`auth.json:${level.authFile}(不存在)`);
|
|
97
|
+
else if (isModeTooOpen(mode))
|
|
98
|
+
report.problem(`auth.json 权限 ${mode?.toString(8)},应为 600:${level.authFile}`);
|
|
99
|
+
else
|
|
100
|
+
report.item(`auth.json:${level.authFile}`);
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
report.problem(error.message);
|
|
104
|
+
}
|
|
105
|
+
if (deps !== undefined) {
|
|
106
|
+
try {
|
|
107
|
+
const registry = await buildRegistry(level, io, deps);
|
|
108
|
+
for (const provider of registry.list()) {
|
|
109
|
+
const key = await registry.resolveApiKey(provider.id);
|
|
110
|
+
const text = key.apiKey !== undefined
|
|
111
|
+
? `${key.source}${key.origin !== undefined ? `(${key.origin})` : ""}`
|
|
112
|
+
: provider.requiresApiKey
|
|
113
|
+
? "无"
|
|
114
|
+
: "无需";
|
|
115
|
+
report.item(`${provider.id.padEnd(20)} ${text}`);
|
|
116
|
+
const env = baseUrlEnvOf(registry, provider.id);
|
|
117
|
+
if (env !== undefined)
|
|
118
|
+
report.item(`${"".padEnd(20)} baseUrl 来自环境变量 ${env}:${provider.baseUrl}(compat 按保守缺省)`);
|
|
119
|
+
}
|
|
120
|
+
const model = await describeModel(level.merged.config, registry);
|
|
121
|
+
report.item(`将使用的模型:${model.ref ?? "(无)"}(${model.reason})`);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
report.problem(`供应商注册表:${error.message}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// 未装配注册表:按文件与环境变量名列出能看到的来源。
|
|
129
|
+
try {
|
|
130
|
+
for (const [id, entry] of Object.entries(readAuthFile(level.authFile).file.providers)) {
|
|
131
|
+
report.item(`${id.padEnd(20)} auth-file(${classifyKeyValue(entry.apiKey)})`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
// 上面已报告
|
|
136
|
+
}
|
|
137
|
+
for (const [id, provider] of Object.entries(level.merged.config.providers ?? {})) {
|
|
138
|
+
if (provider.apiKey !== undefined) {
|
|
139
|
+
report.item(`${id.padEnd(20)} config(${classifyKeyValue(provider.apiKey)})`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const envNames = Object.keys(io.env)
|
|
143
|
+
.filter((n) => /_API_KEY$/.test(n) || n.startsWith("AMA_API_KEY_"))
|
|
144
|
+
.filter((n) => (io.env[n] ?? "") !== "")
|
|
145
|
+
.sort();
|
|
146
|
+
report.item(`环境变量:${envNames.length > 0 ? envNames.join(", ") : "(无)"}`);
|
|
147
|
+
}
|
|
148
|
+
function hookSection(report, io, level, trusted) {
|
|
149
|
+
report.section("Hook");
|
|
150
|
+
try {
|
|
151
|
+
const hooks = loadHookConfigs({
|
|
152
|
+
configDir: level.configDir,
|
|
153
|
+
cwd: io.cwd,
|
|
154
|
+
profileHooksFile: level.profile?.hooksFile,
|
|
155
|
+
trusted,
|
|
156
|
+
defaultTimeoutMs: level.merged.config.hooks?.timeoutMs,
|
|
157
|
+
});
|
|
158
|
+
if (hooks.hooks.length === 0)
|
|
159
|
+
report.item("(无)");
|
|
160
|
+
for (const hook of hooks.hooks) {
|
|
161
|
+
const matcher = hook.matcher !== undefined ? ` [${hook.matcher}]` : "";
|
|
162
|
+
report.item(`${hook.source.padEnd(7)} ${hook.event}${matcher} → ${hook.command}(${hook.timeoutMs} ms)`);
|
|
163
|
+
}
|
|
164
|
+
if (hooks.skippedProject !== undefined) {
|
|
165
|
+
report.item(`未信任,跳过:${hooks.skippedProject}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
report.problem(error.message);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
function terminalSection(report, io) {
|
|
173
|
+
report.section("终端");
|
|
174
|
+
const env = io.env;
|
|
175
|
+
report.item(`stdin TTY:${io.stdinIsTTY ? "是" : "否"} · stdout TTY:${io.stdoutIsTTY ? "是" : "否"}`);
|
|
176
|
+
report.item(`TERM=${env["TERM"] ?? "(未设置)"} · COLORTERM=${env["COLORTERM"] ?? "(未设置)"}`);
|
|
177
|
+
if (env["NO_COLOR"] !== undefined)
|
|
178
|
+
report.item("NO_COLOR 已设置:不输出颜色");
|
|
179
|
+
if (env["TMUX"] !== undefined)
|
|
180
|
+
report.item("在 tmux 中运行");
|
|
181
|
+
const columns = process.stdout.columns;
|
|
182
|
+
if (io.stdoutIsTTY && columns !== undefined)
|
|
183
|
+
report.item(`尺寸:${columns}×${process.stdout.rows}`);
|
|
184
|
+
const tui = io.stdinIsTTY && io.stdoutIsTTY && env["TERM"] !== "dumb";
|
|
185
|
+
report.item(`缺省界面:${tui ? "终端界面" : "行式(--no-tui 等价)"}`);
|
|
186
|
+
}
|
|
187
|
+
export async function runDoctor(argv, io, deps) {
|
|
188
|
+
const { values, flags } = parseSubArgs(argv, ["profile", "auth-file"], ["trust", "no-trust"]);
|
|
189
|
+
if (flags.has("help")) {
|
|
190
|
+
io.stdout(DOCTOR_USAGE);
|
|
191
|
+
return ExitCode.Ok;
|
|
192
|
+
}
|
|
193
|
+
const report = new Report();
|
|
194
|
+
report.section(`ama ${AMA_VERSION} · Node ${process.version} · ${process.platform}-${process.arch}`);
|
|
195
|
+
let level;
|
|
196
|
+
try {
|
|
197
|
+
level = loadUserLevel(io, {
|
|
198
|
+
profile: values.get("profile"),
|
|
199
|
+
authFile: values.get("auth-file"),
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
report.problem(error.message);
|
|
204
|
+
io.stdout(report.text());
|
|
205
|
+
return ExitCode.Config;
|
|
206
|
+
}
|
|
207
|
+
report.section("目录");
|
|
208
|
+
report.item(`配置目录:${level.configDir}`);
|
|
209
|
+
report.item(`数据目录:${level.dataDir}${existsSync(level.dataDir) ? "" : "(尚未创建)"}`);
|
|
210
|
+
report.section("配置层级(缺省 ← 用户级 ← profile ← 项目级(只能收紧)← 命令行)");
|
|
211
|
+
report.item("缺省:内置");
|
|
212
|
+
probeLine(report, "用户级", "config", level.userConfigPath);
|
|
213
|
+
if (level.profile !== undefined) {
|
|
214
|
+
report.item(`profile:${level.profile.path}`);
|
|
215
|
+
if (level.profile.configFile !== undefined)
|
|
216
|
+
probeLine(report, "profile.config", "config", level.profile.configFile);
|
|
217
|
+
}
|
|
218
|
+
const projectPath = projectFile(io.cwd, CONFIG_FILE);
|
|
219
|
+
probeLine(report, "项目级", "config", projectPath);
|
|
220
|
+
try {
|
|
221
|
+
const project = loadConfigFile("config", projectPath);
|
|
222
|
+
const merged = mergeProjectAndCli(level.merged, project?.value, undefined);
|
|
223
|
+
for (const warning of merged.warnings)
|
|
224
|
+
report.item(` 收紧:${warning}`);
|
|
225
|
+
report.item(`有效权限模式:${merged.config.permission?.mode ?? "default"}`);
|
|
226
|
+
}
|
|
227
|
+
catch {
|
|
228
|
+
// probeLine 已报告
|
|
229
|
+
}
|
|
230
|
+
for (const warning of level.warnings)
|
|
231
|
+
report.item(`警告:${warning}`);
|
|
232
|
+
const flag = flags.has("trust") ? true : flags.has("no-trust") ? false : undefined;
|
|
233
|
+
const trusted = trustSection(report, io, level, flag);
|
|
234
|
+
report.section("上下文文件(AGENTS.md,外层在前)");
|
|
235
|
+
const context = findContextFiles({ cwd: io.cwd, configDir: level.configDir });
|
|
236
|
+
if (context.files.length === 0)
|
|
237
|
+
report.item("(无)");
|
|
238
|
+
for (const file of context.files)
|
|
239
|
+
report.item(`${file.scope === "user" ? "用户级" : "项目"} ${file.path}`);
|
|
240
|
+
await keySection(report, io, level, deps);
|
|
241
|
+
hookSection(report, io, level, trusted);
|
|
242
|
+
report.item(`用户级 hooks.json:${userFile(level, HOOKS_FILE)}`);
|
|
243
|
+
terminalSection(report, io);
|
|
244
|
+
io.stdout(report.text());
|
|
245
|
+
return report.problems > 0 ? ExitCode.Config : ExitCode.Ok;
|
|
246
|
+
}
|