moderado 0.3.9 → 0.4.8

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 (70) hide show
  1. package/README.md +51 -30
  2. package/dist/commands/chat.d.ts +2 -2
  3. package/dist/commands/chat.d.ts.map +1 -1
  4. package/dist/commands/chat.js +108 -41
  5. package/dist/commands/chat.js.map +1 -1
  6. package/dist/commands/run.d.ts.map +1 -1
  7. package/dist/commands/run.js +2 -1
  8. package/dist/commands/run.js.map +1 -1
  9. package/dist/config.d.ts +4 -1
  10. package/dist/config.d.ts.map +1 -1
  11. package/dist/config.js +11 -3
  12. package/dist/config.js.map +1 -1
  13. package/dist/index.d.ts +2 -1
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +9 -1
  16. package/dist/index.js.map +1 -1
  17. package/dist/injected_version.d.ts +2 -0
  18. package/dist/injected_version.d.ts.map +1 -0
  19. package/dist/injected_version.js +5 -0
  20. package/dist/injected_version.js.map +1 -0
  21. package/dist/moderado_oauth.d.ts +14 -0
  22. package/dist/moderado_oauth.d.ts.map +1 -0
  23. package/dist/moderado_oauth.js +88 -0
  24. package/dist/moderado_oauth.js.map +1 -0
  25. package/dist/sessions.d.ts.map +1 -1
  26. package/dist/sessions.js +3 -2
  27. package/dist/sessions.js.map +1 -1
  28. package/dist/ui/model_selector.d.ts +6 -1
  29. package/dist/ui/model_selector.d.ts.map +1 -1
  30. package/dist/ui/model_selector.js +48 -9
  31. package/dist/ui/model_selector.js.map +1 -1
  32. package/dist/ui/provider_connect.d.ts +6 -1
  33. package/dist/ui/provider_connect.d.ts.map +1 -1
  34. package/dist/ui/provider_connect.js +118 -4
  35. package/dist/ui/provider_connect.js.map +1 -1
  36. package/dist/ui/welcome.d.ts +6 -2
  37. package/dist/ui/welcome.d.ts.map +1 -1
  38. package/dist/ui/welcome.js +69 -16
  39. package/dist/ui/welcome.js.map +1 -1
  40. package/dist/vendor/contracts/events.d.ts +93 -0
  41. package/dist/vendor/contracts/events.d.ts.map +1 -1
  42. package/dist/vendor/contracts/events.js +6 -1
  43. package/dist/vendor/contracts/events.js.map +1 -1
  44. package/dist/vendor/contracts/provider.d.ts +60 -0
  45. package/dist/vendor/contracts/provider.d.ts.map +1 -1
  46. package/dist/vendor/contracts/provider.js +39 -0
  47. package/dist/vendor/contracts/provider.js.map +1 -1
  48. package/dist/vendor/core/agent.d.ts.map +1 -1
  49. package/dist/vendor/core/agent.js +21 -7
  50. package/dist/vendor/core/agent.js.map +1 -1
  51. package/dist/vendor/providers/model_discovery.d.ts +1 -1
  52. package/dist/vendor/providers/model_discovery.d.ts.map +1 -1
  53. package/dist/vendor/providers/model_discovery.js +43 -2
  54. package/dist/vendor/providers/model_discovery.js.map +1 -1
  55. package/dist/vendor/providers/nvidia/nvidia_adapter.d.ts +1 -0
  56. package/dist/vendor/providers/nvidia/nvidia_adapter.d.ts.map +1 -1
  57. package/dist/vendor/providers/nvidia/nvidia_adapter.js +42 -8
  58. package/dist/vendor/providers/nvidia/nvidia_adapter.js.map +1 -1
  59. package/dist/vendor/providers/nvidia/sse_parser.d.ts +3 -1
  60. package/dist/vendor/providers/nvidia/sse_parser.d.ts.map +1 -1
  61. package/dist/vendor/providers/nvidia/sse_parser.js +31 -2
  62. package/dist/vendor/providers/nvidia/sse_parser.js.map +1 -1
  63. package/dist/vendor/providers/presets.d.ts +8 -1
  64. package/dist/vendor/providers/presets.d.ts.map +1 -1
  65. package/dist/vendor/providers/presets.js +45 -1
  66. package/dist/vendor/providers/presets.js.map +1 -1
  67. package/dist/vendor/tools/tools/web_search.d.ts.map +1 -1
  68. package/dist/vendor/tools/tools/web_search.js +2 -0
  69. package/dist/vendor/tools/tools/web_search.js.map +1 -1
  70. package/package.json +1 -1
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  > **A lightweight, provider-independent CLI coding agent with dynamic NVIDIA NIM discovery, free-first AUTO routing, and an uncompromised human-in-the-loop approval boundary.**
4
4
 
5
- [![Version](https://img.shields.io/badge/version-v0.3.0%2B2609221-blue.svg)](file:///d:/projects/moderado/VERSION)
5
+ [![Latest release](https://img.shields.io/github/v/release/marcuz-apl/moderado)](https://github.com/marcuz-apl/moderado/releases/latest)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](file:///d:/projects/moderado/LICENSE)
7
7
  [![Standards: Alfazen](https://img.shields.io/badge/standard-alfazen--coding-green.svg)](https://github.com/marcuz-apl/alfazen-skills)
8
8
 
@@ -88,15 +88,18 @@ npm install -g moderado
88
88
  moderado --help
89
89
  ```
90
90
 
91
- moderado@0.3.0 is live on the public npm registry (latest tag points at 0.3.0).
91
+ The latest published release is 0.4.8. Its `/login` command uses the Moderado
92
+ Gateway for model routes and inference; `/connect` remains available for direct
93
+ providers. The previous 0.3.10 release is self-contained and works without the
94
+ Gateway. Both releases use the `moderado` command name.
95
+
96
+ Install the latest release with `npm install -g moderado`. To stay on the
97
+ self-contained 0.3.10 CLI, run `npm install -g moderado@0.3.10`.
98
+
92
99
  Any npm-compatible installer works (bun add -g moderado,
93
- pnpm add -g moderado, npx -y moderado@latest --help), and prebuilt
94
- dependency-free binaries (moderado-win-x64.exe, moderado-macos-arm64,
95
- moderado-linux-x64) are attached to the
96
- GitHub Release v0.3.0 at https://github.com/marcuz-apl/moderado/releases/tag/v0.3.0.
97
- The curl installer (`scripts/install.sh`, Linux x64 + macOS arm64), the
98
- Homebrew tap, and the Scoop bucket are available; the winget and AUR
99
- submissions are pending. See docs/DISTRIBUTION.md in the repository.
100
+ pnpm add -g moderado, npx -y moderado@latest --help). Prebuilt binaries and the
101
+ curl installer are documented in the root README. Windows installation options
102
+ are npm and Scoop. See `docs/DISTRIBUTION.md` in the repository.
100
103
  To build from source instead:
101
104
 
102
105
  ```bash
@@ -111,44 +114,58 @@ Maintainers can install a verified release artifact directly with
111
114
  `npm install -g ./moderado-<version>.tgz`. See
112
115
  [docs/RELEASING.md](../../docs/RELEASING.md) for the review procedure.
113
116
 
114
- ### Connect a provider
117
+ ### Connect to the Moderado Gateway
118
+
119
+ Run `/login` in the TUI and choose the public Gateway for keyless inference.
120
+ Browser authorization or a Moderado Cloud `mrd_` API key is optional for account
121
+ features. `/model` lists configured Gateway routes: `auto` follows Gateway
122
+ routing, while selecting a route pins requests to it. Existing BYOK and local
123
+ provider profiles remain independent.
124
+
125
+ Use `/connect` to connect directly to OpenRouter, NVIDIA NIM, Agnes AI,
126
+ OrcaRouter, Ollama, LM Studio, or a custom OpenAI-compatible provider. `/login`
127
+ selects the Moderado Gateway; `/connect` selects a direct provider.
128
+
129
+ Cloud OAuth credentials last 30 days and have no refresh token; authorize again
130
+ after expiry. On Windows, credentials are stored in Windows Credential Manager.
131
+ On other platforms, the current credential store is memory-only, so log in again
132
+ after starting a new CLI process.
115
133
 
116
- Open Moderado and use `/connect`. NVIDIA NIM is the default free-first provider;
117
- OpenRouter, Agnes AI, and compatible OpenAI-style endpoints are also supported.
118
- On Windows, newly saved provider keys go to Windows Credential Manager and
119
- `~/.moderado/config.json` retains only a credential reference. On other
120
- platforms, use an environment variable such as `NVIDIA_API_KEY` or
121
- `OPENROUTER_API_KEY`.
134
+ Cloud connections use their configured Gateway URL. Loopback URLs such as
135
+ `http://127.0.0.1:4788/v1` are treated as local development; HTTPS URLs for a
136
+ NAS, VPS, or other remote host are used as configured. To set the URL before
137
+ first login, define `MODERADO_CLOUD_BASE_URL`. `MODERADO_CLOUD_ENV=development`
138
+ and `MODERADO_CLOUD_ENV=production` remain available as explicit overrides.
122
139
 
123
140
  Run `moderado doctor` to inspect local setup without exposing secrets. Add
124
141
  `--connectivity` only when you want an optional live model-catalog check.
125
142
  `npm test` never performs live provider calls.
126
143
  ## Getting Started & Development
127
144
 
128
- ### Connect a provider in the TUI
145
+ ### Log in and select a model in the TUI
129
146
 
130
147
  Run `moderado` to open the TUI immediately. A fresh installation does not require
131
148
  an API key or a preselected model. The welcome card shows **No model connected —
132
- use `/connect`** until you add one.
149
+ use `/login`** until you add one.
133
150
 
134
- Use `/connect` to add NVIDIA NIM. NVIDIA NIM starts with `AUTO` free-first
135
- routing, so choosing a model is optional; use `/model` later to pin one. Obtain
136
- an NVIDIA key from [build.nvidia.com](https://build.nvidia.com), or set it as
137
- `NVIDIA_API_KEY` before launching Moderado.
151
+ Use `/login` for the public Moderado Gateway and `/model` to browse configured routes.
152
+ Cloud `auto` follows the Gateway's configured pool; choosing a route in `/model`
153
+ pins it for subsequent requests. Existing direct BYOK and local profiles remain
154
+ available separately.
138
155
 
139
156
  ### Sessions and usage
140
157
 
141
158
  Moderado stores sessions per workspace in `~/.moderado/sessions/`. Use
142
159
  `/session` to create, resume, export, or compact a local conversation. Session
143
160
  exports redact recognized API-key prefixes. The status line uses only
144
- provider-reported token usage. It shows the calculated cost when the selected
145
- model exposes prompt and completion prices, and **Cost unknown** otherwise.
161
+ provider-reported token usage. It shows `$0.00` for Moderado Cloud free routes,
162
+ calculates cost when a model exposes prompt and completion prices, and shows
163
+ **Cost unknown** when a metered model's pricing is unavailable.
146
164
 
147
- `/connect` also accepts an OpenAI-compatible base URL, API key, and explicit
148
- model ID for providers such as OpenRouter, Z.AI, DeepSeek, Moonshot, and
149
- Mistral. Compatibility depends on each provider supporting `/v1/models` and
150
- streaming `/v1/chat/completions` with tool calls. Provider credentials are saved
151
- in `~/.moderado/config.json`; protect that file and never commit it.
165
+ Saved OpenAI-compatible profiles continue to support providers such as
166
+ OpenRouter, Z.AI, DeepSeek, Moonshot, and Mistral. Compatibility depends on each
167
+ provider supporting `/v1/models` and streaming `/v1/chat/completions` with tool
168
+ calls. Cloud keys are never written in plaintext to `~/.moderado/config.json`.
152
169
 
153
170
  ### 1. Build & Test
154
171
  ```bash
@@ -213,4 +230,8 @@ Run `moderado doctor` to check the local Node runtime, workspace, Moderado home
213
230
 
214
231
  ## Windows credential storage
215
232
 
216
- On Windows, /connect saves provider API keys in Windows Credential Manager and stores only a provider credential reference in ~/.moderado/config.json. Existing plaintext keys remain usable for compatibility. Migrate them explicitly with moderado doctor --migrate-credentials; a failed migration leaves the existing configuration unchanged. On other platforms, set the provider environment variable (for example, NVIDIA_API_KEY or OPENROUTER_API_KEY).
233
+ On Windows, `/login` stores Moderado Cloud credentials in Windows Credential
234
+ Manager and keeps only a credential reference in `~/.moderado/config.json`.
235
+ Non-Windows platforms use the current memory-only credential store, so log in
236
+ again for each new CLI process. Existing BYOK environment variables and saved
237
+ profiles remain independent.
@@ -10,7 +10,7 @@ export declare function formatSessionExitSummary(session: StoredSession, modelId
10
10
  export declare function createAgentTask(request: string, mode: 'Plan' | 'Execute'): string;
11
11
  export declare function createReviewTask(changedFiles: string[]): string;
12
12
  export declare function createReviewToolRegistry(): IToolRegistry;
13
- export declare const STANDARD_SLASH_COMMANDS: readonly ["/connect", "/model", "/init", "/btw", "/mcp", "/session", "/queue", "/workflow", "/review", "/map", "/budget", "/skills", "/clear", "/help", "/exit", "/quit"];
13
+ export declare const STANDARD_SLASH_COMMANDS: readonly ["/connect", "/login", "/model", "/init", "/btw", "/mcp", "/session", "/queue", "/workflow", "/review", "/map", "/budget", "/skills", "/clear", "/help", "/exit", "/quit"];
14
14
  export declare function levenshteinDistance(a: string, b: string): number;
15
15
  export interface SlashCommandAdvice {
16
16
  isSlashCommand: boolean;
@@ -89,7 +89,7 @@ export declare function resolveLocalMetaQuery(input: string, context: LocalMetaQ
89
89
  export declare function shouldFastRouteWebSearch(input: string): boolean;
90
90
  /** Environment configuration overrides the optional persistent search endpoint. */
91
91
  export declare function resolveWebSearchEndpoint(config: ModeradoConfig): string | undefined;
92
- export declare function formatDirectWebSearchAnswer(result: ToolResult): string;
92
+ export declare function formatDirectWebSearchAnswer(result: ToolResult, modelConnected?: boolean): string;
93
93
  /** Environment configuration overrides the persistent search provider preference. */
94
94
  export declare function resolveWebSearchProvider(config: ModeradoConfig): WebSearchProviderName | undefined;
95
95
  export declare function resolveWebSearchOptions(config: ModeradoConfig): WebSearchToolOptions;
@@ -1 +1 @@
1
- {"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../src/commands/chat.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../src/commands/chat.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAkG,kBAAkB,EAA0D,MAAM,iBAAiB,CAAC;AAC7M,OAAO,KAAK,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AACnF,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,WAAW,EAAoB,aAAa,EAAE,eAAe,EAAyB,UAAU,EAAmB,MAAM,qBAAqB,CAAC;AAC3L,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAuD,cAAc,EAA+L,MAAM,cAAc,CAAC;AAUhS,OAAO,EAAyH,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACtK,OAAO,EAAkC,kBAAkB,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAQrG,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,aAAa,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAazF;AASD,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAGjF;AAED,wBAAgB,gBAAgB,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,MAAM,CAE/D;AAED,wBAAgB,wBAAwB,IAAI,aAAa,CAQxD;AAED,eAAO,MAAM,uBAAuB,qLAkB1B,CAAC;AAEX,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAuBhE;AAED,MAAM,WAAW,kBAAkB;IACjC,cAAc,EAAE,OAAO,CAAC;IACxB,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,kBAAkB,CA6D3E;AAED,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,MAAM,EACd,UAAU,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,EAC1D,OAAO,EAAE,MAAM,EACf,gBAAgB,GAAE,MAAM,EAAO,GAC9B,MAAM,CASR;AAED,wBAAgB,0BAA0B,CACxC,MAAM,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,WAAW,EAAE,CAAC;IAAC,aAAa,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,EACnF,qBAAqB,EAAE,MAAM,EAC7B,cAAc,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,EACnD,cAAc,SAAK,EACnB,YAAY,SAAY,EACxB,gBAAgB,GAAE,MAAM,EAAO,GAC9B;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAiBtC;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAExD;AAED,6EAA6E;AAC7E,wBAAgB,uBAAuB,CAAC,aAAa,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAGzF;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAE/F;AAED,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,KAAK,CAAgB;IAC7B,OAAO,CAAC,KAAK,CAAM;IAEnB,IAAI,KAAK,IAAI,SAAS,MAAM,EAAE,CAE7B;IAED,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAO3B,KAAK,IAAI,MAAM,GAAG,SAAS;IAI3B,KAAK,IAAI,IAAI;IAKb,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAI5B,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAI/B,cAAc,IAAI,IAAI;IAMtB,WAAW,IAAI,MAAM,GAAG,SAAS;CASlC;AAED,wBAAgB,wBAAwB,CACtC,GAAG,EAAE,MAAM,EACX,GAAG,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,SAAS,EACnF,KAAK,EAAE,gBAAgB,EACvB,OAAO,EAAE;IACP,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,aAAa,EAAE,MAAM,IAAI,CAAC;IAC1B,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC,GACA,IAAI,CAiDN;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,IAAI,CAAC,eAAe,EAAE,UAAU,GAAG,cAAc,CAAC,GAAG,OAAO,CAIrG;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAEpF;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAaxD;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAa3D;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAM3D;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAIxD;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAK1D;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAI5D;AAED,MAAM,WAAW,qBAAqB;IACpC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,qBAAqB,GAAG,MAAM,GAAG,SAAS,CA6BvG;AAED,4EAA4E;AAC5E,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAS/D;AAED,mFAAmF;AACnF,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,GAAG,SAAS,CASnF;AAKD,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,UAAU,EAAE,cAAc,UAAQ,GAAG,MAAM,CAQ9F;AACD,qFAAqF;AACrF,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,cAAc,GAAG,qBAAqB,GAAG,SAAS,CAGlG;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,cAAc,GAAG,oBAAoB,CAEpF;AAED,8GAA8G;AAC9G,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,MAAM,CAMlF;AAID,2GAA2G;AAC3G,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,WAAW,EAAE,EAAE,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,WAAW,EAAE,CAElH;AAED,MAAM,WAAW,qBAAqB;IACpC,WAAW,EAAE,OAAO,CAAC;IACrB,qBAAqB,EAAE,OAAO,CAAC;IAC/B,0BAA0B,EAAE,CAAC,OAAO,EAAE,eAAe,EAAE,MAAM,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,gBAAgB,CAAC,CAAC;CAC3G;AAED,wBAAsB,cAAc,CAClC,OAAO,EAAE,eAAe,EACxB,OAAO,EAAE,qBAAqB,EAC9B,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,gBAAgB,CAAC,CAQ3B;AAED,wBAAsB,qBAAqB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,GAAG,SAAS,EAAE,SAAS,GAAE,oBAAyB,GAAG,OAAO,CAAC,aAAa,CAAC,CAE9J;AAED,MAAM,WAAW,iBAAiB;IAChC,cAAc,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IAC5D,kBAAkB,CAAC,EAAE,OAAO,kBAAkB,CAAC;IAC/C,eAAe,CAAC,EAAE,OAAO,eAAe,CAAC;IACzC,kBAAkB,CAAC,EAAE,OAAO,kBAAkB,CAAC;CAChD;AAED,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,IAAI,EACzC,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,IAAI,CAAC,CA+Hf;AAED,wBAAsB,eAAe,CACnC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAA;CAAE,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,EAC9F,QAAQ,EAAE,aAAa,GAAG,SAAS,EACnC,YAAY,EAAE,MAAM,GAAG,SAAS,EAChC,UAAU,EAAE,KAAK,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,EAC1E,MAAM,CAAC,EAAE,WAAW,EACpB,SAAS,CAAC,EAAE,WAAW,GACtB,OAAO,CAAC,IAAI,CAAC,CAoGf;AAED,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CA28BnH"}
@@ -5,20 +5,20 @@ import { AgentLoop, PolicyManager, Router, cleanConversationalFiller, DEFAULT_MA
5
5
  import { NvidiaAdapter } from '../vendor/providers/index.js';
6
6
  import { createDefaultToolRegistry, ToolRegistry, canonicalizeRoot, createMcpTools, createWebSearchTool, discoverMcpServers, resolveInJail, WorkspaceCheckpointStore, WriteFileTool } from '../vendor/tools/index.js';
7
7
  import { McpServerConfigSchema } from '../vendor/contracts/index.js';
8
- import { getActiveConnection, loadConfig, removeMcpServer, resolveApiKey, resolveConnectionCredential, saveConnection, saveMcpServer, setMcpServerEnabled, storeConnectionCredential } from '../config.js';
8
+ import { freeModelPolicyFor, getActiveConnection, loadConfig, removeMcpServer, requiresCredentialReference, resolveApiKey, resolveConnectionCredential, saveConnection, saveMcpServer, setMcpServerEnabled, storeConnectionCredential } from '../config.js';
9
9
  import { MemoryCredentialStore } from '../credentials.js';
10
10
  import { WindowsCredentialStore } from '../windows_credentials.js';
11
11
  import { TerminalApprovalHandler } from '../ui/terminal_approval.js';
12
12
  import { selectCompatibleModelOverlay, selectModelOverlay, showModelConnectionRequired } from '../ui/model_selector.js';
13
- import { connectProviderInteractive, isAuthenticationFailure, replaceProviderKeyInteractive } from '../ui/provider_connect.js';
13
+ import { connectProviderInteractive, isAuthenticationFailure, isModeradoCloudOAuthConnection, loginModeradoCloudInteractive, replaceProviderKeyInteractive } from '../ui/provider_connect.js';
14
14
  import { initWorkspace } from './init.js';
15
15
  import { expandMentions, createWorkspaceFileSource } from '../ui/file_mentions.js';
16
16
  import { listFiles } from '../vendor/tools/index.js';
17
- import { exitCleanly, promptInteractiveTurn, renderChatAnswerDelta, renderChatComposerCursor, renderChatThoughtTimeUpdate, renderChatUsageUpdate, renderFullWelcomeScreen, renderWelcomePopupLayer, terminalCleanExitDone, wrapText } from '../ui/welcome.js';
17
+ import { exitCleanly, promptInteractiveTurn, renderChatAnswerDelta, renderChatComposerCursor, renderChatThoughtTimeUpdate, renderChatUsageUpdate, renderGatewayFallbackUpdate, renderFullWelcomeScreen, renderWelcomePopupLayer, terminalCleanExitDone, wrapText } from '../ui/welcome.js';
18
18
  import { accumulateSessionUsage, compactSessionMessages, createSession, exportSessionMarkdown, formatSessionCost, SessionStore } from '../sessions.js';
19
19
  import { layerPromptBox, renderBoxLines, selectConfirmPopup, selectListPopup } from '../ui/popup.js';
20
20
  import { askModalChoice } from '../ui/prompt.js';
21
- import { findModelPricing } from '../model_pricing.js';
21
+ import { findModelPricing, isFreeModelOption } from '../model_pricing.js';
22
22
  import { inspectGitWorkspace, readGitDiff } from '../vendor/tools/index.js';
23
23
  import { buildWorkspaceMap } from '../repo_map.js';
24
24
  import { evaluateTokenBudget, parseBudgetCommand } from '../budget.js';
@@ -64,6 +64,7 @@ export function createReviewToolRegistry() {
64
64
  }
65
65
  export const STANDARD_SLASH_COMMANDS = [
66
66
  '/connect',
67
+ '/login',
67
68
  '/model',
68
69
  '/init',
69
70
  '/btw',
@@ -169,7 +170,7 @@ export function formatTurnFailureAnswer(status, errorEvent, modelId, otherConnec
169
170
  const cleanDetail = rawDetail
170
171
  .replace(/^(?:NVIDIA NIM|OpenAI|OpenRouter|[a-zA-Z0-9_-]+)\s*(?:request failed|rate limit exceeded|service or model unavailable)?\s*(?:\([^)]*\))?\s*(?:during\s*streamChat)?:\s*/i, '')
171
172
  .trim();
172
- return `⚠️ **Model Error (${errorEvent?.code || 'ERR_INFERENCE_FAILED'}):**\n${cleanDetail || 'Model returned no response.'}\n\n👉 **Suggestion:** The model \`${modelId}\` encountered an error. Switch models with \`/model\` or switch providers with \`/connect\`${altSuggestion}.`;
173
+ return `⚠️ **Model Error (${errorEvent?.code || 'ERR_INFERENCE_FAILED'}):**\n${cleanDetail || 'Model returned no response.'}\n\n👉 **Suggestion:** The model \`${modelId}\` encountered an error. Switch models with \`/model\`, connect directly with \`/connect\`, or reconnect to the Gateway with \`/login\`${altSuggestion}.`;
173
174
  }
174
175
  export function resolveTurnAssistantAnswer(result, previousHistoryLength, lastErrorEvent, streamedAnswer = '', currentModel = 'unknown', otherConnections = []) {
175
176
  if (result.status === 'cancelled') {
@@ -415,7 +416,7 @@ export function resolveWebSearchEndpoint(config) {
415
416
  }
416
417
  /** Provider results are raw page excerpts; strip block metadata so the no-provider fallback stays readable. */
417
418
  const SEARCH_BLOCK_METADATA = /^(title|url|published|author|highlights?|source|score|favicon)\s*:/i;
418
- export function formatDirectWebSearchAnswer(result) {
419
+ export function formatDirectWebSearchAnswer(result, modelConnected = false) {
419
420
  if (result.status !== 'success')
420
421
  return `Web search failed: ${result.output}`;
421
422
  const lines = result.output
@@ -424,7 +425,7 @@ export function formatDirectWebSearchAnswer(result) {
424
425
  .filter((line) => line && line !== '...' && line !== '---' && !SEARCH_BLOCK_METADATA.test(line) && !/^https?:\/\//i.test(line));
425
426
  if (!lines.length)
426
427
  return 'Web search returned no readable content.';
427
- return `${lines.slice(0, 10).join('\n')}\n\n(no model connected — raw search excerpt)`;
428
+ return `${lines.slice(0, 10).join('\n')}\n\n(${modelConnected ? 'model summary unavailable — ' : 'no model connected — '}raw search excerpt)`;
428
429
  }
429
430
  /** Environment configuration overrides the persistent search provider preference. */
430
431
  export function resolveWebSearchProvider(config) {
@@ -624,7 +625,7 @@ export async function executeBtwQuery(command, drawFrame, provider, currentModel
624
625
  if (!provider || !currentModel) {
625
626
  await drawFrame(renderBoxLines('By The Way (/btw)', [
626
627
  'No model or provider connected.',
627
- 'Connect a provider with /connect or select a model with /model first.',
628
+ 'Use /login for the Gateway or /connect for a direct provider, then select a model with /model.',
628
629
  '',
629
630
  'Press Esc or Enter to return.',
630
631
  ], 72));
@@ -720,7 +721,7 @@ export async function handleChatSession(args, version, signal) {
720
721
  const activateConnection = (connection, explicitModel) => {
721
722
  activeConnection = connection;
722
723
  currentModel = explicitModel ?? connection.defaultModel ?? (connection.kind === 'nvidia-nim' ? 'auto' : undefined);
723
- provider = new NvidiaAdapter({ apiKey: connection.apiKey, baseUrl: connection.baseUrl, providerId: connection.id, providerName: connection.displayName });
724
+ provider = new NvidiaAdapter({ apiKey: connection.id === 'moderado-cloud' ? undefined : connection.apiKey, baseUrl: connection.baseUrl, providerId: connection.id, providerName: connection.displayName });
724
725
  };
725
726
  if (activeConnection)
726
727
  activateConnection(activeConnection, args.model);
@@ -735,6 +736,14 @@ export async function handleChatSession(args, version, signal) {
735
736
  modelId: currentModel,
736
737
  mode: activeMode,
737
738
  });
739
+ if (activeSession.usage.available &&
740
+ !activeSession.usage.costKnown &&
741
+ activeSession.providerId === activeConnection?.id &&
742
+ freeModelPolicyFor(activeConnection?.id)?.freeCatalog) {
743
+ activeSession.usage.costKnown = true;
744
+ activeSession.usage.costUsd = 0;
745
+ sessionStore.save(activeSession);
746
+ }
738
747
  let sessionTokens = activeSession.usage.totalTokens;
739
748
  let isFirst = true;
740
749
  if (config.typescriptLanguageServer)
@@ -787,7 +796,7 @@ export async function handleChatSession(args, version, signal) {
787
796
  }
788
797
  else {
789
798
  const turn = await promptInteractiveTurn({
790
- model: currentModel ?? 'No model connected — use /connect', tokens: Math.round(sessionTokens), cost: costLabel(), workspace: canonicalWorkspace, version,
799
+ model: currentModel ?? 'No model connected — use /login or /connect', tokens: Math.round(sessionTokens), cost: costLabel(), workspace: canonicalWorkspace, version,
791
800
  usageAvailable: activeSession.usage.available, usageEstimated: activeSession.usage.estimated, initialMode: activeMode, initialAutoApprove: activeAutoApprove, isFirstTurn: isFirst, signal, chatQuestion: lastQuestion || undefined, chatAnswer: lastAnswer || undefined, chatThoughtTime: lastThoughtTime, outputTokenRate: lastOutputTokenRate, tokenUsage: lastTokenUsage,
792
801
  queuedCommands: commandQueue.items,
793
802
  onExit: exitMessage,
@@ -799,7 +808,7 @@ export async function handleChatSession(args, version, signal) {
799
808
  }
800
809
  if (activeConnection.kind === 'openai-compatible') {
801
810
  const modelId = await selectCompatibleModelOverlay({
802
- apiKey: activeConnection.apiKey,
811
+ apiKey: activeConnection.id === 'moderado-cloud' ? undefined : activeConnection.apiKey,
803
812
  baseUrl: activeConnection.baseUrl,
804
813
  providerId: activeConnection.id,
805
814
  providerName: activeConnection.displayName,
@@ -815,7 +824,7 @@ export async function handleChatSession(args, version, signal) {
815
824
  }
816
825
  return modelId;
817
826
  }
818
- const selection = await selectModelOverlay({ apiKey: activeConnection.apiKey, currentModel, signal, saveSelectionByDefault: true, drawFrame });
827
+ const selection = await selectModelOverlay({ apiKey: activeConnection.id === 'moderado-cloud' ? undefined : activeConnection.apiKey, currentModel, signal, saveSelectionByDefault: true, drawFrame });
819
828
  if (selection.modelId) {
820
829
  currentModel = selection.modelId;
821
830
  activeConnection = { ...activeConnection, defaultModel: selection.modelId };
@@ -824,11 +833,31 @@ export async function handleChatSession(args, version, signal) {
824
833
  }
825
834
  return selection.modelId;
826
835
  },
836
+ onLogin: async (drawFrame) => {
837
+ const connection = await loginModeradoCloudInteractive({
838
+ signal,
839
+ drawFrame,
840
+ gatewayUrl: config.connections?.['moderado-cloud']?.baseUrl,
841
+ });
842
+ if (!connection)
843
+ return currentModel;
844
+ const runtimeConnection = requiresCredentialReference(connection) ? await storeConnectionCredential(connection, credentialStore) : connection;
845
+ saveConnection(runtimeConnection);
846
+ config = loadConfig();
847
+ activateConnection(runtimeConnection);
848
+ return currentModel ?? 'No model connected — use /login or /connect';
849
+ },
827
850
  onConnect: async (drawFrame) => {
828
- const connection = await connectProviderInteractive({ signal, drawFrame, savedConnections: config.connections, connectProviders: config.connectProviders, resolveSavedConnection: (saved) => resolveConnectionCredential(saved, credentialStore) });
851
+ const connection = await connectProviderInteractive({
852
+ signal,
853
+ drawFrame,
854
+ savedConnections: config.connections,
855
+ connectProviders: config.connectProviders,
856
+ resolveSavedConnection: (saved) => resolveConnectionCredential(saved, credentialStore),
857
+ });
829
858
  if (!connection)
830
859
  return currentModel;
831
- const runtimeConnection = process.platform === 'win32' ? await storeConnectionCredential(connection, credentialStore) : connection;
860
+ const runtimeConnection = requiresCredentialReference(connection) ? await storeConnectionCredential(connection, credentialStore) : connection;
832
861
  saveConnection(runtimeConnection);
833
862
  config = loadConfig();
834
863
  activateConnection(runtimeConnection);
@@ -1071,7 +1100,7 @@ export async function handleChatSession(args, version, signal) {
1071
1100
  lastTokenUsage = undefined;
1072
1101
  process.stdout.write('\x1b[H\x1b[J');
1073
1102
  process.stdout.write(renderFullWelcomeScreen({
1074
- model: currentModel ?? 'No model connected — use /connect', tokens: Math.round(sessionTokens), cost: costLabel(),
1103
+ model: currentModel ?? 'No model connected — use /login or /connect', tokens: Math.round(sessionTokens), cost: costLabel(),
1075
1104
  usageAvailable: activeSession.usage.available, usageEstimated: activeSession.usage.estimated, workspace: canonicalWorkspace, mode: activeMode,
1076
1105
  autoApprove: activeAutoApprove, chatQuestion: lastQuestion, chatAnswer: lastAnswer,
1077
1106
  chatThoughtTime: lastThoughtTime, queuedCommands: commandQueue.items,
@@ -1087,7 +1116,7 @@ export async function handleChatSession(args, version, signal) {
1087
1116
  lastTokenUsage = undefined;
1088
1117
  process.stdout.write('\x1b[H\x1b[J');
1089
1118
  process.stdout.write(renderFullWelcomeScreen({
1090
- model: currentModel ?? 'No model connected — use /connect', tokens: Math.round(sessionTokens), cost: costLabel(),
1119
+ model: currentModel ?? 'No model connected — use /login or /connect', tokens: Math.round(sessionTokens), cost: costLabel(),
1091
1120
  usageAvailable: activeSession.usage.available, usageEstimated: activeSession.usage.estimated, workspace: canonicalWorkspace, mode: activeMode,
1092
1121
  autoApprove: activeAutoApprove, chatQuestion: lastQuestion, chatAnswer: lastAnswer,
1093
1122
  chatThoughtTime: lastThoughtTime, queuedCommands: commandQueue.items,
@@ -1122,7 +1151,7 @@ export async function handleChatSession(args, version, signal) {
1122
1151
  }
1123
1152
  process.stdout.write('\x1b[H\x1b[J');
1124
1153
  process.stdout.write(renderFullWelcomeScreen({
1125
- model: currentModel ?? 'No model connected — use /connect', tokens: Math.round(sessionTokens), cost: costLabel(),
1154
+ model: currentModel ?? 'No model connected — use /login or /connect', tokens: Math.round(sessionTokens), cost: costLabel(),
1126
1155
  usageAvailable: activeSession.usage.available, usageEstimated: activeSession.usage.estimated, workspace: canonicalWorkspace, mode: activeMode,
1127
1156
  autoApprove: activeAutoApprove, chatQuestion: lastQuestion, chatAnswer: lastAnswer,
1128
1157
  chatThoughtTime: lastThoughtTime,
@@ -1161,7 +1190,7 @@ export async function handleChatSession(args, version, signal) {
1161
1190
  lastTokenUsage = undefined;
1162
1191
  process.stdout.write('\x1b[H\x1b[J');
1163
1192
  process.stdout.write(renderFullWelcomeScreen({
1164
- model: currentModel ?? 'No model connected — use /connect', tokens: Math.round(sessionTokens), cost: costLabel(),
1193
+ model: currentModel ?? 'No model connected — use /login or /connect', tokens: Math.round(sessionTokens), cost: costLabel(),
1165
1194
  usageAvailable: activeSession.usage.available, usageEstimated: activeSession.usage.estimated, workspace: canonicalWorkspace, mode: activeMode,
1166
1195
  autoApprove: activeAutoApprove, chatQuestion: lastQuestion, chatAnswer: lastAnswer,
1167
1196
  chatThoughtTime: lastThoughtTime,
@@ -1200,7 +1229,7 @@ export async function handleChatSession(args, version, signal) {
1200
1229
  lastOutputTokenRate = undefined;
1201
1230
  lastTokenUsage = undefined;
1202
1231
  process.stdout.write('\x1b[H\x1b[J');
1203
- process.stdout.write(renderFullWelcomeScreen({ model: currentModel ?? 'No model connected — use /connect', tokens: Math.round(sessionTokens), cost: costLabel(), usageAvailable: activeSession.usage.available, usageEstimated: activeSession.usage.estimated, workspace: canonicalWorkspace, mode: activeMode, autoApprove: activeAutoApprove, chatQuestion: lastQuestion, chatAnswer: lastAnswer, chatThoughtTime: lastThoughtTime, queuedCommands: commandQueue.items }, process.stdout.rows));
1232
+ process.stdout.write(renderFullWelcomeScreen({ model: currentModel ?? 'No model connected — use /login or /connect', tokens: Math.round(sessionTokens), cost: costLabel(), usageAvailable: activeSession.usage.available, usageEstimated: activeSession.usage.estimated, workspace: canonicalWorkspace, mode: activeMode, autoApprove: activeAutoApprove, chatQuestion: lastQuestion, chatAnswer: lastAnswer, chatThoughtTime: lastThoughtTime, queuedCommands: commandQueue.items }, process.stdout.rows));
1204
1233
  process.stdout.write(renderChatComposerCursor({ width: process.stdout.columns }, 0));
1205
1234
  continue;
1206
1235
  }
@@ -1210,7 +1239,8 @@ export async function handleChatSession(args, version, signal) {
1210
1239
  if (trimmed === '/help') {
1211
1240
  lastQuestion = trimmed;
1212
1241
  lastAnswer = 'Available slash commands:\n' +
1213
- ' /connect - Connect a model provider\n' +
1242
+ ' /connect - Connect directly to an external or local provider\n' +
1243
+ ' /login - Connect to the Moderado Gateway or log in to Moderado Cloud\n' +
1214
1244
  ' /model - Switch active AI model\n' +
1215
1245
  ' /init - Scaffold AGENTS.md from workspace scan\n' +
1216
1246
  ' /mcp - Manage local MCP servers\n' +
@@ -1229,7 +1259,7 @@ export async function handleChatSession(args, version, signal) {
1229
1259
  lastTokenUsage = undefined;
1230
1260
  process.stdout.write('\x1b[H\x1b[J');
1231
1261
  process.stdout.write(renderFullWelcomeScreen({
1232
- model: currentModel ?? 'No model connected — use /connect', tokens: Math.round(sessionTokens), cost: costLabel(),
1262
+ model: currentModel ?? 'No model connected — use /login or /connect', tokens: Math.round(sessionTokens), cost: costLabel(),
1233
1263
  usageAvailable: activeSession.usage.available, usageEstimated: activeSession.usage.estimated, workspace: canonicalWorkspace, mode: activeMode,
1234
1264
  autoApprove: activeAutoApprove, chatQuestion: lastQuestion, chatAnswer: lastAnswer,
1235
1265
  chatThoughtTime: lastThoughtTime,
@@ -1262,7 +1292,7 @@ export async function handleChatSession(args, version, signal) {
1262
1292
  sessionStore.save(activeSession);
1263
1293
  process.stdout.write('\x1b[H\x1b[J');
1264
1294
  process.stdout.write(renderFullWelcomeScreen({
1265
- model: currentModel ?? 'No model connected — use /connect', tokens: Math.round(sessionTokens), cost: costLabel(),
1295
+ model: currentModel ?? 'No model connected — use /login or /connect', tokens: Math.round(sessionTokens), cost: costLabel(),
1266
1296
  usageAvailable: activeSession.usage.available, usageEstimated: activeSession.usage.estimated, workspace: canonicalWorkspace, mode: activeMode,
1267
1297
  autoApprove: activeAutoApprove, chatQuestion: lastQuestion, chatAnswer: lastAnswer,
1268
1298
  chatThoughtTime: lastThoughtTime,
@@ -1282,7 +1312,7 @@ export async function handleChatSession(args, version, signal) {
1282
1312
  sessionStore.save(activeSession);
1283
1313
  process.stdout.write('\x1b[H\x1b[J');
1284
1314
  process.stdout.write(renderFullWelcomeScreen({
1285
- model: currentModel ?? 'No model connected — use /connect', tokens: Math.round(sessionTokens), cost: costLabel(),
1315
+ model: currentModel ?? 'No model connected — use /login or /connect', tokens: Math.round(sessionTokens), cost: costLabel(),
1286
1316
  usageAvailable: activeSession.usage.available, usageEstimated: activeSession.usage.estimated, workspace: canonicalWorkspace, mode: activeMode,
1287
1317
  autoApprove: activeAutoApprove, chatQuestion: lastQuestion, chatAnswer: lastAnswer,
1288
1318
  chatThoughtTime: lastThoughtTime,
@@ -1294,8 +1324,12 @@ export async function handleChatSession(args, version, signal) {
1294
1324
  // Current-information lane: gather live evidence before inference so the model answers in one turn.
1295
1325
  let liveSearch;
1296
1326
  const liveSearchTool = tools.get('web_search');
1297
- if (activeMode === 'Execute' && shouldFastRouteWebSearch(trimmed) && liveSearchTool) {
1327
+ if (shouldFastRouteWebSearch(trimmed) && liveSearchTool) {
1298
1328
  const searchStartedAt = Date.now();
1329
+ if (!provider && activeSession.usage.totalTokens === 0) {
1330
+ activeSession.usage.costKnown = true;
1331
+ activeSession.usage.costUsd = 0;
1332
+ }
1299
1333
  lastQuestion = trimmed;
1300
1334
  lastAnswer = 'Searching the web...';
1301
1335
  lastThoughtTime = 0;
@@ -1334,13 +1368,16 @@ export async function handleChatSession(args, version, signal) {
1334
1368
  }
1335
1369
  }
1336
1370
  if (!provider) {
1337
- process.stdout.write('\nNo provider is connected. Let\'s connect one before sending this task.\n');
1338
- const connection = await connectProviderInteractive({ signal, savedConnections: config.connections, connectProviders: config.connectProviders, resolveSavedConnection: (saved) => resolveConnectionCredential(saved, credentialStore) });
1371
+ process.stdout.write('\nNo model is connected. Use /login for the Gateway or /connect for a direct provider.\n');
1372
+ const connection = await loginModeradoCloudInteractive({
1373
+ signal,
1374
+ gatewayUrl: config.connections?.['moderado-cloud']?.baseUrl,
1375
+ });
1339
1376
  if (!connection) {
1340
- process.stdout.write('No provider connected. Use /connect whenever you are ready.\n\n');
1377
+ process.stdout.write('No provider connected. Use /login or /connect whenever you are ready.\n\n');
1341
1378
  continue;
1342
1379
  }
1343
- const runtimeConnection = process.platform === 'win32' ? await storeConnectionCredential(connection, credentialStore) : connection;
1380
+ const runtimeConnection = requiresCredentialReference(connection) ? await storeConnectionCredential(connection, credentialStore) : connection;
1344
1381
  saveConnection(runtimeConnection);
1345
1382
  config = loadConfig();
1346
1383
  activateConnection(runtimeConnection);
@@ -1379,8 +1416,8 @@ export async function handleChatSession(args, version, signal) {
1379
1416
  const displayAnswer = streamedAnswer || lastAnswer;
1380
1417
  process.stdout.write('\x1b[H\x1b[J');
1381
1418
  process.stdout.write(renderFullWelcomeScreen({
1382
- model: currentModel ?? 'No model connected — use /connect',
1383
- tokens: Math.round(sessionTokens),
1419
+ model: currentModel ?? 'No model connected — use /login or /connect',
1420
+ tokens: Math.round(sessionTokens + (usageRecorded ? 0 : turnUsage?.usage.totalTokens ?? 0)),
1384
1421
  cost: costLabel(),
1385
1422
  usageAvailable: activeSession.usage.available, usageEstimated: activeSession.usage.estimated,
1386
1423
  workspace: canonicalWorkspace,
@@ -1501,6 +1538,8 @@ export async function handleChatSession(args, version, signal) {
1501
1538
  eventListener: (event) => {
1502
1539
  if (event.type === 'model_change')
1503
1540
  usedModels.add(event.newModelId);
1541
+ if (event.type === 'gateway_status')
1542
+ process.stdout.write(renderGatewayFallbackUpdate(event, process.stdout.columns || 80));
1504
1543
  if (event.type === 'usage') {
1505
1544
  turnUsage = event;
1506
1545
  lastTokenUsage = event;
@@ -1514,7 +1553,7 @@ export async function handleChatSession(args, version, signal) {
1514
1553
  requestAbort.abort();
1515
1554
  }
1516
1555
  if (budgetStopped || firstBudgetWarning || event.final || event.timestamp - lastUsagePaint >= 400) {
1517
- process.stdout.write(renderChatUsageUpdate(event, process.stdout.columns || 80, budgetStopped ? 'Budget reached' : budgetWarningIssued ? 'Budget ≥80%' : undefined));
1556
+ process.stdout.write(renderChatUsageUpdate(event, process.stdout.columns || 80, budgetStopped ? 'Budget reached' : budgetWarningIssued ? 'Budget ≥80%' : undefined, `${Math.round(sessionTokens + event.usage.totalTokens)} tokens / ${costLabel()}`, process.stdout.rows || 24, activeMode));
1518
1557
  lastUsagePaint = event.timestamp;
1519
1558
  }
1520
1559
  }
@@ -1554,28 +1593,42 @@ export async function handleChatSession(args, version, signal) {
1554
1593
  throw error;
1555
1594
  if (thinkingTimer)
1556
1595
  clearInterval(thinkingTimer);
1557
- process.stdout.write(`\nThe saved ${activeConnection.displayName} API key was rejected. Enter a replacement key to retry once.\n`);
1558
- const replacement = await replaceProviderKeyInteractive(activeConnection, { signal });
1596
+ const cloudOAuth = isModeradoCloudOAuthConnection(activeConnection);
1597
+ process.stdout.write(cloudOAuth
1598
+ ? '\nModerado Cloud rejected this browser login. Sign in again to retry once.\n'
1599
+ : `\nThe saved ${activeConnection.displayName} API key was rejected. Enter a replacement key to retry once.\n`);
1600
+ const replacement = cloudOAuth
1601
+ ? await loginModeradoCloudInteractive({ signal, gatewayUrl: activeConnection.baseUrl })
1602
+ : await replaceProviderKeyInteractive(activeConnection, { signal });
1559
1603
  if (!replacement)
1560
1604
  throw error;
1561
1605
  recordTurnUsage();
1562
1606
  turnUsage = undefined;
1563
1607
  usageRecorded = false;
1564
- const secured = process.platform === 'win32' ? await storeConnectionCredential(replacement, credentialStore) : replacement;
1608
+ const secured = requiresCredentialReference(replacement) ? await storeConnectionCredential(replacement, credentialStore) : replacement;
1565
1609
  saveConnection(secured);
1566
1610
  config = loadConfig();
1567
1611
  activateConnection(secured);
1568
1612
  result = await runAgent();
1569
1613
  }
1570
- if (turnUsage && !turnUsage.estimated) {
1614
+ if (turnUsage) {
1571
1615
  let pricing;
1616
+ const modelIds = new Set([...usedModels, result.selectedModel.id]);
1617
+ const freePolicy = freeModelPolicyFor(activeConnection?.id);
1618
+ let inventory = [];
1572
1619
  try {
1573
- if (usedModels.size <= 1)
1574
- pricing = findModelPricing(await provider.discoverModels(signal), result.selectedModel.id);
1620
+ inventory = await provider.discoverModels(signal);
1575
1621
  }
1576
1622
  catch {
1577
1623
  // A valid answer remains usable when the provider catalog is unavailable.
1578
1624
  }
1625
+ const allModelsAreFree = [...modelIds].every((modelId) => isFreeModelOption(inventory.find((entry) => entry.id === modelId) ?? { id: modelId }, modelId === result.selectedModel.id
1626
+ ? result.selectedModel.classification
1627
+ : router.classifyModel(modelId, args.profile.includes('local')), freePolicy));
1628
+ if (allModelsAreFree)
1629
+ pricing = { prompt: '0', completion: '0' };
1630
+ else if (!turnUsage.estimated && modelIds.size === 1)
1631
+ pricing = findModelPricing(inventory, result.selectedModel.id);
1579
1632
  recordTurnUsage(pricing);
1580
1633
  }
1581
1634
  recordTurnUsage();
@@ -1584,13 +1637,27 @@ export async function handleChatSession(args, version, signal) {
1584
1637
  sessionStore.save(activeSession);
1585
1638
  const otherConnections = Object.keys(config.connections ?? {}).filter((id) => id !== activeConnection?.id);
1586
1639
  const resolution = resolveTurnAssistantAnswer(result, conversationHistory.length, lastErrorEvent, streamedAnswer, currentModel, otherConnections);
1587
- lastAnswer = resolution.answer;
1640
+ const searchFallback = result.status === 'failed' && liveSearch?.status === 'success'
1641
+ ? formatDirectWebSearchAnswer(liveSearch, true)
1642
+ : undefined;
1643
+ const hasSearchFallback = searchFallback !== undefined && searchFallback !== 'Web search returned no readable content.';
1644
+ lastAnswer = hasSearchFallback ? searchFallback : resolution.answer;
1588
1645
  if (budgetStopped) {
1589
1646
  const note = `Token budget reached (${tokenBudget?.toLocaleString()} per task). Task stopped; usage may exceed the limit because providers report in chunks.`;
1590
1647
  lastAnswer = [lastAnswer, note].filter(Boolean).join('\n\n');
1591
1648
  streamedAnswer = lastAnswer;
1592
1649
  }
1593
- if (resolution.isError) {
1650
+ if (resolution.isError && hasSearchFallback) {
1651
+ conversationHistory = [...conversationHistory, { role: 'user', content: trimmed }, { role: 'assistant', content: lastAnswer }];
1652
+ activeSession.messages = conversationHistory;
1653
+ activeSession.providerId = activeConnection?.id;
1654
+ activeSession.providerName = activeConnection?.displayName;
1655
+ activeSession.modelId = currentModel;
1656
+ activeSession.mode = activeMode;
1657
+ sessionStore.save(activeSession);
1658
+ streamedAnswer = lastAnswer;
1659
+ }
1660
+ else if (resolution.isError) {
1594
1661
  streamedAnswer = lastAnswer;
1595
1662
  }
1596
1663
  else {
@@ -1628,12 +1695,12 @@ export async function handleChatSession(args, version, signal) {
1628
1695
  const cleanErr = rawErr
1629
1696
  .replace(/^(?:NVIDIA NIM|OpenAI|OpenRouter|[a-zA-Z0-9_-]+)\s*(?:request failed|rate limit exceeded|service or model unavailable)?\s*(?:\([^)]*\))?\s*(?:during\s*streamChat)?:\s*/i, '')
1630
1697
  .trim();
1631
- lastAnswer = `⚠️ **Error:** ${cleanErr || rawErr}\n\n👉 **Suggestion:** Switch models with \`/model\` or switch providers with \`/connect\`${altSuggestion}.`;
1698
+ lastAnswer = `⚠️ **Error:** ${cleanErr || rawErr}\n\n👉 **Suggestion:** Switch models with \`/model\`, connect directly with \`/connect\`, or reconnect to the Gateway with \`/login\`${altSuggestion}.`;
1632
1699
  lastThoughtTime = Math.max(0.001, (Date.now() - startedAt) / 1000);
1633
1700
  lastOutputTokenRate = undefined;
1634
1701
  process.stdout.write('\x1b[H\x1b[J');
1635
1702
  process.stdout.write(renderFullWelcomeScreen({
1636
- model: currentModel ?? 'No model connected — use /connect',
1703
+ model: currentModel ?? 'No model connected — use /login or /connect',
1637
1704
  tokens: Math.round(sessionTokens),
1638
1705
  cost: costLabel(),
1639
1706
  usageAvailable: activeSession.usage.available, usageEstimated: activeSession.usage.estimated,