@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.
- package/README.md +38 -0
- package/bin/dosu.js +1777 -327
- 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
|
|
2228
|
+
return parsed;
|
|
2224
2229
|
if (isRecord2(raw) && "schema_version" in raw)
|
|
2225
|
-
return
|
|
2226
|
-
const migrated = migrateLegacyConfig(raw);
|
|
2230
|
+
return parsed;
|
|
2227
2231
|
try {
|
|
2228
|
-
writeConfig(path2,
|
|
2232
|
+
writeConfig(path2, parsed);
|
|
2229
2233
|
} catch {}
|
|
2230
|
-
return
|
|
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.
|
|
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
|
|
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/
|
|
11558
|
-
|
|
11559
|
-
|
|
11560
|
-
|
|
11561
|
-
|
|
11562
|
-
|
|
11563
|
-
|
|
11564
|
-
|
|
11565
|
-
|
|
11566
|
-
|
|
11567
|
-
|
|
11568
|
-
|
|
11569
|
-
|
|
11570
|
-
|
|
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
|
-
|
|
11577
|
-
|
|
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
|
|
11593
|
-
|
|
11594
|
-
|
|
11595
|
-
|
|
11596
|
-
|
|
11597
|
-
|
|
11598
|
-
|
|
11599
|
-
|
|
11600
|
-
|
|
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
|
|
11604
|
-
|
|
11605
|
-
|
|
11606
|
-
|
|
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
|
|
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
|
-
|
|
11621
|
-
|
|
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
|
-
|
|
11624
|
-
|
|
11625
|
-
|
|
11626
|
-
|
|
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
|
-
|
|
11635
|
-
|
|
11636
|
-
|
|
11637
|
-
|
|
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
|
-
|
|
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
|
|
11663
|
-
return
|
|
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
|
|
11669
|
-
|
|
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
|
-
|
|
11676
|
-
if (
|
|
11677
|
-
return false;
|
|
11678
|
-
if (!claudeCliAvailable()) {
|
|
11679
|
-
printManualAuditNudge();
|
|
11730
|
+
function isEnabledEnvironmentFlag(value) {
|
|
11731
|
+
if (value === undefined)
|
|
11680
11732
|
return false;
|
|
11681
|
-
|
|
11682
|
-
|
|
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
|
|
11693
|
-
|
|
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
|
-
|
|
11704
|
-
|
|
11705
|
-
|
|
11706
|
-
|
|
11707
|
-
|
|
11708
|
-
|
|
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/
|
|
11712
|
-
|
|
11713
|
-
|
|
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
|
|
11716
|
-
|
|
11717
|
-
|
|
11718
|
-
|
|
11719
|
-
|
|
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
|
-
|
|
11722
|
-
|
|
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
|
-
|
|
11726
|
-
|
|
11727
|
-
|
|
11728
|
-
|
|
11729
|
-
|
|
11730
|
-
|
|
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
|
-
|
|
11752
|
-
|
|
11753
|
-
|
|
11754
|
-
|
|
11755
|
-
|
|
11756
|
-
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
@@ -11984,8 +12983,8 @@ h1 {
|
|
|
11984
12983
|
async function startCallbackServer(opts = {}) {
|
|
11985
12984
|
let resolveToken;
|
|
11986
12985
|
let rejectToken;
|
|
11987
|
-
const tokenPromise = new Promise((
|
|
11988
|
-
resolveToken =
|
|
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((
|
|
12047
|
-
httpServer.listen(0, "localhost", () =>
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
12489
|
-
if (err instanceof OAuthCallbackError2) {
|
|
13491
|
+
if (err instanceof OAuthCallbackError) {
|
|
12490
13492
|
log.error(err.userMessage);
|
|
12491
13493
|
if (onboardingRunID) {
|
|
12492
|
-
|
|
12493
|
-
reason: err
|
|
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
|
-
|
|
12501
|
-
reason:
|
|
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
|
|
12821
|
-
import { dirname as dirname7, join as
|
|
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
|
|
13944
|
+
return join30(getConfigDir(), "insights");
|
|
12926
13945
|
}
|
|
12927
13946
|
function reportPath(timestamp) {
|
|
12928
13947
|
const dir = insightsDir();
|
|
12929
13948
|
if (!timestamp)
|
|
12930
|
-
return
|
|
13949
|
+
return join30(dir, "latest.html");
|
|
12931
13950
|
const iso = timestamp.toISOString().slice(0, 19).replace(/:/g, "-");
|
|
12932
|
-
return
|
|
13951
|
+
return join30(dir, `report-${iso}Z.html`);
|
|
12933
13952
|
}
|
|
12934
13953
|
function pruneOldReports(dir, keepN) {
|
|
12935
|
-
if (!
|
|
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
|
-
|
|
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 (!
|
|
13021
|
-
|
|
13022
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
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((
|
|
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 {
|
|
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
|
|
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 =
|
|
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
|
|
16686
|
-
import { existsSync as
|
|
16687
|
-
import { join as
|
|
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 =
|
|
16690
|
-
var FETCH_TIMEOUT_MS3 =
|
|
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
|
|
17992
|
+
return join31(getConfigDir(), CACHE_FILENAME3);
|
|
16711
17993
|
}
|
|
16712
17994
|
function readCache() {
|
|
16713
17995
|
try {
|
|
16714
17996
|
const path3 = getCachePath3();
|
|
16715
|
-
if (!
|
|
17997
|
+
if (!existsSync21(path3))
|
|
16716
17998
|
return null;
|
|
16717
|
-
const data = JSON.parse(
|
|
16718
|
-
if (typeof data.lastCheck === "number" &&
|
|
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 (!
|
|
16730
|
-
|
|
18011
|
+
if (!existsSync21(dir)) {
|
|
18012
|
+
mkdirSync11(dir, { recursive: true, mode: 448 });
|
|
16731
18013
|
}
|
|
16732
|
-
|
|
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
|
|
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 === "
|
|
16753
|
-
return '
|
|
16754
|
-
|
|
16755
|
-
|
|
16756
|
-
|
|
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
|
-
|
|
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
|
-
|
|
16774
|
-
|
|
16775
|
-
|
|
16776
|
-
|
|
16777
|
-
|
|
16778
|
-
|
|
16779
|
-
|
|
16780
|
-
|
|
16781
|
-
|
|
16782
|
-
|
|
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
|
-
|
|
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 (
|
|
16828
|
-
|
|
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
|
|
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
|
|
18435
|
+
throw new CliUsageError("not logged in. Run 'dosu login' first");
|
|
17009
18436
|
}
|
|
17010
18437
|
if (isTokenExpired(cfg) && !await ensureFreshSession(cfg)) {
|
|
17011
|
-
throw new
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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
|
|
17150
|
-
|
|
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
|