@opendatalabs/connect 0.10.0 → 0.10.1-canary.2898948
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/dist/cli/auth.d.ts +112 -0
- package/dist/cli/auth.d.ts.map +1 -0
- package/dist/cli/auth.js +339 -0
- package/dist/cli/auth.js.map +1 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +314 -18
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/render/connect-renderer.d.ts +5 -0
- package/dist/cli/render/connect-renderer.d.ts.map +1 -1
- package/dist/cli/render/connect-renderer.js +62 -58
- package/dist/cli/render/connect-renderer.js.map +1 -1
- package/dist/cli/render/flow-theme.d.ts +17 -0
- package/dist/cli/render/flow-theme.d.ts.map +1 -0
- package/dist/cli/render/flow-theme.js +26 -0
- package/dist/cli/render/flow-theme.js.map +1 -0
- package/dist/cli/render/index.d.ts +1 -0
- package/dist/cli/render/index.d.ts.map +1 -1
- package/dist/cli/render/index.js +1 -0
- package/dist/cli/render/index.js.map +1 -1
- package/dist/cli/render/theme.d.ts +1 -0
- package/dist/cli/render/theme.d.ts.map +1 -1
- package/dist/cli/render/theme.js +19 -3
- package/dist/cli/render/theme.js.map +1 -1
- package/dist/core/cli-types.d.ts +3 -0
- package/dist/core/cli-types.d.ts.map +1 -1
- package/dist/core/cli-types.js +1 -1
- package/dist/core/cli-types.js.map +1 -1
- package/dist/personal-server/client.d.ts +1 -1
- package/dist/personal-server/client.d.ts.map +1 -1
- package/dist/personal-server/client.js +31 -17
- package/dist/personal-server/client.js.map +1 -1
- package/dist/personal-server/index.d.ts +8 -1
- package/dist/personal-server/index.d.ts.map +1 -1
- package/dist/personal-server/index.js +49 -16
- package/dist/personal-server/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -26,14 +26,15 @@ const vanaPromptTheme = {
|
|
|
26
26
|
},
|
|
27
27
|
},
|
|
28
28
|
};
|
|
29
|
-
import { createConnectRenderer, createHumanRenderer, formatDisplayPath, formatRelativeTime, } from "./render/index.js";
|
|
29
|
+
import { createConnectRenderer, createHumanRenderer, createLoginRenderer, formatDisplayPath, formatRelativeTime, } from "./render/index.js";
|
|
30
30
|
import { CliOutcomeStatus, migrateLegacyDataHome, getBrowserProfilesDir, getConnectorCacheDir, getLogsDir, getSessionsDir, getSourceResultPath, readCliState, readCliConfig, updateCliConfig, updateSourceState, } from "../core/index.js";
|
|
31
31
|
import { fetchConnectorToCache, listAvailableSources, readCachedConnectorMetadata, } from "../connectors/registry.js";
|
|
32
|
-
import { detectPersonalServerTarget, ingestResult, } from "../personal-server/index.js";
|
|
32
|
+
import { detectPersonalServerTarget, ingestResult, resolvePersonalServerAuthConfig, } from "../personal-server/index.js";
|
|
33
33
|
import { findDataConnectorsDir, ManagedPlaywrightRuntime, } from "../runtime/index.js";
|
|
34
34
|
import { listAvailableSkills, installSkill, readInstalledSkills, } from "../skills/index.js";
|
|
35
35
|
import { queryStatus, querySources, queryDataList, queryDataShow, queryDoctor, } from "./queries.js";
|
|
36
36
|
import { checkForUpdate, readUpdateCheck, isNewerVersion, } from "./update-check.js";
|
|
37
|
+
import { loadCredentials, saveCredentials, clearCredentials, isExpired, formatAddress, formatExpiresIn, getAuthTarget, resolvePersonalServerUrl, runDeviceCodeFlow, runSelfHostedLoginFlow, } from "./auth.js";
|
|
37
38
|
function cleanDescription(desc) {
|
|
38
39
|
return desc
|
|
39
40
|
.replace(/ using Playwright browser automation\.?/i, ".")
|
|
@@ -73,6 +74,7 @@ export async function runCli(argv = process.argv) {
|
|
|
73
74
|
.showSuggestionAfterError(true)
|
|
74
75
|
.addHelpText("after", `
|
|
75
76
|
Quick start:
|
|
77
|
+
vana login Log in to your Vana account
|
|
76
78
|
vana connect Connect a source and collect data
|
|
77
79
|
vana sources Browse available sources
|
|
78
80
|
vana status Check system health
|
|
@@ -330,6 +332,19 @@ Examples:
|
|
|
330
332
|
.action(async (scope) => {
|
|
331
333
|
process.exitCode = await runServerData(scope, parsedOptions);
|
|
332
334
|
});
|
|
335
|
+
program
|
|
336
|
+
.command("login")
|
|
337
|
+
.description("Log in to your Vana account or a self-hosted Personal Server")
|
|
338
|
+
.option("-s, --server <url>", "Self-hosted Personal Server URL")
|
|
339
|
+
.action(async (loginOptions) => {
|
|
340
|
+
process.exitCode = await runLogin(parsedOptions, loginOptions.server);
|
|
341
|
+
});
|
|
342
|
+
program
|
|
343
|
+
.command("logout")
|
|
344
|
+
.description("Log out and remove saved credentials")
|
|
345
|
+
.action(async () => {
|
|
346
|
+
process.exitCode = await runLogout(parsedOptions);
|
|
347
|
+
});
|
|
333
348
|
program
|
|
334
349
|
.command("mcp")
|
|
335
350
|
.description("Start MCP server for agent integration")
|
|
@@ -1151,10 +1166,18 @@ async function runStatus(options) {
|
|
|
1151
1166
|
}
|
|
1152
1167
|
}
|
|
1153
1168
|
if (options.json) {
|
|
1169
|
+
const jsonAuthCreds = loadCredentials();
|
|
1154
1170
|
const compactJson = {
|
|
1155
1171
|
runtime: status.runtime,
|
|
1156
1172
|
personalServer: status.personalServer,
|
|
1157
1173
|
personalServerUrl: status.personalServerUrl,
|
|
1174
|
+
auth: jsonAuthCreds
|
|
1175
|
+
? {
|
|
1176
|
+
authenticated: !isExpired(jsonAuthCreds),
|
|
1177
|
+
address: jsonAuthCreds.account.address,
|
|
1178
|
+
expires_at: jsonAuthCreds.account.expires_at,
|
|
1179
|
+
}
|
|
1180
|
+
: { authenticated: false },
|
|
1158
1181
|
sources: {
|
|
1159
1182
|
connected: status.summary?.connectedCount ?? 0,
|
|
1160
1183
|
needsAttention: status.summary?.needsAttentionCount ?? 0,
|
|
@@ -1177,6 +1200,16 @@ async function runStatus(options) {
|
|
|
1177
1200
|
else {
|
|
1178
1201
|
emit.keyValue("Personal Server", "not connected", "warning");
|
|
1179
1202
|
}
|
|
1203
|
+
// Auth state
|
|
1204
|
+
const authCreds = loadCredentials();
|
|
1205
|
+
if (authCreds && !isExpired(authCreds)) {
|
|
1206
|
+
emit.keyValue("Account", formatAddress(authCreds.account.address), "success");
|
|
1207
|
+
emit.keyValue("Auth", `Authenticated (expires in ${formatExpiresIn(authCreds.account.expires_at)})`, "success");
|
|
1208
|
+
}
|
|
1209
|
+
else {
|
|
1210
|
+
emit.keyValue("Account", "Not logged in", "muted");
|
|
1211
|
+
emit.keyValue("Auth", "Run `vana login` to authenticate", "muted");
|
|
1212
|
+
}
|
|
1180
1213
|
const connectedCount = status.summary?.connectedCount ?? 0;
|
|
1181
1214
|
const attentionCount = status.summary?.needsAttentionCount ?? 0;
|
|
1182
1215
|
const sourceParts = [
|
|
@@ -1204,8 +1237,18 @@ async function runStatus(options) {
|
|
|
1204
1237
|
const displayName = displaySource(sourceId, sourceLabels);
|
|
1205
1238
|
const sourceStatus = status.sources.find((s) => s.source === sourceId);
|
|
1206
1239
|
const sourceOverdue = sourceStatus?.isOverdue ?? false;
|
|
1207
|
-
const
|
|
1208
|
-
|
|
1240
|
+
const dataState = stored?.dataState;
|
|
1241
|
+
// Show worst of connectionHealth and dataState
|
|
1242
|
+
let healthLabel;
|
|
1243
|
+
let healthTone;
|
|
1244
|
+
if (dataState === "ingest_failed") {
|
|
1245
|
+
healthLabel = "sync failed";
|
|
1246
|
+
healthTone = "warning";
|
|
1247
|
+
}
|
|
1248
|
+
else {
|
|
1249
|
+
healthTone = sourceOverdue ? "warning" : toneForHealth(health);
|
|
1250
|
+
healthLabel = health === "needs_reauth" ? "needs login" : health;
|
|
1251
|
+
}
|
|
1209
1252
|
const staleTag = sourceOverdue
|
|
1210
1253
|
? ` ${emit.badge("stale", "warning")}`
|
|
1211
1254
|
: "";
|
|
@@ -1213,7 +1256,11 @@ async function runStatus(options) {
|
|
|
1213
1256
|
? `collected ${formatRelativeTime(stored.lastCollectedAt)}`
|
|
1214
1257
|
: "";
|
|
1215
1258
|
emit.keyValue(` ${displayName}`, `${healthLabel}${staleTag} ${collectedAgo}`, healthTone);
|
|
1216
|
-
if (
|
|
1259
|
+
if (dataState === "ingest_failed") {
|
|
1260
|
+
const errMsg = stored?.lastError ?? "sync failed";
|
|
1261
|
+
emit.detail(` \u21b3 ${errMsg}. Run \`vana connect ${sourceId}\``);
|
|
1262
|
+
}
|
|
1263
|
+
else if ((health === "needs_reauth" || health === "error") &&
|
|
1217
1264
|
stored?.connectionHealthReason) {
|
|
1218
1265
|
const msg = formatHealthMessage(stored.connectionHealthReason);
|
|
1219
1266
|
const ago = stored.connectionHealthChangedAt
|
|
@@ -1233,6 +1280,47 @@ async function runStatus(options) {
|
|
|
1233
1280
|
return 0;
|
|
1234
1281
|
}
|
|
1235
1282
|
}
|
|
1283
|
+
// Show attention-needing sources that weren't already displayed above
|
|
1284
|
+
const displayedSourceIds = new Set(connectedSources.map(([id]) => id));
|
|
1285
|
+
const hiddenAttentionSources = status.sources.filter((s) => rankSourceStatus(s) <= 4 && !displayedSourceIds.has(s.source));
|
|
1286
|
+
if (hiddenAttentionSources.length > 0) {
|
|
1287
|
+
if (connectedSources.length === 0) {
|
|
1288
|
+
emit.blank();
|
|
1289
|
+
}
|
|
1290
|
+
for (const source of hiddenAttentionSources) {
|
|
1291
|
+
const displayName = displaySource(source.source, sourceLabels);
|
|
1292
|
+
const presentation = getSourceStatusPresentation(source);
|
|
1293
|
+
const collectedAgo = source.lastCollectedAt
|
|
1294
|
+
? `collected ${formatRelativeTime(source.lastCollectedAt)}`
|
|
1295
|
+
: "";
|
|
1296
|
+
emit.keyValue(` ${displayName}`, `${presentation.label}${collectedAgo ? ` ${collectedAgo}` : ""}`, presentation.tone);
|
|
1297
|
+
// Show actionable detail line
|
|
1298
|
+
if (source.lastRunOutcome === CliOutcomeStatus.INGEST_FAILED &&
|
|
1299
|
+
source.ingestScopes) {
|
|
1300
|
+
const failedScopes = source.ingestScopes.filter((s) => s.status === "failed");
|
|
1301
|
+
for (const scope of failedScopes) {
|
|
1302
|
+
const errMsg = scope.error ?? "sync failed";
|
|
1303
|
+
emit.detail(` \u21b3 ${source.source}.${scope.scope}: ${errMsg}. Run \`vana connect ${source.source}\``);
|
|
1304
|
+
}
|
|
1305
|
+
if (failedScopes.length === 0) {
|
|
1306
|
+
emit.detail(` \u21b3 Sync failed. Run \`vana connect ${source.source}\``);
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
else if (source.lastRunOutcome === CliOutcomeStatus.CONNECTOR_UNAVAILABLE) {
|
|
1310
|
+
emit.detail(` \u21b3 No connector available. Run \`vana sources\``);
|
|
1311
|
+
}
|
|
1312
|
+
else if (source.lastRunOutcome === CliOutcomeStatus.RUNTIME_ERROR) {
|
|
1313
|
+
const reason = source.lastError ?? "runtime error";
|
|
1314
|
+
emit.detail(` \u21b3 ${reason}. Run \`vana connect ${source.source}\``);
|
|
1315
|
+
}
|
|
1316
|
+
else if (source.lastRunOutcome === CliOutcomeStatus.NEEDS_INPUT) {
|
|
1317
|
+
emit.detail(` \u21b3 Requires interactive login. Run \`vana connect ${source.source}\``);
|
|
1318
|
+
}
|
|
1319
|
+
else if (source.lastRunOutcome === CliOutcomeStatus.LEGACY_AUTH) {
|
|
1320
|
+
emit.detail(` \u21b3 Manual auth step required. Run \`vana connect ${source.source}\``);
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1236
1324
|
if (nextSteps.length > 0) {
|
|
1237
1325
|
emit.blank();
|
|
1238
1326
|
const command = extractCommand(nextSteps[0]);
|
|
@@ -1353,9 +1441,11 @@ async function runServerStatus(options) {
|
|
|
1353
1441
|
? "(auto-detected)"
|
|
1354
1442
|
: target.source === "config"
|
|
1355
1443
|
? "(saved)"
|
|
1356
|
-
: target.source === "
|
|
1357
|
-
? "(from
|
|
1358
|
-
:
|
|
1444
|
+
: target.source === "auth"
|
|
1445
|
+
? "(from vana login)"
|
|
1446
|
+
: target.source === "env"
|
|
1447
|
+
? "(from VANA_PERSONAL_SERVER_URL)"
|
|
1448
|
+
: `(${target.source ?? "unknown"})`;
|
|
1359
1449
|
emit.keyValue("URL", `${target.url} ${urlSuffix}`, "muted");
|
|
1360
1450
|
}
|
|
1361
1451
|
const stateLabel = target.state === "available" ? "healthy" : "Not connected";
|
|
@@ -2074,12 +2164,20 @@ async function runServerData(scope, options) {
|
|
|
2074
2164
|
}
|
|
2075
2165
|
// If PS is available, try to list remote scopes via client
|
|
2076
2166
|
let remoteScopes = [];
|
|
2167
|
+
let didQueryRemoteScopes = false;
|
|
2077
2168
|
let remoteScopeFallbackReason;
|
|
2078
2169
|
if (target.state === "available" && target.url) {
|
|
2170
|
+
const auth = resolvePersonalServerAuthConfig(target.url);
|
|
2079
2171
|
try {
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2172
|
+
if (auth?.type === "bearerToken") {
|
|
2173
|
+
const { createPersonalServerClient: createClient } = await import("../personal-server/client.js");
|
|
2174
|
+
const client = createClient({
|
|
2175
|
+
url: target.url,
|
|
2176
|
+
auth,
|
|
2177
|
+
});
|
|
2178
|
+
didQueryRemoteScopes = true;
|
|
2179
|
+
remoteScopes = await client.listScopes(scope);
|
|
2180
|
+
}
|
|
2083
2181
|
}
|
|
2084
2182
|
catch (err) {
|
|
2085
2183
|
remoteScopeFallbackReason =
|
|
@@ -2087,7 +2185,7 @@ async function runServerData(scope, options) {
|
|
|
2087
2185
|
}
|
|
2088
2186
|
}
|
|
2089
2187
|
// Use remote scopes if available, otherwise fall back to local
|
|
2090
|
-
const scopeList =
|
|
2188
|
+
const scopeList = didQueryRemoteScopes
|
|
2091
2189
|
? remoteScopes.map((s) => ({
|
|
2092
2190
|
scope: s.scope,
|
|
2093
2191
|
detail: `${s.count} version${s.count !== 1 ? "s" : ""}`,
|
|
@@ -2100,13 +2198,15 @@ async function runServerData(scope, options) {
|
|
|
2100
2198
|
process.stdout.write(`${JSON.stringify({
|
|
2101
2199
|
count: scopeList.length,
|
|
2102
2200
|
scopes: scopeList,
|
|
2103
|
-
source:
|
|
2201
|
+
source: didQueryRemoteScopes ? "remote" : "local",
|
|
2104
2202
|
...(remoteScopeFallbackReason ? { remoteScopeFallbackReason } : {}),
|
|
2105
2203
|
})}\n`);
|
|
2106
2204
|
return 0;
|
|
2107
2205
|
}
|
|
2108
2206
|
if (scopeList.length === 0) {
|
|
2109
|
-
emit.info(
|
|
2207
|
+
emit.info(didQueryRemoteScopes
|
|
2208
|
+
? "No data on your Personal Server."
|
|
2209
|
+
: "No scopes found.");
|
|
2110
2210
|
if (target.state !== "available") {
|
|
2111
2211
|
emit.detail("Personal Server is not available. Showing locally-known scopes only.");
|
|
2112
2212
|
}
|
|
@@ -2115,7 +2215,7 @@ async function runServerData(scope, options) {
|
|
|
2115
2215
|
for (const entry of scopeList) {
|
|
2116
2216
|
emit.keyValue(entry.scope, entry.detail, "muted");
|
|
2117
2217
|
}
|
|
2118
|
-
if (
|
|
2218
|
+
if (!didQueryRemoteScopes && localScopes.length > 0) {
|
|
2119
2219
|
emit.blank();
|
|
2120
2220
|
emit.detail("Showing locally-known scopes. Connect your Personal Server for live data.");
|
|
2121
2221
|
}
|
|
@@ -2872,6 +2972,11 @@ export function getSourceStatusPresentation(source) {
|
|
|
2872
2972
|
if (!source.installed && !source.lastRunOutcome) {
|
|
2873
2973
|
return { label: "not connected", tone: "muted" };
|
|
2874
2974
|
}
|
|
2975
|
+
// Check dataState before early-returning on missing lastRunOutcome —
|
|
2976
|
+
// ingest can fail even when lastRunOutcome is unset
|
|
2977
|
+
if (source.dataState === "ingest_failed") {
|
|
2978
|
+
return { label: "sync failed", tone: "error" };
|
|
2979
|
+
}
|
|
2875
2980
|
if (!source.lastRunOutcome) {
|
|
2876
2981
|
return { label: "installed", tone: "success" };
|
|
2877
2982
|
}
|
|
@@ -2904,9 +3009,6 @@ export function getSourceStatusPresentation(source) {
|
|
|
2904
3009
|
if (source.dataState === "collected_local") {
|
|
2905
3010
|
return { label: "local", tone: "muted" };
|
|
2906
3011
|
}
|
|
2907
|
-
if (source.dataState === "ingest_failed") {
|
|
2908
|
-
return { label: "sync failed", tone: "error" };
|
|
2909
|
-
}
|
|
2910
3012
|
return { label: "connected", tone: "success" };
|
|
2911
3013
|
}
|
|
2912
3014
|
export function toneForRuntime(runtime) {
|
|
@@ -3009,6 +3111,9 @@ export function rankSourceStatus(source) {
|
|
|
3009
3111
|
if (source.lastRunOutcome === CliOutcomeStatus.CONNECTOR_UNAVAILABLE) {
|
|
3010
3112
|
return 4;
|
|
3011
3113
|
}
|
|
3114
|
+
if (source.dataState === "ingest_failed") {
|
|
3115
|
+
return 2;
|
|
3116
|
+
}
|
|
3012
3117
|
if (source.dataState === "ingested_personal_server") {
|
|
3013
3118
|
return 5;
|
|
3014
3119
|
}
|
|
@@ -3899,4 +4004,195 @@ async function runSkillShow(name, options) {
|
|
|
3899
4004
|
return 1;
|
|
3900
4005
|
}
|
|
3901
4006
|
}
|
|
4007
|
+
// ── Login / Logout ─────────────────────────────────────────────────────
|
|
4008
|
+
async function runLogin(options, serverUrl) {
|
|
4009
|
+
// Determine auth target: cloud (account.vana.org) or self-hosted (PS directly)
|
|
4010
|
+
const psUrl = serverUrl ?? resolvePersonalServerUrl() ?? null;
|
|
4011
|
+
const authTarget = getAuthTarget(psUrl);
|
|
4012
|
+
// If self-hosted, use /auth/device flow against the PS
|
|
4013
|
+
if (authTarget === "self-hosted" && psUrl) {
|
|
4014
|
+
const renderer = !options.json && !options.quiet ? createLoginRenderer() : null;
|
|
4015
|
+
renderer?.title(psUrl);
|
|
4016
|
+
try {
|
|
4017
|
+
const result = await runSelfHostedLoginFlow(psUrl, (url) => {
|
|
4018
|
+
renderer?.note("Open this URL in your browser:");
|
|
4019
|
+
renderer?.note(url);
|
|
4020
|
+
renderer?.scopeActive("Waiting for authorization");
|
|
4021
|
+
// Try to open browser — use spawn with args array to prevent shell injection
|
|
4022
|
+
// (a malicious self-hosted PS could return a URL with shell metacharacters)
|
|
4023
|
+
try {
|
|
4024
|
+
const { spawn } = require("node:child_process");
|
|
4025
|
+
const opener = process.platform === "darwin"
|
|
4026
|
+
? "open"
|
|
4027
|
+
: process.platform === "win32"
|
|
4028
|
+
? "start"
|
|
4029
|
+
: "xdg-open";
|
|
4030
|
+
spawn(opener, [url], { detached: true, stdio: "ignore" }).unref();
|
|
4031
|
+
}
|
|
4032
|
+
catch {
|
|
4033
|
+
// Browser open failed — user will open manually
|
|
4034
|
+
}
|
|
4035
|
+
});
|
|
4036
|
+
await saveCredentials({
|
|
4037
|
+
account: {
|
|
4038
|
+
address: result.address,
|
|
4039
|
+
session_token: "",
|
|
4040
|
+
expires_at: result.expires_at,
|
|
4041
|
+
},
|
|
4042
|
+
personal_server: {
|
|
4043
|
+
url: psUrl,
|
|
4044
|
+
session_token: result.session_token,
|
|
4045
|
+
expires_at: result.expires_at,
|
|
4046
|
+
},
|
|
4047
|
+
});
|
|
4048
|
+
await updateCliConfig({ personalServerUrl: psUrl });
|
|
4049
|
+
renderer?.success(`Logged in to ${psUrl}`);
|
|
4050
|
+
renderer?.detail("Credentials saved to ~/.vana/auth.json");
|
|
4051
|
+
return 0;
|
|
4052
|
+
}
|
|
4053
|
+
catch (err) {
|
|
4054
|
+
renderer?.fail("Login failed");
|
|
4055
|
+
renderer?.detail(err instanceof Error ? err.message : String(err));
|
|
4056
|
+
renderer?.next(`vana login --server ${psUrl}`);
|
|
4057
|
+
return 1;
|
|
4058
|
+
}
|
|
4059
|
+
finally {
|
|
4060
|
+
renderer?.cleanup();
|
|
4061
|
+
}
|
|
4062
|
+
}
|
|
4063
|
+
// Cloud flow (account.vana.org)
|
|
4064
|
+
// Check env var shortcut
|
|
4065
|
+
const envToken = process.env.VANA_SESSION_TOKEN;
|
|
4066
|
+
if (envToken) {
|
|
4067
|
+
const creds = loadCredentials();
|
|
4068
|
+
if (creds) {
|
|
4069
|
+
if (options.json) {
|
|
4070
|
+
process.stdout.write(`${JSON.stringify({ status: "authenticated", source: "env", address: creds.account.address })}\n`);
|
|
4071
|
+
}
|
|
4072
|
+
else {
|
|
4073
|
+
const emit = createEmitter(options);
|
|
4074
|
+
emit.success(`Already authenticated via VANA_SESSION_TOKEN env var`);
|
|
4075
|
+
}
|
|
4076
|
+
return 0;
|
|
4077
|
+
}
|
|
4078
|
+
}
|
|
4079
|
+
// Check if already logged in
|
|
4080
|
+
const existing = loadCredentials();
|
|
4081
|
+
if (existing && !isExpired(existing)) {
|
|
4082
|
+
if (options.json) {
|
|
4083
|
+
process.stdout.write(`${JSON.stringify({
|
|
4084
|
+
status: "authenticated",
|
|
4085
|
+
address: existing.account.address,
|
|
4086
|
+
personal_server: existing.personal_server?.url ?? null,
|
|
4087
|
+
expires_at: existing.account.expires_at,
|
|
4088
|
+
})}\n`);
|
|
4089
|
+
}
|
|
4090
|
+
else {
|
|
4091
|
+
const emit = createEmitter(options);
|
|
4092
|
+
emit.success(`Already logged in as ${formatAddress(existing.account.address)}`);
|
|
4093
|
+
if (existing.personal_server) {
|
|
4094
|
+
emit.keyValue("Personal Server", existing.personal_server.url, "success");
|
|
4095
|
+
}
|
|
4096
|
+
emit.info(` Auth expires in ${formatExpiresIn(existing.account.expires_at)}`);
|
|
4097
|
+
emit.blank();
|
|
4098
|
+
emit.info(" Run `vana logout` first to re-authenticate.");
|
|
4099
|
+
}
|
|
4100
|
+
return 0;
|
|
4101
|
+
}
|
|
4102
|
+
if (options.json) {
|
|
4103
|
+
// JSON mode: run flow and output result
|
|
4104
|
+
const creds = await runDeviceCodeFlow({
|
|
4105
|
+
onCode: (code, uri) => {
|
|
4106
|
+
process.stderr.write(JSON.stringify({ event: "device_code", code, uri }) + "\n");
|
|
4107
|
+
},
|
|
4108
|
+
onWaiting: () => { },
|
|
4109
|
+
onAuthorized: () => { },
|
|
4110
|
+
onExpired: () => {
|
|
4111
|
+
process.stdout.write(JSON.stringify({ status: "expired", error: "Device code expired" }) +
|
|
4112
|
+
"\n");
|
|
4113
|
+
},
|
|
4114
|
+
onError: (err) => {
|
|
4115
|
+
process.stdout.write(JSON.stringify({ status: "error", error: err.message }) + "\n");
|
|
4116
|
+
},
|
|
4117
|
+
});
|
|
4118
|
+
if (creds) {
|
|
4119
|
+
await saveCredentials(creds);
|
|
4120
|
+
if (creds.personal_server?.url) {
|
|
4121
|
+
await updateCliConfig({ personalServerUrl: creds.personal_server.url });
|
|
4122
|
+
}
|
|
4123
|
+
process.stdout.write(`${JSON.stringify({
|
|
4124
|
+
status: "authenticated",
|
|
4125
|
+
address: creds.account.address,
|
|
4126
|
+
personal_server: creds.personal_server?.url ?? null,
|
|
4127
|
+
expires_at: creds.account.expires_at,
|
|
4128
|
+
})}\n`);
|
|
4129
|
+
return 0;
|
|
4130
|
+
}
|
|
4131
|
+
return 1;
|
|
4132
|
+
}
|
|
4133
|
+
// Interactive mode
|
|
4134
|
+
const renderer = createLoginRenderer();
|
|
4135
|
+
renderer.title("Vana");
|
|
4136
|
+
const creds = await runDeviceCodeFlow({
|
|
4137
|
+
onCode: (code, uri) => {
|
|
4138
|
+
renderer.note("Open this URL in your browser:");
|
|
4139
|
+
renderer.note(uri);
|
|
4140
|
+
renderer.note(`Enter this code: ${code}`);
|
|
4141
|
+
},
|
|
4142
|
+
onWaiting: () => {
|
|
4143
|
+
renderer.scopeActive("Waiting for authorization");
|
|
4144
|
+
},
|
|
4145
|
+
onAuthorized: async (authedCreds) => {
|
|
4146
|
+
await saveCredentials(authedCreds);
|
|
4147
|
+
if (authedCreds.personal_server?.url) {
|
|
4148
|
+
await updateCliConfig({
|
|
4149
|
+
personalServerUrl: authedCreds.personal_server.url,
|
|
4150
|
+
});
|
|
4151
|
+
}
|
|
4152
|
+
renderer.success(`Logged in as ${formatAddress(authedCreds.account.address)}`);
|
|
4153
|
+
if (authedCreds.personal_server) {
|
|
4154
|
+
renderer.detail(`Personal Server: ${authedCreds.personal_server.url}`);
|
|
4155
|
+
}
|
|
4156
|
+
renderer.detail("Credentials saved to ~/.vana/auth.json");
|
|
4157
|
+
},
|
|
4158
|
+
onExpired: () => {
|
|
4159
|
+
renderer.fail("Authorization expired");
|
|
4160
|
+
renderer.next("vana login");
|
|
4161
|
+
},
|
|
4162
|
+
onError: (err) => {
|
|
4163
|
+
renderer.fail("Login failed");
|
|
4164
|
+
renderer.detail(err.message);
|
|
4165
|
+
renderer.next("vana login");
|
|
4166
|
+
},
|
|
4167
|
+
});
|
|
4168
|
+
renderer.cleanup();
|
|
4169
|
+
return creds ? 0 : 1;
|
|
4170
|
+
}
|
|
4171
|
+
async function runLogout(options) {
|
|
4172
|
+
// Revoke the token server-side before clearing local credentials
|
|
4173
|
+
const creds = loadCredentials();
|
|
4174
|
+
if (creds?.personal_server?.url && creds.personal_server.session_token) {
|
|
4175
|
+
try {
|
|
4176
|
+
await fetch(`${creds.personal_server.url.replace(/\/$/, "")}/auth/device/token`, {
|
|
4177
|
+
method: "DELETE",
|
|
4178
|
+
headers: {
|
|
4179
|
+
Authorization: `Bearer ${creds.personal_server.session_token}`,
|
|
4180
|
+
},
|
|
4181
|
+
signal: AbortSignal.timeout(5000),
|
|
4182
|
+
});
|
|
4183
|
+
}
|
|
4184
|
+
catch {
|
|
4185
|
+
// Best-effort — server may be down, but we still clear local creds
|
|
4186
|
+
}
|
|
4187
|
+
}
|
|
4188
|
+
await clearCredentials();
|
|
4189
|
+
if (options.json) {
|
|
4190
|
+
process.stdout.write(`${JSON.stringify({ status: "logged_out" })}\n`);
|
|
4191
|
+
}
|
|
4192
|
+
else {
|
|
4193
|
+
const emit = createEmitter(options);
|
|
4194
|
+
emit.success("Logged out. Credentials removed.");
|
|
4195
|
+
}
|
|
4196
|
+
return 0;
|
|
4197
|
+
}
|
|
3902
4198
|
//# sourceMappingURL=index.js.map
|