vana-cli 0.15.0 → 0.23.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +73 -15
- package/dist/cli/app/ask.d.ts +38 -0
- package/dist/cli/app/ask.d.ts.map +1 -0
- package/dist/cli/app/ask.js +110 -0
- package/dist/cli/app/ask.js.map +1 -0
- package/dist/cli/app/derivatives.d.ts +41 -0
- package/dist/cli/app/derivatives.d.ts.map +1 -0
- package/dist/cli/app/derivatives.js +227 -0
- package/dist/cli/app/derivatives.js.map +1 -0
- package/dist/cli/app/escrow.d.ts +44 -0
- package/dist/cli/app/escrow.d.ts.map +1 -0
- package/dist/cli/app/escrow.js +275 -0
- package/dist/cli/app/escrow.js.map +1 -0
- package/dist/cli/app/index.d.ts +11 -0
- package/dist/cli/app/index.d.ts.map +1 -0
- package/dist/cli/app/index.js +151 -0
- package/dist/cli/app/index.js.map +1 -0
- package/dist/cli/app/onchain.d.ts +14 -0
- package/dist/cli/app/onchain.d.ts.map +1 -0
- package/dist/cli/app/onchain.js +84 -0
- package/dist/cli/app/onchain.js.map +1 -0
- package/dist/cli/app/outcome.d.ts +45 -0
- package/dist/cli/app/outcome.d.ts.map +1 -0
- package/dist/cli/app/outcome.js +55 -0
- package/dist/cli/app/outcome.js.map +1 -0
- package/dist/cli/app/read.d.ts +43 -0
- package/dist/cli/app/read.d.ts.map +1 -0
- package/dist/cli/app/read.js +429 -0
- package/dist/cli/app/read.js.map +1 -0
- package/dist/cli/app/register.d.ts +34 -0
- package/dist/cli/app/register.d.ts.map +1 -0
- package/dist/cli/app/register.js +113 -0
- package/dist/cli/app/register.js.map +1 -0
- package/dist/cli/app/request.d.ts +50 -0
- package/dist/cli/app/request.d.ts.map +1 -0
- package/dist/cli/app/request.js +252 -0
- package/dist/cli/app/request.js.map +1 -0
- package/dist/cli/app/requests.d.ts +21 -0
- package/dist/cli/app/requests.d.ts.map +1 -0
- package/dist/cli/app/requests.js +163 -0
- package/dist/cli/app/requests.js.map +1 -0
- package/dist/cli/app/whoami.d.ts +13 -0
- package/dist/cli/app/whoami.d.ts.map +1 -0
- package/dist/cli/app/whoami.js +71 -0
- package/dist/cli/app/whoami.js.map +1 -0
- package/dist/cli/auth.d.ts +18 -0
- package/dist/cli/auth.d.ts.map +1 -1
- package/dist/cli/auth.js +109 -7
- package/dist/cli/auth.js.map +1 -1
- package/dist/cli/index.d.ts +2 -2
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +307 -49
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/mcp-server.d.ts.map +1 -1
- package/dist/cli/mcp-server.js +32 -3
- package/dist/cli/mcp-server.js.map +1 -1
- package/dist/core/app-key.d.ts +50 -0
- package/dist/core/app-key.d.ts.map +1 -0
- package/dist/core/app-key.js +171 -0
- package/dist/core/app-key.js.map +1 -0
- package/dist/core/assets.d.ts +33 -0
- package/dist/core/assets.d.ts.map +1 -0
- package/dist/core/assets.js +87 -0
- package/dist/core/assets.js.map +1 -0
- package/dist/core/cli-types.d.ts +30 -0
- package/dist/core/cli-types.d.ts.map +1 -1
- package/dist/core/cli-types.js +16 -0
- package/dist/core/cli-types.js.map +1 -1
- package/dist/core/exit-codes.d.ts +48 -0
- package/dist/core/exit-codes.d.ts.map +1 -0
- package/dist/core/exit-codes.js +96 -0
- package/dist/core/exit-codes.js.map +1 -0
- package/dist/core/network.d.ts +52 -0
- package/dist/core/network.d.ts.map +1 -0
- package/dist/core/network.js +83 -0
- package/dist/core/network.js.map +1 -0
- package/dist/core/paths.d.ts +4 -2
- package/dist/core/paths.d.ts.map +1 -1
- package/dist/core/paths.js +8 -6
- package/dist/core/paths.js.map +1 -1
- package/dist/core/receipts.d.ts +37 -0
- package/dist/core/receipts.d.ts.map +1 -0
- package/dist/core/receipts.js +72 -0
- package/dist/core/receipts.js.map +1 -0
- package/dist/core/requests-store.d.ts +54 -0
- package/dist/core/requests-store.d.ts.map +1 -0
- package/dist/core/requests-store.js +83 -0
- package/dist/core/requests-store.js.map +1 -0
- package/dist/personal-server/registered.d.ts +25 -0
- package/dist/personal-server/registered.d.ts.map +1 -0
- package/dist/personal-server/registered.js +49 -0
- package/dist/personal-server/registered.js.map +1 -0
- package/dist/server/request-signer.d.ts.map +1 -1
- package/dist/server/request-signer.js +7 -24
- package/dist/server/request-signer.js.map +1 -1
- package/dist/server/session-relay.d.ts +6 -5
- package/dist/server/session-relay.d.ts.map +1 -1
- package/dist/server/session-relay.js +48 -51
- package/dist/server/session-relay.js.map +1 -1
- package/package.json +2 -1
package/dist/cli/index.js
CHANGED
|
@@ -2,11 +2,12 @@ import fs from "node:fs";
|
|
|
2
2
|
import fsp from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
5
6
|
import { spawn, execSync } from "node:child_process";
|
|
6
7
|
import os from "node:os";
|
|
7
8
|
import { confirm, input, password } from "@inquirer/prompts";
|
|
8
9
|
import { searchSelect } from "./search-select.js";
|
|
9
|
-
import { Command, CommanderError } from "commander";
|
|
10
|
+
import { Command, CommanderError, Option } from "commander";
|
|
10
11
|
// Vana-branded theme for inquirer prompts — matches brand palette
|
|
11
12
|
const VANA_BLUE = "\x1b[38;2;65;65;252m";
|
|
12
13
|
const VANA_GREEN = "\x1b[38;2;0;213;11m";
|
|
@@ -28,14 +29,91 @@ const vanaPromptTheme = {
|
|
|
28
29
|
};
|
|
29
30
|
import { createConnectRenderer, createHumanRenderer, createLoginRenderer, formatDisplayPath, formatRelativeTime, } from "./render/index.js";
|
|
30
31
|
import { CliOutcomeStatus, migrateLegacyDataHome, getBrowserProfilesDir, getConnectorCacheDir, getLogsDir, getSessionsDir, getSourceResultPath, readCliState, readCliConfig, updateCliConfig, updateSourceState, } from "../core/index.js";
|
|
32
|
+
import { UnknownNetworkError, VANA_NETWORKS, isVanaNetworkName, resolveNetwork, } from "../core/network.js";
|
|
33
|
+
import { CliExitCode } from "../core/exit-codes.js";
|
|
34
|
+
import { registerAppCommands } from "./app/index.js";
|
|
35
|
+
import { lookupRegisteredServers } from "../personal-server/registered.js";
|
|
31
36
|
import { fetchConnectorToCache, listAvailableSources, readCachedConnectorMetadata, } from "../connectors/registry.js";
|
|
32
37
|
import { detectPersonalServerTarget, ingestResult, resolvePersonalServerAuthConfig, } from "../personal-server/index.js";
|
|
33
38
|
import { findDataConnectorsDir, ManagedPlaywrightRuntime, } from "../runtime/index.js";
|
|
34
39
|
import { listAvailableSkills, installSkill, readInstalledSkills, } from "../skills/index.js";
|
|
35
40
|
import { queryStatus, querySources, queryDataList, queryDataShow, queryDoctor, } from "./queries.js";
|
|
36
41
|
import { checkForUpdate, readUpdateCheck, isNewerVersion, } from "./update-check.js";
|
|
37
|
-
import { loadCredentials, saveCredentials, clearCredentials, isExpired, formatAddress, formatExpiresIn, getAuthTarget, resolvePersonalServerUrl, runDeviceCodeFlow, runSelfHostedLoginFlow, } from "./auth.js";
|
|
42
|
+
import { loadCredentials, readStoredAuthFile, saveCredentials, clearCredentials, isExpired, formatAddress, formatExpiresIn, getAuthTarget, resolvePersonalServerUrl, runDeviceCodeFlow, runSelfHostedLoginFlow, } from "./auth.js";
|
|
38
43
|
import { createCliTelemetrySession, flushTelemetryOutbox, getActiveTelemetrySession, getTelemetryStatus, setActiveTelemetrySession, setTelemetryEnabled, trackActiveTelemetryEvent, } from "./telemetry.js";
|
|
44
|
+
/** Long flags shared by every command; declared once, applied to all leaves. */
|
|
45
|
+
const GLOBAL_FLAG_DEFS = [
|
|
46
|
+
["--json", "Output machine-readable JSON"],
|
|
47
|
+
["--no-input", "Never prompt; fail or print a URL instead"],
|
|
48
|
+
["--yes", "Assume yes for confirmation prompts"],
|
|
49
|
+
["--quiet", "Suppress non-essential output"],
|
|
50
|
+
["--ipc", "Emit structured IPC events (internal)"],
|
|
51
|
+
["--detach", "Run in the background where supported"],
|
|
52
|
+
];
|
|
53
|
+
/**
|
|
54
|
+
* Declare the shared global flags on every leaf command that does not
|
|
55
|
+
* already define them, so commander accepts them anywhere on the line and
|
|
56
|
+
* `--help` documents them. Idempotent per flag.
|
|
57
|
+
*/
|
|
58
|
+
function applyGlobalOptions(command) {
|
|
59
|
+
if (command.commands.length === 0) {
|
|
60
|
+
for (const [flags, description] of GLOBAL_FLAG_DEFS) {
|
|
61
|
+
const long = `--${flags.replace(/^--(no-)?/, "")}`;
|
|
62
|
+
const exists = command.options.some((option) => option.long === flags || option.long === long);
|
|
63
|
+
if (!exists) {
|
|
64
|
+
command.option(flags, description);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (!command.options.some((option) => option.long === "--network")) {
|
|
68
|
+
command.addOption(new Option("--network <network>", "Vana network").choices([
|
|
69
|
+
...VANA_NETWORKS,
|
|
70
|
+
]));
|
|
71
|
+
}
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
for (const child of command.commands) {
|
|
75
|
+
applyGlobalOptions(child);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Merge the options commander actually parsed (root -> action command, child
|
|
80
|
+
* wins) into the pre-parse seed, so action handlers see the authoritative
|
|
81
|
+
* values while pre-parse consumers (update-notifier suppression, telemetry
|
|
82
|
+
* context) keep working from the argv scan.
|
|
83
|
+
*/
|
|
84
|
+
function mergeParsedGlobalOptions(target, actionCommand) {
|
|
85
|
+
const chain = [];
|
|
86
|
+
for (let current = actionCommand; current; current = current.parent) {
|
|
87
|
+
chain.unshift(current);
|
|
88
|
+
}
|
|
89
|
+
// Only values commander actually parsed count; a leaf's implicit
|
|
90
|
+
// default (e.g. --no-input declaring input: true) must never overwrite a
|
|
91
|
+
// value the user set at another level of the command line.
|
|
92
|
+
const merged = {};
|
|
93
|
+
for (const command of chain) {
|
|
94
|
+
const opts = command.opts();
|
|
95
|
+
for (const key of Object.keys(opts)) {
|
|
96
|
+
if (command.getOptionValueSource(key) !== "default") {
|
|
97
|
+
merged[key] = opts[key];
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (merged.json === true)
|
|
102
|
+
target.json = true;
|
|
103
|
+
if (merged.input === false)
|
|
104
|
+
target.noInput = true;
|
|
105
|
+
if (merged.yes === true)
|
|
106
|
+
target.yes = true;
|
|
107
|
+
if (merged.quiet === true)
|
|
108
|
+
target.quiet = true;
|
|
109
|
+
if (merged.ipc === true)
|
|
110
|
+
target.ipc = true;
|
|
111
|
+
if (merged.detach === true)
|
|
112
|
+
target.detach = true;
|
|
113
|
+
if (typeof merged.network === "string" && isVanaNetworkName(merged.network)) {
|
|
114
|
+
target.network = merged.network;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
39
117
|
function cleanDescription(desc) {
|
|
40
118
|
return desc
|
|
41
119
|
.replace(/ using Playwright browser automation\.?/i, ".")
|
|
@@ -43,8 +121,10 @@ function cleanDescription(desc) {
|
|
|
43
121
|
}
|
|
44
122
|
const require = createRequire(import.meta.url);
|
|
45
123
|
export async function runCli(argv = process.argv) {
|
|
46
|
-
// Migrate ~/.dataconnect → ~/.vana, symlink old path for DataConnect compat
|
|
47
|
-
|
|
124
|
+
// Migrate ~/.dataconnect → ~/.vana, symlink old path for DataConnect compat.
|
|
125
|
+
// Only an actual data migration announces itself; the compat symlink that
|
|
126
|
+
// every fresh install gets on its second run stays silent.
|
|
127
|
+
if (migrateLegacyDataHome() === "migrated") {
|
|
48
128
|
process.stderr.write("Moved your data to ~/.vana.\n\n");
|
|
49
129
|
}
|
|
50
130
|
const normalizedArgv = normalizeArgv(argv);
|
|
@@ -110,6 +190,16 @@ More:
|
|
|
110
190
|
vana setup Install or repair runtime
|
|
111
191
|
`);
|
|
112
192
|
program.exitOverride();
|
|
193
|
+
// Global flags: declared here for `vana --help`, mirrored onto every leaf
|
|
194
|
+
// command by applyGlobalOptions() so they are accepted anywhere on the
|
|
195
|
+
// line. Values are merged back into parsedOptions in the preAction hook.
|
|
196
|
+
for (const [flags, description] of GLOBAL_FLAG_DEFS) {
|
|
197
|
+
program.option(flags, description);
|
|
198
|
+
}
|
|
199
|
+
program.addOption(new Option("--network <network>", "Vana network for protocol operations (default: moksha)").choices([...VANA_NETWORKS]));
|
|
200
|
+
program.hook("preAction", (_thisCommand, actionCommand) => {
|
|
201
|
+
mergeParsedGlobalOptions(parsedOptions, actionCommand);
|
|
202
|
+
});
|
|
113
203
|
program
|
|
114
204
|
.command("version")
|
|
115
205
|
.description("Print CLI version")
|
|
@@ -383,12 +473,14 @@ Examples:
|
|
|
383
473
|
telemetry
|
|
384
474
|
.command("enable")
|
|
385
475
|
.description("Enable telemetry")
|
|
476
|
+
.option("--json", "Output machine-readable JSON")
|
|
386
477
|
.action(async () => {
|
|
387
478
|
process.exitCode = await runTelemetryEnable(parsedOptions);
|
|
388
479
|
});
|
|
389
480
|
telemetry
|
|
390
481
|
.command("disable")
|
|
391
482
|
.description("Disable telemetry")
|
|
483
|
+
.option("--json", "Output machine-readable JSON")
|
|
392
484
|
.action(async () => {
|
|
393
485
|
process.exitCode = await runTelemetryDisable(parsedOptions);
|
|
394
486
|
});
|
|
@@ -468,6 +560,8 @@ Examples:
|
|
|
468
560
|
.action(async () => {
|
|
469
561
|
process.exitCode = await runCommandWithTelemetry({ ...telemetryBaseContext, command: "schedule", subcommand: "remove" }, async () => runScheduleRemove(parsedOptions));
|
|
470
562
|
});
|
|
563
|
+
registerAppCommands(program, () => parsedOptions);
|
|
564
|
+
applyGlobalOptions(program);
|
|
471
565
|
try {
|
|
472
566
|
await program.parseAsync(normalizedArgv);
|
|
473
567
|
}
|
|
@@ -479,8 +573,13 @@ Examples:
|
|
|
479
573
|
process.exitCode = error.exitCode;
|
|
480
574
|
return Number(process.exitCode ?? 0);
|
|
481
575
|
}
|
|
482
|
-
// Commander already printed to stderr
|
|
483
|
-
|
|
576
|
+
// Commander already printed to stderr. Usage errors (unknown command
|
|
577
|
+
// or option, missing or invalid argument) exit 2 per the exit-code
|
|
578
|
+
// table in docs/CLI-EXIT-CODE-MATRIX.md; anything else keeps
|
|
579
|
+
// commander's own code.
|
|
580
|
+
process.exitCode = error.code.startsWith("commander.")
|
|
581
|
+
? CliExitCode.USAGE
|
|
582
|
+
: error.exitCode;
|
|
484
583
|
return Number(process.exitCode ?? 1);
|
|
485
584
|
}
|
|
486
585
|
throw error;
|
|
@@ -767,9 +866,7 @@ async function runConnect(rawSource, options) {
|
|
|
767
866
|
connectorInstalled: false,
|
|
768
867
|
lastRunAt: new Date().toISOString(),
|
|
769
868
|
lastRunOutcome: CliOutcomeStatus.CONNECTOR_UNAVAILABLE,
|
|
770
|
-
dataState: "none",
|
|
771
869
|
lastError: message,
|
|
772
|
-
lastResultPath: null,
|
|
773
870
|
lastLogPath: getErrorLogPath(retryError),
|
|
774
871
|
});
|
|
775
872
|
renderer?.fail(`${displayName} connector could not be verified.`);
|
|
@@ -790,9 +887,7 @@ async function runConnect(rawSource, options) {
|
|
|
790
887
|
connectorInstalled: false,
|
|
791
888
|
lastRunAt: new Date().toISOString(),
|
|
792
889
|
lastRunOutcome: CliOutcomeStatus.CONNECTOR_UNAVAILABLE,
|
|
793
|
-
dataState: "none",
|
|
794
890
|
lastError: message,
|
|
795
|
-
lastResultPath: null,
|
|
796
891
|
lastLogPath: getErrorLogPath(firstError),
|
|
797
892
|
});
|
|
798
893
|
renderer?.fail(`${displayName} is not available.`);
|
|
@@ -845,9 +940,7 @@ async function runConnect(rawSource, options) {
|
|
|
845
940
|
sessionPresent: fs.existsSync(profilePath),
|
|
846
941
|
lastRunAt: new Date().toISOString(),
|
|
847
942
|
lastRunOutcome: CliOutcomeStatus.LEGACY_AUTH,
|
|
848
|
-
dataState: "none",
|
|
849
943
|
lastError: message,
|
|
850
|
-
lastResultPath: null,
|
|
851
944
|
lastLogPath: fetchLogPath ?? null,
|
|
852
945
|
});
|
|
853
946
|
renderer?.fail(`${displayName} requires a browser window, but no display is available.`);
|
|
@@ -1002,8 +1095,6 @@ async function runConnect(rawSource, options) {
|
|
|
1002
1095
|
lastRunAt: new Date().toISOString(),
|
|
1003
1096
|
lastRunOutcome: CliOutcomeStatus.LEGACY_AUTH,
|
|
1004
1097
|
lastError: event.message ?? "Legacy authentication is required.",
|
|
1005
|
-
dataState: "none",
|
|
1006
|
-
lastResultPath: null,
|
|
1007
1098
|
lastLogPath: event.logPath,
|
|
1008
1099
|
connectionHealth: "needs_reauth",
|
|
1009
1100
|
connectionHealthChangedAt: new Date().toISOString(),
|
|
@@ -1118,9 +1209,7 @@ async function runConnect(rawSource, options) {
|
|
|
1118
1209
|
sessionPresent: fs.existsSync(profilePath),
|
|
1119
1210
|
lastRunAt: new Date().toISOString(),
|
|
1120
1211
|
lastRunOutcome: CliOutcomeStatus.UNEXPECTED_INTERNAL_ERROR,
|
|
1121
|
-
dataState: "none",
|
|
1122
1212
|
lastError: "Connector run ended without a result.",
|
|
1123
|
-
lastResultPath: null,
|
|
1124
1213
|
lastLogPath: runLogPath ?? fetchLogPath ?? null,
|
|
1125
1214
|
});
|
|
1126
1215
|
renderer?.fail(`Problem connecting ${displayName}.`);
|
|
@@ -1171,6 +1260,9 @@ async function runConnect(rawSource, options) {
|
|
|
1171
1260
|
else if (finalDataState === "ingest_unavailable") {
|
|
1172
1261
|
successSummary = `Collected your ${displayName} data. Personal Server sync is pending.`;
|
|
1173
1262
|
}
|
|
1263
|
+
else if (finalDataState === "ingest_failed") {
|
|
1264
|
+
successSummary = `Collected your ${displayName} data, but Personal Server sync failed.`;
|
|
1265
|
+
}
|
|
1174
1266
|
else {
|
|
1175
1267
|
successSummary = `Collected your ${displayName} data and saved it locally.`;
|
|
1176
1268
|
}
|
|
@@ -1187,6 +1279,14 @@ async function runConnect(rawSource, options) {
|
|
|
1187
1279
|
renderer?.detail(`Pending sync will retry during scheduled collection.`);
|
|
1188
1280
|
renderer?.detail(`Retry now: vana server sync`);
|
|
1189
1281
|
}
|
|
1282
|
+
else if (finalDataState === "ingest_failed") {
|
|
1283
|
+
if (ingestFailureMessage?.includes("401")) {
|
|
1284
|
+
renderer?.detail("Your Personal Server requires authentication. Run `vana login` to authenticate, then `vana server sync`.");
|
|
1285
|
+
}
|
|
1286
|
+
else {
|
|
1287
|
+
renderer?.detail(`Retry: vana server sync`);
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1190
1290
|
// Journey-aware next step
|
|
1191
1291
|
const state = await readCliState();
|
|
1192
1292
|
const connectedSourceCount = Object.values(state.sources ?? {}).filter((s) => hasCollectedData(s?.dataState)).length;
|
|
@@ -1288,15 +1388,15 @@ async function runConnectEntry(options) {
|
|
|
1288
1388
|
}
|
|
1289
1389
|
: null,
|
|
1290
1390
|
})}\n`);
|
|
1291
|
-
return
|
|
1391
|
+
return CliExitCode.USAGE;
|
|
1292
1392
|
}
|
|
1293
1393
|
if (options.noInput) {
|
|
1294
1394
|
emit.info(missingSourceMessage);
|
|
1295
|
-
return
|
|
1395
|
+
return CliExitCode.USAGE;
|
|
1296
1396
|
}
|
|
1297
1397
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
1298
1398
|
emit.info(missingSourceMessage);
|
|
1299
|
-
return
|
|
1399
|
+
return CliExitCode.USAGE;
|
|
1300
1400
|
}
|
|
1301
1401
|
if (enrichedSources.length === 0) {
|
|
1302
1402
|
emit.info("No sources are available right now.");
|
|
@@ -1587,7 +1687,41 @@ function formatSyncFailureSummary(source) {
|
|
|
1587
1687
|
return `${failedScopes.length} scopes failed to sync: ${summaryParts.join("; ")}. Run \`vana connect ${source.source}\`.`;
|
|
1588
1688
|
}
|
|
1589
1689
|
async function runDoctor(options) {
|
|
1590
|
-
const
|
|
1690
|
+
const basePayload = await queryDoctor();
|
|
1691
|
+
// Builder-side protocol network (owner-side server config is separate and
|
|
1692
|
+
// reported by its own checks). Additive field; the rest of the payload
|
|
1693
|
+
// still matches CliDoctor exactly. A bad VANA_NETWORK/VANA_ENV combination
|
|
1694
|
+
// is a usage problem, not a crash: exit 2 with a structured message.
|
|
1695
|
+
let network;
|
|
1696
|
+
try {
|
|
1697
|
+
network = resolveNetwork(options.network);
|
|
1698
|
+
}
|
|
1699
|
+
catch (error) {
|
|
1700
|
+
if (error instanceof UnknownNetworkError) {
|
|
1701
|
+
if (options.json) {
|
|
1702
|
+
process.stdout.write(`${JSON.stringify({ error: "bad_usage", message: error.message })}\n`);
|
|
1703
|
+
}
|
|
1704
|
+
else {
|
|
1705
|
+
process.stderr.write(`${error.message}\n`);
|
|
1706
|
+
}
|
|
1707
|
+
return CliExitCode.USAGE;
|
|
1708
|
+
}
|
|
1709
|
+
throw error;
|
|
1710
|
+
}
|
|
1711
|
+
const payload = {
|
|
1712
|
+
...basePayload,
|
|
1713
|
+
network: {
|
|
1714
|
+
name: network.name,
|
|
1715
|
+
env: network.env,
|
|
1716
|
+
chainId: network.chainId,
|
|
1717
|
+
gatewayUrl: network.gatewayUrl,
|
|
1718
|
+
source: options.network
|
|
1719
|
+
? "flag"
|
|
1720
|
+
: process.env.VANA_NETWORK
|
|
1721
|
+
? "env"
|
|
1722
|
+
: "default",
|
|
1723
|
+
},
|
|
1724
|
+
};
|
|
1591
1725
|
if (options.json) {
|
|
1592
1726
|
process.stdout.write(`${JSON.stringify(payload)}\n`);
|
|
1593
1727
|
return 0;
|
|
@@ -1603,6 +1737,7 @@ async function runDoctor(options) {
|
|
|
1603
1737
|
emit.keyValue("Install", formatInstallMethodLabel(payload.installMethod), "muted");
|
|
1604
1738
|
emit.keyValue("Runtime", payload.runtime, toneForRuntime(payload.runtime));
|
|
1605
1739
|
emit.keyValue("Personal Server", payload.personalServer, payload.personalServer === "available" ? "success" : "warning");
|
|
1740
|
+
emit.keyValue("Network", `${payload.network.name} (chain ${payload.network.chainId}, ${payload.network.source})`, "muted");
|
|
1606
1741
|
emit.keyValue("Tracked sources", String(payload.summary.trackedSourceCount), "muted");
|
|
1607
1742
|
emit.keyValue("Attention", String(payload.summary.attentionCount), payload.summary.attentionCount > 0 ? "warning" : "muted");
|
|
1608
1743
|
emit.keyValue("Connected", String(payload.summary.connectedCount), payload.summary.connectedCount > 0 ? "success" : "muted");
|
|
@@ -1669,6 +1804,11 @@ async function runDoctor(options) {
|
|
|
1669
1804
|
async function runServerStatus(options) {
|
|
1670
1805
|
const emit = createEmitter(options);
|
|
1671
1806
|
const target = await detectPersonalServerTarget();
|
|
1807
|
+
// The local transport URL and the gateway-registered public URL are two
|
|
1808
|
+
// names for the same server; show both when the owner is known.
|
|
1809
|
+
const registeredServers = target.health?.owner
|
|
1810
|
+
? await lookupRegisteredServers(target.health.owner)
|
|
1811
|
+
: [];
|
|
1672
1812
|
const state = await readCliState();
|
|
1673
1813
|
// Count scopes from state
|
|
1674
1814
|
let totalScopeCount = 0;
|
|
@@ -1682,6 +1822,7 @@ async function runServerStatus(options) {
|
|
|
1682
1822
|
state: target.state,
|
|
1683
1823
|
url: target.url,
|
|
1684
1824
|
source: target.source,
|
|
1825
|
+
registeredServers,
|
|
1685
1826
|
health: target.health,
|
|
1686
1827
|
scopeCount: totalScopeCount,
|
|
1687
1828
|
})}\n`);
|
|
@@ -1701,6 +1842,9 @@ async function runServerStatus(options) {
|
|
|
1701
1842
|
: `(${target.source ?? "unknown"})`;
|
|
1702
1843
|
emit.keyValue("URL", `${target.url} ${urlSuffix}`, "muted");
|
|
1703
1844
|
}
|
|
1845
|
+
for (const server of registeredServers) {
|
|
1846
|
+
emit.keyValue("Registered", `${server.url} (${server.network})`, "muted");
|
|
1847
|
+
}
|
|
1704
1848
|
const stateLabel = target.state === "available" ? "healthy" : "Not connected";
|
|
1705
1849
|
emit.keyValue("Status", stateLabel, target.state === "available" ? "success" : "warning");
|
|
1706
1850
|
if (target.health) {
|
|
@@ -3293,8 +3437,25 @@ export function getCliChannel(version = getCliVersion()) {
|
|
|
3293
3437
|
? "canary"
|
|
3294
3438
|
: "stable";
|
|
3295
3439
|
}
|
|
3296
|
-
|
|
3297
|
-
|
|
3440
|
+
function normalizePathForMatch(value) {
|
|
3441
|
+
return value.replace(/\\/g, "/").toLowerCase();
|
|
3442
|
+
}
|
|
3443
|
+
/** Where this file itself sits, which is what separates npm from a checkout. */
|
|
3444
|
+
function getOwnModulePath() {
|
|
3445
|
+
try {
|
|
3446
|
+
return fileURLToPath(import.meta.url);
|
|
3447
|
+
}
|
|
3448
|
+
catch {
|
|
3449
|
+
return "";
|
|
3450
|
+
}
|
|
3451
|
+
}
|
|
3452
|
+
/** True when the CLI is running out of an npm install or an npx cache. */
|
|
3453
|
+
function isPackagedUnderNodeModules(normalizedPath) {
|
|
3454
|
+
return (normalizedPath.includes("/node_modules/vana-cli/") ||
|
|
3455
|
+
normalizedPath.includes("/_npx/"));
|
|
3456
|
+
}
|
|
3457
|
+
export function getCliInstallMethod(execPath = process.execPath, modulePath = getOwnModulePath()) {
|
|
3458
|
+
const candidates = [process.env.VANA_APP_ROOT ?? "", execPath].map(normalizePathForMatch);
|
|
3298
3459
|
for (const normalizedPath of candidates) {
|
|
3299
3460
|
if (!normalizedPath) {
|
|
3300
3461
|
continue;
|
|
@@ -3308,6 +3469,18 @@ export function getCliInstallMethod(execPath = process.execPath) {
|
|
|
3308
3469
|
/\/releases\/[^/]+\/app$/.test(normalizedPath)) {
|
|
3309
3470
|
return "installer";
|
|
3310
3471
|
}
|
|
3472
|
+
}
|
|
3473
|
+
// npm and npx both run under the user's own node, so execPath looks exactly
|
|
3474
|
+
// like a development checkout. Only the package's own location tells them
|
|
3475
|
+
// apart, and getting it wrong costs an npm user every update notification.
|
|
3476
|
+
if (modulePath &&
|
|
3477
|
+
isPackagedUnderNodeModules(normalizePathForMatch(modulePath))) {
|
|
3478
|
+
return "npm";
|
|
3479
|
+
}
|
|
3480
|
+
for (const normalizedPath of candidates) {
|
|
3481
|
+
if (!normalizedPath) {
|
|
3482
|
+
continue;
|
|
3483
|
+
}
|
|
3311
3484
|
if (normalizedPath.endsWith("/node") ||
|
|
3312
3485
|
normalizedPath.endsWith("/node.exe") ||
|
|
3313
3486
|
normalizedPath.includes("/.nvm/") ||
|
|
@@ -3336,14 +3509,28 @@ export function formatInstallMethodLabel(method) {
|
|
|
3336
3509
|
return "Homebrew";
|
|
3337
3510
|
case "installer":
|
|
3338
3511
|
return "Hosted installer";
|
|
3512
|
+
case "npm":
|
|
3513
|
+
return "npm";
|
|
3339
3514
|
case "development":
|
|
3340
3515
|
return "Development checkout";
|
|
3341
3516
|
default:
|
|
3342
3517
|
return "Unknown";
|
|
3343
3518
|
}
|
|
3344
3519
|
}
|
|
3345
|
-
export function getLifecycleCommands(installMethod, channel) {
|
|
3520
|
+
export function getLifecycleCommands(installMethod, channel, modulePath = getOwnModulePath()) {
|
|
3346
3521
|
switch (installMethod) {
|
|
3522
|
+
case "npm":
|
|
3523
|
+
// npx installs nothing, so there is nothing to upgrade or remove:
|
|
3524
|
+
// asking for @latest is the whole update story there.
|
|
3525
|
+
return normalizePathForMatch(modulePath).includes("/_npx/")
|
|
3526
|
+
? {
|
|
3527
|
+
upgrade: "npx vana-cli@latest",
|
|
3528
|
+
uninstall: "npx leaves nothing installed. Remove ~/.vana for any state you no longer need.",
|
|
3529
|
+
}
|
|
3530
|
+
: {
|
|
3531
|
+
upgrade: "npm install -g vana-cli@latest",
|
|
3532
|
+
uninstall: "npm uninstall -g vana-cli",
|
|
3533
|
+
};
|
|
3347
3534
|
case "homebrew":
|
|
3348
3535
|
return {
|
|
3349
3536
|
upgrade: "brew update && brew upgrade vana",
|
|
@@ -3374,7 +3561,30 @@ export function getLifecycleCommands(installMethod, channel) {
|
|
|
3374
3561
|
};
|
|
3375
3562
|
}
|
|
3376
3563
|
}
|
|
3377
|
-
|
|
3564
|
+
/**
|
|
3565
|
+
* Pre-parse seed for the global options. Needed before commander runs (the
|
|
3566
|
+
* update-notifier suppression and the telemetry context read it); the
|
|
3567
|
+
* preAction hook overlays the authoritative parsed values afterwards.
|
|
3568
|
+
*/
|
|
3569
|
+
function extractGlobalOptions(rawArgv) {
|
|
3570
|
+
// Everything after a bare "--" is positional; commander will not parse it
|
|
3571
|
+
// as options, so the seed must not either.
|
|
3572
|
+
const boundary = rawArgv.indexOf("--");
|
|
3573
|
+
const argv = boundary === -1 ? rawArgv : rawArgv.slice(0, boundary);
|
|
3574
|
+
let network;
|
|
3575
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
3576
|
+
const argument = argv[index];
|
|
3577
|
+
let candidate;
|
|
3578
|
+
if (argument === "--network") {
|
|
3579
|
+
candidate = argv[index + 1];
|
|
3580
|
+
}
|
|
3581
|
+
else if (argument.startsWith("--network=")) {
|
|
3582
|
+
candidate = argument.slice("--network=".length);
|
|
3583
|
+
}
|
|
3584
|
+
if (candidate && isVanaNetworkName(candidate)) {
|
|
3585
|
+
network = candidate;
|
|
3586
|
+
}
|
|
3587
|
+
}
|
|
3378
3588
|
return {
|
|
3379
3589
|
json: argv.includes("--json"),
|
|
3380
3590
|
noInput: argv.includes("--no-input"),
|
|
@@ -3382,6 +3592,7 @@ function extractGlobalOptions(argv) {
|
|
|
3382
3592
|
yes: argv.includes("--yes"),
|
|
3383
3593
|
quiet: argv.includes("--quiet"),
|
|
3384
3594
|
detach: argv.includes("--detach"),
|
|
3595
|
+
network,
|
|
3385
3596
|
};
|
|
3386
3597
|
}
|
|
3387
3598
|
export function createSourceLabelMap(sources) {
|
|
@@ -4337,7 +4548,7 @@ async function runSkillsGuidedPicker(options) {
|
|
|
4337
4548
|
// In JSON mode, fall back to list behavior
|
|
4338
4549
|
return runSkillList(options);
|
|
4339
4550
|
}
|
|
4340
|
-
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
4551
|
+
if (options.noInput || !process.stdin.isTTY || !process.stdout.isTTY) {
|
|
4341
4552
|
return runSkillList(options);
|
|
4342
4553
|
}
|
|
4343
4554
|
try {
|
|
@@ -4506,23 +4717,32 @@ async function runLogin(options, serverUrl, clientId) {
|
|
|
4506
4717
|
try {
|
|
4507
4718
|
const result = await runSelfHostedLoginFlow(psUrl, (url) => {
|
|
4508
4719
|
if (!options.json && !options.quiet) {
|
|
4509
|
-
|
|
4510
|
-
|
|
4720
|
+
// Same emphasis rule as the cloud flow: the actionable URL
|
|
4721
|
+
// carries the Vana accent, the label stays muted.
|
|
4722
|
+
process.stderr.write(` ${humanRenderer.theme.muted("Open")} ${humanRenderer.theme.accent(url)}\n`);
|
|
4511
4723
|
}
|
|
4512
4724
|
renderer?.scopeActive("Waiting for authorization");
|
|
4513
4725
|
// Try to open browser — use spawn with args array to prevent shell injection
|
|
4514
|
-
// (a malicious self-hosted PS could return a URL with shell metacharacters)
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4726
|
+
// (a malicious self-hosted PS could return a URL with shell metacharacters).
|
|
4727
|
+
// Interactive terminals only: agents, CI and the test suite must never
|
|
4728
|
+
// pop a browser (a vitest run used to open the fixture URL for real).
|
|
4729
|
+
const browserAllowed = Boolean(process.stdout.isTTY) &&
|
|
4730
|
+
!options.json &&
|
|
4731
|
+
!options.noInput &&
|
|
4732
|
+
!process.env.VANA_NO_BROWSER;
|
|
4733
|
+
if (browserAllowed) {
|
|
4734
|
+
try {
|
|
4735
|
+
const { spawn } = require("node:child_process");
|
|
4736
|
+
const opener = process.platform === "darwin"
|
|
4737
|
+
? "open"
|
|
4738
|
+
: process.platform === "win32"
|
|
4739
|
+
? "start"
|
|
4740
|
+
: "xdg-open";
|
|
4741
|
+
spawn(opener, [url], { detached: true, stdio: "ignore" }).unref();
|
|
4742
|
+
}
|
|
4743
|
+
catch {
|
|
4744
|
+
// Browser open failed — user will open manually
|
|
4745
|
+
}
|
|
4526
4746
|
}
|
|
4527
4747
|
});
|
|
4528
4748
|
await saveCredentials({
|
|
@@ -4570,6 +4790,10 @@ async function runLogin(options, serverUrl, clientId) {
|
|
|
4570
4790
|
}
|
|
4571
4791
|
// Check if already logged in
|
|
4572
4792
|
const existing = loadCredentials();
|
|
4793
|
+
// For the account-switch check below: the previous auth file content even
|
|
4794
|
+
// when the stored credentials have expired (loadCredentials hides those).
|
|
4795
|
+
const previousAuth = readStoredAuthFile();
|
|
4796
|
+
const previousAddress = previousAuth?.address ?? null;
|
|
4573
4797
|
if (existing && !isExpired(existing)) {
|
|
4574
4798
|
if (options.json) {
|
|
4575
4799
|
process.stdout.write(`${JSON.stringify({
|
|
@@ -4610,9 +4834,26 @@ async function runLogin(options, serverUrl, clientId) {
|
|
|
4610
4834
|
},
|
|
4611
4835
|
}, { clientId });
|
|
4612
4836
|
if (creds) {
|
|
4837
|
+
if (!creds.personal_server &&
|
|
4838
|
+
previousAuth?.personalServer &&
|
|
4839
|
+
(!previousAddress || previousAddress === creds.account.address)) {
|
|
4840
|
+
// The cloud flow cannot mint PS sessions; keep the one we had.
|
|
4841
|
+
creds.personal_server = previousAuth.personalServer;
|
|
4842
|
+
}
|
|
4613
4843
|
await saveCredentials(creds);
|
|
4844
|
+
// Always sync the pinned PS config to this login's result — including
|
|
4845
|
+
// clearing it when this account has no PS, so a stale PS URL from a
|
|
4846
|
+
// previously logged-in account can't linger and be used by mistake.
|
|
4614
4847
|
if (creds.personal_server?.url) {
|
|
4615
|
-
await updateCliConfig({
|
|
4848
|
+
await updateCliConfig({
|
|
4849
|
+
personalServerUrl: creds.personal_server.url,
|
|
4850
|
+
});
|
|
4851
|
+
}
|
|
4852
|
+
else if (previousAddress && previousAddress !== creds.account.address) {
|
|
4853
|
+
// A different account logged in: its pinned Personal Server URL is
|
|
4854
|
+
// stale. Same-account logins keep the pin - the prod token flow
|
|
4855
|
+
// carries no PS info, so its absence proves nothing.
|
|
4856
|
+
await updateCliConfig({ personalServerUrl: undefined });
|
|
4616
4857
|
}
|
|
4617
4858
|
process.stdout.write(`${JSON.stringify({
|
|
4618
4859
|
status: "authenticated",
|
|
@@ -4627,37 +4868,54 @@ async function runLogin(options, serverUrl, clientId) {
|
|
|
4627
4868
|
// Interactive mode
|
|
4628
4869
|
const renderer = createLoginRenderer();
|
|
4629
4870
|
const humanRenderer = createHumanRenderer();
|
|
4630
|
-
|
|
4871
|
+
// Branding lives in emphasis, not chrome (CLI-BEAUTY doctrine: no
|
|
4872
|
+
// banners). The two things the user must act on - the URL and the code -
|
|
4873
|
+
// carry the Vana accent; everything around them stays muted.
|
|
4874
|
+
const writeDeviceCodePrompt = (code, uri) => {
|
|
4631
4875
|
if (options.json || options.quiet) {
|
|
4632
4876
|
return;
|
|
4633
4877
|
}
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
}
|
|
4878
|
+
const theme = humanRenderer.theme;
|
|
4879
|
+
process.stderr.write(` ${theme.muted("Open")} ${theme.accent(uri)}\n`);
|
|
4880
|
+
process.stderr.write(` ${theme.muted("Enter")} ${theme.heading(code)}\n`);
|
|
4637
4881
|
};
|
|
4638
4882
|
renderer.title("Vana");
|
|
4639
4883
|
const creds = await runDeviceCodeFlow({
|
|
4640
4884
|
onCode: (code, uri) => {
|
|
4641
|
-
|
|
4642
|
-
"Open this URL in your browser:",
|
|
4643
|
-
uri,
|
|
4644
|
-
`Enter this code: ${code}`,
|
|
4645
|
-
]);
|
|
4885
|
+
writeDeviceCodePrompt(code, uri);
|
|
4646
4886
|
},
|
|
4647
4887
|
onWaiting: () => {
|
|
4648
4888
|
renderer.scopeActive("Waiting for authorization");
|
|
4649
4889
|
},
|
|
4650
4890
|
onAuthorized: async (authedCreds) => {
|
|
4891
|
+
if (!authedCreds.personal_server &&
|
|
4892
|
+
previousAuth?.personalServer &&
|
|
4893
|
+
(!previousAddress || previousAddress === authedCreds.account.address)) {
|
|
4894
|
+
// The cloud flow cannot mint PS sessions; keep the one we had.
|
|
4895
|
+
authedCreds.personal_server = previousAuth.personalServer;
|
|
4896
|
+
}
|
|
4651
4897
|
await saveCredentials(authedCreds);
|
|
4898
|
+
// Always sync the pinned PS config to this login's result — including
|
|
4899
|
+
// clearing it when this account has no PS, so a stale PS URL from a
|
|
4900
|
+
// previously logged-in account can't linger and be used by mistake.
|
|
4652
4901
|
if (authedCreds.personal_server?.url) {
|
|
4653
4902
|
await updateCliConfig({
|
|
4654
4903
|
personalServerUrl: authedCreds.personal_server.url,
|
|
4655
4904
|
});
|
|
4656
4905
|
}
|
|
4906
|
+
else if (previousAddress &&
|
|
4907
|
+
previousAddress !== authedCreds.account.address) {
|
|
4908
|
+
// See the JSON branch: clear only on an account switch.
|
|
4909
|
+
await updateCliConfig({ personalServerUrl: undefined });
|
|
4910
|
+
}
|
|
4657
4911
|
renderer.success(`Logged in as ${formatAddress(authedCreds.account.address)}`);
|
|
4658
4912
|
if (authedCreds.personal_server) {
|
|
4659
4913
|
renderer.detail(`Personal Server: ${authedCreds.personal_server.url}`);
|
|
4660
4914
|
}
|
|
4915
|
+
else {
|
|
4916
|
+
renderer.detail("No Personal Server found for this account yet.");
|
|
4917
|
+
renderer.next("vana server set-url <url>");
|
|
4918
|
+
}
|
|
4661
4919
|
renderer.detail("Credentials saved to ~/.vana/auth.json");
|
|
4662
4920
|
},
|
|
4663
4921
|
onExpired: () => {
|