@namzu/cli 0.0.2 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +110 -0
- package/dist/bin.js +4 -32
- package/dist/bin.js.map +1 -1
- package/dist/cli.d.ts +14 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +124 -0
- package/dist/cli.js.map +1 -0
- package/dist/cli.test.d.ts +2 -0
- package/dist/cli.test.d.ts.map +1 -0
- package/dist/cli.test.js +94 -0
- package/dist/cli.test.js.map +1 -0
- package/dist/commands/doctor.d.ts +6 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +10 -0
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/providers.d.ts +17 -0
- package/dist/commands/providers.d.ts.map +1 -0
- package/dist/commands/providers.js +274 -0
- package/dist/commands/providers.js.map +1 -0
- package/dist/commands/registry.d.ts +11 -0
- package/dist/commands/registry.d.ts.map +1 -0
- package/dist/commands/registry.js +28 -0
- package/dist/commands/registry.js.map +1 -0
- package/dist/commands/run.d.ts +14 -0
- package/dist/commands/run.d.ts.map +1 -0
- package/dist/commands/run.js +73 -0
- package/dist/commands/run.js.map +1 -0
- package/dist/commands/stubs.d.ts +12 -0
- package/dist/commands/stubs.d.ts.map +1 -0
- package/dist/commands/stubs.js +32 -0
- package/dist/commands/stubs.js.map +1 -0
- package/dist/commands/tools.d.ts +16 -0
- package/dist/commands/tools.d.ts.map +1 -0
- package/dist/commands/tools.js +161 -0
- package/dist/commands/tools.js.map +1 -0
- package/dist/commands/types.d.ts +25 -0
- package/dist/commands/types.d.ts.map +1 -0
- package/dist/commands/types.js +2 -0
- package/dist/commands/types.js.map +1 -0
- package/dist/config/load.d.ts +25 -0
- package/dist/config/load.d.ts.map +1 -0
- package/dist/config/load.js +92 -0
- package/dist/config/load.js.map +1 -0
- package/dist/config/load.test.d.ts +2 -0
- package/dist/config/load.test.d.ts.map +1 -0
- package/dist/config/load.test.js +57 -0
- package/dist/config/load.test.js.map +1 -0
- package/dist/config/schema.d.ts +29 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +13 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/doctor/registry.d.ts.map +1 -1
- package/dist/doctor/registry.js +3 -1
- package/dist/doctor/registry.js.map +1 -1
- package/dist/doctor/registry.test.js +4 -1
- package/dist/doctor/registry.test.js.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/integrations/clawtool/agents.d.ts +31 -0
- package/dist/integrations/clawtool/agents.d.ts.map +1 -0
- package/dist/integrations/clawtool/agents.js +30 -0
- package/dist/integrations/clawtool/agents.js.map +1 -0
- package/dist/integrations/clawtool/agents.test.d.ts +2 -0
- package/dist/integrations/clawtool/agents.test.d.ts.map +1 -0
- package/dist/integrations/clawtool/agents.test.js +79 -0
- package/dist/integrations/clawtool/agents.test.js.map +1 -0
- package/dist/integrations/clawtool/auth.d.ts +26 -0
- package/dist/integrations/clawtool/auth.d.ts.map +1 -0
- package/dist/integrations/clawtool/auth.js +56 -0
- package/dist/integrations/clawtool/auth.js.map +1 -0
- package/dist/integrations/clawtool/auth.test.d.ts +2 -0
- package/dist/integrations/clawtool/auth.test.d.ts.map +1 -0
- package/dist/integrations/clawtool/auth.test.js +40 -0
- package/dist/integrations/clawtool/auth.test.js.map +1 -0
- package/dist/integrations/clawtool/binary.d.ts +25 -0
- package/dist/integrations/clawtool/binary.d.ts.map +1 -0
- package/dist/integrations/clawtool/binary.js +50 -0
- package/dist/integrations/clawtool/binary.js.map +1 -0
- package/dist/integrations/clawtool/binary.test.d.ts +2 -0
- package/dist/integrations/clawtool/binary.test.d.ts.map +1 -0
- package/dist/integrations/clawtool/binary.test.js +42 -0
- package/dist/integrations/clawtool/binary.test.js.map +1 -0
- package/dist/integrations/clawtool/daemon.d.ts +37 -0
- package/dist/integrations/clawtool/daemon.d.ts.map +1 -0
- package/dist/integrations/clawtool/daemon.js +109 -0
- package/dist/integrations/clawtool/daemon.js.map +1 -0
- package/dist/integrations/clawtool/daemon.test.d.ts +11 -0
- package/dist/integrations/clawtool/daemon.test.d.ts.map +1 -0
- package/dist/integrations/clawtool/daemon.test.js +118 -0
- package/dist/integrations/clawtool/daemon.test.js.map +1 -0
- package/dist/integrations/clawtool/dispatch.d.ts +33 -0
- package/dist/integrations/clawtool/dispatch.d.ts.map +1 -0
- package/dist/integrations/clawtool/dispatch.js +155 -0
- package/dist/integrations/clawtool/dispatch.js.map +1 -0
- package/dist/integrations/clawtool/dispatch.test.d.ts +2 -0
- package/dist/integrations/clawtool/dispatch.test.d.ts.map +1 -0
- package/dist/integrations/clawtool/dispatch.test.js +138 -0
- package/dist/integrations/clawtool/dispatch.test.js.map +1 -0
- package/dist/integrations/clawtool/index.d.ts +11 -0
- package/dist/integrations/clawtool/index.d.ts.map +1 -0
- package/dist/integrations/clawtool/index.js +10 -0
- package/dist/integrations/clawtool/index.js.map +1 -0
- package/dist/integrations/clawtool/mcp.d.ts +51 -0
- package/dist/integrations/clawtool/mcp.d.ts.map +1 -0
- package/dist/integrations/clawtool/mcp.js +156 -0
- package/dist/integrations/clawtool/mcp.js.map +1 -0
- package/dist/integrations/clawtool/mcp.test.d.ts +2 -0
- package/dist/integrations/clawtool/mcp.test.d.ts.map +1 -0
- package/dist/integrations/clawtool/mcp.test.js +126 -0
- package/dist/integrations/clawtool/mcp.test.js.map +1 -0
- package/dist/integrations/clawtool/paths.d.ts +8 -0
- package/dist/integrations/clawtool/paths.d.ts.map +1 -0
- package/dist/integrations/clawtool/paths.js +19 -0
- package/dist/integrations/clawtool/paths.js.map +1 -0
- package/dist/integrations/clawtool/peers.d.ts +67 -0
- package/dist/integrations/clawtool/peers.d.ts.map +1 -0
- package/dist/integrations/clawtool/peers.js +150 -0
- package/dist/integrations/clawtool/peers.js.map +1 -0
- package/dist/integrations/clawtool/plugin.d.ts +42 -0
- package/dist/integrations/clawtool/plugin.d.ts.map +1 -0
- package/dist/integrations/clawtool/plugin.js +38 -0
- package/dist/integrations/clawtool/plugin.js.map +1 -0
- package/dist/integrations/clawtool/state.d.ts +11 -0
- package/dist/integrations/clawtool/state.d.ts.map +1 -0
- package/dist/integrations/clawtool/state.js +41 -0
- package/dist/integrations/clawtool/state.js.map +1 -0
- package/dist/integrations/clawtool/state.test.d.ts +2 -0
- package/dist/integrations/clawtool/state.test.d.ts.map +1 -0
- package/dist/integrations/clawtool/state.test.js +38 -0
- package/dist/integrations/clawtool/state.test.js.map +1 -0
- package/dist/integrations/clawtool/tooling.d.ts +50 -0
- package/dist/integrations/clawtool/tooling.d.ts.map +1 -0
- package/dist/integrations/clawtool/tooling.js +89 -0
- package/dist/integrations/clawtool/tooling.js.map +1 -0
- package/dist/integrations/clawtool/tooling.test.d.ts +2 -0
- package/dist/integrations/clawtool/tooling.test.d.ts.map +1 -0
- package/dist/integrations/clawtool/tooling.test.js +58 -0
- package/dist/integrations/clawtool/tooling.test.js.map +1 -0
- package/dist/integrations/clawtool/types.d.ts +41 -0
- package/dist/integrations/clawtool/types.d.ts.map +1 -0
- package/dist/integrations/clawtool/types.js +9 -0
- package/dist/integrations/clawtool/types.js.map +1 -0
- package/dist/integrations/clipboard/image.d.ts +18 -0
- package/dist/integrations/clipboard/image.d.ts.map +1 -0
- package/dist/integrations/clipboard/image.js +88 -0
- package/dist/integrations/clipboard/image.js.map +1 -0
- package/dist/integrations/providers/discover.d.ts +68 -0
- package/dist/integrations/providers/discover.d.ts.map +1 -0
- package/dist/integrations/providers/discover.js +108 -0
- package/dist/integrations/providers/discover.js.map +1 -0
- package/dist/integrations/providers/discover.test.d.ts +2 -0
- package/dist/integrations/providers/discover.test.d.ts.map +1 -0
- package/dist/integrations/providers/discover.test.js +120 -0
- package/dist/integrations/providers/discover.test.js.map +1 -0
- package/dist/integrations/providers/index.d.ts +10 -0
- package/dist/integrations/providers/index.d.ts.map +1 -0
- package/dist/integrations/providers/index.js +12 -0
- package/dist/integrations/providers/index.js.map +1 -0
- package/dist/integrations/providers/keychain.d.ts +44 -0
- package/dist/integrations/providers/keychain.d.ts.map +1 -0
- package/dist/integrations/providers/keychain.js +154 -0
- package/dist/integrations/providers/keychain.js.map +1 -0
- package/dist/integrations/providers/keychain.test.d.ts +2 -0
- package/dist/integrations/providers/keychain.test.d.ts.map +1 -0
- package/dist/integrations/providers/keychain.test.js +21 -0
- package/dist/integrations/providers/keychain.test.js.map +1 -0
- package/dist/integrations/providers/mask.d.ts +7 -0
- package/dist/integrations/providers/mask.d.ts.map +1 -0
- package/dist/integrations/providers/mask.js +14 -0
- package/dist/integrations/providers/mask.js.map +1 -0
- package/dist/integrations/providers/mask.test.d.ts +2 -0
- package/dist/integrations/providers/mask.test.d.ts.map +1 -0
- package/dist/integrations/providers/mask.test.js +20 -0
- package/dist/integrations/providers/mask.test.js.map +1 -0
- package/dist/integrations/providers/oauth.d.ts +28 -0
- package/dist/integrations/providers/oauth.d.ts.map +1 -0
- package/dist/integrations/providers/oauth.js +65 -0
- package/dist/integrations/providers/oauth.js.map +1 -0
- package/dist/integrations/providers/oauth.test.d.ts +2 -0
- package/dist/integrations/providers/oauth.test.d.ts.map +1 -0
- package/dist/integrations/providers/oauth.test.js +86 -0
- package/dist/integrations/providers/oauth.test.js.map +1 -0
- package/dist/integrations/providers/preferences.d.ts +43 -0
- package/dist/integrations/providers/preferences.d.ts.map +1 -0
- package/dist/integrations/providers/preferences.js +111 -0
- package/dist/integrations/providers/preferences.js.map +1 -0
- package/dist/integrations/providers/preferences.test.d.ts +2 -0
- package/dist/integrations/providers/preferences.test.d.ts.map +1 -0
- package/dist/integrations/providers/preferences.test.js +68 -0
- package/dist/integrations/providers/preferences.test.js.map +1 -0
- package/dist/integrations/providers/registry.d.ts +31 -0
- package/dist/integrations/providers/registry.d.ts.map +1 -0
- package/dist/integrations/providers/registry.js +79 -0
- package/dist/integrations/providers/registry.js.map +1 -0
- package/dist/integrations/providers/schema.d.ts +73 -0
- package/dist/integrations/providers/schema.d.ts.map +1 -0
- package/dist/integrations/providers/schema.js +70 -0
- package/dist/integrations/providers/schema.js.map +1 -0
- package/dist/integrations/providers/schema.test.d.ts +2 -0
- package/dist/integrations/providers/schema.test.d.ts.map +1 -0
- package/dist/integrations/providers/schema.test.js +43 -0
- package/dist/integrations/providers/schema.test.js.map +1 -0
- package/dist/integrations/providers/secrets.d.ts +37 -0
- package/dist/integrations/providers/secrets.d.ts.map +1 -0
- package/dist/integrations/providers/secrets.js +69 -0
- package/dist/integrations/providers/secrets.js.map +1 -0
- package/dist/integrations/providers/store.d.ts +32 -0
- package/dist/integrations/providers/store.d.ts.map +1 -0
- package/dist/integrations/providers/store.js +133 -0
- package/dist/integrations/providers/store.js.map +1 -0
- package/dist/integrations/providers/store.test.d.ts +2 -0
- package/dist/integrations/providers/store.test.d.ts.map +1 -0
- package/dist/integrations/providers/store.test.js +127 -0
- package/dist/integrations/providers/store.test.js.map +1 -0
- package/dist/integrations/sessions/store.d.ts +40 -0
- package/dist/integrations/sessions/store.d.ts.map +1 -0
- package/dist/integrations/sessions/store.js +90 -0
- package/dist/integrations/sessions/store.js.map +1 -0
- package/dist/integrations/subagents/runtime.d.ts +37 -0
- package/dist/integrations/subagents/runtime.d.ts.map +1 -0
- package/dist/integrations/subagents/runtime.js +183 -0
- package/dist/integrations/subagents/runtime.js.map +1 -0
- package/dist/integrations/trust/store.d.ts +16 -0
- package/dist/integrations/trust/store.d.ts.map +1 -0
- package/dist/integrations/trust/store.js +59 -0
- package/dist/integrations/trust/store.js.map +1 -0
- package/dist/integrations/trust/store.test.d.ts +2 -0
- package/dist/integrations/trust/store.test.d.ts.map +1 -0
- package/dist/integrations/trust/store.test.js +50 -0
- package/dist/integrations/trust/store.test.js.map +1 -0
- package/dist/integrations/updates.d.ts +27 -0
- package/dist/integrations/updates.d.ts.map +1 -0
- package/dist/integrations/updates.js +99 -0
- package/dist/integrations/updates.js.map +1 -0
- package/dist/memory/store.d.ts +29 -0
- package/dist/memory/store.d.ts.map +1 -0
- package/dist/memory/store.js +74 -0
- package/dist/memory/store.js.map +1 -0
- package/dist/memory/store.test.d.ts +2 -0
- package/dist/memory/store.test.d.ts.map +1 -0
- package/dist/memory/store.test.js +63 -0
- package/dist/memory/store.test.js.map +1 -0
- package/dist/output/formatter.d.ts +30 -0
- package/dist/output/formatter.d.ts.map +1 -0
- package/dist/output/formatter.js +16 -0
- package/dist/output/formatter.js.map +1 -0
- package/dist/output/formatter.test.d.ts +2 -0
- package/dist/output/formatter.test.d.ts.map +1 -0
- package/dist/output/formatter.test.js +88 -0
- package/dist/output/formatter.test.js.map +1 -0
- package/dist/output/index.d.ts +5 -0
- package/dist/output/index.d.ts.map +1 -0
- package/dist/output/index.js +22 -0
- package/dist/output/index.js.map +1 -0
- package/dist/output/json.d.ts +13 -0
- package/dist/output/json.d.ts.map +1 -0
- package/dist/output/json.js +51 -0
- package/dist/output/json.js.map +1 -0
- package/dist/output/text.d.ts +13 -0
- package/dist/output/text.d.ts.map +1 -0
- package/dist/output/text.js +36 -0
- package/dist/output/text.js.map +1 -0
- package/dist/output/yaml.d.ts +13 -0
- package/dist/output/yaml.d.ts.map +1 -0
- package/dist/output/yaml.js +26 -0
- package/dist/output/yaml.js.map +1 -0
- package/dist/skills/store.d.ts +49 -0
- package/dist/skills/store.d.ts.map +1 -0
- package/dist/skills/store.js +109 -0
- package/dist/skills/store.js.map +1 -0
- package/dist/skills/store.test.d.ts +2 -0
- package/dist/skills/store.test.d.ts.map +1 -0
- package/dist/skills/store.test.js +80 -0
- package/dist/skills/store.test.js.map +1 -0
- package/dist/tui/App.d.ts +18 -0
- package/dist/tui/App.d.ts.map +1 -0
- package/dist/tui/App.js +742 -0
- package/dist/tui/App.js.map +1 -0
- package/dist/tui/Composer.d.ts +17 -0
- package/dist/tui/Composer.d.ts.map +1 -0
- package/dist/tui/Composer.js +123 -0
- package/dist/tui/Composer.js.map +1 -0
- package/dist/tui/LiveActivity.d.ts +22 -0
- package/dist/tui/LiveActivity.d.ts.map +1 -0
- package/dist/tui/LiveActivity.js +46 -0
- package/dist/tui/LiveActivity.js.map +1 -0
- package/dist/tui/Markdown.d.ts +18 -0
- package/dist/tui/Markdown.d.ts.map +1 -0
- package/dist/tui/Markdown.js +68 -0
- package/dist/tui/Markdown.js.map +1 -0
- package/dist/tui/PermissionOverlay.d.ts +14 -0
- package/dist/tui/PermissionOverlay.d.ts.map +1 -0
- package/dist/tui/PermissionOverlay.js +22 -0
- package/dist/tui/PermissionOverlay.js.map +1 -0
- package/dist/tui/Picker.d.ts +20 -0
- package/dist/tui/Picker.d.ts.map +1 -0
- package/dist/tui/Picker.js +72 -0
- package/dist/tui/Picker.js.map +1 -0
- package/dist/tui/ResumePicker.d.ts +12 -0
- package/dist/tui/ResumePicker.d.ts.map +1 -0
- package/dist/tui/ResumePicker.js +26 -0
- package/dist/tui/ResumePicker.js.map +1 -0
- package/dist/tui/StatusBar.d.ts +22 -0
- package/dist/tui/StatusBar.d.ts.map +1 -0
- package/dist/tui/StatusBar.js +73 -0
- package/dist/tui/StatusBar.js.map +1 -0
- package/dist/tui/Transcript.d.ts +29 -0
- package/dist/tui/Transcript.d.ts.map +1 -0
- package/dist/tui/Transcript.js +105 -0
- package/dist/tui/Transcript.js.map +1 -0
- package/dist/tui/TrustPrompt.d.ts +11 -0
- package/dist/tui/TrustPrompt.d.ts.map +1 -0
- package/dist/tui/TrustPrompt.js +13 -0
- package/dist/tui/TrustPrompt.js.map +1 -0
- package/dist/tui/agent.d.ts +163 -0
- package/dist/tui/agent.d.ts.map +1 -0
- package/dist/tui/agent.js +684 -0
- package/dist/tui/agent.js.map +1 -0
- package/dist/tui/agent.test.d.ts +2 -0
- package/dist/tui/agent.test.d.ts.map +1 -0
- package/dist/tui/agent.test.js +225 -0
- package/dist/tui/agent.test.js.map +1 -0
- package/dist/tui/index.d.ts +7 -0
- package/dist/tui/index.d.ts.map +1 -0
- package/dist/tui/index.js +29 -0
- package/dist/tui/index.js.map +1 -0
- package/dist/tui/logo.d.ts +23 -0
- package/dist/tui/logo.d.ts.map +1 -0
- package/dist/tui/logo.js +35 -0
- package/dist/tui/logo.js.map +1 -0
- package/dist/tui/markdownParser.d.ts +45 -0
- package/dist/tui/markdownParser.d.ts.map +1 -0
- package/dist/tui/markdownParser.js +127 -0
- package/dist/tui/markdownParser.js.map +1 -0
- package/dist/tui/markdownParser.test.d.ts +2 -0
- package/dist/tui/markdownParser.test.d.ts.map +1 -0
- package/dist/tui/markdownParser.test.js +90 -0
- package/dist/tui/markdownParser.test.js.map +1 -0
- package/dist/tui/mentions.d.ts +22 -0
- package/dist/tui/mentions.d.ts.map +1 -0
- package/dist/tui/mentions.js +59 -0
- package/dist/tui/mentions.js.map +1 -0
- package/dist/tui/mentions.test.d.ts +2 -0
- package/dist/tui/mentions.test.d.ts.map +1 -0
- package/dist/tui/mentions.test.js +35 -0
- package/dist/tui/mentions.test.js.map +1 -0
- package/dist/tui/slashCommands.d.ts +64 -0
- package/dist/tui/slashCommands.d.ts.map +1 -0
- package/dist/tui/slashCommands.js +153 -0
- package/dist/tui/slashCommands.js.map +1 -0
- package/dist/tui/slashCommands.test.d.ts +2 -0
- package/dist/tui/slashCommands.test.d.ts.map +1 -0
- package/dist/tui/slashCommands.test.js +114 -0
- package/dist/tui/slashCommands.test.js.map +1 -0
- package/dist/tui/theme.d.ts +34 -0
- package/dist/tui/theme.d.ts.map +1 -0
- package/dist/tui/theme.js +32 -0
- package/dist/tui/theme.js.map +1 -0
- package/dist/tui/types.d.ts +27 -0
- package/dist/tui/types.d.ts.map +1 -0
- package/dist/tui/types.js +7 -0
- package/dist/tui/types.js.map +1 -0
- package/package.json +67 -57
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential discoverer for LLM provider clients.
|
|
3
|
+
*
|
|
4
|
+
* For each entry in `PROVIDER_REGISTRY`, ask three questions in order:
|
|
5
|
+
* 1. Is one of its env vars set in `process.env`?
|
|
6
|
+
* 2. Does clawtool's secrets.toml carry one of its env vars?
|
|
7
|
+
* 3. Is the probe URL (if any) reachable right now?
|
|
8
|
+
*
|
|
9
|
+
* The first positive answer per provider wins; subsequent sources are
|
|
10
|
+
* recorded as "also available from" so the picker can show alternatives
|
|
11
|
+
* (e.g. anthropic via env, also available in `[secrets.personal]`).
|
|
12
|
+
*
|
|
13
|
+
* Discovery is non-throwing. Network probes have short timeouts. The
|
|
14
|
+
* picker can render immediately and refine if discovery completes later.
|
|
15
|
+
*/
|
|
16
|
+
import { type ProviderId, type ProviderRegistryEntry } from './registry.js';
|
|
17
|
+
export type DetectionSource = {
|
|
18
|
+
readonly kind: 'env';
|
|
19
|
+
readonly envName: string;
|
|
20
|
+
} | {
|
|
21
|
+
readonly kind: 'secrets-toml';
|
|
22
|
+
readonly envName: string;
|
|
23
|
+
readonly scope: string;
|
|
24
|
+
} | {
|
|
25
|
+
readonly kind: 'probe';
|
|
26
|
+
readonly url: string;
|
|
27
|
+
} | {
|
|
28
|
+
readonly kind: 'keychain';
|
|
29
|
+
readonly service: string;
|
|
30
|
+
};
|
|
31
|
+
export interface DetectedProvider {
|
|
32
|
+
readonly entry: ProviderRegistryEntry;
|
|
33
|
+
/** First positive source (used by default). */
|
|
34
|
+
readonly source: DetectionSource;
|
|
35
|
+
/** Resolved API key, if the source carried one. */
|
|
36
|
+
readonly apiKey?: string;
|
|
37
|
+
/** Resolved base URL (overrides the registry default if set). */
|
|
38
|
+
readonly baseUrl?: string;
|
|
39
|
+
/**
|
|
40
|
+
* OAuth refresh metadata, present only when `apiKey` is an OAuth access
|
|
41
|
+
* token carrying a refresh token + expiry (the Claude Code Keychain). Lets
|
|
42
|
+
* the session layer renew a lapsed token instead of failing with a 401.
|
|
43
|
+
*/
|
|
44
|
+
readonly oauth?: {
|
|
45
|
+
readonly refreshToken?: string;
|
|
46
|
+
readonly expiresAt?: number;
|
|
47
|
+
};
|
|
48
|
+
/** Other sources that also satisfy this provider — informational. */
|
|
49
|
+
readonly alternatives: readonly DetectionSource[];
|
|
50
|
+
}
|
|
51
|
+
export interface DiscoverOptions {
|
|
52
|
+
/** Override `process.env` for tests. */
|
|
53
|
+
readonly env?: NodeJS.ProcessEnv;
|
|
54
|
+
/** Override `homedir()` for tests (read clawtool secrets from a fixture). */
|
|
55
|
+
readonly home?: string;
|
|
56
|
+
/** Override the fetch impl for probe URLs (tests inject a mock). */
|
|
57
|
+
readonly fetch?: typeof fetch;
|
|
58
|
+
/** Probe deadline in ms (default 500). */
|
|
59
|
+
readonly probeTimeoutMs?: number;
|
|
60
|
+
/** Skip network probes entirely (tests, offline mode). */
|
|
61
|
+
readonly skipProbes?: boolean;
|
|
62
|
+
/** Skip the macOS Keychain read (tests, non-darwin runs). */
|
|
63
|
+
readonly skipKeychain?: boolean;
|
|
64
|
+
}
|
|
65
|
+
export declare function discoverProviders(opts?: DiscoverOptions): Promise<readonly DetectedProvider[]>;
|
|
66
|
+
/** Resolve a single detected provider by id from the discovered list. */
|
|
67
|
+
export declare function findDetected(list: readonly DetectedProvider[], id: ProviderId): DetectedProvider | null;
|
|
68
|
+
//# sourceMappingURL=discover.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discover.d.ts","sourceRoot":"","sources":["../../../src/integrations/providers/discover.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,EAAqB,KAAK,UAAU,EAAE,KAAK,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAG9F,MAAM,MAAM,eAAe,GACxB;IAAE,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAClD;IAAE,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACnF;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAChD;IAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAA;AAE1D,MAAM,WAAW,gBAAgB;IAChC,QAAQ,CAAC,KAAK,EAAE,qBAAqB,CAAA;IACrC,+CAA+C;IAC/C,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAA;IAChC,mDAAmD;IACnD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,iEAAiE;IACjE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IACzB;;;;OAIG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE;QAAE,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAChF,qEAAqE;IACrE,QAAQ,CAAC,YAAY,EAAE,SAAS,eAAe,EAAE,CAAA;CACjD;AAED,MAAM,WAAW,eAAe;IAC/B,wCAAwC;IACxC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAA;IAChC,6EAA6E;IAC7E,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,oEAAoE;IACpE,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAA;IAC7B,0CAA0C;IAC1C,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAA;IAChC,0DAA0D;IAC1D,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAA;IAC7B,6DAA6D;IAC7D,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,CAAA;CAC/B;AAID,wBAAsB,iBAAiB,CACtC,IAAI,GAAE,eAAoB,GACxB,OAAO,CAAC,SAAS,gBAAgB,EAAE,CAAC,CAmEtC;AAmBD,yEAAyE;AACzE,wBAAgB,YAAY,CAC3B,IAAI,EAAE,SAAS,gBAAgB,EAAE,EACjC,EAAE,EAAE,UAAU,GACZ,gBAAgB,GAAG,IAAI,CAEzB"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential discoverer for LLM provider clients.
|
|
3
|
+
*
|
|
4
|
+
* For each entry in `PROVIDER_REGISTRY`, ask three questions in order:
|
|
5
|
+
* 1. Is one of its env vars set in `process.env`?
|
|
6
|
+
* 2. Does clawtool's secrets.toml carry one of its env vars?
|
|
7
|
+
* 3. Is the probe URL (if any) reachable right now?
|
|
8
|
+
*
|
|
9
|
+
* The first positive answer per provider wins; subsequent sources are
|
|
10
|
+
* recorded as "also available from" so the picker can show alternatives
|
|
11
|
+
* (e.g. anthropic via env, also available in `[secrets.personal]`).
|
|
12
|
+
*
|
|
13
|
+
* Discovery is non-throwing. Network probes have short timeouts. The
|
|
14
|
+
* picker can render immediately and refine if discovery completes later.
|
|
15
|
+
*/
|
|
16
|
+
import { readClaudeCodeKeychainCredential } from './keychain.js';
|
|
17
|
+
import { PROVIDER_REGISTRY } from './registry.js';
|
|
18
|
+
import { readClawtoolSecrets } from './secrets.js';
|
|
19
|
+
const DEFAULT_PROBE_TIMEOUT_MS = 500;
|
|
20
|
+
export async function discoverProviders(opts = {}) {
|
|
21
|
+
const env = opts.env ?? process.env;
|
|
22
|
+
const secrets = readClawtoolSecrets(opts.home);
|
|
23
|
+
const detected = [];
|
|
24
|
+
// macOS-only: read Claude Code's OAuth credential from the login
|
|
25
|
+
// Keychain once. Only anthropic consumes it, but we scan up front so
|
|
26
|
+
// the loop body stays uniform.
|
|
27
|
+
const claudeKeychain = opts.skipKeychain ? null : readClaudeCodeKeychainCredential();
|
|
28
|
+
for (const id of Object.keys(PROVIDER_REGISTRY)) {
|
|
29
|
+
const entry = PROVIDER_REGISTRY[id];
|
|
30
|
+
const sources = [];
|
|
31
|
+
let apiKey;
|
|
32
|
+
let oauth;
|
|
33
|
+
for (const envName of entry.envVars) {
|
|
34
|
+
const v = env[envName];
|
|
35
|
+
if (v && v.length > 0) {
|
|
36
|
+
if (apiKey === undefined)
|
|
37
|
+
apiKey = v;
|
|
38
|
+
sources.push({ kind: 'env', envName });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
for (const cand of secrets) {
|
|
42
|
+
if (entry.envVars.includes(cand.envName)) {
|
|
43
|
+
if (apiKey === undefined)
|
|
44
|
+
apiKey = cand.value;
|
|
45
|
+
sources.push({ kind: 'secrets-toml', envName: cand.envName, scope: cand.scope });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (id === 'anthropic' && claudeKeychain) {
|
|
49
|
+
if (apiKey === undefined)
|
|
50
|
+
apiKey = claudeKeychain.accessToken;
|
|
51
|
+
sources.push({ kind: 'keychain', service: 'Claude Code-credentials' });
|
|
52
|
+
// Only carry refresh metadata when the Keychain token is the one we'll
|
|
53
|
+
// actually use (an env/secrets token has no refresh path).
|
|
54
|
+
if (apiKey === claudeKeychain.accessToken) {
|
|
55
|
+
oauth = {
|
|
56
|
+
refreshToken: claudeKeychain.refreshToken,
|
|
57
|
+
expiresAt: claudeKeychain.expiresAt,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (sources.length === 0 && entry.probeUrl && !opts.skipProbes) {
|
|
62
|
+
const reachable = await probe(entry.probeUrl, opts);
|
|
63
|
+
if (reachable) {
|
|
64
|
+
sources.push({ kind: 'probe', url: entry.probeUrl });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (sources.length > 0) {
|
|
68
|
+
detected.push({
|
|
69
|
+
entry,
|
|
70
|
+
source: sources[0],
|
|
71
|
+
apiKey,
|
|
72
|
+
baseUrl: entry.defaultBaseUrl,
|
|
73
|
+
...(oauth ? { oauth } : {}),
|
|
74
|
+
alternatives: sources.slice(1),
|
|
75
|
+
});
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
// Probe-only providers (Ollama, LM Studio): include even when probe
|
|
79
|
+
// fails so the picker can show "(not running)" and the user knows
|
|
80
|
+
// they could start the server. Local providers with no apiKey need
|
|
81
|
+
// only the URL to be addressable.
|
|
82
|
+
if (!entry.requiresApiKey && entry.probeUrl && !opts.skipProbes === false) {
|
|
83
|
+
// `skipProbes === true` reaches this branch; surface as not-detected,
|
|
84
|
+
// no row in the picker, so user isn't confused by phantom entries.
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return detected;
|
|
88
|
+
}
|
|
89
|
+
async function probe(url, opts) {
|
|
90
|
+
const fetchFn = opts.fetch ?? globalThis.fetch;
|
|
91
|
+
const controller = new AbortController();
|
|
92
|
+
const timer = setTimeout(() => controller.abort(), opts.probeTimeoutMs ?? DEFAULT_PROBE_TIMEOUT_MS);
|
|
93
|
+
try {
|
|
94
|
+
const res = await fetchFn(url, { method: 'GET', signal: controller.signal });
|
|
95
|
+
return res.ok;
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
finally {
|
|
101
|
+
clearTimeout(timer);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/** Resolve a single detected provider by id from the discovered list. */
|
|
105
|
+
export function findDetected(list, id) {
|
|
106
|
+
return list.find((d) => d.entry.id === id) ?? null;
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=discover.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discover.js","sourceRoot":"","sources":["../../../src/integrations/providers/discover.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discover.test.d.ts","sourceRoot":"","sources":["../../../src/integrations/providers/discover.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { mkdirSync, mkdtempSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
5
|
+
import { discoverProviders, findDetected } from './discover.js';
|
|
6
|
+
function tmpHome() {
|
|
7
|
+
const home = mkdtempSync(join(tmpdir(), 'namzu-discover-'));
|
|
8
|
+
mkdirSync(join(home, '.config', 'clawtool'), { recursive: true });
|
|
9
|
+
return home;
|
|
10
|
+
}
|
|
11
|
+
// Every test must opt out of host-ambient sources (Keychain, network
|
|
12
|
+
// probes) so the suite stays hermetic regardless of who runs it. The
|
|
13
|
+
// keychain code path is covered by a focused unit test below.
|
|
14
|
+
const HERMETIC = { skipProbes: true, skipKeychain: true };
|
|
15
|
+
describe('discoverProviders — env-var scan', () => {
|
|
16
|
+
it('picks anthropic from ANTHROPIC_API_KEY', async () => {
|
|
17
|
+
const list = await discoverProviders({
|
|
18
|
+
...HERMETIC,
|
|
19
|
+
env: { ANTHROPIC_API_KEY: 'sk-ant-test' },
|
|
20
|
+
home: tmpHome(),
|
|
21
|
+
});
|
|
22
|
+
const anthropic = findDetected(list, 'anthropic');
|
|
23
|
+
expect(anthropic).not.toBeNull();
|
|
24
|
+
expect(anthropic?.apiKey).toBe('sk-ant-test');
|
|
25
|
+
expect(anthropic?.source.kind).toBe('env');
|
|
26
|
+
if (anthropic?.source.kind === 'env') {
|
|
27
|
+
expect(anthropic.source.envName).toBe('ANTHROPIC_API_KEY');
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
it('falls back to CLAUDE_CODE_OAUTH_TOKEN if no anthropic key set', async () => {
|
|
31
|
+
const list = await discoverProviders({
|
|
32
|
+
...HERMETIC,
|
|
33
|
+
env: { CLAUDE_CODE_OAUTH_TOKEN: 'oauth-tok' },
|
|
34
|
+
home: tmpHome(),
|
|
35
|
+
});
|
|
36
|
+
const anthropic = findDetected(list, 'anthropic');
|
|
37
|
+
expect(anthropic?.apiKey).toBe('oauth-tok');
|
|
38
|
+
if (anthropic?.source.kind === 'env') {
|
|
39
|
+
expect(anthropic.source.envName).toBe('CLAUDE_CODE_OAUTH_TOKEN');
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
it('returns empty list when no env + no secrets + no probes + no keychain', async () => {
|
|
43
|
+
const list = await discoverProviders({
|
|
44
|
+
...HERMETIC,
|
|
45
|
+
env: {},
|
|
46
|
+
home: tmpHome(),
|
|
47
|
+
});
|
|
48
|
+
expect(list).toHaveLength(0);
|
|
49
|
+
});
|
|
50
|
+
it('detects multiple providers in one scan', async () => {
|
|
51
|
+
const list = await discoverProviders({
|
|
52
|
+
...HERMETIC,
|
|
53
|
+
env: { ANTHROPIC_API_KEY: 'a', OPENAI_API_KEY: 'o' },
|
|
54
|
+
home: tmpHome(),
|
|
55
|
+
});
|
|
56
|
+
expect(findDetected(list, 'anthropic')).not.toBeNull();
|
|
57
|
+
expect(findDetected(list, 'openai')).not.toBeNull();
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
describe('discoverProviders — clawtool secrets.toml', () => {
|
|
61
|
+
it('reads anthropic key from clawtool secrets', async () => {
|
|
62
|
+
const home = tmpHome();
|
|
63
|
+
writeFileSync(join(home, '.config', 'clawtool', 'secrets.toml'), '[secrets.work]\nANTHROPIC_API_KEY = "sk-from-toml"\n');
|
|
64
|
+
const list = await discoverProviders({ ...HERMETIC, env: {}, home });
|
|
65
|
+
const anthropic = findDetected(list, 'anthropic');
|
|
66
|
+
expect(anthropic?.apiKey).toBe('sk-from-toml');
|
|
67
|
+
if (anthropic?.source.kind === 'secrets-toml') {
|
|
68
|
+
expect(anthropic.source.scope).toBe('work');
|
|
69
|
+
expect(anthropic.source.envName).toBe('ANTHROPIC_API_KEY');
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
it('env var wins over secrets.toml when both present', async () => {
|
|
73
|
+
const home = tmpHome();
|
|
74
|
+
writeFileSync(join(home, '.config', 'clawtool', 'secrets.toml'), '[secrets.work]\nANTHROPIC_API_KEY = "from-toml"\n');
|
|
75
|
+
const list = await discoverProviders({
|
|
76
|
+
...HERMETIC,
|
|
77
|
+
env: { ANTHROPIC_API_KEY: 'from-env' },
|
|
78
|
+
home,
|
|
79
|
+
});
|
|
80
|
+
const anthropic = findDetected(list, 'anthropic');
|
|
81
|
+
expect(anthropic?.apiKey).toBe('from-env');
|
|
82
|
+
expect(anthropic?.source.kind).toBe('env');
|
|
83
|
+
expect(anthropic?.alternatives.length).toBeGreaterThan(0);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
describe('discoverProviders — local probes', () => {
|
|
87
|
+
it('detects ollama when its probe URL is reachable', async () => {
|
|
88
|
+
const fetchMock = vi.fn().mockResolvedValue(new Response('', { status: 200 }));
|
|
89
|
+
const list = await discoverProviders({
|
|
90
|
+
skipKeychain: true,
|
|
91
|
+
env: {},
|
|
92
|
+
home: tmpHome(),
|
|
93
|
+
fetch: fetchMock,
|
|
94
|
+
});
|
|
95
|
+
const ollama = findDetected(list, 'ollama');
|
|
96
|
+
expect(ollama).not.toBeNull();
|
|
97
|
+
expect(ollama?.source.kind).toBe('probe');
|
|
98
|
+
});
|
|
99
|
+
it('does not include ollama when its probe fails', async () => {
|
|
100
|
+
const fetchMock = vi.fn().mockRejectedValue(new Error('ECONNREFUSED'));
|
|
101
|
+
const list = await discoverProviders({
|
|
102
|
+
skipKeychain: true,
|
|
103
|
+
env: {},
|
|
104
|
+
home: tmpHome(),
|
|
105
|
+
fetch: fetchMock,
|
|
106
|
+
});
|
|
107
|
+
expect(findDetected(list, 'ollama')).toBeNull();
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
describe('discoverProviders — http provider', () => {
|
|
111
|
+
it('is never auto-discovered (no envVars, no probe)', async () => {
|
|
112
|
+
const list = await discoverProviders({
|
|
113
|
+
...HERMETIC,
|
|
114
|
+
env: {},
|
|
115
|
+
home: tmpHome(),
|
|
116
|
+
});
|
|
117
|
+
expect(findDetected(list, 'http')).toBeNull();
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
//# sourceMappingURL=discover.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discover.test.js","sourceRoot":"","sources":["../../../src/integrations/providers/discover.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { type DetectedProvider, type DetectionSource, type DiscoverOptions, discoverProviders, findDetected, } from './discover.js';
|
|
2
|
+
export { type ClaudeCodeOAuthCredential, isAnthropicOAuthToken, readClaudeCodeKeychainCredential, } from './keychain.js';
|
|
3
|
+
export { maskSecret } from './mask.js';
|
|
4
|
+
export { ensureFreshAnthropicToken, type OAuthMetadata, refreshClaudeCodeToken, } from './oauth.js';
|
|
5
|
+
export { type Preferences, PREFERENCES_FILE_VERSION, PreferencesError, preferencesPath, type ReadResult, readPreferences, writePreferences, } from './preferences.js';
|
|
6
|
+
export { ALL_PROVIDER_IDS, PROVIDER_REGISTRY, type ProviderId, type ProviderRegistryEntry, type SdkProviderType, } from './registry.js';
|
|
7
|
+
export { clawtoolSecretsPath, readClawtoolSecrets, type SecretCandidate } from './secrets.js';
|
|
8
|
+
export { type AnthropicProfile, type BaseProfile, type BedrockProfile, type HttpProfile, type LMStudioProfile, type OllamaProfile, type OpenAIProfile, type OpenRouterProfile, PROVIDER_TYPES, type ProviderProfile, type ProviderType, type ProvidersFile, PROVIDERS_FILE_VERSION, ProfileValidationError, TYPE_ENV_FALLBACK, isProviderType, validateProfile, } from './schema.js';
|
|
9
|
+
export { ProvidersStoreError, assertInvariants, findDefault, providersPath, readProfiles, resolveApiKey, writeProfiles, } from './store.js';
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/integrations/providers/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,iBAAiB,EACjB,YAAY,GACZ,MAAM,eAAe,CAAA;AACtB,OAAO,EACN,KAAK,yBAAyB,EAC9B,qBAAqB,EACrB,gCAAgC,GAChC,MAAM,eAAe,CAAA;AACtB,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AACtC,OAAO,EACN,yBAAyB,EACzB,KAAK,aAAa,EAClB,sBAAsB,GACtB,MAAM,YAAY,CAAA;AACnB,OAAO,EACN,KAAK,WAAW,EAChB,wBAAwB,EACxB,gBAAgB,EAChB,eAAe,EACf,KAAK,UAAU,EACf,eAAe,EACf,gBAAgB,GAChB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EACN,gBAAgB,EAChB,iBAAiB,EACjB,KAAK,UAAU,EACf,KAAK,qBAAqB,EAC1B,KAAK,eAAe,GACpB,MAAM,eAAe,CAAA;AACtB,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,KAAK,eAAe,EAAE,MAAM,cAAc,CAAA;AAG7F,OAAO,EACN,KAAK,gBAAgB,EACrB,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACtB,cAAc,EACd,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,sBAAsB,EACtB,sBAAsB,EACtB,iBAAiB,EACjB,cAAc,EACd,eAAe,GACf,MAAM,aAAa,CAAA;AACpB,OAAO,EACN,mBAAmB,EACnB,gBAAgB,EAChB,WAAW,EACX,aAAa,EACb,YAAY,EACZ,aAAa,EACb,aAAa,GACb,MAAM,YAAY,CAAA"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { discoverProviders, findDetected, } from './discover.js';
|
|
2
|
+
export { isAnthropicOAuthToken, readClaudeCodeKeychainCredential, } from './keychain.js';
|
|
3
|
+
export { maskSecret } from './mask.js';
|
|
4
|
+
export { ensureFreshAnthropicToken, refreshClaudeCodeToken, } from './oauth.js';
|
|
5
|
+
export { PREFERENCES_FILE_VERSION, PreferencesError, preferencesPath, readPreferences, writePreferences, } from './preferences.js';
|
|
6
|
+
export { ALL_PROVIDER_IDS, PROVIDER_REGISTRY, } from './registry.js';
|
|
7
|
+
export { clawtoolSecretsPath, readClawtoolSecrets } from './secrets.js';
|
|
8
|
+
// Manual-profile escape hatch — kept for users who configure providers
|
|
9
|
+
// by API key directly without going through the auto-discovery picker.
|
|
10
|
+
export { PROVIDER_TYPES, PROVIDERS_FILE_VERSION, ProfileValidationError, TYPE_ENV_FALLBACK, isProviderType, validateProfile, } from './schema.js';
|
|
11
|
+
export { ProvidersStoreError, assertInvariants, findDefault, providersPath, readProfiles, resolveApiKey, writeProfiles, } from './store.js';
|
|
12
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/integrations/providers/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAIN,iBAAiB,EACjB,YAAY,GACZ,MAAM,eAAe,CAAA;AACtB,OAAO,EAEN,qBAAqB,EACrB,gCAAgC,GAChC,MAAM,eAAe,CAAA;AACtB,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AACtC,OAAO,EACN,yBAAyB,EAEzB,sBAAsB,GACtB,MAAM,YAAY,CAAA;AACnB,OAAO,EAEN,wBAAwB,EACxB,gBAAgB,EAChB,eAAe,EAEf,eAAe,EACf,gBAAgB,GAChB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EACN,gBAAgB,EAChB,iBAAiB,GAIjB,MAAM,eAAe,CAAA;AACtB,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAwB,MAAM,cAAc,CAAA;AAC7F,uEAAuE;AACvE,uEAAuE;AACvE,OAAO,EASN,cAAc,EAId,sBAAsB,EACtB,sBAAsB,EACtB,iBAAiB,EACjB,cAAc,EACd,eAAe,GACf,MAAM,aAAa,CAAA;AACpB,OAAO,EACN,mBAAmB,EACnB,gBAAgB,EAChB,WAAW,EACX,aAAa,EACb,YAAY,EACZ,aAAa,EACb,aAAa,GACb,MAAM,YAAY,CAAA"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* macOS Keychain reader for Claude Code OAuth credentials.
|
|
3
|
+
*
|
|
4
|
+
* Claude Code stores its OAuth credentials in the macOS login Keychain
|
|
5
|
+
* under the generic-password service name "Claude Code-credentials",
|
|
6
|
+
* not in a flat file. The password value is a JSON envelope:
|
|
7
|
+
*
|
|
8
|
+
* { "claudeAiOauth": { "accessToken": "...", "refreshToken": "...",
|
|
9
|
+
* "expiresAt": ..., "scopes": [...] } }
|
|
10
|
+
*
|
|
11
|
+
* Pattern ported from NousResearch's hermes-agent
|
|
12
|
+
* (`agent/anthropic_adapter.py:_read_claude_code_credentials_from_keychain`).
|
|
13
|
+
* Non-throwing — every failure (not-darwin, security not installed,
|
|
14
|
+
* entry missing, payload malformed) returns `null` so discovery treats
|
|
15
|
+
* the source as "not available" rather than crashing.
|
|
16
|
+
*/
|
|
17
|
+
export interface ClaudeCodeOAuthCredential {
|
|
18
|
+
readonly accessToken: string;
|
|
19
|
+
readonly refreshToken?: string;
|
|
20
|
+
readonly expiresAt?: number;
|
|
21
|
+
readonly scopes?: readonly string[];
|
|
22
|
+
}
|
|
23
|
+
export declare function readClaudeCodeKeychainCredential(): ClaudeCodeOAuthCredential | null;
|
|
24
|
+
/**
|
|
25
|
+
* Persist a refreshed OAuth credential back into the same Keychain entry so
|
|
26
|
+
* the new access token survives across launches and Claude Code itself stays
|
|
27
|
+
* in sync. The full envelope is re-read and merged (only the OAuth sub-fields
|
|
28
|
+
* change) so any extra keys Claude Code stores are preserved. Non-throwing:
|
|
29
|
+
* returns `false` (not-darwin, no entry, missing account, write denied) and
|
|
30
|
+
* the caller falls back to using the refreshed token only in memory.
|
|
31
|
+
*
|
|
32
|
+
* Note: `security` takes the secret as an argv (`-w`), so it is briefly
|
|
33
|
+
* visible to `ps` — acceptable for a local dev CLI updating a credential that
|
|
34
|
+
* already lives on this machine.
|
|
35
|
+
*/
|
|
36
|
+
export declare function writeClaudeCodeKeychainCredential(cred: ClaudeCodeOAuthCredential): boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Detect whether a credential value is an Anthropic OAuth-style token
|
|
39
|
+
* (must be sent via `Authorization: Bearer`) vs a console API key (sent
|
|
40
|
+
* via `x-api-key`). Ported from hermes's `_is_oauth_token` —
|
|
41
|
+
* positively identifies by prefix; defaults to API-key when unsure.
|
|
42
|
+
*/
|
|
43
|
+
export declare function isAnthropicOAuthToken(value: string): boolean;
|
|
44
|
+
//# sourceMappingURL=keychain.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keychain.d.ts","sourceRoot":"","sources":["../../../src/integrations/providers/keychain.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAOH,MAAM,WAAW,yBAAyB;IACzC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;IAC9B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CACnC;AAED,wBAAgB,gCAAgC,IAAI,yBAAyB,GAAG,IAAI,CAmCnF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iCAAiC,CAAC,IAAI,EAAE,yBAAyB,GAAG,OAAO,CAiD1F;AAiBD;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAM5D"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* macOS Keychain reader for Claude Code OAuth credentials.
|
|
3
|
+
*
|
|
4
|
+
* Claude Code stores its OAuth credentials in the macOS login Keychain
|
|
5
|
+
* under the generic-password service name "Claude Code-credentials",
|
|
6
|
+
* not in a flat file. The password value is a JSON envelope:
|
|
7
|
+
*
|
|
8
|
+
* { "claudeAiOauth": { "accessToken": "...", "refreshToken": "...",
|
|
9
|
+
* "expiresAt": ..., "scopes": [...] } }
|
|
10
|
+
*
|
|
11
|
+
* Pattern ported from NousResearch's hermes-agent
|
|
12
|
+
* (`agent/anthropic_adapter.py:_read_claude_code_credentials_from_keychain`).
|
|
13
|
+
* Non-throwing — every failure (not-darwin, security not installed,
|
|
14
|
+
* entry missing, payload malformed) returns `null` so discovery treats
|
|
15
|
+
* the source as "not available" rather than crashing.
|
|
16
|
+
*/
|
|
17
|
+
import { execFileSync } from 'node:child_process';
|
|
18
|
+
import { platform } from 'node:os';
|
|
19
|
+
const KEYCHAIN_SERVICE = 'Claude Code-credentials';
|
|
20
|
+
export function readClaudeCodeKeychainCredential() {
|
|
21
|
+
if (platform() !== 'darwin')
|
|
22
|
+
return null;
|
|
23
|
+
let raw;
|
|
24
|
+
try {
|
|
25
|
+
raw = execFileSync('security', ['find-generic-password', '-s', KEYCHAIN_SERVICE, '-w'], {
|
|
26
|
+
encoding: 'utf8',
|
|
27
|
+
timeout: 5_000,
|
|
28
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
29
|
+
}).trim();
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
if (raw.length === 0)
|
|
35
|
+
return null;
|
|
36
|
+
let parsed;
|
|
37
|
+
try {
|
|
38
|
+
parsed = JSON.parse(raw);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
if (typeof parsed !== 'object' || parsed === null)
|
|
44
|
+
return null;
|
|
45
|
+
const env = parsed.claudeAiOauth;
|
|
46
|
+
if (typeof env !== 'object' || env === null)
|
|
47
|
+
return null;
|
|
48
|
+
const oauth = env;
|
|
49
|
+
const accessToken = oauth.accessToken;
|
|
50
|
+
if (typeof accessToken !== 'string' || accessToken.length === 0)
|
|
51
|
+
return null;
|
|
52
|
+
return {
|
|
53
|
+
accessToken,
|
|
54
|
+
refreshToken: typeof oauth.refreshToken === 'string' ? oauth.refreshToken : undefined,
|
|
55
|
+
expiresAt: typeof oauth.expiresAt === 'number' ? oauth.expiresAt : undefined,
|
|
56
|
+
scopes: Array.isArray(oauth.scopes)
|
|
57
|
+
? oauth.scopes.filter((s) => typeof s === 'string')
|
|
58
|
+
: undefined,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Persist a refreshed OAuth credential back into the same Keychain entry so
|
|
63
|
+
* the new access token survives across launches and Claude Code itself stays
|
|
64
|
+
* in sync. The full envelope is re-read and merged (only the OAuth sub-fields
|
|
65
|
+
* change) so any extra keys Claude Code stores are preserved. Non-throwing:
|
|
66
|
+
* returns `false` (not-darwin, no entry, missing account, write denied) and
|
|
67
|
+
* the caller falls back to using the refreshed token only in memory.
|
|
68
|
+
*
|
|
69
|
+
* Note: `security` takes the secret as an argv (`-w`), so it is briefly
|
|
70
|
+
* visible to `ps` — acceptable for a local dev CLI updating a credential that
|
|
71
|
+
* already lives on this machine.
|
|
72
|
+
*/
|
|
73
|
+
export function writeClaudeCodeKeychainCredential(cred) {
|
|
74
|
+
if (platform() !== 'darwin')
|
|
75
|
+
return false;
|
|
76
|
+
// `add-generic-password -U` matches an existing item by service + account,
|
|
77
|
+
// so we must reuse the original account or we'd create a duplicate entry.
|
|
78
|
+
const account = readKeychainAccount();
|
|
79
|
+
if (!account)
|
|
80
|
+
return false;
|
|
81
|
+
let envelope = {};
|
|
82
|
+
try {
|
|
83
|
+
const raw = execFileSync('security', ['find-generic-password', '-s', KEYCHAIN_SERVICE, '-w'], {
|
|
84
|
+
encoding: 'utf8',
|
|
85
|
+
timeout: 5_000,
|
|
86
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
87
|
+
}).trim();
|
|
88
|
+
const parsed = JSON.parse(raw);
|
|
89
|
+
if (typeof parsed === 'object' && parsed !== null)
|
|
90
|
+
envelope = parsed;
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
// Fall back to a fresh envelope below.
|
|
94
|
+
}
|
|
95
|
+
const prev = typeof envelope.claudeAiOauth === 'object' && envelope.claudeAiOauth !== null
|
|
96
|
+
? envelope.claudeAiOauth
|
|
97
|
+
: {};
|
|
98
|
+
envelope.claudeAiOauth = {
|
|
99
|
+
...prev,
|
|
100
|
+
accessToken: cred.accessToken,
|
|
101
|
+
...(cred.refreshToken ? { refreshToken: cred.refreshToken } : {}),
|
|
102
|
+
...(cred.expiresAt ? { expiresAt: cred.expiresAt } : {}),
|
|
103
|
+
...(cred.scopes ? { scopes: cred.scopes } : {}),
|
|
104
|
+
};
|
|
105
|
+
try {
|
|
106
|
+
execFileSync('security', [
|
|
107
|
+
'add-generic-password',
|
|
108
|
+
'-U',
|
|
109
|
+
'-s',
|
|
110
|
+
KEYCHAIN_SERVICE,
|
|
111
|
+
'-a',
|
|
112
|
+
account,
|
|
113
|
+
'-w',
|
|
114
|
+
JSON.stringify(envelope),
|
|
115
|
+
], { timeout: 5_000, stdio: ['ignore', 'ignore', 'ignore'] });
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/** Read the account (`acct`) of the Claude Code Keychain entry, for updates. */
|
|
123
|
+
function readKeychainAccount() {
|
|
124
|
+
try {
|
|
125
|
+
const out = execFileSync('security', ['find-generic-password', '-s', KEYCHAIN_SERVICE], {
|
|
126
|
+
encoding: 'utf8',
|
|
127
|
+
timeout: 5_000,
|
|
128
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
129
|
+
});
|
|
130
|
+
const m = out.match(/"acct"<blob>="((?:[^"\\]|\\.)*)"/);
|
|
131
|
+
return m?.[1] ?? null;
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Detect whether a credential value is an Anthropic OAuth-style token
|
|
139
|
+
* (must be sent via `Authorization: Bearer`) vs a console API key (sent
|
|
140
|
+
* via `x-api-key`). Ported from hermes's `_is_oauth_token` —
|
|
141
|
+
* positively identifies by prefix; defaults to API-key when unsure.
|
|
142
|
+
*/
|
|
143
|
+
export function isAnthropicOAuthToken(value) {
|
|
144
|
+
if (value.startsWith('sk-ant-api'))
|
|
145
|
+
return false; // console API key
|
|
146
|
+
if (value.startsWith('sk-ant-oat'))
|
|
147
|
+
return true; // Anthropic OAuth setup token
|
|
148
|
+
if (value.startsWith('eyJ'))
|
|
149
|
+
return true; // JWT
|
|
150
|
+
if (value.startsWith('cc-'))
|
|
151
|
+
return true; // Claude Code OAuth access token
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=keychain.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keychain.js","sourceRoot":"","sources":["../../../src/integrations/providers/keychain.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keychain.test.d.ts","sourceRoot":"","sources":["../../../src/integrations/providers/keychain.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { isAnthropicOAuthToken } from './keychain.js';
|
|
3
|
+
describe('isAnthropicOAuthToken', () => {
|
|
4
|
+
it('detects Claude Code OAuth tokens (cc- prefix)', () => {
|
|
5
|
+
expect(isAnthropicOAuthToken('cc-some-opaque-id')).toBe(true);
|
|
6
|
+
});
|
|
7
|
+
it('detects Anthropic setup OAuth tokens (sk-ant-oat- prefix)', () => {
|
|
8
|
+
expect(isAnthropicOAuthToken('sk-ant-oat01-...')).toBe(true);
|
|
9
|
+
});
|
|
10
|
+
it('detects JWT-shaped tokens (eyJ prefix)', () => {
|
|
11
|
+
expect(isAnthropicOAuthToken('eyJhbGciOiJIUzI1NiIs...')).toBe(true);
|
|
12
|
+
});
|
|
13
|
+
it('rejects console API keys (sk-ant-api*)', () => {
|
|
14
|
+
expect(isAnthropicOAuthToken('sk-ant-api03-deadbeef')).toBe(false);
|
|
15
|
+
});
|
|
16
|
+
it('defaults to false (API-key path) for unknown shapes', () => {
|
|
17
|
+
expect(isAnthropicOAuthToken('arbitrary-string')).toBe(false);
|
|
18
|
+
expect(isAnthropicOAuthToken('')).toBe(false);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
//# sourceMappingURL=keychain.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keychain.test.js","sourceRoot":"","sources":["../../../src/integrations/providers/keychain.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAA;AAE7C,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAErD,QAAQ,CAAC,uBAAuB,EAAE,GAAG,EAAE;IACtC,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;QACxD,MAAM,CAAC,qBAAqB,CAAC,mBAAmB,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAC9D,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,2DAA2D,EAAE,GAAG,EAAE;QACpE,MAAM,CAAC,qBAAqB,CAAC,kBAAkB,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAC7D,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;QACjD,MAAM,CAAC,qBAAqB,CAAC,yBAAyB,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACpE,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;QACjD,MAAM,CAAC,qBAAqB,CAAC,uBAAuB,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACnE,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC9D,MAAM,CAAC,qBAAqB,CAAC,kBAAkB,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAC7D,MAAM,CAAC,qBAAqB,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IAC9C,CAAC,CAAC,CAAA;AACH,CAAC,CAAC,CAAA"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mask a secret for terminal display. Default keeps the last 4 chars
|
|
3
|
+
* (familiar from `aws configure list-profiles` / `gh auth status`).
|
|
4
|
+
* Returns `null` for null/empty inputs (so callers can render "—").
|
|
5
|
+
*/
|
|
6
|
+
export declare function maskSecret(secret: string | null | undefined, keep?: number): string | null;
|
|
7
|
+
//# sourceMappingURL=mask.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mask.d.ts","sourceRoot":"","sources":["../../../src/integrations/providers/mask.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,IAAI,SAAI,GAAG,MAAM,GAAG,IAAI,CAKrF"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mask a secret for terminal display. Default keeps the last 4 chars
|
|
3
|
+
* (familiar from `aws configure list-profiles` / `gh auth status`).
|
|
4
|
+
* Returns `null` for null/empty inputs (so callers can render "—").
|
|
5
|
+
*/
|
|
6
|
+
export function maskSecret(secret, keep = 4) {
|
|
7
|
+
if (!secret)
|
|
8
|
+
return null;
|
|
9
|
+
if (secret.length <= keep)
|
|
10
|
+
return '***';
|
|
11
|
+
const tail = secret.slice(-keep);
|
|
12
|
+
return `***${tail}`;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=mask.js.map
|