@opendatalabs/connect 0.8.0 → 0.8.1-canary.18332a5

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 (107) hide show
  1. package/README.md +123 -0
  2. package/dist/cli/bin.d.ts +3 -0
  3. package/dist/cli/bin.d.ts.map +1 -0
  4. package/dist/cli/bin.js +7 -0
  5. package/dist/cli/bin.js.map +1 -0
  6. package/dist/cli/index.d.ts +2 -0
  7. package/dist/cli/index.d.ts.map +1 -0
  8. package/dist/cli/index.js +901 -0
  9. package/dist/cli/index.js.map +1 -0
  10. package/dist/cli/main.d.ts +2 -0
  11. package/dist/cli/main.d.ts.map +1 -0
  12. package/dist/cli/main.js +2 -0
  13. package/dist/cli/main.js.map +1 -0
  14. package/dist/cli/render/capabilities.d.ts +9 -0
  15. package/dist/cli/render/capabilities.d.ts.map +1 -0
  16. package/dist/cli/render/capabilities.js +24 -0
  17. package/dist/cli/render/capabilities.js.map +1 -0
  18. package/dist/cli/render/format.d.ts +20 -0
  19. package/dist/cli/render/format.d.ts.map +1 -0
  20. package/dist/cli/render/format.js +58 -0
  21. package/dist/cli/render/format.js.map +1 -0
  22. package/dist/cli/render/index.d.ts +5 -0
  23. package/dist/cli/render/index.d.ts.map +1 -0
  24. package/dist/cli/render/index.js +5 -0
  25. package/dist/cli/render/index.js.map +1 -0
  26. package/dist/cli/render/symbols.d.ts +11 -0
  27. package/dist/cli/render/symbols.d.ts.map +1 -0
  28. package/dist/cli/render/symbols.js +21 -0
  29. package/dist/cli/render/symbols.js.map +1 -0
  30. package/dist/cli/render/theme.d.ts +15 -0
  31. package/dist/cli/render/theme.d.ts.map +1 -0
  32. package/dist/cli/render/theme.js +17 -0
  33. package/dist/cli/render/theme.js.map +1 -0
  34. package/dist/connectors/index.d.ts +2 -0
  35. package/dist/connectors/index.d.ts.map +1 -0
  36. package/dist/connectors/index.js +2 -0
  37. package/dist/connectors/index.js.map +1 -0
  38. package/dist/connectors/registry.d.ts +30 -0
  39. package/dist/connectors/registry.d.ts.map +1 -0
  40. package/dist/connectors/registry.js +166 -0
  41. package/dist/connectors/registry.js.map +1 -0
  42. package/dist/core/cli-types.d.ts +123 -0
  43. package/dist/core/cli-types.d.ts.map +1 -0
  44. package/dist/core/cli-types.js +64 -0
  45. package/dist/core/cli-types.js.map +1 -0
  46. package/dist/core/index.d.ts +3 -0
  47. package/dist/core/index.d.ts.map +1 -1
  48. package/dist/core/index.js +3 -0
  49. package/dist/core/index.js.map +1 -1
  50. package/dist/core/paths.d.ts +8 -0
  51. package/dist/core/paths.d.ts.map +1 -0
  52. package/dist/core/paths.js +25 -0
  53. package/dist/core/paths.js.map +1 -0
  54. package/dist/core/state-store.d.ts +17 -0
  55. package/dist/core/state-store.d.ts.map +1 -0
  56. package/dist/core/state-store.js +23 -0
  57. package/dist/core/state-store.js.map +1 -0
  58. package/dist/core/types.d.ts +4 -0
  59. package/dist/core/types.d.ts.map +1 -1
  60. package/dist/personal-server/index.d.ts +8 -0
  61. package/dist/personal-server/index.d.ts.map +1 -0
  62. package/dist/personal-server/index.js +76 -0
  63. package/dist/personal-server/index.js.map +1 -0
  64. package/dist/react/useVanaData.d.ts +1 -1
  65. package/dist/react/useVanaData.d.ts.map +1 -1
  66. package/dist/react/useVanaData.js +22 -9
  67. package/dist/react/useVanaData.js.map +1 -1
  68. package/dist/runtime/core/contracts.d.ts +69 -0
  69. package/dist/runtime/core/contracts.d.ts.map +1 -0
  70. package/dist/runtime/core/contracts.js +2 -0
  71. package/dist/runtime/core/contracts.js.map +1 -0
  72. package/dist/runtime/core/index.d.ts +2 -0
  73. package/dist/runtime/core/index.d.ts.map +1 -0
  74. package/dist/runtime/core/index.js +2 -0
  75. package/dist/runtime/core/index.js.map +1 -0
  76. package/dist/runtime/index.d.ts +4 -0
  77. package/dist/runtime/index.d.ts.map +1 -0
  78. package/dist/runtime/index.js +3 -0
  79. package/dist/runtime/index.js.map +1 -0
  80. package/dist/runtime/managed-playwright.d.ts +33 -0
  81. package/dist/runtime/managed-playwright.d.ts.map +1 -0
  82. package/dist/runtime/managed-playwright.js +113 -0
  83. package/dist/runtime/managed-playwright.js.map +1 -0
  84. package/dist/runtime/playwright/browser.d.ts +11 -0
  85. package/dist/runtime/playwright/browser.d.ts.map +1 -0
  86. package/dist/runtime/playwright/browser.js +208 -0
  87. package/dist/runtime/playwright/browser.js.map +1 -0
  88. package/dist/runtime/playwright/in-process-run.d.ts +6 -0
  89. package/dist/runtime/playwright/in-process-run.d.ts.map +1 -0
  90. package/dist/runtime/playwright/in-process-run.js +534 -0
  91. package/dist/runtime/playwright/in-process-run.js.map +1 -0
  92. package/dist/runtime/playwright/index.d.ts +3 -0
  93. package/dist/runtime/playwright/index.d.ts.map +1 -0
  94. package/dist/runtime/playwright/index.js +3 -0
  95. package/dist/runtime/playwright/index.js.map +1 -0
  96. package/dist/runtime/repo-paths.d.ts +2 -0
  97. package/dist/runtime/repo-paths.d.ts.map +1 -0
  98. package/dist/runtime/repo-paths.js +36 -0
  99. package/dist/runtime/repo-paths.js.map +1 -0
  100. package/dist/server/config.d.ts +2 -0
  101. package/dist/server/config.d.ts.map +1 -1
  102. package/dist/server/config.js +1 -0
  103. package/dist/server/config.js.map +1 -1
  104. package/dist/server/connect.d.ts.map +1 -1
  105. package/dist/server/connect.js +7 -0
  106. package/dist/server/connect.js.map +1 -1
  107. package/package.json +38 -4
@@ -0,0 +1,901 @@
1
+ import fs from "node:fs";
2
+ import fsp from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { confirm, input, password, select } from "@inquirer/prompts";
5
+ import { Command } from "commander";
6
+ import { createHumanRenderer } from "./render/index.js";
7
+ import { CliOutcomeStatus, getBrowserProfilesDir, getConnectorCacheDir, getLastResultPath, readCliState, updateSourceState, } from "../core/index.js";
8
+ import { listAvailableSources } from "../connectors/registry.js";
9
+ import { detectPersonalServerTarget, ingestResult, } from "../personal-server/index.js";
10
+ import { findDataConnectorsDir, ManagedPlaywrightRuntime, } from "../runtime/index.js";
11
+ export async function runCli(argv = process.argv) {
12
+ const normalizedArgv = normalizeArgv(argv);
13
+ const parsedOptions = extractGlobalOptions(normalizedArgv);
14
+ const program = new Command();
15
+ program.name("vana").description("Vana Connect CLI");
16
+ program
17
+ .command("connect [source]")
18
+ .option("--json", "Output machine-readable JSON")
19
+ .option("--no-input", "Fail instead of prompting for input")
20
+ .option("--yes", "Approve safe setup prompts automatically")
21
+ .option("--quiet", "Reduce non-essential output")
22
+ .action(async (source) => {
23
+ process.exitCode = source
24
+ ? await runConnect(source, parsedOptions)
25
+ : await runConnectEntry(parsedOptions);
26
+ });
27
+ program
28
+ .command("sources")
29
+ .description("List supported sources")
30
+ .option("--json", "Output machine-readable JSON")
31
+ .action(async () => {
32
+ process.exitCode = await runList(parsedOptions);
33
+ });
34
+ program
35
+ .command("status")
36
+ .description("Show runtime and Personal Server status")
37
+ .option("--json", "Output machine-readable JSON")
38
+ .action(async () => {
39
+ process.exitCode = await runStatus(parsedOptions);
40
+ });
41
+ program
42
+ .command("setup")
43
+ .description("Install or repair the local runtime")
44
+ .option("--json", "Output machine-readable JSON")
45
+ .option("--yes", "Approve safe setup prompts automatically")
46
+ .action(async () => {
47
+ process.exitCode = await runSetup(parsedOptions);
48
+ });
49
+ const data = program.command("data").description("Inspect collected data");
50
+ data
51
+ .command("list")
52
+ .description("List locally available collected datasets")
53
+ .option("--json", "Output machine-readable JSON")
54
+ .action(async () => {
55
+ process.exitCode = await runDataList(parsedOptions);
56
+ });
57
+ data
58
+ .command("show <source>")
59
+ .description("Show a collected dataset")
60
+ .option("--json", "Output machine-readable JSON")
61
+ .action(async (source) => {
62
+ process.exitCode = await runDataShow(source, parsedOptions);
63
+ });
64
+ data
65
+ .command("path <source>")
66
+ .description("Print the local path for a collected dataset")
67
+ .action(async (source) => {
68
+ process.exitCode = await runDataPath(source, parsedOptions);
69
+ });
70
+ await program.parseAsync(normalizedArgv);
71
+ return Number(process.exitCode ?? 0);
72
+ }
73
+ async function runConnect(source, options) {
74
+ const runtime = new ManagedPlaywrightRuntime();
75
+ const emit = createEmitter(options);
76
+ const registrySources = await loadRegistrySources();
77
+ const sourceLabels = createSourceLabelMap(registrySources);
78
+ let setupLogPath;
79
+ let fetchLogPath;
80
+ let runLogPath;
81
+ try {
82
+ emit.info(`Finding a connector for ${displaySource(source, sourceLabels)}...`);
83
+ const target = await detectPersonalServerTarget();
84
+ if (runtime.state !== "installed") {
85
+ emit.info(`Vana Connect needs a local browser runtime before it can connect ${displaySource(source, sourceLabels)}.`);
86
+ emit.info("");
87
+ emit.info("This will install:");
88
+ emit.info("- the local browser runtime");
89
+ emit.info("- a Chromium browser engine");
90
+ emit.info("- local runtime files under ~/.dataconnect/");
91
+ emit.info("");
92
+ emit.info("Your credentials stay on this machine. Nothing is sent anywhere except the platform you’re connecting to.");
93
+ if (!options.yes) {
94
+ if (options.noInput) {
95
+ emit.event({
96
+ type: "outcome",
97
+ status: CliOutcomeStatus.SETUP_REQUIRED,
98
+ source,
99
+ });
100
+ return 1;
101
+ }
102
+ const shouldContinue = await confirm({
103
+ message: "Continue?",
104
+ default: true,
105
+ });
106
+ if (!shouldContinue) {
107
+ emit.event({
108
+ type: "outcome",
109
+ status: CliOutcomeStatus.SETUP_REQUIRED,
110
+ source,
111
+ reason: "setup_declined",
112
+ });
113
+ return 1;
114
+ }
115
+ }
116
+ const installResult = await runtime.ensureInstalled(Boolean(options.yes));
117
+ setupLogPath = installResult.logPath;
118
+ emit.event({
119
+ type: "setup-complete",
120
+ runtime: installResult.runtime,
121
+ logPath: installResult.logPath,
122
+ });
123
+ emit.info("Runtime ready.");
124
+ if (installResult.logPath) {
125
+ emit.info(`Setup log: ${installResult.logPath}`);
126
+ }
127
+ }
128
+ else {
129
+ emit.event({
130
+ type: "setup-check",
131
+ runtime: runtime.state,
132
+ });
133
+ }
134
+ const fetched = await runtime.fetchConnector(source);
135
+ fetchLogPath = fetched.logPath;
136
+ const sourceDetails = registrySources.find((item) => item.id === source);
137
+ const resolution = {
138
+ source,
139
+ connectorPath: fetched.connectorPath,
140
+ };
141
+ emit.event({
142
+ type: "connector-resolved",
143
+ source: resolution.source,
144
+ connectorPath: resolution.connectorPath,
145
+ logPath: fetched.logPath,
146
+ });
147
+ emit.info("Connector ready.");
148
+ if (sourceDetails?.description) {
149
+ emit.info(sourceDetails.description);
150
+ }
151
+ const profilePath = path.join(getBrowserProfilesDir(), `${path.basename(resolution.connectorPath, path.extname(resolution.connectorPath))}`);
152
+ if (fs.existsSync(profilePath)) {
153
+ emit.info(`Found an existing ${displaySource(source, sourceLabels)} session. Reusing it if it is still valid...`);
154
+ }
155
+ await updateSourceState(resolution.source, {
156
+ connectorInstalled: true,
157
+ sessionPresent: fs.existsSync(profilePath),
158
+ lastError: null,
159
+ });
160
+ emit.info(`Connecting to ${displaySource(source, sourceLabels)}...`);
161
+ emit.info("Collecting your data...");
162
+ let finalStatus = CliOutcomeStatus.UNEXPECTED_INTERNAL_ERROR;
163
+ let resultPath = getLastResultPath();
164
+ let collectedResult = false;
165
+ for await (const event of runtime.runConnector({
166
+ connectorPath: resolution.connectorPath,
167
+ source: resolution.source,
168
+ noInput: options.noInput,
169
+ onNeedInput: async (needInput) => {
170
+ emit.info("");
171
+ emit.info(`To connect ${displaySource(source, sourceLabels)}, Vana Connect will open a local browser session on this machine.`);
172
+ emit.info("Your credentials stay local.");
173
+ emit.info("");
174
+ emit.info(needInput.message ??
175
+ `${displaySource(source, sourceLabels)} needs additional details to continue.`);
176
+ const values = {};
177
+ for (const field of needInput.fields) {
178
+ if (field.toLowerCase().includes("password")) {
179
+ values[field] = await password({ message: humanizeField(field) });
180
+ }
181
+ else {
182
+ values[field] = await input({ message: humanizeField(field) });
183
+ }
184
+ }
185
+ return values;
186
+ },
187
+ })) {
188
+ emit.event(event);
189
+ if (event.logPath) {
190
+ runLogPath = event.logPath;
191
+ }
192
+ if (event.type === "needs-input") {
193
+ await updateSourceState(resolution.source, {
194
+ lastRunAt: new Date().toISOString(),
195
+ lastRunOutcome: CliOutcomeStatus.NEEDS_INPUT,
196
+ lastError: event.message ?? "Input required.",
197
+ });
198
+ emit.event({
199
+ type: "outcome",
200
+ status: CliOutcomeStatus.NEEDS_INPUT,
201
+ source: resolution.source,
202
+ });
203
+ if (!options.json) {
204
+ emit.info(`${displaySource(source, sourceLabels)} needs additional input before it can connect.`);
205
+ emit.info(`Next: run \`vana connect ${source}\` without \`--no-input\`.`);
206
+ }
207
+ return 1;
208
+ }
209
+ if (event.type === "runtime-error") {
210
+ await updateSourceState(resolution.source, {
211
+ lastRunAt: new Date().toISOString(),
212
+ lastRunOutcome: CliOutcomeStatus.RUNTIME_ERROR,
213
+ lastError: event.message ?? "Connector run failed.",
214
+ });
215
+ emit.info(event.message ?? "Connector run failed.");
216
+ emit.event({
217
+ type: "outcome",
218
+ status: CliOutcomeStatus.RUNTIME_ERROR,
219
+ source: resolution.source,
220
+ });
221
+ if (event.logPath) {
222
+ emit.info(`Run log: ${event.logPath}`);
223
+ }
224
+ return 1;
225
+ }
226
+ if (event.type === "headed-required") {
227
+ if (event.message) {
228
+ emit.info(event.message);
229
+ }
230
+ if (event.url) {
231
+ emit.info(`Opening ${displaySource(source, sourceLabels)} in a local browser session...`);
232
+ }
233
+ continue;
234
+ }
235
+ if (event.type === "legacy-auth") {
236
+ await updateSourceState(resolution.source, {
237
+ lastRunAt: new Date().toISOString(),
238
+ lastRunOutcome: CliOutcomeStatus.LEGACY_AUTH,
239
+ lastError: event.message ?? "Legacy authentication is required.",
240
+ dataState: "none",
241
+ lastResultPath: null,
242
+ });
243
+ emit.info(event.message ??
244
+ "This connector needs a manual browser step that is not available in non-interactive mode.");
245
+ emit.info(`Next: run \`vana connect ${source}\` without \`--no-input\`.`);
246
+ if (event.logPath) {
247
+ emit.info(`Run log: ${event.logPath}`);
248
+ }
249
+ emit.event({
250
+ type: "outcome",
251
+ status: CliOutcomeStatus.LEGACY_AUTH,
252
+ source: resolution.source,
253
+ });
254
+ return 1;
255
+ }
256
+ if (event.type === "collection-complete" && event.resultPath) {
257
+ collectedResult = true;
258
+ resultPath = event.resultPath;
259
+ const ingestEvents = await ingestResult(resolution.source, resultPath, target);
260
+ for (const ingestEvent of ingestEvents) {
261
+ emit.event(ingestEvent);
262
+ }
263
+ const ingestCompleted = ingestEvents.some((ingestEvent) => ingestEvent.type === "ingest-complete");
264
+ finalStatus = ingestCompleted
265
+ ? CliOutcomeStatus.CONNECTED_AND_INGESTED
266
+ : CliOutcomeStatus.CONNECTED_LOCAL_ONLY;
267
+ }
268
+ }
269
+ if (!collectedResult) {
270
+ await updateSourceState(resolution.source, {
271
+ connectorInstalled: true,
272
+ sessionPresent: fs.existsSync(profilePath),
273
+ lastRunAt: new Date().toISOString(),
274
+ lastRunOutcome: CliOutcomeStatus.UNEXPECTED_INTERNAL_ERROR,
275
+ dataState: "none",
276
+ lastError: "Connector run ended without a result.",
277
+ lastResultPath: null,
278
+ });
279
+ emit.event({
280
+ type: "outcome",
281
+ status: CliOutcomeStatus.UNEXPECTED_INTERNAL_ERROR,
282
+ source: resolution.source,
283
+ reason: "Connector run ended without a result.",
284
+ });
285
+ if (runLogPath) {
286
+ emit.info(`Run log: ${runLogPath}`);
287
+ }
288
+ return 1;
289
+ }
290
+ const dataState = finalStatus === CliOutcomeStatus.CONNECTED_AND_INGESTED
291
+ ? "ingested_personal_server"
292
+ : "collected_local";
293
+ await updateSourceState(resolution.source, {
294
+ connectorInstalled: true,
295
+ sessionPresent: true,
296
+ lastRunAt: new Date().toISOString(),
297
+ lastRunOutcome: finalStatus,
298
+ dataState,
299
+ lastError: null,
300
+ lastResultPath: resultPath,
301
+ });
302
+ const resultSummary = await readResultSummary(resultPath);
303
+ const connectCommand = emit.code("vana status");
304
+ const dataCommand = emit.code(`vana data show ${source}`);
305
+ emit.title(`Connected ${displaySource(source, sourceLabels)}.`);
306
+ emit.blank();
307
+ if (resultSummary) {
308
+ emit.section("Collected");
309
+ for (const line of resultSummary.lines) {
310
+ emit.bullet(line);
311
+ }
312
+ }
313
+ emit.blank();
314
+ if (finalStatus === CliOutcomeStatus.CONNECTED_AND_INGESTED) {
315
+ emit.section("Synced");
316
+ emit.bullet("Your data is now available in your Personal Server.");
317
+ }
318
+ else {
319
+ emit.section("Saved locally");
320
+ emit.bullet(resultPath);
321
+ if (target.state !== "available") {
322
+ emit.detail("No Personal Server is available right now, so this run stayed local.");
323
+ }
324
+ }
325
+ if (runLogPath) {
326
+ emit.detail(`Run log: ${runLogPath}`);
327
+ }
328
+ else if (fetchLogPath) {
329
+ emit.detail(`Fetch log: ${fetchLogPath}`);
330
+ }
331
+ else if (setupLogPath) {
332
+ emit.detail(`Setup log: ${setupLogPath}`);
333
+ }
334
+ emit.blank();
335
+ emit.section("Next");
336
+ emit.bullet(`Run ${connectCommand}`);
337
+ emit.bullet(`Or inspect the data with ${dataCommand}`);
338
+ emit.event({
339
+ type: "outcome",
340
+ status: finalStatus,
341
+ source: resolution.source,
342
+ resultPath,
343
+ });
344
+ return 0;
345
+ }
346
+ catch (error) {
347
+ const message = error instanceof Error ? error.message : "Unexpected error.";
348
+ emit.info(message);
349
+ emit.event({
350
+ type: "outcome",
351
+ status: CliOutcomeStatus.UNEXPECTED_INTERNAL_ERROR,
352
+ source,
353
+ reason: message,
354
+ });
355
+ if (runLogPath) {
356
+ emit.info(`Run log: ${runLogPath}`);
357
+ }
358
+ else if (fetchLogPath) {
359
+ emit.info(`Fetch log: ${fetchLogPath}`);
360
+ }
361
+ else if (setupLogPath) {
362
+ emit.info(`Setup log: ${setupLogPath}`);
363
+ }
364
+ return 1;
365
+ }
366
+ }
367
+ async function runConnectEntry(options) {
368
+ const emit = createEmitter(options);
369
+ const sources = await loadRegistrySources();
370
+ if (options.json) {
371
+ process.stdout.write(`${JSON.stringify({
372
+ error: "source_required",
373
+ message: "Specify a source. Run `vana sources` to see available options.",
374
+ })}\n`);
375
+ return 1;
376
+ }
377
+ if (options.noInput) {
378
+ emit.info("Specify a source. Run `vana sources` to see available options.");
379
+ return 1;
380
+ }
381
+ if (sources.length === 0) {
382
+ emit.info("No sources are available right now.");
383
+ emit.info("Run `vana sources` to verify the local connector registry.");
384
+ return 1;
385
+ }
386
+ emit.title("Connect data");
387
+ emit.blank();
388
+ emit.info("Choose a source to connect:");
389
+ let source;
390
+ try {
391
+ source = await select({
392
+ message: "Source",
393
+ choices: sources.map((item) => ({
394
+ name: `${item.name}${formatAuthModeBadge(item.authMode, emit)}${item.description ? ` - ${item.description}` : ""}`,
395
+ value: item.id,
396
+ })),
397
+ });
398
+ }
399
+ catch (error) {
400
+ if (isPromptCancelled(error)) {
401
+ emit.info("Cancelled.");
402
+ return 1;
403
+ }
404
+ throw error;
405
+ }
406
+ emit.blank();
407
+ return runConnect(source, options);
408
+ }
409
+ async function runList(options) {
410
+ const sources = await loadRegistrySources();
411
+ const installedSourceIds = new Set((await listInstalledConnectorFiles()).map((source) => source.source));
412
+ const enrichedSources = sources.map((source) => ({
413
+ ...source,
414
+ installed: installedSourceIds.has(source.id),
415
+ }));
416
+ if (options.json) {
417
+ process.stdout.write(`${JSON.stringify({ sources: enrichedSources })}\n`);
418
+ return 0;
419
+ }
420
+ const emit = createEmitter(options);
421
+ emit.title("Available sources");
422
+ emit.blank();
423
+ for (const source of enrichedSources) {
424
+ const badges = [];
425
+ if (source.installed) {
426
+ badges.push({ text: "installed", tone: "success" });
427
+ }
428
+ if (source.authMode === "interactive") {
429
+ badges.push({ text: "interactive", tone: "info" });
430
+ }
431
+ else if (source.authMode === "legacy") {
432
+ badges.push({ text: "legacy", tone: "warning" });
433
+ }
434
+ emit.sourceTitle(source.name, badges);
435
+ if (source.description) {
436
+ emit.detail(source.description);
437
+ }
438
+ }
439
+ return 0;
440
+ }
441
+ async function runStatus(options) {
442
+ const emit = createEmitter(options);
443
+ const runtime = new ManagedPlaywrightRuntime();
444
+ const personalServer = await detectPersonalServerTarget();
445
+ const state = await readCliState();
446
+ const registrySources = await loadRegistrySources();
447
+ const sourceLabels = createSourceLabelMap(registrySources);
448
+ const sourceMetadata = createSourceMetadataMap(registrySources);
449
+ const sources = await gatherSourceStatuses(state.sources, sourceMetadata);
450
+ const status = {
451
+ runtime: runtime.state,
452
+ runtimePath: runtime.runtimePath,
453
+ personalServer: personalServer.state,
454
+ personalServerUrl: personalServer.url,
455
+ sources,
456
+ };
457
+ if (options.json) {
458
+ process.stdout.write(`${JSON.stringify(status)}\n`);
459
+ return 0;
460
+ }
461
+ emit.title("Vana Connect status");
462
+ emit.blank();
463
+ emit.section("Environment");
464
+ emit.keyValue("Runtime", status.runtime, toneForRuntime(status.runtime));
465
+ emit.keyValue("Personal Server", status.personalServer, status.personalServer === "available" ? "success" : "muted");
466
+ if (status.sources.length > 0) {
467
+ emit.blank();
468
+ emit.section("Sources");
469
+ }
470
+ for (const source of status.sources) {
471
+ emit.info(formatSourceStatus(source, sourceLabels, emit));
472
+ const details = formatSourceStatusDetail(source);
473
+ if (details) {
474
+ emit.detail(details);
475
+ }
476
+ }
477
+ return 0;
478
+ }
479
+ async function runSetup(options) {
480
+ const emit = createEmitter(options);
481
+ const runtime = new ManagedPlaywrightRuntime();
482
+ if (runtime.state === "installed") {
483
+ emit.info("Vana Connect runtime is already installed.");
484
+ emit.event({ type: "setup-check", runtime: runtime.state });
485
+ return 0;
486
+ }
487
+ try {
488
+ const result = await runtime.ensureInstalled(Boolean(options.yes));
489
+ emit.info("Runtime ready.");
490
+ if (result.logPath) {
491
+ emit.info(`Setup log: ${result.logPath}`);
492
+ }
493
+ emit.event({
494
+ type: "setup-complete",
495
+ runtime: result.runtime,
496
+ logPath: result.logPath,
497
+ });
498
+ return 0;
499
+ }
500
+ catch (error) {
501
+ const message = error instanceof Error
502
+ ? error.message
503
+ : "Vana Connect could not finish installing the local runtime.";
504
+ emit.info(message);
505
+ emit.event({
506
+ type: "outcome",
507
+ status: CliOutcomeStatus.RUNTIME_ERROR,
508
+ reason: message,
509
+ });
510
+ return 1;
511
+ }
512
+ }
513
+ async function runDataList(options) {
514
+ const state = await readCliState();
515
+ const sources = await gatherSourceStatuses(state.sources, createSourceMetadataMap(await loadRegistrySources()));
516
+ const datasets = sources
517
+ .filter((source) => Boolean(source.lastResultPath))
518
+ .map((source) => ({
519
+ source: source.source,
520
+ name: source.name,
521
+ dataState: source.dataState,
522
+ lastRunAt: source.lastRunAt ?? null,
523
+ path: source.lastResultPath ?? null,
524
+ }));
525
+ if (options.json) {
526
+ process.stdout.write(`${JSON.stringify({ datasets })}\n`);
527
+ return 0;
528
+ }
529
+ const emit = createEmitter(options);
530
+ if (datasets.length === 0) {
531
+ emit.title("Collected data");
532
+ emit.blank();
533
+ emit.info("No local datasets collected yet.");
534
+ emit.info("Run `vana connect <source>` to collect data.");
535
+ return 0;
536
+ }
537
+ emit.title("Collected data");
538
+ emit.blank();
539
+ for (const dataset of datasets) {
540
+ const badges = dataset.dataState === "ingested_personal_server"
541
+ ? [{ text: "synced", tone: "success" }]
542
+ : [{ text: "local", tone: "muted" }];
543
+ emit.sourceTitle(dataset.name ?? displaySource(dataset.source), badges);
544
+ emit.detail(dataset.path ?? "");
545
+ }
546
+ return 0;
547
+ }
548
+ async function runDataShow(source, options) {
549
+ const sourceLabels = createSourceLabelMap(await loadRegistrySources());
550
+ const state = await readCliState();
551
+ const record = state.sources[source];
552
+ const resultPath = record?.lastResultPath;
553
+ const emit = createEmitter(options);
554
+ if (!resultPath) {
555
+ if (options.json) {
556
+ process.stdout.write(`${JSON.stringify({
557
+ error: "dataset_not_found",
558
+ source,
559
+ message: `No collected dataset found for ${displaySource(source, sourceLabels)}. Run \`vana connect ${source}\` first.`,
560
+ })}\n`);
561
+ }
562
+ else {
563
+ emit.info(`No collected dataset found for ${displaySource(source, sourceLabels)}. Run \`vana connect ${source}\` first.`);
564
+ }
565
+ return 1;
566
+ }
567
+ try {
568
+ const raw = await fsp.readFile(resultPath, "utf8");
569
+ const data = JSON.parse(raw);
570
+ if (options.json) {
571
+ process.stdout.write(`${JSON.stringify({ source, path: resultPath, data })}\n`);
572
+ return 0;
573
+ }
574
+ const summary = summarizeResultData(data);
575
+ emit.title(`${displaySource(source, sourceLabels)} data`);
576
+ emit.blank();
577
+ if (summary) {
578
+ for (const line of summary.lines) {
579
+ emit.bullet(line);
580
+ }
581
+ emit.blank();
582
+ }
583
+ emit.keyValue("Path", resultPath, "muted");
584
+ return 0;
585
+ }
586
+ catch (error) {
587
+ const message = error instanceof Error ? error.message : `Could not read ${resultPath}.`;
588
+ if (options.json) {
589
+ process.stdout.write(`${JSON.stringify({ error: "dataset_read_failed", source, path: resultPath, message })}\n`);
590
+ }
591
+ else {
592
+ emit.info(message);
593
+ }
594
+ return 1;
595
+ }
596
+ }
597
+ async function runDataPath(source, options) {
598
+ const sourceLabels = createSourceLabelMap(await loadRegistrySources());
599
+ const state = await readCliState();
600
+ const resultPath = state.sources[source]?.lastResultPath;
601
+ if (!resultPath) {
602
+ createEmitter(options).info(`No collected dataset found for ${displaySource(source, sourceLabels)}.`);
603
+ return 1;
604
+ }
605
+ process.stdout.write(`${resultPath}\n`);
606
+ return 0;
607
+ }
608
+ function createEmitter(options) {
609
+ const renderer = createHumanRenderer();
610
+ return {
611
+ event(event) {
612
+ if (options.json) {
613
+ process.stdout.write(`${JSON.stringify(event)}\n`);
614
+ }
615
+ },
616
+ info(message) {
617
+ if (options.json || options.quiet) {
618
+ return;
619
+ }
620
+ process.stdout.write(`${message}\n`);
621
+ },
622
+ blank() {
623
+ if (options.json || options.quiet) {
624
+ return;
625
+ }
626
+ process.stdout.write("\n");
627
+ },
628
+ title(message) {
629
+ if (options.json || options.quiet) {
630
+ return;
631
+ }
632
+ process.stdout.write(`${renderer.title(message)}\n`);
633
+ },
634
+ section(message) {
635
+ if (options.json || options.quiet) {
636
+ return;
637
+ }
638
+ process.stdout.write(`${renderer.section(message)}\n`);
639
+ },
640
+ keyValue(label, value, tone = "muted") {
641
+ if (options.json || options.quiet) {
642
+ return;
643
+ }
644
+ process.stdout.write(`${renderer.keyValue(label, value, tone)}\n`);
645
+ },
646
+ detail(message) {
647
+ if (options.json || options.quiet) {
648
+ return;
649
+ }
650
+ process.stdout.write(`${renderer.detail(message)}\n`);
651
+ },
652
+ bullet(message) {
653
+ if (options.json || options.quiet) {
654
+ return;
655
+ }
656
+ process.stdout.write(`${renderer.bullet(message)}\n`);
657
+ },
658
+ sourceTitle(name, badges = []) {
659
+ if (options.json || options.quiet) {
660
+ return;
661
+ }
662
+ process.stdout.write(`${renderer.sourceTitle(name, badges.map((badge) => renderer.badge(badge.text, badge.tone)))}\n`);
663
+ },
664
+ badge(text, tone = "muted") {
665
+ return renderer.badge(text, tone);
666
+ },
667
+ code(text) {
668
+ return renderer.theme.code(text);
669
+ },
670
+ };
671
+ }
672
+ function displaySource(source, labels = {}) {
673
+ return labels[source] ?? source.charAt(0).toUpperCase() + source.slice(1);
674
+ }
675
+ function humanizeField(value) {
676
+ return value
677
+ .replace(/([a-z])([A-Z])/g, "$1 $2")
678
+ .replace(/[_-]/g, " ")
679
+ .replace(/^\w/, (match) => match.toUpperCase());
680
+ }
681
+ async function gatherSourceStatuses(storedSources, metadata = {}) {
682
+ const installedFiles = await listInstalledConnectorFiles();
683
+ const sourceNames = new Set([
684
+ ...Object.keys(storedSources),
685
+ ...installedFiles.map((file) => file.source),
686
+ ]);
687
+ return [...sourceNames]
688
+ .sort((left, right) => left.localeCompare(right))
689
+ .map((source) => {
690
+ const stored = storedSources[source] ?? {};
691
+ const installed = installedFiles.some((file) => file.source === source);
692
+ const details = metadata[source];
693
+ return {
694
+ source,
695
+ name: details?.name,
696
+ company: details?.company,
697
+ description: details?.description,
698
+ authMode: details?.authMode ?? inferInstalledAuthMode(installedFiles, source),
699
+ installed,
700
+ sessionPresent: stored.sessionPresent ?? false,
701
+ lastRunAt: stored.lastRunAt ?? null,
702
+ lastRunOutcome: stored.lastRunOutcome ?? null,
703
+ dataState: stored.dataState === "ingested_personal_server"
704
+ ? "ingested_personal_server"
705
+ : stored.dataState === "collected_local"
706
+ ? "collected_local"
707
+ : "none",
708
+ lastError: stored.lastError ?? null,
709
+ lastResultPath: stored.lastResultPath ?? null,
710
+ };
711
+ });
712
+ }
713
+ async function listInstalledConnectorFiles() {
714
+ const connectorsDir = getConnectorCacheDir();
715
+ try {
716
+ const results = [];
717
+ const entries = await fsp.readdir(connectorsDir, { withFileTypes: true });
718
+ for (const entry of entries) {
719
+ if (!entry.isDirectory()) {
720
+ continue;
721
+ }
722
+ const companyDir = path.join(connectorsDir, entry.name);
723
+ const files = await fsp.readdir(companyDir);
724
+ for (const file of files) {
725
+ if (!file.endsWith("-playwright.js")) {
726
+ continue;
727
+ }
728
+ results.push({
729
+ source: file.replace(/-playwright\.js$/, ""),
730
+ path: path.join(companyDir, file),
731
+ });
732
+ }
733
+ }
734
+ return results;
735
+ }
736
+ catch {
737
+ return [];
738
+ }
739
+ }
740
+ function formatSourceStatus(source, labels = {}, emit) {
741
+ const authModeSuffix = formatAuthModeBadge(source.authMode, emit);
742
+ if (!source.installed) {
743
+ return `${displaySource(source.source, labels)}${authModeSuffix}: not connected`;
744
+ }
745
+ if (!source.lastRunOutcome) {
746
+ return `${displaySource(source.source, labels)}${authModeSuffix}: installed`;
747
+ }
748
+ if (source.lastRunOutcome === CliOutcomeStatus.NEEDS_INPUT) {
749
+ return `${displaySource(source.source, labels)}${authModeSuffix}: needs input`;
750
+ }
751
+ if (source.lastRunOutcome === CliOutcomeStatus.RUNTIME_ERROR) {
752
+ return `${displaySource(source.source, labels)}${authModeSuffix}: error`;
753
+ }
754
+ if (source.lastRunOutcome === CliOutcomeStatus.LEGACY_AUTH) {
755
+ return `${displaySource(source.source, labels)}${authModeSuffix}: manual browser step required`;
756
+ }
757
+ if (source.dataState === "ingested_personal_server") {
758
+ return `${displaySource(source.source, labels)}${authModeSuffix}: connected, synced`;
759
+ }
760
+ if (source.dataState === "collected_local") {
761
+ return `${displaySource(source.source, labels)}${authModeSuffix}: connected, local only`;
762
+ }
763
+ return `${displaySource(source.source, labels)}${authModeSuffix}: connected`;
764
+ }
765
+ function formatSourceStatusDetail(source) {
766
+ if (source.lastRunOutcome === CliOutcomeStatus.NEEDS_INPUT) {
767
+ return source.lastError
768
+ ? `${source.lastError}. Run \`vana connect ${source.source}\` interactively.`
769
+ : `Run \`vana connect ${source.source}\` interactively.`;
770
+ }
771
+ if (source.lastRunOutcome === CliOutcomeStatus.LEGACY_AUTH) {
772
+ return `Run \`vana connect ${source.source}\` without \`--no-input\` to complete the manual browser step.`;
773
+ }
774
+ if (source.lastRunOutcome === CliOutcomeStatus.RUNTIME_ERROR) {
775
+ return source.lastError ?? "The last connector run failed.";
776
+ }
777
+ if (!source.lastRunOutcome && source.installed) {
778
+ return `Run \`vana connect ${source.source}\` to collect data.`;
779
+ }
780
+ return null;
781
+ }
782
+ function normalizeArgv(argv) {
783
+ if (argv[2] === "connect" &&
784
+ ["list", "status", "setup"].includes(argv[3] ?? "")) {
785
+ const mapping = {
786
+ list: "sources",
787
+ status: "status",
788
+ setup: "setup",
789
+ };
790
+ return [argv[0], argv[1], mapping[argv[3]], ...argv.slice(4)];
791
+ }
792
+ return argv;
793
+ }
794
+ function extractGlobalOptions(argv) {
795
+ return {
796
+ json: argv.includes("--json"),
797
+ noInput: argv.includes("--no-input"),
798
+ yes: argv.includes("--yes"),
799
+ quiet: argv.includes("--quiet"),
800
+ };
801
+ }
802
+ function createSourceLabelMap(sources) {
803
+ return Object.fromEntries(sources.map((source) => [source.id, source.name]));
804
+ }
805
+ function createSourceMetadataMap(sources) {
806
+ return Object.fromEntries(sources.map((source) => [
807
+ source.id,
808
+ {
809
+ name: source.name,
810
+ company: source.company,
811
+ description: source.description,
812
+ authMode: source.authMode,
813
+ },
814
+ ]));
815
+ }
816
+ function formatAuthModeBadge(authMode, emit) {
817
+ if (authMode === "legacy") {
818
+ return ` ${emit ? emit.badge("legacy", "warning") : "[legacy]"}`;
819
+ }
820
+ if (authMode === "interactive") {
821
+ return ` ${emit ? emit.badge("interactive", "info") : "[interactive]"}`;
822
+ }
823
+ return "";
824
+ }
825
+ function toneForRuntime(runtime) {
826
+ if (runtime === "installed") {
827
+ return "success";
828
+ }
829
+ if (runtime === "missing") {
830
+ return "warning";
831
+ }
832
+ return "muted";
833
+ }
834
+ function inferInstalledAuthMode(installedFiles, source) {
835
+ const match = installedFiles.find((file) => file.source === source);
836
+ if (!match) {
837
+ return undefined;
838
+ }
839
+ try {
840
+ const script = fs.readFileSync(match.path, "utf8");
841
+ if (/page\.requestInput\(/.test(script)) {
842
+ return "interactive";
843
+ }
844
+ if (/page\.(showBrowser|promptUser)\(/.test(script)) {
845
+ return "legacy";
846
+ }
847
+ return "automated";
848
+ }
849
+ catch {
850
+ return undefined;
851
+ }
852
+ }
853
+ async function loadRegistrySources() {
854
+ try {
855
+ return ((await listAvailableSources(findDataConnectorsDir() ?? undefined)) ?? []);
856
+ }
857
+ catch {
858
+ return [];
859
+ }
860
+ }
861
+ async function readResultSummary(resultPath) {
862
+ try {
863
+ const raw = await fsp.readFile(resultPath, "utf8");
864
+ return summarizeResultData(JSON.parse(raw));
865
+ }
866
+ catch {
867
+ return null;
868
+ }
869
+ }
870
+ function summarizeResultData(data) {
871
+ const lines = [];
872
+ const exportSummary = typeof data.exportSummary === "object" && data.exportSummary
873
+ ? data.exportSummary
874
+ : null;
875
+ const profile = typeof data.profile === "object" && data.profile
876
+ ? data.profile
877
+ : null;
878
+ if (profile?.username && typeof profile.username === "string") {
879
+ lines.push(`Profile: ${profile.username}`);
880
+ }
881
+ if (Array.isArray(data.repositories)) {
882
+ lines.push(`Repositories: ${data.repositories.length}`);
883
+ }
884
+ if (Array.isArray(data.starred)) {
885
+ lines.push(`Starred: ${data.starred.length}`);
886
+ }
887
+ if (Array.isArray(data.orders)) {
888
+ lines.push(`Orders: ${data.orders.length}`);
889
+ }
890
+ if (exportSummary?.details &&
891
+ typeof exportSummary.details === "string" &&
892
+ !lines.includes(exportSummary.details)) {
893
+ lines.push(exportSummary.details);
894
+ }
895
+ return lines.length > 0 ? { lines } : null;
896
+ }
897
+ function isPromptCancelled(error) {
898
+ return (error instanceof Error &&
899
+ (error.name === "ExitPromptError" || error.message.includes("SIGINT")));
900
+ }
901
+ //# sourceMappingURL=index.js.map