@dosu/cli 0.11.0 → 0.12.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 (2) hide show
  1. package/bin/dosu.js +223 -32
  2. package/package.json +2 -2
package/bin/dosu.js CHANGED
@@ -4341,7 +4341,7 @@ function getSupabaseAnonKey() {
4341
4341
  function getVersionString() {
4342
4342
  return `v${VERSION}`;
4343
4343
  }
4344
- var VERSION = "0.11.0";
4344
+ var VERSION = "0.12.0";
4345
4345
 
4346
4346
  // src/debug/logger.ts
4347
4347
  import {
@@ -6258,6 +6258,88 @@ var init_dist6 = __esm(() => {
6258
6258
  J2 = `${import_picocolors20.default.gray(o)} `;
6259
6259
  });
6260
6260
 
6261
+ // src/setup/analytics.ts
6262
+ async function trackCliOnboardingEvent(cfg, onboardingRunID, event, properties = {}) {
6263
+ if (!cfg.access_token)
6264
+ return;
6265
+ try {
6266
+ const trpc = createTypedClient(cfg);
6267
+ await withTimeout(trpc.user.trackCliOnboardingEvent.mutate({
6268
+ event,
6269
+ properties: {
6270
+ ...baseProperties(cfg),
6271
+ onboarding_run_id: onboardingRunID,
6272
+ ...properties
6273
+ }
6274
+ }), TRACKING_TIMEOUT_MS);
6275
+ } catch (err) {
6276
+ const msg = err instanceof Error ? err.message : String(err);
6277
+ logger.debug("setup", `CLI onboarding analytics failed: ${event}: ${msg}`);
6278
+ }
6279
+ }
6280
+ async function trackCliOnboardingPreAuthEvent(onboardingRunID, event, properties = {}) {
6281
+ try {
6282
+ const trpc = createAnonymousClient();
6283
+ await withTimeout(trpc.user.trackCliOnboardingPreAuthEvent.mutate({
6284
+ event,
6285
+ onboarding_run_id: onboardingRunID,
6286
+ properties: {
6287
+ ...baseProperties({ access_token: "", refresh_token: "", expires_at: 0 }),
6288
+ ...properties
6289
+ }
6290
+ }), TRACKING_TIMEOUT_MS);
6291
+ } catch (err) {
6292
+ const msg = err instanceof Error ? err.message : String(err);
6293
+ logger.debug("setup", `CLI onboarding pre-auth analytics failed: ${event}: ${msg}`);
6294
+ }
6295
+ }
6296
+ function baseProperties(cfg) {
6297
+ return {
6298
+ cli_version: VERSION,
6299
+ platform: process.platform,
6300
+ arch: process.arch,
6301
+ mode: cfg.mode ?? "cloud",
6302
+ org_id: cfg.org_id,
6303
+ deployment_id: cfg.deployment_id,
6304
+ space_id: cfg.space_id
6305
+ };
6306
+ }
6307
+ function createAnonymousClient() {
6308
+ const webAppURL = getWebAppURL();
6309
+ if (!webAppURL) {
6310
+ throw new Error("Web app URL not configured");
6311
+ }
6312
+ return createTRPCClient({
6313
+ links: [
6314
+ httpLink({
6315
+ url: `${webAppURL}/api/trpc`,
6316
+ transformer: dist_default
6317
+ })
6318
+ ]
6319
+ });
6320
+ }
6321
+ async function withTimeout(promise, timeoutMs) {
6322
+ let timeout;
6323
+ try {
6324
+ return await Promise.race([
6325
+ promise,
6326
+ new Promise((_3, reject) => {
6327
+ timeout = setTimeout(() => reject(new Error("tracking timeout")), timeoutMs);
6328
+ })
6329
+ ]);
6330
+ } finally {
6331
+ if (timeout)
6332
+ clearTimeout(timeout);
6333
+ }
6334
+ }
6335
+ var TRACKING_TIMEOUT_MS = 1500;
6336
+ var init_analytics = __esm(() => {
6337
+ init_dist();
6338
+ init_dist4();
6339
+ init_trpc();
6340
+ init_logger();
6341
+ });
6342
+
6261
6343
  // src/setup/styles.ts
6262
6344
  function dim(msg) {
6263
6345
  return import_picocolors21.default.dim(msg);
@@ -8043,7 +8125,7 @@ async function stepImportGitHubDocs(cfg, opts = {}) {
8043
8125
  }
8044
8126
  if (files.length === 0) {
8045
8127
  M2.info("No markdown docs available to import right now. You can import them later.");
8046
- return { advance: true };
8128
+ return { advance: true, imported: false, imported_count: 0 };
8047
8129
  }
8048
8130
  const repositories = buildRepositories(files);
8049
8131
  const selected = await promptGitHubDocsImport({ repositories });
@@ -8054,7 +8136,7 @@ async function stepImportGitHubDocs(cfg, opts = {}) {
8054
8136
  const fileIDs = selected;
8055
8137
  if (fileIDs.length === 0) {
8056
8138
  M2.info("Skipped importing docs for now. You can import them later.");
8057
- return { advance: true };
8139
+ return { advance: true, imported: false, imported_count: 0 };
8058
8140
  }
8059
8141
  const knowledgeStoreID = await getKnowledgeStoreID(trpc, cfg.space_id);
8060
8142
  if (!knowledgeStoreID) {
@@ -8073,7 +8155,7 @@ async function stepImportGitHubDocs(cfg, opts = {}) {
8073
8155
  spinner.stop("Import task started");
8074
8156
  M2.success("Import task started.");
8075
8157
  M2.info("Your docs should finish importing in a few minutes.");
8076
- return { advance: true };
8158
+ return { advance: true, imported: false, imported_count: 0, queued: true };
8077
8159
  }
8078
8160
  spinner.stop("Import task started");
8079
8161
  logger.info("setup", `Watching import task ${taskID}`);
@@ -8085,10 +8167,23 @@ async function stepImportGitHubDocs(cfg, opts = {}) {
8085
8167
  progressSpinner.stop("Stopped watching import progress");
8086
8168
  M2.info(`The import is still running in the background.
8087
8169
  Check status later with: dosu docs import-status ${taskID}`);
8088
- return { advance: true };
8170
+ return {
8171
+ advance: true,
8172
+ imported: false,
8173
+ imported_count: 0,
8174
+ queued: true,
8175
+ task_id: taskID
8176
+ };
8089
8177
  }
8090
8178
  handleImportCompletion(finalStatus, progressSpinner);
8091
- return { advance: true };
8179
+ const importedCount = finalStatus.detail?.completed ?? 0;
8180
+ return {
8181
+ advance: true,
8182
+ imported: importedCount > 0,
8183
+ imported_count: importedCount,
8184
+ failed_count: finalStatus.detail?.failed ?? 0,
8185
+ task_id: taskID
8186
+ };
8092
8187
  } catch (err) {
8093
8188
  spinner.stop("Failed");
8094
8189
  const msg = err instanceof Error ? err.message : String(err);
@@ -8533,13 +8628,13 @@ var exports_flow = {};
8533
8628
  __export(exports_flow, {
8534
8629
  startOAuthFlow: () => startOAuthFlow
8535
8630
  });
8536
- async function startOAuthFlow(signal, path2 = "/cli/auth") {
8631
+ async function startOAuthFlow(signal, path2 = "/cli/auth", params = {}) {
8537
8632
  const { server, tokenPromise } = await startCallbackServer();
8538
8633
  let timeoutId;
8539
8634
  try {
8540
8635
  const callbackURL = `http://localhost:${server.port}/callback`;
8541
8636
  logger.debug("auth.flow", `Callback URL: ${callbackURL}`);
8542
- const authURL = buildAuthURL(callbackURL, path2);
8637
+ const authURL = buildAuthURL(callbackURL, path2, params);
8543
8638
  logger.info("auth.flow", `Auth URL: ${authURL}`);
8544
8639
  const open2 = await Promise.resolve().then(() => (init_open(), exports_open));
8545
8640
  await open2.default(authURL);
@@ -8565,9 +8660,9 @@ async function startOAuthFlow(signal, path2 = "/cli/auth") {
8565
8660
  logger.debug("auth.flow", "Cleaning up: timeout cleared, server closed");
8566
8661
  }
8567
8662
  }
8568
- function buildAuthURL(callbackURL, path2) {
8663
+ function buildAuthURL(callbackURL, path2, extraParams) {
8569
8664
  const webAppURL = getWebAppURL();
8570
- const params = new URLSearchParams({ callback: callbackURL });
8665
+ const params = new URLSearchParams({ callback: callbackURL, ...extraParams });
8571
8666
  return `${webAppURL}${path2}?${params}`;
8572
8667
  }
8573
8668
  var init_flow = __esm(() => {
@@ -8586,21 +8681,28 @@ __export(exports_flow2, {
8586
8681
  runInstallSkill: () => runInstallSkill,
8587
8682
  isStdioOnly: () => isStdioOnly
8588
8683
  });
8684
+ import { randomUUID } from "node:crypto";
8589
8685
  import { existsSync as existsSync9 } from "node:fs";
8590
8686
  import { join as join19 } from "node:path";
8591
8687
  async function runSetup(opts = {}) {
8688
+ const onboardingRunID = randomUUID();
8592
8689
  logger.info("setup", `Setup flow started${opts.deploymentID ? ` deployment=${opts.deploymentID}` : ""}${opts.mode ? ` mode=${opts.mode}` : ""}`);
8593
8690
  Ie("Dosu CLI Setup");
8691
+ await trackCliOnboardingPreAuthEvent(onboardingRunID, "cli_onboarding_launch_attempted", {
8692
+ has_deployment_option: Boolean(opts.deploymentID),
8693
+ mode_option: opts.mode
8694
+ });
8594
8695
  let cfg = loadConfig();
8595
8696
  applyModeOverride(cfg, opts);
8596
8697
  if (opts.deploymentID) {
8597
8698
  cfg.mode = undefined;
8598
8699
  saveConfig(cfg);
8599
8700
  }
8600
- const authedCfg = await stepAuthenticate(cfg);
8701
+ const authedCfg = await stepAuthenticate(cfg, onboardingRunID);
8601
8702
  if (!authedCfg)
8602
8703
  return;
8603
8704
  cfg = authedCfg;
8705
+ await trackCliOnboardingEvent(cfg, onboardingRunID, "cli_onboarding_auth_completed");
8604
8706
  const apiClient = new Client(cfg);
8605
8707
  let cloudSetupContext = null;
8606
8708
  if (cfg.mode !== MODE_OSS) {
@@ -8608,46 +8710,101 @@ async function runSetup(opts = {}) {
8608
8710
  s.start("Loading your workspace...");
8609
8711
  cloudSetupContext = await resolveCloudSetupContext(cfg);
8610
8712
  if (!cloudSetupContext) {
8713
+ await trackCliOnboardingEvent(cfg, onboardingRunID, "cli_onboarding_failed", {
8714
+ reason: "cloud_setup_context_failed"
8715
+ });
8611
8716
  s.stop("Workspace load failed");
8612
8717
  return;
8613
8718
  }
8614
8719
  s.stop("Workspace loaded");
8615
8720
  }
8721
+ await trackCliOnboardingEvent(cfg, onboardingRunID, "cli_onboarding_started", {
8722
+ flow_kind: cloudSetupContext?.kind ?? "oss"
8723
+ });
8616
8724
  if (cfg.mode !== MODE_OSS && cloudSetupContext?.kind === "onboarding") {
8617
8725
  const ok = await bindOnboardingDeployment(apiClient, cfg, cloudSetupContext.targetOrg ?? null);
8618
- if (!ok)
8726
+ if (!ok) {
8727
+ await trackCliOnboardingEvent(cfg, onboardingRunID, "cli_onboarding_failed", {
8728
+ reason: "onboarding_deployment_failed"
8729
+ });
8619
8730
  return;
8731
+ }
8620
8732
  } else if (!cfg.deployment_id || opts.deploymentID) {
8621
8733
  const ok = await resolveDeployment(apiClient, cfg, opts);
8622
- if (!ok)
8734
+ if (!ok) {
8735
+ await trackCliOnboardingEvent(cfg, onboardingRunID, "cli_onboarding_failed", {
8736
+ reason: "deployment_resolution_failed"
8737
+ });
8623
8738
  return;
8739
+ }
8624
8740
  }
8625
8741
  const apiKey = await stepMintAPIKey(apiClient, cfg);
8626
- if (!apiKey)
8742
+ if (!apiKey) {
8743
+ await trackCliOnboardingEvent(cfg, onboardingRunID, "cli_onboarding_failed", {
8744
+ reason: "api_key_failed"
8745
+ });
8627
8746
  return;
8747
+ }
8628
8748
  cfg.api_key = apiKey;
8629
8749
  saveConfig(cfg);
8630
8750
  const choices = await stepOneShotConfirm({
8631
8751
  includeGitHub: cloudSetupContext?.kind === "onboarding"
8632
8752
  });
8633
- if (!choices)
8753
+ if (!choices) {
8754
+ await trackCliOnboardingEvent(cfg, onboardingRunID, "cli_onboarding_cancelled", {
8755
+ reason: "options_cancelled"
8756
+ });
8634
8757
  return;
8758
+ }
8759
+ await trackCliOnboardingEvent(cfg, onboardingRunID, "cli_onboarding_options_selected", {
8760
+ configure_mcp: choices.configureMcp,
8761
+ install_skill: choices.installSkill,
8762
+ connect_github: choices.connectGitHub
8763
+ });
8635
8764
  let mcpConfiguredThisRun = false;
8765
+ let mcpCompleted = false;
8766
+ let skillCompleted = false;
8767
+ let docsImported = false;
8636
8768
  if (choices.configureMcp) {
8637
8769
  const configured = await stepConfigureMcpTools(cfg);
8638
- if (configured === null)
8770
+ if (configured === null) {
8771
+ await trackCliOnboardingEvent(cfg, onboardingRunID, "cli_onboarding_cancelled", {
8772
+ reason: "mcp_selection_cancelled"
8773
+ });
8639
8774
  return;
8775
+ }
8640
8776
  mcpConfiguredThisRun = configured.some((r) => r.action === "install" || r.action === "skip");
8777
+ const configuredProviders = configured.filter((r) => (r.action === "install" || r.action === "skip") && !r.error);
8778
+ mcpCompleted = configuredProviders.length > 0;
8779
+ if (mcpCompleted) {
8780
+ await trackCliOnboardingEvent(cfg, onboardingRunID, "cli_onboarding_mcp_configured", {
8781
+ provider_count: configuredProviders.length,
8782
+ providers: configuredProviders.map((r) => r.provider.id())
8783
+ });
8784
+ }
8641
8785
  }
8642
8786
  if (choices.installSkill) {
8643
- await runInstallSkill();
8787
+ skillCompleted = await runInstallSkill();
8788
+ if (skillCompleted) {
8789
+ await trackCliOnboardingEvent(cfg, onboardingRunID, "cli_onboarding_skill_installed");
8790
+ }
8644
8791
  }
8645
8792
  let githubOnboardingDone = !choices.connectGitHub;
8646
8793
  if (choices.connectGitHub && cloudSetupContext?.kind === "onboarding") {
8647
8794
  const { stepConnectGitHubRepo: stepConnectGitHubRepo2 } = await Promise.resolve().then(() => (init_github_step(), exports_github_step));
8648
8795
  const connectResult = await stepConnectGitHubRepo2(cfg);
8649
- if (!connectResult.advance)
8796
+ if (!connectResult.advance) {
8797
+ await trackCliOnboardingEvent(cfg, onboardingRunID, "cli_onboarding_cancelled", {
8798
+ reason: "github_connect_not_advanced",
8799
+ has_connected_repo: connectResult.has_connected_repo
8800
+ });
8650
8801
  return;
8802
+ }
8803
+ if (connectResult.has_connected_repo || (connectResult.created_data_source_ids?.length ?? 0) > 0) {
8804
+ await trackCliOnboardingEvent(cfg, onboardingRunID, "cli_onboarding_github_connected", {
8805
+ created_data_source_count: connectResult.created_data_source_ids?.length ?? 0
8806
+ });
8807
+ }
8651
8808
  if (connectResult.space_id && !cfg.space_id) {
8652
8809
  cfg.space_id = connectResult.space_id;
8653
8810
  saveConfig(cfg);
@@ -8657,8 +8814,24 @@ async function runSetup(opts = {}) {
8657
8814
  waitForFreshDocs: Boolean(connectResult.deployment_id),
8658
8815
  expectedDataSourceIds: connectResult.created_data_source_ids
8659
8816
  });
8660
- if (!importResult.advance)
8817
+ if (!importResult.advance) {
8818
+ await trackCliOnboardingEvent(cfg, onboardingRunID, "cli_onboarding_failed", {
8819
+ reason: "github_docs_import_failed"
8820
+ });
8661
8821
  return;
8822
+ }
8823
+ docsImported = importResult.imported === true && (importResult.imported_count ?? 0) > 0;
8824
+ if (docsImported) {
8825
+ await trackCliOnboardingEvent(cfg, onboardingRunID, "cli_onboarding_docs_imported", {
8826
+ imported_count: importResult.imported_count ?? 0,
8827
+ failed_count: importResult.failed_count ?? 0,
8828
+ task_id: importResult.task_id
8829
+ });
8830
+ await trackCliOnboardingEvent(cfg, onboardingRunID, "cli_onboarding_activated", {
8831
+ imported_count: importResult.imported_count ?? 0,
8832
+ failed_count: importResult.failed_count ?? 0
8833
+ });
8834
+ }
8662
8835
  githubOnboardingDone = true;
8663
8836
  }
8664
8837
  const shouldCompleteRemoteOnboarding = cloudSetupContext?.kind === "onboarding" && githubOnboardingDone;
@@ -8684,6 +8857,13 @@ async function runSetup(opts = {}) {
8684
8857
  hasAgentsMd: existsSync9(join19(process.cwd(), "AGENTS.md"))
8685
8858
  });
8686
8859
  }
8860
+ if (mcpCompleted || skillCompleted || docsImported) {
8861
+ await trackCliOnboardingEvent(cfg, onboardingRunID, "cli_onboarding_completed", {
8862
+ completed_mcp: mcpCompleted,
8863
+ completed_skill: skillCompleted,
8864
+ imported_docs: docsImported
8865
+ });
8866
+ }
8687
8867
  if (cfg.mode === MODE_OSS) {
8688
8868
  Se("Setup complete! Using open-source libraries only.\n\nTips: Run `dosu setup --mode cloud` to connect your own repos.");
8689
8869
  } else {
@@ -8767,7 +8947,7 @@ async function runInstallSkill() {
8767
8947
  return false;
8768
8948
  }
8769
8949
  }
8770
- async function stepAuthenticate(existingCfg) {
8950
+ async function stepAuthenticate(existingCfg, onboardingRunID) {
8771
8951
  logger.info("setup", "Step: authenticate");
8772
8952
  const cfg = existingCfg ?? loadConfig();
8773
8953
  if (cfg.access_token) {
@@ -8798,16 +8978,25 @@ async function stepAuthenticate(existingCfg) {
8798
8978
  }
8799
8979
  }
8800
8980
  const shouldLogin = await ye({ message: "Open browser to log in?" });
8801
- if (pD(shouldLogin) || !shouldLogin)
8981
+ if (pD(shouldLogin) || !shouldLogin) {
8982
+ if (onboardingRunID) {
8983
+ await trackCliOnboardingPreAuthEvent(onboardingRunID, "cli_onboarding_auth_cancelled", {
8984
+ reason: pD(shouldLogin) ? "prompt_cancelled" : "login_declined"
8985
+ });
8986
+ }
8802
8987
  return null;
8803
- return await openBrowserForSetup(cfg);
8988
+ }
8989
+ if (onboardingRunID) {
8990
+ await trackCliOnboardingPreAuthEvent(onboardingRunID, "cli_onboarding_auth_started");
8991
+ }
8992
+ return await openBrowserForSetup(cfg, onboardingRunID);
8804
8993
  }
8805
- async function openBrowserForSetup(cfg) {
8994
+ async function openBrowserForSetup(cfg, onboardingRunID) {
8806
8995
  try {
8807
8996
  const { startOAuthFlow: startOAuthFlow2 } = await Promise.resolve().then(() => (init_flow(), exports_flow));
8808
8997
  const s = Y2();
8809
8998
  s.start("Waiting for authentication...");
8810
- const token = await startOAuthFlow2(undefined, "/cli/auth");
8999
+ const token = await startOAuthFlow2(undefined, "/cli/auth", onboardingRunID ? { onboarding_run_id: onboardingRunID } : {});
8811
9000
  s.stop("Authenticated");
8812
9001
  logger.info("setup", "Browser auth completed");
8813
9002
  cfg.access_token = token.access_token;
@@ -8819,6 +9008,11 @@ async function openBrowserForSetup(cfg) {
8819
9008
  const msg = err instanceof Error ? err.stack ?? err.message : String(err);
8820
9009
  logger.error("setup", `Auth failed: ${msg}`);
8821
9010
  M2.error(`Authentication failed: ${err instanceof Error ? err.message : String(err)}`);
9011
+ if (onboardingRunID) {
9012
+ await trackCliOnboardingPreAuthEvent(onboardingRunID, "cli_onboarding_auth_failed", {
9013
+ reason: err instanceof Error ? err.message : String(err)
9014
+ });
9015
+ }
8822
9016
  return null;
8823
9017
  }
8824
9018
  }
@@ -8856,14 +9050,11 @@ async function resolveCloudSetupContext(cfg) {
8856
9050
  }
8857
9051
  }
8858
9052
  async function resolveOnboardingTargetOrg(trpc) {
8859
- const ownerOrgs = await trpc.organization.getOrganizations.query({
8860
- userRole: "OWNER",
8861
- exact: true
8862
- });
8863
- if (ownerOrgs.length > 0) {
8864
- return ownerOrgs[0];
8865
- }
8866
9053
  const accessibleOrgs = await trpc.organization.getOrganizations.query();
9054
+ const ownerOrg = accessibleOrgs.find((org) => org.user_role === "OWNER");
9055
+ if (ownerOrg) {
9056
+ return ownerOrg;
9057
+ }
8867
9058
  return accessibleOrgs[0] ?? null;
8868
9059
  }
8869
9060
  async function bindOnboardingDeployment(apiClient, cfg, targetOrg) {
@@ -9142,6 +9333,7 @@ var init_flow2 = __esm(() => {
9142
9333
  init_config();
9143
9334
  init_logger();
9144
9335
  init_providers();
9336
+ init_analytics();
9145
9337
  init_styles();
9146
9338
  });
9147
9339
 
@@ -9379,7 +9571,6 @@ function analyticsCommand() {
9379
9571
  ["Low Confidence", String(stats.byConfidence.low)],
9380
9572
  ["Positive Reactions", String(stats.reactions.totalPositive)],
9381
9573
  ["Negative Reactions", String(stats.reactions.totalNegative)],
9382
- ["Reaction Rate", pct(stats.reactions.reactionRate)],
9383
9574
  ["Positive Rate", pct(stats.reactions.positiveRate)]
9384
9575
  ], { rawData: stats });
9385
9576
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dosu/cli",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "type": "module",
5
5
  "description": "Dosu CLI - Manage MCP servers for AI tools",
6
6
  "license": "MIT",
@@ -48,7 +48,7 @@
48
48
  },
49
49
  "dependencies": {
50
50
  "@clack/prompts": "^0.10.0",
51
- "@dosu/api-types": "0.0.4",
51
+ "@dosu/api-types": "0.0.7",
52
52
  "commander": "^13.1.0",
53
53
  "open": "^10.1.0",
54
54
  "picocolors": "^1.1.1",