@asobacloud/nehanda 0.7.0 → 0.9.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/README.md +52 -19
- package/bin/agent.mjs +51 -37
- package/bin/nehanda-ui.mjs +256 -79
- package/lib/configureProvider.mjs +4 -3
- package/lib/explore.mjs +109 -0
- package/lib/issues.mjs +144 -0
- package/lib/laya_sidecar.mjs +11 -5
- package/lib/modelPicker.mjs +124 -0
- package/lib/openaiCompat.mjs +17 -1
- package/lib/orchestrate.mjs +160 -23
- package/lib/scripts/laya_worker.py +7 -2
- package/lib/slashCommands.mjs +128 -0
- package/lib/store.mjs +8 -0
- package/lib/ui.mjs +24 -7
- package/lib/workflow.mjs +18 -6
- package/package.json +1 -1
- package/scripts/build-artifacts.sh +81 -0
- package/scripts/install.sh +51 -0
- package/lib/scripts/__pycache__/laya_worker.cpython-314.pyc +0 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Nehanda Command-Line Interface (`nehanda-cli`)
|
|
2
2
|
|
|
3
|
-
An agentic terminal REPL and single-process engine built for energy systems research and software development. `nehanda-cli`
|
|
3
|
+
An agentic terminal REPL and single-process engine built for energy systems research and software development. `nehanda-cli` works with Ollama, LM Studio, OpenRouter, Anthropic Claude, ZhipuAI, or any OpenAI-compatible API — local or cloud. It normalizes multi-OEM energy telemetry through the ODSE protocol and enforces a governed development workflow across the full research-to-production lifecycle.
|
|
4
4
|
|
|
5
5
|
Every conversation turn, tool call, phase transition, and permission check is stored in a local, queryable SQLite database you own, ensuring complete transcripts exist for auditing and debugging.
|
|
6
6
|
|
|
@@ -94,7 +94,7 @@ Local Ollama / LM Studio: pick that provider in `/model`, accept or edit the hos
|
|
|
94
94
|
|
|
95
95
|
* **Dynamic Tool Rescue (`[TOOL_CALL]`):** Native support for endpoints that strip OpenAI tool schemas (such as `nehandaMlProxy`). The engine dynamically injects active tool schemas directly into system prompts as `[TOOL_CALL]` blocks, parsing and executing tools locally without server-side function-calling support. Uses `[TOOL_CALL]` delimiters instead of `<tool_call>` XML to prevent vLLM's `--tool-call-parser qwen3_xml` stop-token interception.
|
|
96
96
|
|
|
97
|
-
* **Deterministic SDLC Workflow:** Enforces a
|
|
97
|
+
* **Deterministic SDLC Workflow:** Enforces a 7-phase state machine (`idle` → `explore` → `planning` → `test` → `implement` → `test` → `verify` → `done`) following the AGENTS.md TDD mandate. Transitions are gated: `planning → implement` is blocked until an approved plan exists and at least one RED behavioral test is recorded; `test → verify` requires all tests passing and safety checkers clean.
|
|
98
98
|
|
|
99
99
|
* **Interactive Safety Interventions:** When execution checkers flag a command — opening local sockets, inspecting HIL testbed hardware paths, or writing outside the working directory — the engine raises a structured `{ status, reason, risk_score, proposed_remediation }` payload rather than crashing. In the TUI, blocked operations surface a `BlockConfirmMenu` (`[A] Approve for Session | [S] Run in Laya Sandbox | [C] Cancel`) wired directly into the Ink event loop. Session approvals are cached so the same operation is not re-prompted within a session. Headless pipe mode auto-denies without prompting.
|
|
100
100
|
|
|
@@ -102,7 +102,7 @@ Local Ollama / LM Studio: pick that provider in `/model`, accept or edit the hos
|
|
|
102
102
|
|
|
103
103
|
* **Runtime Visibility:** `RuntimeHealthMonitor` probes active inference endpoints (1s HEAD request) and checks Laya sidecar liveness before each session. The TUI status header shows active model, endpoint health and latency, sidecar PID, and context usage percentage — so researchers have immediate visual confirmation of whether the target backend is an internal local host or an external cloud endpoint before uploading grid telemetry.
|
|
104
104
|
|
|
105
|
-
* **Process Legibility:** Every tool call is visible while it runs. Tool-start lines show argument summaries (`⚙ Read(ui.mjs)`, `⚙ Bash(git status)`), a live spinner with plain-English verbs tracks activity between outputs (`⠋ Analyzing…`, `⠙ Reading ui.mjs…`), and tool results carry honest context suffixes (`✓ Read: … (N lines)`, `✓ Bash: … (240ms)`, `✓ Write: … (N chars)`) — never fabricated data. A dim `esc to cancel` hint stays available while a turn runs, and result previews are capped at 120 characters so raw output never floods the screen. Backed by `summarizeToolArgs`, `verbForTool`, `formatToolResultSuffix`, and `TOOL_VERBS` in `lib/ui.mjs`, with a live spinner channel (`bridge.updateSpinner`) in the Ink TUI.
|
|
105
|
+
* **Process Legibility:** Every tool call is visible while it runs. Tool-start lines show argument summaries (`⚙ Read(ui.mjs)`, `⚙ Bash(git status)`), a live spinner with plain-English verbs tracks activity between outputs (`⠋ Analyzing…`, `⠙ Reading ui.mjs…`), and tool results carry honest context suffixes (`✓ Read: … (N lines)`, `✓ Bash: … (240ms)`, `✓ Write: … (N chars)`) — never fabricated data. When a reasoning model (Qwen3, Gemma) is thinking, the spinner shows live token progress (`⠋ Thinking (42 tokens)…`) rather than a frozen screen. Pressing Esc immediately updates the spinner to `Cancelling…` before the in-flight request is torn down. A dim `esc to cancel` hint stays available while a turn runs, and result previews are capped at 120 characters so raw output never floods the screen. Backed by `summarizeToolArgs`, `verbForTool`, `formatToolResultSuffix`, and `TOOL_VERBS` in `lib/ui.mjs`, with a live spinner channel (`bridge.updateSpinner`) in the Ink TUI.
|
|
106
106
|
|
|
107
107
|
* **Interactive TUI & Pipe Support:** Rich Ink-based TUI (`bin/nehanda-ui.mjs`) for interactive development sessions, with headless pipe-mode support (`bin/agent.mjs`) for acceptance testing and automation.
|
|
108
108
|
|
|
@@ -343,10 +343,10 @@ Place the script at `lib/scripts/my-new-tool.py`. It will receive arguments inte
|
|
|
343
343
|
| `/mcp list` | Connect to each server and enumerate available tools |
|
|
344
344
|
| `/mcp reload [server]` | Re-read `mcp.json` and bust the tool cache (optionally for one server) |
|
|
345
345
|
| `/mcp add <name> <command> [args…]` | Register a new server and save it to `~/.config/nehanda/mcp.json` |
|
|
346
|
-
| `/mcp env` | Show environment variables for all configured servers |
|
|
346
|
+
| `/mcp env` | Show environment variables for all configured servers (values masked) |
|
|
347
347
|
| `/mcp env <server>` | Show environment variables for a specific server |
|
|
348
|
-
| `/mcp env <server> <
|
|
349
|
-
| `/mcp env <server> <
|
|
348
|
+
| `/mcp env <server> <NAME> <VALUE>` | Set an environment variable (e.g. an API key) |
|
|
349
|
+
| `/mcp env unset <server> <NAME>` | Remove an environment variable (`clear` is also accepted) |
|
|
350
350
|
|
|
351
351
|
---
|
|
352
352
|
|
|
@@ -388,18 +388,18 @@ Many MCP servers require an API key or other secrets. Rather than editing `mcp.j
|
|
|
388
388
|
```
|
|
389
389
|
❯ /mcp env # show env vars for all servers
|
|
390
390
|
❯ /mcp env asoba # show env vars for 'asoba' server
|
|
391
|
-
❯ /mcp env asoba ASOBA_API_KEY sk-abc123... # set the key
|
|
391
|
+
❯ /mcp env asoba ASOBA_API_KEY sk-abc123... # set the key (NAME + VALUE)
|
|
392
392
|
✓ Set ASOBA_API_KEY = ******** on asoba
|
|
393
393
|
Config file: ~/.config/nehanda/mcp.json
|
|
394
394
|
```
|
|
395
395
|
|
|
396
396
|
This writes the value to the `mcp.json` file where the server is defined (project-local `./mcp.json` takes priority over global `~/.config/nehanda/mcp.json`), then automatically reloads the server so the change takes effect immediately.
|
|
397
397
|
|
|
398
|
-
To
|
|
398
|
+
To remove a key (explicit unset — omitting VALUE no longer clears):
|
|
399
399
|
|
|
400
400
|
```
|
|
401
|
-
❯ /mcp env asoba ASOBA_API_KEY
|
|
402
|
-
✓
|
|
401
|
+
❯ /mcp env unset asoba ASOBA_API_KEY
|
|
402
|
+
✓ Unset ASOBA_API_KEY on asoba
|
|
403
403
|
```
|
|
404
404
|
|
|
405
405
|
**Tip:** Keys are masked in `/mcp env` output. Only the first 6 and last 4 characters are shown.
|
|
@@ -430,19 +430,23 @@ Numeric values are auto-converted. Changes persist in the local settings databas
|
|
|
430
430
|
|
|
431
431
|
## SDLC Workflow
|
|
432
432
|
|
|
433
|
-
The engine enforces state transitions across
|
|
433
|
+
The engine enforces state transitions across seven distinct phases, aligned with the AGENTS.md TDD mandate:
|
|
434
434
|
|
|
435
|
-
1. **`idle`**: Discovery and triage. Mutating file tools are
|
|
435
|
+
1. **`idle`**: Discovery and triage. Mutating file tools are available. Safety and analysis tools are available on-demand.
|
|
436
436
|
|
|
437
|
-
2. **`
|
|
437
|
+
2. **`explore`**: Codebase exploration only. `lib/explore.mjs` drives this phase — the model uses Glob, Grep, and Read to aggregate file summaries before entering planning. Ends when the model outputs `Exploration complete.`
|
|
438
438
|
|
|
439
|
-
3. **`
|
|
439
|
+
3. **`planning`**: Model formulates a plan using exploration summaries (`EnterPlanMode`). Mutating tools are blocked until a plan is approved. No code changes permitted.
|
|
440
440
|
|
|
441
|
-
4. **`test`**:
|
|
441
|
+
4. **`test`**: Behavioral test creation and RED confirmation. The model writes tests and runs them to confirm they fail for the diagnosed reason. **Gated:** `planning → implement` is blocked until at least one RED test result is recorded here.
|
|
442
442
|
|
|
443
|
-
5. **`
|
|
443
|
+
5. **`implement`**: Code changes applied using file editing and shell execution (`ExitPlanMode`). Only reachable after a confirmed RED test exists and a plan is approved.
|
|
444
444
|
|
|
445
|
-
6. **`
|
|
445
|
+
6. **`test`**: Automated test execution to verify GREEN (`SubmitImplementation`). **All tools marked `mandatory_in: ["test"]` must run and pass** before this phase can advance — the system prompt enforces this.
|
|
446
|
+
|
|
447
|
+
7. **`verify`**: Inspection of test outputs and coverage verification (`SubmitTest`). **Gated:** requires all test results passing and at least one safety checker event recorded.
|
|
448
|
+
|
|
449
|
+
8. **`done`**: Final sign-off and git commit creation.
|
|
446
450
|
|
|
447
451
|
---
|
|
448
452
|
|
|
@@ -494,11 +498,40 @@ node tests/unit/gap4_pinned_compaction_behavioral.mjs
|
|
|
494
498
|
node tests/unit/ui_legibility_behavioral.mjs
|
|
495
499
|
```
|
|
496
500
|
|
|
501
|
+
Run the Ollama, spinner, and Laya behavioral suites:
|
|
502
|
+
|
|
503
|
+
```bash
|
|
504
|
+
node tests/unit/test-ollama-llm-call-behavioral.mjs
|
|
505
|
+
node tests/unit/test-ollama-permission-prompt-behavioral.mjs
|
|
506
|
+
node tests/unit/laya_warm_load_behavioral.mjs
|
|
507
|
+
node tests/unit/issue22_query_context_behavioral.mjs
|
|
508
|
+
```
|
|
509
|
+
|
|
497
510
|
---
|
|
498
511
|
|
|
499
512
|
## Changelog
|
|
500
513
|
|
|
501
|
-
### 0.
|
|
514
|
+
### 0.9.0
|
|
515
|
+
|
|
516
|
+
- **AGENTS.md SDLC workflow + local issue tracking (`lib/workflow.mjs`, `lib/issues.mjs`, `lib/explore.mjs`):** The orchestrator now enforces the full E-P-C-V-D workflow defined in AGENTS.md — Explore, Plan, Confirm, Code, Validate. A local `.issues/` directory tracks open and closed issues with structured metadata (observed behavior, diagnosis, acceptance criteria). `lib/explore.mjs` drives the EXPLORE phase, aggregating file summaries before entering PLANNING. Closes #15.
|
|
517
|
+
|
|
518
|
+
- **Generic agent config restored to `~/.config/nehanda` (`lib/settings.mjs`, `scripts/postinstall.mjs`):** Removed the `.aimee` dependency and all references to the legacy agent registry. Agent configuration now lives exclusively at `~/.config/nehanda/agents.json`, consistent with the documented config file locations. Closes #16.
|
|
519
|
+
|
|
520
|
+
- **Working directory in welcome banner and footer (`bin/nehanda-ui.mjs`):** The TUI banner displayed on startup and the status footer both now show the current working directory, giving researchers immediate confirmation of which project root is active before issuing any commands. Closes #17.
|
|
521
|
+
|
|
522
|
+
- **Spinner stabilization: AskPrompt, spitfire suppression, null-safe `askHuman` (`bin/nehanda-ui.mjs`):** Fixed three related spinner regressions: (1) the spinner was visible and cycling behind interactive `io.ask` permission prompts, obscuring the question; (2) rapid `bridge.updateSpinner` calls between tool results caused visible label flicker (spitfire); (3) `askHuman` threw on a null `io` reference in headless contexts. Closes #18.
|
|
523
|
+
|
|
524
|
+
- **Ollama LLM call path: behavioral coverage + documentation (`lib/orchestrate.mjs`, `tests/unit/test-ollama-llm-call-behavioral.mjs`, `docs/MAP.md`):** Full behavioral RED/GREEN test suite for the Ollama call path — `detectOllamaToolSupport` probe logic (capabilities, template, modelfile fields, HTTP error fail-open), native vs. manual tool injection dispatch, `Authorization: Bearer ollama` header, `OLLAMA_BASE_URL` env var resolution, and `buildSystemPrompt` injection format. `docs/MAP.md` documents the full call path contract. Closes #19.
|
|
525
|
+
|
|
526
|
+
- **Turn-start and probe spinner stabilization (`bin/nehanda-ui.mjs`, `lib/orchestrate.mjs`):** Prior to this fix, the spinner had no label during the gap between turn submission and the first model event — cycling generic `TOOL_VERBS` while Laya intent classification and the Ollama `detectOllamaToolSupport` probe (up to 3 s) ran silently. Fixed by pre-seeding `bridge.updateSpinner('Analyzing')` immediately on submit and `io.spinner.start('Analyzing')` at `runSdlcOpenAICompat` entry. Behavioral coverage in `tests/unit/test-ollama-permission-prompt-behavioral.mjs`. Closes #20.
|
|
527
|
+
|
|
528
|
+
- **Laya sidecar startup warming (`lib/laya_sidecar.mjs`):** `ensureLayaReady()` now calls `loadLaya()` (`method: 'load'`) instead of `pingLaya()` (`method: 'ping'`). The `ping` path responded immediately without loading model weights, leaving the first user-turn `classifyUserIntent` call to cold-load the 322M-parameter checkpoint mid-turn (200–400 ms stall). `load` invokes `_ensure_loaded()` in `laya_worker.py`, priming both `noul` and `choice` criteria into MPS/CPU RAM before the first turn. Behavioral coverage in `tests/unit/laya_warm_load_behavioral.mjs`. Closes #21.
|
|
529
|
+
|
|
530
|
+
- **Grounded query context, Ollama `think:false`, reasoning token streaming, instant Esc cancel (`lib/orchestrate.mjs`, `lib/openaiCompat.mjs`, `bin/nehanda-ui.mjs`):** Four improvements to reduce reasoning model overhead and improve cancellation UX: (1) `buildSystemPrompt` appends an explicit `[GROUNDED FACTS]` block (date, cwd, platform, shell) to every phase prompt so models never spend tokens debating their own awareness; (2) `streamOpenAIChatCompletion` now parses `delta.reasoning` and `delta.reasoning_content` SSE fields and updates the spinner live — `⠋ Thinking (42 tokens)…` — rather than sitting frozen during the thinking phase; (3) a `think: false` option is forwarded to the Ollama API request body when `model_config.thinking === false`, disabling chain-of-thought for supported models; (4) `detectOllamaToolSupport` now accepts `rt.signal` composed via `AbortSignal.any([signal, AbortSignal.timeout(3000)])` so Esc cancels the capability probe immediately rather than waiting the full 3 s timeout, and `bridge.abortCurrent` updates the spinner to `Cancelling…` before invoking `controller.abort()`. Behavioral coverage in `tests/unit/issue22_query_context_behavioral.mjs`. Closes #22.
|
|
531
|
+
|
|
532
|
+
### 0.8.0
|
|
533
|
+
|
|
534
|
+
- **Full `/help` + explicit MCP env unset (`lib/slashCommands.mjs`, `bin/nehanda-ui.mjs`):** `/help` lists every command and subcommand (`/key` variants, full `/mcp` surface). Omitting a VALUE no longer silently clears env vars — use `/mcp env unset <server> <NAME>` (or `clear`). Autocomplete stays top-level unless you type `/mcp` or `/key`. Behavioral coverage in `tests/unit/help_commands_behavioral.mjs`. Closes #9.
|
|
502
535
|
|
|
503
536
|
- **Unified `/model` configure wizard with keyed discovery (`lib/configureProvider.mjs`, `lib/modelDiscovery.mjs`, `bin/nehanda-ui.mjs`):** Provider setup is a single Goose-style flow — provider → API key → host → live model catalog → select → persist. OpenRouter / OpenAI-compat / Zhipu / Nehanda / LM Studio discover via authenticated `GET {base}/models`; Ollama keeps `/api/tags`. Discovery failure falls back to custom model name. `/key <provider>` chains into the same wizard tail. `/config` is read-only status. Behavioral coverage in `tests/unit/configure_provider_behavioral.mjs`. Closes #8.
|
|
504
537
|
|
|
@@ -539,7 +572,7 @@ node tests/unit/ui_legibility_behavioral.mjs
|
|
|
539
572
|
### 0.2.0
|
|
540
573
|
- Jev-Mem memory architecture (System-1 Laya control plane)
|
|
541
574
|
- Dynamic Tool Rescue (`[TOOL_CALL]` delimiter path)
|
|
542
|
-
- Deterministic
|
|
575
|
+
- Deterministic SDLC workflow (expanded to 7 phases in 0.9.0)
|
|
543
576
|
- Multi-provider switching
|
|
544
577
|
|
|
545
578
|
---
|
package/bin/agent.mjs
CHANGED
|
@@ -21,6 +21,7 @@ import { executeBuiltinTool, invalidateMcpToolCache } from '../lib/tools.mjs'
|
|
|
21
21
|
import { appendEntry, makeUserPayload, makeToolResultPayload } from '../lib/transcript.mjs'
|
|
22
22
|
import { loadInstructions } from '../lib/instructions.mjs'
|
|
23
23
|
import { discoverModelsForProvider, formatModelList } from '../lib/modelDiscovery.mjs'
|
|
24
|
+
import { parseMcpEnvParts } from '../lib/slashCommands.mjs'
|
|
24
25
|
import { compactConversation } from '../lib/compact.mjs'
|
|
25
26
|
import { createTeam, getTeam, listTeams, deleteTeam } from '../lib/team.mjs'
|
|
26
27
|
import { ensureLayaReady } from '../lib/laya_sidecar.mjs'
|
|
@@ -165,16 +166,19 @@ Tools:
|
|
|
165
166
|
/team Manage teams
|
|
166
167
|
|
|
167
168
|
Session:
|
|
168
|
-
/model
|
|
169
|
-
/models Discover available models
|
|
169
|
+
/model Configure provider → key → host → model
|
|
170
|
+
/models Discover available models for active provider
|
|
171
|
+
/key Provider API keys (see /help in nehanda TUI for variants)
|
|
170
172
|
/login Authenticate
|
|
171
173
|
/logout Clear credentials
|
|
172
174
|
/status Auth status
|
|
173
175
|
/config Settings (try /config set <key> <value>)
|
|
174
|
-
/mcp MCP
|
|
175
|
-
/mcp list List
|
|
176
|
-
/mcp status Show configured
|
|
177
|
-
/mcp env
|
|
176
|
+
/mcp MCP guide (list · status · reload · add · env)
|
|
177
|
+
/mcp list List tools from configured MCP servers
|
|
178
|
+
/mcp status Show configured servers
|
|
179
|
+
/mcp env Show env vars (masked)
|
|
180
|
+
/mcp env <server> <NAME> <VALUE> Set env var
|
|
181
|
+
/mcp env unset <server> <NAME> Remove env var
|
|
178
182
|
/clear New conversation
|
|
179
183
|
/exit Quit`)
|
|
180
184
|
return
|
|
@@ -531,11 +535,14 @@ Session:
|
|
|
531
535
|
if (!sub || sub === 'help') {
|
|
532
536
|
const lines = [
|
|
533
537
|
'MCP Server & Tool Commands:',
|
|
534
|
-
' /mcp list
|
|
535
|
-
' /mcp status
|
|
536
|
-
' /mcp reload [server]
|
|
537
|
-
' /mcp add <name> <
|
|
538
|
-
' /mcp env
|
|
538
|
+
' /mcp list List tools from configured MCP servers',
|
|
539
|
+
' /mcp status Show configured servers',
|
|
540
|
+
' /mcp reload [server] Reload mcp.json into this session',
|
|
541
|
+
' /mcp add <name> <command> [args…] Register a server',
|
|
542
|
+
' /mcp env Show env vars for every server (values masked)',
|
|
543
|
+
' /mcp env <server> Show env vars for one server',
|
|
544
|
+
' /mcp env <server> <NAME> <VALUE> Set env var e.g. /mcp env jupyter JUPYTER_TOKEN sk-…',
|
|
545
|
+
' /mcp env unset <server> <NAME> Remove an env var',
|
|
539
546
|
'',
|
|
540
547
|
`Configured servers: ${serverNames.length ? serverNames.join(', ') : 'none'}`,
|
|
541
548
|
'Tip: Run /mcp list to inspect tools injected into the assistant.',
|
|
@@ -619,10 +626,12 @@ Session:
|
|
|
619
626
|
}
|
|
620
627
|
|
|
621
628
|
if (sub === 'env') {
|
|
622
|
-
const
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
629
|
+
const parsed = parseMcpEnvParts(parts.slice(1))
|
|
630
|
+
if (parsed.action === 'usage_error') {
|
|
631
|
+
app.addSystemMessage(parsed.error)
|
|
632
|
+
return
|
|
633
|
+
}
|
|
634
|
+
if (parsed.action === 'list_all') {
|
|
626
635
|
const mcpServers = settings?.mcp_servers || {}
|
|
627
636
|
const names = Object.keys(mcpServers)
|
|
628
637
|
if (!names.length) { app.addSystemMessage('No MCP servers configured.'); return }
|
|
@@ -645,13 +654,15 @@ Session:
|
|
|
645
654
|
app.addSystemMessage(lines.join('\n'))
|
|
646
655
|
return
|
|
647
656
|
}
|
|
648
|
-
if (
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
if (!cfg) { app.addSystemMessage(`Server "${srvName}" not found. Use /mcp status to see configured servers.`); return }
|
|
657
|
+
if (parsed.action === 'list_server') {
|
|
658
|
+
const cfg = (settings?.mcp_servers || {})[parsed.server]
|
|
659
|
+
if (!cfg) { app.addSystemMessage(`Server "${parsed.server}" not found. Use /mcp status to see configured servers.`); return }
|
|
652
660
|
const envKeys = Object.keys(cfg.env || {})
|
|
653
|
-
if (!envKeys.length) {
|
|
654
|
-
|
|
661
|
+
if (!envKeys.length) {
|
|
662
|
+
app.addSystemMessage(`${parsed.server}: no env vars set\nUse /mcp env ${parsed.server} <NAME> <VALUE> to set one.`)
|
|
663
|
+
return
|
|
664
|
+
}
|
|
665
|
+
const lines = [`${parsed.server} environment variables:`]
|
|
655
666
|
for (const k of envKeys) {
|
|
656
667
|
const v = cfg.env[k]
|
|
657
668
|
const masked = v ? v.slice(0, 6) + '…' + v.slice(-4) : '(empty)'
|
|
@@ -660,25 +671,25 @@ Session:
|
|
|
660
671
|
app.addSystemMessage(lines.join('\n'))
|
|
661
672
|
return
|
|
662
673
|
}
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
674
|
+
if (parsed.action === 'set' || parsed.action === 'unset') {
|
|
675
|
+
const value = parsed.action === 'unset' ? '' : parsed.value
|
|
676
|
+
const result = updateMcpServerEnv(parsed.server, parsed.name, value, cwd)
|
|
677
|
+
if (!result.ok) {
|
|
678
|
+
app.addSystemMessage(`✗ ${result.error}`)
|
|
679
|
+
return
|
|
680
|
+
}
|
|
681
|
+
settings.mcp_servers = {}
|
|
682
|
+
applyMcpConfig(settings, cwd)
|
|
683
|
+
invalidateMcpToolCache()
|
|
684
|
+
const action = parsed.action === 'set' ? 'Set' : 'Unset'
|
|
685
|
+
const display = parsed.action === 'set' ? ' = ********' : ''
|
|
686
|
+
const relPath = path.relative(os.homedir(), result.path).startsWith('..') ? result.path : '~/' + path.relative(os.homedir(), result.path)
|
|
687
|
+
app.addSystemMessage(`✓ ${action} ${parsed.name}${display} on ${parsed.server}\n Config file: ${relPath}`)
|
|
668
688
|
return
|
|
669
689
|
}
|
|
670
|
-
// Reload to pick up the change
|
|
671
|
-
settings.mcp_servers = {}
|
|
672
|
-
applyMcpConfig(settings, cwd)
|
|
673
|
-
invalidateMcpToolCache()
|
|
674
|
-
const action = result.set ? 'Set' : 'Cleared'
|
|
675
|
-
const display = result.set ? ' = ********' : ''
|
|
676
|
-
const relPath = path.relative(os.homedir(), result.path).startsWith('..') ? result.path : '~/' + path.relative(os.homedir(), result.path)
|
|
677
|
-
app.addSystemMessage(`✓ ${action} ${envKey}${display} on ${srvName}\n Config file: ${relPath}`)
|
|
678
|
-
return
|
|
679
690
|
}
|
|
680
691
|
|
|
681
|
-
app.addSystemMessage('/mcp sub-commands: status · list · reload
|
|
692
|
+
app.addSystemMessage('/mcp sub-commands: status · list · reload · add · env · env unset — type /help for full list')
|
|
682
693
|
return
|
|
683
694
|
}
|
|
684
695
|
|
|
@@ -1070,7 +1081,10 @@ function resolveModelArg(arg) {
|
|
|
1070
1081
|
|
|
1071
1082
|
// First, try exact match in hardcoded WIRE map (backward compatibility)
|
|
1072
1083
|
if (arg.includes('/')) {
|
|
1073
|
-
|
|
1084
|
+
// Split on the FIRST "/" only — the model name may itself contain slashes (#12)
|
|
1085
|
+
const slashIdx = arg.indexOf('/')
|
|
1086
|
+
const p = arg.slice(0, slashIdx)
|
|
1087
|
+
const m = arg.slice(slashIdx + 1)
|
|
1074
1088
|
|
|
1075
1089
|
// Validate provider and model_id are non-empty
|
|
1076
1090
|
if (!p || !p.trim()) {
|