vigthoria-cli 1.13.31 → 1.13.42

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 (51) hide show
  1. package/README.md +11 -56
  2. package/SECURITY_HARDENING.md +2 -4
  3. package/dist/commands/auth.js +16 -14
  4. package/dist/commands/cancel.d.ts +1 -0
  5. package/dist/commands/cancel.js +21 -4
  6. package/dist/commands/chat.d.ts +13 -0
  7. package/dist/commands/chat.js +170 -14
  8. package/dist/commands/fork.js +8 -3
  9. package/dist/commands/history.js +8 -3
  10. package/dist/commands/legion.js +1 -1
  11. package/dist/commands/replay.js +8 -3
  12. package/dist/commands/update-registration.js +63 -206
  13. package/dist/commands/v4-menu.d.ts +0 -1
  14. package/dist/commands/v4-menu.js +8 -6
  15. package/dist/commands/v4-registration.js +0 -2
  16. package/dist/commands/v4.js +9 -4
  17. package/dist/commands/wallet.d.ts +1 -1
  18. package/dist/commands/wallet.js +3 -3
  19. package/dist/index.js +2 -3
  20. package/dist/utils/agent-stream-state.d.ts +7 -0
  21. package/dist/utils/agent-stream-state.js +31 -0
  22. package/dist/utils/agentRunOutcome.js +5 -1
  23. package/dist/utils/api.d.ts +21 -3
  24. package/dist/utils/api.js +379 -550
  25. package/dist/utils/frontend-preview-service.d.ts +12 -2
  26. package/dist/utils/frontend-preview-service.js +214 -24
  27. package/dist/utils/javascript-syntax.d.ts +19 -0
  28. package/dist/utils/javascript-syntax.js +35 -0
  29. package/dist/utils/localTestMode.js +1 -1
  30. package/dist/utils/mutation-journal.d.ts +10 -0
  31. package/dist/utils/mutation-journal.js +104 -76
  32. package/dist/utils/network-policy.js +7 -10
  33. package/dist/utils/preview-screenshot-adapter.d.ts +111 -1
  34. package/dist/utils/preview-screenshot-adapter.js +990 -7
  35. package/dist/utils/release-install.d.ts +0 -1
  36. package/dist/utils/release-install.js +44 -7
  37. package/dist/utils/requestIntent.d.ts +1 -1
  38. package/dist/utils/requestIntent.js +17 -3
  39. package/dist/utils/tools.js +21 -15
  40. package/dist/utils/update-policy.d.ts +0 -4
  41. package/dist/utils/update-policy.js +5 -9
  42. package/dist/utils/v3-agent-client.js +25 -11
  43. package/install.ps1 +30 -50
  44. package/install.sh +12 -23
  45. package/package.json +4 -2
  46. package/release-policy.json +1 -5
  47. package/scripts/release/LOCAL_MACHINE_USER_VERIFICATION.md +6 -7
  48. package/scripts/release/install-release.mjs +5 -4
  49. package/scripts/release/publish-cli-release.mjs +21 -8
  50. package/scripts/release/test-balanced-model-live.sh +4 -1
  51. package/scripts/release/validate-live-service-gates.sh +27 -5
@@ -8,39 +8,11 @@ import { CH } from '../utils/logger.js';
8
8
  import { isOfflineMode, isUpdateCheckSuppressed, readCachedLatestVersion, writeCachedLatestVersion } from '../utils/cli-state.js';
9
9
  import { guardedFetch } from '../utils/network-policy.js';
10
10
  import { safeChildProcessEnv } from '../utils/secret-policy.js';
11
- import { CliCommandError, commandFailure } from '../utils/command-contract.js';
12
- import { compareSemanticVersions as compareVersions, resolveUpdateSource } from '../utils/update-policy.js';
11
+ import { CliCommandError } from '../utils/command-contract.js';
12
+ import { compareSemanticVersions as compareVersions } from '../utils/update-policy.js';
13
13
  import { assertReleaseTransition, assertReleaseUrl, getReleasePolicy, parseReleaseManifestText, verifyReleaseSignature } from '../utils/release-policy.js';
14
14
  import { installReleaseTransaction } from '../utils/release-install.js';
15
15
  import { createRuntimeTempDirectory, removeRuntimeTempDirectory } from '../utils/runtime-temp.js';
16
- function maxVersion(...versions) {
17
- let best = null;
18
- for (const version of versions) {
19
- if (!version)
20
- continue;
21
- if (!best || compareVersions(version, best) > 0)
22
- best = version;
23
- }
24
- return best;
25
- }
26
- async function fetchNpmLatestVersion() {
27
- try {
28
- // Hard 5s timeout: a slow/blocked path to the public npm registry must
29
- // never hang the caller (e.g. the Workbench's blocking `vigthoria update`
30
- // invocation) indefinitely.
31
- const latest = execFileSyncProcess(process.platform === 'win32' ? 'npm.cmd' : 'npm', ['view', 'vigthoria-cli', 'version'], {
32
- encoding: 'utf8',
33
- stdio: ['pipe', 'pipe', 'pipe'],
34
- windowsHide: true,
35
- timeout: 5000,
36
- env: safeChildProcessEnv(),
37
- }).trim();
38
- return latest || null;
39
- }
40
- catch {
41
- return null;
42
- }
43
- }
44
16
  const VIGTHORIA_DEFAULT_MANIFEST_URL = getReleasePolicy().origins.manifest;
45
17
  const MAX_RELEASE_ARCHIVE_BYTES = 256 * 1024 * 1024;
46
18
  async function readBoundedResponse(response, maximumBytes) {
@@ -114,15 +86,31 @@ function renderUpdateBanner(latestVersion, version) {
114
86
  console.log(chalk.red.bold(`\n${CH.warnEmoji} SECURITY UPDATE AVAILABLE`));
115
87
  console.log(chalk.red(` Version ${version} has security vulnerabilities.`));
116
88
  console.log(chalk.yellow(` Please update to ${latestVersion} immediately:`));
117
- console.log(chalk.white.bold(' npm install -g vigthoria-cli@latest\n'));
89
+ console.log(chalk.white.bold(' vigthoria update\n'));
118
90
  }
119
91
  else {
120
92
  console.log(chalk.yellow(`\n${CH.warnEmoji} Update available: ${version} -> ${latestVersion}`));
121
93
  console.log(chalk.gray(' Run `vigthoria update` to install\n'));
122
94
  }
123
95
  }
124
- // Check for updates quietly on startup. Cached (24 h) so we never block
125
- // startup on `npm view`. Honours VIGTHORIA_OFFLINE / VIGTHORIA_NO_UPDATE_CHECK.
96
+ async function fetchVerifiedManifestEntry(manifestUrl, channel) {
97
+ assertReleaseUrl(manifestUrl, 'manifest');
98
+ const response = await guardedFetch(manifestUrl, {
99
+ headers: { Accept: 'application/json' },
100
+ }, { audience: 'release', maxRedirects: getReleasePolicy().origins.maximumRedirects });
101
+ if (!response.ok)
102
+ throw new Error(`Manifest returned HTTP ${response.status}`);
103
+ const entry = parseReleaseManifestText(await response.text(), channel);
104
+ const signatureResponse = await guardedFetch(entry.signature.url, {
105
+ headers: { Accept: 'application/octet-stream' },
106
+ }, { audience: 'release', maxRedirects: getReleasePolicy().origins.maximumRedirects });
107
+ if (!signatureResponse.ok)
108
+ throw new Error(`Detached signature returned HTTP ${signatureResponse.status}`);
109
+ verifyReleaseSignature(channel, entry, await signatureResponse.text());
110
+ return entry;
111
+ }
112
+ // Check the signed extension manifest quietly on startup. Cached (24 h) so
113
+ // startup does not repeatedly reach the network. No registry fallback exists.
126
114
  export async function checkForUpdatesQuietly(version) {
127
115
  if (isUpdateCheckSuppressed()) {
128
116
  return;
@@ -134,26 +122,15 @@ export async function checkForUpdatesQuietly(version) {
134
122
  }
135
123
  return;
136
124
  }
137
- // Cache miss: probe npm registry in the background, with a hard 5 s
138
- // timeout. Result is cached for 24 h so subsequent runs are instant.
139
125
  try {
140
- const npmVersion = execFileSyncProcess(process.platform === 'win32' ? 'npm.cmd' : 'npm', ['view', 'vigthoria-cli', 'version'], {
141
- encoding: 'utf8',
142
- timeout: 5000,
143
- stdio: ['pipe', 'pipe', 'pipe'],
144
- windowsHide: true,
145
- env: safeChildProcessEnv(),
146
- }).trim();
147
- if (!npmVersion) {
148
- return;
149
- }
150
- writeCachedLatestVersion(npmVersion);
151
- if (compareVersions(npmVersion, version) > 0) {
152
- renderUpdateBanner(npmVersion, version);
126
+ const entry = await fetchVerifiedManifestEntry(VIGTHORIA_DEFAULT_MANIFEST_URL, 'stable');
127
+ writeCachedLatestVersion(entry.version);
128
+ if (compareVersions(entry.version, version) > 0) {
129
+ renderUpdateBanner(entry.version, version);
153
130
  }
154
131
  }
155
132
  catch {
156
- // Network or npm failure should never block CLI; do not cache failure.
133
+ // Network/signature failure never blocks startup and is never cached.
157
134
  }
158
135
  }
159
136
  export function registerUpdateCommand(program, version) {
@@ -163,72 +140,13 @@ export function registerUpdateCommand(program, version) {
163
140
  .alias('upgrade')
164
141
  .description('Check for updates and upgrade Vigthoria CLI. Default manifest: https://extension.vigthoria.io/downloads/manifest.json')
165
142
  .option('-c, --check', 'Only check for updates, don\'t install')
166
- .option('-f, --from <target>', 'Install from exact vigthoria-cli@version, local .tgz, or trusted HTTPS .tgz')
167
- .option('--sha256 <hex>', 'Required SHA-256 for HTTPS archives; optional verification for local archives')
168
143
  .option('-m, --manifest <url>', 'Update manifest URL for server-driven releases')
169
144
  .option('--channel <name>', 'Release channel to use from manifest (default: stable)', 'stable')
170
145
  .option('--allow-downgrade', 'Allow installing an older version from custom update source')
171
146
  .action(async (options) => {
172
- const updateTarget = typeof options.from === 'string' ? options.from.trim() : '';
173
147
  const manifestUrl = typeof options.manifest === 'string' ? options.manifest.trim() : VIGTHORIA_DEFAULT_MANIFEST_URL.trim();
174
148
  const channel = typeof options.channel === 'string' ? options.channel.trim() : 'stable';
175
149
  const allowDowngrade = !!options.allowDowngrade;
176
- if (updateTarget) {
177
- const source = resolveUpdateSource(updateTarget, { sha256: options.sha256 });
178
- try {
179
- if (options.check) {
180
- console.log(chalk.cyan(`Update source validated (${source.kind}): ${updateTarget}`));
181
- console.log(chalk.gray('Run `vigthoria update --from <target>` to install from this source'));
182
- return;
183
- }
184
- console.log(chalk.cyan(`Installing update from ${updateTarget}...`));
185
- let installTarget = source.kind === 'remote' ? '' : source.installTarget;
186
- let updateTempDirectory = null;
187
- try {
188
- if (source.kind === 'remote') {
189
- updateTempDirectory = createRuntimeTempDirectory('update-', 320 * 1024 * 1024);
190
- installTarget = path.join(updateTempDirectory, 'candidate.tgz');
191
- await downloadFile(source.downloadUrl, installTarget);
192
- }
193
- if (source.expectedSha256) {
194
- const actual = sha256File(installTarget).toLowerCase();
195
- if (actual !== source.expectedSha256) {
196
- throw new CliCommandError('Update archive checksum verification failed.', {
197
- code: 'UPDATE_CHECKSUM_MISMATCH',
198
- details: { expected: source.expectedSha256, actual },
199
- });
200
- }
201
- }
202
- const identity = source.kind === 'registry'
203
- ? { name: getReleasePolicy().packageName, version: installTarget.slice(installTarget.lastIndexOf('@') + 1) }
204
- : inspectReleaseArchive(installTarget);
205
- if (identity.name !== getReleasePolicy().packageName) {
206
- throw new CliCommandError('Update archive package identity is not vigthoria-cli.', { code: 'UPDATE_PACKAGE_IDENTITY_INVALID' });
207
- }
208
- if (!allowDowngrade && compareVersions(identity.version, version) < 0) {
209
- throw new CliCommandError(`Downgrade from ${version} to ${identity.version} requires --allow-downgrade.`, { code: 'UPDATE_DOWNGRADE_PROHIBITED' });
210
- }
211
- const installed = installReleaseTransaction(installTarget, identity.version, {
212
- ...(source.kind === 'registry' ? { npmRegistry: 'https://registry.npmjs.org/' } : {}),
213
- });
214
- console.log(chalk.gray(`Versioned installation: ${installed.versionDirectory}`));
215
- console.log(chalk.gray(`Active pointer: ${installed.currentLink}`));
216
- }
217
- finally {
218
- if (updateTempDirectory)
219
- removeRuntimeTempDirectory(updateTempDirectory);
220
- }
221
- console.log(chalk.green('Update installed successfully'));
222
- console.log(chalk.gray('Please restart the CLI to use the new version'));
223
- return;
224
- }
225
- catch (error) {
226
- throw commandFailure(error, {
227
- code: 'UPDATE_INSTALL_FAILED',
228
- message: `Failed to install the validated ${source.kind} update source.`,
229
- });
230
- }
231
- }
232
150
  if (isOfflineMode()) {
233
151
  console.log(chalk.yellow('Offline mode (VIGTHORIA_OFFLINE=1): skipping update check.'));
234
152
  return;
@@ -237,55 +155,25 @@ export function registerUpdateCommand(program, version) {
237
155
  let manifestEntry = null;
238
156
  if (manifestUrl) {
239
157
  try {
240
- assertReleaseUrl(manifestUrl, 'manifest');
241
158
  console.log(chalk.cyan(`Checking manifest channel ${channel}...`));
242
- const response = await guardedFetch(manifestUrl, {
243
- headers: { Accept: 'application/json' },
244
- }, { audience: 'release', maxRedirects: getReleasePolicy().origins.maximumRedirects });
245
- if (!response.ok)
246
- throw new Error(`Manifest returned HTTP ${response.status}`);
247
- const manifestText = await response.text();
248
- manifestEntry = parseReleaseManifestText(manifestText, channel);
249
- const signatureResponse = await guardedFetch(manifestEntry.signature.url, {
250
- headers: { Accept: 'application/octet-stream' },
251
- }, { audience: 'release', maxRedirects: getReleasePolicy().origins.maximumRedirects });
252
- if (!signatureResponse.ok)
253
- throw new Error(`Detached signature returned HTTP ${signatureResponse.status}`);
254
- verifyReleaseSignature(channel, manifestEntry, await signatureResponse.text());
159
+ manifestEntry = await fetchVerifiedManifestEntry(manifestUrl, channel);
255
160
  console.log(chalk.gray(`Verified ${channel} manifest: version=${manifestEntry.version} package=${manifestEntry.packageName}@${manifestEntry.packageVersion} size=${manifestEntry.size} sha256=${manifestEntry.sha256}`));
256
161
  }
257
162
  catch (error) {
258
163
  if (error instanceof CliCommandError)
259
164
  throw error;
260
- console.log(chalk.yellow(`Manifest check failed: ${error.message}`));
261
- console.log(chalk.gray('Continuing with the exact-version npm registry fallback...'));
262
- }
263
- }
264
- // The release manifest (server-curated, sha256-verified) is the
265
- // authoritative source of truth for this private CLI. Only fall back
266
- // to probing the public npm registry when the manifest itself has no
267
- // usable entry - never let an npm version number override a valid,
268
- // verified manifest entry (npm install -g against the public registry
269
- // is slower, unverified, and has previously caused the Workbench's
270
- // update button to hang for minutes).
271
- let npmVersion = null;
272
- if (manifestEntry?.version && manifestEntry?.url) {
273
- console.log(chalk.gray('Release manifest has a valid entry; skipping public npm registry lookup.'));
274
- }
275
- else {
276
- console.log(chalk.cyan('Checking npm registry...'));
277
- npmVersion = await fetchNpmLatestVersion();
278
- if (!npmVersion) {
279
- console.log(chalk.yellow('Could not read latest version from npm registry.'));
165
+ throw new CliCommandError(`Signed extension release manifest is unavailable: ${error.message}`, {
166
+ code: 'UPDATE_MANIFEST_UNAVAILABLE',
167
+ category: 'network',
168
+ });
280
169
  }
281
170
  }
282
- const effectiveLatest = maxVersion(manifestEntry?.version, npmVersion);
283
- if (!effectiveLatest) {
284
- throw new CliCommandError('Unable to determine the latest CLI version from manifest or npm.', {
285
- code: 'UPDATE_VERSION_UNAVAILABLE',
171
+ if (!manifestEntry)
172
+ throw new CliCommandError('Signed extension release manifest is unavailable.', {
173
+ code: 'UPDATE_MANIFEST_UNAVAILABLE',
286
174
  category: 'network',
287
175
  });
288
- }
176
+ const effectiveLatest = manifestEntry.version;
289
177
  if (!allowDowngrade && compareVersions(effectiveLatest, currentVersion) <= 0) {
290
178
  console.log(chalk.green(`You are running the latest version (${currentVersion})`));
291
179
  return;
@@ -298,72 +186,41 @@ export function registerUpdateCommand(program, version) {
298
186
  console.log(chalk.gray('Run `vigthoria update` to install the update'));
299
187
  return;
300
188
  }
301
- const manifestIsAuthoritative = Boolean(manifestEntry
302
- && manifestEntry.url
303
- && compareVersions(manifestEntry.version, effectiveLatest) >= 0
304
- && compareVersions(manifestEntry.version, currentVersion) > 0);
305
- if (manifestIsAuthoritative && manifestEntry) {
306
- assertReleaseTransition(currentVersion, manifestEntry, channel, allowDowngrade);
307
- const updateTempDirectory = createRuntimeTempDirectory('update-', 320 * 1024 * 1024);
308
- const tmpFile = path.join(updateTempDirectory, 'candidate.tgz');
309
- try {
310
- console.log(chalk.cyan(`Downloading release package (${manifestEntry.version})...`));
311
- await downloadFile(manifestEntry.url, tmpFile);
312
- const expected = String(manifestEntry.sha256).toLowerCase();
313
- const actual = sha256File(tmpFile).toLowerCase();
314
- if (actual !== expected) {
315
- throw new CliCommandError('Release checksum verification failed.', {
316
- code: 'UPDATE_CHECKSUM_MISMATCH',
317
- details: { expected, actual },
318
- });
319
- }
320
- if (fs.statSync(tmpFile).size !== manifestEntry.size) {
321
- throw new CliCommandError('Release archive size does not match signed manifest metadata.', {
322
- code: 'UPDATE_SIZE_MISMATCH',
323
- });
324
- }
325
- console.log(chalk.green('Checksum verification passed'));
326
- console.log(chalk.cyan('Installing update...'));
327
- const identity = inspectReleaseArchive(tmpFile);
328
- if (identity.name !== manifestEntry.packageName || identity.version !== manifestEntry.packageVersion) {
329
- throw new CliCommandError('Release archive package metadata conflicts with signed manifest identity.', {
330
- code: 'UPDATE_PACKAGE_IDENTITY_INVALID',
331
- });
332
- }
333
- const installed = installReleaseTransaction(tmpFile, manifestEntry.version);
334
- console.log(chalk.gray(`Versioned installation: ${installed.versionDirectory}`));
335
- console.log(chalk.green(`Updated to version ${manifestEntry.version}`));
336
- console.log(chalk.gray('Please restart the CLI to use the new version'));
337
- return;
189
+ assertReleaseTransition(currentVersion, manifestEntry, channel, allowDowngrade);
190
+ const updateTempDirectory = createRuntimeTempDirectory('update-', 320 * 1024 * 1024);
191
+ const tmpFile = path.join(updateTempDirectory, 'candidate.tgz');
192
+ try {
193
+ console.log(chalk.cyan(`Downloading release package (${manifestEntry.version})...`));
194
+ await downloadFile(manifestEntry.url, tmpFile);
195
+ const expected = String(manifestEntry.sha256).toLowerCase();
196
+ const actual = sha256File(tmpFile).toLowerCase();
197
+ if (actual !== expected) {
198
+ throw new CliCommandError('Release checksum verification failed.', {
199
+ code: 'UPDATE_CHECKSUM_MISMATCH',
200
+ details: { expected, actual },
201
+ });
338
202
  }
339
- finally {
340
- removeRuntimeTempDirectory(updateTempDirectory);
203
+ if (fs.statSync(tmpFile).size !== manifestEntry.size) {
204
+ throw new CliCommandError('Release archive size does not match signed manifest metadata.', {
205
+ code: 'UPDATE_SIZE_MISMATCH',
206
+ });
341
207
  }
342
- }
343
- const npmSpec = npmVersion && compareVersions(npmVersion, currentVersion) > 0
344
- ? `vigthoria-cli@${npmVersion}`
345
- : `vigthoria-cli@${effectiveLatest}`;
346
- try {
347
- const registrySource = resolveUpdateSource(npmSpec);
348
- if (registrySource.kind !== 'registry') {
349
- throw new CliCommandError('Registry fallback did not resolve to an exact package version.', {
350
- code: 'UPDATE_REGISTRY_SOURCE_INVALID',
351
- category: 'configuration',
208
+ console.log(chalk.green('Checksum verification passed'));
209
+ console.log(chalk.cyan('Installing update...'));
210
+ const identity = inspectReleaseArchive(tmpFile);
211
+ if (identity.name !== manifestEntry.packageName || identity.version !== manifestEntry.packageVersion) {
212
+ throw new CliCommandError('Release archive package metadata conflicts with signed manifest identity.', {
213
+ code: 'UPDATE_PACKAGE_IDENTITY_INVALID',
352
214
  });
353
215
  }
354
- console.log(chalk.cyan(`Installing update from npm registry (${npmSpec})...`));
355
- const installed = installReleaseTransaction(registrySource.installTarget, effectiveLatest, {
356
- npmRegistry: 'https://registry.npmjs.org/',
357
- });
216
+ const installed = installReleaseTransaction(tmpFile, manifestEntry.version);
358
217
  console.log(chalk.gray(`Versioned installation: ${installed.versionDirectory}`));
359
- console.log(chalk.green(`Updated to version ${effectiveLatest}`));
218
+ console.log(chalk.green(`Updated to version ${manifestEntry.version}`));
360
219
  console.log(chalk.gray('Please restart the CLI to use the new version'));
220
+ return;
361
221
  }
362
- catch (error) {
363
- throw commandFailure(error, {
364
- code: 'UPDATE_REGISTRY_INSTALL_FAILED',
365
- message: `Failed to install the exact registry package ${npmSpec}.`,
366
- });
222
+ finally {
223
+ removeRuntimeTempDirectory(updateTempDirectory);
367
224
  }
368
225
  });
369
226
  }
@@ -1,7 +1,6 @@
1
1
  export interface V4MenuOptions {
2
2
  defaultAgent?: string;
3
3
  defaultProvider?: string;
4
- byokApiKey?: string;
5
4
  byokBaseUrl?: string;
6
5
  byokModel?: string;
7
6
  outFile: string;
@@ -18,7 +18,9 @@ function isCancelError(error) {
18
18
  return name === 'ExitPromptError' || name === 'ERR_CLOSED';
19
19
  }
20
20
  function writeResult(outFile, result) {
21
- fs.writeFileSync(outFile, JSON.stringify(result), 'utf8');
21
+ fs.writeFileSync(outFile, JSON.stringify(result), { encoding: 'utf8', mode: 0o600 });
22
+ if (process.platform !== 'win32')
23
+ fs.chmodSync(outFile, 0o600);
22
24
  }
23
25
  export async function runV4Menu(options) {
24
26
  const defaultAgent = options.defaultAgent === 'v4' ? 'v4' : 'v3';
@@ -71,16 +73,16 @@ export async function runV4Menu(options) {
71
73
  writeResult(options.outFile, { agent: 'v4', provider });
72
74
  return;
73
75
  }
74
- const existingApiKey = String(options.byokApiKey || '').trim();
76
+ const existingApiKey = String(process.env.VIGTHORIA_BYOK_API_KEY || '').trim();
75
77
  const existingBaseUrl = String(options.byokBaseUrl || '').trim();
76
78
  const existingModel = String(options.byokModel || '').trim();
77
- const missing = !existingApiKey || !existingBaseUrl || !existingModel;
79
+ const missingDefaults = !existingBaseUrl || !existingModel;
78
80
  const questions = [];
79
81
  if (!existingApiKey) {
80
82
  questions.push({
81
83
  type: 'password',
82
84
  name: 'apiKey',
83
- message: 'BYOK API key:',
85
+ message: 'BYOK API key (used for this session only):',
84
86
  mask: '*',
85
87
  validate: (value) => (String(value || '').trim() ? true : 'API key is required.'),
86
88
  });
@@ -106,12 +108,12 @@ export async function runV4Menu(options) {
106
108
  const baseUrl = String(answers.baseUrl || existingBaseUrl).trim();
107
109
  const model = String(answers.model || existingModel).trim();
108
110
  let save = false;
109
- if (missing) {
111
+ if (missingDefaults) {
110
112
  const { saveLocally } = await inquirer.prompt([
111
113
  {
112
114
  type: 'confirm',
113
115
  name: 'saveLocally',
114
- message: 'Store BYOK values locally in ~/.config/vigthoria/config.yaml?',
116
+ message: 'Store the non-secret base URL and model locally? (The API key is never stored.)',
115
117
  default: true,
116
118
  },
117
119
  ]);
@@ -7,7 +7,6 @@ export function registerV4Commands(program, config, logger) {
7
7
  .description('Internal: interactive V3/V4 agent + provider picker used by the V4 Operating Agent launcher')
8
8
  .option('--default-agent <agent>', 'v3 | v4', 'v3')
9
9
  .option('--default-provider <provider>', 'cloud | v3_local | byok', 'cloud')
10
- .option('--byok-api-key <key>', 'Existing BYOK api key', '')
11
10
  .option('--byok-base-url <url>', 'Existing BYOK base url', '')
12
11
  .option('--byok-model <name>', 'Existing BYOK model', '')
13
12
  .requiredOption('--out-file <path>', 'Write JSON selection result to this file')
@@ -15,7 +14,6 @@ export function registerV4Commands(program, config, logger) {
15
14
  await runV4Menu({
16
15
  defaultAgent: options.defaultAgent,
17
16
  defaultProvider: options.defaultProvider,
18
- byokApiKey: options.byokApiKey,
19
17
  byokBaseUrl: options.byokBaseUrl,
20
18
  byokModel: options.byokModel,
21
19
  outFile: options.outFile,
@@ -32,7 +32,7 @@ export class V4Command {
32
32
  .alias('v4-agent')
33
33
  .description('Launch Vigthoria V4 Operating Agent (DeerFlow 2.0 harness)')
34
34
  .option('--provider <provider>', 'Provider: cloud | v3_local | byok')
35
- .option('--api-key <key>', 'BYOK API key for non-interactive mode')
35
+ .option('--api-key <key>', 'BYOK API key for compatibility; prefer VIGTHORIA_BYOK_API_KEY to avoid process-list exposure')
36
36
  .option('--base-url <url>', 'Provider base URL override')
37
37
  .option('--model <name>', 'Provider model override')
38
38
  .option('--transport <mode>', 'Bridge transport: auto | ws | sse', 'auto')
@@ -68,8 +68,13 @@ export class V4Command {
68
68
  if (options.provider) {
69
69
  args.push('--provider', String(options.provider));
70
70
  }
71
- if (options.apiKey) {
72
- args.push('--api-key', String(options.apiKey));
71
+ const childEnv = safeChildProcessEnv(process.env, { PYTHONUNBUFFERED: '1' });
72
+ const runtimeByokKey = options.apiKey || process.env.VIGTHORIA_BYOK_API_KEY;
73
+ if (runtimeByokKey) {
74
+ // Do not place provider secrets in the child process argument vector.
75
+ // The CLI option is retained for backwards compatibility, while the
76
+ // V4 process receives it through an ephemeral environment variable.
77
+ childEnv.VIGTHORIA_BYOK_API_KEY = String(runtimeByokKey);
73
78
  }
74
79
  if (options.baseUrl) {
75
80
  args.push('--base-url', String(options.baseUrl));
@@ -110,7 +115,7 @@ export class V4Command {
110
115
  const child = spawn(pythonBin, args, {
111
116
  cwd: v4Path,
112
117
  stdio: 'inherit',
113
- env: safeChildProcessEnv(process.env, { PYTHONUNBUFFERED: '1' }),
118
+ env: childEnv,
114
119
  });
115
120
  const exitCode = await new Promise((resolve, reject) => {
116
121
  let interrupted = false;
@@ -1,5 +1,5 @@
1
1
  /**
2
- * wallet.ts — VigCoin wallet management for Vigthoria CLI.
2
+ * wallet.ts — monetary Vigthoria Credits management for Vigthoria CLI.
3
3
  *
4
4
  * vigthoria wallet balance — show current balance
5
5
  * vigthoria wallet history [--n 20] — recent transactions
@@ -1,5 +1,5 @@
1
1
  /**
2
- * wallet.ts — VigCoin wallet management for Vigthoria CLI.
2
+ * wallet.ts — monetary Vigthoria Credits management for Vigthoria CLI.
3
3
  *
4
4
  * vigthoria wallet balance — show current balance
5
5
  * vigthoria wallet history [--n 20] — recent transactions
@@ -131,7 +131,7 @@ export class WalletCommand {
131
131
  }
132
132
  const txs = data.transactions || [];
133
133
  console.log('');
134
- console.log(chalk.bold.cyan(` VigCoin Transaction History (last ${txs.length})`));
134
+ console.log(chalk.bold.cyan(` Vigthoria Credits Transaction History (last ${txs.length})`));
135
135
  console.log(chalk.gray(' ──────────────────────────────────────────────────────────'));
136
136
  if (txs.length === 0) {
137
137
  console.log(chalk.gray(' No transactions found.'));
@@ -141,7 +141,7 @@ export class WalletCommand {
141
141
  const sign = tx.amount >= 0 ? chalk.green(`+${tx.amount}`) : chalk.red(String(tx.amount));
142
142
  const date = new Date(tx.created_at).toLocaleString();
143
143
  const desc = tx.description || tx.action || tx.type;
144
- console.log(` ${chalk.gray(date)} ${sign.padStart(8)} ${chalk.gray(`→`)} ${chalk.yellow(tx.balance_after)} ${desc}`);
144
+ console.log(` ${chalk.gray(date)} ${sign.padStart(8)} credits ${chalk.gray(`→`)} ${chalk.yellow(tx.balance_after)} credits ${desc}`);
145
145
  }
146
146
  }
147
147
  console.log('');
package/dist/index.js CHANGED
@@ -179,9 +179,8 @@ export function validateReleaseMetadata() {
179
179
  const readme = fs.readFileSync(readmePath, 'utf8');
180
180
  const bins = pkg.bin && typeof pkg.bin === 'object' ? pkg.bin : {};
181
181
  const requiredReadmePhrases = [
182
- 'npm install -g vigthoria-cli',
183
- 'curl -fsSL https://cli.vigthoria.io/install.sh | bash',
184
- 'irm https://cli.vigthoria.io/install.ps1 | iex',
182
+ 'curl -fsSL https://extension.vigthoria.io/downloads/install.sh | bash',
183
+ 'irm https://extension.vigthoria.io/downloads/install.ps1 | iex',
185
184
  'vigthoria login',
186
185
  'vigthoria chat',
187
186
  'vig c',
@@ -3,6 +3,11 @@ export interface AgentStreamStateDependencies {
3
3
  isJsonOutput(): boolean;
4
4
  idleTimeoutMs(): number;
5
5
  }
6
+ export type AgentToolResultPresentation = 'normal' | 'hidden' | 'correcting';
7
+ /** Decode escaped line breaks without corrupting POSIX paths such as src/render.js. */
8
+ export declare function normalizeAgentToolOutputForDisplay(output: string): string;
9
+ /** Keep governed executor recovery details out of the normal user transcript. */
10
+ export declare function classifyAgentToolResultPresentation(event: any): AgentToolResultPresentation;
6
11
  /** Owns the mutable lifecycle and deduplication state for one rendered agent stream. */
7
12
  export declare class AgentStreamState {
8
13
  private readonly dependencies;
@@ -18,9 +23,11 @@ export declare class AgentStreamState {
18
23
  readonly seenToolResults: Set<string>;
19
24
  private idleWatchInterval;
20
25
  private idleNoticeShown;
26
+ private lastCorrectionNoticeAt;
21
27
  constructor(dependencies: AgentStreamStateDependencies);
22
28
  reset(): void;
23
29
  noteActivity(): void;
30
+ shouldShowCorrectionNotice(now?: number, minimumIntervalMs?: number): boolean;
24
31
  startIdleWatch(spinner: Ora | null): void;
25
32
  stopIdleWatch(): void;
26
33
  sanitizeVisibleText(text: string): string;
@@ -1,4 +1,26 @@
1
1
  import chalk from 'chalk';
2
+ /** Decode escaped line breaks without corrupting POSIX paths such as src/render.js. */
3
+ export function normalizeAgentToolOutputForDisplay(output) {
4
+ return String(output || '')
5
+ .replace(/\\r\\n|\\n/g, '\n')
6
+ .replace(/\\r/g, '')
7
+ .replace(/\t/g, ' ');
8
+ }
9
+ /** Keep governed executor recovery details out of the normal user transcript. */
10
+ export function classifyAgentToolResultPresentation(event) {
11
+ if (!event || event.type !== 'tool_result')
12
+ return 'normal';
13
+ if (event.user_visible === false || String(event.visibility || '').toLowerCase() === 'internal') {
14
+ return 'hidden';
15
+ }
16
+ if (event.success !== false)
17
+ return 'normal';
18
+ const detail = String(event.error || event.output || '');
19
+ if (/\b(?:deferred a second mutation|byte-identical source|duplicate reads are blocked|mutation recovery is active|was not offered for this atomic task|verification repair is pinned|blocked wrong-path|out-of-scope mutation|old_string found 0 times|mutation rejected before (?:write|edit)|degenerate mutation blocked before task acceptance|skipped (?:syntax|preview|runtime)_check[^.]*file does not exist yet|file not found in the local workspace)\b/i.test(detail)) {
20
+ return 'correcting';
21
+ }
22
+ return 'normal';
23
+ }
2
24
  /** Owns the mutable lifecycle and deduplication state for one rendered agent stream. */
3
25
  export class AgentStreamState {
4
26
  dependencies;
@@ -14,6 +36,7 @@ export class AgentStreamState {
14
36
  seenToolResults = new Set();
15
37
  idleWatchInterval = null;
16
38
  idleNoticeShown = false;
39
+ lastCorrectionNoticeAt = 0;
17
40
  constructor(dependencies) {
18
41
  this.dependencies = dependencies;
19
42
  }
@@ -29,8 +52,16 @@ export class AgentStreamState {
29
52
  this.seenToolCalls.clear();
30
53
  this.seenToolResults.clear();
31
54
  this.idleNoticeShown = false;
55
+ this.lastCorrectionNoticeAt = 0;
32
56
  }
33
57
  noteActivity() { this.lastActivity = Date.now(); }
58
+ shouldShowCorrectionNotice(now = Date.now(), minimumIntervalMs = 5_000) {
59
+ if (this.lastCorrectionNoticeAt > 0 && now - this.lastCorrectionNoticeAt < minimumIntervalMs) {
60
+ return false;
61
+ }
62
+ this.lastCorrectionNoticeAt = now;
63
+ return true;
64
+ }
34
65
  startIdleWatch(spinner) {
35
66
  this.stopIdleWatch();
36
67
  if (this.dependencies.isJsonOutput() || !spinner)
@@ -42,7 +42,11 @@ const LIST_ONLY_DISCOVERY_TOOLS = new Set([
42
42
  'glob',
43
43
  'dir',
44
44
  ]);
45
- const GENERIC_SUMMARY_RE = /^(completed the requested analysis|reviewed the workspace without writing changes|task completed|done|finished|analysis completed)([.\s]|$)/i;
45
+ // Match only the complete placeholder. A real completion report commonly
46
+ // starts with "Done" before listing verified work and the user's next step;
47
+ // treating every such report as a stub discards the authoritative server
48
+ // summary and leaves the CLI showing only a filename list.
49
+ const GENERIC_SUMMARY_RE = /^(completed the requested analysis|reviewed the workspace without writing changes|task completed|done|finished|analysis completed)[.!]?$/i;
46
50
  /** Planner fallback / degraded inference — generic greeting unrelated to the user question. */
47
51
  const GENERIC_FALLBACK_GREETING_RE = /^(?:hello!?|hi!?|hey!?)\s*(?:how can i assist|how can i help|what would you like|whether you'?re looking to build)/i;
48
52
  /** System-prompt fragments leaked when the model falls back without real context. */