moderado 0.3.10 → 0.4.10

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 (66) hide show
  1. package/README.md +52 -30
  2. package/dist/commands/chat.d.ts +3 -2
  3. package/dist/commands/chat.d.ts.map +1 -1
  4. package/dist/commands/chat.js +101 -29
  5. package/dist/commands/chat.js.map +1 -1
  6. package/dist/config.d.ts +4 -1
  7. package/dist/config.d.ts.map +1 -1
  8. package/dist/config.js +11 -3
  9. package/dist/config.js.map +1 -1
  10. package/dist/moderado_oauth.d.ts +14 -0
  11. package/dist/moderado_oauth.d.ts.map +1 -0
  12. package/dist/moderado_oauth.js +88 -0
  13. package/dist/moderado_oauth.js.map +1 -0
  14. package/dist/sessions.d.ts.map +1 -1
  15. package/dist/sessions.js +3 -2
  16. package/dist/sessions.js.map +1 -1
  17. package/dist/ui/model_selector.d.ts +7 -1
  18. package/dist/ui/model_selector.d.ts.map +1 -1
  19. package/dist/ui/model_selector.js +52 -13
  20. package/dist/ui/model_selector.js.map +1 -1
  21. package/dist/ui/popup.d.ts.map +1 -1
  22. package/dist/ui/popup.js +22 -2
  23. package/dist/ui/popup.js.map +1 -1
  24. package/dist/ui/provider_connect.d.ts +30 -1
  25. package/dist/ui/provider_connect.d.ts.map +1 -1
  26. package/dist/ui/provider_connect.js +202 -4
  27. package/dist/ui/provider_connect.js.map +1 -1
  28. package/dist/ui/welcome.d.ts +4 -2
  29. package/dist/ui/welcome.d.ts.map +1 -1
  30. package/dist/ui/welcome.js +46 -16
  31. package/dist/ui/welcome.js.map +1 -1
  32. package/dist/vendor/contracts/events.d.ts +93 -0
  33. package/dist/vendor/contracts/events.d.ts.map +1 -1
  34. package/dist/vendor/contracts/events.js +6 -1
  35. package/dist/vendor/contracts/events.js.map +1 -1
  36. package/dist/vendor/contracts/models.d.ts +4 -0
  37. package/dist/vendor/contracts/models.d.ts.map +1 -1
  38. package/dist/vendor/contracts/models.js +2 -0
  39. package/dist/vendor/contracts/models.js.map +1 -1
  40. package/dist/vendor/contracts/provider.d.ts +60 -0
  41. package/dist/vendor/contracts/provider.d.ts.map +1 -1
  42. package/dist/vendor/contracts/provider.js +39 -0
  43. package/dist/vendor/contracts/provider.js.map +1 -1
  44. package/dist/vendor/core/agent.d.ts.map +1 -1
  45. package/dist/vendor/core/agent.js +21 -7
  46. package/dist/vendor/core/agent.js.map +1 -1
  47. package/dist/vendor/providers/model_discovery.d.ts +1 -1
  48. package/dist/vendor/providers/model_discovery.d.ts.map +1 -1
  49. package/dist/vendor/providers/model_discovery.js +43 -2
  50. package/dist/vendor/providers/model_discovery.js.map +1 -1
  51. package/dist/vendor/providers/nvidia/nvidia_adapter.d.ts +1 -0
  52. package/dist/vendor/providers/nvidia/nvidia_adapter.d.ts.map +1 -1
  53. package/dist/vendor/providers/nvidia/nvidia_adapter.js +42 -8
  54. package/dist/vendor/providers/nvidia/nvidia_adapter.js.map +1 -1
  55. package/dist/vendor/providers/nvidia/sse_parser.d.ts +3 -1
  56. package/dist/vendor/providers/nvidia/sse_parser.d.ts.map +1 -1
  57. package/dist/vendor/providers/nvidia/sse_parser.js +31 -2
  58. package/dist/vendor/providers/nvidia/sse_parser.js.map +1 -1
  59. package/dist/vendor/providers/presets.d.ts +9 -2
  60. package/dist/vendor/providers/presets.d.ts.map +1 -1
  61. package/dist/vendor/providers/presets.js +43 -1
  62. package/dist/vendor/providers/presets.js.map +1 -1
  63. package/dist/vendor/tools/tools/web_search.d.ts.map +1 -1
  64. package/dist/vendor/tools/tools/web_search.js +2 -0
  65. package/dist/vendor/tools/tools/web_search.js.map +1 -1
  66. package/package.json +2 -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.10-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,16 +88,19 @@ npm install -g moderado
88
88
  moderado --help
89
89
  ```
90
90
 
91
- moderado@0.3.10 is live on the public npm registry (latest tag points at 0.3.10).
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 0.4.10 source build uses `/connect` for all providers and does
94
+ not offer `/login`. The previous 0.3.10 release is self-contained and works
95
+ without the Gateway. All three use the `moderado` command name.
96
+
97
+ Install the latest release with `npm install -g moderado`. To stay on the
98
+ self-contained 0.3.10 CLI, run `npm install -g moderado@0.3.10`.
99
+
92
100
  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.10 at https://github.com/marcuz-apl/moderado/releases/tag/v0.3.10.
97
- The curl installer (`scripts/install.sh`, Linux x64 + macOS arm64), the
98
- Homebrew tap, and the Scoop bucket are available; the winget submission is
99
- pending merge and AUR has not been uploaded yet. See docs/DISTRIBUTION.md in
100
- the repository.
101
+ pnpm add -g moderado, npx -y moderado@latest --help). Prebuilt binaries and the
102
+ curl installer are documented in the root README. Windows installation options
103
+ are npm and Scoop. See `docs/DISTRIBUTION.md` in the repository.
101
104
  To build from source instead:
102
105
 
103
106
  ```bash
@@ -112,44 +115,59 @@ Maintainers can install a verified release artifact directly with
112
115
  `npm install -g ./moderado-<version>.tgz`. See
113
116
  [docs/RELEASING.md](../../docs/RELEASING.md) for the review procedure.
114
117
 
115
- ### Connect a provider
118
+ ### Connect to the Moderado Gateway
119
+
120
+ In the current source build, run `/connect`, choose Moderado Gateway, and then
121
+ choose public access or sign in.
122
+ Browser authorization or a Moderado Cloud `mrd_` API key is optional for account
123
+ features. `/model` lists configured Gateway routes: `auto` follows Gateway
124
+ routing, while selecting a route pins requests to it. Existing BYOK and local
125
+ provider profiles remain independent.
126
+
127
+ The same `/connect` menu offers OpenRouter, NVIDIA NIM, Agnes AI, OrcaRouter,
128
+ Ollama, LM Studio, and custom OpenAI-compatible providers. The published 0.4.8
129
+ release still uses `/login` for the Gateway.
130
+
131
+ Cloud OAuth credentials last 30 days and have no refresh token; authorize again
132
+ after expiry. On Windows, credentials are stored in Windows Credential Manager.
133
+ On other platforms, the current credential store is memory-only, so log in again
134
+ after starting a new CLI process.
116
135
 
117
- Open Moderado and use `/connect`. NVIDIA NIM is the default free-first provider;
118
- OpenRouter, Agnes AI, and compatible OpenAI-style endpoints are also supported.
119
- On Windows, newly saved provider keys go to Windows Credential Manager and
120
- `~/.moderado/config.json` retains only a credential reference. On other
121
- platforms, use an environment variable such as `NVIDIA_API_KEY` or
122
- `OPENROUTER_API_KEY`.
136
+ Cloud connections use their configured Gateway URL. Loopback URLs such as
137
+ `http://127.0.0.1:4788/v1` are treated as local development; HTTPS URLs for a
138
+ NAS, VPS, or other remote host are used as configured. To set the URL before
139
+ first login, define `MODERADO_CLOUD_BASE_URL`. `MODERADO_CLOUD_ENV=development`
140
+ and `MODERADO_CLOUD_ENV=production` remain available as explicit overrides.
123
141
 
124
142
  Run `moderado doctor` to inspect local setup without exposing secrets. Add
125
143
  `--connectivity` only when you want an optional live model-catalog check.
126
144
  `npm test` never performs live provider calls.
127
145
  ## Getting Started & Development
128
146
 
129
- ### Connect a provider in the TUI
147
+ ### Log in and select a model in the TUI
130
148
 
131
149
  Run `moderado` to open the TUI immediately. A fresh installation does not require
132
150
  an API key or a preselected model. The welcome card shows **No model connected —
133
151
  use `/connect`** until you add one.
134
152
 
135
- Use `/connect` to add NVIDIA NIM. NVIDIA NIM starts with `AUTO` free-first
136
- routing, so choosing a model is optional; use `/model` later to pin one. Obtain
137
- an NVIDIA key from [build.nvidia.com](https://build.nvidia.com), or set it as
138
- `NVIDIA_API_KEY` before launching Moderado.
153
+ Use `/connect` to choose the Gateway or another provider. The Gateway offers a
154
+ route picker after connection; `/model` can change the route later. Cloud `auto`
155
+ follows the Gateway's configured pool, while choosing a route pins subsequent
156
+ requests to it.
139
157
 
140
158
  ### Sessions and usage
141
159
 
142
160
  Moderado stores sessions per workspace in `~/.moderado/sessions/`. Use
143
161
  `/session` to create, resume, export, or compact a local conversation. Session
144
162
  exports redact recognized API-key prefixes. The status line uses only
145
- provider-reported token usage. It shows the calculated cost when the selected
146
- model exposes prompt and completion prices, and **Cost unknown** otherwise.
163
+ provider-reported token usage. It shows `$0.00` for Moderado Cloud free routes,
164
+ calculates cost when a model exposes prompt and completion prices, and shows
165
+ **Cost unknown** when a metered model's pricing is unavailable.
147
166
 
148
- `/connect` also accepts an OpenAI-compatible base URL, API key, and explicit
149
- model ID for providers such as OpenRouter, Z.AI, DeepSeek, Moonshot, and
150
- Mistral. Compatibility depends on each provider supporting `/v1/models` and
151
- streaming `/v1/chat/completions` with tool calls. Provider credentials are saved
152
- in `~/.moderado/config.json`; protect that file and never commit it.
167
+ Saved OpenAI-compatible profiles continue to support providers such as
168
+ OpenRouter, Z.AI, DeepSeek, Moonshot, and Mistral. Compatibility depends on each
169
+ provider supporting `/v1/models` and streaming `/v1/chat/completions` with tool
170
+ calls. Cloud keys are never written in plaintext to `~/.moderado/config.json`.
153
171
 
154
172
  ### 1. Build & Test
155
173
  ```bash
@@ -214,4 +232,8 @@ Run `moderado doctor` to check the local Node runtime, workspace, Moderado home
214
232
 
215
233
  ## Windows credential storage
216
234
 
217
- 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).
235
+ On Windows, Gateway sign-in through `/connect` stores credentials in Windows Credential
236
+ Manager and keeps only a credential reference in `~/.moderado/config.json`.
237
+ Non-Windows platforms use the current memory-only credential store, so log in
238
+ again for each new CLI process. Existing BYOK environment variables and saved
239
+ profiles remain independent.
@@ -3,9 +3,10 @@ import { discoverMcpServers } from '@moderado/tools';
3
3
  import type { WebSearchProviderName, WebSearchToolOptions } from '@moderado/tools';
4
4
  import { ApprovalDecision, ApprovalRequest, ChatMessage, IToolRegistry, McpServerConfig, ToolResult } from '@moderado/contracts';
5
5
  import { CliParsedArgs } from '../args.js';
6
- import { ModeradoConfig } from '../config.js';
6
+ import { ModeradoConfig, ProviderConnection } from '../config.js';
7
7
  import { StoredSession } from '../sessions.js';
8
8
  import { selectConfirmPopup, selectListPopup } from '../ui/popup.js';
9
+ export declare function createConnectionProvider(connection: ProviderConnection): NvidiaAdapter;
9
10
  export declare function formatSessionExitSummary(session: StoredSession, modelId?: string): string;
10
11
  export declare function createAgentTask(request: string, mode: 'Plan' | 'Execute'): string;
11
12
  export declare function createReviewTask(changedFiles: string[]): string;
@@ -89,7 +90,7 @@ export declare function resolveLocalMetaQuery(input: string, context: LocalMetaQ
89
90
  export declare function shouldFastRouteWebSearch(input: string): boolean;
90
91
  /** Environment configuration overrides the optional persistent search endpoint. */
91
92
  export declare function resolveWebSearchEndpoint(config: ModeradoConfig): string | undefined;
92
- export declare function formatDirectWebSearchAnswer(result: ToolResult): string;
93
+ export declare function formatDirectWebSearchAnswer(result: ToolResult, modelConnected?: boolean): string;
93
94
  /** Environment configuration overrides the persistent search provider preference. */
94
95
  export declare function resolveWebSearchProvider(config: ModeradoConfig): WebSearchProviderName | undefined;
95
96
  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":"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"}
@@ -5,24 +5,27 @@ 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';
25
25
  import { discoverSkills, formatSkillContext, formatSkillsList, getEnabledSkillNames, selectEnabledSkills, setSkillEnabled } from '../skills.js';
26
+ export function createConnectionProvider(connection) {
27
+ return new NvidiaAdapter({ apiKey: connection.apiKey, baseUrl: connection.baseUrl, providerId: connection.id, providerName: connection.displayName });
28
+ }
26
29
  export function formatSessionExitSummary(session, modelId) {
27
30
  const usage = session.usage;
28
31
  const tokens = usage.available
@@ -169,7 +172,7 @@ export function formatTurnFailureAnswer(status, errorEvent, modelId, otherConnec
169
172
  const cleanDetail = rawDetail
170
173
  .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
174
  .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}.`;
175
+ 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 choose a provider with \`/connect\`${altSuggestion}.`;
173
176
  }
174
177
  export function resolveTurnAssistantAnswer(result, previousHistoryLength, lastErrorEvent, streamedAnswer = '', currentModel = 'unknown', otherConnections = []) {
175
178
  if (result.status === 'cancelled') {
@@ -415,7 +418,7 @@ export function resolveWebSearchEndpoint(config) {
415
418
  }
416
419
  /** Provider results are raw page excerpts; strip block metadata so the no-provider fallback stays readable. */
417
420
  const SEARCH_BLOCK_METADATA = /^(title|url|published|author|highlights?|source|score|favicon)\s*:/i;
418
- export function formatDirectWebSearchAnswer(result) {
421
+ export function formatDirectWebSearchAnswer(result, modelConnected = false) {
419
422
  if (result.status !== 'success')
420
423
  return `Web search failed: ${result.output}`;
421
424
  const lines = result.output
@@ -424,7 +427,7 @@ export function formatDirectWebSearchAnswer(result) {
424
427
  .filter((line) => line && line !== '...' && line !== '---' && !SEARCH_BLOCK_METADATA.test(line) && !/^https?:\/\//i.test(line));
425
428
  if (!lines.length)
426
429
  return 'Web search returned no readable content.';
427
- return `${lines.slice(0, 10).join('\n')}\n\n(no model connected — raw search excerpt)`;
430
+ return `${lines.slice(0, 10).join('\n')}\n\n(${modelConnected ? 'model summary unavailable — ' : 'no model connected — '}raw search excerpt)`;
428
431
  }
429
432
  /** Environment configuration overrides the persistent search provider preference. */
430
433
  export function resolveWebSearchProvider(config) {
@@ -624,7 +627,7 @@ export async function executeBtwQuery(command, drawFrame, provider, currentModel
624
627
  if (!provider || !currentModel) {
625
628
  await drawFrame(renderBoxLines('By The Way (/btw)', [
626
629
  'No model or provider connected.',
627
- 'Connect a provider with /connect or select a model with /model first.',
630
+ 'Use /connect to choose a provider and model.',
628
631
  '',
629
632
  'Press Esc or Enter to return.',
630
633
  ], 72));
@@ -720,7 +723,7 @@ export async function handleChatSession(args, version, signal) {
720
723
  const activateConnection = (connection, explicitModel) => {
721
724
  activeConnection = connection;
722
725
  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 });
726
+ provider = createConnectionProvider(connection);
724
727
  };
725
728
  if (activeConnection)
726
729
  activateConnection(activeConnection, args.model);
@@ -735,6 +738,14 @@ export async function handleChatSession(args, version, signal) {
735
738
  modelId: currentModel,
736
739
  mode: activeMode,
737
740
  });
741
+ if (activeSession.usage.available &&
742
+ !activeSession.usage.costKnown &&
743
+ activeSession.providerId === activeConnection?.id &&
744
+ freeModelPolicyFor(activeConnection?.id)?.freeCatalog) {
745
+ activeSession.usage.costKnown = true;
746
+ activeSession.usage.costUsd = 0;
747
+ sessionStore.save(activeSession);
748
+ }
738
749
  let sessionTokens = activeSession.usage.totalTokens;
739
750
  let isFirst = true;
740
751
  if (config.typescriptLanguageServer)
@@ -799,7 +810,7 @@ export async function handleChatSession(args, version, signal) {
799
810
  }
800
811
  if (activeConnection.kind === 'openai-compatible') {
801
812
  const modelId = await selectCompatibleModelOverlay({
802
- apiKey: activeConnection.apiKey,
813
+ apiKey: activeConnection.id === 'moderado-cloud' ? undefined : activeConnection.apiKey,
803
814
  baseUrl: activeConnection.baseUrl,
804
815
  providerId: activeConnection.id,
805
816
  providerName: activeConnection.displayName,
@@ -815,7 +826,7 @@ export async function handleChatSession(args, version, signal) {
815
826
  }
816
827
  return modelId;
817
828
  }
818
- const selection = await selectModelOverlay({ apiKey: activeConnection.apiKey, currentModel, signal, saveSelectionByDefault: true, drawFrame });
829
+ const selection = await selectModelOverlay({ apiKey: activeConnection.id === 'moderado-cloud' ? undefined : activeConnection.apiKey, currentModel, signal, saveSelectionByDefault: true, drawFrame });
819
830
  if (selection.modelId) {
820
831
  currentModel = selection.modelId;
821
832
  activeConnection = { ...activeConnection, defaultModel: selection.modelId };
@@ -825,10 +836,18 @@ export async function handleChatSession(args, version, signal) {
825
836
  return selection.modelId;
826
837
  },
827
838
  onConnect: async (drawFrame) => {
828
- const connection = await connectProviderInteractive({ signal, drawFrame, savedConnections: config.connections, connectProviders: config.connectProviders, resolveSavedConnection: (saved) => resolveConnectionCredential(saved, credentialStore) });
839
+ const connection = await connectProviderInteractive({
840
+ signal,
841
+ drawFrame,
842
+ gatewayUrl: config.connections?.['moderado-cloud']?.baseUrl,
843
+ activeConnection,
844
+ savedConnections: config.connections,
845
+ connectProviders: config.connectProviders,
846
+ resolveSavedConnection: (saved) => resolveConnectionCredential(saved, credentialStore),
847
+ });
829
848
  if (!connection)
830
849
  return currentModel;
831
- const runtimeConnection = process.platform === 'win32' ? await storeConnectionCredential(connection, credentialStore) : connection;
850
+ const runtimeConnection = requiresCredentialReference(connection) ? await storeConnectionCredential(connection, credentialStore) : connection;
832
851
  saveConnection(runtimeConnection);
833
852
  config = loadConfig();
834
853
  activateConnection(runtimeConnection);
@@ -1210,7 +1229,7 @@ export async function handleChatSession(args, version, signal) {
1210
1229
  if (trimmed === '/help') {
1211
1230
  lastQuestion = trimmed;
1212
1231
  lastAnswer = 'Available slash commands:\n' +
1213
- ' /connect - Connect a model provider\n' +
1232
+ ' /connect - Choose a Gateway, hosted, or local provider\n' +
1214
1233
  ' /model - Switch active AI model\n' +
1215
1234
  ' /init - Scaffold AGENTS.md from workspace scan\n' +
1216
1235
  ' /mcp - Manage local MCP servers\n' +
@@ -1294,8 +1313,12 @@ export async function handleChatSession(args, version, signal) {
1294
1313
  // Current-information lane: gather live evidence before inference so the model answers in one turn.
1295
1314
  let liveSearch;
1296
1315
  const liveSearchTool = tools.get('web_search');
1297
- if (activeMode === 'Execute' && shouldFastRouteWebSearch(trimmed) && liveSearchTool) {
1316
+ if (shouldFastRouteWebSearch(trimmed) && liveSearchTool) {
1298
1317
  const searchStartedAt = Date.now();
1318
+ if (!provider && activeSession.usage.totalTokens === 0) {
1319
+ activeSession.usage.costKnown = true;
1320
+ activeSession.usage.costUsd = 0;
1321
+ }
1299
1322
  lastQuestion = trimmed;
1300
1323
  lastAnswer = 'Searching the web...';
1301
1324
  lastThoughtTime = 0;
@@ -1334,13 +1357,29 @@ export async function handleChatSession(args, version, signal) {
1334
1357
  }
1335
1358
  }
1336
1359
  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) });
1360
+ process.stdout.write('\nNo model is connected. Use /connect to choose a provider.\n');
1361
+ const connection = await connectProviderInteractive({
1362
+ signal,
1363
+ drawFrame: (popupLines) => {
1364
+ process.stdout.write('\x1b[H\x1b[J');
1365
+ process.stdout.write(renderWelcomePopupLayer({
1366
+ model: currentModel ?? 'No model connected — use /connect',
1367
+ tokens: Math.round(sessionTokens), cost: costLabel(), workspace: canonicalWorkspace,
1368
+ mode: activeMode, autoApprove: activeAutoApprove,
1369
+ chatQuestion: lastQuestion, chatAnswer: lastAnswer,
1370
+ }, popupLines, process.stdout.columns, process.stdout.rows));
1371
+ },
1372
+ gatewayUrl: config.connections?.['moderado-cloud']?.baseUrl,
1373
+ activeConnection,
1374
+ savedConnections: config.connections,
1375
+ connectProviders: config.connectProviders,
1376
+ resolveSavedConnection: (saved) => resolveConnectionCredential(saved, credentialStore),
1377
+ });
1339
1378
  if (!connection) {
1340
1379
  process.stdout.write('No provider connected. Use /connect whenever you are ready.\n\n');
1341
1380
  continue;
1342
1381
  }
1343
- const runtimeConnection = process.platform === 'win32' ? await storeConnectionCredential(connection, credentialStore) : connection;
1382
+ const runtimeConnection = requiresCredentialReference(connection) ? await storeConnectionCredential(connection, credentialStore) : connection;
1344
1383
  saveConnection(runtimeConnection);
1345
1384
  config = loadConfig();
1346
1385
  activateConnection(runtimeConnection);
@@ -1380,7 +1419,7 @@ export async function handleChatSession(args, version, signal) {
1380
1419
  process.stdout.write('\x1b[H\x1b[J');
1381
1420
  process.stdout.write(renderFullWelcomeScreen({
1382
1421
  model: currentModel ?? 'No model connected — use /connect',
1383
- tokens: Math.round(sessionTokens),
1422
+ tokens: Math.round(sessionTokens + (usageRecorded ? 0 : turnUsage?.usage.totalTokens ?? 0)),
1384
1423
  cost: costLabel(),
1385
1424
  usageAvailable: activeSession.usage.available, usageEstimated: activeSession.usage.estimated,
1386
1425
  workspace: canonicalWorkspace,
@@ -1501,6 +1540,8 @@ export async function handleChatSession(args, version, signal) {
1501
1540
  eventListener: (event) => {
1502
1541
  if (event.type === 'model_change')
1503
1542
  usedModels.add(event.newModelId);
1543
+ if (event.type === 'gateway_status')
1544
+ process.stdout.write(renderGatewayFallbackUpdate(event, process.stdout.columns || 80));
1504
1545
  if (event.type === 'usage') {
1505
1546
  turnUsage = event;
1506
1547
  lastTokenUsage = event;
@@ -1514,7 +1555,7 @@ export async function handleChatSession(args, version, signal) {
1514
1555
  requestAbort.abort();
1515
1556
  }
1516
1557
  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));
1558
+ 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
1559
  lastUsagePaint = event.timestamp;
1519
1560
  }
1520
1561
  }
@@ -1554,28 +1595,45 @@ export async function handleChatSession(args, version, signal) {
1554
1595
  throw error;
1555
1596
  if (thinkingTimer)
1556
1597
  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 });
1598
+ const cloudOAuth = isModeradoCloudOAuthConnection(activeConnection);
1599
+ const cloudWithoutKey = activeConnection.id === 'moderado-cloud' && !activeConnection.apiKey;
1600
+ process.stdout.write(cloudWithoutKey
1601
+ ? '\nThis free Gateway route requires a Moderado website API key. Sign in or enter a key to retry once.\n'
1602
+ : cloudOAuth
1603
+ ? '\nModerado Cloud rejected this browser login. Sign in again to retry once.\n'
1604
+ : `\nThe saved ${activeConnection.displayName} API key was rejected. Enter a replacement key to retry once.\n`);
1605
+ const replacement = cloudOAuth || cloudWithoutKey
1606
+ ? await loginModeradoCloudInteractive({ signal, gatewayUrl: activeConnection.baseUrl })
1607
+ : await replaceProviderKeyInteractive(activeConnection, { signal });
1559
1608
  if (!replacement)
1560
1609
  throw error;
1561
1610
  recordTurnUsage();
1562
1611
  turnUsage = undefined;
1563
1612
  usageRecorded = false;
1564
- const secured = process.platform === 'win32' ? await storeConnectionCredential(replacement, credentialStore) : replacement;
1613
+ const secured = requiresCredentialReference(replacement) ? await storeConnectionCredential(replacement, credentialStore) : replacement;
1565
1614
  saveConnection(secured);
1566
1615
  config = loadConfig();
1567
1616
  activateConnection(secured);
1568
1617
  result = await runAgent();
1569
1618
  }
1570
- if (turnUsage && !turnUsage.estimated) {
1619
+ if (turnUsage) {
1571
1620
  let pricing;
1621
+ const modelIds = new Set([...usedModels, result.selectedModel.id]);
1622
+ const freePolicy = freeModelPolicyFor(activeConnection?.id);
1623
+ let inventory = [];
1572
1624
  try {
1573
- if (usedModels.size <= 1)
1574
- pricing = findModelPricing(await provider.discoverModels(signal), result.selectedModel.id);
1625
+ inventory = await provider.discoverModels(signal);
1575
1626
  }
1576
1627
  catch {
1577
1628
  // A valid answer remains usable when the provider catalog is unavailable.
1578
1629
  }
1630
+ const allModelsAreFree = [...modelIds].every((modelId) => isFreeModelOption(inventory.find((entry) => entry.id === modelId) ?? { id: modelId }, modelId === result.selectedModel.id
1631
+ ? result.selectedModel.classification
1632
+ : router.classifyModel(modelId, args.profile.includes('local')), freePolicy));
1633
+ if (allModelsAreFree)
1634
+ pricing = { prompt: '0', completion: '0' };
1635
+ else if (!turnUsage.estimated && modelIds.size === 1)
1636
+ pricing = findModelPricing(inventory, result.selectedModel.id);
1579
1637
  recordTurnUsage(pricing);
1580
1638
  }
1581
1639
  recordTurnUsage();
@@ -1584,13 +1642,27 @@ export async function handleChatSession(args, version, signal) {
1584
1642
  sessionStore.save(activeSession);
1585
1643
  const otherConnections = Object.keys(config.connections ?? {}).filter((id) => id !== activeConnection?.id);
1586
1644
  const resolution = resolveTurnAssistantAnswer(result, conversationHistory.length, lastErrorEvent, streamedAnswer, currentModel, otherConnections);
1587
- lastAnswer = resolution.answer;
1645
+ const searchFallback = result.status === 'failed' && liveSearch?.status === 'success'
1646
+ ? formatDirectWebSearchAnswer(liveSearch, true)
1647
+ : undefined;
1648
+ const hasSearchFallback = searchFallback !== undefined && searchFallback !== 'Web search returned no readable content.';
1649
+ lastAnswer = hasSearchFallback ? searchFallback : resolution.answer;
1588
1650
  if (budgetStopped) {
1589
1651
  const note = `Token budget reached (${tokenBudget?.toLocaleString()} per task). Task stopped; usage may exceed the limit because providers report in chunks.`;
1590
1652
  lastAnswer = [lastAnswer, note].filter(Boolean).join('\n\n');
1591
1653
  streamedAnswer = lastAnswer;
1592
1654
  }
1593
- if (resolution.isError) {
1655
+ if (resolution.isError && hasSearchFallback) {
1656
+ conversationHistory = [...conversationHistory, { role: 'user', content: trimmed }, { role: 'assistant', content: lastAnswer }];
1657
+ activeSession.messages = conversationHistory;
1658
+ activeSession.providerId = activeConnection?.id;
1659
+ activeSession.providerName = activeConnection?.displayName;
1660
+ activeSession.modelId = currentModel;
1661
+ activeSession.mode = activeMode;
1662
+ sessionStore.save(activeSession);
1663
+ streamedAnswer = lastAnswer;
1664
+ }
1665
+ else if (resolution.isError) {
1594
1666
  streamedAnswer = lastAnswer;
1595
1667
  }
1596
1668
  else {
@@ -1628,7 +1700,7 @@ export async function handleChatSession(args, version, signal) {
1628
1700
  const cleanErr = rawErr
1629
1701
  .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
1702
  .trim();
1631
- lastAnswer = `⚠️ **Error:** ${cleanErr || rawErr}\n\n👉 **Suggestion:** Switch models with \`/model\` or switch providers with \`/connect\`${altSuggestion}.`;
1703
+ lastAnswer = `⚠️ **Error:** ${cleanErr || rawErr}\n\n👉 **Suggestion:** Switch models with \`/model\` or choose a provider with \`/connect\`${altSuggestion}.`;
1632
1704
  lastThoughtTime = Math.max(0.001, (Date.now() - startedAt) / 1000);
1633
1705
  lastOutputTokenRate = undefined;
1634
1706
  process.stdout.write('\x1b[H\x1b[J');