@dosu/cli 0.43.0 → 0.45.0

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 (3) hide show
  1. package/README.md +38 -0
  2. package/bin/dosu.js +1777 -327
  3. package/package.json +6 -1
package/bin/dosu.js CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+
2
3
  import { createRequire } from "node:module";
3
4
  var __create = Object.create;
4
5
  var __getProtoOf = Object.getPrototypeOf;
@@ -2113,6 +2114,9 @@ function getAccessTokenClaim(accessToken, claim) {
2113
2114
  function getAccessTokenUserID(accessToken) {
2114
2115
  return getAccessTokenClaim(accessToken, "sub");
2115
2116
  }
2117
+ function getAccessTokenEmail(accessToken) {
2118
+ return getAccessTokenClaim(accessToken, "email");
2119
+ }
2116
2120
  function getAccessTokenOAuthClientID(accessToken) {
2117
2121
  return getAccessTokenClaim(accessToken, "client_id");
2118
2122
  }
@@ -2219,15 +2223,22 @@ function loadConfig() {
2219
2223
  } catch {
2220
2224
  return emptyConfig();
2221
2225
  }
2226
+ const parsed = parseConfig(raw);
2222
2227
  if (isConfigV2(raw))
2223
- return normalizeV2(raw);
2228
+ return parsed;
2224
2229
  if (isRecord2(raw) && "schema_version" in raw)
2225
- return emptyConfig();
2226
- const migrated = migrateLegacyConfig(raw);
2230
+ return parsed;
2227
2231
  try {
2228
- writeConfig(path2, migrated);
2232
+ writeConfig(path2, parsed);
2229
2233
  } catch {}
2230
- return migrated;
2234
+ return parsed;
2235
+ }
2236
+ function parseConfig(raw) {
2237
+ if (isConfigV2(raw))
2238
+ return normalizeV2(raw);
2239
+ if (isRecord2(raw) && "schema_version" in raw)
2240
+ return emptyConfig();
2241
+ return migrateLegacyConfig(raw);
2231
2242
  }
2232
2243
  function saveConfig(cfg) {
2233
2244
  writeConfig(getConfigPath(), cfg);
@@ -4756,10 +4767,13 @@ var init_dist4 = __esm(() => {
4756
4767
  });
4757
4768
 
4758
4769
  // src/version/version.ts
4770
+ function isNpxInvocation(channel = INSTALL_CHANNEL, env = process.env) {
4771
+ return channel === "npm" && (env.npm_lifecycle_event === "npx" || env.npm_command === "exec");
4772
+ }
4759
4773
  function getVersionString() {
4760
4774
  return `v${VERSION}`;
4761
4775
  }
4762
- var VERSION = "0.43.0", INSTALL_CHANNEL = "npm";
4776
+ var VERSION = "0.45.0", INSTALL_CHANNEL = "npm";
4763
4777
 
4764
4778
  // src/debug/logger.ts
4765
4779
  import {
@@ -8177,7 +8191,6 @@ var exports_prompts = {};
8177
8191
  __export(exports_prompts, {
8178
8192
  buildReadyEnvelope: () => buildReadyEnvelope,
8179
8193
  STOP_PREFIX: () => STOP_PREFIX,
8180
- SAVE_NUDGE: () => SAVE_NUDGE,
8181
8194
  LOOKUP_STARTED_NOTE: () => LOOKUP_STARTED_NOTE,
8182
8195
  LATE_RESULT_NOTE: () => LATE_RESULT_NOTE
8183
8196
  });
@@ -9059,8 +9072,7 @@ __export(exports_codex, {
9059
9072
  removeCodexHooks: () => removeCodexHooks,
9060
9073
  installCodexHooks: () => installCodexHooks,
9061
9074
  inspectCodexHooks: () => inspectCodexHooks,
9062
- codexHooksPath: () => codexHooksPath,
9063
- codexHookCommand: () => codexHookCommand
9075
+ codexHooksPath: () => codexHooksPath
9064
9076
  });
9065
9077
  import { existsSync as existsSync9, readFileSync as readFileSync9 } from "node:fs";
9066
9078
  import { join as join8 } from "node:path";
@@ -9189,8 +9201,7 @@ __export(exports_factory, {
9189
9201
  removeFactoryHooks: () => removeFactoryHooks,
9190
9202
  installFactoryHooks: () => installFactoryHooks,
9191
9203
  inspectFactoryHooks: () => inspectFactoryHooks,
9192
- factoryHooksPath: () => factoryHooksPath,
9193
- factoryHookCommand: () => factoryHookCommand
9204
+ factoryHooksPath: () => factoryHooksPath
9194
9205
  });
9195
9206
  import { existsSync as existsSync10, readFileSync as readFileSync10 } from "node:fs";
9196
9207
  import { join as join9 } from "node:path";
@@ -10386,6 +10397,34 @@ var init_insights2 = __esm(() => {
10386
10397
  init_insights();
10387
10398
  });
10388
10399
 
10400
+ // src/auth/errors.ts
10401
+ var exports_errors = {};
10402
+ __export(exports_errors, {
10403
+ OAuthCallbackError: () => OAuthCallbackError
10404
+ });
10405
+ var OAuthCallbackError;
10406
+ var init_errors = __esm(() => {
10407
+ OAuthCallbackError = class OAuthCallbackError extends Error {
10408
+ error;
10409
+ errorCode;
10410
+ errorDescription;
10411
+ constructor(message, details = {}) {
10412
+ super(message);
10413
+ this.name = "OAuthCallbackError";
10414
+ this.error = details.error;
10415
+ this.errorCode = details.errorCode;
10416
+ this.errorDescription = details.errorDescription;
10417
+ }
10418
+ get userMessage() {
10419
+ const desc = this.errorDescription ?? this.message;
10420
+ if (this.errorCode === "bad_oauth_state" || /state/i.test(desc)) {
10421
+ return `Authentication failed: ${desc}. Run \`dosu login\` again.`;
10422
+ }
10423
+ return `Authentication failed: ${desc}`;
10424
+ }
10425
+ };
10426
+ });
10427
+
10389
10428
  // src/version/skill-update-check.ts
10390
10429
  import { existsSync as existsSync12, mkdirSync as mkdirSync7, readFileSync as readFileSync12, writeFileSync as writeFileSync6 } from "node:fs";
10391
10430
  import { join as join10 } from "node:path";
@@ -11428,7 +11467,7 @@ When \`write_knowledge\` is listed, use it after the task for durable, non-obvio
11428
11467
  Use \`review_knowledge\` only when the user asks to inspect or manage pending knowledge. Preview one item at a time and require explicit confirmation before making changes.
11429
11468
 
11430
11469
  When \`read_knowledge\` or \`write_knowledge\` returned a \`receipt_item_id\` this turn, call \`finalize_session_knowledge\` exactly once at the end of the turn — after completing the task, immediately before your final reply — passing all receipt_item_ids from this turn. Never call it when the current turn produced no receipt_item_id, and never call it more than once per turn.
11431
- `, RULE_TARGETS, SUPPORTED_RULE_AGENT_IDS;
11470
+ `, RULE_TARGETS;
11432
11471
  var init_installer = __esm(() => {
11433
11472
  DOSU_RULE_SECTION_START = `<!-- dosu:rules:start v${DOSU_RULE_SECTION_VERSION} -->`;
11434
11473
  DOSU_RULE_SECTION_START_RE = /<!-- dosu:rules:start(?: v\d+)? -->/;
@@ -11463,7 +11502,6 @@ var init_installer = __esm(() => {
11463
11502
  path: () => join27(homedir4(), ".gemini", "GEMINI.md")
11464
11503
  }
11465
11504
  };
11466
- SUPPORTED_RULE_AGENT_IDS = Object.keys(RULE_TARGETS);
11467
11505
  });
11468
11506
 
11469
11507
  // src/setup/agents-md-step.ts
@@ -11554,206 +11592,1195 @@ var init_agents_md_step = __esm(() => {
11554
11592
  SECTION_START_RE = /<!-- dosu:mcp:start(?: v(\d+))? -->/;
11555
11593
  });
11556
11594
 
11557
- // src/setup/analytics.ts
11558
- async function trackCliOnboardingEvent(cfg, onboardingRunID, event, properties = {}) {
11559
- if (!cfg.active_account?.session.access_token)
11560
- return;
11561
- try {
11562
- const trpc = createTypedClient(cfg);
11563
- await withTimeout(trpc.user.trackCliOnboardingEvent.mutate({
11564
- event,
11565
- properties: {
11566
- ...baseProperties(cfg),
11567
- onboarding_run_id: onboardingRunID,
11568
- ...properties
11569
- }
11570
- }), TRACKING_TIMEOUT_MS);
11571
- } catch (err) {
11572
- const msg = err instanceof Error ? err.message : String(err);
11573
- logger.debug("setup", `CLI onboarding analytics failed: ${event}: ${msg}`);
11574
- }
11595
+ // src/telemetry/settings.ts
11596
+ import { randomUUID } from "node:crypto";
11597
+ import {
11598
+ existsSync as existsSync18,
11599
+ lstatSync,
11600
+ mkdirSync as mkdirSync9,
11601
+ readFileSync as readFileSync16,
11602
+ renameSync as renameSync3,
11603
+ unlinkSync as unlinkSync2,
11604
+ writeFileSync as writeFileSync9
11605
+ } from "node:fs";
11606
+ import { join as join29 } from "node:path";
11607
+ function getTelemetrySettingsPath() {
11608
+ return join29(getConfigDir(), TELEMETRY_SETTINGS_FILENAME);
11575
11609
  }
11576
- async function trackCliOnboardingPreAuthEvent(onboardingRunID, event, properties = {}) {
11577
- try {
11578
- const trpc = createAnonymousClient();
11579
- await withTimeout(trpc.user.trackCliOnboardingPreAuthEvent.mutate({
11580
- event,
11581
- onboarding_run_id: onboardingRunID,
11582
- properties: {
11583
- ...baseProperties(emptyConfig()),
11584
- ...properties
11585
- }
11586
- }), TRACKING_TIMEOUT_MS);
11587
- } catch (err) {
11588
- const msg = err instanceof Error ? err.message : String(err);
11589
- logger.debug("setup", `CLI onboarding pre-auth analytics failed: ${event}: ${msg}`);
11590
- }
11610
+ function loadTelemetrySettings() {
11611
+ return readTelemetrySettings().settings;
11591
11612
  }
11592
- function baseProperties(cfg) {
11593
- return {
11594
- cli_version: VERSION,
11595
- platform: process.platform,
11596
- arch: process.arch,
11597
- mode: cfg.mode ?? "cloud",
11598
- org_id: cfg.active_account?.target?.org_id,
11599
- deployment_id: cfg.active_account?.target?.deployment_id,
11600
- space_id: cfg.active_account?.target?.space_id
11601
- };
11613
+ function setTelemetryEnabled(enabled) {
11614
+ if (typeof enabled !== "boolean")
11615
+ return false;
11616
+ const current = readTelemetrySettings();
11617
+ if (current.status === "unsupported" || current.status === "error")
11618
+ return false;
11619
+ const settings2 = current.status === "current" ? current.settings : emptyTelemetrySettings();
11620
+ if (enabled)
11621
+ delete settings2.disabled;
11622
+ else
11623
+ settings2.disabled = true;
11624
+ return writeTelemetrySettings(settings2);
11602
11625
  }
11603
- function createAnonymousClient() {
11604
- const webAppURL = getWebAppURL();
11605
- if (!webAppURL) {
11606
- throw new Error("Web app URL not configured");
11626
+ function telemetryDisabledByEnvironment() {
11627
+ if (isEnabledEnvironmentFlag(process.env.DO_NOT_TRACK))
11628
+ return "DO_NOT_TRACK";
11629
+ if (isEnabledEnvironmentFlag(process.env.DOSU_TELEMETRY_DISABLED)) {
11630
+ return "DOSU_TELEMETRY_DISABLED";
11607
11631
  }
11608
- return createTRPCClient({
11609
- links: [
11610
- httpLink({
11611
- url: `${webAppURL}/api/cli-trpc`,
11612
- transformer: dist_default,
11613
- headers: {
11614
- "x-dosu-cli-contract": CLI_CONTRACT_HASH
11615
- }
11616
- })
11617
- ]
11618
- });
11632
+ return;
11619
11633
  }
11620
- async function withTimeout(promise, timeoutMs) {
11621
- let timeout;
11634
+ function isTelemetryEnabled(settings2 = loadTelemetrySettings()) {
11635
+ if (telemetryDisabledByEnvironment() !== undefined)
11636
+ return false;
11637
+ return settings2.disabled !== true;
11638
+ }
11639
+ function getOrCreateInstallID() {
11640
+ const current = readTelemetrySettings();
11641
+ if (current.status === "current" && current.settings.install_id) {
11642
+ return current.settings.install_id;
11643
+ }
11644
+ if (current.status !== "current" && current.status !== "missing")
11645
+ return;
11646
+ const installID = randomUUID();
11647
+ const settings2 = current.status === "current" ? current.settings : emptyTelemetrySettings();
11648
+ settings2.install_id = installID;
11649
+ return writeTelemetrySettings(settings2) ? installID : undefined;
11650
+ }
11651
+ function resetInstallID() {
11652
+ const current = readTelemetrySettings();
11653
+ if (current.status !== "current" && current.status !== "missing")
11654
+ return;
11655
+ const settings2 = current.status === "current" ? current.settings : emptyTelemetrySettings();
11656
+ const installID = randomUUID();
11657
+ settings2.install_id = installID;
11658
+ return writeTelemetrySettings(settings2) ? installID : undefined;
11659
+ }
11660
+ function readTelemetrySettings() {
11661
+ const path3 = getTelemetrySettingsPath();
11662
+ let file;
11622
11663
  try {
11623
- return await Promise.race([
11624
- promise,
11625
- new Promise((_2, reject) => {
11626
- timeout = setTimeout(() => reject(new Error("tracking timeout")), timeoutMs);
11627
- })
11628
- ]);
11629
- } finally {
11630
- if (timeout)
11631
- clearTimeout(timeout);
11664
+ file = lstatSync(path3);
11665
+ } catch (error) {
11666
+ if (isErrorCode(error, "ENOENT")) {
11667
+ return { status: "missing", settings: emptyTelemetrySettings() };
11668
+ }
11669
+ return { status: "error", settings: disabledTelemetrySettings() };
11632
11670
  }
11671
+ if (!file.isFile() || file.size > MAX_TELEMETRY_SETTINGS_BYTES) {
11672
+ return { status: "invalid", settings: disabledTelemetrySettings() };
11673
+ }
11674
+ let content;
11675
+ try {
11676
+ content = readFileSync16(path3, "utf-8");
11677
+ } catch {
11678
+ return { status: "error", settings: disabledTelemetrySettings() };
11679
+ }
11680
+ let raw;
11681
+ try {
11682
+ raw = JSON.parse(content);
11683
+ } catch {
11684
+ return { status: "invalid", settings: disabledTelemetrySettings() };
11685
+ }
11686
+ if (!isRecord3(raw))
11687
+ return { status: "invalid", settings: disabledTelemetrySettings() };
11688
+ if (raw.schema_version !== TELEMETRY_SCHEMA_VERSION) {
11689
+ const status = "schema_version" in raw ? "unsupported" : "invalid";
11690
+ return { status, settings: disabledTelemetrySettings() };
11691
+ }
11692
+ if ("disabled" in raw && typeof raw.disabled !== "boolean") {
11693
+ return { status: "invalid", settings: disabledTelemetrySettings() };
11694
+ }
11695
+ const settings2 = emptyTelemetrySettings();
11696
+ if (raw.disabled === true)
11697
+ settings2.disabled = true;
11698
+ if (typeof raw.install_id === "string" && isUUID(raw.install_id)) {
11699
+ settings2.install_id = raw.install_id;
11700
+ }
11701
+ return { status: "current", settings: settings2 };
11633
11702
  }
11634
- var TRACKING_TIMEOUT_MS = 1500;
11635
- var init_analytics = __esm(() => {
11636
- init_dist();
11637
- init_dist4();
11638
- init_trpc();
11639
- init_config();
11640
- init_logger();
11641
- });
11642
-
11643
- // src/setup/audit-handoff.ts
11644
- import { spawnSync as spawnSync2 } from "node:child_process";
11645
- function buildAuditHandoffPrompt(dosuCmd = dosuInvocation()) {
11646
- return [
11647
- "Audit this repository to see what docs Dosu can generate for it.",
11648
- "1. Run the codebase audit from the Dosu skill. It writes findings to .dosu/audit.json.",
11649
- "2. Show me the findings and which docs you recommend Dosu generate.",
11650
- `3. Once I confirm, run \`${dosuCmd} audit --tasks <comma-separated task ids> --json\` to kick off generation. Dosu opens a PR and the CLI notifies me when it's ready.`
11651
- ].join(`
11652
- `);
11653
- }
11654
- function binOnPath(bin) {
11655
- const cmd = process.platform === "win32" ? "where" : "which";
11703
+ function writeTelemetrySettings(settings2) {
11704
+ const path3 = getTelemetrySettingsPath();
11705
+ const dir = getConfigDir();
11706
+ const temporaryPath = `${path3}.${process.pid}.${randomUUID()}.tmp`;
11656
11707
  try {
11657
- return spawnSync2(cmd, [bin], { stdio: "ignore" }).status === 0;
11708
+ if (!existsSync18(dir))
11709
+ mkdirSync9(dir, { recursive: true, mode: 448 });
11710
+ writeFileSync9(temporaryPath, `${JSON.stringify(settings2, null, 2)}
11711
+ `, {
11712
+ flag: "wx",
11713
+ mode: 384
11714
+ });
11715
+ renameSync3(temporaryPath, path3);
11716
+ return true;
11658
11717
  } catch {
11718
+ try {
11719
+ unlinkSync2(temporaryPath);
11720
+ } catch {}
11659
11721
  return false;
11660
11722
  }
11661
11723
  }
11662
- function claudeCliAvailable() {
11663
- return binOnPath("claude");
11664
- }
11665
- function dosuInvocation() {
11666
- return binOnPath("dosu") ? "dosu" : "npx -y @dosu/cli";
11724
+ function emptyTelemetrySettings() {
11725
+ return { schema_version: TELEMETRY_SCHEMA_VERSION };
11667
11726
  }
11668
- function printManualAuditNudge() {
11669
- log.message(`See what docs Dosu can generate for this repo:
11670
-
11671
- ${info("Ask Dosu to audit this repo and show what docs it can generate.")}
11672
-
11673
- Or run ${info(`${dosuInvocation()} audit`)} after your agent writes the audit.`);
11727
+ function disabledTelemetrySettings() {
11728
+ return { schema_version: TELEMETRY_SCHEMA_VERSION, disabled: true };
11674
11729
  }
11675
- async function offerAuditHandoff() {
11676
- if (!detectGitRepo())
11677
- return false;
11678
- if (!claudeCliAvailable()) {
11679
- printManualAuditNudge();
11730
+ function isEnabledEnvironmentFlag(value) {
11731
+ if (value === undefined)
11680
11732
  return false;
11681
- }
11682
- const go = await confirm({
11683
- message: "Kick off the codebase audit in Claude Code now?",
11684
- initialValue: true
11685
- });
11686
- if (isCancel(go) || !go) {
11687
- printManualAuditNudge();
11733
+ const normalized = value.trim().toLowerCase();
11734
+ if (normalized === "")
11688
11735
  return false;
11689
- }
11690
- return true;
11736
+ return !["0", "false", "no", "off"].includes(normalized);
11691
11737
  }
11692
- function launchAuditAgent() {
11693
- logger.info("setup", "Handing off to Claude Code for the codebase audit");
11694
- const result = spawnSync2("claude", ["--model", AUDIT_AGENT_MODEL, buildAuditHandoffPrompt()], {
11695
- stdio: "inherit",
11696
- shell: process.platform === "win32"
11697
- });
11698
- if (result.error) {
11699
- logger.warn("setup", `Claude Code launch failed: ${result.error.message}`);
11700
- printManualAuditNudge();
11701
- }
11738
+ function isUUID(value) {
11739
+ return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value);
11702
11740
  }
11703
- var AUDIT_AGENT_MODEL = "haiku";
11704
- var init_audit_handoff = __esm(() => {
11705
- init_dist8();
11706
- init_logger();
11707
- init_github_step();
11708
- init_styles();
11741
+ function isRecord3(value) {
11742
+ return typeof value === "object" && value !== null && !Array.isArray(value);
11743
+ }
11744
+ function isErrorCode(error, code) {
11745
+ return typeof error === "object" && error !== null && "code" in error && error.code === code;
11746
+ }
11747
+ var TELEMETRY_SCHEMA_VERSION = 1, TELEMETRY_SETTINGS_FILENAME = "telemetry.json", MAX_TELEMETRY_SETTINGS_BYTES;
11748
+ var init_settings = __esm(() => {
11749
+ init_config();
11750
+ MAX_TELEMETRY_SETTINGS_BYTES = 16 * 1024;
11709
11751
  });
11710
11752
 
11711
- // src/setup/rules-step.ts
11712
- function successfulAgentIDs(results, action) {
11713
- return new Set(results.filter((result) => result.action === action && !result.error).map((result) => result.provider.id()));
11753
+ // src/telemetry/debug-id.ts
11754
+ var BUNDLE_DEBUG_ID_PLACEHOLDER = "df4adcb4-addf-31e4-6475-6e2164756e21";
11755
+
11756
+ // src/telemetry/telemetry.ts
11757
+ import { randomUUID as randomUUID2 } from "node:crypto";
11758
+ import { existsSync as existsSync19 } from "node:fs";
11759
+ import { request as httpRequest2 } from "node:http";
11760
+ import { request as httpsRequest } from "node:https";
11761
+ import { isAbsolute, relative, resolve, sep } from "node:path";
11762
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
11763
+ function safeString(value, pattern, fallback) {
11764
+ if (typeof value !== "string" || !pattern.test(value))
11765
+ return fallback;
11766
+ return value;
11767
+ }
11768
+ function canonicalCommand(value) {
11769
+ if (typeof value !== "string")
11770
+ return UNKNOWN_COMMAND;
11771
+ const command = value.trim();
11772
+ if (command.length > 80)
11773
+ return UNKNOWN_COMMAND;
11774
+ if (!/^[a-z][a-z0-9-]*(?: [a-z][a-z0-9-]*){0,3}$/.test(command)) {
11775
+ return UNKNOWN_COMMAND;
11776
+ }
11777
+ return command;
11778
+ }
11779
+ function normalizeContext(context) {
11780
+ const isAuthenticated2 = context?.isAuthenticated === true;
11781
+ const id = safeRead(context?.user, "id");
11782
+ const email = safeRead(context?.user, "email");
11783
+ const user = isAuthenticated2 && validUserId(id) ? { id, ...validEmail(email) ? { email } : {} } : undefined;
11784
+ return {
11785
+ mode: context?.mode === "oss" ? "oss" : "cloud",
11786
+ isAuthenticated: isAuthenticated2,
11787
+ ...user ? { user } : {}
11788
+ };
11714
11789
  }
11715
- function logRuleResults(results) {
11716
- const ready = results.filter((result) => !result.error && (result.action === "created" || result.action === "updated" || result.action === "unchanged"));
11717
- const removed = results.filter((result) => !result.error && result.action === "removed");
11718
- if (ready.length > 0) {
11719
- log.success(formatSetupSummary(`Rules ready for ${ready.length} agent(s):`, ready.map((result) => ({ label: result.provider.name(), path: result.path }))));
11790
+ function normalizeExitCode(value, fallback = 1) {
11791
+ if (typeof value !== "number" || !Number.isFinite(value))
11792
+ return fallback;
11793
+ return Math.min(255, Math.max(0, Math.trunc(value)));
11794
+ }
11795
+ function normalizeRuntime(runtime) {
11796
+ return {
11797
+ version: safeString(runtime.version, /^[A-Za-z0-9.+_-]{1,32}$/, "unknown"),
11798
+ installChannel: safeString(runtime.installChannel, /^[A-Za-z][A-Za-z0-9_-]{0,31}$/, "unknown"),
11799
+ platform: safeString(runtime.platform, /^[A-Za-z0-9_-]{1,24}$/, "unknown"),
11800
+ arch: safeString(runtime.arch, /^[A-Za-z0-9_-]{1,24}$/, "unknown"),
11801
+ runtime: safeString(runtime.runtime, /^[A-Za-z][A-Za-z0-9_-]{0,23}$/, "unknown"),
11802
+ runtimeMajor: Math.min(9999, Math.max(0, Math.trunc(runtime.runtimeMajor) || 0)),
11803
+ isCi: runtime.isCi === true,
11804
+ isTty: runtime.isTty === true
11805
+ };
11806
+ }
11807
+ function validInstallId(value) {
11808
+ return typeof value === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value);
11809
+ }
11810
+ function validUserId(value) {
11811
+ return validInstallId(value);
11812
+ }
11813
+ function validEmail(value) {
11814
+ return typeof value === "string" && value.length <= 254 && /^[A-Za-z0-9.!#$%&'*+/=?^_`{|}~-]+@[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?(?:\.[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)+$/.test(value);
11815
+ }
11816
+ function validEventId(value) {
11817
+ return typeof value === "string" && /^[0-9a-f]{32}$/i.test(value);
11818
+ }
11819
+ function validDebugId(value) {
11820
+ return typeof value === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);
11821
+ }
11822
+ function validErrorType(value) {
11823
+ return typeof value === "string" && SAFE_ERROR_TYPES.has(value);
11824
+ }
11825
+ function validErrorCode(value) {
11826
+ return typeof value === "string" && SAFE_ERROR_CODES.has(value);
11827
+ }
11828
+ function validStatus(value) {
11829
+ return typeof value === "number" && Number.isInteger(value) && value >= 100 && value <= 599;
11830
+ }
11831
+ function objectLike(value) {
11832
+ return typeof value === "object" && value !== null || typeof value === "function";
11833
+ }
11834
+ function safeRead(value, key) {
11835
+ if (!objectLike(value))
11836
+ return;
11837
+ try {
11838
+ return Reflect.get(value, key);
11839
+ } catch {
11840
+ return;
11720
11841
  }
11721
- if (removed.length > 0) {
11722
- log.info(formatSetupSummary(`Rules removed from ${removed.length} agent(s):`, removed.map((result) => ({ label: result.provider.name(), path: result.path })), IconRemove));
11842
+ }
11843
+ function parseStack(stack) {
11844
+ if (typeof stack !== "string" || !CLI_PACKAGE_ROOT)
11845
+ return [];
11846
+ const frames = [];
11847
+ const lines = stack.slice(0, 1e5).split(`
11848
+ `, 200);
11849
+ for (const originalLine of lines) {
11850
+ if (frames.length >= MAX_STACK_FRAMES)
11851
+ break;
11852
+ const parsed = parseOwnedStackLine(originalLine, CLI_PACKAGE_ROOT);
11853
+ if (!parsed)
11854
+ continue;
11855
+ const { filename, lineno, colno } = parsed;
11856
+ if (!Number.isSafeInteger(lineno) || !Number.isSafeInteger(colno) || lineno < 1 || colno < 1 || lineno > 1e7 || colno > 1e7) {
11857
+ continue;
11858
+ }
11859
+ frames.push({ filename, lineno, colno, in_app: true });
11723
11860
  }
11861
+ return frames;
11724
11862
  }
11725
- async function stepConfigureAgentRules(selection, mcpResults) {
11726
- const successfulInstalls = successfulAgentIDs(mcpResults, "install");
11727
- const successfulRemovals = successfulAgentIDs(mcpResults, "remove");
11728
- const toInstall = selection.toInstall.filter((provider) => successfulInstalls.has(provider.id()) && isRuleAgent(provider.id()));
11729
- const toRemove = selection.toRemove.filter((provider) => successfulRemovals.has(provider.id()) && isRuleAgent(provider.id()));
11730
- const results = [];
11731
- if (toInstall.length > 0) {
11732
- const content = await fetchDosuRule();
11733
- for (const provider of toInstall) {
11734
- try {
11735
- const installed = installRuleForAgent(provider.id(), content);
11736
- if (installed)
11737
- results.push({ provider, action: installed.action, path: installed.path });
11738
- } catch (err) {
11739
- const error = err instanceof Error ? err : new Error(String(err));
11740
- logger.error("setup", `Rule install failed for ${provider.name()}: ${error.stack ?? error.message}`);
11741
- log.error(`Failed to install ${provider.name()} rule: ${error.message}`);
11742
- results.push({
11743
- provider,
11744
- action: "not_found",
11745
- path: rulePathForAgent(provider.id()) ?? "",
11746
- error
11747
- });
11748
- }
11863
+ function detectCliPackageRoot() {
11864
+ try {
11865
+ const modulePath = fileURLToPath2(import.meta.url).replaceAll("\\", "/");
11866
+ for (const suffix of ["/src/telemetry/telemetry.ts", "/bin/dosu.js"]) {
11867
+ if (modulePath.endsWith(suffix))
11868
+ return modulePath.slice(0, -suffix.length);
11749
11869
  }
11870
+ } catch {}
11871
+ return;
11872
+ }
11873
+ function parseOwnedStackLine(originalLine, packageRoot) {
11874
+ const line = originalLine.replaceAll("\\", "/");
11875
+ const match = line.match(/^\s*at\s+.*\s+\((.+):(\d+):(\d+)\)\s*$/) ?? line.match(/^\s*at\s+(.+):(\d+):(\d+)\s*$/);
11876
+ if (!match?.[1] || !match[2] || !match[3])
11877
+ return;
11878
+ let absolutePath = match[1];
11879
+ try {
11880
+ if (absolutePath.startsWith("file://"))
11881
+ absolutePath = fileURLToPath2(absolutePath);
11882
+ } catch {
11883
+ return;
11750
11884
  }
11751
- const retainedPaths = new Set(toInstall.map((provider) => rulePathForAgent(provider.id())).filter((path3) => path3 !== null));
11752
- for (const provider of toRemove) {
11753
- const path3 = rulePathForAgent(provider.id());
11754
- if (path3 && retainedPaths.has(path3))
11755
- continue;
11756
- try {
11885
+ if (!isAbsolute(absolutePath))
11886
+ return;
11887
+ const resolvedPath = resolve(absolutePath);
11888
+ const relativePath = relative(packageRoot, resolvedPath);
11889
+ if (relativePath === "" || relativePath === ".." || relativePath.startsWith(`..${sep}`) || isAbsolute(relativePath)) {
11890
+ return;
11891
+ }
11892
+ const filename = relativePath.replaceAll("\\", "/");
11893
+ if (!/^(?:src\/(?:[A-Za-z0-9_.-]+\/)*[A-Za-z0-9_.-]+\.(?:[cm]?[jt]sx?)|bin\/dosu\.js)$/.test(filename)) {
11894
+ return;
11895
+ }
11896
+ try {
11897
+ if (!existsSync19(resolvedPath))
11898
+ return;
11899
+ } catch {
11900
+ return;
11901
+ }
11902
+ return { filename, lineno: Number(match[2]), colno: Number(match[3]) };
11903
+ }
11904
+ function sanitizeError(error) {
11905
+ const data = safeRead(error, "data");
11906
+ const typeCandidate = safeRead(error, "name");
11907
+ const codeCandidates = [safeRead(data, "code"), safeRead(error, "code")];
11908
+ const statusCandidates = [
11909
+ safeRead(data, "httpStatus"),
11910
+ safeRead(error, "status"),
11911
+ safeRead(error, "statusCode")
11912
+ ];
11913
+ const exitCodeCandidate = safeRead(error, "exitCode");
11914
+ const code = codeCandidates.find(validErrorCode);
11915
+ const status = statusCandidates.find(validStatus);
11916
+ const exitCode = typeof exitCodeCandidate === "number" && Number.isFinite(exitCodeCandidate) ? normalizeExitCode(exitCodeCandidate) : undefined;
11917
+ return {
11918
+ type: validErrorType(typeCandidate) ? typeCandidate : "Error",
11919
+ ...code ? { code } : {},
11920
+ ...status ? { status } : {},
11921
+ frames: parseStack(safeRead(error, "stack")),
11922
+ ...exitCode === undefined ? {} : { exitCode }
11923
+ };
11924
+ }
11925
+ function durationBucket(durationMs) {
11926
+ const duration = Number.isFinite(durationMs) ? Math.max(0, durationMs) : 0;
11927
+ if (duration < 100)
11928
+ return "<100ms";
11929
+ if (duration < 500)
11930
+ return "100-499ms";
11931
+ if (duration < 2000)
11932
+ return "500ms-1.9s";
11933
+ if (duration < 1e4)
11934
+ return "2-9.9s";
11935
+ if (duration < 60000)
11936
+ return "10-59s";
11937
+ return "60s+";
11938
+ }
11939
+ function buildPostHogPayload(input) {
11940
+ const context = normalizeContext(input.context);
11941
+ const runtime = normalizeRuntime(input.runtime);
11942
+ const errorCode = validErrorCode(input.errorCode) ? input.errorCode : undefined;
11943
+ const result = input.result === "success" || input.result === "validation_error" ? input.result : "failure";
11944
+ return {
11945
+ api_key: input.apiKey,
11946
+ distinct_id: context.user?.id ?? (validInstallId(input.installId) ? input.installId : FALLBACK_INSTALL_ID),
11947
+ event: "cli_command_completed",
11948
+ properties: {
11949
+ $geoip_disable: true,
11950
+ ...context.user ? {} : { $process_person_profile: false },
11951
+ schema_version: 1,
11952
+ command: canonicalCommand(input.command),
11953
+ result,
11954
+ duration_bucket: durationBucket(input.durationMs),
11955
+ cli_version: runtime.version,
11956
+ install_channel: runtime.installChannel,
11957
+ platform: runtime.platform,
11958
+ arch: runtime.arch,
11959
+ runtime: runtime.runtime,
11960
+ runtime_major: runtime.runtimeMajor,
11961
+ is_ci: runtime.isCi,
11962
+ is_tty: runtime.isTty,
11963
+ mode: context.mode,
11964
+ is_authenticated: context.isAuthenticated,
11965
+ exit_code: normalizeExitCode(input.exitCode),
11966
+ ...errorCode ? { error_code: errorCode } : {}
11967
+ }
11968
+ };
11969
+ }
11970
+ function parsePostHogProjectToken(value) {
11971
+ if (typeof value !== "string")
11972
+ return null;
11973
+ const token = value.trim();
11974
+ return /^phc_[A-Za-z0-9_-]{3,252}$/.test(token) ? token : null;
11975
+ }
11976
+ function parseTelemetryWebAppURL(value, allowInsecureLoopback = false) {
11977
+ if (typeof value !== "string" || value.length === 0 || value.length > 2048)
11978
+ return null;
11979
+ try {
11980
+ const url = new URL(value.trim());
11981
+ if (!url.hostname || url.username || url.password || url.search || url.hash)
11982
+ return null;
11983
+ const loopbackHosts = new Set(["localhost", "127.0.0.1", "[::1]"]);
11984
+ const secure = url.protocol === "https:";
11985
+ const allowedDevelopmentOrigin = allowInsecureLoopback && url.protocol === "http:" && loopbackHosts.has(url.hostname);
11986
+ if (!secure && !allowedDevelopmentOrigin)
11987
+ return null;
11988
+ return url.toString().replace(/\/$/, "");
11989
+ } catch {
11990
+ return null;
11991
+ }
11992
+ }
11993
+ function parseSentryDsn(value) {
11994
+ if (typeof value !== "string" || value.length === 0 || value.length > 2048)
11995
+ return null;
11996
+ const dsn = value.trim();
11997
+ try {
11998
+ const url = new URL(dsn);
11999
+ if (url.protocol !== "https:" || !url.hostname || !url.username || url.password || url.search || url.hash || !/^[A-Za-z0-9_.-]{1,128}$/.test(url.username) || url.username.toLowerCase().startsWith("sntry")) {
12000
+ return null;
12001
+ }
12002
+ const segments = url.pathname.split("/").filter(Boolean);
12003
+ const projectId = segments.pop();
12004
+ if (!projectId || !/^[A-Za-z0-9_-]{1,128}$/.test(projectId))
12005
+ return null;
12006
+ if (segments.some((segment) => !/^[A-Za-z0-9._~-]{1,128}$/.test(segment)))
12007
+ return null;
12008
+ const prefix = segments.length > 0 ? `/${segments.join("/")}` : "";
12009
+ return {
12010
+ dsn,
12011
+ endpoint: `${url.protocol}//${url.host}${prefix}/api/${projectId}/envelope/`,
12012
+ projectId,
12013
+ publicKey: url.username
12014
+ };
12015
+ } catch {
12016
+ return null;
12017
+ }
12018
+ }
12019
+ function sentryTags(command, context, runtime, error) {
12020
+ return {
12021
+ schema_version: "1",
12022
+ command,
12023
+ cli_version: runtime.version,
12024
+ install_channel: runtime.installChannel,
12025
+ os: runtime.platform,
12026
+ arch: runtime.arch,
12027
+ runtime: runtime.runtime,
12028
+ runtime_major: String(runtime.runtimeMajor),
12029
+ is_ci: String(runtime.isCi),
12030
+ is_tty: String(runtime.isTty),
12031
+ mode: context.mode,
12032
+ is_authenticated: String(context.isAuthenticated),
12033
+ ...error.code ? { error_code: error.code } : {},
12034
+ ...error.status ? { http_status: String(error.status) } : {},
12035
+ ...error.exitCode === undefined ? {} : { exit_code: String(error.exitCode) }
12036
+ };
12037
+ }
12038
+ function buildSentryEnvelope(input) {
12039
+ const parsedDsn = parseSentryDsn(input.dsn);
12040
+ if (!parsedDsn || !validEventId(input.eventId))
12041
+ return null;
12042
+ const context = normalizeContext(input.context);
12043
+ const runtime = normalizeRuntime(input.runtime);
12044
+ const command = canonicalCommand(input.command);
12045
+ const debugId = validDebugId(input.debugId) ? input.debugId.toLowerCase() : undefined;
12046
+ const error = {
12047
+ type: validErrorType(input.error.type) ? input.error.type : "Error",
12048
+ ...validErrorCode(input.error.code) ? { code: input.error.code } : {},
12049
+ ...validStatus(input.error.status) ? { status: input.error.status } : {},
12050
+ ...typeof input.error.exitCode === "number" && Number.isFinite(input.error.exitCode) ? { exitCode: normalizeExitCode(input.error.exitCode) } : {},
12051
+ frames: input.error.frames.slice(0, MAX_STACK_FRAMES).map((frame) => {
12052
+ const filename = /^(?:src\/(?:[A-Za-z0-9_.-]+\/)*[A-Za-z0-9_.-]+\.(?:[cm]?[jt]sx?)|bin\/dosu\.js)$/.test(frame.filename) ? frame.filename : "src/unknown.ts";
12053
+ return {
12054
+ filename,
12055
+ ...debugId && filename === "bin/dosu.js" ? { abs_path: BUNDLE_CODE_FILE } : {},
12056
+ lineno: Math.min(1e7, Math.max(1, Math.trunc(frame.lineno) || 1)),
12057
+ colno: Math.min(1e7, Math.max(1, Math.trunc(frame.colno) || 1)),
12058
+ in_app: true
12059
+ };
12060
+ }).reverse()
12061
+ };
12062
+ const exceptionValue = {
12063
+ type: error.type,
12064
+ value: error.code ?? error.type,
12065
+ ...error.frames.length > 0 ? { stacktrace: { frames: error.frames } } : {}
12066
+ };
12067
+ const newestFrame = error.frames.at(-1);
12068
+ const safeCallsite = newestFrame ? `${newestFrame.filename}:${newestFrame.lineno}` : "unknown";
12069
+ const timestampMs = Number.isFinite(input.timestampMs) ? Math.max(0, input.timestampMs) : 0;
12070
+ const mappedBundle = debugId && error.frames.some((frame) => frame.abs_path === BUNDLE_CODE_FILE);
12071
+ const event = {
12072
+ event_id: input.eventId.toLowerCase(),
12073
+ timestamp: timestampMs / 1000,
12074
+ platform: "node",
12075
+ level: "error",
12076
+ release: `dosu-cli@${runtime.version}`,
12077
+ tags: sentryTags(command, context, runtime, error),
12078
+ ...context.user ? { user: context.user } : {},
12079
+ ...mappedBundle ? {
12080
+ debug_meta: {
12081
+ images: [{ type: "sourcemap", code_file: BUNDLE_CODE_FILE, debug_id: debugId }]
12082
+ }
12083
+ } : {},
12084
+ fingerprint: ["dosu-cli", command, error.type, error.code ?? "unknown", safeCallsite],
12085
+ exception: { values: [exceptionValue] }
12086
+ };
12087
+ const envelopeHeader = {
12088
+ event_id: input.eventId.toLowerCase(),
12089
+ dsn: parsedDsn.dsn,
12090
+ sent_at: new Date(timestampMs).toISOString()
12091
+ };
12092
+ return {
12093
+ endpoint: parsedDsn.endpoint,
12094
+ body: `${JSON.stringify(envelopeHeader)}
12095
+ ${JSON.stringify({ type: "event" })}
12096
+ ${JSON.stringify(event)}`
12097
+ };
12098
+ }
12099
+ function fetchWithoutRedirect(url, init = {}) {
12100
+ let parsedURL;
12101
+ try {
12102
+ parsedURL = new URL(url);
12103
+ if (parsedURL.protocol !== "http:" && parsedURL.protocol !== "https:") {
12104
+ return Promise.reject(new Error("unsupported telemetry protocol"));
12105
+ }
12106
+ } catch (error) {
12107
+ return Promise.reject(error);
12108
+ }
12109
+ return new Promise((resolve2, reject) => {
12110
+ let request;
12111
+ let response;
12112
+ let settled = false;
12113
+ const signal = init.signal;
12114
+ const cleanup = () => signal?.removeEventListener("abort", onAbort);
12115
+ const fail = (error) => {
12116
+ if (settled)
12117
+ return;
12118
+ settled = true;
12119
+ cleanup();
12120
+ response?.destroy();
12121
+ request?.socket?.destroy();
12122
+ request?.destroy();
12123
+ reject(error);
12124
+ };
12125
+ const succeed = (value) => {
12126
+ if (settled)
12127
+ return;
12128
+ settled = true;
12129
+ cleanup();
12130
+ response?.destroy();
12131
+ request?.socket?.destroy();
12132
+ request?.destroy();
12133
+ resolve2(value);
12134
+ };
12135
+ const onAbort = () => {
12136
+ const error = new Error("telemetry request aborted");
12137
+ error.name = "AbortError";
12138
+ fail(error);
12139
+ };
12140
+ try {
12141
+ if (signal?.aborted) {
12142
+ onAbort();
12143
+ return;
12144
+ }
12145
+ const body = init.body;
12146
+ if (body !== undefined && body !== null && typeof body !== "string") {
12147
+ fail(new Error("unsupported telemetry request body"));
12148
+ return;
12149
+ }
12150
+ const headers = Object.fromEntries(new Headers(init.headers).entries());
12151
+ const transport = parsedURL.protocol === "https:" ? httpsRequest : httpRequest2;
12152
+ request = transport(parsedURL, {
12153
+ method: init.method ?? "GET",
12154
+ headers,
12155
+ agent: false
12156
+ }, (incoming) => {
12157
+ response = incoming;
12158
+ const chunks = [];
12159
+ let totalBytes = 0;
12160
+ incoming.on("data", (chunk) => {
12161
+ const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
12162
+ totalBytes += bytes.byteLength;
12163
+ if (totalBytes > MAX_RESPONSE_BYTES) {
12164
+ fail(new Error("telemetry response too large"));
12165
+ return;
12166
+ }
12167
+ chunks.push(bytes);
12168
+ });
12169
+ incoming.once("error", fail);
12170
+ incoming.once("end", () => {
12171
+ try {
12172
+ const responseHeaders = new Headers;
12173
+ for (let index = 0;index < incoming.rawHeaders.length; index += 2) {
12174
+ const name = incoming.rawHeaders[index];
12175
+ const value = incoming.rawHeaders[index + 1];
12176
+ if (name && value)
12177
+ responseHeaders.append(name, value);
12178
+ }
12179
+ const status = incoming.statusCode;
12180
+ if (!status)
12181
+ throw new Error("telemetry response missing status");
12182
+ succeed(new Response(chunks.length > 0 ? Buffer.concat(chunks) : null, {
12183
+ status,
12184
+ statusText: incoming.statusMessage,
12185
+ headers: responseHeaders
12186
+ }));
12187
+ } catch (error) {
12188
+ fail(error);
12189
+ }
12190
+ });
12191
+ });
12192
+ request.once("error", fail);
12193
+ signal?.addEventListener("abort", onAbort, { once: true });
12194
+ request.end(body);
12195
+ } catch (error) {
12196
+ fail(error);
12197
+ }
12198
+ });
12199
+ }
12200
+ async function sendHttpsRequest(url, init, fetcher = fetchWithoutRedirect) {
12201
+ try {
12202
+ if (new URL(url).protocol !== "https:")
12203
+ return false;
12204
+ } catch {
12205
+ return false;
12206
+ }
12207
+ const controller = new AbortController;
12208
+ let timer;
12209
+ const timeout = new Promise((_resolve, reject) => {
12210
+ timer = setTimeout(() => {
12211
+ controller.abort();
12212
+ reject(new Error("telemetry request timed out"));
12213
+ }, REQUEST_TIMEOUT_MS);
12214
+ });
12215
+ try {
12216
+ const request = fetcher(url, { ...init, redirect: "error", signal: controller.signal });
12217
+ const result = await Promise.race([request, timeout]);
12218
+ return result.ok;
12219
+ } catch {
12220
+ return false;
12221
+ } finally {
12222
+ controller.abort();
12223
+ if (timer !== undefined)
12224
+ clearTimeout(timer);
12225
+ }
12226
+ }
12227
+ function environmentFlag(value) {
12228
+ if (value === undefined)
12229
+ return false;
12230
+ const normalized = value.trim().toLowerCase();
12231
+ return normalized !== "" && !["0", "false", "no", "off"].includes(normalized);
12232
+ }
12233
+ function runtimeNameAndMajor() {
12234
+ const versions = process.versions;
12235
+ const runtime = versions.bun ? "bun" : "node";
12236
+ const version = versions.bun ?? versions.node;
12237
+ return { runtime, runtimeMajor: Number.parseInt(version.split(".")[0] ?? "0", 10) || 0 };
12238
+ }
12239
+ function resolveRuntime(dependencies, env) {
12240
+ const detected = runtimeNameAndMajor();
12241
+ return normalizeRuntime({
12242
+ version: dependencies.version ?? VERSION,
12243
+ installChannel: dependencies.installChannel ?? INSTALL_CHANNEL,
12244
+ platform: dependencies.platform ?? process.platform,
12245
+ arch: dependencies.arch ?? process.arch,
12246
+ runtime: dependencies.runtime ?? detected.runtime,
12247
+ runtimeMajor: dependencies.runtimeMajor ?? detected.runtimeMajor,
12248
+ isCi: dependencies.isCi ?? environmentFlag(env.CI),
12249
+ isTty: dependencies.isTty ?? process.stdout.isTTY === true
12250
+ });
12251
+ }
12252
+ function validationFailure(exitCode, error) {
12253
+ if (exitCode === 2)
12254
+ return true;
12255
+ if (error?.type === "CliUsageError")
12256
+ return true;
12257
+ if (!error?.code)
12258
+ return false;
12259
+ return error.code.startsWith("commander.") || ["BAD_REQUEST", "INVALID_ARGUMENT", "UNPROCESSABLE_CONTENT", "VALIDATION_ERROR"].includes(error.code);
12260
+ }
12261
+ function safeNow(now) {
12262
+ try {
12263
+ const value = now();
12264
+ return Number.isFinite(value) ? Math.max(0, value) : 0;
12265
+ } catch {
12266
+ return 0;
12267
+ }
12268
+ }
12269
+ function safeUuid(generate) {
12270
+ try {
12271
+ const value = generate();
12272
+ return validInstallId(value) ? value : undefined;
12273
+ } catch {
12274
+ return;
12275
+ }
12276
+ }
12277
+ function eventId(generate) {
12278
+ const uuid = safeUuid(generate);
12279
+ return uuid?.replaceAll("-", "");
12280
+ }
12281
+ function createCommandTelemetry(settings2, dependencies = {}) {
12282
+ const env = dependencies.env ?? process.env;
12283
+ const fetcher = dependencies.fetch;
12284
+ const now = dependencies.now ?? Date.now;
12285
+ const generateUuid = dependencies.randomUUID ?? randomUUID2;
12286
+ const writeStderr = dependencies.stderr ?? ((payload) => process.stderr.write(`${payload}
12287
+ `));
12288
+ const webAppURL = dependencies.webAppURL ?? getWebAppURL;
12289
+ const runtime = resolveRuntime(dependencies, env);
12290
+ const debug = env.DOSU_TELEMETRY_DEBUG === "1";
12291
+ const disabled = settings2.disabled === true || environmentFlag(env.DO_NOT_TRACK) || environmentFlag(env.DOSU_TELEMETRY_DISABLED);
12292
+ const rawAnalyticsToken = dependencies.env ? env.DOSU_POSTHOG_PROJECT_TOKEN_OVERRIDE ?? env.DOSU_POSTHOG_PROJECT_TOKEN : process.env.DOSU_POSTHOG_PROJECT_TOKEN_OVERRIDE ?? "phc_Uo9RxhN0HRk8gZkPlT18BCr7eqsM0MPWVW88HZB95V5";
12293
+ const rawSentryDsn = dependencies.env ? env.DOSU_CLI_SENTRY_DSN_OVERRIDE ?? env.DOSU_CLI_SENTRY_DSN : process.env.DOSU_CLI_SENTRY_DSN_OVERRIDE ?? "https://f6b3e51dea6f50746316aa1896df76e0@o4505364738670592.ingest.us.sentry.io/4511883653283840";
12294
+ const analyticsToken = parsePostHogProjectToken(rawAnalyticsToken) ?? undefined;
12295
+ const sentryDsn = parseSentryDsn(rawSentryDsn)?.dsn;
12296
+ let started = false;
12297
+ let terminal = false;
12298
+ let startedAt = 0;
12299
+ let command = UNKNOWN_COMMAND;
12300
+ let context = normalizeContext(undefined);
12301
+ let installIdResolved = false;
12302
+ let resolvedInstallId = settings2.install_id;
12303
+ function installId() {
12304
+ if (validInstallId(resolvedInstallId))
12305
+ return resolvedInstallId;
12306
+ if (installIdResolved)
12307
+ return;
12308
+ installIdResolved = true;
12309
+ try {
12310
+ resolvedInstallId = dependencies.resolveInstallId?.();
12311
+ } catch {
12312
+ resolvedInstallId = undefined;
12313
+ }
12314
+ return validInstallId(resolvedInstallId) ? resolvedInstallId : undefined;
12315
+ }
12316
+ function debugPayload(payload) {
12317
+ try {
12318
+ writeStderr(payload);
12319
+ } catch {}
12320
+ }
12321
+ async function dispatch(result, exitCode, error) {
12322
+ try {
12323
+ const tasks = [];
12324
+ if (!disabled && analyticsToken) {
12325
+ const id = context.user?.id ?? installId();
12326
+ if (id) {
12327
+ const payload = buildPostHogPayload({
12328
+ apiKey: analyticsToken,
12329
+ ...context.user ? {} : { installId: id },
12330
+ command,
12331
+ result,
12332
+ durationMs: safeNow(now) - startedAt,
12333
+ exitCode,
12334
+ errorCode: error?.code,
12335
+ context,
12336
+ runtime
12337
+ });
12338
+ const body = JSON.stringify(payload);
12339
+ if (debug) {
12340
+ debugPayload(body);
12341
+ } else {
12342
+ const base = parseTelemetryWebAppURL(webAppURL());
12343
+ if (base) {
12344
+ tasks.push(sendHttpsRequest(`${base}/ph-api/i/v0/e/`, {
12345
+ method: "POST",
12346
+ headers: { "content-type": "application/json" },
12347
+ body
12348
+ }, fetcher));
12349
+ }
12350
+ }
12351
+ }
12352
+ }
12353
+ if (!disabled && result === "failure" && error && sentryDsn) {
12354
+ const id = eventId(generateUuid);
12355
+ const envelope = id ? buildSentryEnvelope({
12356
+ dsn: sentryDsn,
12357
+ command,
12358
+ context,
12359
+ runtime,
12360
+ error,
12361
+ eventId: id,
12362
+ timestampMs: safeNow(now),
12363
+ debugId: BUNDLE_DEBUG_ID_PLACEHOLDER
12364
+ }) : null;
12365
+ if (envelope) {
12366
+ if (debug) {
12367
+ debugPayload(envelope.body);
12368
+ } else {
12369
+ tasks.push(sendHttpsRequest(envelope.endpoint, {
12370
+ method: "POST",
12371
+ headers: { "content-type": "application/x-sentry-envelope" },
12372
+ body: envelope.body
12373
+ }, fetcher));
12374
+ }
12375
+ }
12376
+ }
12377
+ await Promise.all(tasks);
12378
+ } catch {}
12379
+ }
12380
+ return {
12381
+ start(commandName, commandContext) {
12382
+ if (started || terminal)
12383
+ return;
12384
+ started = true;
12385
+ command = canonicalCommand(commandName);
12386
+ context = normalizeContext(commandContext);
12387
+ startedAt = safeNow(now);
12388
+ },
12389
+ async complete(exitCode = 0) {
12390
+ if (!started || terminal)
12391
+ return;
12392
+ terminal = true;
12393
+ const normalizedExitCode = normalizeExitCode(exitCode, 0);
12394
+ const result = normalizedExitCode === 0 ? "success" : validationFailure(normalizedExitCode, undefined) ? "validation_error" : "failure";
12395
+ const error = result === "failure" ? { type: "CommandExitError", frames: [], exitCode: normalizedExitCode } : undefined;
12396
+ await dispatch(result, normalizedExitCode, error);
12397
+ },
12398
+ async fail(rawError) {
12399
+ if (!started || terminal)
12400
+ return;
12401
+ terminal = true;
12402
+ const error = sanitizeError(rawError);
12403
+ const exitCode = error.exitCode ?? 1;
12404
+ const result = validationFailure(exitCode, error) ? "validation_error" : "failure";
12405
+ await dispatch(result, exitCode, error);
12406
+ }
12407
+ };
12408
+ }
12409
+ var REQUEST_TIMEOUT_MS = 500, MAX_RESPONSE_BYTES, MAX_STACK_FRAMES = 20, UNKNOWN_COMMAND = "unknown", FALLBACK_INSTALL_ID = "00000000-0000-4000-8000-000000000000", BUNDLE_CODE_FILE = "app:///bin/dosu.js", CLI_PACKAGE_ROOT, SAFE_ERROR_TYPES, SAFE_ERROR_CODES;
12410
+ var init_telemetry = __esm(() => {
12411
+ MAX_RESPONSE_BYTES = 1024 * 1024;
12412
+ CLI_PACKAGE_ROOT = detectCliPackageRoot();
12413
+ SAFE_ERROR_TYPES = new Set([
12414
+ "AbortError",
12415
+ "AggregateError",
12416
+ "CommanderError",
12417
+ "CliUsageError",
12418
+ "CommandExitError",
12419
+ "Error",
12420
+ "OAuthCallbackError",
12421
+ "RangeError",
12422
+ "ReferenceError",
12423
+ "SessionExpiredError",
12424
+ "SyntaxError",
12425
+ "TicketHttpError",
12426
+ "TRPCClientError",
12427
+ "TypeError"
12428
+ ]);
12429
+ SAFE_ERROR_CODES = new Set([
12430
+ "ABORT_ERR",
12431
+ "BAD_GATEWAY",
12432
+ "BAD_REQUEST",
12433
+ "CLIENT_CLOSED_REQUEST",
12434
+ "CONFLICT",
12435
+ "EACCES",
12436
+ "EAI_AGAIN",
12437
+ "EBUSY",
12438
+ "ECONNREFUSED",
12439
+ "ECONNRESET",
12440
+ "EEXIST",
12441
+ "EHOSTUNREACH",
12442
+ "EINVAL",
12443
+ "EISDIR",
12444
+ "ELOOP",
12445
+ "EMFILE",
12446
+ "ENAMETOOLONG",
12447
+ "ENETUNREACH",
12448
+ "ENFILE",
12449
+ "ENOENT",
12450
+ "ENOMEM",
12451
+ "ENOSPC",
12452
+ "ENOTFOUND",
12453
+ "ENOTDIR",
12454
+ "EPIPE",
12455
+ "EPERM",
12456
+ "EROFS",
12457
+ "ETIMEDOUT",
12458
+ "FORBIDDEN",
12459
+ "GATEWAY_TIMEOUT",
12460
+ "IMPORT_ALREADY_IN_PROGRESS",
12461
+ "INTERNAL_SERVER_ERROR",
12462
+ "INVALID_ARGUMENT",
12463
+ "METHOD_NOT_SUPPORTED",
12464
+ "NOT_FOUND",
12465
+ "NOT_IMPLEMENTED",
12466
+ "PARSE_ERROR",
12467
+ "PAYLOAD_TOO_LARGE",
12468
+ "PAYMENT_REQUIRED",
12469
+ "PRECONDITION_FAILED",
12470
+ "SERVICE_UNAVAILABLE",
12471
+ "TIMEOUT",
12472
+ "TOO_MANY_REQUESTS",
12473
+ "UNAUTHORIZED",
12474
+ "UNPROCESSABLE_CONTENT",
12475
+ "UNSUPPORTED_MEDIA_TYPE",
12476
+ "VALIDATION_ERROR",
12477
+ "commander.conflictingOption",
12478
+ "commander.error",
12479
+ "commander.excessArguments",
12480
+ "commander.invalidArgument",
12481
+ "commander.missingArgument",
12482
+ "commander.missingMandatoryOptionValue",
12483
+ "commander.optionMissingArgument",
12484
+ "commander.unknownCommand",
12485
+ "commander.unknownOption"
12486
+ ]);
12487
+ });
12488
+
12489
+ // src/setup/analytics.ts
12490
+ async function trackCliOnboardingEvent(cfg, onboardingRunID, event, properties = {}) {
12491
+ try {
12492
+ if (!isTelemetryEnabled())
12493
+ return;
12494
+ if (!analyticsReleaseEnabled())
12495
+ return;
12496
+ if (!cfg.active_account?.session.access_token)
12497
+ return;
12498
+ if (!isUUID2(onboardingRunID))
12499
+ return;
12500
+ const input = {
12501
+ event,
12502
+ properties: {
12503
+ ...baseProperties(cfg),
12504
+ onboarding_run_id: onboardingRunID,
12505
+ ...allowlistedWorkflowProperties(properties)
12506
+ }
12507
+ };
12508
+ if (printDebugPayload(input))
12509
+ return;
12510
+ await withTimeout((signal) => createAnalyticsClient(cfg.active_account?.session.access_token, signal).user.trackCliOnboardingEvent.mutate(input), TRACKING_TIMEOUT_MS);
12511
+ } catch (err) {
12512
+ logTrackingFailure("analytics", event, err);
12513
+ }
12514
+ }
12515
+ async function trackCliOnboardingPreAuthEvent(onboardingRunID, event, properties = {}) {
12516
+ try {
12517
+ if (!isTelemetryEnabled())
12518
+ return;
12519
+ if (!analyticsReleaseEnabled())
12520
+ return;
12521
+ if (!isUUID2(onboardingRunID))
12522
+ return;
12523
+ const input = {
12524
+ event,
12525
+ onboarding_run_id: onboardingRunID,
12526
+ properties: {
12527
+ ...baseProperties(emptyConfig()),
12528
+ ...allowlistedWorkflowProperties(properties)
12529
+ }
12530
+ };
12531
+ if (printDebugPayload(input))
12532
+ return;
12533
+ await withTimeout((signal) => createAnalyticsClient(undefined, signal).user.trackCliOnboardingPreAuthEvent.mutate(input), TRACKING_TIMEOUT_MS);
12534
+ } catch (err) {
12535
+ logTrackingFailure("pre-auth analytics", event, err);
12536
+ }
12537
+ }
12538
+ function logTrackingFailure(kind, event, error) {
12539
+ try {
12540
+ const message = error instanceof Error ? error.message : String(error);
12541
+ logger.debug("setup", `CLI onboarding ${kind} failed: ${event}: ${message}`);
12542
+ } catch {}
12543
+ }
12544
+ function printDebugPayload(payload) {
12545
+ if (process.env.DOSU_TELEMETRY_DEBUG !== "1")
12546
+ return false;
12547
+ console.error(`[dosu telemetry:posthog] ${JSON.stringify(payload)}`);
12548
+ return true;
12549
+ }
12550
+ function analyticsReleaseEnabled() {
12551
+ return Boolean(parsePostHogProjectToken(process.env.DOSU_POSTHOG_PROJECT_TOKEN_OVERRIDE ?? "phc_Uo9RxhN0HRk8gZkPlT18BCr7eqsM0MPWVW88HZB95V5"));
12552
+ }
12553
+ function baseProperties(cfg) {
12554
+ return {
12555
+ cli_version: safeIdentifier(VERSION, 32),
12556
+ install_channel: safeIdentifier(INSTALL_CHANNEL, 32),
12557
+ platform: safeIdentifier(process.platform, 24),
12558
+ arch: safeIdentifier(process.arch, 24),
12559
+ mode: cfg.mode ?? "cloud",
12560
+ org_id: safeIdentifier(cfg.active_account?.target?.org_id, 128),
12561
+ deployment_id: safeIdentifier(cfg.active_account?.target?.deployment_id, 128),
12562
+ space_id: safeIdentifier(cfg.active_account?.target?.space_id, 128)
12563
+ };
12564
+ }
12565
+ function allowlistedWorkflowProperties(properties) {
12566
+ const input = properties;
12567
+ const safe = {};
12568
+ if (typeof input.has_deployment_option === "boolean") {
12569
+ safe.has_deployment_option = input.has_deployment_option;
12570
+ }
12571
+ if (input.mode_option === "cloud" || input.mode_option === "oss") {
12572
+ safe.mode_option = input.mode_option;
12573
+ }
12574
+ if (input.flow_kind === "onboarding" || input.flow_kind === "oss" || input.flow_kind === "setup") {
12575
+ safe.flow_kind = input.flow_kind;
12576
+ }
12577
+ if (typeof input.reason === "string" && SETUP_FAILURE_REASONS.has(input.reason)) {
12578
+ safe.reason = input.reason;
12579
+ }
12580
+ if (typeof input.provider_count === "number" && Number.isInteger(input.provider_count) && input.provider_count >= 0 && input.provider_count <= 50) {
12581
+ safe.provider_count = input.provider_count;
12582
+ }
12583
+ if (Array.isArray(input.providers)) {
12584
+ safe.providers = input.providers.filter((provider) => typeof provider === "string" && /^[a-z][a-z0-9-]{0,31}$/.test(provider)).slice(0, 50);
12585
+ }
12586
+ for (const key of ["completed_mcp", "completed_skill", "completed_agents_md"]) {
12587
+ if (typeof input[key] === "boolean")
12588
+ safe[key] = input[key];
12589
+ }
12590
+ return safe;
12591
+ }
12592
+ function safeIdentifier(value, maxLength) {
12593
+ return typeof value === "string" && value.length <= maxLength && /^[A-Za-z0-9][A-Za-z0-9._+-]*$/.test(value) ? value : undefined;
12594
+ }
12595
+ function isUUID2(value) {
12596
+ return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value);
12597
+ }
12598
+ function createAnalyticsClient(accessToken, requestSignal) {
12599
+ const webAppURL = parseTelemetryWebAppURL(getWebAppURL(), process.env.DOSU_DEV === "true");
12600
+ if (!webAppURL) {
12601
+ throw new Error("Secure web app URL not configured");
12602
+ }
12603
+ return createTRPCClient({
12604
+ links: [
12605
+ httpLink({
12606
+ url: `${webAppURL}/api/cli-trpc`,
12607
+ transformer: dist_default,
12608
+ headers: {
12609
+ "x-dosu-cli-contract": CLI_CONTRACT_HASH,
12610
+ ...accessToken ? { "Supabase-Access-Token": accessToken } : {}
12611
+ },
12612
+ fetch: (url, options) => {
12613
+ const target = typeof url === "string" ? url : url instanceof URL ? url.toString() : url.url;
12614
+ return fetchWithoutRedirect(target, {
12615
+ ...options,
12616
+ redirect: "error",
12617
+ ...requestSignal ? { signal: requestSignal } : {}
12618
+ });
12619
+ }
12620
+ })
12621
+ ]
12622
+ });
12623
+ }
12624
+ async function withTimeout(operation, timeoutMs) {
12625
+ const controller = new AbortController;
12626
+ let timeout;
12627
+ try {
12628
+ return await Promise.race([
12629
+ operation(controller.signal),
12630
+ new Promise((_2, reject) => {
12631
+ timeout = setTimeout(() => {
12632
+ controller.abort();
12633
+ reject(new Error("tracking timeout"));
12634
+ }, timeoutMs);
12635
+ })
12636
+ ]);
12637
+ } finally {
12638
+ if (timeout)
12639
+ clearTimeout(timeout);
12640
+ }
12641
+ }
12642
+ var SETUP_FAILURE_REASONS, TRACKING_TIMEOUT_MS = 500;
12643
+ var init_analytics = __esm(() => {
12644
+ init_dist();
12645
+ init_dist4();
12646
+ init_config();
12647
+ init_logger();
12648
+ init_settings();
12649
+ init_telemetry();
12650
+ SETUP_FAILURE_REASONS = new Set([
12651
+ "access_denied",
12652
+ "api_key_failed",
12653
+ "bad_oauth_state",
12654
+ "cloud_setup_context_failed",
12655
+ "deployment_resolution_failed",
12656
+ "invalid_request",
12657
+ "invalid_scope",
12658
+ "mcp_selection_cancelled",
12659
+ "oauth_callback_error",
12660
+ "onboarding_incomplete_after_handshake",
12661
+ "server_error",
12662
+ "temporarily_unavailable",
12663
+ "unauthorized_client",
12664
+ "unexpected_auth_error",
12665
+ "unsupported_response_type",
12666
+ "web_onboarding_incomplete"
12667
+ ]);
12668
+ });
12669
+
12670
+ // src/setup/audit-handoff.ts
12671
+ import { spawnSync as spawnSync2 } from "node:child_process";
12672
+ function buildAuditHandoffPrompt(dosuCmd = dosuInvocation()) {
12673
+ return [
12674
+ "Audit this repository to see what docs Dosu can generate for it.",
12675
+ "1. Run the codebase audit from the Dosu skill. It writes findings to .dosu/audit.json.",
12676
+ "2. Show me the findings and which docs you recommend Dosu generate.",
12677
+ `3. Once I confirm, run \`${dosuCmd} audit --tasks <comma-separated task ids> --json\` to kick off generation. Dosu opens a PR and the CLI notifies me when it's ready.`
12678
+ ].join(`
12679
+ `);
12680
+ }
12681
+ function binOnPath(bin) {
12682
+ const cmd = process.platform === "win32" ? "where" : "which";
12683
+ try {
12684
+ return spawnSync2(cmd, [bin], { stdio: "ignore" }).status === 0;
12685
+ } catch {
12686
+ return false;
12687
+ }
12688
+ }
12689
+ function claudeCliAvailable() {
12690
+ return binOnPath("claude");
12691
+ }
12692
+ function dosuInvocation() {
12693
+ return binOnPath("dosu") ? "dosu" : "npx -y @dosu/cli";
12694
+ }
12695
+ function printManualAuditNudge() {
12696
+ log.message(`See what docs Dosu can generate for this repo:
12697
+
12698
+ ${info("Ask Dosu to audit this repo and show what docs it can generate.")}
12699
+
12700
+ Or run ${info(`${dosuInvocation()} audit`)} after your agent writes the audit.`);
12701
+ }
12702
+ async function offerAuditHandoff() {
12703
+ if (!detectGitRepo())
12704
+ return false;
12705
+ if (!claudeCliAvailable()) {
12706
+ printManualAuditNudge();
12707
+ return false;
12708
+ }
12709
+ const go = await confirm({
12710
+ message: "Kick off the codebase audit in Claude Code now?",
12711
+ initialValue: true
12712
+ });
12713
+ if (isCancel(go) || !go) {
12714
+ printManualAuditNudge();
12715
+ return false;
12716
+ }
12717
+ return true;
12718
+ }
12719
+ function launchAuditAgent() {
12720
+ logger.info("setup", "Handing off to Claude Code for the codebase audit");
12721
+ const result = spawnSync2("claude", ["--model", AUDIT_AGENT_MODEL, buildAuditHandoffPrompt()], {
12722
+ stdio: "inherit",
12723
+ shell: process.platform === "win32"
12724
+ });
12725
+ if (result.error) {
12726
+ logger.warn("setup", `Claude Code launch failed: ${result.error.message}`);
12727
+ printManualAuditNudge();
12728
+ }
12729
+ }
12730
+ var AUDIT_AGENT_MODEL = "haiku";
12731
+ var init_audit_handoff = __esm(() => {
12732
+ init_dist8();
12733
+ init_logger();
12734
+ init_github_step();
12735
+ init_styles();
12736
+ });
12737
+
12738
+ // src/setup/rules-step.ts
12739
+ function successfulAgentIDs(results, action) {
12740
+ return new Set(results.filter((result) => result.action === action && !result.error).map((result) => result.provider.id()));
12741
+ }
12742
+ function logRuleResults(results) {
12743
+ const ready = results.filter((result) => !result.error && (result.action === "created" || result.action === "updated" || result.action === "unchanged"));
12744
+ const removed = results.filter((result) => !result.error && result.action === "removed");
12745
+ if (ready.length > 0) {
12746
+ log.success(formatSetupSummary(`Rules ready for ${ready.length} agent(s):`, ready.map((result) => ({ label: result.provider.name(), path: result.path }))));
12747
+ }
12748
+ if (removed.length > 0) {
12749
+ log.info(formatSetupSummary(`Rules removed from ${removed.length} agent(s):`, removed.map((result) => ({ label: result.provider.name(), path: result.path })), IconRemove));
12750
+ }
12751
+ }
12752
+ async function stepConfigureAgentRules(selection, mcpResults) {
12753
+ const successfulInstalls = successfulAgentIDs(mcpResults, "install");
12754
+ const successfulRemovals = successfulAgentIDs(mcpResults, "remove");
12755
+ const toInstall = selection.toInstall.filter((provider) => successfulInstalls.has(provider.id()) && isRuleAgent(provider.id()));
12756
+ const toRemove = selection.toRemove.filter((provider) => successfulRemovals.has(provider.id()) && isRuleAgent(provider.id()));
12757
+ const results = [];
12758
+ if (toInstall.length > 0) {
12759
+ const content = await fetchDosuRule();
12760
+ for (const provider of toInstall) {
12761
+ try {
12762
+ const installed = installRuleForAgent(provider.id(), content);
12763
+ if (installed)
12764
+ results.push({ provider, action: installed.action, path: installed.path });
12765
+ } catch (err) {
12766
+ const error = err instanceof Error ? err : new Error(String(err));
12767
+ logger.error("setup", `Rule install failed for ${provider.name()}: ${error.stack ?? error.message}`);
12768
+ log.error(`Failed to install ${provider.name()} rule: ${error.message}`);
12769
+ results.push({
12770
+ provider,
12771
+ action: "not_found",
12772
+ path: rulePathForAgent(provider.id()) ?? "",
12773
+ error
12774
+ });
12775
+ }
12776
+ }
12777
+ }
12778
+ const retainedPaths = new Set(toInstall.map((provider) => rulePathForAgent(provider.id())).filter((path3) => path3 !== null));
12779
+ for (const provider of toRemove) {
12780
+ const path3 = rulePathForAgent(provider.id());
12781
+ if (path3 && retainedPaths.has(path3))
12782
+ continue;
12783
+ try {
11757
12784
  const removed = removeRuleForAgent(provider.id());
11758
12785
  if (removed)
11759
12786
  results.push({ provider, action: removed.action, path: removed.path });
@@ -11779,34 +12806,6 @@ var init_rules_step = __esm(() => {
11779
12806
  init_styles();
11780
12807
  });
11781
12808
 
11782
- // src/auth/errors.ts
11783
- var exports_errors = {};
11784
- __export(exports_errors, {
11785
- OAuthCallbackError: () => OAuthCallbackError
11786
- });
11787
- var OAuthCallbackError;
11788
- var init_errors = __esm(() => {
11789
- OAuthCallbackError = class OAuthCallbackError extends Error {
11790
- error;
11791
- errorCode;
11792
- errorDescription;
11793
- constructor(message, details = {}) {
11794
- super(message);
11795
- this.name = "OAuthCallbackError";
11796
- this.error = details.error;
11797
- this.errorCode = details.errorCode;
11798
- this.errorDescription = details.errorDescription;
11799
- }
11800
- get userMessage() {
11801
- const desc = this.errorDescription ?? this.message;
11802
- if (this.errorCode === "bad_oauth_state" || /state/i.test(desc)) {
11803
- return `Authentication failed: ${desc}. Run \`dosu login\` again.`;
11804
- }
11805
- return `Authentication failed: ${desc}`;
11806
- }
11807
- };
11808
- });
11809
-
11810
12809
  // src/auth/server.ts
11811
12810
  function escapeHtml(value) {
11812
12811
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
@@ -11984,8 +12983,8 @@ h1 {
11984
12983
  async function startCallbackServer(opts = {}) {
11985
12984
  let resolveToken;
11986
12985
  let rejectToken;
11987
- const tokenPromise = new Promise((resolve, reject) => {
11988
- resolveToken = resolve;
12986
+ const tokenPromise = new Promise((resolve2, reject) => {
12987
+ resolveToken = resolve2;
11989
12988
  rejectToken = reject;
11990
12989
  });
11991
12990
  const http = __require("node:http");
@@ -12043,8 +13042,8 @@ async function startCallbackServer(opts = {}) {
12043
13042
  res.writeHead(200, { "Content-Type": "text/html" });
12044
13043
  res.end(buildSuccessHtml(email ?? undefined, opts.successVariant ?? "auth"));
12045
13044
  });
12046
- await new Promise((resolve) => {
12047
- httpServer.listen(0, "localhost", () => resolve());
13045
+ await new Promise((resolve2) => {
13046
+ httpServer.listen(0, "localhost", () => resolve2());
12048
13047
  });
12049
13048
  httpServer.unref();
12050
13049
  const addr = httpServer.address();
@@ -12214,17 +13213,21 @@ __export(exports_flow2, {
12214
13213
  stepDetectTools: () => stepDetectTools,
12215
13214
  stepConfigureTools: () => stepConfigureTools,
12216
13215
  runSetup: () => runSetup,
12217
- runInstallSkill: () => runInstallSkill
13216
+ runInstallSkill: () => runInstallSkill,
13217
+ cliAuthFailureReason: () => cliAuthFailureReason
12218
13218
  });
12219
- import { randomUUID } from "node:crypto";
13219
+ import { randomUUID as randomUUID3 } from "node:crypto";
13220
+ function trackInBackground(tracking) {
13221
+ tracking.catch(() => {});
13222
+ }
12220
13223
  async function runSetup(opts = {}) {
12221
- const onboardingRunID = randomUUID();
13224
+ const onboardingRunID = randomUUID3();
12222
13225
  logger.info("setup", `Setup flow started${opts.deploymentID ? ` deployment=${opts.deploymentID}` : ""}${opts.mode ? ` mode=${opts.mode}` : ""}`);
12223
13226
  intro("Dosu CLI Setup");
12224
- await trackCliOnboardingPreAuthEvent(onboardingRunID, "cli_onboarding_launch_attempted", {
13227
+ trackInBackground(trackCliOnboardingPreAuthEvent(onboardingRunID, "cli_onboarding_launch_attempted", {
12225
13228
  has_deployment_option: Boolean(opts.deploymentID),
12226
13229
  mode_option: opts.mode
12227
- });
13230
+ }));
12228
13231
  let cfg = loadConfig();
12229
13232
  applyModeOverride(cfg, opts);
12230
13233
  if (opts.deploymentID) {
@@ -12235,7 +13238,7 @@ async function runSetup(opts = {}) {
12235
13238
  if (!authed)
12236
13239
  return;
12237
13240
  cfg = authed.cfg;
12238
- await trackCliOnboardingEvent(cfg, onboardingRunID, "cli_onboarding_auth_completed");
13241
+ trackInBackground(trackCliOnboardingEvent(cfg, onboardingRunID, "cli_onboarding_auth_completed"));
12239
13242
  let apiClient = new Client(cfg);
12240
13243
  let cloudSetupContext = null;
12241
13244
  if (cfg.mode !== MODE_OSS) {
@@ -12243,39 +13246,39 @@ async function runSetup(opts = {}) {
12243
13246
  s.start("Loading your workspace...");
12244
13247
  cloudSetupContext = await resolveCloudSetupContext(cfg);
12245
13248
  if (!cloudSetupContext) {
12246
- await trackCliOnboardingEvent(cfg, onboardingRunID, "cli_onboarding_failed", {
13249
+ trackInBackground(trackCliOnboardingEvent(cfg, onboardingRunID, "cli_onboarding_failed", {
12247
13250
  reason: "cloud_setup_context_failed"
12248
- });
13251
+ }));
12249
13252
  s.stop("Workspace load failed");
12250
13253
  return;
12251
13254
  }
12252
13255
  s.stop("Workspace loaded");
12253
13256
  }
12254
- await trackCliOnboardingEvent(cfg, onboardingRunID, "cli_onboarding_started", {
13257
+ trackInBackground(trackCliOnboardingEvent(cfg, onboardingRunID, "cli_onboarding_started", {
12255
13258
  flow_kind: cloudSetupContext ? cloudSetupContext.finishedOnboarding ? "setup" : "onboarding" : "oss"
12256
- });
13259
+ }));
12257
13260
  const needsHandshake = cfg.mode !== MODE_OSS && cloudSetupContext !== null && !cloudSetupContext.finishedOnboarding && !opts.deploymentID;
12258
13261
  if (needsHandshake) {
12259
13262
  const handshook = await stepSetupHandshake(cfg, onboardingRunID);
12260
13263
  if (!handshook) {
12261
- await trackCliOnboardingEvent(cfg, onboardingRunID, "cli_onboarding_failed", {
13264
+ trackInBackground(trackCliOnboardingEvent(cfg, onboardingRunID, "cli_onboarding_failed", {
12262
13265
  reason: "web_onboarding_incomplete"
12263
- });
13266
+ }));
12264
13267
  return;
12265
13268
  }
12266
13269
  apiClient = new Client(cfg);
12267
13270
  const refreshed = await resolveCloudSetupContext(cfg);
12268
13271
  if (refreshed === null) {
12269
- await trackCliOnboardingEvent(cfg, onboardingRunID, "cli_onboarding_failed", {
13272
+ trackInBackground(trackCliOnboardingEvent(cfg, onboardingRunID, "cli_onboarding_failed", {
12270
13273
  reason: "cloud_setup_context_failed"
12271
- });
13274
+ }));
12272
13275
  return;
12273
13276
  }
12274
13277
  if (!refreshed.finishedOnboarding) {
12275
13278
  log.warn("Your account still needs onboarding. Finish it in the browser at the Dosu app, then re-run `dosu setup`.");
12276
- await trackCliOnboardingEvent(cfg, onboardingRunID, "cli_onboarding_failed", {
13279
+ trackInBackground(trackCliOnboardingEvent(cfg, onboardingRunID, "cli_onboarding_failed", {
12277
13280
  reason: "onboarding_incomplete_after_handshake"
12278
- });
13281
+ }));
12279
13282
  process.exitCode = 1;
12280
13283
  return;
12281
13284
  }
@@ -12284,43 +13287,43 @@ async function runSetup(opts = {}) {
12284
13287
  if (!cfg.active_account?.target?.deployment_id || opts.deploymentID) {
12285
13288
  const ok = await resolveDeployment(apiClient, cfg, opts);
12286
13289
  if (!ok) {
12287
- await trackCliOnboardingEvent(cfg, onboardingRunID, "cli_onboarding_failed", {
13290
+ trackInBackground(trackCliOnboardingEvent(cfg, onboardingRunID, "cli_onboarding_failed", {
12288
13291
  reason: "deployment_resolution_failed"
12289
- });
13292
+ }));
12290
13293
  return;
12291
13294
  }
12292
13295
  }
12293
13296
  const apiKey = await stepMintAPIKey(apiClient, cfg);
12294
13297
  if (!apiKey) {
12295
- await trackCliOnboardingEvent(cfg, onboardingRunID, "cli_onboarding_failed", {
13298
+ trackInBackground(trackCliOnboardingEvent(cfg, onboardingRunID, "cli_onboarding_failed", {
12296
13299
  reason: "api_key_failed"
12297
- });
13300
+ }));
12298
13301
  return;
12299
13302
  }
12300
13303
  updateTarget(cfg, { api_key: apiKey });
12301
13304
  saveConfig(cfg);
12302
13305
  const configured = await stepConfigureMcpTools(cfg);
12303
13306
  if (configured === null) {
12304
- await trackCliOnboardingEvent(cfg, onboardingRunID, "cli_onboarding_cancelled", {
13307
+ trackInBackground(trackCliOnboardingEvent(cfg, onboardingRunID, "cli_onboarding_cancelled", {
12305
13308
  reason: "mcp_selection_cancelled"
12306
- });
13309
+ }));
12307
13310
  return;
12308
13311
  }
12309
13312
  const mcpConfiguredThisRun = configured.some((result) => result.action === "install" || result.action === "skip");
12310
13313
  const configuredProviders = configured.filter((result) => (result.action === "install" || result.action === "skip") && !result.error);
12311
13314
  const mcpCompleted = configuredProviders.length > 0;
12312
13315
  if (mcpCompleted) {
12313
- await trackCliOnboardingEvent(cfg, onboardingRunID, "cli_onboarding_mcp_configured", {
13316
+ trackInBackground(trackCliOnboardingEvent(cfg, onboardingRunID, "cli_onboarding_mcp_configured", {
12314
13317
  provider_count: configuredProviders.length,
12315
13318
  providers: configuredProviders.map((result) => result.provider.id())
12316
- });
13319
+ }));
12317
13320
  }
12318
13321
  let skillCompleted = false;
12319
13322
  const skillProviders = configuredProviders.map((result) => result.provider).filter((provider) => skillInstallTargetForProvider(provider.id()) !== null);
12320
13323
  if (skillProviders.length > 0) {
12321
13324
  skillCompleted = await runInstallSkill(skillProviders);
12322
13325
  if (skillCompleted) {
12323
- await trackCliOnboardingEvent(cfg, onboardingRunID, "cli_onboarding_skill_installed");
13326
+ trackInBackground(trackCliOnboardingEvent(cfg, onboardingRunID, "cli_onboarding_skill_installed"));
12324
13327
  }
12325
13328
  }
12326
13329
  let agentsMdCompleted = false;
@@ -12332,11 +13335,11 @@ async function runSetup(opts = {}) {
12332
13335
  handoffToAudit = await offerAuditHandoff();
12333
13336
  }
12334
13337
  if (mcpCompleted || skillCompleted || agentsMdCompleted) {
12335
- await trackCliOnboardingEvent(cfg, onboardingRunID, "cli_onboarding_completed", {
13338
+ trackInBackground(trackCliOnboardingEvent(cfg, onboardingRunID, "cli_onboarding_completed", {
12336
13339
  completed_mcp: mcpCompleted,
12337
13340
  completed_skill: skillCompleted,
12338
13341
  completed_agents_md: agentsMdCompleted
12339
- });
13342
+ }));
12340
13343
  }
12341
13344
  if (cfg.mode === MODE_OSS) {
12342
13345
  outro("Setup complete! Using open-source libraries only.\n\nTips: Run `dosu setup --mode cloud` to connect your own repos.");
@@ -12448,7 +13451,7 @@ async function stepAuthenticate(existingCfg, onboardingRunID) {
12448
13451
  }
12449
13452
  }
12450
13453
  if (onboardingRunID) {
12451
- await trackCliOnboardingPreAuthEvent(onboardingRunID, "cli_onboarding_auth_started");
13454
+ trackInBackground(trackCliOnboardingPreAuthEvent(onboardingRunID, "cli_onboarding_auth_started"));
12452
13455
  }
12453
13456
  return await openBrowserForSetup(cfg, onboardingRunID);
12454
13457
  }
@@ -12485,25 +13488,30 @@ async function openBrowserForSetup(cfg, onboardingRunID) {
12485
13488
  } catch (err) {
12486
13489
  const msg = err instanceof Error ? err.stack ?? err.message : String(err);
12487
13490
  logger.error("setup", `Auth failed: ${msg}`);
12488
- const { OAuthCallbackError: OAuthCallbackError2 } = await Promise.resolve().then(() => (init_errors(), exports_errors));
12489
- if (err instanceof OAuthCallbackError2) {
13491
+ if (err instanceof OAuthCallbackError) {
12490
13492
  log.error(err.userMessage);
12491
13493
  if (onboardingRunID) {
12492
- await trackCliOnboardingPreAuthEvent(onboardingRunID, "cli_onboarding_auth_failed", {
12493
- reason: err.errorCode ?? err.errorDescription ?? "oauth_callback_error"
12494
- });
13494
+ trackInBackground(trackCliOnboardingPreAuthEvent(onboardingRunID, "cli_onboarding_auth_failed", {
13495
+ reason: cliAuthFailureReason(err)
13496
+ }));
12495
13497
  }
12496
13498
  return null;
12497
13499
  }
12498
13500
  log.error(`Authentication failed: ${err instanceof Error ? err.message : String(err)}`);
12499
13501
  if (onboardingRunID) {
12500
- await trackCliOnboardingPreAuthEvent(onboardingRunID, "cli_onboarding_auth_failed", {
12501
- reason: err instanceof Error ? err.message : String(err)
12502
- });
13502
+ trackInBackground(trackCliOnboardingPreAuthEvent(onboardingRunID, "cli_onboarding_auth_failed", {
13503
+ reason: cliAuthFailureReason(err)
13504
+ }));
12503
13505
  }
12504
13506
  return null;
12505
13507
  }
12506
13508
  }
13509
+ function cliAuthFailureReason(err) {
13510
+ if (!(err instanceof OAuthCallbackError))
13511
+ return "unexpected_auth_error";
13512
+ const code = err.errorCode;
13513
+ return code && OAUTH_ANALYTICS_REASONS.has(code) ? code : "oauth_callback_error";
13514
+ }
12507
13515
  async function stepSetupHandshake(cfg, onboardingRunID) {
12508
13516
  logger.info("setup", "Step: setup handshake");
12509
13517
  log.info("Almost there — finish setting up in the browser and we'll pick up from here.");
@@ -12801,9 +13809,10 @@ function stepShowSummary(results) {
12801
13809
  log.success("All agents already configured. No changes needed.");
12802
13810
  }
12803
13811
  }
12804
- var SETUP_HANDSHAKE_TIMEOUT_MS = 1800000;
13812
+ var OAUTH_ANALYTICS_REASONS, SETUP_HANDSHAKE_TIMEOUT_MS = 1800000;
12805
13813
  var init_flow2 = __esm(() => {
12806
13814
  init_dist8();
13815
+ init_errors();
12807
13816
  init_client();
12808
13817
  init_skill();
12809
13818
  init_config();
@@ -12814,11 +13823,21 @@ var init_flow2 = __esm(() => {
12814
13823
  init_audit_handoff();
12815
13824
  init_rules_step();
12816
13825
  init_styles();
13826
+ OAUTH_ANALYTICS_REASONS = new Set([
13827
+ "access_denied",
13828
+ "bad_oauth_state",
13829
+ "invalid_request",
13830
+ "invalid_scope",
13831
+ "server_error",
13832
+ "temporarily_unavailable",
13833
+ "unauthorized_client",
13834
+ "unsupported_response_type"
13835
+ ]);
12817
13836
  });
12818
13837
 
12819
13838
  // src/commands/insights.ts
12820
- import { existsSync as existsSync18, mkdirSync as mkdirSync9, readdirSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync9 } from "node:fs";
12821
- import { dirname as dirname7, join as join29 } from "node:path";
13839
+ import { existsSync as existsSync20, mkdirSync as mkdirSync10, readdirSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync10 } from "node:fs";
13840
+ import { dirname as dirname7, join as join30 } from "node:path";
12822
13841
  function shuffled(arr) {
12823
13842
  const copy2 = [...arr];
12824
13843
  for (let i2 = copy2.length - 1;i2 > 0; i2--) {
@@ -12922,17 +13941,17 @@ function makeAskFn(cfg) {
12922
13941
  };
12923
13942
  }
12924
13943
  function insightsDir() {
12925
- return join29(getConfigDir(), "insights");
13944
+ return join30(getConfigDir(), "insights");
12926
13945
  }
12927
13946
  function reportPath(timestamp) {
12928
13947
  const dir = insightsDir();
12929
13948
  if (!timestamp)
12930
- return join29(dir, "latest.html");
13949
+ return join30(dir, "latest.html");
12931
13950
  const iso = timestamp.toISOString().slice(0, 19).replace(/:/g, "-");
12932
- return join29(dir, `report-${iso}Z.html`);
13951
+ return join30(dir, `report-${iso}Z.html`);
12933
13952
  }
12934
13953
  function pruneOldReports(dir, keepN) {
12935
- if (!existsSync18(dir))
13954
+ if (!existsSync20(dir))
12936
13955
  return;
12937
13956
  let entries;
12938
13957
  try {
@@ -12944,7 +13963,7 @@ function pruneOldReports(dir, keepN) {
12944
13963
  const reports = entries.filter((f2) => REPORT_FILE_PATTERN.test(f2)).sort().reverse();
12945
13964
  for (const f2 of reports.slice(keepN)) {
12946
13965
  try {
12947
- unlinkSync2(join29(dir, f2));
13966
+ unlinkSync3(join30(dir, f2));
12948
13967
  } catch (err) {
12949
13968
  logger.debug("insights", `failed to prune ${f2}: ${err instanceof Error ? err.message : err}`);
12950
13969
  }
@@ -13017,9 +14036,9 @@ function defaultRunner(cfg) {
13017
14036
  render: renderHTML,
13018
14037
  writeFile: (path3, content) => {
13019
14038
  const dir = dirname7(path3);
13020
- if (!existsSync18(dir))
13021
- mkdirSync9(dir, { recursive: true, mode: 448 });
13022
- writeFileSync9(path3, content, { mode: 384 });
14039
+ if (!existsSync20(dir))
14040
+ mkdirSync10(dir, { recursive: true, mode: 448 });
14041
+ writeFileSync10(path3, content, { mode: 384 });
13023
14042
  },
13024
14043
  prune: pruneOldReports,
13025
14044
  openInBrowser: async (path3) => {
@@ -13088,7 +14107,7 @@ __export(exports_tui, {
13088
14107
  handleLogout: () => handleLogout
13089
14108
  });
13090
14109
  async function runTUI() {
13091
- console.log(import_picocolors25.default.magenta(LOGO));
14110
+ console.log(import_picocolors26.default.magenta(LOGO));
13092
14111
  const cfg = loadConfig();
13093
14112
  while (true) {
13094
14113
  const insightsReady = Boolean(cfg.active_account?.target?.space_id && cfg.active_account?.target?.deployment_id && cfg.active_account?.target?.api_key);
@@ -13202,7 +14221,7 @@ function handleLogout(cfg) {
13202
14221
  saveConfig(cfg);
13203
14222
  log.success("Credentials cleared.");
13204
14223
  }
13205
- var import_picocolors25, LOGO = `
14224
+ var import_picocolors26, LOGO = `
13206
14225
  /$$$$$$$
13207
14226
  | $$__ $$
13208
14227
  | $$ \\ $$ /$$$$$$ /$$$$$$$ /$$ /$$
@@ -13219,7 +14238,7 @@ var init_tui = __esm(() => {
13219
14238
  init_config();
13220
14239
  init_flow2();
13221
14240
  init_styles();
13222
- import_picocolors25 = __toESM(require_picocolors(), 1);
14241
+ import_picocolors26 = __toESM(require_picocolors(), 1);
13223
14242
  });
13224
14243
 
13225
14244
  // src/auth/ticket.ts
@@ -13443,7 +14462,7 @@ Could not open a browser automatically.`);
13443
14462
  throw new Error("Authentication timed out. Run 'dosu login' to try again.");
13444
14463
  }
13445
14464
  function sleep5(ms) {
13446
- return new Promise((resolve) => setTimeout(resolve, ms));
14465
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
13447
14466
  }
13448
14467
  var POLL_INTERVAL_MS = 5000;
13449
14468
  var init_device = __esm(() => {
@@ -13812,7 +14831,15 @@ var init_flow3 = __esm(() => {
13812
14831
  // src/cli/cli.ts
13813
14832
  init_commander();
13814
14833
  init_client();
13815
- import { readFileSync as readFileSync18, unlinkSync as unlinkSync3 } from "node:fs";
14834
+ import {
14835
+ closeSync,
14836
+ constants,
14837
+ fstatSync,
14838
+ openSync,
14839
+ readFileSync as readFileSync19,
14840
+ readSync,
14841
+ unlinkSync as unlinkSync4
14842
+ } from "node:fs";
13816
14843
 
13817
14844
  // src/commands/analytics.ts
13818
14845
  init_commander();
@@ -15987,7 +17014,7 @@ init_dist();
15987
17014
  init_commander();
15988
17015
  init_trpc();
15989
17016
  var import_picocolors19 = __toESM(require_picocolors(), 1);
15990
- import { readFileSync as readFileSync16 } from "node:fs";
17017
+ import { readFileSync as readFileSync17 } from "node:fs";
15991
17018
  function requireConfig10() {
15992
17019
  return requireLoginConfig();
15993
17020
  }
@@ -16163,7 +17190,7 @@ function reviewCommand() {
16163
17190
  let body = opts.body;
16164
17191
  if (opts.bodyFile) {
16165
17192
  try {
16166
- body = readFileSync16(opts.bodyFile, "utf-8");
17193
+ body = readFileSync17(opts.bodyFile, "utf-8");
16167
17194
  } catch (err) {
16168
17195
  console.error(import_picocolors19.default.red(`Failed to read --body-file: ${err.message}`));
16169
17196
  process.exit(1);
@@ -16481,6 +17508,71 @@ function suggestCommand() {
16481
17508
  return cmd;
16482
17509
  }
16483
17510
 
17511
+ // src/commands/telemetry.ts
17512
+ init_commander();
17513
+ init_settings();
17514
+ init_telemetry();
17515
+ function statusPayload() {
17516
+ const settings2 = loadTelemetrySettings();
17517
+ const environmentOverride = telemetryDisabledByEnvironment();
17518
+ const webAppURL = getWebAppURL();
17519
+ const commandWebAppURL = parseTelemetryWebAppURL(webAppURL);
17520
+ const setupWebAppURL = parseTelemetryWebAppURL(webAppURL, process.env.DOSU_DEV === "true");
17521
+ const analyticsReleaseToken = parsePostHogProjectToken(process.env.DOSU_POSTHOG_PROJECT_TOKEN_OVERRIDE ?? "phc_Uo9RxhN0HRk8gZkPlT18BCr7eqsM0MPWVW88HZB95V5");
17522
+ return {
17523
+ schema_version: settings2.schema_version,
17524
+ enabled: isTelemetryEnabled(settings2),
17525
+ destinations: {
17526
+ command_analytics: Boolean(commandWebAppURL && analyticsReleaseToken),
17527
+ setup_analytics: Boolean(setupWebAppURL && analyticsReleaseToken),
17528
+ error_diagnostics: Boolean(parseSentryDsn(process.env.DOSU_CLI_SENTRY_DSN_OVERRIDE ?? "https://f6b3e51dea6f50746316aa1896df76e0@o4505364738670592.ingest.us.sentry.io/4511883653283840"))
17529
+ },
17530
+ environment_override: environmentOverride ?? null,
17531
+ debug_mode: process.env.DOSU_TELEMETRY_DEBUG === "1",
17532
+ telemetry_id: settings2.install_id ?? null
17533
+ };
17534
+ }
17535
+ function printHumanStatus() {
17536
+ const status = statusPayload();
17537
+ console.log(`Telemetry: ${status.enabled ? "enabled" : "disabled"}`);
17538
+ console.log(`Destinations: command analytics PostHog ${status.destinations.command_analytics ? "configured" : "not configured"}, setup analytics Dosu API ${status.destinations.setup_analytics ? "configured" : "not configured"}, error diagnostics Sentry ${status.destinations.error_diagnostics ? "configured" : "not configured"}`);
17539
+ if (status.environment_override) {
17540
+ console.log(`Environment override: ${status.environment_override}`);
17541
+ }
17542
+ if (status.debug_mode)
17543
+ console.log("Debug mode: on (payloads are printed to stderr, not sent)");
17544
+ if (status.telemetry_id)
17545
+ console.log(`Telemetry ID: ${status.telemetry_id}`);
17546
+ console.log("Telemetry fields never include raw command lines, free-form arguments or option values, user source files, local paths, credentials, error messages, or debug.log; setup analytics uses only documented coarse fields and its existing Dosu session header only for API authorization.");
17547
+ }
17548
+ function updateTelemetry(enabled) {
17549
+ if (!setTelemetryEnabled(enabled)) {
17550
+ throw new Error("Could not save telemetry settings");
17551
+ }
17552
+ console.log(`Telemetry ${enabled ? "enabled" : "disabled"}.`);
17553
+ }
17554
+ function telemetryCommand() {
17555
+ const command = new Command("telemetry").description("Manage privacy-preserving telemetry");
17556
+ command.command("status").description("Show telemetry state and destination configuration").option("--json", "Output as JSON").action((opts) => {
17557
+ if (opts.json) {
17558
+ console.log(JSON.stringify(statusPayload(), null, 2));
17559
+ return;
17560
+ }
17561
+ printHumanStatus();
17562
+ });
17563
+ command.command("enable").description("Enable telemetry").action(() => updateTelemetry(true));
17564
+ command.command("disable").description("Disable telemetry").action(() => updateTelemetry(false));
17565
+ command.command("reset").description("Rotate the local pseudonymous telemetry ID (does not delete prior events)").action(() => {
17566
+ const previous = loadTelemetrySettings().install_id;
17567
+ const next = previous ? resetInstallID() : getOrCreateInstallID();
17568
+ if (!next)
17569
+ throw new Error("Could not save telemetry settings");
17570
+ console.log(`Telemetry ID ${previous ? "rotated" : "created"}: ${next}`);
17571
+ console.log("This does not delete events already retained by telemetry providers.");
17572
+ });
17573
+ return command;
17574
+ }
17575
+
16484
17576
  // src/commands/threads.ts
16485
17577
  init_commander();
16486
17578
  init_trpc();
@@ -16672,23 +17764,213 @@ function topicsCommand() {
16672
17764
  return cmd;
16673
17765
  }
16674
17766
 
17767
+ // src/commands/upgrade.ts
17768
+ init_commander();
17769
+ var import_picocolors24 = __toESM(require_picocolors(), 1);
17770
+ import { spawnSync as spawnSync3 } from "node:child_process";
17771
+ import { realpathSync } from "node:fs";
17772
+ import { homedir as homedir5 } from "node:os";
17773
+ import { posix, win32 } from "node:path";
17774
+ var PACKAGE_NAME = "@dosu/cli";
17775
+ var LATEST_PACKAGE = `${PACKAGE_NAME}@latest`;
17776
+ var BREW_MANUAL_COMMAND = "brew upgrade dosu-ai/dosu/dosu";
17777
+ var NPX_COMMAND = "npx -y @dosu/cli@latest";
17778
+ var PROBE_TIMEOUT_MS = 5000;
17779
+ var RELEASES_URL = "https://github.com/dosu-ai/dosu-cli/releases/latest";
17780
+ var PACKAGE_MANAGERS = {
17781
+ npm: {
17782
+ label: "npm",
17783
+ locateArgs: ["root", "-g"],
17784
+ installArgs: ["install", "-g", LATEST_PACKAGE]
17785
+ },
17786
+ pnpm: {
17787
+ label: "pnpm",
17788
+ locateArgs: ["list", "-g", "--depth=0", "--parseable", PACKAGE_NAME],
17789
+ installArgs: ["add", "-g", LATEST_PACKAGE]
17790
+ },
17791
+ yarn: {
17792
+ label: "Yarn Classic",
17793
+ locateArgs: ["--silent", "global", "dir"],
17794
+ installArgs: ["global", "add", LATEST_PACKAGE]
17795
+ }
17796
+ };
17797
+ function isAbsoluteCommandProcessor(value) {
17798
+ return Boolean(value && win32.isAbsolute(value) && win32.basename(value).toLowerCase() === "cmd.exe");
17799
+ }
17800
+ function windowsCommandProcessor(env = process.env) {
17801
+ if (isAbsoluteCommandProcessor(env.ComSpec))
17802
+ return env.ComSpec;
17803
+ const systemRoot = env.SystemRoot && win32.isAbsolute(env.SystemRoot) ? env.SystemRoot : "C:\\Windows";
17804
+ return win32.join(systemRoot, "System32", "cmd.exe");
17805
+ }
17806
+ function safePackageManagerEnv(env) {
17807
+ return {
17808
+ ...env,
17809
+ COREPACK_ENABLE_NETWORK: "0",
17810
+ COREPACK_ENABLE_PROJECT_SPEC: "0",
17811
+ YARN_IGNORE_PATH: "1"
17812
+ };
17813
+ }
17814
+ function buildPackageManagerInvocation(manager, action, platform4 = process.platform, comSpec, env = process.env) {
17815
+ const args = action === "locate" ? PACKAGE_MANAGERS[manager].locateArgs : PACKAGE_MANAGERS[manager].installArgs;
17816
+ const safeEnv = safePackageManagerEnv(env);
17817
+ if (platform4 === "win32") {
17818
+ return {
17819
+ command: isAbsoluteCommandProcessor(comSpec) ? comSpec : windowsCommandProcessor(env),
17820
+ args: ["/d", "/s", "/c", [manager, ...args].join(" ")],
17821
+ env: { ...safeEnv, NoDefaultCurrentDirectoryInExePath: "1" }
17822
+ };
17823
+ }
17824
+ return { command: manager, args: [...args], env: safeEnv };
17825
+ }
17826
+ function packageRoots(manager, platform4, comSpec, env, cwd) {
17827
+ const invocation = buildPackageManagerInvocation(manager, "locate", platform4, comSpec, env);
17828
+ const result = spawnSync3(invocation.command, invocation.args, {
17829
+ cwd,
17830
+ encoding: "utf8",
17831
+ env: invocation.env,
17832
+ shell: false,
17833
+ stdio: ["ignore", "pipe", "ignore"],
17834
+ timeout: PROBE_TIMEOUT_MS
17835
+ });
17836
+ if (result.status !== 0 || typeof result.stdout !== "string")
17837
+ return [];
17838
+ const path3 = platform4 === "win32" ? win32 : posix;
17839
+ const lines = result.stdout.split(/\r?\n/).map((line) => line.trim()).filter((line) => path3.isAbsolute(line));
17840
+ if (manager === "pnpm") {
17841
+ return lines.filter((line) => path3.basename(line) === "cli" && path3.basename(path3.dirname(line)) === "@dosu" && path3.basename(path3.dirname(path3.dirname(line))) === "node_modules");
17842
+ }
17843
+ if (lines.length !== 1)
17844
+ return [];
17845
+ return [
17846
+ manager === "npm" ? path3.join(lines[0], "@dosu", "cli") : path3.join(lines[0], "node_modules", "@dosu", "cli")
17847
+ ];
17848
+ }
17849
+ function owningPackageManager(entrypoint, platform4, comSpec, env = process.env, cwd = homedir5()) {
17850
+ const path3 = platform4 === "win32" ? win32 : posix;
17851
+ if (!entrypoint || !path3.isAbsolute(entrypoint))
17852
+ return null;
17853
+ let realEntrypoint;
17854
+ try {
17855
+ realEntrypoint = realpathSync(entrypoint);
17856
+ } catch {
17857
+ return null;
17858
+ }
17859
+ const owners = Object.keys(PACKAGE_MANAGERS).filter((manager) => packageRoots(manager, platform4, comSpec, env, cwd).some((root) => {
17860
+ try {
17861
+ const relative2 = path3.relative(realpathSync(root), realEntrypoint);
17862
+ return relative2 !== ".." && !relative2.startsWith(`..${path3.sep}`) && !path3.isAbsolute(relative2);
17863
+ } catch {
17864
+ return false;
17865
+ }
17866
+ }));
17867
+ return owners.length === 1 ? owners[0] : null;
17868
+ }
17869
+ function upgradePlan(channel, manager, platform4, comSpec, env = process.env) {
17870
+ if (channel === "homebrew") {
17871
+ return {
17872
+ label: "Homebrew",
17873
+ command: "brew",
17874
+ args: ["upgrade", "dosu-ai/dosu/dosu"],
17875
+ manualCommand: BREW_MANUAL_COMMAND
17876
+ };
17877
+ }
17878
+ if (channel === "npm" && manager) {
17879
+ const invocation = buildPackageManagerInvocation(manager, "install", platform4, comSpec, env);
17880
+ return {
17881
+ label: PACKAGE_MANAGERS[manager].label,
17882
+ ...invocation,
17883
+ manualCommand: [manager, ...PACKAGE_MANAGERS[manager].installArgs].join(" ")
17884
+ };
17885
+ }
17886
+ return null;
17887
+ }
17888
+ function printNonGlobalPackageGuidance() {
17889
+ console.log("This Dosu copy is not a uniquely identified global package installation, so it was not changed.");
17890
+ console.log(`
17891
+ Run the latest version without installing:
17892
+ ${NPX_COMMAND}`);
17893
+ console.log(`
17894
+ Or install Dosu globally with your package manager:`);
17895
+ for (const manager of Object.keys(PACKAGE_MANAGERS)) {
17896
+ console.log(` ${manager} ${PACKAGE_MANAGERS[manager].installArgs.join(" ")}`);
17897
+ }
17898
+ }
17899
+ function runUpgrade(channel = INSTALL_CHANNEL, options = {}) {
17900
+ const platform4 = options.platform ?? process.platform;
17901
+ const env = options.env ?? process.env;
17902
+ const cwd = options.cwd ?? homedir5();
17903
+ let manager = null;
17904
+ if (channel === "npm") {
17905
+ if (isNpxInvocation(channel, env)) {
17906
+ printNonGlobalPackageGuidance();
17907
+ return 1;
17908
+ }
17909
+ manager = owningPackageManager(options.entrypoint ?? process.argv[1], platform4, options.comSpec, env, cwd);
17910
+ if (!manager) {
17911
+ printNonGlobalPackageGuidance();
17912
+ return 1;
17913
+ }
17914
+ }
17915
+ const plan = upgradePlan(channel, manager, platform4, options.comSpec, env);
17916
+ if (!plan) {
17917
+ console.log("Automatic upgrades are not available for this installation yet.");
17918
+ console.log(`Download the latest release:
17919
+ ${RELEASES_URL}`);
17920
+ return 1;
17921
+ }
17922
+ console.log(`Updating Dosu with ${plan.label}...`);
17923
+ console.log(` ${plan.manualCommand}
17924
+ `);
17925
+ const result = spawnSync3(plan.command, plan.args, {
17926
+ ...channel === "npm" ? { cwd } : {},
17927
+ ...plan.env ? { env: plan.env } : {},
17928
+ shell: false,
17929
+ stdio: "inherit"
17930
+ });
17931
+ if (result.error || result.status !== 0) {
17932
+ console.error(`
17933
+ Could not update Dosu automatically.`);
17934
+ console.error(`Run manually:
17935
+ ${plan.manualCommand}`);
17936
+ return result.status && result.status > 0 ? result.status : 1;
17937
+ }
17938
+ console.log(import_picocolors24.default.green(`
17939
+ ✓ Update command completed.`));
17940
+ console.log('Run "dosu --version" to verify.');
17941
+ return 0;
17942
+ }
17943
+ function upgradeCommand() {
17944
+ return new Command("upgrade").description("Update Dosu to the latest version").action(() => {
17945
+ const status = runUpgrade();
17946
+ if (status !== 0)
17947
+ process.exitCode = status;
17948
+ });
17949
+ }
17950
+
16675
17951
  // src/cli/cli.ts
16676
17952
  init_config();
16677
17953
  init_logger();
16678
17954
  init_providers();
16679
17955
  init_styles();
17956
+ init_settings();
17957
+ init_telemetry();
16680
17958
  init_skill_update_check();
16681
17959
 
16682
17960
  // src/version/update-check.ts
16683
17961
  init_config();
16684
17962
  init_logger();
16685
- var import_picocolors24 = __toESM(require_picocolors(), 1);
16686
- import { existsSync as existsSync19, mkdirSync as mkdirSync10, readFileSync as readFileSync17, writeFileSync as writeFileSync10 } from "node:fs";
16687
- import { join as join30 } from "node:path";
17963
+ var import_picocolors25 = __toESM(require_picocolors(), 1);
17964
+ import { existsSync as existsSync21, mkdirSync as mkdirSync11, readFileSync as readFileSync18, writeFileSync as writeFileSync11 } from "node:fs";
17965
+ import { join as join31 } from "node:path";
16688
17966
  var CACHE_FILENAME3 = "update-check.json";
16689
- var CHECK_INTERVAL_MS3 = 24 * 60 * 60 * 1000;
16690
- var FETCH_TIMEOUT_MS3 = 5000;
17967
+ var CHECK_INTERVAL_MS3 = 6 * 60 * 60 * 1000;
17968
+ var FETCH_TIMEOUT_MS3 = 1000;
16691
17969
  var REGISTRY_URL = "https://registry.npmjs.org/-/package/@dosu/cli/dist-tags";
17970
+ var SEMVER_PATTERN = /^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
17971
+ function isValidVersion(value) {
17972
+ return typeof value === "string" && SEMVER_PATTERN.test(value);
17973
+ }
16692
17974
  function stripPrerelease(version) {
16693
17975
  return version.replace(/[-+].*$/, "");
16694
17976
  }
@@ -16707,15 +17989,15 @@ function isNewerVersion(latest, current) {
16707
17989
  return false;
16708
17990
  }
16709
17991
  function getCachePath3() {
16710
- return join30(getConfigDir(), CACHE_FILENAME3);
17992
+ return join31(getConfigDir(), CACHE_FILENAME3);
16711
17993
  }
16712
17994
  function readCache() {
16713
17995
  try {
16714
17996
  const path3 = getCachePath3();
16715
- if (!existsSync19(path3))
17997
+ if (!existsSync21(path3))
16716
17998
  return null;
16717
- const data = JSON.parse(readFileSync17(path3, "utf-8"));
16718
- if (typeof data.lastCheck === "number" && typeof data.latestVersion === "string") {
17999
+ const data = JSON.parse(readFileSync18(path3, "utf-8"));
18000
+ if (typeof data.lastCheck === "number" && isValidVersion(data.latestVersion)) {
16719
18001
  return data;
16720
18002
  }
16721
18003
  return null;
@@ -16726,10 +18008,10 @@ function readCache() {
16726
18008
  function writeCache(cache) {
16727
18009
  try {
16728
18010
  const dir = getConfigDir();
16729
- if (!existsSync19(dir)) {
16730
- mkdirSync10(dir, { recursive: true, mode: 448 });
18011
+ if (!existsSync21(dir)) {
18012
+ mkdirSync11(dir, { recursive: true, mode: 448 });
16731
18013
  }
16732
- writeFileSync10(getCachePath3(), JSON.stringify(cache), { mode: 384 });
18014
+ writeFileSync11(getCachePath3(), JSON.stringify(cache), { mode: 384 });
16733
18015
  } catch {}
16734
18016
  }
16735
18017
  async function fetchLatestVersion() {
@@ -16741,46 +18023,71 @@ async function fetchLatestVersion() {
16741
18023
  return null;
16742
18024
  const data = await resp.json();
16743
18025
  const latest = data.latest;
16744
- return typeof latest === "string" ? latest : null;
18026
+ return isValidVersion(latest) ? latest : null;
16745
18027
  } catch {
16746
18028
  return null;
16747
18029
  } finally {
16748
18030
  clearTimeout(timeout);
16749
18031
  }
16750
18032
  }
16751
- function buildUpdateHint(channel) {
16752
- if (channel === "homebrew")
16753
- return 'Run "brew upgrade dosu"';
16754
- if (channel === "binary")
16755
- return "Download from https://github.com/dosu-ai/dosu-cli/releases";
16756
- return 'Run "npm update -g @dosu/cli"';
18033
+ function buildUpdateHint(channel, npx = false) {
18034
+ if (channel === "npm" && npx) {
18035
+ return 'Use "npx -y @dosu/cli@latest" for the next Dosu command';
18036
+ }
18037
+ return 'Run "dosu upgrade"';
18038
+ }
18039
+ function buildUpdateNotice(current, latest, channel, interactive, npx = false) {
18040
+ const hint = buildUpdateHint(channel, npx);
18041
+ if (interactive) {
18042
+ const versionMessage = `Update available: ${current} → ${latest}`;
18043
+ const contentWidth = Math.max(versionMessage.length, hint.length);
18044
+ const innerWidth = contentWidth + 4;
18045
+ const horizontal = "─".repeat(innerWidth);
18046
+ const emptyLine = `│${" ".repeat(innerWidth)}│`;
18047
+ const frameLines = [
18048
+ `┌${horizontal}┐`,
18049
+ emptyLine,
18050
+ `│ ${versionMessage}${" ".repeat(contentWidth - versionMessage.length)} │`,
18051
+ `│ ${hint}${" ".repeat(contentWidth - hint.length)} │`,
18052
+ emptyLine,
18053
+ `└${horizontal}┘`
18054
+ ];
18055
+ return `
18056
+ ${frameLines.map((line) => import_picocolors25.default.yellow(line)).join(`
18057
+ `)}
18058
+ `;
18059
+ }
18060
+ const agentAction = hint[0].toLowerCase() + hint.slice(1);
18061
+ const verifyCommand = npx ? "npx -y @dosu/cli@latest --version" : "dosu --version";
18062
+ return `
18063
+ [dosu:update] Update available: ${current} → ${latest}
18064
+ ` + `Tell the user Dosu CLI is outdated. After they approve, ${agentAction}, ` + `then verify with "${verifyCommand}".
18065
+ `;
16757
18066
  }
16758
18067
  function displayNotice2(current, latest) {
16759
- const msg = `
16760
- ${import_picocolors24.default.yellow(` Update available: ${current} → ${latest}`)}
16761
- ` + `${import_picocolors24.default.dim(` ${buildUpdateHint(INSTALL_CHANNEL)}`)}
16762
- `;
16763
- console.error(msg);
18068
+ console.error(buildUpdateNotice(current, latest, INSTALL_CHANNEL, process.stderr.isTTY === true, isNpxInvocation()));
16764
18069
  }
16765
- function checkForUpdates() {
18070
+ async function checkForUpdates() {
16766
18071
  try {
16767
18072
  const cache = readCache();
16768
- if (cache && isNewerVersion(cache.latestVersion, VERSION)) {
16769
- displayNotice2(VERSION, cache.latestVersion);
16770
- }
16771
18073
  const isStale = !cache || Date.now() - cache.lastCheck > CHECK_INTERVAL_MS3;
16772
- if (isStale) {
16773
- fetchLatestVersion().then((latest) => {
16774
- writeCache({
16775
- lastCheck: Date.now(),
16776
- latestVersion: latest ?? cache?.latestVersion ?? VERSION
16777
- });
16778
- if (latest) {
16779
- logger.debug("update-check", `Cached latest version: ${latest}`);
16780
- }
16781
- }).catch((err) => {
16782
- logger.error("update-check", `Background fetch failed: ${err}`);
16783
- });
18074
+ if (!isStale) {
18075
+ if (isNewerVersion(cache.latestVersion, VERSION)) {
18076
+ displayNotice2(VERSION, cache.latestVersion);
18077
+ }
18078
+ return;
18079
+ }
18080
+ const latest = await fetchLatestVersion();
18081
+ const latestKnownVersion = latest ?? cache?.latestVersion ?? VERSION;
18082
+ writeCache({
18083
+ lastCheck: Date.now(),
18084
+ latestVersion: latestKnownVersion
18085
+ });
18086
+ if (latest) {
18087
+ logger.debug("update-check", `Cached latest version: ${latest}`);
18088
+ }
18089
+ if (isNewerVersion(latestKnownVersion, VERSION)) {
18090
+ displayNotice2(VERSION, latestKnownVersion);
16784
18091
  }
16785
18092
  } catch (err) {
16786
18093
  logger.error("update-check", `Update check failed: ${err}`);
@@ -16793,6 +18100,107 @@ function isHookEntrypointInvocation(argv) {
16793
18100
  const i2 = argv.indexOf("hooks");
16794
18101
  return i2 >= 0 && HOOK_ENTRYPOINTS.has(argv[i2 + 1] ?? "");
16795
18102
  }
18103
+ function shouldRunBackgroundChecks(actionName, argv) {
18104
+ return actionName !== "upgrade" && !isHookEntrypointInvocation(argv);
18105
+ }
18106
+ var TELEMETRY_EXCLUDED_COMMANDS = new Set([
18107
+ "hooks user-prompt-submit",
18108
+ "hooks post-tool-use",
18109
+ "hooks stop"
18110
+ ]);
18111
+ var TELEMETRY_FLUSH_TIMEOUT_MS = 750;
18112
+ var MAX_TELEMETRY_CONFIG_BYTES = 64 * 1024;
18113
+
18114
+ class CliUsageError extends Error {
18115
+ exitCode = 1;
18116
+ constructor(message) {
18117
+ super(message);
18118
+ this.name = "CliUsageError";
18119
+ }
18120
+ }
18121
+ function commandTelemetryName(actionCommand) {
18122
+ const segments = [];
18123
+ let current = actionCommand;
18124
+ while (current.parent) {
18125
+ segments.unshift(current.name());
18126
+ current = current.parent;
18127
+ }
18128
+ if (segments.length > 0)
18129
+ return segments.join(" ");
18130
+ return actionCommand.args.length > 0 ? "unknown" : "tui";
18131
+ }
18132
+ function shouldTrackCommand(command) {
18133
+ if (command === "telemetry" || command.startsWith("telemetry "))
18134
+ return false;
18135
+ return !TELEMETRY_EXCLUDED_COMMANDS.has(command);
18136
+ }
18137
+ function commandTelemetryContext() {
18138
+ try {
18139
+ const cfg = loadConfigForTelemetry();
18140
+ if (!cfg)
18141
+ return { mode: "cloud", isAuthenticated: false };
18142
+ const authenticated = isAuthenticated(cfg);
18143
+ const accessToken = authenticated ? cfg.active_account.session.access_token : "";
18144
+ const configUserID = getConfigUserID(cfg);
18145
+ const tokenUserID = getAccessTokenUserID(accessToken);
18146
+ const userID = configUserID && configUserID === tokenUserID ? configUserID : undefined;
18147
+ const email = userID ? getAccessTokenEmail(accessToken) : undefined;
18148
+ return {
18149
+ mode: cfg.mode === MODE_OSS ? "oss" : "cloud",
18150
+ isAuthenticated: authenticated,
18151
+ ...userID ? { user: { id: userID, ...email ? { email } : {} } } : {}
18152
+ };
18153
+ } catch {
18154
+ return { mode: "cloud", isAuthenticated: false };
18155
+ }
18156
+ }
18157
+ function loadConfigForTelemetry() {
18158
+ let fd;
18159
+ try {
18160
+ const nonblocking = typeof constants.O_NONBLOCK === "number" ? constants.O_NONBLOCK : 0;
18161
+ fd = openSync(getConfigPath(), constants.O_RDONLY | nonblocking);
18162
+ const file = fstatSync(fd);
18163
+ if (!file.isFile() || file.size > MAX_TELEMETRY_CONFIG_BYTES)
18164
+ return;
18165
+ const content = Buffer.alloc(MAX_TELEMETRY_CONFIG_BYTES + 1);
18166
+ const bytesRead = readSync(fd, content, 0, content.byteLength, 0);
18167
+ if (bytesRead > MAX_TELEMETRY_CONFIG_BYTES)
18168
+ return;
18169
+ return parseConfig(JSON.parse(content.subarray(0, bytesRead).toString("utf8")));
18170
+ } catch {
18171
+ return;
18172
+ } finally {
18173
+ if (fd !== undefined) {
18174
+ try {
18175
+ closeSync(fd);
18176
+ } catch {}
18177
+ }
18178
+ }
18179
+ }
18180
+ function startTelemetry(telemetry, command, context) {
18181
+ if (!telemetry)
18182
+ return false;
18183
+ try {
18184
+ telemetry.start(command, context);
18185
+ return true;
18186
+ } catch {
18187
+ return false;
18188
+ }
18189
+ }
18190
+ async function finishTelemetry(operation) {
18191
+ let timeout;
18192
+ try {
18193
+ await Promise.race([
18194
+ Promise.resolve().then(operation),
18195
+ new Promise((resolve2) => {
18196
+ timeout = setTimeout(resolve2, TELEMETRY_FLUSH_TIMEOUT_MS);
18197
+ })
18198
+ ]);
18199
+ } catch {} finally {
18200
+ if (timeout)
18201
+ clearTimeout(timeout);
18202
+ }
18203
+ }
16796
18204
  function suggestClosestCommand(input, program2) {
16797
18205
  let best;
16798
18206
  let bestDistance = Infinity;
@@ -16819,16 +18227,34 @@ function editDistance2(a3, b2) {
16819
18227
  }
16820
18228
  return prev[b2.length];
16821
18229
  }
16822
- function createProgram() {
18230
+ function createProgram(options = {}) {
16823
18231
  const program2 = new Command;
16824
- program2.name("dosu").description("Dosu CLI - Manage MCP servers for AI tools").version(getVersionString(), "-v, --version").helpCommand("help [command]", "Show help for a command").option("--debug", "Enable debug logging to stderr", false).hook("preAction", (thisCommand) => {
18232
+ let telemetryStarted = false;
18233
+ let telemetryValidationFailure = false;
18234
+ program2.name("dosu").description("Dosu CLI - Manage MCP servers for AI tools").version(getVersionString(), "-v, --version").helpCommand("help [command]", "Show help for a command").option("--debug", "Enable debug logging to stderr", false).hook("preAction", async (thisCommand, actionCommand) => {
16825
18235
  const opts = thisCommand.optsWithGlobals();
16826
18236
  logger.init({ debug: opts.debug });
16827
- if (!isHookEntrypointInvocation(process.argv)) {
16828
- checkForUpdates();
18237
+ if (shouldRunBackgroundChecks(actionCommand.name(), process.argv)) {
18238
+ if (!process.env.CI) {
18239
+ await checkForUpdates();
18240
+ }
16829
18241
  checkForSkillUpdates();
16830
18242
  checkForReadyTasks();
16831
18243
  }
18244
+ const command = commandTelemetryName(actionCommand);
18245
+ if (options.telemetry && shouldTrackCommand(command)) {
18246
+ telemetryStarted = startTelemetry(options.telemetry, command, commandTelemetryContext());
18247
+ }
18248
+ }).hook("postAction", async () => {
18249
+ const exitCode = Number(process.exitCode ?? 0);
18250
+ const telemetry = options.telemetry;
18251
+ if (telemetryStarted && telemetry) {
18252
+ if (telemetryValidationFailure) {
18253
+ await finishTelemetry(() => telemetry.fail(new CliUsageError("expected CLI usage error")));
18254
+ } else {
18255
+ await finishTelemetry(() => telemetry.complete(Number.isFinite(exitCode) ? exitCode : 1));
18256
+ }
18257
+ }
16832
18258
  }).allowExcessArguments(true).action(async () => {
16833
18259
  const command = program2.args[0];
16834
18260
  if (command !== undefined) {
@@ -16840,6 +18266,7 @@ function createProgram() {
16840
18266
  message += `
16841
18267
  Run 'dosu --help' to see available commands.`;
16842
18268
  console.error(message);
18269
+ telemetryValidationFailure = true;
16843
18270
  process.exitCode = 1;
16844
18271
  return;
16845
18272
  }
@@ -17001,20 +18428,20 @@ Run 'dosu --help' to see available commands.`;
17001
18428
  try {
17002
18429
  provider = getProvider(toolId.toLowerCase());
17003
18430
  } catch {
17004
- throw new Error(`unknown tool '${toolId}'. Use 'dosu mcp list' to see available tools`);
18431
+ throw new CliUsageError(`unknown tool '${toolId}'. Use 'dosu mcp list' to see available tools`);
17005
18432
  }
17006
18433
  const cfg = loadConfig();
17007
18434
  if (!isAuthenticated(cfg)) {
17008
- throw new Error("not logged in. Run 'dosu login' first");
18435
+ throw new CliUsageError("not logged in. Run 'dosu login' first");
17009
18436
  }
17010
18437
  if (isTokenExpired(cfg) && !await ensureFreshSession(cfg)) {
17011
- throw new Error("session expired. Run 'dosu login' to re-authenticate");
18438
+ throw new CliUsageError("session expired. Run 'dosu login' to re-authenticate");
17012
18439
  }
17013
18440
  if (cfg.mode !== MODE_OSS && !cfg.active_account?.target?.deployment_id) {
17014
- throw new Error("no MCP selected. Run 'dosu' to open the TUI and select an MCP");
18441
+ throw new CliUsageError("no MCP selected. Run 'dosu' to open the TUI and select an MCP");
17015
18442
  }
17016
18443
  if (!cfg.active_account?.target?.api_key) {
17017
- throw new Error("no API key available. Run 'dosu setup' to create one");
18444
+ throw new CliUsageError("no API key available. Run 'dosu setup' to create one");
17018
18445
  }
17019
18446
  if (provider.id() === "manual") {
17020
18447
  provider.install(cfg, false, { showSecret: opts.showSecret });
@@ -17067,9 +18494,11 @@ Use 'dosu mcp add <agent>' to add Dosu MCP to a tool.`);
17067
18494
  program2.addCommand(reviewCommand());
17068
18495
  program2.addCommand(sourcesCommand());
17069
18496
  program2.addCommand(suggestCommand());
18497
+ program2.addCommand(telemetryCommand());
17070
18498
  program2.addCommand(topicsCommand());
17071
18499
  program2.addCommand(threadsCommand());
17072
18500
  program2.addCommand(skillCommand());
18501
+ program2.addCommand(upgradeCommand());
17073
18502
  program2.command("setup").description("Set up Dosu MCP for your AI tools").option("--deployment <id>", "Skip to tool configuration for a specific MCP").option("--mode <mode>", "Force OSS or Cloud mode, skipping the interactive prompt (oss|cloud)").option("--agent", "Run non-interactive setup designed for coding agents (requires --tool)").option("--tool <id>", "Configure a single AI tool by id (claude, cursor, codex, …). Required with --agent.").option("--login-ticket <ticket>", "Resume an --agent setup by redeeming a ticket from a previous run").action(async (opts) => {
17074
18503
  if (opts.agent) {
17075
18504
  if (!opts.tool) {
@@ -17092,14 +18521,14 @@ Use 'dosu mcp add <agent>' to add Dosu MCP to a tool.`);
17092
18521
  return;
17093
18522
  }
17094
18523
  if (opts.tool || opts.loginTicket) {
17095
- throw new Error("--tool and --login-ticket require --agent");
18524
+ throw new CliUsageError("--tool and --login-ticket require --agent");
17096
18525
  }
17097
18526
  const { runSetup: runSetup2 } = await Promise.resolve().then(() => (init_flow2(), exports_flow2));
17098
18527
  let mode;
17099
18528
  if (opts.mode !== undefined) {
17100
18529
  const normalized = opts.mode.toLowerCase();
17101
18530
  if (normalized !== "oss" && normalized !== "cloud") {
17102
- throw new Error(`invalid --mode value '${opts.mode}' (expected 'oss' or 'cloud')`);
18531
+ throw new CliUsageError(`invalid --mode value '${opts.mode}' (expected 'oss' or 'cloud')`);
17103
18532
  }
17104
18533
  mode = normalized;
17105
18534
  }
@@ -17109,7 +18538,7 @@ Use 'dosu mcp add <agent>' to add Dosu MCP to a tool.`);
17109
18538
  const logPath = logger.getLogPath();
17110
18539
  if (opts.clear) {
17111
18540
  try {
17112
- unlinkSync3(logPath);
18541
+ unlinkSync4(logPath);
17113
18542
  console.log("Log file deleted.");
17114
18543
  } catch {
17115
18544
  console.log("No log file to delete.");
@@ -17119,7 +18548,7 @@ Use 'dosu mcp add <agent>' to add Dosu MCP to a tool.`);
17119
18548
  if (opts.tail !== undefined) {
17120
18549
  const n2 = typeof opts.tail === "string" ? parseInt(opts.tail, 10) || 50 : 50;
17121
18550
  try {
17122
- const content = readFileSync18(logPath, "utf-8");
18551
+ const content = readFileSync19(logPath, "utf-8");
17123
18552
  const lines = content.split(`
17124
18553
  `);
17125
18554
  console.log(lines.slice(-n2).join(`
@@ -17146,8 +18575,27 @@ async function ensureFreshSession(cfg) {
17146
18575
  }
17147
18576
  }
17148
18577
  async function execute() {
17149
- const program2 = createProgram();
17150
- await program2.parseAsync(process.argv);
18578
+ const telemetry = isHookEntrypointInvocation(process.argv) ? undefined : processCommandTelemetry();
18579
+ const program2 = createProgram({ telemetry });
18580
+ try {
18581
+ await program2.parseAsync(process.argv);
18582
+ } catch (err) {
18583
+ if (telemetry)
18584
+ await finishTelemetry(() => telemetry.fail(err));
18585
+ throw err;
18586
+ }
18587
+ }
18588
+ function processCommandTelemetry() {
18589
+ try {
18590
+ const settings2 = loadTelemetrySettings();
18591
+ if (!isTelemetryEnabled(settings2))
18592
+ return;
18593
+ return createCommandTelemetry({}, {
18594
+ resolveInstallId: () => settings2.install_id ?? getOrCreateInstallID()
18595
+ });
18596
+ } catch {
18597
+ return;
18598
+ }
17151
18599
  }
17152
18600
 
17153
18601
  // src/index.ts
@@ -17165,3 +18613,5 @@ execute().catch((err) => {
17165
18613
  }
17166
18614
  process.exit(1);
17167
18615
  });
18616
+
18617
+ //# debugId=df4adcb4-addf-31e4-6475-6e2164756e21