@dosu/cli 0.53.1 → 0.54.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/bin/dosu.js +1295 -1191
  2. package/package.json +1 -1
package/bin/dosu.js CHANGED
@@ -5018,7 +5018,7 @@ function isNpxInvocation(channel = INSTALL_CHANNEL, env = process.env) {
5018
5018
  function getVersionString() {
5019
5019
  return `v${VERSION}`;
5020
5020
  }
5021
- var VERSION = "0.53.1", INSTALL_CHANNEL = "npm";
5021
+ var VERSION = "0.54.0", INSTALL_CHANNEL = "npm";
5022
5022
 
5023
5023
  // src/debug/logger.ts
5024
5024
  import {
@@ -10727,6 +10727,854 @@ var init_sync = __esm(() => {
10727
10727
  init_watermark();
10728
10728
  });
10729
10729
 
10730
+ // src/telemetry/debug-id.ts
10731
+ var BUNDLE_DEBUG_ID_PLACEHOLDER = "d4304c43-c40e-f448-6475-6e2164756e21";
10732
+
10733
+ // src/telemetry/telemetry.ts
10734
+ import { randomUUID as randomUUID2 } from "node:crypto";
10735
+ import { existsSync as existsSync13 } from "node:fs";
10736
+ import { request as httpRequest2 } from "node:http";
10737
+ import { request as httpsRequest } from "node:https";
10738
+ import { isAbsolute, relative, resolve as resolve2, sep } from "node:path";
10739
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
10740
+ function safeString(value, pattern, fallback) {
10741
+ if (typeof value !== "string" || !pattern.test(value))
10742
+ return fallback;
10743
+ return value;
10744
+ }
10745
+ function canonicalCommand(value) {
10746
+ if (typeof value !== "string")
10747
+ return UNKNOWN_COMMAND;
10748
+ const command = value.trim();
10749
+ if (command.length > 80)
10750
+ return UNKNOWN_COMMAND;
10751
+ if (!/^[a-z][a-z0-9-]*(?: [a-z][a-z0-9-]*){0,3}$/.test(command)) {
10752
+ return UNKNOWN_COMMAND;
10753
+ }
10754
+ return command;
10755
+ }
10756
+ function normalizeContext(context) {
10757
+ const isAuthenticated2 = context?.isAuthenticated === true;
10758
+ const id = safeRead(context?.user, "id");
10759
+ const email = safeRead(context?.user, "email");
10760
+ const orgId = safeRead(context, "orgId");
10761
+ const user = isAuthenticated2 && isUUID(id) ? { id, ...validEmail(email) ? { email } : {} } : undefined;
10762
+ return {
10763
+ mode: context?.mode === "oss" ? "oss" : "cloud",
10764
+ isAuthenticated: isAuthenticated2,
10765
+ ...user ? { user } : {},
10766
+ ...user && isUUID(orgId) ? { orgId } : {}
10767
+ };
10768
+ }
10769
+ function normalizeExitCode(value, fallback = 1) {
10770
+ if (typeof value !== "number" || !Number.isFinite(value))
10771
+ return fallback;
10772
+ return Math.min(255, Math.max(0, Math.trunc(value)));
10773
+ }
10774
+ function normalizeRuntime(runtime) {
10775
+ return {
10776
+ version: safeString(runtime.version, /^[A-Za-z0-9.+_-]{1,32}$/, "unknown"),
10777
+ installChannel: safeString(runtime.installChannel, /^[A-Za-z][A-Za-z0-9_-]{0,31}$/, "unknown"),
10778
+ platform: safeString(runtime.platform, /^[A-Za-z0-9_-]{1,24}$/, "unknown"),
10779
+ arch: safeString(runtime.arch, /^[A-Za-z0-9_-]{1,24}$/, "unknown"),
10780
+ runtime: safeString(runtime.runtime, /^[A-Za-z][A-Za-z0-9_-]{0,23}$/, "unknown"),
10781
+ runtimeMajor: Math.min(9999, Math.max(0, Math.trunc(runtime.runtimeMajor) || 0)),
10782
+ isCi: runtime.isCi === true,
10783
+ isTty: runtime.isTty === true
10784
+ };
10785
+ }
10786
+ function isUUID(value) {
10787
+ 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);
10788
+ }
10789
+ function validEmail(value) {
10790
+ 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);
10791
+ }
10792
+ function validEventId(value) {
10793
+ return typeof value === "string" && /^[0-9a-f]{32}$/i.test(value);
10794
+ }
10795
+ function validDebugId(value) {
10796
+ 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);
10797
+ }
10798
+ function validErrorType(value) {
10799
+ return typeof value === "string" && SAFE_ERROR_TYPES.has(value);
10800
+ }
10801
+ function validErrorCode(value) {
10802
+ return typeof value === "string" && SAFE_ERROR_CODES.has(value);
10803
+ }
10804
+ function validStatus(value) {
10805
+ return typeof value === "number" && Number.isInteger(value) && value >= 100 && value <= 599;
10806
+ }
10807
+ function objectLike(value) {
10808
+ return typeof value === "object" && value !== null || typeof value === "function";
10809
+ }
10810
+ function safeRead(value, key) {
10811
+ if (!objectLike(value))
10812
+ return;
10813
+ try {
10814
+ return Reflect.get(value, key);
10815
+ } catch {
10816
+ return;
10817
+ }
10818
+ }
10819
+ function parseStack(stack) {
10820
+ if (typeof stack !== "string" || !CLI_PACKAGE_ROOT)
10821
+ return [];
10822
+ const frames = [];
10823
+ const lines = stack.slice(0, 1e5).split(`
10824
+ `, 200);
10825
+ for (const originalLine of lines) {
10826
+ if (frames.length >= MAX_STACK_FRAMES)
10827
+ break;
10828
+ const parsed = parseOwnedStackLine(originalLine, CLI_PACKAGE_ROOT);
10829
+ if (!parsed)
10830
+ continue;
10831
+ const { filename, lineno, colno } = parsed;
10832
+ if (!Number.isSafeInteger(lineno) || !Number.isSafeInteger(colno) || lineno < 1 || colno < 1 || lineno > 1e7 || colno > 1e7) {
10833
+ continue;
10834
+ }
10835
+ frames.push({ filename, lineno, colno, in_app: true });
10836
+ }
10837
+ return frames;
10838
+ }
10839
+ function detectCliPackageRoot() {
10840
+ try {
10841
+ const modulePath = fileURLToPath2(import.meta.url).replaceAll("\\", "/");
10842
+ for (const suffix of ["/src/telemetry/telemetry.ts", "/bin/dosu.js"]) {
10843
+ if (modulePath.endsWith(suffix))
10844
+ return modulePath.slice(0, -suffix.length);
10845
+ }
10846
+ } catch {}
10847
+ return;
10848
+ }
10849
+ function parseOwnedStackLine(originalLine, packageRoot) {
10850
+ const line = originalLine.replaceAll("\\", "/");
10851
+ const match = line.match(/^\s*at\s+.*\s+\((.+):(\d+):(\d+)\)\s*$/) ?? line.match(/^\s*at\s+(.+):(\d+):(\d+)\s*$/);
10852
+ if (!match?.[1] || !match[2] || !match[3])
10853
+ return;
10854
+ let absolutePath = match[1];
10855
+ try {
10856
+ if (absolutePath.startsWith("file://"))
10857
+ absolutePath = fileURLToPath2(absolutePath);
10858
+ } catch {
10859
+ return;
10860
+ }
10861
+ if (!isAbsolute(absolutePath))
10862
+ return;
10863
+ const resolvedPath = resolve2(absolutePath);
10864
+ const relativePath = relative(packageRoot, resolvedPath);
10865
+ if (relativePath === "" || relativePath === ".." || relativePath.startsWith(`..${sep}`) || isAbsolute(relativePath)) {
10866
+ return;
10867
+ }
10868
+ const filename = relativePath.replaceAll("\\", "/");
10869
+ if (!/^(?:src\/(?:[A-Za-z0-9_.-]+\/)*[A-Za-z0-9_.-]+\.(?:[cm]?[jt]sx?)|bin\/dosu\.js)$/.test(filename)) {
10870
+ return;
10871
+ }
10872
+ try {
10873
+ if (!existsSync13(resolvedPath))
10874
+ return;
10875
+ } catch {
10876
+ return;
10877
+ }
10878
+ return { filename, lineno: Number(match[2]), colno: Number(match[3]) };
10879
+ }
10880
+ function sanitizeError(error) {
10881
+ const normalizedError = domainErrorFromCauseChain(error) ?? error;
10882
+ const data = safeRead(normalizedError, "data");
10883
+ const typeCandidate = safeRead(normalizedError, "name");
10884
+ const codeCandidates = [safeRead(data, "code"), safeRead(normalizedError, "code")];
10885
+ const statusCandidates = [
10886
+ safeRead(data, "httpStatus"),
10887
+ safeRead(normalizedError, "status"),
10888
+ safeRead(normalizedError, "statusCode")
10889
+ ];
10890
+ const exitCodeCandidate = safeRead(normalizedError, "exitCode");
10891
+ const code = codeCandidates.find(validErrorCode);
10892
+ const status = statusCandidates.find(validStatus);
10893
+ const exitCode = typeof exitCodeCandidate === "number" && Number.isFinite(exitCodeCandidate) ? normalizeExitCode(exitCodeCandidate) : undefined;
10894
+ return {
10895
+ type: validErrorType(typeCandidate) ? typeCandidate : "Error",
10896
+ ...code ? { code } : {},
10897
+ ...status ? { status } : {},
10898
+ frames: parseStack(safeRead(normalizedError, "stack")),
10899
+ ...exitCode === undefined ? {} : { exitCode }
10900
+ };
10901
+ }
10902
+ function domainErrorFromCauseChain(error) {
10903
+ const seen = new Set;
10904
+ let current = error;
10905
+ for (let depth = 0;depth < 5 && objectLike(current); depth += 1) {
10906
+ if (seen.has(current))
10907
+ return;
10908
+ seen.add(current);
10909
+ const code = safeRead(current, "code");
10910
+ if (code === "SESSION_EXPIRED" || code === "SESSION_PERSISTENCE_ERROR" || code === "SESSION_REFRESH_ERROR") {
10911
+ return current;
10912
+ }
10913
+ current = safeRead(current, "cause");
10914
+ }
10915
+ return;
10916
+ }
10917
+ function durationBucket(durationMs) {
10918
+ const duration = Number.isFinite(durationMs) ? Math.max(0, durationMs) : 0;
10919
+ if (duration < 100)
10920
+ return "<100ms";
10921
+ if (duration < 500)
10922
+ return "100-499ms";
10923
+ if (duration < 2000)
10924
+ return "500ms-1.9s";
10925
+ if (duration < 1e4)
10926
+ return "2-9.9s";
10927
+ if (duration < 60000)
10928
+ return "10-59s";
10929
+ return "60s+";
10930
+ }
10931
+ function countBucket(value) {
10932
+ const n2 = typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0;
10933
+ if (n2 === 0)
10934
+ return "0";
10935
+ if (n2 < 5)
10936
+ return "1-4";
10937
+ if (n2 < 10)
10938
+ return "5-9";
10939
+ if (n2 < 20)
10940
+ return "10-19";
10941
+ if (n2 < 50)
10942
+ return "20-49";
10943
+ return "50+";
10944
+ }
10945
+ function recordCommandFacets(facets) {
10946
+ pendingFacets = { ...pendingFacets, ...facets };
10947
+ }
10948
+ function consumeCommandFacets() {
10949
+ const facets = pendingFacets;
10950
+ pendingFacets = undefined;
10951
+ return facets;
10952
+ }
10953
+ function allowlisted(value, allowed) {
10954
+ return typeof value === "string" && allowed.has(value) ? value : undefined;
10955
+ }
10956
+ function sanitizeFacets(facets) {
10957
+ if (!facets || typeof facets !== "object")
10958
+ return {};
10959
+ const trigger = allowlisted(facets.sync_trigger, SYNC_TRIGGERS);
10960
+ const status = allowlisted(facets.sync_status, SYNC_STATUSES);
10961
+ const learner = allowlisted(facets.learner_outcome, LEARNER_OUTCOMES);
10962
+ const backfill = allowlisted(facets.backfill_offer, BACKFILL_OFFERS);
10963
+ return {
10964
+ ...trigger ? { sync_trigger: trigger } : {},
10965
+ ...status ? { sync_status: status } : {},
10966
+ ...facets.sessions_studied !== undefined ? { sessions_studied: countBucket(facets.sessions_studied) } : {},
10967
+ ...facets.notes_written !== undefined ? { notes_written: countBucket(facets.notes_written) } : {},
10968
+ ...learner ? { learner_outcome: learner } : {},
10969
+ ...backfill ? { backfill_offer: backfill } : {}
10970
+ };
10971
+ }
10972
+ function buildPostHogPayload(input) {
10973
+ const context = normalizeContext(input.context);
10974
+ const runtime = normalizeRuntime(input.runtime);
10975
+ const errorCode = validErrorCode(input.errorCode) ? input.errorCode : undefined;
10976
+ const facets = sanitizeFacets(input.facets);
10977
+ const result = input.result === "success" || input.result === "validation_error" ? input.result : "failure";
10978
+ return {
10979
+ api_key: input.apiKey,
10980
+ distinct_id: context.user?.id ?? (isUUID(input.installId) ? input.installId : FALLBACK_INSTALL_ID),
10981
+ event: "cli_command_completed",
10982
+ properties: {
10983
+ $geoip_disable: true,
10984
+ ...context.user ? {} : { $process_person_profile: false },
10985
+ schema_version: 1,
10986
+ command: canonicalCommand(input.command),
10987
+ result,
10988
+ duration_bucket: durationBucket(input.durationMs),
10989
+ cli_version: runtime.version,
10990
+ install_channel: runtime.installChannel,
10991
+ platform: runtime.platform,
10992
+ arch: runtime.arch,
10993
+ runtime: runtime.runtime,
10994
+ runtime_major: runtime.runtimeMajor,
10995
+ is_ci: runtime.isCi,
10996
+ is_tty: runtime.isTty,
10997
+ mode: context.mode,
10998
+ ...context.orgId ? {
10999
+ org_id: context.orgId,
11000
+ $groups: { organization: context.orgId }
11001
+ } : {},
11002
+ is_authenticated: context.isAuthenticated,
11003
+ exit_code: normalizeExitCode(input.exitCode),
11004
+ ...errorCode ? { error_code: errorCode } : {},
11005
+ ...facets
11006
+ }
11007
+ };
11008
+ }
11009
+ function parsePostHogProjectToken(value) {
11010
+ if (typeof value !== "string")
11011
+ return null;
11012
+ const token = value.trim();
11013
+ return /^phc_[A-Za-z0-9_-]{3,252}$/.test(token) ? token : null;
11014
+ }
11015
+ function parseTelemetryWebAppURL(value, allowInsecureLoopback = false) {
11016
+ if (typeof value !== "string" || value.length === 0 || value.length > 2048)
11017
+ return null;
11018
+ try {
11019
+ const url = new URL(value.trim());
11020
+ if (!url.hostname || url.username || url.password || url.search || url.hash)
11021
+ return null;
11022
+ const loopbackHosts = new Set(["localhost", "127.0.0.1", "[::1]"]);
11023
+ const secure = url.protocol === "https:";
11024
+ const allowedDevelopmentOrigin = allowInsecureLoopback && url.protocol === "http:" && loopbackHosts.has(url.hostname);
11025
+ if (!secure && !allowedDevelopmentOrigin)
11026
+ return null;
11027
+ return url.toString().replace(/\/$/, "");
11028
+ } catch {
11029
+ return null;
11030
+ }
11031
+ }
11032
+ function parseSentryDsn(value) {
11033
+ if (typeof value !== "string" || value.length === 0 || value.length > 2048)
11034
+ return null;
11035
+ const dsn = value.trim();
11036
+ try {
11037
+ const url = new URL(dsn);
11038
+ 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")) {
11039
+ return null;
11040
+ }
11041
+ const segments = url.pathname.split("/").filter(Boolean);
11042
+ const projectId = segments.pop();
11043
+ if (!projectId || !/^[A-Za-z0-9_-]{1,128}$/.test(projectId))
11044
+ return null;
11045
+ if (segments.some((segment) => !/^[A-Za-z0-9._~-]{1,128}$/.test(segment)))
11046
+ return null;
11047
+ const prefix = segments.length > 0 ? `/${segments.join("/")}` : "";
11048
+ return {
11049
+ dsn,
11050
+ endpoint: `${url.protocol}//${url.host}${prefix}/api/${projectId}/envelope/`,
11051
+ projectId,
11052
+ publicKey: url.username
11053
+ };
11054
+ } catch {
11055
+ return null;
11056
+ }
11057
+ }
11058
+ function sentryTags(command, context, runtime, error) {
11059
+ return {
11060
+ schema_version: "1",
11061
+ command,
11062
+ cli_version: runtime.version,
11063
+ install_channel: runtime.installChannel,
11064
+ os: runtime.platform,
11065
+ arch: runtime.arch,
11066
+ runtime: runtime.runtime,
11067
+ runtime_major: String(runtime.runtimeMajor),
11068
+ is_ci: String(runtime.isCi),
11069
+ is_tty: String(runtime.isTty),
11070
+ mode: context.mode,
11071
+ is_authenticated: String(context.isAuthenticated),
11072
+ ...error.code ? { error_code: error.code } : {},
11073
+ ...error.status ? { http_status: String(error.status) } : {},
11074
+ ...error.exitCode === undefined ? {} : { exit_code: String(error.exitCode) }
11075
+ };
11076
+ }
11077
+ function buildSentryEnvelope(input) {
11078
+ const parsedDsn = parseSentryDsn(input.dsn);
11079
+ if (!parsedDsn || !validEventId(input.eventId))
11080
+ return null;
11081
+ const context = normalizeContext(input.context);
11082
+ const runtime = normalizeRuntime(input.runtime);
11083
+ const command = canonicalCommand(input.command);
11084
+ const debugId = validDebugId(input.debugId) ? input.debugId.toLowerCase() : undefined;
11085
+ const error = {
11086
+ type: validErrorType(input.error.type) ? input.error.type : "Error",
11087
+ ...validErrorCode(input.error.code) ? { code: input.error.code } : {},
11088
+ ...validStatus(input.error.status) ? { status: input.error.status } : {},
11089
+ ...typeof input.error.exitCode === "number" && Number.isFinite(input.error.exitCode) ? { exitCode: normalizeExitCode(input.error.exitCode) } : {},
11090
+ frames: input.error.frames.slice(0, MAX_STACK_FRAMES).map((frame) => {
11091
+ 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";
11092
+ return {
11093
+ filename,
11094
+ ...debugId && filename === "bin/dosu.js" ? { abs_path: BUNDLE_CODE_FILE } : {},
11095
+ lineno: Math.min(1e7, Math.max(1, Math.trunc(frame.lineno) || 1)),
11096
+ colno: Math.min(1e7, Math.max(1, Math.trunc(frame.colno) || 1)),
11097
+ in_app: true
11098
+ };
11099
+ }).reverse()
11100
+ };
11101
+ const exceptionValue = {
11102
+ type: error.type,
11103
+ value: error.code ?? error.type,
11104
+ ...error.frames.length > 0 ? { stacktrace: { frames: error.frames } } : {}
11105
+ };
11106
+ const newestFrame = error.frames.at(-1);
11107
+ const safeCallsite = newestFrame ? `${newestFrame.filename}:${newestFrame.lineno}` : "unknown";
11108
+ const timestampMs = Number.isFinite(input.timestampMs) ? Math.max(0, input.timestampMs) : 0;
11109
+ const mappedBundle = debugId && error.frames.some((frame) => frame.abs_path === BUNDLE_CODE_FILE);
11110
+ const event = {
11111
+ event_id: input.eventId.toLowerCase(),
11112
+ timestamp: timestampMs / 1000,
11113
+ platform: "node",
11114
+ level: "error",
11115
+ release: `dosu-cli@${runtime.version}`,
11116
+ tags: sentryTags(command, context, runtime, error),
11117
+ ...context.user ? {
11118
+ user: {
11119
+ id: context.user.id,
11120
+ ...context.user.email ? { email: context.user.email } : {}
11121
+ }
11122
+ } : {},
11123
+ ...mappedBundle ? {
11124
+ debug_meta: {
11125
+ images: [{ type: "sourcemap", code_file: BUNDLE_CODE_FILE, debug_id: debugId }]
11126
+ }
11127
+ } : {},
11128
+ fingerprint: ["dosu-cli", command, error.type, error.code ?? "unknown", safeCallsite],
11129
+ exception: { values: [exceptionValue] }
11130
+ };
11131
+ const envelopeHeader = {
11132
+ event_id: input.eventId.toLowerCase(),
11133
+ dsn: parsedDsn.dsn,
11134
+ sent_at: new Date(timestampMs).toISOString()
11135
+ };
11136
+ return {
11137
+ endpoint: parsedDsn.endpoint,
11138
+ body: `${JSON.stringify(envelopeHeader)}
11139
+ ${JSON.stringify({ type: "event" })}
11140
+ ${JSON.stringify(event)}`
11141
+ };
11142
+ }
11143
+ function fetchWithoutRedirect(url, init = {}) {
11144
+ let parsedURL;
11145
+ try {
11146
+ parsedURL = new URL(url);
11147
+ if (parsedURL.protocol !== "http:" && parsedURL.protocol !== "https:") {
11148
+ return Promise.reject(new Error("unsupported telemetry protocol"));
11149
+ }
11150
+ } catch (error) {
11151
+ return Promise.reject(error);
11152
+ }
11153
+ return new Promise((resolve3, reject) => {
11154
+ let request;
11155
+ let response;
11156
+ let settled = false;
11157
+ const signal = init.signal;
11158
+ const cleanup = () => signal?.removeEventListener("abort", onAbort);
11159
+ const fail = (error) => {
11160
+ if (settled)
11161
+ return;
11162
+ settled = true;
11163
+ cleanup();
11164
+ response?.destroy();
11165
+ request?.socket?.destroy();
11166
+ request?.destroy();
11167
+ reject(error);
11168
+ };
11169
+ const succeed = (value) => {
11170
+ if (settled)
11171
+ return;
11172
+ settled = true;
11173
+ cleanup();
11174
+ response?.destroy();
11175
+ request?.socket?.destroy();
11176
+ request?.destroy();
11177
+ resolve3(value);
11178
+ };
11179
+ const onAbort = () => {
11180
+ const error = new Error("telemetry request aborted");
11181
+ error.name = "AbortError";
11182
+ fail(error);
11183
+ };
11184
+ try {
11185
+ if (signal?.aborted) {
11186
+ onAbort();
11187
+ return;
11188
+ }
11189
+ const body = init.body;
11190
+ if (body !== undefined && body !== null && typeof body !== "string") {
11191
+ fail(new Error("unsupported telemetry request body"));
11192
+ return;
11193
+ }
11194
+ const headers = Object.fromEntries(new Headers(init.headers).entries());
11195
+ const transport = parsedURL.protocol === "https:" ? httpsRequest : httpRequest2;
11196
+ request = transport(parsedURL, {
11197
+ method: init.method ?? "GET",
11198
+ headers,
11199
+ agent: false
11200
+ }, (incoming) => {
11201
+ response = incoming;
11202
+ const chunks = [];
11203
+ let totalBytes = 0;
11204
+ incoming.on("data", (chunk) => {
11205
+ const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
11206
+ totalBytes += bytes.byteLength;
11207
+ if (totalBytes > MAX_RESPONSE_BYTES) {
11208
+ fail(new Error("telemetry response too large"));
11209
+ return;
11210
+ }
11211
+ chunks.push(bytes);
11212
+ });
11213
+ incoming.once("error", fail);
11214
+ incoming.once("end", () => {
11215
+ try {
11216
+ const responseHeaders = new Headers;
11217
+ for (let index = 0;index < incoming.rawHeaders.length; index += 2) {
11218
+ const name = incoming.rawHeaders[index];
11219
+ const value = incoming.rawHeaders[index + 1];
11220
+ if (name && value)
11221
+ responseHeaders.append(name, value);
11222
+ }
11223
+ const status = incoming.statusCode;
11224
+ if (!status)
11225
+ throw new Error("telemetry response missing status");
11226
+ succeed(new Response(chunks.length > 0 ? Buffer.concat(chunks) : null, {
11227
+ status,
11228
+ statusText: incoming.statusMessage,
11229
+ headers: responseHeaders
11230
+ }));
11231
+ } catch (error) {
11232
+ fail(error);
11233
+ }
11234
+ });
11235
+ });
11236
+ request.once("error", fail);
11237
+ signal?.addEventListener("abort", onAbort, { once: true });
11238
+ request.end(body);
11239
+ } catch (error) {
11240
+ fail(error);
11241
+ }
11242
+ });
11243
+ }
11244
+ async function sendHttpsRequest(url, init, fetcher = fetchWithoutRedirect) {
11245
+ try {
11246
+ if (new URL(url).protocol !== "https:")
11247
+ return false;
11248
+ } catch {
11249
+ return false;
11250
+ }
11251
+ const controller = new AbortController;
11252
+ let timer;
11253
+ const timeout = new Promise((_resolve, reject) => {
11254
+ timer = setTimeout(() => {
11255
+ controller.abort();
11256
+ reject(new Error("telemetry request timed out"));
11257
+ }, REQUEST_TIMEOUT_MS);
11258
+ });
11259
+ try {
11260
+ const request = fetcher(url, { ...init, redirect: "error", signal: controller.signal });
11261
+ const result = await Promise.race([request, timeout]);
11262
+ return result.ok;
11263
+ } catch {
11264
+ return false;
11265
+ } finally {
11266
+ controller.abort();
11267
+ if (timer !== undefined)
11268
+ clearTimeout(timer);
11269
+ }
11270
+ }
11271
+ function environmentFlag(value) {
11272
+ if (value === undefined)
11273
+ return false;
11274
+ const normalized = value.trim().toLowerCase();
11275
+ return normalized !== "" && !["0", "false", "no", "off"].includes(normalized);
11276
+ }
11277
+ function runtimeNameAndMajor() {
11278
+ const versions = process.versions;
11279
+ const runtime = versions.bun ? "bun" : "node";
11280
+ const version = versions.bun ?? versions.node;
11281
+ return { runtime, runtimeMajor: Number.parseInt(version.split(".")[0] ?? "0", 10) || 0 };
11282
+ }
11283
+ function resolveRuntime(dependencies, env) {
11284
+ const detected = runtimeNameAndMajor();
11285
+ return normalizeRuntime({
11286
+ version: dependencies.version ?? VERSION,
11287
+ installChannel: dependencies.installChannel ?? INSTALL_CHANNEL,
11288
+ platform: dependencies.platform ?? process.platform,
11289
+ arch: dependencies.arch ?? process.arch,
11290
+ runtime: dependencies.runtime ?? detected.runtime,
11291
+ runtimeMajor: dependencies.runtimeMajor ?? detected.runtimeMajor,
11292
+ isCi: dependencies.isCi ?? environmentFlag(env.CI),
11293
+ isTty: dependencies.isTty ?? process.stdout.isTTY === true
11294
+ });
11295
+ }
11296
+ function validationFailure(exitCode, error) {
11297
+ if (exitCode === 2)
11298
+ return true;
11299
+ if (error?.type === "CliUsageError")
11300
+ return true;
11301
+ if (!error?.code)
11302
+ return false;
11303
+ return error.code.startsWith("commander.") || ["BAD_REQUEST", "INVALID_ARGUMENT", "UNPROCESSABLE_CONTENT", "VALIDATION_ERROR"].includes(error.code);
11304
+ }
11305
+ function safeNow(now) {
11306
+ try {
11307
+ const value = now();
11308
+ return Number.isFinite(value) ? Math.max(0, value) : 0;
11309
+ } catch {
11310
+ return 0;
11311
+ }
11312
+ }
11313
+ function safeUuid(generate) {
11314
+ try {
11315
+ const value = generate();
11316
+ return isUUID(value) ? value : undefined;
11317
+ } catch {
11318
+ return;
11319
+ }
11320
+ }
11321
+ function eventId(generate) {
11322
+ const uuid2 = safeUuid(generate);
11323
+ return uuid2?.replaceAll("-", "");
11324
+ }
11325
+ function createCommandTelemetry(settings2, dependencies = {}) {
11326
+ const env = dependencies.env ?? process.env;
11327
+ const fetcher = dependencies.fetch;
11328
+ const now = dependencies.now ?? Date.now;
11329
+ const generateUuid = dependencies.randomUUID ?? randomUUID2;
11330
+ const writeStderr = dependencies.stderr ?? ((payload) => process.stderr.write(`${payload}
11331
+ `));
11332
+ const webAppURL = dependencies.webAppURL ?? getWebAppURL;
11333
+ const resolveFacets = dependencies.facets ?? consumeCommandFacets;
11334
+ const runtime = resolveRuntime(dependencies, env);
11335
+ const debug = env.DOSU_TELEMETRY_DEBUG === "1";
11336
+ const disabled = settings2.disabled === true || environmentFlag(env.DO_NOT_TRACK) || environmentFlag(env.DOSU_TELEMETRY_DISABLED);
11337
+ const rawAnalyticsToken = dependencies.env ? env.DOSU_POSTHOG_PROJECT_TOKEN_OVERRIDE ?? env.DOSU_POSTHOG_PROJECT_TOKEN : process.env.DOSU_POSTHOG_PROJECT_TOKEN_OVERRIDE ?? "phc_Uo9RxhN0HRk8gZkPlT18BCr7eqsM0MPWVW88HZB95V5";
11338
+ 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";
11339
+ const analyticsToken = parsePostHogProjectToken(rawAnalyticsToken) ?? undefined;
11340
+ const sentryDsn = parseSentryDsn(rawSentryDsn)?.dsn;
11341
+ let started = false;
11342
+ let terminal = false;
11343
+ let startedAt = 0;
11344
+ let command = UNKNOWN_COMMAND;
11345
+ let context = normalizeContext(undefined);
11346
+ let installIdResolved = false;
11347
+ let resolvedInstallId = settings2.install_id;
11348
+ function installId() {
11349
+ if (isUUID(resolvedInstallId))
11350
+ return resolvedInstallId;
11351
+ if (installIdResolved)
11352
+ return;
11353
+ installIdResolved = true;
11354
+ try {
11355
+ resolvedInstallId = dependencies.resolveInstallId?.();
11356
+ } catch {
11357
+ resolvedInstallId = undefined;
11358
+ }
11359
+ return isUUID(resolvedInstallId) ? resolvedInstallId : undefined;
11360
+ }
11361
+ function debugPayload(payload) {
11362
+ try {
11363
+ writeStderr(payload);
11364
+ } catch {}
11365
+ }
11366
+ async function dispatch(result, exitCode, error) {
11367
+ try {
11368
+ const tasks = [];
11369
+ if (!disabled && analyticsToken) {
11370
+ const id = context.user?.id ?? installId();
11371
+ if (id) {
11372
+ let facets;
11373
+ try {
11374
+ facets = resolveFacets();
11375
+ } catch {
11376
+ facets = undefined;
11377
+ }
11378
+ const payload = buildPostHogPayload({
11379
+ apiKey: analyticsToken,
11380
+ ...context.user ? {} : { installId: id },
11381
+ command,
11382
+ result,
11383
+ durationMs: safeNow(now) - startedAt,
11384
+ exitCode,
11385
+ errorCode: error?.code,
11386
+ ...facets ? { facets } : {},
11387
+ context,
11388
+ runtime
11389
+ });
11390
+ const body = JSON.stringify(payload);
11391
+ if (debug) {
11392
+ debugPayload(body);
11393
+ } else {
11394
+ const base = parseTelemetryWebAppURL(webAppURL());
11395
+ if (base) {
11396
+ tasks.push(sendHttpsRequest(`${base}/ph-api/i/v0/e/`, {
11397
+ method: "POST",
11398
+ headers: { "content-type": "application/json" },
11399
+ body
11400
+ }, fetcher));
11401
+ }
11402
+ }
11403
+ }
11404
+ }
11405
+ if (!disabled && result === "failure" && error && shouldSendToSentry(error) && sentryDsn) {
11406
+ const id = eventId(generateUuid);
11407
+ const envelope = id ? buildSentryEnvelope({
11408
+ dsn: sentryDsn,
11409
+ command,
11410
+ context,
11411
+ runtime,
11412
+ error,
11413
+ eventId: id,
11414
+ timestampMs: safeNow(now),
11415
+ debugId: BUNDLE_DEBUG_ID_PLACEHOLDER
11416
+ }) : null;
11417
+ if (envelope) {
11418
+ if (debug) {
11419
+ debugPayload(envelope.body);
11420
+ } else {
11421
+ tasks.push(sendHttpsRequest(envelope.endpoint, {
11422
+ method: "POST",
11423
+ headers: { "content-type": "application/x-sentry-envelope" },
11424
+ body: envelope.body
11425
+ }, fetcher));
11426
+ }
11427
+ }
11428
+ }
11429
+ await Promise.all(tasks);
11430
+ } catch {}
11431
+ }
11432
+ return {
11433
+ start(commandName, commandContext) {
11434
+ if (started || terminal)
11435
+ return;
11436
+ started = true;
11437
+ command = canonicalCommand(commandName);
11438
+ context = normalizeContext(commandContext);
11439
+ startedAt = safeNow(now);
11440
+ },
11441
+ async complete(exitCode = 0) {
11442
+ if (!started || terminal)
11443
+ return;
11444
+ terminal = true;
11445
+ const normalizedExitCode = normalizeExitCode(exitCode, 0);
11446
+ const result = normalizedExitCode === 0 ? "success" : validationFailure(normalizedExitCode, undefined) ? "validation_error" : "failure";
11447
+ const error = result === "failure" ? { type: "CommandExitError", frames: [], exitCode: normalizedExitCode } : undefined;
11448
+ await dispatch(result, normalizedExitCode, error);
11449
+ },
11450
+ async fail(rawError) {
11451
+ if (!started || terminal)
11452
+ return;
11453
+ terminal = true;
11454
+ const error = sanitizeError(rawError);
11455
+ const exitCode = error.exitCode ?? 1;
11456
+ const result = validationFailure(exitCode, error) ? "validation_error" : "failure";
11457
+ await dispatch(result, exitCode, error);
11458
+ }
11459
+ };
11460
+ }
11461
+ function shouldSendToSentry(error) {
11462
+ return !["SESSION_EXPIRED", "SESSION_PERSISTENCE_ERROR"].includes(error.code ?? "");
11463
+ }
11464
+ 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, SYNC_TRIGGERS, SYNC_STATUSES, LEARNER_OUTCOMES, BACKFILL_OFFERS, pendingFacets;
11465
+ var init_telemetry = __esm(() => {
11466
+ MAX_RESPONSE_BYTES = 1024 * 1024;
11467
+ CLI_PACKAGE_ROOT = detectCliPackageRoot();
11468
+ SAFE_ERROR_TYPES = new Set([
11469
+ "AbortError",
11470
+ "AggregateError",
11471
+ "CommanderError",
11472
+ "CliUsageError",
11473
+ "CommandExitError",
11474
+ "Error",
11475
+ "OAuthCallbackError",
11476
+ "RangeError",
11477
+ "ReferenceError",
11478
+ "SessionExpiredError",
11479
+ "SessionPersistenceError",
11480
+ "SessionRefreshError",
11481
+ "SyntaxError",
11482
+ "TRPCClientError",
11483
+ "TypeError"
11484
+ ]);
11485
+ SAFE_ERROR_CODES = new Set([
11486
+ "ABORT_ERR",
11487
+ "BAD_GATEWAY",
11488
+ "BAD_REQUEST",
11489
+ "CLIENT_CLOSED_REQUEST",
11490
+ "CONFLICT",
11491
+ "EACCES",
11492
+ "EAI_AGAIN",
11493
+ "EBUSY",
11494
+ "ECONNREFUSED",
11495
+ "ECONNRESET",
11496
+ "EEXIST",
11497
+ "EHOSTUNREACH",
11498
+ "EINVAL",
11499
+ "EISDIR",
11500
+ "ELOOP",
11501
+ "EMFILE",
11502
+ "ENAMETOOLONG",
11503
+ "ENETUNREACH",
11504
+ "ENFILE",
11505
+ "ENOENT",
11506
+ "ENOMEM",
11507
+ "ENOSPC",
11508
+ "ENOTFOUND",
11509
+ "ENOTDIR",
11510
+ "EPIPE",
11511
+ "EPERM",
11512
+ "EROFS",
11513
+ "ETIMEDOUT",
11514
+ "FORBIDDEN",
11515
+ "GATEWAY_TIMEOUT",
11516
+ "IMPORT_ALREADY_IN_PROGRESS",
11517
+ "INTERNAL_SERVER_ERROR",
11518
+ "INVALID_ARGUMENT",
11519
+ "METHOD_NOT_SUPPORTED",
11520
+ "NOT_FOUND",
11521
+ "NOT_IMPLEMENTED",
11522
+ "PARSE_ERROR",
11523
+ "PAYLOAD_TOO_LARGE",
11524
+ "PAYMENT_REQUIRED",
11525
+ "PRECONDITION_FAILED",
11526
+ "SERVICE_UNAVAILABLE",
11527
+ "SESSION_EXPIRED",
11528
+ "SESSION_PERSISTENCE_ERROR",
11529
+ "SESSION_REFRESH_ERROR",
11530
+ "TIMEOUT",
11531
+ "TOO_MANY_REQUESTS",
11532
+ "UNAUTHORIZED",
11533
+ "UNPROCESSABLE_CONTENT",
11534
+ "UNSUPPORTED_MEDIA_TYPE",
11535
+ "VALIDATION_ERROR",
11536
+ "commander.conflictingOption",
11537
+ "commander.error",
11538
+ "commander.excessArguments",
11539
+ "commander.invalidArgument",
11540
+ "commander.missingArgument",
11541
+ "commander.missingMandatoryOptionValue",
11542
+ "commander.optionMissingArgument",
11543
+ "commander.unknownCommand",
11544
+ "commander.unknownOption"
11545
+ ]);
11546
+ SYNC_TRIGGERS = new Set(["hook", "manual", "bootstrap"]);
11547
+ SYNC_STATUSES = new Set([
11548
+ "backlog",
11549
+ "nothing-new",
11550
+ "skipped-backoff",
11551
+ "skipped-lock",
11552
+ "skipped-gateway",
11553
+ "skipped-paused",
11554
+ "studied",
11555
+ "mine-failed",
11556
+ "error",
11557
+ "detached",
11558
+ "detach-failed",
11559
+ "status-only"
11560
+ ]);
11561
+ LEARNER_OUTCOMES = new Set([
11562
+ "completed",
11563
+ "settings_conflict",
11564
+ "consent_off",
11565
+ "credit_limit",
11566
+ "quota_exceeded",
11567
+ "error"
11568
+ ]);
11569
+ BACKFILL_OFFERS = new Set([
11570
+ "not-offered",
11571
+ "accepted",
11572
+ "declined",
11573
+ "cancelled",
11574
+ "spawn-failed"
11575
+ ]);
11576
+ });
11577
+
10730
11578
  // src/report/backfill.ts
10731
11579
  function noteTime2(at) {
10732
11580
  const parsed = Date.parse(at ?? "");
@@ -10872,7 +11720,7 @@ function createRunConfigDir() {
10872
11720
  var init_config_dir = () => {};
10873
11721
 
10874
11722
  // src/learner/conflicts.ts
10875
- import { existsSync as existsSync13, readFileSync as readFileSync15 } from "node:fs";
11723
+ import { existsSync as existsSync14, readFileSync as readFileSync15 } from "node:fs";
10876
11724
  function managedSettingsPaths(platform3 = process.platform) {
10877
11725
  switch (platform3) {
10878
11726
  case "darwin":
@@ -10901,7 +11749,7 @@ function conflictingKeysIn(settings2) {
10901
11749
  function detectSettingsConflicts(paths = managedSettingsPaths()) {
10902
11750
  const conflicts = [];
10903
11751
  for (const file of paths) {
10904
- if (!existsSync13(file))
11752
+ if (!existsSync14(file))
10905
11753
  continue;
10906
11754
  let settings2;
10907
11755
  try {
@@ -10964,7 +11812,7 @@ var init_env = __esm(() => {
10964
11812
  });
10965
11813
 
10966
11814
  // src/learner/executable.ts
10967
- import { accessSync, constants, existsSync as existsSync14 } from "node:fs";
11815
+ import { accessSync, constants, existsSync as existsSync15 } from "node:fs";
10968
11816
  import { createRequire as createRequire3 } from "node:module";
10969
11817
  import { homedir as homedir4 } from "node:os";
10970
11818
  import { delimiter as delimiter2, dirname as dirname5, join as join14 } from "node:path";
@@ -10976,7 +11824,7 @@ function sdkNativeBinaryExists() {
10976
11824
  const requireRuntime = createRequire3(import.meta.url);
10977
11825
  const pkg = `@anthropic-ai/claude-agent-sdk-${process.platform}-${process.arch}`;
10978
11826
  const pkgDir = dirname5(requireRuntime.resolve(`${pkg}/package.json`));
10979
- return binaryNames().some((name) => existsSync14(join14(pkgDir, name)));
11827
+ return binaryNames().some((name) => existsSync15(join14(pkgDir, name)));
10980
11828
  } catch {
10981
11829
  return false;
10982
11830
  }
@@ -73091,1246 +73939,479 @@ async function runLearner(options) {
73091
73939
  const gatewayError = classifyGatewayError(text);
73092
73940
  if (gatewayError) {
73093
73941
  return { outcome: gatewayError.outcome, notesWritten, turns, message: gatewayError.message };
73094
- }
73095
- logger.debug("learner", `run ${runID} threw: ${text}`);
73096
- return {
73097
- outcome: "error",
73098
- notesWritten,
73099
- turns,
73100
- message: abort.signal.aborted ? "Study run timed out and was aborted." : "Study run failed; see debug log for details."
73101
- };
73102
- } finally {
73103
- clearTimeout(timer);
73104
- configDir.cleanup();
73105
- }
73106
- }
73107
- var DEFAULT_MAX_TURNS = 160, DEFAULT_MAX_NOTES = 80, DEFAULT_TIMEOUT_MS2, KNOWLEDGE_SERVER_NAME = "dosu", GATEWAY_ERRORS, TRACE_SNIPPET_LIMIT = 400;
73108
- var init_runner = __esm(() => {
73109
- init_logger();
73110
- init_config_helpers();
73111
- init_notes();
73112
- init_config_dir();
73113
- init_conflicts();
73114
- init_env();
73115
- init_executable();
73116
- init_tools();
73117
- DEFAULT_TIMEOUT_MS2 = 30 * 60 * 1000;
73118
- GATEWAY_ERRORS = {
73119
- dosu_consent_off: {
73120
- outcome: "consent_off",
73121
- message: "Your org hasn't enabled Dosu Remote Sessions. Ask an org admin to turn it on."
73122
- },
73123
- dosu_credit_limit_reached: {
73124
- outcome: "credit_limit",
73125
- message: "Your org has used its Dosu credits for this billing period. An admin can enable overage or upgrade."
73126
- },
73127
- dosu_quota_exceeded: {
73128
- outcome: "quota_exceeded",
73129
- message: "Daily Dosu Remote Sessions budget reached; runs resume tomorrow."
73130
- }
73131
- };
73132
- });
73133
-
73134
- // src/version/skill-update-check.ts
73135
- import { existsSync as existsSync17, mkdirSync as mkdirSync10, readFileSync as readFileSync18, writeFileSync as writeFileSync11 } from "node:fs";
73136
- import { join as join20 } from "node:path";
73137
- function getCachePath2() {
73138
- return join20(getConfigDir(), CACHE_FILENAME3);
73139
- }
73140
- function readSkillCache() {
73141
- try {
73142
- const path3 = getCachePath2();
73143
- if (!existsSync17(path3))
73144
- return null;
73145
- const data = JSON.parse(readFileSync18(path3, "utf-8"));
73146
- if (typeof data.lastCheck === "number" && typeof data.latestSha === "string" && typeof data.installedSha === "string") {
73147
- return data;
73148
- }
73149
- return null;
73150
- } catch {
73151
- return null;
73152
- }
73153
- }
73154
- function writeSkillCache(cache) {
73155
- try {
73156
- const dir = getConfigDir();
73157
- if (!existsSync17(dir)) {
73158
- mkdirSync10(dir, { recursive: true, mode: 448 });
73159
- }
73160
- writeFileSync11(getCachePath2(), JSON.stringify(cache), { mode: 384 });
73161
- } catch {}
73162
- }
73163
- function clearInstalledSha() {
73164
- const cache = readSkillCache();
73165
- if (cache)
73166
- writeSkillCache({ ...cache, installedSha: "" });
73167
- }
73168
- async function fetchLatestSha() {
73169
- const controller = new AbortController;
73170
- const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS2);
73171
- try {
73172
- const resp = await fetch(GITHUB_API_URL, {
73173
- signal: controller.signal,
73174
- headers: {
73175
- "User-Agent": `dosu-cli/${VERSION}`,
73176
- Accept: "application/vnd.github+json"
73177
- }
73178
- });
73179
- if (!resp.ok)
73180
- return null;
73181
- const data = await resp.json();
73182
- const sha = data.sha;
73183
- return typeof sha === "string" ? sha : null;
73184
- } catch {
73185
- return null;
73186
- } finally {
73187
- clearTimeout(timeout);
73188
- }
73189
- }
73190
- function displayNotice() {
73191
- const msg = `
73192
- ${import_picocolors23.default.yellow(" Dosu skill update available")}
73193
- ` + `${import_picocolors23.default.dim(' Run "dosu skill update" to upgrade')}
73194
- `;
73195
- console.error(msg);
73196
- }
73197
- function checkForSkillUpdates() {
73198
- try {
73199
- const cache = readSkillCache();
73200
- if (cache?.installedSha && cache.latestSha && cache.latestSha !== cache.installedSha) {
73201
- displayNotice();
73202
- }
73203
- const isStale = !cache || Date.now() - cache.lastCheck > CHECK_INTERVAL_MS2;
73204
- if (isStale) {
73205
- fetchLatestSha().then((latest) => {
73206
- const fresh = readSkillCache();
73207
- writeSkillCache({
73208
- lastCheck: Date.now(),
73209
- latestSha: latest ?? fresh?.latestSha ?? "",
73210
- installedSha: fresh?.installedSha ?? ""
73211
- });
73212
- if (latest) {
73213
- logger.debug("skill-update-check", `Cached latest SHA: ${latest}`);
73214
- }
73215
- }).catch((err) => {
73216
- logger.error("skill-update-check", `Background fetch failed: ${err}`);
73217
- });
73218
- }
73219
- } catch (err) {
73220
- logger.error("skill-update-check", `Skill update check failed: ${err}`);
73221
- }
73222
- }
73223
- var import_picocolors23, CACHE_FILENAME3 = "skill-update-check.json", CHECK_INTERVAL_MS2, FETCH_TIMEOUT_MS2 = 5000, GITHUB_API_URL = "https://api.github.com/repos/dosu-ai/dosu-skill/commits/HEAD";
73224
- var init_skill_update_check = __esm(() => {
73225
- init_config();
73226
- init_logger();
73227
- import_picocolors23 = __toESM(require_picocolors(), 1);
73228
- CHECK_INTERVAL_MS2 = 24 * 60 * 60 * 1000;
73229
- });
73230
-
73231
- // src/commands/skill.ts
73232
- import { exec as exec2, execSync as execSync2 } from "node:child_process";
73233
- import { homedir as homedir6 } from "node:os";
73234
- import { join as join21 } from "node:path";
73235
- function skillAgentIDsForProviders(providerIDs) {
73236
- return [
73237
- ...new Set(providerIDs.map((providerID) => SKILL_AGENT_BY_PROVIDER[providerID]).filter((agent) => Boolean(agent)))
73238
- ];
73239
- }
73240
- function skillInstallTargetForProvider(providerID) {
73241
- const agentID = SKILL_AGENT_BY_PROVIDER[providerID];
73242
- if (!agentID)
73243
- return null;
73244
- if (agentID === "claude-code") {
73245
- const claudeConfigDir = process.env.CLAUDE_CONFIG_DIR?.trim() || join21(homedir6(), ".claude");
73246
- return { path: join21(claudeConfigDir, "skills", SKILL_NAME), symlink: true };
73247
- }
73248
- if (agentID === "windsurf") {
73249
- return {
73250
- path: join21(homedir6(), ".codeium", "windsurf", "skills", SKILL_NAME),
73251
- symlink: true
73252
- };
73253
- }
73254
- return {
73255
- path: join21(homedir6(), ".agents", "skills", SKILL_NAME),
73256
- symlink: false
73257
- };
73258
- }
73259
- function skillAgentArgs(providerIDs) {
73260
- const agents = providerIDs === undefined ? SUPPORTED_SKILL_AGENTS : skillAgentIDsForProviders(providerIDs);
73261
- return agents.map((agent) => `-a ${agent}`).join(" ");
73262
- }
73263
- function execQuiet(command) {
73264
- return new Promise((resolve6, reject) => {
73265
- exec2(command, { windowsHide: true }, (error61) => {
73266
- if (error61)
73267
- reject(error61);
73268
- else
73269
- resolve6();
73270
- });
73271
- });
73272
- }
73273
- async function installSkill(providerIDs, options = {}) {
73274
- const agentArgs = skillAgentArgs(providerIDs);
73275
- if (!agentArgs) {
73276
- logger.debug("skill", "No selected providers support the Dosu skill");
73277
- return { success: true };
73278
- }
73279
- try {
73280
- const command = `npx skills add ${SKILL_REPO} -g ${agentArgs} -s "*" -y`;
73281
- if (options.quiet)
73282
- await execQuiet(command);
73283
- else
73284
- execSync2(command, { stdio: "inherit" });
73285
- } catch (err) {
73286
- logger.error("skill", `Failed to install skill: ${err}`);
73287
- return { success: false };
73288
- }
73289
- const sha = await fetchLatestSha();
73290
- if (sha) {
73291
- writeSkillCache({
73292
- lastCheck: Date.now(),
73293
- latestSha: sha,
73294
- installedSha: sha
73295
- });
73296
- return { success: true, sha };
73297
- }
73298
- logger.debug("skill", "Skill installed but could not fetch latest SHA");
73299
- return { success: true };
73300
- }
73301
- function installedSkillNames() {
73302
- let entries;
73303
- try {
73304
- const json2 = execSync2("npx skills list -g --json", {
73305
- encoding: "utf-8",
73306
- stdio: ["ignore", "pipe", "ignore"]
73307
- });
73308
- const parsed = JSON.parse(json2);
73309
- if (!Array.isArray(parsed))
73310
- throw new Error("expected a JSON array");
73311
- entries = parsed;
73312
- } catch (err) {
73313
- logger.debug("skill", `Could not list installed skills: ${err}`);
73314
- return null;
73315
- }
73316
- const names = [];
73317
- for (const entry of entries) {
73318
- if (entry.source !== SKILL_REPO || typeof entry.name !== "string")
73319
- continue;
73320
- if (!SAFE_SKILL_NAME.test(entry.name)) {
73321
- logger.warn("skill", `Skipping skill with an unsupported name: ${entry.name}`);
73322
- continue;
73323
- }
73324
- names.push(entry.name);
73325
- }
73326
- return names;
73327
- }
73328
- function skillCommand() {
73329
- const cmd = new Command("skill").description("Manage the Dosu agent skill");
73330
- cmd.command("install").description("Install the Dosu skill for AI coding agents").action(async () => {
73331
- console.log(`Installing skills from ${SKILL_REPO}...`);
73332
- const result = await installSkill();
73333
- if (result.success) {
73334
- console.log(import_picocolors24.default.green(`
73335
- ✓ Skills installed successfully.`));
73336
- } else {
73337
- console.error(import_picocolors24.default.red(`
73338
- Failed to install skill. Make sure npx is available.`));
73339
- process.exit(1);
73340
- }
73341
- });
73342
- cmd.command("remove").description("Remove the Dosu skills").action(() => {
73343
- const installed = installedSkillNames();
73344
- if (installed?.length === 0) {
73345
- console.log(`No skills from ${SKILL_REPO} are installed.`);
73346
- return;
73347
- }
73348
- const targets = installed ?? [SKILL_NAME];
73349
- console.log(`Removing skills from ${SKILL_REPO}...`);
73350
- try {
73351
- execSync2(`npx skills remove -g ${targets.join(" ")} -y`, {
73352
- stdio: "inherit"
73353
- });
73354
- clearInstalledSha();
73355
- console.log(import_picocolors24.default.green(`
73356
- ✓ Skills removed.`));
73357
- } catch {
73358
- console.error(import_picocolors24.default.red(`
73359
- Failed to remove skills.`));
73360
- process.exit(1);
73361
- }
73362
- });
73363
- cmd.command("update").description("Update the Dosu skill to the latest version").action(async () => {
73364
- console.log(`Updating skills from ${SKILL_REPO}...`);
73365
- const result = await installSkill();
73366
- if (!result.success) {
73367
- console.error(import_picocolors24.default.red(`
73368
- Failed to update skill.`));
73369
- process.exit(1);
73370
- }
73371
- console.log(import_picocolors24.default.green(`
73372
- ✓ Skills updated.`));
73373
- });
73374
- return cmd;
73375
- }
73376
- var import_picocolors24, SKILL_REPO = "dosu-ai/dosu-skill", SKILL_NAME = "dosu", SAFE_SKILL_NAME, SUPPORTED_SKILL_AGENTS, SKILL_AGENT_BY_PROVIDER;
73377
- var init_skill = __esm(() => {
73378
- init_commander();
73379
- init_logger();
73380
- init_skill_update_check();
73381
- import_picocolors24 = __toESM(require_picocolors(), 1);
73382
- SAFE_SKILL_NAME = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
73383
- SUPPORTED_SKILL_AGENTS = [
73384
- "claude-code",
73385
- "cursor",
73386
- "gemini-cli",
73387
- "codex",
73388
- "windsurf",
73389
- "zed",
73390
- "cline",
73391
- "github-copilot",
73392
- "opencode",
73393
- "antigravity"
73394
- ];
73395
- SKILL_AGENT_BY_PROVIDER = {
73396
- claude: "claude-code",
73397
- cursor: "cursor",
73398
- vscode: "github-copilot",
73399
- gemini: "gemini-cli",
73400
- codex: "codex",
73401
- windsurf: "windsurf",
73402
- zed: "zed",
73403
- cline: "cline",
73404
- "cline-cli": "cline",
73405
- copilot: "github-copilot",
73406
- opencode: "opencode",
73407
- antigravity: "antigravity"
73408
- };
73409
- });
73410
-
73411
- // src/telemetry/settings.ts
73412
- import { randomUUID as randomUUID3 } from "node:crypto";
73413
- import {
73414
- existsSync as existsSync18,
73415
- lstatSync as lstatSync2,
73416
- mkdirSync as mkdirSync11,
73417
- readFileSync as readFileSync19,
73418
- renameSync as renameSync5,
73419
- unlinkSync as unlinkSync3,
73420
- writeFileSync as writeFileSync12
73421
- } from "node:fs";
73422
- import { join as join22 } from "node:path";
73423
- function getTelemetrySettingsPath() {
73424
- return join22(getConfigDir(), TELEMETRY_SETTINGS_FILENAME);
73425
- }
73426
- function loadTelemetrySettings() {
73427
- return readTelemetrySettings().settings;
73428
- }
73429
- function setTelemetryEnabled(enabled) {
73430
- if (typeof enabled !== "boolean")
73431
- return false;
73432
- const current = readTelemetrySettings();
73433
- if (current.status === "unsupported" || current.status === "error")
73434
- return false;
73435
- const settings2 = current.status === "current" ? current.settings : emptyTelemetrySettings();
73436
- if (enabled)
73437
- delete settings2.disabled;
73438
- else
73439
- settings2.disabled = true;
73440
- return writeTelemetrySettings(settings2);
73441
- }
73442
- function telemetryDisabledByEnvironment() {
73443
- if (isEnabledEnvironmentFlag(process.env.DO_NOT_TRACK))
73444
- return "DO_NOT_TRACK";
73445
- if (isEnabledEnvironmentFlag(process.env.DOSU_TELEMETRY_DISABLED)) {
73446
- return "DOSU_TELEMETRY_DISABLED";
73447
- }
73448
- return;
73449
- }
73450
- function isTelemetryEnabled(settings2 = loadTelemetrySettings()) {
73451
- if (telemetryDisabledByEnvironment() !== undefined)
73452
- return false;
73453
- return settings2.disabled !== true;
73454
- }
73455
- function getOrCreateInstallID() {
73456
- const current = readTelemetrySettings();
73457
- if (current.status === "current" && current.settings.install_id) {
73458
- return current.settings.install_id;
73459
- }
73460
- if (current.status !== "current" && current.status !== "missing")
73461
- return;
73462
- const installID = randomUUID3();
73463
- const settings2 = current.status === "current" ? current.settings : emptyTelemetrySettings();
73464
- settings2.install_id = installID;
73465
- return writeTelemetrySettings(settings2) ? installID : undefined;
73466
- }
73467
- function resetInstallID() {
73468
- const current = readTelemetrySettings();
73469
- if (current.status !== "current" && current.status !== "missing")
73470
- return;
73471
- const settings2 = current.status === "current" ? current.settings : emptyTelemetrySettings();
73472
- const installID = randomUUID3();
73473
- settings2.install_id = installID;
73474
- return writeTelemetrySettings(settings2) ? installID : undefined;
73475
- }
73476
- function readTelemetrySettings() {
73477
- const path3 = getTelemetrySettingsPath();
73478
- let file2;
73479
- try {
73480
- file2 = lstatSync2(path3);
73481
- } catch (error61) {
73482
- if (isErrorCode(error61, "ENOENT")) {
73483
- return { status: "missing", settings: emptyTelemetrySettings() };
73484
- }
73485
- return { status: "error", settings: disabledTelemetrySettings() };
73486
- }
73487
- if (!file2.isFile() || file2.size > MAX_TELEMETRY_SETTINGS_BYTES) {
73488
- return { status: "invalid", settings: disabledTelemetrySettings() };
73489
- }
73490
- let content;
73491
- try {
73492
- content = readFileSync19(path3, "utf-8");
73493
- } catch {
73494
- return { status: "error", settings: disabledTelemetrySettings() };
73495
- }
73496
- let raw;
73497
- try {
73498
- raw = JSON.parse(content);
73499
- } catch {
73500
- return { status: "invalid", settings: disabledTelemetrySettings() };
73501
- }
73502
- if (!isRecord5(raw))
73503
- return { status: "invalid", settings: disabledTelemetrySettings() };
73504
- if (raw.schema_version !== TELEMETRY_SCHEMA_VERSION) {
73505
- const status = "schema_version" in raw ? "unsupported" : "invalid";
73506
- return { status, settings: disabledTelemetrySettings() };
73507
- }
73508
- if ("disabled" in raw && typeof raw.disabled !== "boolean") {
73509
- return { status: "invalid", settings: disabledTelemetrySettings() };
73510
- }
73511
- const settings2 = emptyTelemetrySettings();
73512
- if (raw.disabled === true)
73513
- settings2.disabled = true;
73514
- if (typeof raw.install_id === "string" && isUUID(raw.install_id)) {
73515
- settings2.install_id = raw.install_id;
73516
- }
73517
- return { status: "current", settings: settings2 };
73518
- }
73519
- function writeTelemetrySettings(settings2) {
73520
- const path3 = getTelemetrySettingsPath();
73521
- const dir = getConfigDir();
73522
- const temporaryPath = `${path3}.${process.pid}.${randomUUID3()}.tmp`;
73523
- try {
73524
- if (!existsSync18(dir))
73525
- mkdirSync11(dir, { recursive: true, mode: 448 });
73526
- writeFileSync12(temporaryPath, `${JSON.stringify(settings2, null, 2)}
73527
- `, {
73528
- flag: "wx",
73529
- mode: 384
73530
- });
73531
- renameSync5(temporaryPath, path3);
73532
- return true;
73533
- } catch {
73534
- try {
73535
- unlinkSync3(temporaryPath);
73536
- } catch {}
73537
- return false;
73942
+ }
73943
+ logger.debug("learner", `run ${runID} threw: ${text}`);
73944
+ return {
73945
+ outcome: "error",
73946
+ notesWritten,
73947
+ turns,
73948
+ message: abort.signal.aborted ? "Study run timed out and was aborted." : "Study run failed; see debug log for details."
73949
+ };
73950
+ } finally {
73951
+ clearTimeout(timer);
73952
+ configDir.cleanup();
73538
73953
  }
73539
73954
  }
73540
- function emptyTelemetrySettings() {
73541
- return { schema_version: TELEMETRY_SCHEMA_VERSION };
73542
- }
73543
- function disabledTelemetrySettings() {
73544
- return { schema_version: TELEMETRY_SCHEMA_VERSION, disabled: true };
73545
- }
73546
- function isEnabledEnvironmentFlag(value) {
73547
- if (value === undefined)
73548
- return false;
73549
- const normalized = value.trim().toLowerCase();
73550
- if (normalized === "")
73551
- return false;
73552
- return !["0", "false", "no", "off"].includes(normalized);
73553
- }
73554
- function isUUID(value) {
73555
- 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);
73556
- }
73557
- function isRecord5(value) {
73558
- return typeof value === "object" && value !== null && !Array.isArray(value);
73559
- }
73560
- function isErrorCode(error61, code) {
73561
- return typeof error61 === "object" && error61 !== null && "code" in error61 && error61.code === code;
73562
- }
73563
- var TELEMETRY_SCHEMA_VERSION = 1, TELEMETRY_SETTINGS_FILENAME = "telemetry.json", MAX_TELEMETRY_SETTINGS_BYTES;
73564
- var init_settings = __esm(() => {
73565
- init_config();
73566
- MAX_TELEMETRY_SETTINGS_BYTES = 16 * 1024;
73955
+ var DEFAULT_MAX_TURNS = 160, DEFAULT_MAX_NOTES = 80, DEFAULT_TIMEOUT_MS2, KNOWLEDGE_SERVER_NAME = "dosu", GATEWAY_ERRORS, TRACE_SNIPPET_LIMIT = 400;
73956
+ var init_runner = __esm(() => {
73957
+ init_logger();
73958
+ init_config_helpers();
73959
+ init_notes();
73960
+ init_config_dir();
73961
+ init_conflicts();
73962
+ init_env();
73963
+ init_executable();
73964
+ init_tools();
73965
+ DEFAULT_TIMEOUT_MS2 = 30 * 60 * 1000;
73966
+ GATEWAY_ERRORS = {
73967
+ dosu_consent_off: {
73968
+ outcome: "consent_off",
73969
+ message: "Your org hasn't enabled Dosu Remote Sessions. Ask an org admin to turn it on."
73970
+ },
73971
+ dosu_credit_limit_reached: {
73972
+ outcome: "credit_limit",
73973
+ message: "Your org has used its Dosu credits for this billing period. An admin can enable overage or upgrade."
73974
+ },
73975
+ dosu_quota_exceeded: {
73976
+ outcome: "quota_exceeded",
73977
+ message: "Daily Dosu Remote Sessions budget reached; runs resume tomorrow."
73978
+ }
73979
+ };
73567
73980
  });
73568
73981
 
73569
- // src/telemetry/debug-id.ts
73570
- var BUNDLE_DEBUG_ID_PLACEHOLDER = "5776df20-501e-ff5f-6475-6e2164756e21";
73571
-
73572
- // src/telemetry/telemetry.ts
73573
- import { randomUUID as randomUUID4 } from "node:crypto";
73574
- import { existsSync as existsSync19 } from "node:fs";
73575
- import { request as httpRequest2 } from "node:http";
73576
- import { request as httpsRequest } from "node:https";
73577
- import { isAbsolute as isAbsolute4, relative as relative2, resolve as resolve6, sep as sep5 } from "node:path";
73578
- import { fileURLToPath as fileURLToPath2 } from "node:url";
73579
- function safeString(value, pattern, fallback) {
73580
- if (typeof value !== "string" || !pattern.test(value))
73581
- return fallback;
73582
- return value;
73583
- }
73584
- function canonicalCommand(value) {
73585
- if (typeof value !== "string")
73586
- return UNKNOWN_COMMAND;
73587
- const command = value.trim();
73588
- if (command.length > 80)
73589
- return UNKNOWN_COMMAND;
73590
- if (!/^[a-z][a-z0-9-]*(?: [a-z][a-z0-9-]*){0,3}$/.test(command)) {
73591
- return UNKNOWN_COMMAND;
73592
- }
73593
- return command;
73594
- }
73595
- function normalizeContext(context) {
73596
- const isAuthenticated2 = context?.isAuthenticated === true;
73597
- const id2 = safeRead(context?.user, "id");
73598
- const email3 = safeRead(context?.user, "email");
73599
- const orgId = safeRead(context, "orgId");
73600
- const user = isAuthenticated2 && isUUID2(id2) ? { id: id2, ...validEmail(email3) ? { email: email3 } : {} } : undefined;
73601
- return {
73602
- mode: context?.mode === "oss" ? "oss" : "cloud",
73603
- isAuthenticated: isAuthenticated2,
73604
- ...user ? { user } : {},
73605
- ...user && isUUID2(orgId) ? { orgId } : {}
73606
- };
73607
- }
73608
- function normalizeExitCode(value, fallback = 1) {
73609
- if (typeof value !== "number" || !Number.isFinite(value))
73610
- return fallback;
73611
- return Math.min(255, Math.max(0, Math.trunc(value)));
73612
- }
73613
- function normalizeRuntime(runtime) {
73614
- return {
73615
- version: safeString(runtime.version, /^[A-Za-z0-9.+_-]{1,32}$/, "unknown"),
73616
- installChannel: safeString(runtime.installChannel, /^[A-Za-z][A-Za-z0-9_-]{0,31}$/, "unknown"),
73617
- platform: safeString(runtime.platform, /^[A-Za-z0-9_-]{1,24}$/, "unknown"),
73618
- arch: safeString(runtime.arch, /^[A-Za-z0-9_-]{1,24}$/, "unknown"),
73619
- runtime: safeString(runtime.runtime, /^[A-Za-z][A-Za-z0-9_-]{0,23}$/, "unknown"),
73620
- runtimeMajor: Math.min(9999, Math.max(0, Math.trunc(runtime.runtimeMajor) || 0)),
73621
- isCi: runtime.isCi === true,
73622
- isTty: runtime.isTty === true
73623
- };
73624
- }
73625
- function isUUID2(value) {
73626
- 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);
73627
- }
73628
- function validEmail(value) {
73629
- 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);
73630
- }
73631
- function validEventId(value) {
73632
- return typeof value === "string" && /^[0-9a-f]{32}$/i.test(value);
73633
- }
73634
- function validDebugId(value) {
73635
- 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);
73636
- }
73637
- function validErrorType(value) {
73638
- return typeof value === "string" && SAFE_ERROR_TYPES.has(value);
73639
- }
73640
- function validErrorCode(value) {
73641
- return typeof value === "string" && SAFE_ERROR_CODES.has(value);
73642
- }
73643
- function validStatus(value) {
73644
- return typeof value === "number" && Number.isInteger(value) && value >= 100 && value <= 599;
73645
- }
73646
- function objectLike(value) {
73647
- return typeof value === "object" && value !== null || typeof value === "function";
73982
+ // src/version/skill-update-check.ts
73983
+ import { existsSync as existsSync18, mkdirSync as mkdirSync10, readFileSync as readFileSync18, writeFileSync as writeFileSync11 } from "node:fs";
73984
+ import { join as join20 } from "node:path";
73985
+ function getCachePath2() {
73986
+ return join20(getConfigDir(), CACHE_FILENAME3);
73648
73987
  }
73649
- function safeRead(value, key) {
73650
- if (!objectLike(value))
73651
- return;
73988
+ function readSkillCache() {
73652
73989
  try {
73653
- return Reflect.get(value, key);
73654
- } catch {
73655
- return;
73656
- }
73657
- }
73658
- function parseStack(stack) {
73659
- if (typeof stack !== "string" || !CLI_PACKAGE_ROOT)
73660
- return [];
73661
- const frames = [];
73662
- const lines = stack.slice(0, 1e5).split(`
73663
- `, 200);
73664
- for (const originalLine of lines) {
73665
- if (frames.length >= MAX_STACK_FRAMES)
73666
- break;
73667
- const parsed = parseOwnedStackLine(originalLine, CLI_PACKAGE_ROOT);
73668
- if (!parsed)
73669
- continue;
73670
- const { filename, lineno, colno } = parsed;
73671
- if (!Number.isSafeInteger(lineno) || !Number.isSafeInteger(colno) || lineno < 1 || colno < 1 || lineno > 1e7 || colno > 1e7) {
73672
- continue;
73990
+ const path3 = getCachePath2();
73991
+ if (!existsSync18(path3))
73992
+ return null;
73993
+ const data = JSON.parse(readFileSync18(path3, "utf-8"));
73994
+ if (typeof data.lastCheck === "number" && typeof data.latestSha === "string" && typeof data.installedSha === "string") {
73995
+ return data;
73673
73996
  }
73674
- frames.push({ filename, lineno, colno, in_app: true });
73997
+ return null;
73998
+ } catch {
73999
+ return null;
73675
74000
  }
73676
- return frames;
73677
74001
  }
73678
- function detectCliPackageRoot() {
74002
+ function writeSkillCache(cache) {
73679
74003
  try {
73680
- const modulePath = fileURLToPath2(import.meta.url).replaceAll("\\", "/");
73681
- for (const suffix of ["/src/telemetry/telemetry.ts", "/bin/dosu.js"]) {
73682
- if (modulePath.endsWith(suffix))
73683
- return modulePath.slice(0, -suffix.length);
74004
+ const dir = getConfigDir();
74005
+ if (!existsSync18(dir)) {
74006
+ mkdirSync10(dir, { recursive: true, mode: 448 });
73684
74007
  }
74008
+ writeFileSync11(getCachePath2(), JSON.stringify(cache), { mode: 384 });
73685
74009
  } catch {}
73686
- return;
73687
74010
  }
73688
- function parseOwnedStackLine(originalLine, packageRoot) {
73689
- const line = originalLine.replaceAll("\\", "/");
73690
- const match = line.match(/^\s*at\s+.*\s+\((.+):(\d+):(\d+)\)\s*$/) ?? line.match(/^\s*at\s+(.+):(\d+):(\d+)\s*$/);
73691
- if (!match?.[1] || !match[2] || !match[3])
73692
- return;
73693
- let absolutePath = match[1];
73694
- try {
73695
- if (absolutePath.startsWith("file://"))
73696
- absolutePath = fileURLToPath2(absolutePath);
73697
- } catch {
73698
- return;
73699
- }
73700
- if (!isAbsolute4(absolutePath))
73701
- return;
73702
- const resolvedPath = resolve6(absolutePath);
73703
- const relativePath = relative2(packageRoot, resolvedPath);
73704
- if (relativePath === "" || relativePath === ".." || relativePath.startsWith(`..${sep5}`) || isAbsolute4(relativePath)) {
73705
- return;
73706
- }
73707
- const filename = relativePath.replaceAll("\\", "/");
73708
- if (!/^(?:src\/(?:[A-Za-z0-9_.-]+\/)*[A-Za-z0-9_.-]+\.(?:[cm]?[jt]sx?)|bin\/dosu\.js)$/.test(filename)) {
73709
- return;
73710
- }
74011
+ function clearInstalledSha() {
74012
+ const cache = readSkillCache();
74013
+ if (cache)
74014
+ writeSkillCache({ ...cache, installedSha: "" });
74015
+ }
74016
+ async function fetchLatestSha() {
74017
+ const controller = new AbortController;
74018
+ const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS2);
73711
74019
  try {
73712
- if (!existsSync19(resolvedPath))
73713
- return;
74020
+ const resp = await fetch(GITHUB_API_URL, {
74021
+ signal: controller.signal,
74022
+ headers: {
74023
+ "User-Agent": `dosu-cli/${VERSION}`,
74024
+ Accept: "application/vnd.github+json"
74025
+ }
74026
+ });
74027
+ if (!resp.ok)
74028
+ return null;
74029
+ const data = await resp.json();
74030
+ const sha = data.sha;
74031
+ return typeof sha === "string" ? sha : null;
73714
74032
  } catch {
73715
- return;
74033
+ return null;
74034
+ } finally {
74035
+ clearTimeout(timeout);
73716
74036
  }
73717
- return { filename, lineno: Number(match[2]), colno: Number(match[3]) };
73718
74037
  }
73719
- function sanitizeError(error61) {
73720
- const normalizedError = domainErrorFromCauseChain(error61) ?? error61;
73721
- const data = safeRead(normalizedError, "data");
73722
- const typeCandidate = safeRead(normalizedError, "name");
73723
- const codeCandidates = [safeRead(data, "code"), safeRead(normalizedError, "code")];
73724
- const statusCandidates = [
73725
- safeRead(data, "httpStatus"),
73726
- safeRead(normalizedError, "status"),
73727
- safeRead(normalizedError, "statusCode")
73728
- ];
73729
- const exitCodeCandidate = safeRead(normalizedError, "exitCode");
73730
- const code = codeCandidates.find(validErrorCode);
73731
- const status = statusCandidates.find(validStatus);
73732
- const exitCode = typeof exitCodeCandidate === "number" && Number.isFinite(exitCodeCandidate) ? normalizeExitCode(exitCodeCandidate) : undefined;
73733
- return {
73734
- type: validErrorType(typeCandidate) ? typeCandidate : "Error",
73735
- ...code ? { code } : {},
73736
- ...status ? { status } : {},
73737
- frames: parseStack(safeRead(normalizedError, "stack")),
73738
- ...exitCode === undefined ? {} : { exitCode }
73739
- };
74038
+ function displayNotice() {
74039
+ const msg = `
74040
+ ${import_picocolors23.default.yellow(" Dosu skill update available")}
74041
+ ` + `${import_picocolors23.default.dim(' Run "dosu skill update" to upgrade')}
74042
+ `;
74043
+ console.error(msg);
73740
74044
  }
73741
- function domainErrorFromCauseChain(error61) {
73742
- const seen = new Set;
73743
- let current = error61;
73744
- for (let depth = 0;depth < 5 && objectLike(current); depth += 1) {
73745
- if (seen.has(current))
73746
- return;
73747
- seen.add(current);
73748
- const code = safeRead(current, "code");
73749
- if (code === "SESSION_EXPIRED" || code === "SESSION_PERSISTENCE_ERROR" || code === "SESSION_REFRESH_ERROR") {
73750
- return current;
74045
+ function checkForSkillUpdates() {
74046
+ try {
74047
+ const cache = readSkillCache();
74048
+ if (cache?.installedSha && cache.latestSha && cache.latestSha !== cache.installedSha) {
74049
+ displayNotice();
73751
74050
  }
73752
- current = safeRead(current, "cause");
74051
+ const isStale = !cache || Date.now() - cache.lastCheck > CHECK_INTERVAL_MS2;
74052
+ if (isStale) {
74053
+ fetchLatestSha().then((latest) => {
74054
+ const fresh = readSkillCache();
74055
+ writeSkillCache({
74056
+ lastCheck: Date.now(),
74057
+ latestSha: latest ?? fresh?.latestSha ?? "",
74058
+ installedSha: fresh?.installedSha ?? ""
74059
+ });
74060
+ if (latest) {
74061
+ logger.debug("skill-update-check", `Cached latest SHA: ${latest}`);
74062
+ }
74063
+ }).catch((err) => {
74064
+ logger.error("skill-update-check", `Background fetch failed: ${err}`);
74065
+ });
74066
+ }
74067
+ } catch (err) {
74068
+ logger.error("skill-update-check", `Skill update check failed: ${err}`);
73753
74069
  }
73754
- return;
73755
74070
  }
73756
- function durationBucket(durationMs) {
73757
- const duration3 = Number.isFinite(durationMs) ? Math.max(0, durationMs) : 0;
73758
- if (duration3 < 100)
73759
- return "<100ms";
73760
- if (duration3 < 500)
73761
- return "100-499ms";
73762
- if (duration3 < 2000)
73763
- return "500ms-1.9s";
73764
- if (duration3 < 1e4)
73765
- return "2-9.9s";
73766
- if (duration3 < 60000)
73767
- return "10-59s";
73768
- return "60s+";
74071
+ var import_picocolors23, CACHE_FILENAME3 = "skill-update-check.json", CHECK_INTERVAL_MS2, FETCH_TIMEOUT_MS2 = 5000, GITHUB_API_URL = "https://api.github.com/repos/dosu-ai/dosu-skill/commits/HEAD";
74072
+ var init_skill_update_check = __esm(() => {
74073
+ init_config();
74074
+ init_logger();
74075
+ import_picocolors23 = __toESM(require_picocolors(), 1);
74076
+ CHECK_INTERVAL_MS2 = 24 * 60 * 60 * 1000;
74077
+ });
74078
+
74079
+ // src/commands/skill.ts
74080
+ import { exec as exec2, execSync as execSync2 } from "node:child_process";
74081
+ import { homedir as homedir6 } from "node:os";
74082
+ import { join as join21 } from "node:path";
74083
+ function skillAgentIDsForProviders(providerIDs) {
74084
+ return [
74085
+ ...new Set(providerIDs.map((providerID) => SKILL_AGENT_BY_PROVIDER[providerID]).filter((agent) => Boolean(agent)))
74086
+ ];
73769
74087
  }
73770
- function buildPostHogPayload(input2) {
73771
- const context = normalizeContext(input2.context);
73772
- const runtime = normalizeRuntime(input2.runtime);
73773
- const errorCode = validErrorCode(input2.errorCode) ? input2.errorCode : undefined;
73774
- const result = input2.result === "success" || input2.result === "validation_error" ? input2.result : "failure";
74088
+ function skillInstallTargetForProvider(providerID) {
74089
+ const agentID = SKILL_AGENT_BY_PROVIDER[providerID];
74090
+ if (!agentID)
74091
+ return null;
74092
+ if (agentID === "claude-code") {
74093
+ const claudeConfigDir = process.env.CLAUDE_CONFIG_DIR?.trim() || join21(homedir6(), ".claude");
74094
+ return { path: join21(claudeConfigDir, "skills", SKILL_NAME), symlink: true };
74095
+ }
74096
+ if (agentID === "windsurf") {
74097
+ return {
74098
+ path: join21(homedir6(), ".codeium", "windsurf", "skills", SKILL_NAME),
74099
+ symlink: true
74100
+ };
74101
+ }
73775
74102
  return {
73776
- api_key: input2.apiKey,
73777
- distinct_id: context.user?.id ?? (isUUID2(input2.installId) ? input2.installId : FALLBACK_INSTALL_ID),
73778
- event: "cli_command_completed",
73779
- properties: {
73780
- $geoip_disable: true,
73781
- ...context.user ? {} : { $process_person_profile: false },
73782
- schema_version: 1,
73783
- command: canonicalCommand(input2.command),
73784
- result,
73785
- duration_bucket: durationBucket(input2.durationMs),
73786
- cli_version: runtime.version,
73787
- install_channel: runtime.installChannel,
73788
- platform: runtime.platform,
73789
- arch: runtime.arch,
73790
- runtime: runtime.runtime,
73791
- runtime_major: runtime.runtimeMajor,
73792
- is_ci: runtime.isCi,
73793
- is_tty: runtime.isTty,
73794
- mode: context.mode,
73795
- ...context.orgId ? {
73796
- org_id: context.orgId,
73797
- $groups: { organization: context.orgId }
73798
- } : {},
73799
- is_authenticated: context.isAuthenticated,
73800
- exit_code: normalizeExitCode(input2.exitCode),
73801
- ...errorCode ? { error_code: errorCode } : {}
73802
- }
74103
+ path: join21(homedir6(), ".agents", "skills", SKILL_NAME),
74104
+ symlink: false
73803
74105
  };
73804
74106
  }
73805
- function parsePostHogProjectToken(value) {
73806
- if (typeof value !== "string")
73807
- return null;
73808
- const token = value.trim();
73809
- return /^phc_[A-Za-z0-9_-]{3,252}$/.test(token) ? token : null;
74107
+ function skillAgentArgs(providerIDs) {
74108
+ const agents = providerIDs === undefined ? SUPPORTED_SKILL_AGENTS : skillAgentIDsForProviders(providerIDs);
74109
+ return agents.map((agent) => `-a ${agent}`).join(" ");
73810
74110
  }
73811
- function parseTelemetryWebAppURL(value, allowInsecureLoopback = false) {
73812
- if (typeof value !== "string" || value.length === 0 || value.length > 2048)
73813
- return null;
74111
+ function execQuiet(command) {
74112
+ return new Promise((resolve7, reject) => {
74113
+ exec2(command, { windowsHide: true }, (error61) => {
74114
+ if (error61)
74115
+ reject(error61);
74116
+ else
74117
+ resolve7();
74118
+ });
74119
+ });
74120
+ }
74121
+ async function installSkill(providerIDs, options = {}) {
74122
+ const agentArgs = skillAgentArgs(providerIDs);
74123
+ if (!agentArgs) {
74124
+ logger.debug("skill", "No selected providers support the Dosu skill");
74125
+ return { success: true };
74126
+ }
73814
74127
  try {
73815
- const url2 = new URL(value.trim());
73816
- if (!url2.hostname || url2.username || url2.password || url2.search || url2.hash)
73817
- return null;
73818
- const loopbackHosts = new Set(["localhost", "127.0.0.1", "[::1]"]);
73819
- const secure = url2.protocol === "https:";
73820
- const allowedDevelopmentOrigin = allowInsecureLoopback && url2.protocol === "http:" && loopbackHosts.has(url2.hostname);
73821
- if (!secure && !allowedDevelopmentOrigin)
73822
- return null;
73823
- return url2.toString().replace(/\/$/, "");
73824
- } catch {
73825
- return null;
74128
+ const command = `npx skills add ${SKILL_REPO} -g ${agentArgs} -s "*" -y`;
74129
+ if (options.quiet)
74130
+ await execQuiet(command);
74131
+ else
74132
+ execSync2(command, { stdio: "inherit" });
74133
+ } catch (err) {
74134
+ logger.error("skill", `Failed to install skill: ${err}`);
74135
+ return { success: false };
74136
+ }
74137
+ const sha = await fetchLatestSha();
74138
+ if (sha) {
74139
+ writeSkillCache({
74140
+ lastCheck: Date.now(),
74141
+ latestSha: sha,
74142
+ installedSha: sha
74143
+ });
74144
+ return { success: true, sha };
73826
74145
  }
74146
+ logger.debug("skill", "Skill installed but could not fetch latest SHA");
74147
+ return { success: true };
73827
74148
  }
73828
- function parseSentryDsn(value) {
73829
- if (typeof value !== "string" || value.length === 0 || value.length > 2048)
73830
- return null;
73831
- const dsn = value.trim();
74149
+ function installedSkillNames() {
74150
+ let entries;
73832
74151
  try {
73833
- const url2 = new URL(dsn);
73834
- if (url2.protocol !== "https:" || !url2.hostname || !url2.username || url2.password || url2.search || url2.hash || !/^[A-Za-z0-9_.-]{1,128}$/.test(url2.username) || url2.username.toLowerCase().startsWith("sntry")) {
73835
- return null;
73836
- }
73837
- const segments = url2.pathname.split("/").filter(Boolean);
73838
- const projectId = segments.pop();
73839
- if (!projectId || !/^[A-Za-z0-9_-]{1,128}$/.test(projectId))
73840
- return null;
73841
- if (segments.some((segment) => !/^[A-Za-z0-9._~-]{1,128}$/.test(segment)))
73842
- return null;
73843
- const prefix = segments.length > 0 ? `/${segments.join("/")}` : "";
73844
- return {
73845
- dsn,
73846
- endpoint: `${url2.protocol}//${url2.host}${prefix}/api/${projectId}/envelope/`,
73847
- projectId,
73848
- publicKey: url2.username
73849
- };
73850
- } catch {
74152
+ const json2 = execSync2("npx skills list -g --json", {
74153
+ encoding: "utf-8",
74154
+ stdio: ["ignore", "pipe", "ignore"]
74155
+ });
74156
+ const parsed = JSON.parse(json2);
74157
+ if (!Array.isArray(parsed))
74158
+ throw new Error("expected a JSON array");
74159
+ entries = parsed;
74160
+ } catch (err) {
74161
+ logger.debug("skill", `Could not list installed skills: ${err}`);
73851
74162
  return null;
73852
74163
  }
74164
+ const names = [];
74165
+ for (const entry of entries) {
74166
+ if (entry.source !== SKILL_REPO || typeof entry.name !== "string")
74167
+ continue;
74168
+ if (!SAFE_SKILL_NAME.test(entry.name)) {
74169
+ logger.warn("skill", `Skipping skill with an unsupported name: ${entry.name}`);
74170
+ continue;
74171
+ }
74172
+ names.push(entry.name);
74173
+ }
74174
+ return names;
73853
74175
  }
73854
- function sentryTags(command, context, runtime, error61) {
73855
- return {
73856
- schema_version: "1",
73857
- command,
73858
- cli_version: runtime.version,
73859
- install_channel: runtime.installChannel,
73860
- os: runtime.platform,
73861
- arch: runtime.arch,
73862
- runtime: runtime.runtime,
73863
- runtime_major: String(runtime.runtimeMajor),
73864
- is_ci: String(runtime.isCi),
73865
- is_tty: String(runtime.isTty),
73866
- mode: context.mode,
73867
- is_authenticated: String(context.isAuthenticated),
73868
- ...error61.code ? { error_code: error61.code } : {},
73869
- ...error61.status ? { http_status: String(error61.status) } : {},
73870
- ...error61.exitCode === undefined ? {} : { exit_code: String(error61.exitCode) }
73871
- };
73872
- }
73873
- function buildSentryEnvelope(input2) {
73874
- const parsedDsn = parseSentryDsn(input2.dsn);
73875
- if (!parsedDsn || !validEventId(input2.eventId))
73876
- return null;
73877
- const context = normalizeContext(input2.context);
73878
- const runtime = normalizeRuntime(input2.runtime);
73879
- const command = canonicalCommand(input2.command);
73880
- const debugId = validDebugId(input2.debugId) ? input2.debugId.toLowerCase() : undefined;
73881
- const error61 = {
73882
- type: validErrorType(input2.error.type) ? input2.error.type : "Error",
73883
- ...validErrorCode(input2.error.code) ? { code: input2.error.code } : {},
73884
- ...validStatus(input2.error.status) ? { status: input2.error.status } : {},
73885
- ...typeof input2.error.exitCode === "number" && Number.isFinite(input2.error.exitCode) ? { exitCode: normalizeExitCode(input2.error.exitCode) } : {},
73886
- frames: input2.error.frames.slice(0, MAX_STACK_FRAMES).map((frame) => {
73887
- 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";
73888
- return {
73889
- filename,
73890
- ...debugId && filename === "bin/dosu.js" ? { abs_path: BUNDLE_CODE_FILE } : {},
73891
- lineno: Math.min(1e7, Math.max(1, Math.trunc(frame.lineno) || 1)),
73892
- colno: Math.min(1e7, Math.max(1, Math.trunc(frame.colno) || 1)),
73893
- in_app: true
73894
- };
73895
- }).reverse()
73896
- };
73897
- const exceptionValue = {
73898
- type: error61.type,
73899
- value: error61.code ?? error61.type,
73900
- ...error61.frames.length > 0 ? { stacktrace: { frames: error61.frames } } : {}
73901
- };
73902
- const newestFrame = error61.frames.at(-1);
73903
- const safeCallsite = newestFrame ? `${newestFrame.filename}:${newestFrame.lineno}` : "unknown";
73904
- const timestampMs = Number.isFinite(input2.timestampMs) ? Math.max(0, input2.timestampMs) : 0;
73905
- const mappedBundle = debugId && error61.frames.some((frame) => frame.abs_path === BUNDLE_CODE_FILE);
73906
- const event = {
73907
- event_id: input2.eventId.toLowerCase(),
73908
- timestamp: timestampMs / 1000,
73909
- platform: "node",
73910
- level: "error",
73911
- release: `dosu-cli@${runtime.version}`,
73912
- tags: sentryTags(command, context, runtime, error61),
73913
- ...context.user ? {
73914
- user: {
73915
- id: context.user.id,
73916
- ...context.user.email ? { email: context.user.email } : {}
73917
- }
73918
- } : {},
73919
- ...mappedBundle ? {
73920
- debug_meta: {
73921
- images: [{ type: "sourcemap", code_file: BUNDLE_CODE_FILE, debug_id: debugId }]
73922
- }
73923
- } : {},
73924
- fingerprint: ["dosu-cli", command, error61.type, error61.code ?? "unknown", safeCallsite],
73925
- exception: { values: [exceptionValue] }
73926
- };
73927
- const envelopeHeader = {
73928
- event_id: input2.eventId.toLowerCase(),
73929
- dsn: parsedDsn.dsn,
73930
- sent_at: new Date(timestampMs).toISOString()
73931
- };
73932
- return {
73933
- endpoint: parsedDsn.endpoint,
73934
- body: `${JSON.stringify(envelopeHeader)}
73935
- ${JSON.stringify({ type: "event" })}
73936
- ${JSON.stringify(event)}`
73937
- };
73938
- }
73939
- function fetchWithoutRedirect(url2, init = {}) {
73940
- let parsedURL;
73941
- try {
73942
- parsedURL = new URL(url2);
73943
- if (parsedURL.protocol !== "http:" && parsedURL.protocol !== "https:") {
73944
- return Promise.reject(new Error("unsupported telemetry protocol"));
74176
+ function skillCommand() {
74177
+ const cmd = new Command("skill").description("Manage the Dosu agent skill");
74178
+ cmd.command("install").description("Install the Dosu skill for AI coding agents").action(async () => {
74179
+ console.log(`Installing skills from ${SKILL_REPO}...`);
74180
+ const result = await installSkill();
74181
+ if (result.success) {
74182
+ console.log(import_picocolors24.default.green(`
74183
+ ✓ Skills installed successfully.`));
74184
+ } else {
74185
+ console.error(import_picocolors24.default.red(`
74186
+ Failed to install skill. Make sure npx is available.`));
74187
+ process.exit(1);
74188
+ }
74189
+ });
74190
+ cmd.command("remove").description("Remove the Dosu skills").action(() => {
74191
+ const installed = installedSkillNames();
74192
+ if (installed?.length === 0) {
74193
+ console.log(`No skills from ${SKILL_REPO} are installed.`);
74194
+ return;
73945
74195
  }
73946
- } catch (error61) {
73947
- return Promise.reject(error61);
73948
- }
73949
- return new Promise((resolve7, reject) => {
73950
- let request;
73951
- let response;
73952
- let settled = false;
73953
- const signal = init.signal;
73954
- const cleanup = () => signal?.removeEventListener("abort", onAbort);
73955
- const fail = (error61) => {
73956
- if (settled)
73957
- return;
73958
- settled = true;
73959
- cleanup();
73960
- response?.destroy();
73961
- request?.socket?.destroy();
73962
- request?.destroy();
73963
- reject(error61);
73964
- };
73965
- const succeed = (value) => {
73966
- if (settled)
73967
- return;
73968
- settled = true;
73969
- cleanup();
73970
- response?.destroy();
73971
- request?.socket?.destroy();
73972
- request?.destroy();
73973
- resolve7(value);
73974
- };
73975
- const onAbort = () => {
73976
- const error61 = new Error("telemetry request aborted");
73977
- error61.name = "AbortError";
73978
- fail(error61);
73979
- };
74196
+ const targets = installed ?? [SKILL_NAME];
74197
+ console.log(`Removing skills from ${SKILL_REPO}...`);
73980
74198
  try {
73981
- if (signal?.aborted) {
73982
- onAbort();
73983
- return;
73984
- }
73985
- const body = init.body;
73986
- if (body !== undefined && body !== null && typeof body !== "string") {
73987
- fail(new Error("unsupported telemetry request body"));
73988
- return;
73989
- }
73990
- const headers = Object.fromEntries(new Headers(init.headers).entries());
73991
- const transport = parsedURL.protocol === "https:" ? httpsRequest : httpRequest2;
73992
- request = transport(parsedURL, {
73993
- method: init.method ?? "GET",
73994
- headers,
73995
- agent: false
73996
- }, (incoming) => {
73997
- response = incoming;
73998
- const chunks = [];
73999
- let totalBytes = 0;
74000
- incoming.on("data", (chunk) => {
74001
- const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
74002
- totalBytes += bytes.byteLength;
74003
- if (totalBytes > MAX_RESPONSE_BYTES) {
74004
- fail(new Error("telemetry response too large"));
74005
- return;
74006
- }
74007
- chunks.push(bytes);
74008
- });
74009
- incoming.once("error", fail);
74010
- incoming.once("end", () => {
74011
- try {
74012
- const responseHeaders = new Headers;
74013
- for (let index = 0;index < incoming.rawHeaders.length; index += 2) {
74014
- const name = incoming.rawHeaders[index];
74015
- const value = incoming.rawHeaders[index + 1];
74016
- if (name && value)
74017
- responseHeaders.append(name, value);
74018
- }
74019
- const status = incoming.statusCode;
74020
- if (!status)
74021
- throw new Error("telemetry response missing status");
74022
- succeed(new Response(chunks.length > 0 ? Buffer.concat(chunks) : null, {
74023
- status,
74024
- statusText: incoming.statusMessage,
74025
- headers: responseHeaders
74026
- }));
74027
- } catch (error61) {
74028
- fail(error61);
74029
- }
74030
- });
74199
+ execSync2(`npx skills remove -g ${targets.join(" ")} -y`, {
74200
+ stdio: "inherit"
74031
74201
  });
74032
- request.once("error", fail);
74033
- signal?.addEventListener("abort", onAbort, { once: true });
74034
- request.end(body);
74035
- } catch (error61) {
74036
- fail(error61);
74202
+ clearInstalledSha();
74203
+ console.log(import_picocolors24.default.green(`
74204
+ ✓ Skills removed.`));
74205
+ } catch {
74206
+ console.error(import_picocolors24.default.red(`
74207
+ Failed to remove skills.`));
74208
+ process.exit(1);
74209
+ }
74210
+ });
74211
+ cmd.command("update").description("Update the Dosu skill to the latest version").action(async () => {
74212
+ console.log(`Updating skills from ${SKILL_REPO}...`);
74213
+ const result = await installSkill();
74214
+ if (!result.success) {
74215
+ console.error(import_picocolors24.default.red(`
74216
+ Failed to update skill.`));
74217
+ process.exit(1);
74037
74218
  }
74219
+ console.log(import_picocolors24.default.green(`
74220
+ ✓ Skills updated.`));
74038
74221
  });
74222
+ return cmd;
74039
74223
  }
74040
- async function sendHttpsRequest(url2, init, fetcher = fetchWithoutRedirect) {
74041
- try {
74042
- if (new URL(url2).protocol !== "https:")
74043
- return false;
74044
- } catch {
74224
+ var import_picocolors24, SKILL_REPO = "dosu-ai/dosu-skill", SKILL_NAME = "dosu", SAFE_SKILL_NAME, SUPPORTED_SKILL_AGENTS, SKILL_AGENT_BY_PROVIDER;
74225
+ var init_skill = __esm(() => {
74226
+ init_commander();
74227
+ init_logger();
74228
+ init_skill_update_check();
74229
+ import_picocolors24 = __toESM(require_picocolors(), 1);
74230
+ SAFE_SKILL_NAME = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
74231
+ SUPPORTED_SKILL_AGENTS = [
74232
+ "claude-code",
74233
+ "cursor",
74234
+ "gemini-cli",
74235
+ "codex",
74236
+ "windsurf",
74237
+ "zed",
74238
+ "cline",
74239
+ "github-copilot",
74240
+ "opencode",
74241
+ "antigravity"
74242
+ ];
74243
+ SKILL_AGENT_BY_PROVIDER = {
74244
+ claude: "claude-code",
74245
+ cursor: "cursor",
74246
+ vscode: "github-copilot",
74247
+ gemini: "gemini-cli",
74248
+ codex: "codex",
74249
+ windsurf: "windsurf",
74250
+ zed: "zed",
74251
+ cline: "cline",
74252
+ "cline-cli": "cline",
74253
+ copilot: "github-copilot",
74254
+ opencode: "opencode",
74255
+ antigravity: "antigravity"
74256
+ };
74257
+ });
74258
+
74259
+ // src/telemetry/settings.ts
74260
+ import { randomUUID as randomUUID4 } from "node:crypto";
74261
+ import {
74262
+ existsSync as existsSync19,
74263
+ lstatSync as lstatSync2,
74264
+ mkdirSync as mkdirSync11,
74265
+ readFileSync as readFileSync19,
74266
+ renameSync as renameSync5,
74267
+ unlinkSync as unlinkSync3,
74268
+ writeFileSync as writeFileSync12
74269
+ } from "node:fs";
74270
+ import { join as join22 } from "node:path";
74271
+ function getTelemetrySettingsPath() {
74272
+ return join22(getConfigDir(), TELEMETRY_SETTINGS_FILENAME);
74273
+ }
74274
+ function loadTelemetrySettings() {
74275
+ return readTelemetrySettings().settings;
74276
+ }
74277
+ function setTelemetryEnabled(enabled) {
74278
+ if (typeof enabled !== "boolean")
74045
74279
  return false;
74046
- }
74047
- const controller = new AbortController;
74048
- let timer;
74049
- const timeout = new Promise((_resolve, reject) => {
74050
- timer = setTimeout(() => {
74051
- controller.abort();
74052
- reject(new Error("telemetry request timed out"));
74053
- }, REQUEST_TIMEOUT_MS);
74054
- });
74055
- try {
74056
- const request = fetcher(url2, { ...init, redirect: "error", signal: controller.signal });
74057
- const result = await Promise.race([request, timeout]);
74058
- return result.ok;
74059
- } catch {
74280
+ const current = readTelemetrySettings();
74281
+ if (current.status === "unsupported" || current.status === "error")
74060
74282
  return false;
74061
- } finally {
74062
- controller.abort();
74063
- if (timer !== undefined)
74064
- clearTimeout(timer);
74283
+ const settings2 = current.status === "current" ? current.settings : emptyTelemetrySettings();
74284
+ if (enabled)
74285
+ delete settings2.disabled;
74286
+ else
74287
+ settings2.disabled = true;
74288
+ return writeTelemetrySettings(settings2);
74289
+ }
74290
+ function telemetryDisabledByEnvironment() {
74291
+ if (isEnabledEnvironmentFlag(process.env.DO_NOT_TRACK))
74292
+ return "DO_NOT_TRACK";
74293
+ if (isEnabledEnvironmentFlag(process.env.DOSU_TELEMETRY_DISABLED)) {
74294
+ return "DOSU_TELEMETRY_DISABLED";
74065
74295
  }
74296
+ return;
74066
74297
  }
74067
- function environmentFlag(value) {
74068
- if (value === undefined)
74298
+ function isTelemetryEnabled(settings2 = loadTelemetrySettings()) {
74299
+ if (telemetryDisabledByEnvironment() !== undefined)
74069
74300
  return false;
74070
- const normalized = value.trim().toLowerCase();
74071
- return normalized !== "" && !["0", "false", "no", "off"].includes(normalized);
74072
- }
74073
- function runtimeNameAndMajor() {
74074
- const versions2 = process.versions;
74075
- const runtime = versions2.bun ? "bun" : "node";
74076
- const version2 = versions2.bun ?? versions2.node;
74077
- return { runtime, runtimeMajor: Number.parseInt(version2.split(".")[0] ?? "0", 10) || 0 };
74301
+ return settings2.disabled !== true;
74078
74302
  }
74079
- function resolveRuntime(dependencies, env) {
74080
- const detected = runtimeNameAndMajor();
74081
- return normalizeRuntime({
74082
- version: dependencies.version ?? VERSION,
74083
- installChannel: dependencies.installChannel ?? INSTALL_CHANNEL,
74084
- platform: dependencies.platform ?? process.platform,
74085
- arch: dependencies.arch ?? process.arch,
74086
- runtime: dependencies.runtime ?? detected.runtime,
74087
- runtimeMajor: dependencies.runtimeMajor ?? detected.runtimeMajor,
74088
- isCi: dependencies.isCi ?? environmentFlag(env.CI),
74089
- isTty: dependencies.isTty ?? process.stdout.isTTY === true
74090
- });
74303
+ function getOrCreateInstallID() {
74304
+ const current = readTelemetrySettings();
74305
+ if (current.status === "current" && current.settings.install_id) {
74306
+ return current.settings.install_id;
74307
+ }
74308
+ if (current.status !== "current" && current.status !== "missing")
74309
+ return;
74310
+ const installID = randomUUID4();
74311
+ const settings2 = current.status === "current" ? current.settings : emptyTelemetrySettings();
74312
+ settings2.install_id = installID;
74313
+ return writeTelemetrySettings(settings2) ? installID : undefined;
74091
74314
  }
74092
- function validationFailure(exitCode, error61) {
74093
- if (exitCode === 2)
74094
- return true;
74095
- if (error61?.type === "CliUsageError")
74096
- return true;
74097
- if (!error61?.code)
74098
- return false;
74099
- return error61.code.startsWith("commander.") || ["BAD_REQUEST", "INVALID_ARGUMENT", "UNPROCESSABLE_CONTENT", "VALIDATION_ERROR"].includes(error61.code);
74315
+ function resetInstallID() {
74316
+ const current = readTelemetrySettings();
74317
+ if (current.status !== "current" && current.status !== "missing")
74318
+ return;
74319
+ const settings2 = current.status === "current" ? current.settings : emptyTelemetrySettings();
74320
+ const installID = randomUUID4();
74321
+ settings2.install_id = installID;
74322
+ return writeTelemetrySettings(settings2) ? installID : undefined;
74100
74323
  }
74101
- function safeNow(now) {
74324
+ function readTelemetrySettings() {
74325
+ const path3 = getTelemetrySettingsPath();
74326
+ let file2;
74102
74327
  try {
74103
- const value = now();
74104
- return Number.isFinite(value) ? Math.max(0, value) : 0;
74328
+ file2 = lstatSync2(path3);
74329
+ } catch (error61) {
74330
+ if (isErrorCode(error61, "ENOENT")) {
74331
+ return { status: "missing", settings: emptyTelemetrySettings() };
74332
+ }
74333
+ return { status: "error", settings: disabledTelemetrySettings() };
74334
+ }
74335
+ if (!file2.isFile() || file2.size > MAX_TELEMETRY_SETTINGS_BYTES) {
74336
+ return { status: "invalid", settings: disabledTelemetrySettings() };
74337
+ }
74338
+ let content;
74339
+ try {
74340
+ content = readFileSync19(path3, "utf-8");
74105
74341
  } catch {
74106
- return 0;
74342
+ return { status: "error", settings: disabledTelemetrySettings() };
74107
74343
  }
74108
- }
74109
- function safeUuid(generate) {
74344
+ let raw;
74110
74345
  try {
74111
- const value = generate();
74112
- return isUUID2(value) ? value : undefined;
74346
+ raw = JSON.parse(content);
74113
74347
  } catch {
74114
- return;
74348
+ return { status: "invalid", settings: disabledTelemetrySettings() };
74115
74349
  }
74116
- }
74117
- function eventId(generate) {
74118
- const uuid5 = safeUuid(generate);
74119
- return uuid5?.replaceAll("-", "");
74120
- }
74121
- function createCommandTelemetry(settings2, dependencies = {}) {
74122
- const env = dependencies.env ?? process.env;
74123
- const fetcher = dependencies.fetch;
74124
- const now = dependencies.now ?? Date.now;
74125
- const generateUuid = dependencies.randomUUID ?? randomUUID4;
74126
- const writeStderr = dependencies.stderr ?? ((payload) => process.stderr.write(`${payload}
74127
- `));
74128
- const webAppURL = dependencies.webAppURL ?? getWebAppURL;
74129
- const runtime = resolveRuntime(dependencies, env);
74130
- const debug = env.DOSU_TELEMETRY_DEBUG === "1";
74131
- const disabled = settings2.disabled === true || environmentFlag(env.DO_NOT_TRACK) || environmentFlag(env.DOSU_TELEMETRY_DISABLED);
74132
- const rawAnalyticsToken = dependencies.env ? env.DOSU_POSTHOG_PROJECT_TOKEN_OVERRIDE ?? env.DOSU_POSTHOG_PROJECT_TOKEN : process.env.DOSU_POSTHOG_PROJECT_TOKEN_OVERRIDE ?? "phc_Uo9RxhN0HRk8gZkPlT18BCr7eqsM0MPWVW88HZB95V5";
74133
- 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";
74134
- const analyticsToken = parsePostHogProjectToken(rawAnalyticsToken) ?? undefined;
74135
- const sentryDsn = parseSentryDsn(rawSentryDsn)?.dsn;
74136
- let started = false;
74137
- let terminal = false;
74138
- let startedAt = 0;
74139
- let command = UNKNOWN_COMMAND;
74140
- let context = normalizeContext(undefined);
74141
- let installIdResolved = false;
74142
- let resolvedInstallId = settings2.install_id;
74143
- function installId() {
74144
- if (isUUID2(resolvedInstallId))
74145
- return resolvedInstallId;
74146
- if (installIdResolved)
74147
- return;
74148
- installIdResolved = true;
74149
- try {
74150
- resolvedInstallId = dependencies.resolveInstallId?.();
74151
- } catch {
74152
- resolvedInstallId = undefined;
74153
- }
74154
- return isUUID2(resolvedInstallId) ? resolvedInstallId : undefined;
74350
+ if (!isRecord5(raw))
74351
+ return { status: "invalid", settings: disabledTelemetrySettings() };
74352
+ if (raw.schema_version !== TELEMETRY_SCHEMA_VERSION) {
74353
+ const status = "schema_version" in raw ? "unsupported" : "invalid";
74354
+ return { status, settings: disabledTelemetrySettings() };
74155
74355
  }
74156
- function debugPayload(payload) {
74157
- try {
74158
- writeStderr(payload);
74159
- } catch {}
74356
+ if ("disabled" in raw && typeof raw.disabled !== "boolean") {
74357
+ return { status: "invalid", settings: disabledTelemetrySettings() };
74358
+ }
74359
+ const settings2 = emptyTelemetrySettings();
74360
+ if (raw.disabled === true)
74361
+ settings2.disabled = true;
74362
+ if (typeof raw.install_id === "string" && isUUID2(raw.install_id)) {
74363
+ settings2.install_id = raw.install_id;
74160
74364
  }
74161
- async function dispatch(result, exitCode, error61) {
74365
+ return { status: "current", settings: settings2 };
74366
+ }
74367
+ function writeTelemetrySettings(settings2) {
74368
+ const path3 = getTelemetrySettingsPath();
74369
+ const dir = getConfigDir();
74370
+ const temporaryPath = `${path3}.${process.pid}.${randomUUID4()}.tmp`;
74371
+ try {
74372
+ if (!existsSync19(dir))
74373
+ mkdirSync11(dir, { recursive: true, mode: 448 });
74374
+ writeFileSync12(temporaryPath, `${JSON.stringify(settings2, null, 2)}
74375
+ `, {
74376
+ flag: "wx",
74377
+ mode: 384
74378
+ });
74379
+ renameSync5(temporaryPath, path3);
74380
+ return true;
74381
+ } catch {
74162
74382
  try {
74163
- const tasks = [];
74164
- if (!disabled && analyticsToken) {
74165
- const id2 = context.user?.id ?? installId();
74166
- if (id2) {
74167
- const payload = buildPostHogPayload({
74168
- apiKey: analyticsToken,
74169
- ...context.user ? {} : { installId: id2 },
74170
- command,
74171
- result,
74172
- durationMs: safeNow(now) - startedAt,
74173
- exitCode,
74174
- errorCode: error61?.code,
74175
- context,
74176
- runtime
74177
- });
74178
- const body = JSON.stringify(payload);
74179
- if (debug) {
74180
- debugPayload(body);
74181
- } else {
74182
- const base = parseTelemetryWebAppURL(webAppURL());
74183
- if (base) {
74184
- tasks.push(sendHttpsRequest(`${base}/ph-api/i/v0/e/`, {
74185
- method: "POST",
74186
- headers: { "content-type": "application/json" },
74187
- body
74188
- }, fetcher));
74189
- }
74190
- }
74191
- }
74192
- }
74193
- if (!disabled && result === "failure" && error61 && shouldSendToSentry(error61) && sentryDsn) {
74194
- const id2 = eventId(generateUuid);
74195
- const envelope = id2 ? buildSentryEnvelope({
74196
- dsn: sentryDsn,
74197
- command,
74198
- context,
74199
- runtime,
74200
- error: error61,
74201
- eventId: id2,
74202
- timestampMs: safeNow(now),
74203
- debugId: BUNDLE_DEBUG_ID_PLACEHOLDER
74204
- }) : null;
74205
- if (envelope) {
74206
- if (debug) {
74207
- debugPayload(envelope.body);
74208
- } else {
74209
- tasks.push(sendHttpsRequest(envelope.endpoint, {
74210
- method: "POST",
74211
- headers: { "content-type": "application/x-sentry-envelope" },
74212
- body: envelope.body
74213
- }, fetcher));
74214
- }
74215
- }
74216
- }
74217
- await Promise.all(tasks);
74383
+ unlinkSync3(temporaryPath);
74218
74384
  } catch {}
74385
+ return false;
74219
74386
  }
74220
- return {
74221
- start(commandName, commandContext) {
74222
- if (started || terminal)
74223
- return;
74224
- started = true;
74225
- command = canonicalCommand(commandName);
74226
- context = normalizeContext(commandContext);
74227
- startedAt = safeNow(now);
74228
- },
74229
- async complete(exitCode = 0) {
74230
- if (!started || terminal)
74231
- return;
74232
- terminal = true;
74233
- const normalizedExitCode = normalizeExitCode(exitCode, 0);
74234
- const result = normalizedExitCode === 0 ? "success" : validationFailure(normalizedExitCode, undefined) ? "validation_error" : "failure";
74235
- const error61 = result === "failure" ? { type: "CommandExitError", frames: [], exitCode: normalizedExitCode } : undefined;
74236
- await dispatch(result, normalizedExitCode, error61);
74237
- },
74238
- async fail(rawError) {
74239
- if (!started || terminal)
74240
- return;
74241
- terminal = true;
74242
- const error61 = sanitizeError(rawError);
74243
- const exitCode = error61.exitCode ?? 1;
74244
- const result = validationFailure(exitCode, error61) ? "validation_error" : "failure";
74245
- await dispatch(result, exitCode, error61);
74246
- }
74247
- };
74248
74387
  }
74249
- function shouldSendToSentry(error61) {
74250
- return !["SESSION_EXPIRED", "SESSION_PERSISTENCE_ERROR"].includes(error61.code ?? "");
74388
+ function emptyTelemetrySettings() {
74389
+ return { schema_version: TELEMETRY_SCHEMA_VERSION };
74251
74390
  }
74252
- 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;
74253
- var init_telemetry = __esm(() => {
74254
- MAX_RESPONSE_BYTES = 1024 * 1024;
74255
- CLI_PACKAGE_ROOT = detectCliPackageRoot();
74256
- SAFE_ERROR_TYPES = new Set([
74257
- "AbortError",
74258
- "AggregateError",
74259
- "CommanderError",
74260
- "CliUsageError",
74261
- "CommandExitError",
74262
- "Error",
74263
- "OAuthCallbackError",
74264
- "RangeError",
74265
- "ReferenceError",
74266
- "SessionExpiredError",
74267
- "SessionPersistenceError",
74268
- "SessionRefreshError",
74269
- "SyntaxError",
74270
- "TRPCClientError",
74271
- "TypeError"
74272
- ]);
74273
- SAFE_ERROR_CODES = new Set([
74274
- "ABORT_ERR",
74275
- "BAD_GATEWAY",
74276
- "BAD_REQUEST",
74277
- "CLIENT_CLOSED_REQUEST",
74278
- "CONFLICT",
74279
- "EACCES",
74280
- "EAI_AGAIN",
74281
- "EBUSY",
74282
- "ECONNREFUSED",
74283
- "ECONNRESET",
74284
- "EEXIST",
74285
- "EHOSTUNREACH",
74286
- "EINVAL",
74287
- "EISDIR",
74288
- "ELOOP",
74289
- "EMFILE",
74290
- "ENAMETOOLONG",
74291
- "ENETUNREACH",
74292
- "ENFILE",
74293
- "ENOENT",
74294
- "ENOMEM",
74295
- "ENOSPC",
74296
- "ENOTFOUND",
74297
- "ENOTDIR",
74298
- "EPIPE",
74299
- "EPERM",
74300
- "EROFS",
74301
- "ETIMEDOUT",
74302
- "FORBIDDEN",
74303
- "GATEWAY_TIMEOUT",
74304
- "IMPORT_ALREADY_IN_PROGRESS",
74305
- "INTERNAL_SERVER_ERROR",
74306
- "INVALID_ARGUMENT",
74307
- "METHOD_NOT_SUPPORTED",
74308
- "NOT_FOUND",
74309
- "NOT_IMPLEMENTED",
74310
- "PARSE_ERROR",
74311
- "PAYLOAD_TOO_LARGE",
74312
- "PAYMENT_REQUIRED",
74313
- "PRECONDITION_FAILED",
74314
- "SERVICE_UNAVAILABLE",
74315
- "SESSION_EXPIRED",
74316
- "SESSION_PERSISTENCE_ERROR",
74317
- "SESSION_REFRESH_ERROR",
74318
- "TIMEOUT",
74319
- "TOO_MANY_REQUESTS",
74320
- "UNAUTHORIZED",
74321
- "UNPROCESSABLE_CONTENT",
74322
- "UNSUPPORTED_MEDIA_TYPE",
74323
- "VALIDATION_ERROR",
74324
- "commander.conflictingOption",
74325
- "commander.error",
74326
- "commander.excessArguments",
74327
- "commander.invalidArgument",
74328
- "commander.missingArgument",
74329
- "commander.missingMandatoryOptionValue",
74330
- "commander.optionMissingArgument",
74331
- "commander.unknownCommand",
74332
- "commander.unknownOption"
74333
- ]);
74391
+ function disabledTelemetrySettings() {
74392
+ return { schema_version: TELEMETRY_SCHEMA_VERSION, disabled: true };
74393
+ }
74394
+ function isEnabledEnvironmentFlag(value) {
74395
+ if (value === undefined)
74396
+ return false;
74397
+ const normalized = value.trim().toLowerCase();
74398
+ if (normalized === "")
74399
+ return false;
74400
+ return !["0", "false", "no", "off"].includes(normalized);
74401
+ }
74402
+ function isUUID2(value) {
74403
+ 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);
74404
+ }
74405
+ function isRecord5(value) {
74406
+ return typeof value === "object" && value !== null && !Array.isArray(value);
74407
+ }
74408
+ function isErrorCode(error61, code) {
74409
+ return typeof error61 === "object" && error61 !== null && "code" in error61 && error61.code === code;
74410
+ }
74411
+ var TELEMETRY_SCHEMA_VERSION = 1, TELEMETRY_SETTINGS_FILENAME = "telemetry.json", MAX_TELEMETRY_SETTINGS_BYTES;
74412
+ var init_settings = __esm(() => {
74413
+ init_config();
74414
+ MAX_TELEMETRY_SETTINGS_BYTES = 16 * 1024;
74334
74415
  });
74335
74416
 
74336
74417
  // src/debug/follow.ts
@@ -77048,6 +77129,7 @@ async function stepOfferInitialSync(cfg) {
77048
77129
  const outcome = await runKnowledgeSync({ bootstrap: true });
77049
77130
  if (outcome.status !== "backlog" || outcome.readySessions === 0) {
77050
77131
  s.stop("No past agent sessions to study. Dosu will learn from new ones as you work.");
77132
+ recordCommandFacets({ backfill_offer: "not-offered" });
77051
77133
  return;
77052
77134
  }
77053
77135
  const n5 = outcome.readySessions;
@@ -77067,10 +77149,12 @@ async function stepOfferInitialSync(cfg) {
77067
77149
  initialValue: true
77068
77150
  });
77069
77151
  if (isCancel2(mineNow) || !mineNow) {
77152
+ recordCommandFacets({ backfill_offer: isCancel2(mineNow) ? "cancelled" : "declined" });
77070
77153
  log2.info(`Skipped. Dosu studies new sessions in the background as you work; run ${info("dosu knowledge sync")} anytime to study these too.`);
77071
77154
  return;
77072
77155
  }
77073
77156
  if (spawnDetachedSelf(["knowledge", "sync", "--quiet", "--bootstrap"])) {
77157
+ recordCommandFacets({ backfill_offer: "accepted" });
77074
77158
  log2.success(`\uD83D\uDCDA Studying ${n5} session${n5 === 1 ? "" : "s"} in the background. Watch progress on the Activity screen; when it finishes, the new notes are in Dosu and agents connected to this MCP can use them.`);
77075
77159
  const watch = await confirm2({
77076
77160
  message: "What next?",
@@ -77081,6 +77165,7 @@ async function stepOfferInitialSync(cfg) {
77081
77165
  if (!isCancel2(watch) && watch)
77082
77166
  await runActivityView();
77083
77167
  } else {
77168
+ recordCommandFacets({ backfill_offer: "spawn-failed" });
77084
77169
  log2.warn(`Could not start the background sync. Run ${info("dosu knowledge sync")} manually.`);
77085
77170
  }
77086
77171
  }
@@ -77818,6 +77903,7 @@ var init_flow2 = __esm(() => {
77818
77903
  init_providers();
77819
77904
  init_detach();
77820
77905
  init_sync();
77906
+ init_telemetry();
77821
77907
  init_activity_view();
77822
77908
  init_layout();
77823
77909
  init_prompts();
@@ -82665,8 +82751,9 @@ init_detach();
82665
82751
  init_status();
82666
82752
  init_sync();
82667
82753
  init_watermark();
82754
+ init_telemetry();
82668
82755
  var import_picocolors18 = __toESM(require_picocolors(), 1);
82669
- import { existsSync as existsSync16 } from "node:fs";
82756
+ import { existsSync as existsSync17 } from "node:fs";
82670
82757
  import { homedir as homedir5 } from "node:os";
82671
82758
  import { delimiter as delimiter4, join as join19 } from "node:path";
82672
82759
  function requireConfig7() {
@@ -82776,7 +82863,9 @@ ${results.length - opts.limit} more results not shown.`));
82776
82863
  }
82777
82864
  });
82778
82865
  cmd.command("sync").description("Scan local agent session history and report the study backlog").option("--quiet", "Background mode for hooks: honor backoff, exit 0, print nothing").option("--detach", "Re-spawn detached and return immediately (used by agent hooks)").option("--bootstrap", "Backfill mode: study the full local session history regardless of age and drain the backlog (used by setup)").option("--status", "Show whether a sync is running now, plus watermark and recent activity").option("--report", "Write the same HTML harvest report as the log-to-dosu-knowledge skill and open it").option("--out <path>", "HTML report path (default: tmp/dosu-knowledge-report.html)").option("--json", "Output as JSON").action(async (opts) => {
82866
+ const trigger = opts.bootstrap ? "bootstrap" : opts.quiet ? "hook" : "manual";
82779
82867
  if (opts.status) {
82868
+ recordCommandFacets({ sync_trigger: trigger, sync_status: "status-only" });
82780
82869
  const status = getSyncStatus();
82781
82870
  if (opts.json) {
82782
82871
  printResult(status, opts);
@@ -82786,7 +82875,7 @@ ${results.length - opts.limit} more results not shown.`));
82786
82875
  return;
82787
82876
  }
82788
82877
  if (opts.detach) {
82789
- spawnDetachedSelf([
82878
+ const spawned = spawnDetachedSelf([
82790
82879
  "knowledge",
82791
82880
  "sync",
82792
82881
  ...opts.quiet ? ["--quiet"] : [],
@@ -82794,6 +82883,10 @@ ${results.length - opts.limit} more results not shown.`));
82794
82883
  ...opts.report ? ["--report"] : [],
82795
82884
  ...opts.out ? ["--out", opts.out] : []
82796
82885
  ]);
82886
+ recordCommandFacets({
82887
+ sync_trigger: trigger,
82888
+ sync_status: spawned ? "detached" : "detach-failed"
82889
+ });
82797
82890
  return;
82798
82891
  }
82799
82892
  const deps = { mine: buildLearner(opts.quiet ? "hook" : "manual") };
@@ -82802,14 +82895,25 @@ ${results.length - opts.limit} more results not shown.`));
82802
82895
  bootstrap: opts.bootstrap,
82803
82896
  deps
82804
82897
  });
82898
+ let sessionsStudied = outcome.studiedSessions ?? 0;
82899
+ let notesWritten = outcome.learner?.notesWritten ?? 0;
82805
82900
  if (opts.bootstrap && deps.mine) {
82806
82901
  const maxRounds = Math.ceil(outcome.readySessions / MINE_BATCH_LIMIT) + 2;
82807
82902
  for (let round = 1;outcome.status === "studied" && round < maxRounds; round++) {
82808
82903
  if (!opts.quiet && !opts.json)
82809
82904
  printSyncOutcome(outcome);
82810
82905
  outcome = await runKnowledgeSync({ quiet: opts.quiet, bootstrap: true, deps });
82906
+ sessionsStudied += outcome.studiedSessions ?? 0;
82907
+ notesWritten += outcome.learner?.notesWritten ?? 0;
82811
82908
  }
82812
82909
  }
82910
+ recordCommandFacets({
82911
+ sync_trigger: trigger,
82912
+ sync_status: outcome.status,
82913
+ sessions_studied: sessionsStudied,
82914
+ notes_written: notesWritten,
82915
+ ...outcome.learner ? { learner_outcome: outcome.learner.outcome } : {}
82916
+ });
82813
82917
  if (opts.quiet)
82814
82918
  return;
82815
82919
  if (opts.json) {
@@ -83079,7 +83183,7 @@ function reportHookFailure(agent, err) {
83079
83183
  }
83080
83184
  function dosuOnPath() {
83081
83185
  const bin = process.platform === "win32" ? "dosu.cmd" : "dosu";
83082
- return (process.env.PATH ?? "").split(delimiter4).some((dir) => dir !== "" && existsSync16(join19(dir, bin)));
83186
+ return (process.env.PATH ?? "").split(delimiter4).some((dir) => dir !== "" && existsSync17(join19(dir, bin)));
83083
83187
  }
83084
83188
 
83085
83189
  // src/commands/libraries.ts
@@ -83871,8 +83975,8 @@ function sourcesCommand() {
83871
83975
  }
83872
83976
  console.log(import_picocolors25.default.dim("Waiting for the install to complete..."));
83873
83977
  }
83874
- const timedOut = new Promise((resolve6) => {
83875
- timeoutId = setTimeout(() => resolve6(null), timeoutSeconds * 1000);
83978
+ const timedOut = new Promise((resolve7) => {
83979
+ timeoutId = setTimeout(() => resolve7(null), timeoutSeconds * 1000);
83876
83980
  });
83877
83981
  const result = await Promise.race([installationPromise, timedOut]);
83878
83982
  if (result === null) {
@@ -84988,4 +85092,4 @@ execute().catch((err) => {
84988
85092
  process.exit(1);
84989
85093
  });
84990
85094
 
84991
- //# debugId=5776df20-501e-ff5f-6475-6e2164756e21
85095
+ //# debugId=d4304c43-c40e-f448-6475-6e2164756e21