@opendatalabs/connect 0.11.4 → 0.11.6-canary.1807d4e

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/index.js CHANGED
@@ -35,6 +35,7 @@ import { listAvailableSkills, installSkill, readInstalledSkills, } from "../skil
35
35
  import { queryStatus, querySources, queryDataList, queryDataShow, queryDoctor, } from "./queries.js";
36
36
  import { checkForUpdate, readUpdateCheck, isNewerVersion, } from "./update-check.js";
37
37
  import { loadCredentials, saveCredentials, clearCredentials, isExpired, formatAddress, formatExpiresIn, getAuthTarget, resolvePersonalServerUrl, runDeviceCodeFlow, runSelfHostedLoginFlow, } from "./auth.js";
38
+ import { createCliTelemetrySession, flushTelemetryOutbox, getTelemetryStatus, setActiveTelemetrySession, setTelemetryEnabled, trackActiveTelemetryEvent, } from "./telemetry.js";
38
39
  function cleanDescription(desc) {
39
40
  return desc
40
41
  .replace(/ using Playwright browser automation\.?/i, ".")
@@ -53,9 +54,16 @@ export async function runCli(argv = process.argv) {
53
54
  const parsedOptions = extractGlobalOptions(normalizedArgv);
54
55
  const cliVersion = getCliVersion();
55
56
  const installMethod = getCliInstallMethod();
57
+ const telemetryBaseContext = {
58
+ cliVersion,
59
+ channel: getCliChannel(cliVersion),
60
+ installMethod,
61
+ options: parsedOptions,
62
+ };
56
63
  // Non-blocking update check — compute suppression flags early
57
64
  const shouldNotify = !parsedOptions.json &&
58
65
  process.stdout.isTTY &&
66
+ installMethod !== "development" &&
59
67
  !process.env.VANA_NO_UPDATE_NOTIFIER &&
60
68
  !process.env.CI &&
61
69
  !process.env.AGENT &&
@@ -107,17 +115,18 @@ More:
107
115
  .description("Print CLI version")
108
116
  .option("--json", "Output machine-readable JSON")
109
117
  .action(async () => {
110
- if (parsedOptions.json) {
111
- process.stdout.write(`${JSON.stringify({
112
- cliVersion,
113
- channel: getCliChannel(cliVersion),
114
- installMethod: getCliInstallMethod(),
115
- })}\n`);
116
- process.exitCode = 0;
117
- return;
118
- }
119
- process.stdout.write(`${cliVersion} (${getCliChannel(cliVersion)}, ${formatInstallMethodLabel(getCliInstallMethod()).toLowerCase()})\n`);
120
- process.exitCode = 0;
118
+ process.exitCode = await runCommandWithTelemetry({ ...telemetryBaseContext, command: "version" }, async () => {
119
+ if (parsedOptions.json) {
120
+ process.stdout.write(`${JSON.stringify({
121
+ cliVersion,
122
+ channel: getCliChannel(cliVersion),
123
+ installMethod: getCliInstallMethod(),
124
+ })}\n`);
125
+ return 0;
126
+ }
127
+ process.stdout.write(`${cliVersion} (${getCliChannel(cliVersion)}, ${formatInstallMethodLabel(getCliInstallMethod()).toLowerCase()})\n`);
128
+ return 0;
129
+ });
121
130
  });
122
131
  const connectCommand = program
123
132
  .command("connect [source]")
@@ -129,13 +138,14 @@ More:
129
138
  .option("--quiet", "Reduce non-essential output")
130
139
  .option("--detach", "Run in the background")
131
140
  .action(async (source) => {
132
- if (parsedOptions.detach && source) {
133
- process.exitCode = await runDetached("connect", source, parsedOptions);
134
- return;
135
- }
136
- process.exitCode = source
137
- ? await runConnect(source, parsedOptions)
138
- : await runConnectEntry(parsedOptions);
141
+ process.exitCode = await runCommandWithTelemetry({ ...telemetryBaseContext, command: "connect", source }, async () => {
142
+ if (parsedOptions.detach && source) {
143
+ return runDetached("connect", source, parsedOptions);
144
+ }
145
+ return source
146
+ ? runConnect(source, parsedOptions)
147
+ : runConnectEntry(parsedOptions);
148
+ });
139
149
  });
140
150
  connectCommand.addHelpText("after", `
141
151
  Examples:
@@ -149,9 +159,9 @@ Examples:
149
159
  .description("List supported sources, or show detail for one source")
150
160
  .option("--json", "Output machine-readable JSON")
151
161
  .action(async (source) => {
152
- process.exitCode = source
153
- ? await runSourceDetail(source, parsedOptions)
154
- : await runList(parsedOptions);
162
+ process.exitCode = await runCommandWithTelemetry({ ...telemetryBaseContext, command: "sources", source }, async () => source
163
+ ? runSourceDetail(source, parsedOptions)
164
+ : runList(parsedOptions));
155
165
  });
156
166
  sourcesCommand.addHelpText("after", `
157
167
  Examples:
@@ -170,13 +180,14 @@ Examples:
170
180
  .option("--detach", "Run in the background")
171
181
  .option("--all", "Collect from all connected sources")
172
182
  .action(async (source) => {
173
- if (parsedOptions.detach && source) {
174
- process.exitCode = await runDetached("collect", source, parsedOptions);
175
- return;
176
- }
177
- process.exitCode = source
178
- ? await runCollect(source, parsedOptions)
179
- : await runCollectAll(parsedOptions);
183
+ process.exitCode = await runCommandWithTelemetry({ ...telemetryBaseContext, command: "collect", source }, async () => {
184
+ if (parsedOptions.detach && source) {
185
+ return runDetached("collect", source, parsedOptions);
186
+ }
187
+ return source
188
+ ? runCollect(source, parsedOptions)
189
+ : runCollectAll(parsedOptions);
190
+ });
180
191
  });
181
192
  collectCommand.addHelpText("after", `
182
193
  Examples:
@@ -189,7 +200,7 @@ Examples:
189
200
  .description("Show runtime and Personal Server status")
190
201
  .option("--json", "Output machine-readable JSON")
191
202
  .action(async () => {
192
- process.exitCode = await runStatus(parsedOptions);
203
+ process.exitCode = await runCommandWithTelemetry({ ...telemetryBaseContext, command: "status" }, async () => runStatus(parsedOptions));
193
204
  });
194
205
  statusCommand.addHelpText("after", `
195
206
  Examples:
@@ -201,7 +212,7 @@ Examples:
201
212
  .description("Inspect local CLI, runtime, and install health")
202
213
  .option("--json", "Output machine-readable JSON")
203
214
  .action(async () => {
204
- process.exitCode = await runDoctor(parsedOptions);
215
+ process.exitCode = await runCommandWithTelemetry({ ...telemetryBaseContext, command: "doctor" }, async () => runDoctor(parsedOptions));
205
216
  });
206
217
  doctorCommand.addHelpText("after", `
207
218
  Examples:
@@ -214,7 +225,7 @@ Examples:
214
225
  .option("--json", "Output machine-readable JSON")
215
226
  .option("--yes", "Approve safe setup prompts automatically")
216
227
  .action(async () => {
217
- process.exitCode = await runSetup(parsedOptions);
228
+ process.exitCode = await runCommandWithTelemetry({ ...telemetryBaseContext, command: "setup" }, async () => runSetup(parsedOptions));
218
229
  });
219
230
  setupCommand.addHelpText("after", `
220
231
  Examples:
@@ -239,7 +250,7 @@ Examples:
239
250
  .description("List locally available collected datasets")
240
251
  .option("--json", "Output machine-readable JSON")
241
252
  .action(async () => {
242
- process.exitCode = await runDataList(parsedOptions);
253
+ process.exitCode = await runCommandWithTelemetry({ ...telemetryBaseContext, command: "data", subcommand: "list" }, async () => runDataList(parsedOptions));
243
254
  });
244
255
  dataListCommand.addHelpText("after", `
245
256
  Examples:
@@ -251,7 +262,12 @@ Examples:
251
262
  .description("Show a collected dataset")
252
263
  .option("--json", "Output machine-readable JSON")
253
264
  .action(async (source) => {
254
- process.exitCode = await runDataShow(source, parsedOptions);
265
+ process.exitCode = await runCommandWithTelemetry({
266
+ ...telemetryBaseContext,
267
+ command: "data",
268
+ subcommand: "show",
269
+ source,
270
+ }, async () => runDataShow(source, parsedOptions));
255
271
  });
256
272
  dataShowCommand.addHelpText("after", `
257
273
  Examples:
@@ -263,7 +279,12 @@ Examples:
263
279
  .description("Print the local path for a collected dataset")
264
280
  .option("--json", "Output machine-readable JSON")
265
281
  .action(async (source) => {
266
- process.exitCode = await runDataPath(source, parsedOptions);
282
+ process.exitCode = await runCommandWithTelemetry({
283
+ ...telemetryBaseContext,
284
+ command: "data",
285
+ subcommand: "path",
286
+ source,
287
+ }, async () => runDataPath(source, parsedOptions));
267
288
  });
268
289
  dataPathCommand.addHelpText("after", `
269
290
  Examples:
@@ -275,7 +296,7 @@ Examples:
275
296
  .description("Inspect stored connector run logs")
276
297
  .option("--json", "Output machine-readable JSON")
277
298
  .action(async (source) => {
278
- process.exitCode = await runLogs(source, parsedOptions);
299
+ process.exitCode = await runCommandWithTelemetry({ ...telemetryBaseContext, command: "logs", source }, async () => runLogs(source, parsedOptions));
279
300
  });
280
301
  logsCommand.addHelpText("after", `
281
302
  Examples:
@@ -295,62 +316,89 @@ Examples:
295
316
  vana server clear-url
296
317
  `);
297
318
  server.action(async () => {
298
- process.exitCode = await runServerStatus(parsedOptions);
319
+ process.exitCode = await runCommandWithTelemetry({ ...telemetryBaseContext, command: "server", subcommand: "status" }, async () => runServerStatus(parsedOptions));
299
320
  });
300
321
  server
301
322
  .command("status")
302
323
  .description("Show Personal Server status")
303
324
  .option("--json", "Output machine-readable JSON")
304
325
  .action(async () => {
305
- process.exitCode = await runServerStatus(parsedOptions);
326
+ process.exitCode = await runCommandWithTelemetry({ ...telemetryBaseContext, command: "server", subcommand: "status" }, async () => runServerStatus(parsedOptions));
306
327
  });
307
328
  server
308
329
  .command("set-url <url>")
309
330
  .description("Save a Personal Server URL")
310
331
  .option("--json", "Output machine-readable JSON")
311
332
  .action(async (url) => {
312
- process.exitCode = await runServerSetUrl(url, parsedOptions);
333
+ process.exitCode = await runCommandWithTelemetry({ ...telemetryBaseContext, command: "server", subcommand: "set-url" }, async () => runServerSetUrl(url, parsedOptions));
313
334
  });
314
335
  server
315
336
  .command("clear-url")
316
337
  .description("Remove the saved Personal Server URL")
317
338
  .option("--json", "Output machine-readable JSON")
318
339
  .action(async () => {
319
- process.exitCode = await runServerClearUrl(parsedOptions);
340
+ process.exitCode = await runCommandWithTelemetry({ ...telemetryBaseContext, command: "server", subcommand: "clear-url" }, async () => runServerClearUrl(parsedOptions));
320
341
  });
321
342
  server
322
343
  .command("sync")
323
344
  .description("Sync all local-only datasets to your Personal Server")
324
345
  .option("--json", "Output machine-readable JSON")
325
346
  .action(async () => {
326
- process.exitCode = await runServerSync(parsedOptions);
347
+ process.exitCode = await runCommandWithTelemetry({ ...telemetryBaseContext, command: "server", subcommand: "sync" }, async () => runServerSync(parsedOptions));
327
348
  });
328
349
  server
329
350
  .command("data [scope]")
330
351
  .description("List scopes stored in your Personal Server")
331
352
  .option("--json", "Output machine-readable JSON")
332
353
  .action(async (scope) => {
333
- process.exitCode = await runServerData(scope, parsedOptions);
354
+ process.exitCode = await runCommandWithTelemetry({ ...telemetryBaseContext, command: "server", subcommand: "data" }, async () => runServerData(scope, parsedOptions));
334
355
  });
335
356
  program
336
357
  .command("login")
337
358
  .description("Log in to your Vana account or a self-hosted Personal Server")
338
359
  .option("-s, --server <url>", "Self-hosted Personal Server URL")
339
360
  .action(async (loginOptions) => {
340
- process.exitCode = await runLogin(parsedOptions, loginOptions.server);
361
+ process.exitCode = await runCommandWithTelemetry({ ...telemetryBaseContext, command: "login" }, async () => runLogin(parsedOptions, loginOptions.server));
341
362
  });
342
363
  program
343
364
  .command("logout")
344
365
  .description("Log out and remove saved credentials")
345
366
  .action(async () => {
346
- process.exitCode = await runLogout(parsedOptions);
367
+ process.exitCode = await runCommandWithTelemetry({ ...telemetryBaseContext, command: "logout" }, async () => runLogout(parsedOptions));
368
+ });
369
+ const telemetry = program
370
+ .command("telemetry")
371
+ .description("Inspect and manage CLI telemetry");
372
+ telemetry.action(async () => {
373
+ process.exitCode = await runTelemetryStatus(parsedOptions);
374
+ });
375
+ telemetry
376
+ .command("status")
377
+ .description("Show telemetry state")
378
+ .option("--json", "Output machine-readable JSON")
379
+ .action(async () => {
380
+ process.exitCode = await runTelemetryStatus(parsedOptions);
381
+ });
382
+ telemetry
383
+ .command("enable")
384
+ .description("Enable telemetry")
385
+ .action(async () => {
386
+ process.exitCode = await runTelemetryEnable(parsedOptions);
387
+ });
388
+ telemetry
389
+ .command("disable")
390
+ .description("Disable telemetry")
391
+ .action(async () => {
392
+ process.exitCode = await runTelemetryDisable(parsedOptions);
347
393
  });
348
394
  program
349
395
  .command("mcp")
350
396
  .description("Start MCP server for agent integration")
351
397
  .action(async () => {
352
- const { startMcpServer } = await import("./mcp-server.js");
353
- await startMcpServer();
398
+ process.exitCode = await runLongRunningCommandWithTelemetry({ ...telemetryBaseContext, command: "mcp" }, async () => {
399
+ const { startMcpServer } = await import("./mcp-server.js");
400
+ await startMcpServer();
401
+ });
354
402
  });
355
403
  const skill = program
356
404
  .command("skills")
@@ -363,26 +411,26 @@ Examples:
363
411
  vana skills show connect-data
364
412
  `);
365
413
  skill.action(async () => {
366
- process.exitCode = await runSkillsGuidedPicker(parsedOptions);
414
+ process.exitCode = await runCommandWithTelemetry({ ...telemetryBaseContext, command: "skills" }, async () => runSkillsGuidedPicker(parsedOptions));
367
415
  });
368
416
  skill
369
417
  .command("list")
370
418
  .description("List available agent skills")
371
419
  .option("--json", "Output as JSON")
372
420
  .action(async () => {
373
- process.exitCode = await runSkillList(parsedOptions);
421
+ process.exitCode = await runCommandWithTelemetry({ ...telemetryBaseContext, command: "skills", subcommand: "list" }, async () => runSkillList(parsedOptions));
374
422
  });
375
423
  skill
376
424
  .command("install <name>")
377
425
  .description("Install a skill for your agent")
378
426
  .action(async (name) => {
379
- process.exitCode = await runSkillInstall(name, parsedOptions);
427
+ process.exitCode = await runCommandWithTelemetry({ ...telemetryBaseContext, command: "skills", subcommand: "install" }, async () => runSkillInstall(name, parsedOptions));
380
428
  });
381
429
  skill
382
430
  .command("show <name>")
383
431
  .description("Show skill details")
384
432
  .action(async (name) => {
385
- process.exitCode = await runSkillShow(name, parsedOptions);
433
+ process.exitCode = await runCommandWithTelemetry({ ...telemetryBaseContext, command: "skills", subcommand: "show" }, async () => runSkillShow(name, parsedOptions));
386
434
  });
387
435
  // --- Schedule commands ---
388
436
  const schedule = program
@@ -404,20 +452,20 @@ Examples:
404
452
  .description("Add a scheduled collection")
405
453
  .option("--every <interval>", "Collection interval (e.g. 24h, 12h, 1h)", "24h")
406
454
  .action(async (opts) => {
407
- process.exitCode = await runScheduleAdd(opts.every, parsedOptions);
455
+ process.exitCode = await runCommandWithTelemetry({ ...telemetryBaseContext, command: "schedule", subcommand: "add" }, async () => runScheduleAdd(opts.every, parsedOptions));
408
456
  });
409
457
  schedule
410
458
  .command("list")
411
459
  .description("Show scheduled tasks")
412
460
  .option("--json", "Output machine-readable JSON")
413
461
  .action(async () => {
414
- process.exitCode = await runScheduleList(parsedOptions);
462
+ process.exitCode = await runCommandWithTelemetry({ ...telemetryBaseContext, command: "schedule", subcommand: "list" }, async () => runScheduleList(parsedOptions));
415
463
  });
416
464
  schedule
417
465
  .command("remove")
418
466
  .description("Remove the scheduled collection")
419
467
  .action(async () => {
420
- process.exitCode = await runScheduleRemove(parsedOptions);
468
+ process.exitCode = await runCommandWithTelemetry({ ...telemetryBaseContext, command: "schedule", subcommand: "remove" }, async () => runScheduleRemove(parsedOptions));
421
469
  });
422
470
  try {
423
471
  await program.parseAsync(normalizedArgv);
@@ -456,6 +504,139 @@ Examples:
456
504
  }
457
505
  return Number(process.exitCode ?? 0);
458
506
  }
507
+ function classifyCommandFailure(error) {
508
+ if (error instanceof Error) {
509
+ const value = error.message.toLowerCase();
510
+ if (value.includes("auth"))
511
+ return "auth_failed";
512
+ if (value.includes("setup"))
513
+ return "setup_required";
514
+ if (value.includes("runtime"))
515
+ return "runtime_error";
516
+ if (value.includes("connector"))
517
+ return "connector_unavailable";
518
+ if (value.includes("ingest"))
519
+ return "ingest_failed";
520
+ }
521
+ return "unknown";
522
+ }
523
+ async function runCommandWithTelemetry(context, action) {
524
+ const session = await createCliTelemetrySession({
525
+ ...context,
526
+ options: {
527
+ json: Boolean(context.options.json),
528
+ noInput: Boolean(context.options.noInput),
529
+ quiet: Boolean(context.options.quiet),
530
+ detach: Boolean(context.options.detach),
531
+ ipc: Boolean(context.options.ipc),
532
+ },
533
+ localOnly: context.localOnly,
534
+ });
535
+ setActiveTelemetrySession(session);
536
+ await flushTelemetryOutbox();
537
+ try {
538
+ const exitCode = await action();
539
+ session.markCommandResult({ exitCode });
540
+ return exitCode;
541
+ }
542
+ catch (error) {
543
+ session.markCommandResult({
544
+ exitCode: 1,
545
+ errorClass: classifyCommandFailure(error),
546
+ });
547
+ throw error;
548
+ }
549
+ finally {
550
+ await session.persist();
551
+ await session.flush();
552
+ setActiveTelemetrySession(null);
553
+ }
554
+ }
555
+ async function runLongRunningCommandWithTelemetry(context, action) {
556
+ const session = await createCliTelemetrySession({
557
+ ...context,
558
+ options: {
559
+ json: Boolean(context.options.json),
560
+ noInput: Boolean(context.options.noInput),
561
+ quiet: Boolean(context.options.quiet),
562
+ detach: Boolean(context.options.detach),
563
+ ipc: Boolean(context.options.ipc),
564
+ },
565
+ });
566
+ setActiveTelemetrySession(session);
567
+ await flushTelemetryOutbox();
568
+ session.trackCustomEvent("mcp_started");
569
+ session.markCommandResult({ exitCode: 0, outcome: "started" });
570
+ await session.persist();
571
+ await session.flush();
572
+ try {
573
+ await action();
574
+ return 0;
575
+ }
576
+ finally {
577
+ setActiveTelemetrySession(null);
578
+ }
579
+ }
580
+ async function runTelemetryStatus(options) {
581
+ const status = await getTelemetryStatus();
582
+ const endpointHost = (() => {
583
+ try {
584
+ return new URL(status.endpoint).host;
585
+ }
586
+ catch {
587
+ return status.endpoint;
588
+ }
589
+ })();
590
+ if (options.json) {
591
+ process.stdout.write(`${JSON.stringify(status)}\n`);
592
+ return 0;
593
+ }
594
+ const emit = createEmitter(options);
595
+ emit.title("Telemetry");
596
+ emit.blank();
597
+ emit.keyValue("Enabled", status.enabled ? "yes" : "no");
598
+ emit.keyValue("Mode", status.mode);
599
+ emit.keyValue("Reason", status.reason.replaceAll("_", " "));
600
+ emit.keyValue("Endpoint", endpointHost);
601
+ emit.keyValue("Queued", String(status.queuedBatches));
602
+ emit.detail("Collected data stays local. Remote telemetry only includes small operational events.");
603
+ if (status.enabled) {
604
+ emit.detail(`Disable with: ${emit.code("vana telemetry disable")}`);
605
+ }
606
+ else {
607
+ emit.detail(`Enable with: ${emit.code("vana telemetry enable")}`);
608
+ }
609
+ if (process.env.VANA_TELEMETRY_DEBUG === "1") {
610
+ emit.detail(`Debug mode is active via ${emit.code("VANA_TELEMETRY_DEBUG=1")}. Events print to stderr and are not uploaded.`);
611
+ }
612
+ if (process.env.VANA_TELEMETRY_DISABLED === "1") {
613
+ emit.detail(`Telemetry is currently overridden by ${emit.code("VANA_TELEMETRY_DISABLED=1")}.`);
614
+ }
615
+ return 0;
616
+ }
617
+ async function runTelemetryEnable(options) {
618
+ await setTelemetryEnabled(true);
619
+ if (options.json) {
620
+ process.stdout.write(`${JSON.stringify({ enabled: true })}\n`);
621
+ return 0;
622
+ }
623
+ const emit = createEmitter(options);
624
+ emit.success("Telemetry enabled.");
625
+ if (process.env.VANA_TELEMETRY_DISABLED === "1") {
626
+ emit.detail(`The current shell still disables uploads via ${emit.code("VANA_TELEMETRY_DISABLED=1")}.`);
627
+ }
628
+ return 0;
629
+ }
630
+ async function runTelemetryDisable(options) {
631
+ await setTelemetryEnabled(false);
632
+ if (options.json) {
633
+ process.stdout.write(`${JSON.stringify({ enabled: false })}\n`);
634
+ return 0;
635
+ }
636
+ const emit = createEmitter(options);
637
+ emit.success("Telemetry disabled.");
638
+ return 0;
639
+ }
459
640
  async function runConnect(rawSource, options) {
460
641
  const source = rawSource.toLowerCase();
461
642
  const runtime = new ManagedPlaywrightRuntime();
@@ -509,7 +690,18 @@ async function runConnect(rawSource, options) {
509
690
  }
510
691
  process.stderr.write("\n");
511
692
  }
512
- const installResult = await runtime.ensureInstalled(Boolean(options.yes));
693
+ trackActiveTelemetryEvent("runtime_install_started", { source });
694
+ let installResult;
695
+ try {
696
+ installResult = await runtime.ensureInstalled(Boolean(options.yes));
697
+ }
698
+ catch (error) {
699
+ trackActiveTelemetryEvent("runtime_install_failed", {
700
+ source,
701
+ errorClass: classifyCommandFailure(error),
702
+ });
703
+ throw error;
704
+ }
513
705
  setupLogPath = installResult.logPath;
514
706
  emit.event({
515
707
  type: "setup-complete",
@@ -598,6 +790,13 @@ async function runConnect(rawSource, options) {
598
790
  }
599
791
  }
600
792
  if (fetched.updated && fetched.previousVersion) {
793
+ trackActiveTelemetryEvent("connector_update_applied", {
794
+ source,
795
+ metadata: {
796
+ previousVersion: fetched.previousVersion,
797
+ connectorVersion: fetched.version,
798
+ },
799
+ });
601
800
  renderer?.detail(`Updated connector (${fetched.previousVersion} → ${fetched.version}).`);
602
801
  }
603
802
  fetchLogPath = fetched.logPath;
@@ -1217,7 +1416,7 @@ async function runStatus(options) {
1217
1416
  // Auth state
1218
1417
  const authCreds = loadCredentials();
1219
1418
  if (authCreds && !isExpired(authCreds)) {
1220
- emit.keyValue("Account", formatAddress(authCreds.account.address), "success");
1419
+ emit.keyValue("Account", authCreds.account.address, "success");
1221
1420
  emit.keyValue("Auth", `Authenticated (expires in ${formatExpiresIn(authCreds.account.expires_at)})`, "success");
1222
1421
  }
1223
1422
  else {
@@ -2195,7 +2394,11 @@ async function runCollectAll(options) {
2195
2394
  async function runServerSync(options) {
2196
2395
  const emit = createEmitter(options);
2197
2396
  const target = await detectPersonalServerTarget();
2397
+ trackActiveTelemetryEvent("server_sync_started");
2198
2398
  if (target.state !== "available") {
2399
+ trackActiveTelemetryEvent("server_sync_failed", {
2400
+ errorClass: "personal_server_unavailable",
2401
+ });
2199
2402
  if (options.json) {
2200
2403
  process.stdout.write(`${JSON.stringify({
2201
2404
  error: "personal_server_unavailable",
@@ -2208,7 +2411,16 @@ async function runServerSync(options) {
2208
2411
  return 1;
2209
2412
  }
2210
2413
  const syncResult = await syncPendingSources(target, "manual");
2414
+ const storedScopeCount = syncResult.sourceResults.reduce((total, entry) => total +
2415
+ (entry.scopeResults?.filter((scopeResult) => scopeResult.status === "stored").length ?? 0), 0);
2416
+ const failedScopeCount = syncResult.sourceResults.reduce((total, entry) => total +
2417
+ (entry.scopeResults?.filter((scopeResult) => scopeResult.status === "failed").length ?? 0), 0);
2211
2418
  if (syncResult.sourceResults.length === 0) {
2419
+ trackActiveTelemetryEvent("server_sync_completed", {
2420
+ storedScopeCount: 0,
2421
+ failedScopeCount: 0,
2422
+ metadata: { syncedSources: 0 },
2423
+ });
2212
2424
  if (options.json) {
2213
2425
  process.stdout.write(`${JSON.stringify({ message: "No pending datasets to sync.", syncedCount: 0 })}\n`);
2214
2426
  }
@@ -2231,7 +2443,7 @@ async function runServerSync(options) {
2231
2443
  emit.info(` ${renderer.theme.success("\u2713")} ${sr.scope}`);
2232
2444
  }
2233
2445
  else {
2234
- const errDetail = sr.error ?? "failed";
2446
+ const errDetail = sr.error ? humanizeIssue(sr.error) : "Failed";
2235
2447
  emit.info(` ${renderer.theme.error("\u2717")} ${sr.scope} ${renderer.theme.muted(`\u2014 ${errDetail}`)}`);
2236
2448
  }
2237
2449
  }
@@ -2249,6 +2461,11 @@ async function runServerSync(options) {
2249
2461
  emit.next("vana server sync");
2250
2462
  }
2251
2463
  }
2464
+ trackActiveTelemetryEvent("server_sync_completed", {
2465
+ storedScopeCount,
2466
+ failedScopeCount,
2467
+ metadata: { syncedSources: syncResult.syncedCount },
2468
+ });
2252
2469
  return 0;
2253
2470
  }
2254
2471
  async function runServerData(scope, options) {
@@ -3559,6 +3776,7 @@ async function runDetached(command, source, options) {
3559
3776
  stdio: ["ignore", logFd, logFd],
3560
3777
  env: { ...process.env, VANA_DETACHED: "1" },
3561
3778
  });
3779
+ trackActiveTelemetryEvent("detached_run_spawned", { source });
3562
3780
  child.unref();
3563
3781
  fs.closeSync(logFd);
3564
3782
  // Write session file
@@ -3763,6 +3981,9 @@ async function runScheduleAdd(interval, options) {
3763
3981
  emit.detail(`launchctl load "${LAUNCHD_PLIST_PATH}"`);
3764
3982
  return 1;
3765
3983
  }
3984
+ trackActiveTelemetryEvent("schedule_added", {
3985
+ metadata: { interval: intervalLabel, mechanism: "launchd" },
3986
+ });
3766
3987
  if (options.json) {
3767
3988
  process.stdout.write(`${JSON.stringify({ ok: true, interval: intervalLabel, mechanism: "launchd", plistPath: LAUNCHD_PLIST_PATH })}\n`);
3768
3989
  return 0;
@@ -3803,6 +4024,9 @@ async function runScheduleAdd(interval, options) {
3803
4024
  emit.detail(entry);
3804
4025
  return 1;
3805
4026
  }
4027
+ trackActiveTelemetryEvent("schedule_added", {
4028
+ metadata: { interval: intervalLabel, mechanism: "cron" },
4029
+ });
3806
4030
  if (options.json) {
3807
4031
  process.stdout.write(`${JSON.stringify({ ok: true, interval: intervalLabel, mechanism: "cron" })}\n`);
3808
4032
  return 0;
@@ -3844,6 +4068,9 @@ async function runScheduleAdd(interval, options) {
3844
4068
  emit.detail(`schtasks /Create /TN "${WINDOWS_TASK_NAME}" /TR "${trCmd}" /SC DAILY /ST 09:00 /F`);
3845
4069
  return 1;
3846
4070
  }
4071
+ trackActiveTelemetryEvent("schedule_added", {
4072
+ metadata: { interval: intervalLabel, mechanism: "schtasks" },
4073
+ });
3847
4074
  if (options.json) {
3848
4075
  process.stdout.write(`${JSON.stringify({ ok: true, interval: intervalLabel, mechanism: "schtasks" })}\n`);
3849
4076
  return 0;
@@ -3961,6 +4188,9 @@ async function runScheduleRemove(options) {
3961
4188
  // Already unloaded
3962
4189
  }
3963
4190
  await fsp.unlink(LAUNCHD_PLIST_PATH);
4191
+ trackActiveTelemetryEvent("schedule_removed", {
4192
+ metadata: { mechanism: "launchd" },
4193
+ });
3964
4194
  if (options.json) {
3965
4195
  process.stdout.write(`${JSON.stringify({ ok: true, removed: true })}\n`);
3966
4196
  return 0;
@@ -3986,6 +4216,9 @@ async function runScheduleRemove(options) {
3986
4216
  input: `${filtered.trimEnd()}\n`,
3987
4217
  encoding: "utf8",
3988
4218
  });
4219
+ trackActiveTelemetryEvent("schedule_removed", {
4220
+ metadata: { mechanism: "cron" },
4221
+ });
3989
4222
  if (options.json) {
3990
4223
  process.stdout.write(`${JSON.stringify({ ok: true, removed: true })}\n`);
3991
4224
  return 0;
@@ -4003,6 +4236,9 @@ async function runScheduleRemove(options) {
4003
4236
  execSync(`schtasks /Delete /TN "${WINDOWS_TASK_NAME}" /F`, {
4004
4237
  stdio: "ignore",
4005
4238
  });
4239
+ trackActiveTelemetryEvent("schedule_removed", {
4240
+ metadata: { mechanism: "schtasks" },
4241
+ });
4006
4242
  if (options.json) {
4007
4243
  process.stdout.write(`${JSON.stringify({ ok: true, removed: true })}\n`);
4008
4244
  return 0;
@@ -4157,6 +4393,9 @@ async function runSkillInstall(name, options) {
4157
4393
  const emit = createEmitter(options);
4158
4394
  try {
4159
4395
  const { installedPath } = await installSkill(name);
4396
+ trackActiveTelemetryEvent("skill_installed", {
4397
+ metadata: { skillName: name },
4398
+ });
4160
4399
  if (options.json) {
4161
4400
  process.stdout.write(`${JSON.stringify({ ok: true, id: name, installedPath })}\n`);
4162
4401
  return 0;
@@ -4233,11 +4472,14 @@ async function runLogin(options, serverUrl) {
4233
4472
  // If self-hosted, use /auth/device flow against the PS
4234
4473
  if (authTarget === "self-hosted" && psUrl) {
4235
4474
  const renderer = !options.json && !options.quiet ? createLoginRenderer() : null;
4475
+ const humanRenderer = createHumanRenderer();
4236
4476
  renderer?.title(psUrl);
4237
4477
  try {
4238
4478
  const result = await runSelfHostedLoginFlow(psUrl, (url) => {
4239
- renderer?.note("Open this URL in your browser:");
4240
- renderer?.note(url);
4479
+ if (!options.json && !options.quiet) {
4480
+ process.stderr.write(` ${humanRenderer.theme.muted("Open this URL in your browser:")}\n`);
4481
+ process.stderr.write(` ${humanRenderer.theme.muted(url)}\n`);
4482
+ }
4241
4483
  renderer?.scopeActive("Waiting for authorization");
4242
4484
  // Try to open browser — use spawn with args array to prevent shell injection
4243
4485
  // (a malicious self-hosted PS could return a URL with shell metacharacters)
@@ -4353,12 +4595,23 @@ async function runLogin(options, serverUrl) {
4353
4595
  }
4354
4596
  // Interactive mode
4355
4597
  const renderer = createLoginRenderer();
4598
+ const humanRenderer = createHumanRenderer();
4599
+ const writeStaticLoginNotes = (lines) => {
4600
+ if (options.json || options.quiet) {
4601
+ return;
4602
+ }
4603
+ for (const line of lines) {
4604
+ process.stderr.write(` ${humanRenderer.theme.muted(line)}\n`);
4605
+ }
4606
+ };
4356
4607
  renderer.title("Vana");
4357
4608
  const creds = await runDeviceCodeFlow({
4358
4609
  onCode: (code, uri) => {
4359
- renderer.note("Open this URL in your browser:");
4360
- renderer.note(uri);
4361
- renderer.note(`Enter this code: ${code}`);
4610
+ writeStaticLoginNotes([
4611
+ "Open this URL in your browser:",
4612
+ uri,
4613
+ `Enter this code: ${code}`,
4614
+ ]);
4362
4615
  },
4363
4616
  onWaiting: () => {
4364
4617
  renderer.scopeActive("Waiting for authorization");