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.
- package/CHANGELOG.md +87 -0
- package/LICENSE +21 -0
- package/README.md +239 -0
- package/bin/mod8.js +2 -0
- package/dist/cli.js +302 -0
- package/dist/commands/addProvider.js +105 -0
- package/dist/commands/all.js +158 -0
- package/dist/commands/chat.js +855 -0
- package/dist/commands/config.js +29 -0
- package/dist/commands/devAuthStatus.js +34 -0
- package/dist/commands/devHostAsk.js +51 -0
- package/dist/commands/devHostSystem.js +15 -0
- package/dist/commands/devResolve.js +54 -0
- package/dist/commands/devSimulate.js +235 -0
- package/dist/commands/devWorkAsk.js +55 -0
- package/dist/commands/intentRouting.js +280 -0
- package/dist/commands/keys.js +55 -0
- package/dist/commands/list.js +27 -0
- package/dist/commands/login.js +147 -0
- package/dist/commands/logout.js +17 -0
- package/dist/commands/prompt.js +63 -0
- package/dist/commands/providers.js +30 -0
- package/dist/commands/verify.js +5 -0
- package/dist/input/compose.js +37 -0
- package/dist/input/files.js +49 -0
- package/dist/input/stdin.js +14 -0
- package/dist/providers/anthropic.js +115 -0
- package/dist/providers/displayName.js +25 -0
- package/dist/providers/errorHints.js +175 -0
- package/dist/providers/generic.js +331 -0
- package/dist/providers/genericChat.js +265 -0
- package/dist/providers/google.js +63 -0
- package/dist/providers/hostSystem.js +173 -0
- package/dist/providers/index.js +38 -0
- package/dist/providers/mock.js +87 -0
- package/dist/providers/modelResolution.js +42 -0
- package/dist/providers/openai.js +75 -0
- package/dist/providers/pricing.js +47 -0
- package/dist/providers/proxy.js +148 -0
- package/dist/providers/registry.js +196 -0
- package/dist/providers/types.js +1 -0
- package/dist/providers/workSystem.js +33 -0
- package/dist/storage/auth.js +65 -0
- package/dist/storage/config.js +35 -0
- package/dist/storage/keys.js +59 -0
- package/dist/storage/providers.js +337 -0
- package/dist/storage/sessions.js +150 -0
- package/dist/types.js +9 -0
- package/dist/util/debug.js +79 -0
- package/dist/util/errors.js +157 -0
- package/dist/util/prompt.js +111 -0
- package/dist/util/secrets.js +110 -0
- package/dist/util/text.js +53 -0
- package/dist/util/time.js +25 -0
- package/dist/verify/runner.js +437 -0
- package/package.json +69 -0
- package/specs/all-mode.yaml +44 -0
- package/specs/behavior/auto-fallback.yaml +49 -0
- package/specs/behavior/bare-name-routing.yaml +223 -0
- package/specs/behavior/bare-paste-confirm.yaml +125 -0
- package/specs/behavior/env-var-respected.yaml +108 -0
- package/specs/behavior/error-fidelity.yaml +92 -0
- package/specs/behavior/error-hints.yaml +160 -0
- package/specs/behavior/fresh-vs-resume.yaml +94 -0
- package/specs/behavior/fuzzy-match.yaml +208 -0
- package/specs/behavior/host-self-knowledge-fresh.yaml +66 -0
- package/specs/behavior/intent-no-mismatch.yaml +115 -0
- package/specs/behavior/login-logout.yaml +97 -0
- package/specs/behavior/no-model-allowlist.yaml +80 -0
- package/specs/behavior/paste-key.yaml +342 -0
- package/specs/behavior/provider-switching.yaml +186 -0
- package/specs/behavior/providers-json-respected.yaml +106 -0
- package/specs/behavior/self-knowledge.yaml +119 -0
- package/specs/behavior/stress-session.yaml +226 -0
- package/specs/behavior/switch-back-when-failing.yaml +90 -0
- package/specs/behavior/work-character.yaml +109 -0
- package/specs/chat-meta.yaml +349 -0
- package/specs/chat-startup.yaml +148 -0
- package/specs/chat.yaml +91 -0
- package/specs/config.yaml +42 -0
- package/specs/install.yaml +112 -0
- package/specs/keys.yaml +81 -0
- package/specs/one-shot.yaml +65 -0
- package/specs/pipe-and-files.yaml +40 -0
- package/specs/providers.yaml +172 -0
- package/specs/sessions.yaml +115 -0
package/specs/chat.yaml
ADDED
|
@@ -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"
|
package/specs/keys.yaml
ADDED
|
@@ -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
|