mod8-cli 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/CHANGELOG.md +87 -0
  2. package/LICENSE +21 -0
  3. package/README.md +239 -0
  4. package/bin/mod8.js +2 -0
  5. package/dist/cli.js +302 -0
  6. package/dist/commands/addProvider.js +105 -0
  7. package/dist/commands/all.js +158 -0
  8. package/dist/commands/chat.js +855 -0
  9. package/dist/commands/config.js +29 -0
  10. package/dist/commands/devAuthStatus.js +34 -0
  11. package/dist/commands/devHostAsk.js +51 -0
  12. package/dist/commands/devHostSystem.js +15 -0
  13. package/dist/commands/devResolve.js +54 -0
  14. package/dist/commands/devSimulate.js +235 -0
  15. package/dist/commands/devWorkAsk.js +55 -0
  16. package/dist/commands/intentRouting.js +280 -0
  17. package/dist/commands/keys.js +55 -0
  18. package/dist/commands/list.js +27 -0
  19. package/dist/commands/login.js +147 -0
  20. package/dist/commands/logout.js +17 -0
  21. package/dist/commands/prompt.js +63 -0
  22. package/dist/commands/providers.js +30 -0
  23. package/dist/commands/verify.js +5 -0
  24. package/dist/input/compose.js +37 -0
  25. package/dist/input/files.js +49 -0
  26. package/dist/input/stdin.js +14 -0
  27. package/dist/providers/anthropic.js +115 -0
  28. package/dist/providers/displayName.js +25 -0
  29. package/dist/providers/errorHints.js +175 -0
  30. package/dist/providers/generic.js +331 -0
  31. package/dist/providers/genericChat.js +265 -0
  32. package/dist/providers/google.js +63 -0
  33. package/dist/providers/hostSystem.js +173 -0
  34. package/dist/providers/index.js +38 -0
  35. package/dist/providers/mock.js +87 -0
  36. package/dist/providers/modelResolution.js +42 -0
  37. package/dist/providers/openai.js +75 -0
  38. package/dist/providers/pricing.js +47 -0
  39. package/dist/providers/proxy.js +148 -0
  40. package/dist/providers/registry.js +196 -0
  41. package/dist/providers/types.js +1 -0
  42. package/dist/providers/workSystem.js +33 -0
  43. package/dist/storage/auth.js +65 -0
  44. package/dist/storage/config.js +35 -0
  45. package/dist/storage/keys.js +59 -0
  46. package/dist/storage/providers.js +337 -0
  47. package/dist/storage/sessions.js +150 -0
  48. package/dist/types.js +9 -0
  49. package/dist/util/debug.js +79 -0
  50. package/dist/util/errors.js +157 -0
  51. package/dist/util/prompt.js +111 -0
  52. package/dist/util/secrets.js +110 -0
  53. package/dist/util/text.js +53 -0
  54. package/dist/util/time.js +25 -0
  55. package/dist/verify/runner.js +437 -0
  56. package/package.json +69 -0
  57. package/specs/all-mode.yaml +44 -0
  58. package/specs/behavior/auto-fallback.yaml +49 -0
  59. package/specs/behavior/bare-name-routing.yaml +223 -0
  60. package/specs/behavior/bare-paste-confirm.yaml +125 -0
  61. package/specs/behavior/env-var-respected.yaml +108 -0
  62. package/specs/behavior/error-fidelity.yaml +92 -0
  63. package/specs/behavior/error-hints.yaml +160 -0
  64. package/specs/behavior/fresh-vs-resume.yaml +94 -0
  65. package/specs/behavior/fuzzy-match.yaml +208 -0
  66. package/specs/behavior/host-self-knowledge-fresh.yaml +66 -0
  67. package/specs/behavior/intent-no-mismatch.yaml +115 -0
  68. package/specs/behavior/login-logout.yaml +97 -0
  69. package/specs/behavior/no-model-allowlist.yaml +80 -0
  70. package/specs/behavior/paste-key.yaml +342 -0
  71. package/specs/behavior/provider-switching.yaml +186 -0
  72. package/specs/behavior/providers-json-respected.yaml +106 -0
  73. package/specs/behavior/self-knowledge.yaml +119 -0
  74. package/specs/behavior/stress-session.yaml +226 -0
  75. package/specs/behavior/switch-back-when-failing.yaml +90 -0
  76. package/specs/behavior/work-character.yaml +109 -0
  77. package/specs/chat-meta.yaml +349 -0
  78. package/specs/chat-startup.yaml +148 -0
  79. package/specs/chat.yaml +91 -0
  80. package/specs/config.yaml +42 -0
  81. package/specs/install.yaml +112 -0
  82. package/specs/keys.yaml +81 -0
  83. package/specs/one-shot.yaml +65 -0
  84. package/specs/pipe-and-files.yaml +40 -0
  85. package/specs/providers.yaml +172 -0
  86. package/specs/sessions.yaml +115 -0
@@ -0,0 +1,91 @@
1
+ name: chat REPL surface
2
+ description: |
3
+ The chat REPL uses Ink, which needs a raw-mode TTY. Verify spawns child
4
+ processes with piped stdin (no PTY available), so we can't drive the REPL
5
+ through a full interaction. What we *can* do is run `mod8 new` / `mod8
6
+ resume`, capture whatever Ink rendered before crashing on raw-mode, and
7
+ assert against that early output. Deeper REPL flows (mode switching, /clear,
8
+ intent detection) are covered by chat-smoke.mjs (ink-testing-library) and
9
+ manual smoke tests, not by mod8 verify.
10
+
11
+ tests:
12
+ - name: mod8 new prints welcome banner before raw-mode crash
13
+ requires_api_key: true
14
+ repl:
15
+ run: mod8 new
16
+ timeout_ms: 6000
17
+ inputs:
18
+ - send: ""
19
+ delay_ms: 1500
20
+ expect:
21
+ stdout_contains:
22
+ - "mod8"
23
+ - "switch to claude"
24
+ - "/clear"
25
+ - "/exit"
26
+
27
+ - name: mod8 new without key reports friendly key error
28
+ repl:
29
+ run: "unset ANTHROPIC_API_KEY; mod8 new"
30
+ timeout_ms: 6000
31
+ inputs:
32
+ - send: ""
33
+ delay_ms: 1500
34
+ expect:
35
+ stderr_contains:
36
+ - "No Anthropic key configured"
37
+ - "mod8 keys set anthropic"
38
+
39
+ - name: mod8 resume on missing id is rejected before chat starts
40
+ repl:
41
+ run: mod8 resume 2099-12-31-zzzz
42
+ timeout_ms: 6000
43
+ inputs:
44
+ - send: ""
45
+ delay_ms: 1500
46
+ expect:
47
+ stderr_contains:
48
+ - "no session with id"
49
+ - "mod8 list"
50
+ exit_code: 1
51
+
52
+ - name: mod8 resume on malformed id is rejected before chat starts
53
+ repl:
54
+ run: mod8 resume not-a-real-id
55
+ timeout_ms: 6000
56
+ inputs:
57
+ - send: ""
58
+ delay_ms: 1500
59
+ expect:
60
+ stderr_contains: "no session with id"
61
+ exit_code: 1
62
+
63
+ - name: mod8 resume against seeded session loads transcript into welcome
64
+ requires_api_key: true
65
+ setup:
66
+ - shell: |
67
+ mkdir -p $MOD8_CONFIG_DIR/sessions
68
+ cat > $MOD8_CONFIG_DIR/sessions/2026-05-07-rsme.json <<'JSON'
69
+ {
70
+ "version": 1,
71
+ "id": "2026-05-07-rsme",
72
+ "title": "Resume test",
73
+ "createdAt": 1715000000000,
74
+ "lastActivity": 1715000000000,
75
+ "messages": [
76
+ {"role": "user", "content": "what is the capital of France", "mode": "host"},
77
+ {"role": "assistant", "content": "Paris.", "mode": "host"}
78
+ ]
79
+ }
80
+ JSON
81
+ chmod 600 $MOD8_CONFIG_DIR/sessions/2026-05-07-rsme.json
82
+ repl:
83
+ run: mod8 resume 2026-05-07-rsme
84
+ timeout_ms: 6000
85
+ inputs:
86
+ - send: ""
87
+ delay_ms: 1500
88
+ expect:
89
+ stdout_contains:
90
+ - "what is the capital of France"
91
+ - "Paris"
@@ -0,0 +1,42 @@
1
+ name: config management
2
+ description: mod8 config get / set
3
+
4
+ tests:
5
+ - name: config get on a fresh install shows fallback default
6
+ run: mod8 config get
7
+ expect:
8
+ stdout_contains:
9
+ - "default:"
10
+ - anthropic
11
+ - "(fallback)"
12
+ exit_code: 0
13
+
14
+ - name: config set default openai persists
15
+ steps:
16
+ - run: mod8 config set default openai
17
+ expect:
18
+ stdout_contains: "Default provider set to openai"
19
+ - run: mod8 config get
20
+ expect:
21
+ stdout_contains:
22
+ - openai
23
+ - "(configured)"
24
+
25
+ - name: config set rejects unknown key
26
+ run: mod8 config set fnord value
27
+ expect:
28
+ stderr_contains: "Unknown config key"
29
+ exit_code: 1
30
+
31
+ - name: config set rejects unknown provider value
32
+ run: mod8 config set default claude
33
+ expect:
34
+ stderr_contains: "Unknown provider"
35
+ exit_code: 1
36
+
37
+ - name: default routing follows config (mocked one-shot)
38
+ steps:
39
+ - run: mod8 config set default openai
40
+ - shell: "MOD8_MOCK=1 mod8 'say hi'"
41
+ expect:
42
+ stdout_contains: "gpt-4o"
@@ -0,0 +1,112 @@
1
+ name: install flow
2
+ description: |
3
+ npm pack + install in a fresh directory + the installed binary runs all the
4
+ basic commands. This catches bin-shim regressions, missing files in the
5
+ tarball, and environment leakage from the dev tree.
6
+
7
+ These tests pack the project from /Users/yonatanzlit/mod8 (the absolute
8
+ source path), then `npm install` the resulting tarball into the per-test
9
+ sandbox. Each test gets a fresh node_modules, so installs are repeatable.
10
+
11
+ tests:
12
+ - name: pack produces a tarball with the expected files
13
+ setup:
14
+ - shell: |
15
+ cd /Users/yonatanzlit/mod8
16
+ rm -f mod8-0.2.0.tgz
17
+ npm pack > /tmp/mod8-pack.log 2>&1
18
+ test -f mod8-0.2.0.tgz || { cat /tmp/mod8-pack.log; exit 1; }
19
+ shell: tar tzf /Users/yonatanzlit/mod8/mod8-0.2.0.tgz
20
+ expect:
21
+ stdout_contains:
22
+ - "package/bin/mod8.js"
23
+ - "package/dist/cli.js"
24
+ - "package/dist/providers/registry.js"
25
+ - "package/dist/providers/hostSystem.js"
26
+ - "package/specs/providers.yaml"
27
+ - "package/README.md"
28
+ - "package/LICENSE"
29
+ - "package/CHANGELOG.md"
30
+ - "package/package.json"
31
+ exit_code: 0
32
+
33
+ - name: tarball does not include source TypeScript
34
+ shell: tar tzf /Users/yonatanzlit/mod8/mod8-0.2.0.tgz
35
+ expect:
36
+ stdout_omits:
37
+ - "package/src/"
38
+ - ".ts"
39
+ - "tsconfig"
40
+ - "node_modules"
41
+ exit_code: 0
42
+
43
+ - name: install in a fresh dir places the bin shim
44
+ setup:
45
+ - shell: |
46
+ cd $MOD8_CONFIG_DIR
47
+ npm install --no-audit --no-fund /Users/yonatanzlit/mod8/mod8-0.2.0.tgz > /tmp/mod8-install.log 2>&1
48
+ test -x ./node_modules/.bin/mod8 || { echo "missing or non-exec bin"; cat /tmp/mod8-install.log; exit 1; }
49
+ shell: $MOD8_CONFIG_DIR/node_modules/.bin/mod8 --version
50
+ expect:
51
+ stdout_contains: "0.2.0"
52
+ exit_code: 0
53
+
54
+ - name: installed binary --help lists the new commands
55
+ setup:
56
+ - shell: |
57
+ cd $MOD8_CONFIG_DIR
58
+ npm install --no-audit --no-fund /Users/yonatanzlit/mod8/mod8-0.2.0.tgz > /tmp/mod8-install.log 2>&1
59
+ shell: $MOD8_CONFIG_DIR/node_modules/.bin/mod8 --help
60
+ expect:
61
+ stdout_contains:
62
+ - mod8
63
+ - keys
64
+ - providers
65
+ - add-provider
66
+ - new
67
+ - resume
68
+ - verify
69
+ - "OpenAI-compatible"
70
+ exit_code: 0
71
+
72
+ - name: installed binary keys list shows all 9 built-in templates
73
+ setup:
74
+ - shell: |
75
+ cd $MOD8_CONFIG_DIR
76
+ npm install --no-audit --no-fund /Users/yonatanzlit/mod8/mod8-0.2.0.tgz > /tmp/mod8-install.log 2>&1
77
+ shell: $MOD8_CONFIG_DIR/node_modules/.bin/mod8 keys list
78
+ expect:
79
+ stdout_contains:
80
+ - anthropic
81
+ - openai
82
+ - google
83
+ - deepseek
84
+ - mistral
85
+ - groq
86
+ - openrouter
87
+ - xai
88
+ - together
89
+
90
+ - name: installed binary --all in mock mode renders 3 blocks
91
+ setup:
92
+ - shell: |
93
+ cd $MOD8_CONFIG_DIR
94
+ npm install --no-audit --no-fund /Users/yonatanzlit/mod8/mod8-0.2.0.tgz > /tmp/mod8-install.log 2>&1
95
+ shell: "MOD8_MOCK=1 MOD8_AUTO_CONFIRM=1 $MOD8_CONFIG_DIR/node_modules/.bin/mod8 --all 'say hi'"
96
+ expect:
97
+ stdout_contains:
98
+ - "Anthropic (Claude)"
99
+ - "OpenAI (GPT)"
100
+ - "Google (Gemini)"
101
+ - "3/3 ok"
102
+
103
+ - name: installed binary persists keys to providers.json (not legacy)
104
+ setup:
105
+ - shell: |
106
+ cd $MOD8_CONFIG_DIR
107
+ npm install --no-audit --no-fund /Users/yonatanzlit/mod8/mod8-0.2.0.tgz > /tmp/mod8-install.log 2>&1
108
+ - shell: "echo 'sk-test-fresh-install' | $MOD8_CONFIG_DIR/node_modules/.bin/mod8 keys set anthropic"
109
+ shell: "true"
110
+ expect:
111
+ file_exists: "$MOD8_CONFIG_DIR/providers.json"
112
+ file_mode: "$MOD8_CONFIG_DIR/providers.json:600"
@@ -0,0 +1,81 @@
1
+ name: keys management
2
+ description: mod8 keys set / list / remove
3
+
4
+ tests:
5
+ - name: empty state shows all three providers as not set
6
+ run: mod8 keys list
7
+ expect:
8
+ stdout_contains:
9
+ - anthropic
10
+ - openai
11
+ - google
12
+ - "(not set)"
13
+ exit_code: 0
14
+
15
+ - name: keys list points at the right config path
16
+ run: mod8 keys list
17
+ expect:
18
+ stdout_contains: "Stored at"
19
+ stdout_matches: 'Stored at .+/providers\.json'
20
+
21
+ - name: set saves a key, list shows it masked
22
+ steps:
23
+ - run: mod8 keys set anthropic
24
+ stdin: "sk-ant-fake-1234567890abcdef"
25
+ expect:
26
+ stdout_contains: "Saved key for"
27
+ - run: mod8 keys list
28
+ expect:
29
+ stdout_matches: 'anthropic\s+sk-a.*cdef'
30
+ stdout_omits: "1234567890"
31
+
32
+ - name: providers.json is created with mode 0600
33
+ steps:
34
+ - run: mod8 keys set anthropic
35
+ stdin: "test-key-value"
36
+ - shell: "true"
37
+ expect:
38
+ file_exists: "$MOD8_CONFIG_DIR/providers.json"
39
+ file_mode: "$MOD8_CONFIG_DIR/providers.json:600"
40
+
41
+ - name: remove deletes the saved key
42
+ steps:
43
+ - run: mod8 keys set openai
44
+ stdin: "sk-openai-fake-key"
45
+ - run: mod8 keys remove openai
46
+ expect:
47
+ stdout_contains: "Removed key for openai"
48
+ - run: mod8 keys list
49
+ expect:
50
+ stdout_omits: "fake"
51
+
52
+ - name: invalid provider name rejected with friendly error
53
+ run: mod8 keys set claude
54
+ stdin: "ignored"
55
+ expect:
56
+ stderr_contains: "Unknown provider"
57
+ exit_code: 1
58
+
59
+ - name: remove on never-set key is non-fatal
60
+ run: mod8 keys remove google
61
+ expect:
62
+ stdout_contains: "No key was set for google"
63
+ exit_code: 0
64
+
65
+ - name: multiple keys coexist
66
+ steps:
67
+ - run: mod8 keys set anthropic
68
+ stdin: "ant-key-aaaa"
69
+ - run: mod8 keys set openai
70
+ stdin: "oai-key-bbbb"
71
+ - run: mod8 keys set google
72
+ stdin: "ggl-key-cccc"
73
+ - run: mod8 keys list
74
+ expect:
75
+ stdout_matches: 'anthropic\s+ant-.*aaaa'
76
+ - run: mod8 keys list
77
+ expect:
78
+ stdout_matches: 'openai\s+oai-.*bbbb'
79
+ - run: mod8 keys list
80
+ expect:
81
+ stdout_matches: 'google\s+ggl-.*cccc'
@@ -0,0 +1,65 @@
1
+ name: one-shot prompts
2
+ description: -c / -o / -g / default prompt with mocked providers
3
+
4
+ tests:
5
+ - name: -c routes to anthropic with stats footer
6
+ shell: "MOD8_MOCK=1 mod8 -c 'hi'"
7
+ expect:
8
+ stdout_contains:
9
+ - "claude-sonnet-4-6"
10
+ - "tok ·"
11
+ exit_code: 0
12
+
13
+ - name: -o routes to openai
14
+ shell: "MOD8_MOCK=1 mod8 -o 'hi'"
15
+ expect:
16
+ stdout_contains:
17
+ - gpt-4o
18
+ - "tok ·"
19
+ exit_code: 0
20
+
21
+ - name: -g routes to gemini
22
+ shell: "MOD8_MOCK=1 mod8 -g 'hi'"
23
+ expect:
24
+ stdout_contains:
25
+ - "gemini-2.0-flash"
26
+ - "tok ·"
27
+ exit_code: 0
28
+
29
+ - name: default (no flag) routes to anthropic
30
+ shell: "MOD8_MOCK=1 mod8 'hi'"
31
+ expect:
32
+ stdout_contains: "claude-sonnet-4-6"
33
+
34
+ - name: multiple provider flags rejected
35
+ shell: "MOD8_MOCK=1 mod8 -c -o 'hi'"
36
+ expect:
37
+ stderr_contains: "Cannot use multiple provider flags"
38
+ exit_code: 1
39
+
40
+ - name: missing key error is friendly (real path)
41
+ shell: 'unset ANTHROPIC_API_KEY; mod8 -c "test"'
42
+ expect:
43
+ stderr_contains:
44
+ - "Anthropic"
45
+ - "key configured"
46
+ - "mod8 keys set anthropic"
47
+ exit_code: 1
48
+
49
+ - name: invalid key error is friendly (mocked 401)
50
+ shell: "MOD8_MOCK=1 MOD8_MOCK_ERROR=401 mod8 -c 'hi'"
51
+ expect:
52
+ stderr_contains: "invalid API key"
53
+ exit_code: 1
54
+
55
+ - name: rate limit error is friendly (mocked 429)
56
+ shell: "MOD8_MOCK=1 MOD8_MOCK_ERROR=429 mod8 -c 'hi'"
57
+ expect:
58
+ stderr_contains: "rate limited"
59
+ exit_code: 1
60
+
61
+ - name: network error is friendly
62
+ shell: "MOD8_MOCK=1 MOD8_MOCK_ERROR=network mod8 -c 'hi'"
63
+ expect:
64
+ stderr_contains: "network error"
65
+ exit_code: 1
@@ -0,0 +1,40 @@
1
+ name: pipe + @file inputs
2
+ description: stdin pipe and @path references reach the model
3
+
4
+ tests:
5
+ - name: piped stdin is appended to the prompt sent to provider
6
+ shell: "echo 'piped content here' | MOD8_MOCK=1 MOD8_MOCK_ECHO=1 MOD8_AUTO_CONFIRM=1 mod8 --all 'explain'"
7
+ expect:
8
+ stdout_contains:
9
+ - explain
10
+ - "piped content here"
11
+
12
+ - name: "@file is read and inlined under [file:] header"
13
+ steps:
14
+ - shell: |
15
+ mkdir -p $MOD8_CONFIG_DIR/fixtures
16
+ echo 'function broken() { return -1; }' > $MOD8_CONFIG_DIR/fixtures/sample.js
17
+ - shell: "MOD8_MOCK=1 MOD8_MOCK_ECHO=1 MOD8_AUTO_CONFIRM=1 mod8 --all \"review @$MOD8_CONFIG_DIR/fixtures/sample.js\""
18
+ expect:
19
+ stdout_contains:
20
+ - "[file:"
21
+ - "function broken"
22
+
23
+ - name: "missing @file produces friendly warning, prompt still runs"
24
+ shell: "MOD8_MOCK=1 MOD8_MOCK_ECHO=1 MOD8_AUTO_CONFIRM=1 mod8 --all 'explain @/tmp/nope-does-not-exist.js'"
25
+ expect:
26
+ stderr_contains:
27
+ - warning
28
+ - "not found"
29
+ stdout_contains: "Total:"
30
+
31
+ - name: "@file with relative path resolves against cwd"
32
+ steps:
33
+ - shell: |
34
+ mkdir -p $MOD8_CONFIG_DIR/work
35
+ echo 'export const x = 42;' > $MOD8_CONFIG_DIR/work/util.ts
36
+ - shell: "cd $MOD8_CONFIG_DIR/work && MOD8_MOCK=1 MOD8_MOCK_ECHO=1 MOD8_AUTO_CONFIRM=1 mod8 --all 'review @util.ts'"
37
+ expect:
38
+ stdout_contains:
39
+ - "[file:"
40
+ - "export const x = 42"
@@ -0,0 +1,172 @@
1
+ name: multi-provider registration
2
+ description: |
3
+ Provider registry, store, and listing. Covers built-in providers (anthropic,
4
+ openai, google) plus at least one less-common one (deepseek) to prove the
5
+ generic dispatch. Custom-provider registration is exercised via direct
6
+ providers.json seeding (the interactive add-provider flow needs raw-mode
7
+ TTY, same caveat as the chat REPL).
8
+
9
+ tests:
10
+ - name: providers list is empty on a fresh sandbox
11
+ run: mod8 providers
12
+ expect:
13
+ stdout_contains: "no providers configured yet"
14
+ exit_code: 0
15
+
16
+ - name: keys list shows all built-in templates including deepseek and grok
17
+ run: mod8 keys list
18
+ expect:
19
+ stdout_contains:
20
+ - anthropic
21
+ - openai
22
+ - google
23
+ - deepseek
24
+ - mistral
25
+ - groq
26
+ - xai
27
+ - openrouter
28
+ - together
29
+ exit_code: 0
30
+
31
+ - name: keys set works for a non-legacy built-in (deepseek)
32
+ steps:
33
+ - run: mod8 keys set deepseek
34
+ stdin: "sk-test-deepseek-1234567890"
35
+ expect:
36
+ stdout_contains: "Saved key for"
37
+ - run: mod8 keys list
38
+ expect:
39
+ stdout_matches: 'deepseek\s+sk-t.*7890'
40
+ - run: mod8 providers
41
+ expect:
42
+ stdout_contains:
43
+ - deepseek
44
+ - DeepSeek
45
+ - deepseek-chat
46
+ - "openai-compat"
47
+
48
+ - name: keys set works for groq (gsk_ prefix)
49
+ steps:
50
+ - run: mod8 keys set groq
51
+ stdin: "gsk_test_groq_abcdef123456"
52
+ - run: mod8 providers
53
+ expect:
54
+ stdout_contains:
55
+ - groq
56
+ - Groq
57
+ - "openai-compat"
58
+
59
+ - name: providers list shows base URLs for openai-compat providers
60
+ steps:
61
+ - run: mod8 keys set deepseek
62
+ stdin: "sk-deepseek-test"
63
+ - run: mod8 keys set mistral
64
+ stdin: "test-mistral-key"
65
+ - run: mod8 providers
66
+ expect:
67
+ stdout_contains:
68
+ - "https://api.deepseek.com"
69
+ - "https://api.mistral.ai/v1"
70
+
71
+ - name: providers.json is the new storage location with mode 0600
72
+ steps:
73
+ - run: mod8 keys set deepseek
74
+ stdin: "sk-test-deepseek"
75
+ - shell: "true"
76
+ expect:
77
+ file_exists: "$MOD8_CONFIG_DIR/providers.json"
78
+ file_mode: "$MOD8_CONFIG_DIR/providers.json:600"
79
+
80
+ - name: legacy keys.json migrates to providers.json on first read
81
+ steps:
82
+ - shell: |
83
+ mkdir -p $MOD8_CONFIG_DIR
84
+ cat > $MOD8_CONFIG_DIR/keys.json <<'JSON'
85
+ {"anthropic": "sk-ant-legacy-12345", "openai": "sk-legacy-oai-67890"}
86
+ JSON
87
+ chmod 600 $MOD8_CONFIG_DIR/keys.json
88
+ - run: mod8 providers
89
+ expect:
90
+ stdout_contains:
91
+ - anthropic
92
+ - openai
93
+ - shell: "true"
94
+ expect:
95
+ file_exists: "$MOD8_CONFIG_DIR/providers.json"
96
+
97
+ - name: pre-seeded custom provider shows up in keys list and providers
98
+ setup:
99
+ - shell: |
100
+ mkdir -p $MOD8_CONFIG_DIR
101
+ cat > $MOD8_CONFIG_DIR/providers.json <<'JSON'
102
+ {
103
+ "fireworks": {
104
+ "apiKey": "fw-test-1234",
105
+ "apiType": "openai-compat",
106
+ "name": "Fireworks AI",
107
+ "baseUrl": "https://api.fireworks.ai/inference/v1",
108
+ "defaultModel": "accounts/fireworks/models/llama-v3p1-70b-instruct",
109
+ "color": "#FB923C",
110
+ "custom": true
111
+ }
112
+ }
113
+ JSON
114
+ chmod 600 $MOD8_CONFIG_DIR/providers.json
115
+ run: mod8 providers
116
+ expect:
117
+ stdout_contains:
118
+ - fireworks
119
+ - "Fireworks AI"
120
+ - "(custom)"
121
+ - "https://api.fireworks.ai"
122
+
123
+ - name: keys remove works for non-legacy provider
124
+ steps:
125
+ - run: mod8 keys set deepseek
126
+ stdin: "sk-deepseek-removeme"
127
+ - run: mod8 keys remove deepseek
128
+ expect:
129
+ stdout_contains: "Removed key for deepseek"
130
+ - run: mod8 providers
131
+ expect:
132
+ stdout_contains: "no providers configured"
133
+
134
+ - name: keys set rejects unknown built-in id
135
+ run: mod8 keys set llama
136
+ stdin: "ignored"
137
+ expect:
138
+ stderr_contains:
139
+ - "Unknown provider 'llama'"
140
+ - "mod8 add-provider"
141
+ exit_code: 1
142
+
143
+ - name: --all in mock mode renders 3 distinct provider blocks
144
+ shell: "MOD8_MOCK=1 MOD8_AUTO_CONFIRM=1 mod8 --all 'compare in 5 words'"
145
+ expect:
146
+ stdout_contains:
147
+ - "Anthropic (Claude)"
148
+ - "OpenAI (GPT)"
149
+ - "Google (Gemini)"
150
+ - "3/3 ok"
151
+
152
+ - name: config set default deepseek persists
153
+ steps:
154
+ - run: mod8 keys set deepseek
155
+ stdin: "sk-test-deepseek-default"
156
+ - run: mod8 config set default deepseek
157
+ expect:
158
+ stdout_contains: "Default provider set to deepseek"
159
+ - run: mod8 config get
160
+ expect:
161
+ stdout_contains:
162
+ - deepseek
163
+ - "(configured)"
164
+
165
+ - name: config set default rejects unknown provider with helpful message
166
+ run: mod8 config set default fakeprovider
167
+ expect:
168
+ stderr_contains:
169
+ - "Unknown provider 'fakeprovider'"
170
+ - "Built-in:"
171
+ - "deepseek"
172
+ exit_code: 1