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
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
name: behavior — switch-back works mid-error
|
|
2
|
+
description: |
|
|
3
|
+
Repro: user is in work mode, the work provider is rate-limited / 401 / down,
|
|
4
|
+
user types "mod8" or "back to mod8" trying to escape — every input was
|
|
5
|
+
being sent to the broken provider, getting an error, and the user was
|
|
6
|
+
stuck.
|
|
7
|
+
|
|
8
|
+
Fix verified by these tests: parseHostBack runs LOCALLY in the chat REPL
|
|
9
|
+
before any provider call, so all of these inputs are intercepted as a
|
|
10
|
+
switch-back to host regardless of whether the work provider is healthy
|
|
11
|
+
or failing. dev:resolve mirrors chat.tsx's match priority — "host-back"
|
|
12
|
+
always wins over "route" / "compare" / etc.
|
|
13
|
+
|
|
14
|
+
tests:
|
|
15
|
+
- name: 'bare "mod8" is intercepted as switch-back'
|
|
16
|
+
shell: 'mod8 dev:resolve "mod8"'
|
|
17
|
+
expect:
|
|
18
|
+
stdout_contains: "host-back"
|
|
19
|
+
|
|
20
|
+
- name: '"/mod8" is intercepted as switch-back'
|
|
21
|
+
shell: 'mod8 dev:resolve "/mod8"'
|
|
22
|
+
expect:
|
|
23
|
+
stdout_contains: "host-back"
|
|
24
|
+
|
|
25
|
+
- name: '"@mod8" is intercepted as switch-back'
|
|
26
|
+
shell: 'mod8 dev:resolve "@mod8"'
|
|
27
|
+
expect:
|
|
28
|
+
stdout_contains: "host-back"
|
|
29
|
+
|
|
30
|
+
- name: '"back to mod8" is intercepted'
|
|
31
|
+
shell: 'mod8 dev:resolve "back to mod8"'
|
|
32
|
+
expect:
|
|
33
|
+
stdout_contains: "host-back"
|
|
34
|
+
|
|
35
|
+
- name: '"switch to mod8" is intercepted (was previously routing to non-existent provider id "mod8")'
|
|
36
|
+
shell: 'mod8 dev:resolve "switch to mod8"'
|
|
37
|
+
expect:
|
|
38
|
+
stdout_contains: "host-back"
|
|
39
|
+
|
|
40
|
+
- name: '"change to mod8" is intercepted'
|
|
41
|
+
shell: 'mod8 dev:resolve "change to mod8"'
|
|
42
|
+
expect:
|
|
43
|
+
stdout_contains: "host-back"
|
|
44
|
+
|
|
45
|
+
- name: '"go back" is intercepted'
|
|
46
|
+
shell: 'mod8 dev:resolve "go back"'
|
|
47
|
+
expect:
|
|
48
|
+
stdout_contains: "host-back"
|
|
49
|
+
|
|
50
|
+
- name: 'bare "back" is intercepted'
|
|
51
|
+
shell: 'mod8 dev:resolve "back"'
|
|
52
|
+
expect:
|
|
53
|
+
stdout_contains: "host-back"
|
|
54
|
+
|
|
55
|
+
- name: '"switch back" is intercepted'
|
|
56
|
+
shell: 'mod8 dev:resolve "switch back"'
|
|
57
|
+
expect:
|
|
58
|
+
stdout_contains: "host-back"
|
|
59
|
+
|
|
60
|
+
- name: '"return to mod8" is intercepted'
|
|
61
|
+
shell: 'mod8 dev:resolve "return to mod8"'
|
|
62
|
+
expect:
|
|
63
|
+
stdout_contains: "host-back"
|
|
64
|
+
|
|
65
|
+
- name: '"talk to mod8" is intercepted (mod8 is special — never a provider id)'
|
|
66
|
+
shell: 'mod8 dev:resolve "talk to mod8"'
|
|
67
|
+
expect:
|
|
68
|
+
stdout_contains: "host-back"
|
|
69
|
+
|
|
70
|
+
- name: '"back to host" is intercepted (alternate phrasing)'
|
|
71
|
+
shell: 'mod8 dev:resolve "back to host"'
|
|
72
|
+
expect:
|
|
73
|
+
stdout_contains: "host-back"
|
|
74
|
+
|
|
75
|
+
- name: '"mod8 thanks for the help" carries rest="thanks for the help"'
|
|
76
|
+
shell: 'mod8 dev:resolve "mod8 thanks for the help"'
|
|
77
|
+
expect:
|
|
78
|
+
stdout_matches: 'host-back rest="thanks for the help"'
|
|
79
|
+
|
|
80
|
+
- name: an unrelated message ("write me a haiku") is NOT mistaken for switch-back
|
|
81
|
+
shell: 'mod8 dev:resolve "write me a haiku"'
|
|
82
|
+
expect:
|
|
83
|
+
stdout_contains: "none"
|
|
84
|
+
|
|
85
|
+
- name: '"use codex" is NOT swallowed by host-back (still a provider route)'
|
|
86
|
+
shell: 'mod8 dev:resolve "use codex"'
|
|
87
|
+
expect:
|
|
88
|
+
stdout_contains: "route id=codex"
|
|
89
|
+
stdout_omits:
|
|
90
|
+
- "host-back"
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
name: behavior — work-mode model stays in character
|
|
2
|
+
description: |
|
|
3
|
+
Drilling deeper into work-mode discipline. When the user is in work
|
|
4
|
+
mode, the worker model should:
|
|
5
|
+
- Identify as itself (claude / codex / etc), NOT as mod8
|
|
6
|
+
- Just do the work, NOT lecture about provider config
|
|
7
|
+
- Defer meta questions about mod8 back to host with <SWITCH_TO_HOST>
|
|
8
|
+
- Stay engaged for follow-ups, not bounce on every turn
|
|
9
|
+
|
|
10
|
+
tests:
|
|
11
|
+
- name: work — "what is mod8?" defers, doesn't impersonate
|
|
12
|
+
requires_api_key: true
|
|
13
|
+
setup:
|
|
14
|
+
- shell: |
|
|
15
|
+
mkdir -p $MOD8_CONFIG_DIR
|
|
16
|
+
cat > $MOD8_CONFIG_DIR/providers.json <<JSON
|
|
17
|
+
{
|
|
18
|
+
"anthropic": {
|
|
19
|
+
"apiKey": "$ANTHROPIC_API_KEY",
|
|
20
|
+
"apiType": "anthropic",
|
|
21
|
+
"name": "Anthropic (Claude)",
|
|
22
|
+
"defaultModel": "claude-sonnet-4-6",
|
|
23
|
+
"color": "#A78BFA"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
JSON
|
|
27
|
+
chmod 600 $MOD8_CONFIG_DIR/providers.json
|
|
28
|
+
shell: 'mod8 dev:work-ask anthropic "what is mod8?"'
|
|
29
|
+
expect:
|
|
30
|
+
stdout_omits:
|
|
31
|
+
- "I am mod8"
|
|
32
|
+
- "I'm mod8"
|
|
33
|
+
- "mod8 is a"
|
|
34
|
+
# Should hand back, mention host, or say "ask mod8"
|
|
35
|
+
stdout_matches: "(?i)(host|mod8|/mod8|handing back)"
|
|
36
|
+
|
|
37
|
+
- name: work — "how do I add a new provider?" is deferred to host
|
|
38
|
+
requires_api_key: true
|
|
39
|
+
setup:
|
|
40
|
+
- shell: |
|
|
41
|
+
mkdir -p $MOD8_CONFIG_DIR
|
|
42
|
+
cat > $MOD8_CONFIG_DIR/providers.json <<JSON
|
|
43
|
+
{
|
|
44
|
+
"anthropic": {
|
|
45
|
+
"apiKey": "$ANTHROPIC_API_KEY",
|
|
46
|
+
"apiType": "anthropic",
|
|
47
|
+
"name": "Anthropic (Claude)",
|
|
48
|
+
"defaultModel": "claude-sonnet-4-6",
|
|
49
|
+
"color": "#A78BFA"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
JSON
|
|
53
|
+
chmod 600 $MOD8_CONFIG_DIR/providers.json
|
|
54
|
+
shell: 'mod8 dev:work-ask anthropic "how do I add a new provider to mod8?"'
|
|
55
|
+
expect:
|
|
56
|
+
# Deferral signals: hand-off phrasing OR pointing the user back to host.
|
|
57
|
+
stdout_matches: "(?i)(handing back|hand.+back|host|/mod8|mod8 question|ask mod8|that.+for mod8)"
|
|
58
|
+
stdout_omits:
|
|
59
|
+
- "Run `mod8 add-provider`"
|
|
60
|
+
- "use mod8 add-provider"
|
|
61
|
+
|
|
62
|
+
- name: work — coding task is answered fully, no hand-off
|
|
63
|
+
requires_api_key: true
|
|
64
|
+
setup:
|
|
65
|
+
- shell: |
|
|
66
|
+
mkdir -p $MOD8_CONFIG_DIR
|
|
67
|
+
cat > $MOD8_CONFIG_DIR/providers.json <<JSON
|
|
68
|
+
{
|
|
69
|
+
"anthropic": {
|
|
70
|
+
"apiKey": "$ANTHROPIC_API_KEY",
|
|
71
|
+
"apiType": "anthropic",
|
|
72
|
+
"name": "Anthropic (Claude)",
|
|
73
|
+
"defaultModel": "claude-sonnet-4-6",
|
|
74
|
+
"color": "#A78BFA"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
JSON
|
|
78
|
+
chmod 600 $MOD8_CONFIG_DIR/providers.json
|
|
79
|
+
shell: 'mod8 dev:work-ask anthropic "give me a one-line javascript array reverse"'
|
|
80
|
+
expect:
|
|
81
|
+
stdout_matches: "(?i)(reverse|slice\\(\\)\\.reverse)"
|
|
82
|
+
stdout_omits:
|
|
83
|
+
- "<SWITCH_TO_HOST>"
|
|
84
|
+
- "handing back"
|
|
85
|
+
- "mod8 question"
|
|
86
|
+
|
|
87
|
+
- name: work — "who are you" answers as the worker, not mod8
|
|
88
|
+
requires_api_key: true
|
|
89
|
+
setup:
|
|
90
|
+
- shell: |
|
|
91
|
+
mkdir -p $MOD8_CONFIG_DIR
|
|
92
|
+
cat > $MOD8_CONFIG_DIR/providers.json <<JSON
|
|
93
|
+
{
|
|
94
|
+
"anthropic": {
|
|
95
|
+
"apiKey": "$ANTHROPIC_API_KEY",
|
|
96
|
+
"apiType": "anthropic",
|
|
97
|
+
"name": "Anthropic (Claude)",
|
|
98
|
+
"defaultModel": "claude-sonnet-4-6",
|
|
99
|
+
"color": "#A78BFA"
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
JSON
|
|
103
|
+
chmod 600 $MOD8_CONFIG_DIR/providers.json
|
|
104
|
+
shell: 'mod8 dev:work-ask anthropic "who are you exactly?"'
|
|
105
|
+
expect:
|
|
106
|
+
stdout_matches: "(?i)claude"
|
|
107
|
+
stdout_omits:
|
|
108
|
+
- "I am mod8"
|
|
109
|
+
- "I'm mod8"
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
name: chat meta — mod8 knows itself
|
|
2
|
+
description: |
|
|
3
|
+
Regression suite for "mod8 doesn't know mod8" bug. The host system prompt
|
|
4
|
+
must include live data about the user's configured providers and the CLI's
|
|
5
|
+
feature surface, so meta questions get answered from facts — not pivoted
|
|
6
|
+
into "tell me about your project."
|
|
7
|
+
|
|
8
|
+
Tests use mod8 dev:host-ask, which runs a one-shot through the same host
|
|
9
|
+
system prompt the chat REPL uses. All require a real Anthropic key.
|
|
10
|
+
|
|
11
|
+
tests:
|
|
12
|
+
- name: meta — what is mod8
|
|
13
|
+
requires_api_key: true
|
|
14
|
+
setup:
|
|
15
|
+
- shell: |
|
|
16
|
+
mkdir -p $MOD8_CONFIG_DIR
|
|
17
|
+
cat > $MOD8_CONFIG_DIR/providers.json <<JSON
|
|
18
|
+
{
|
|
19
|
+
"anthropic": {
|
|
20
|
+
"apiKey": "$ANTHROPIC_API_KEY",
|
|
21
|
+
"apiType": "anthropic",
|
|
22
|
+
"name": "Anthropic (Claude)",
|
|
23
|
+
"defaultModel": "claude-sonnet-4-6",
|
|
24
|
+
"color": "#A78BFA"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
JSON
|
|
28
|
+
chmod 600 $MOD8_CONFIG_DIR/providers.json
|
|
29
|
+
shell: 'mod8 dev:host-ask "what is mod8?"'
|
|
30
|
+
expect:
|
|
31
|
+
stdout_contains:
|
|
32
|
+
- mod8
|
|
33
|
+
stdout_matches: "(BYOK|provider|terminal)"
|
|
34
|
+
|
|
35
|
+
- name: meta — what platforms are connected
|
|
36
|
+
requires_api_key: true
|
|
37
|
+
setup:
|
|
38
|
+
- shell: |
|
|
39
|
+
mkdir -p $MOD8_CONFIG_DIR
|
|
40
|
+
cat > $MOD8_CONFIG_DIR/providers.json <<JSON
|
|
41
|
+
{
|
|
42
|
+
"anthropic": {
|
|
43
|
+
"apiKey": "$ANTHROPIC_API_KEY",
|
|
44
|
+
"apiType": "anthropic",
|
|
45
|
+
"name": "Anthropic (Claude)",
|
|
46
|
+
"defaultModel": "claude-sonnet-4-6",
|
|
47
|
+
"color": "#A78BFA"
|
|
48
|
+
},
|
|
49
|
+
"openai": {
|
|
50
|
+
"apiKey": "sk-fake-test-not-called",
|
|
51
|
+
"apiType": "openai-compat",
|
|
52
|
+
"name": "OpenAI (GPT)",
|
|
53
|
+
"baseUrl": "https://api.openai.com/v1",
|
|
54
|
+
"defaultModel": "gpt-4o",
|
|
55
|
+
"color": "#10B981"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
JSON
|
|
59
|
+
chmod 600 $MOD8_CONFIG_DIR/providers.json
|
|
60
|
+
shell: 'mod8 dev:host-ask "what platforms are connected to you right now?"'
|
|
61
|
+
expect:
|
|
62
|
+
# Accept either provider ids ("anthropic", "openai") or display names
|
|
63
|
+
# ("Anthropic", "OpenAI"/"GPT") — the host may use either.
|
|
64
|
+
stdout_matches: "(?i)(anthropic|claude)"
|
|
65
|
+
stdout_contains:
|
|
66
|
+
- "configured"
|
|
67
|
+
|
|
68
|
+
- name: meta — user's custom display name is recognized
|
|
69
|
+
requires_api_key: true
|
|
70
|
+
setup:
|
|
71
|
+
- shell: |
|
|
72
|
+
mkdir -p $MOD8_CONFIG_DIR
|
|
73
|
+
cat > $MOD8_CONFIG_DIR/providers.json <<JSON
|
|
74
|
+
{
|
|
75
|
+
"anthropic": {
|
|
76
|
+
"apiKey": "$ANTHROPIC_API_KEY",
|
|
77
|
+
"apiType": "anthropic",
|
|
78
|
+
"name": "Anthropic (Claude)",
|
|
79
|
+
"defaultModel": "claude-sonnet-4-6",
|
|
80
|
+
"color": "#A78BFA"
|
|
81
|
+
},
|
|
82
|
+
"openai": {
|
|
83
|
+
"apiKey": "sk-fake-test-not-called",
|
|
84
|
+
"apiType": "openai-compat",
|
|
85
|
+
"name": "codex",
|
|
86
|
+
"baseUrl": "https://api.openai.com/v1",
|
|
87
|
+
"defaultModel": "gpt-4o",
|
|
88
|
+
"color": "#10B981"
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
JSON
|
|
92
|
+
chmod 600 $MOD8_CONFIG_DIR/providers.json
|
|
93
|
+
shell: 'mod8 dev:host-ask "what is codex?"'
|
|
94
|
+
expect:
|
|
95
|
+
stdout_contains:
|
|
96
|
+
- codex
|
|
97
|
+
stdout_matches: "(openai|gpt|provider)"
|
|
98
|
+
stdout_omits:
|
|
99
|
+
- "tell me about your project"
|
|
100
|
+
- "what is your project"
|
|
101
|
+
|
|
102
|
+
- name: meta — how do I add a new provider
|
|
103
|
+
requires_api_key: true
|
|
104
|
+
setup:
|
|
105
|
+
- shell: |
|
|
106
|
+
mkdir -p $MOD8_CONFIG_DIR
|
|
107
|
+
cat > $MOD8_CONFIG_DIR/providers.json <<JSON
|
|
108
|
+
{
|
|
109
|
+
"anthropic": {
|
|
110
|
+
"apiKey": "$ANTHROPIC_API_KEY",
|
|
111
|
+
"apiType": "anthropic",
|
|
112
|
+
"name": "Anthropic (Claude)",
|
|
113
|
+
"defaultModel": "claude-sonnet-4-6",
|
|
114
|
+
"color": "#A78BFA"
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
JSON
|
|
118
|
+
chmod 600 $MOD8_CONFIG_DIR/providers.json
|
|
119
|
+
shell: 'mod8 dev:host-ask "how do I add a new provider?"'
|
|
120
|
+
expect:
|
|
121
|
+
stdout_contains:
|
|
122
|
+
- "add-provider"
|
|
123
|
+
|
|
124
|
+
- name: meta — what models do I have
|
|
125
|
+
requires_api_key: true
|
|
126
|
+
setup:
|
|
127
|
+
- shell: |
|
|
128
|
+
mkdir -p $MOD8_CONFIG_DIR
|
|
129
|
+
cat > $MOD8_CONFIG_DIR/providers.json <<JSON
|
|
130
|
+
{
|
|
131
|
+
"anthropic": {
|
|
132
|
+
"apiKey": "$ANTHROPIC_API_KEY",
|
|
133
|
+
"apiType": "anthropic",
|
|
134
|
+
"name": "Anthropic (Claude)",
|
|
135
|
+
"defaultModel": "claude-sonnet-4-6",
|
|
136
|
+
"color": "#A78BFA"
|
|
137
|
+
},
|
|
138
|
+
"deepseek": {
|
|
139
|
+
"apiKey": "sk-fake",
|
|
140
|
+
"apiType": "openai-compat",
|
|
141
|
+
"name": "DeepSeek",
|
|
142
|
+
"baseUrl": "https://api.deepseek.com",
|
|
143
|
+
"defaultModel": "deepseek-chat",
|
|
144
|
+
"color": "#3B82F6"
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
JSON
|
|
148
|
+
chmod 600 $MOD8_CONFIG_DIR/providers.json
|
|
149
|
+
shell: 'mod8 dev:host-ask "what models do I have available?"'
|
|
150
|
+
expect:
|
|
151
|
+
stdout_contains:
|
|
152
|
+
- claude-sonnet-4-6
|
|
153
|
+
- deepseek-chat
|
|
154
|
+
|
|
155
|
+
- name: meta — what can you do mentions key features
|
|
156
|
+
requires_api_key: true
|
|
157
|
+
setup:
|
|
158
|
+
- shell: |
|
|
159
|
+
mkdir -p $MOD8_CONFIG_DIR
|
|
160
|
+
cat > $MOD8_CONFIG_DIR/providers.json <<JSON
|
|
161
|
+
{
|
|
162
|
+
"anthropic": {
|
|
163
|
+
"apiKey": "$ANTHROPIC_API_KEY",
|
|
164
|
+
"apiType": "anthropic",
|
|
165
|
+
"name": "Anthropic (Claude)",
|
|
166
|
+
"defaultModel": "claude-sonnet-4-6",
|
|
167
|
+
"color": "#A78BFA"
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
JSON
|
|
171
|
+
chmod 600 $MOD8_CONFIG_DIR/providers.json
|
|
172
|
+
shell: 'mod8 dev:host-ask "what can you do?"'
|
|
173
|
+
expect:
|
|
174
|
+
stdout_contains:
|
|
175
|
+
- mod8
|
|
176
|
+
stdout_matches: "(compare|chat|provider|--all)"
|
|
177
|
+
|
|
178
|
+
- name: meta — empty provider list is acknowledged honestly
|
|
179
|
+
requires_api_key: true
|
|
180
|
+
shell: 'mod8 dev:host-ask "what providers are configured?"'
|
|
181
|
+
expect:
|
|
182
|
+
stdout_matches: "(?i)(none|no providers|nothing|not configured|empty|haven't)"
|
|
183
|
+
|
|
184
|
+
- name: meta — does NOT pivot to user's project on bare meta question
|
|
185
|
+
requires_api_key: true
|
|
186
|
+
setup:
|
|
187
|
+
- shell: |
|
|
188
|
+
mkdir -p $MOD8_CONFIG_DIR
|
|
189
|
+
cat > $MOD8_CONFIG_DIR/providers.json <<JSON
|
|
190
|
+
{
|
|
191
|
+
"anthropic": {
|
|
192
|
+
"apiKey": "$ANTHROPIC_API_KEY",
|
|
193
|
+
"apiType": "anthropic",
|
|
194
|
+
"name": "Anthropic (Claude)",
|
|
195
|
+
"defaultModel": "claude-sonnet-4-6",
|
|
196
|
+
"color": "#A78BFA"
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
JSON
|
|
200
|
+
chmod 600 $MOD8_CONFIG_DIR/providers.json
|
|
201
|
+
shell: 'mod8 dev:host-ask "what is this?"'
|
|
202
|
+
expect:
|
|
203
|
+
stdout_contains: mod8
|
|
204
|
+
stdout_omits:
|
|
205
|
+
- "tell me about your project"
|
|
206
|
+
- "what are you building"
|
|
207
|
+
|
|
208
|
+
- name: meta — typo'd "operator" phrasing still recognized
|
|
209
|
+
requires_api_key: true
|
|
210
|
+
setup:
|
|
211
|
+
- shell: |
|
|
212
|
+
mkdir -p $MOD8_CONFIG_DIR
|
|
213
|
+
cat > $MOD8_CONFIG_DIR/providers.json <<JSON
|
|
214
|
+
{
|
|
215
|
+
"anthropic": {
|
|
216
|
+
"apiKey": "$ANTHROPIC_API_KEY",
|
|
217
|
+
"apiType": "anthropic",
|
|
218
|
+
"name": "Anthropic (Claude)",
|
|
219
|
+
"defaultModel": "claude-sonnet-4-6",
|
|
220
|
+
"color": "#A78BFA"
|
|
221
|
+
},
|
|
222
|
+
"openai": {
|
|
223
|
+
"apiKey": "sk-fake",
|
|
224
|
+
"apiType": "openai-compat",
|
|
225
|
+
"name": "codex",
|
|
226
|
+
"baseUrl": "https://api.openai.com/v1",
|
|
227
|
+
"defaultModel": "gpt-4o",
|
|
228
|
+
"color": "#10B981"
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
JSON
|
|
232
|
+
chmod 600 $MOD8_CONFIG_DIR/providers.json
|
|
233
|
+
# The exact user-reported phrasing that triggered the original bug.
|
|
234
|
+
shell: 'mod8 dev:host-ask "hi how many operator you connect 2 ?"'
|
|
235
|
+
expect:
|
|
236
|
+
stdout_contains:
|
|
237
|
+
- "2"
|
|
238
|
+
stdout_omits:
|
|
239
|
+
- "I don't have info"
|
|
240
|
+
- "I don't have details"
|
|
241
|
+
- "tell me about your project"
|
|
242
|
+
- "what are you building"
|
|
243
|
+
|
|
244
|
+
- name: meta — never says "I don't have details about my setup"
|
|
245
|
+
requires_api_key: true
|
|
246
|
+
setup:
|
|
247
|
+
- shell: |
|
|
248
|
+
mkdir -p $MOD8_CONFIG_DIR
|
|
249
|
+
cat > $MOD8_CONFIG_DIR/providers.json <<JSON
|
|
250
|
+
{
|
|
251
|
+
"anthropic": {
|
|
252
|
+
"apiKey": "$ANTHROPIC_API_KEY",
|
|
253
|
+
"apiType": "anthropic",
|
|
254
|
+
"name": "Anthropic (Claude)",
|
|
255
|
+
"defaultModel": "claude-sonnet-4-6",
|
|
256
|
+
"color": "#A78BFA"
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
JSON
|
|
260
|
+
chmod 600 $MOD8_CONFIG_DIR/providers.json
|
|
261
|
+
shell: 'mod8 dev:host-ask "what is powering you?"'
|
|
262
|
+
expect:
|
|
263
|
+
stdout_omits:
|
|
264
|
+
- "I don't have info"
|
|
265
|
+
- "I don't have details"
|
|
266
|
+
- "what's powering me"
|
|
267
|
+
|
|
268
|
+
# ------------------------------------------------------------
|
|
269
|
+
# Routing / synonym tests — deterministic, no LLM involved.
|
|
270
|
+
# Use mod8 dev:resolve, which runs the chat REPL's
|
|
271
|
+
# parseProviderRoute + resolveProviderHint and prints the result.
|
|
272
|
+
# ------------------------------------------------------------
|
|
273
|
+
|
|
274
|
+
- name: routing — "use deepseek" matches and resolves to deepseek
|
|
275
|
+
shell: 'mod8 dev:resolve "use deepseek"'
|
|
276
|
+
expect:
|
|
277
|
+
stdout_contains: "route id=deepseek resolved=deepseek"
|
|
278
|
+
|
|
279
|
+
- name: routing — "let me talk to gpt" maps to openai
|
|
280
|
+
shell: 'mod8 dev:resolve "let me talk to gpt"'
|
|
281
|
+
expect:
|
|
282
|
+
stdout_contains: "route id=gpt resolved=openai"
|
|
283
|
+
|
|
284
|
+
- name: routing — "talk to claude" maps to anthropic
|
|
285
|
+
shell: 'mod8 dev:resolve "talk to claude"'
|
|
286
|
+
expect:
|
|
287
|
+
stdout_contains: "route id=claude resolved=anthropic"
|
|
288
|
+
|
|
289
|
+
- name: routing — display-name match resolves to canonical id
|
|
290
|
+
setup:
|
|
291
|
+
- shell: |
|
|
292
|
+
mkdir -p $MOD8_CONFIG_DIR
|
|
293
|
+
cat > $MOD8_CONFIG_DIR/providers.json <<JSON
|
|
294
|
+
{
|
|
295
|
+
"openai": {
|
|
296
|
+
"apiKey": "sk-fake",
|
|
297
|
+
"apiType": "openai-compat",
|
|
298
|
+
"name": "codex",
|
|
299
|
+
"baseUrl": "https://api.openai.com/v1",
|
|
300
|
+
"defaultModel": "gpt-4o",
|
|
301
|
+
"color": "#10B981"
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
JSON
|
|
305
|
+
chmod 600 $MOD8_CONFIG_DIR/providers.json
|
|
306
|
+
shell: 'mod8 dev:resolve "let me talk to codex"'
|
|
307
|
+
expect:
|
|
308
|
+
stdout_contains: "route id=codex resolved=openai"
|
|
309
|
+
|
|
310
|
+
- name: 'routing — "ask grok: <prompt>" carries the rest payload'
|
|
311
|
+
shell: 'mod8 dev:resolve "ask grok: what is the weather"'
|
|
312
|
+
expect:
|
|
313
|
+
stdout_contains: "route id=grok resolved=xai"
|
|
314
|
+
stdout_matches: 'rest="what is the weather"'
|
|
315
|
+
|
|
316
|
+
- name: routing — "switch over to mistral" matches
|
|
317
|
+
shell: 'mod8 dev:resolve "switch over to mistral"'
|
|
318
|
+
expect:
|
|
319
|
+
stdout_contains: "route id=mistral resolved=mistral"
|
|
320
|
+
|
|
321
|
+
- name: routing — non-route input falls through cleanly
|
|
322
|
+
shell: 'mod8 dev:resolve "what is the weather today"'
|
|
323
|
+
expect:
|
|
324
|
+
stdout_contains: "none"
|
|
325
|
+
|
|
326
|
+
- name: 'routing — "compare all: <prompt>" extracts payload'
|
|
327
|
+
shell: 'mod8 dev:resolve "compare all: write a haiku"'
|
|
328
|
+
expect:
|
|
329
|
+
stdout_matches: 'compare payload="write a haiku"'
|
|
330
|
+
|
|
331
|
+
- name: 'routing — "/compare <prompt>" extracts payload'
|
|
332
|
+
shell: 'mod8 dev:resolve "/compare write a haiku"'
|
|
333
|
+
expect:
|
|
334
|
+
stdout_matches: 'compare payload="write a haiku"'
|
|
335
|
+
|
|
336
|
+
- name: routing — bare "compare all" is recognized
|
|
337
|
+
shell: 'mod8 dev:resolve "compare all"'
|
|
338
|
+
expect:
|
|
339
|
+
stdout_contains: "compare-bare"
|
|
340
|
+
|
|
341
|
+
- name: routing — unknown id resolves to null
|
|
342
|
+
shell: 'mod8 dev:resolve "use unknownprovider"'
|
|
343
|
+
expect:
|
|
344
|
+
stdout_contains: "route id=unknownprovider resolved=null"
|
|
345
|
+
|
|
346
|
+
- name: routing — display-name "Anthropic (Claude)" resolves
|
|
347
|
+
shell: 'mod8 dev:resolve "use anthropic"'
|
|
348
|
+
expect:
|
|
349
|
+
stdout_contains: "route id=anthropic resolved=anthropic"
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
name: chat startup behavior
|
|
2
|
+
description: |
|
|
3
|
+
Bare `mod8` opens a NEW chat session, not the most-recent one. Resume is
|
|
4
|
+
explicit — `mod8 resume` (no args) for most-recent, `mod8 resume <id>` for a
|
|
5
|
+
specific session.
|
|
6
|
+
|
|
7
|
+
Drives the CLI through piped stdin so Ink crashes on raw-mode entry, but
|
|
8
|
+
whatever printed BEFORE the crash is captured for assertion. In particular,
|
|
9
|
+
the "resuming session …" line is printed by runChat() before render(),
|
|
10
|
+
so its presence/absence is observable even without a real PTY.
|
|
11
|
+
|
|
12
|
+
tests:
|
|
13
|
+
- name: bare mod8 with prior sessions does NOT resume
|
|
14
|
+
requires_api_key: true
|
|
15
|
+
setup:
|
|
16
|
+
- shell: |
|
|
17
|
+
mkdir -p $MOD8_CONFIG_DIR/sessions
|
|
18
|
+
cat > $MOD8_CONFIG_DIR/sessions/2026-05-07-prev.json <<JSON
|
|
19
|
+
{
|
|
20
|
+
"version": 1,
|
|
21
|
+
"id": "2026-05-07-prev",
|
|
22
|
+
"title": "old session about something",
|
|
23
|
+
"createdAt": 1715000000000,
|
|
24
|
+
"lastActivity": 1715000000000,
|
|
25
|
+
"messages": [
|
|
26
|
+
{"role": "user", "content": "remember the keyword fuchsia", "mode": "host"},
|
|
27
|
+
{"role": "assistant", "content": "got it, fuchsia.", "mode": "host"}
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
JSON
|
|
31
|
+
chmod 600 $MOD8_CONFIG_DIR/sessions/2026-05-07-prev.json
|
|
32
|
+
repl:
|
|
33
|
+
run: mod8
|
|
34
|
+
timeout_ms: 6000
|
|
35
|
+
inputs:
|
|
36
|
+
- send: ""
|
|
37
|
+
delay_ms: 1500
|
|
38
|
+
expect:
|
|
39
|
+
# Welcome banner must show, but the resuming-session line must NOT.
|
|
40
|
+
stdout_contains:
|
|
41
|
+
- "switch to claude"
|
|
42
|
+
stdout_omits:
|
|
43
|
+
- "resuming session"
|
|
44
|
+
- "fuchsia"
|
|
45
|
+
- "old session about something"
|
|
46
|
+
|
|
47
|
+
- name: mod8 new also opens a fresh session (no resume line)
|
|
48
|
+
requires_api_key: true
|
|
49
|
+
setup:
|
|
50
|
+
- shell: |
|
|
51
|
+
mkdir -p $MOD8_CONFIG_DIR/sessions
|
|
52
|
+
cat > $MOD8_CONFIG_DIR/sessions/2026-05-07-prev.json <<JSON
|
|
53
|
+
{
|
|
54
|
+
"version": 1,
|
|
55
|
+
"id": "2026-05-07-prev",
|
|
56
|
+
"title": "old",
|
|
57
|
+
"createdAt": 1715000000000,
|
|
58
|
+
"lastActivity": 1715000000000,
|
|
59
|
+
"messages": [
|
|
60
|
+
{"role": "user", "content": "x", "mode": "host"},
|
|
61
|
+
{"role": "assistant", "content": "y", "mode": "host"}
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
JSON
|
|
65
|
+
chmod 600 $MOD8_CONFIG_DIR/sessions/2026-05-07-prev.json
|
|
66
|
+
repl:
|
|
67
|
+
run: mod8 new
|
|
68
|
+
timeout_ms: 6000
|
|
69
|
+
inputs:
|
|
70
|
+
- send: ""
|
|
71
|
+
delay_ms: 1500
|
|
72
|
+
expect:
|
|
73
|
+
stdout_contains:
|
|
74
|
+
- "switch to claude"
|
|
75
|
+
stdout_omits:
|
|
76
|
+
- "resuming session"
|
|
77
|
+
|
|
78
|
+
- name: mod8 resume (no id) resumes the most recent session
|
|
79
|
+
requires_api_key: true
|
|
80
|
+
setup:
|
|
81
|
+
- shell: |
|
|
82
|
+
mkdir -p $MOD8_CONFIG_DIR/sessions
|
|
83
|
+
cat > $MOD8_CONFIG_DIR/sessions/2026-05-07-prev.json <<JSON
|
|
84
|
+
{
|
|
85
|
+
"version": 1,
|
|
86
|
+
"id": "2026-05-07-prev",
|
|
87
|
+
"title": "the recent session",
|
|
88
|
+
"createdAt": 1715000000000,
|
|
89
|
+
"lastActivity": 1715000000000,
|
|
90
|
+
"messages": [
|
|
91
|
+
{"role": "user", "content": "x", "mode": "host"},
|
|
92
|
+
{"role": "assistant", "content": "y", "mode": "host"}
|
|
93
|
+
]
|
|
94
|
+
}
|
|
95
|
+
JSON
|
|
96
|
+
chmod 600 $MOD8_CONFIG_DIR/sessions/2026-05-07-prev.json
|
|
97
|
+
repl:
|
|
98
|
+
run: mod8 resume
|
|
99
|
+
timeout_ms: 6000
|
|
100
|
+
inputs:
|
|
101
|
+
- send: ""
|
|
102
|
+
delay_ms: 1500
|
|
103
|
+
expect:
|
|
104
|
+
stdout_contains:
|
|
105
|
+
- "resuming session 2026-05-07-prev"
|
|
106
|
+
|
|
107
|
+
- name: mod8 resume on empty store is rejected, not silently new
|
|
108
|
+
repl:
|
|
109
|
+
run: mod8 resume
|
|
110
|
+
timeout_ms: 6000
|
|
111
|
+
inputs:
|
|
112
|
+
- send: ""
|
|
113
|
+
delay_ms: 1500
|
|
114
|
+
expect:
|
|
115
|
+
stderr_contains:
|
|
116
|
+
- "no sessions to resume"
|
|
117
|
+
- "mod8 list"
|
|
118
|
+
exit_code: 1
|
|
119
|
+
|
|
120
|
+
- name: mod8 resume <id> still works
|
|
121
|
+
requires_api_key: true
|
|
122
|
+
setup:
|
|
123
|
+
- shell: |
|
|
124
|
+
mkdir -p $MOD8_CONFIG_DIR/sessions
|
|
125
|
+
cat > $MOD8_CONFIG_DIR/sessions/2026-05-07-spec.json <<JSON
|
|
126
|
+
{
|
|
127
|
+
"version": 1,
|
|
128
|
+
"id": "2026-05-07-spec",
|
|
129
|
+
"title": "specific",
|
|
130
|
+
"createdAt": 1715000000000,
|
|
131
|
+
"lastActivity": 1715000000000,
|
|
132
|
+
"messages": [
|
|
133
|
+
{"role": "user", "content": "marker fuchsia-bytemark", "mode": "host"},
|
|
134
|
+
{"role": "assistant", "content": "noted", "mode": "host"}
|
|
135
|
+
]
|
|
136
|
+
}
|
|
137
|
+
JSON
|
|
138
|
+
chmod 600 $MOD8_CONFIG_DIR/sessions/2026-05-07-spec.json
|
|
139
|
+
repl:
|
|
140
|
+
run: mod8 resume 2026-05-07-spec
|
|
141
|
+
timeout_ms: 6000
|
|
142
|
+
inputs:
|
|
143
|
+
- send: ""
|
|
144
|
+
delay_ms: 1500
|
|
145
|
+
expect:
|
|
146
|
+
stdout_contains:
|
|
147
|
+
- "resuming session 2026-05-07-spec"
|
|
148
|
+
- "marker fuchsia-bytemark"
|