@dosu/cli 0.53.0 → 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 +1386 -1230
  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.0", 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
  }
@@ -73068,1269 +73916,502 @@ async function runLearner(options) {
73068
73916
  };
73069
73917
  }
73070
73918
  if (message.subtype !== "success" || message.is_error) {
73071
- logger.debug("learner", `run ${runID} failed: ${text}`);
73072
- return {
73073
- outcome: "error",
73074
- notesWritten,
73075
- turns,
73076
- message: "Study run failed; see debug log for details."
73077
- };
73078
- }
73079
- logger.debug("learner", `run ${runID} completed: ${turns} turns, ${notesWritten} suggested pages`);
73080
- return { outcome: "completed", notesWritten, turns, message: text };
73081
- }
73082
- }
73083
- return {
73084
- outcome: "error",
73085
- notesWritten,
73086
- turns,
73087
- message: "Study run ended without a result."
73088
- };
73089
- } catch (error61) {
73090
- const text = error61 instanceof Error ? error61.message : String(error61);
73091
- const gatewayError = classifyGatewayError(text);
73092
- if (gatewayError) {
73093
- 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;
73538
- }
73539
- }
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;
73567
- });
73568
-
73569
- // src/telemetry/debug-id.ts
73570
- var BUNDLE_DEBUG_ID_PLACEHOLDER = "e98d2bf5-99f8-402d-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;
73919
+ logger.debug("learner", `run ${runID} failed: ${text}`);
73920
+ return {
73921
+ outcome: "error",
73922
+ notesWritten,
73923
+ turns,
73924
+ message: "Study run failed; see debug log for details."
73925
+ };
73926
+ }
73927
+ logger.debug("learner", `run ${runID} completed: ${turns} turns, ${notesWritten} suggested pages`);
73928
+ return { outcome: "completed", notesWritten, turns, message: text };
73929
+ }
73930
+ }
73931
+ return {
73932
+ outcome: "error",
73933
+ notesWritten,
73934
+ turns,
73935
+ message: "Study run ended without a result."
73936
+ };
73937
+ } catch (error61) {
73938
+ const text = error61 instanceof Error ? error61.message : String(error61);
73939
+ const gatewayError = classifyGatewayError(text);
73940
+ if (gatewayError) {
73941
+ return { outcome: gatewayError.outcome, notesWritten, turns, message: gatewayError.message };
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();
73592
73953
  }
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
73954
  }
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
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
+ }
73623
73979
  };
73980
+ });
73981
+
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);
73624
73987
  }
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";
73648
- }
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");
73753
- }
73754
- return;
73755
- }
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+";
73769
- }
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";
73775
- 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 } : {}
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
+ });
73802
74066
  }
73803
- };
74067
+ } catch (err) {
74068
+ logger.error("skill-update-check", `Skill update check failed: ${err}`);
74069
+ }
73804
74070
  }
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;
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
+ ];
73810
74087
  }
73811
- function parseTelemetryWebAppURL(value, allowInsecureLoopback = false) {
73812
- if (typeof value !== "string" || value.length === 0 || value.length > 2048)
73813
- return null;
73814
- 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 {
74088
+ function skillInstallTargetForProvider(providerID) {
74089
+ const agentID = SKILL_AGENT_BY_PROVIDER[providerID];
74090
+ if (!agentID)
73825
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 };
73826
74095
  }
73827
- }
73828
- function parseSentryDsn(value) {
73829
- if (typeof value !== "string" || value.length === 0 || value.length > 2048)
73830
- return null;
73831
- const dsn = value.trim();
73832
- 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("/")}` : "";
74096
+ if (agentID === "windsurf") {
73844
74097
  return {
73845
- dsn,
73846
- endpoint: `${url2.protocol}//${url2.host}${prefix}/api/${projectId}/envelope/`,
73847
- projectId,
73848
- publicKey: url2.username
74098
+ path: join21(homedir6(), ".codeium", "windsurf", "skills", SKILL_NAME),
74099
+ symlink: true
73849
74100
  };
73850
- } catch {
73851
- return null;
73852
74101
  }
73853
- }
73854
- function sentryTags(command, context, runtime, error61) {
73855
74102
  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) }
74103
+ path: join21(homedir6(), ".agents", "skills", SKILL_NAME),
74104
+ symlink: false
73871
74105
  };
73872
74106
  }
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
- };
74107
+ function skillAgentArgs(providerIDs) {
74108
+ const agents = providerIDs === undefined ? SUPPORTED_SKILL_AGENTS : skillAgentIDsForProviders(providerIDs);
74109
+ return agents.map((agent) => `-a ${agent}`).join(" ");
73938
74110
  }
73939
- function fetchWithoutRedirect(url2, init = {}) {
73940
- let parsedURL;
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
+ }
73941
74127
  try {
73942
- parsedURL = new URL(url2);
73943
- if (parsedURL.protocol !== "http:" && parsedURL.protocol !== "https:") {
73944
- return Promise.reject(new Error("unsupported telemetry protocol"));
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 };
74145
+ }
74146
+ logger.debug("skill", "Skill installed but could not fetch latest SHA");
74147
+ return { success: true };
74148
+ }
74149
+ function installedSkillNames() {
74150
+ let entries;
74151
+ try {
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}`);
74162
+ return null;
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;
73945
74171
  }
73946
- } catch (error61) {
73947
- return Promise.reject(error61);
74172
+ names.push(entry.name);
73948
74173
  }
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
- };
74174
+ return names;
74175
+ }
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;
74195
+ }
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
@@ -74538,7 +74619,7 @@ var init_cline_cli = __esm(() => {
74538
74619
 
74539
74620
  // src/mcp/providers/codex.ts
74540
74621
  import { existsSync as existsSync20, readFileSync as readFileSync20 } from "node:fs";
74541
- import { join as join27 } from "node:path";
74622
+ import { dirname as dirname9, join as join27 } from "node:path";
74542
74623
  function codexHome2() {
74543
74624
  return process.env.CODEX_HOME ?? expandHome("~/.codex");
74544
74625
  }
@@ -74578,19 +74659,24 @@ ${envEntries}
74578
74659
  content += section;
74579
74660
  writeTOML(path3, content);
74580
74661
  }
74662
+ function sectionName(line) {
74663
+ const header = line.trim();
74664
+ const match = header.match(/^\[\[([^\]]*)]]\s*(?:#.*)?$/) ?? header.match(/^\[([^\]]*)]\s*(?:#.*)?$/);
74665
+ return match ? match[1].trim() : null;
74666
+ }
74667
+ function isDosuSection(name) {
74668
+ return name === "mcp_servers.dosu" || name.startsWith("mcp_servers.dosu.");
74669
+ }
74581
74670
  function removeDosuFromTOML(content) {
74582
- const lines = content.split(`
74583
- `);
74584
74671
  const result = [];
74585
74672
  let inDosuSection = false;
74586
- for (const line of lines) {
74587
- const trimmed = line.trim();
74588
- if (trimmed.match(/^\[mcp_servers\.dosu(\..*)?]$/)) {
74589
- inDosuSection = true;
74590
- continue;
74591
- }
74592
- if (inDosuSection && trimmed.startsWith("[")) {
74593
- inDosuSection = false;
74673
+ for (const line of content.split(`
74674
+ `)) {
74675
+ const name = sectionName(line);
74676
+ if (name !== null) {
74677
+ inDosuSection = isDosuSection(name);
74678
+ if (inDosuSection)
74679
+ continue;
74594
74680
  }
74595
74681
  if (!inDosuSection) {
74596
74682
  result.push(line);
@@ -74599,7 +74685,45 @@ function removeDosuFromTOML(content) {
74599
74685
  return result.join(`
74600
74686
  `);
74601
74687
  }
74602
- var CodexProvider = () => ({
74688
+ function hasRemoteHTTPForm(content) {
74689
+ let inDosuRoot = false;
74690
+ for (const line of content.split(`
74691
+ `)) {
74692
+ const name = sectionName(line);
74693
+ if (name !== null) {
74694
+ if (name === "mcp_servers.dosu.http_headers")
74695
+ return true;
74696
+ inDosuRoot = name === "mcp_servers.dosu";
74697
+ continue;
74698
+ }
74699
+ if (inDosuRoot && REMOTE_HTTP_KEY.test(line.trim()))
74700
+ return true;
74701
+ }
74702
+ return false;
74703
+ }
74704
+ function projectConfigPaths() {
74705
+ const cwd = process.cwd();
74706
+ const chain = [];
74707
+ for (let dir = cwd;; ) {
74708
+ chain.push(dir);
74709
+ if (existsSync20(join27(dir, ".git")))
74710
+ break;
74711
+ const parent = dirname9(dir);
74712
+ if (parent === dir)
74713
+ return [join27(cwd, ".codex", "config.toml")];
74714
+ dir = parent;
74715
+ }
74716
+ return chain.reverse().map((dir) => join27(dir, ".codex", "config.toml"));
74717
+ }
74718
+ function pruneLegacyRemoteEntry(path3) {
74719
+ if (!existsSync20(path3))
74720
+ return;
74721
+ const content = readTOML(path3);
74722
+ if (!hasRemoteHTTPForm(content))
74723
+ return;
74724
+ writeTOML(path3, removeDosuFromTOML(content));
74725
+ }
74726
+ var REMOTE_HTTP_KEY, CodexProvider = () => ({
74603
74727
  name: () => "Codex (CLI + Desktop)",
74604
74728
  id: () => "codex",
74605
74729
  supportsLocal: () => true,
@@ -74614,7 +74738,15 @@ var CodexProvider = () => ({
74614
74738
  install(cfg, global2) {
74615
74739
  if (cfg.mode !== MODE_OSS && !cfg.active_account?.target?.deployment_id)
74616
74740
  throw new Error("deployment ID is required");
74617
- installDosuToTOML(getConfigPath2(global2), cfg);
74741
+ if (!global2 && hasRemoteHTTPForm(readTOML(getConfigPath2(true)))) {
74742
+ throw new Error("the global Codex config still holds the legacy remote-HTTP Dosu entry, which Codex would " + "merge with a project-local entry and reject. Migrate it first with " + "`dosu mcp add codex --global`, then re-run this.");
74743
+ }
74744
+ const written = getConfigPath2(global2);
74745
+ installDosuToTOML(written, cfg);
74746
+ for (const path3 of projectConfigPaths()) {
74747
+ if (path3 !== written)
74748
+ pruneLegacyRemoteEntry(path3);
74749
+ }
74618
74750
  },
74619
74751
  remove(global2) {
74620
74752
  const path3 = getConfigPath2(global2);
@@ -74627,6 +74759,7 @@ var init_codex = __esm(() => {
74627
74759
  init_config();
74628
74760
  init_config_helpers();
74629
74761
  init_detect();
74762
+ REMOTE_HTTP_KEY = /^(url|type|bearer_token_env_var)\s*=/;
74630
74763
  });
74631
74764
 
74632
74765
  // src/mcp/providers/copilot.ts
@@ -75144,7 +75277,7 @@ var init_update_check = __esm(() => {
75144
75277
  // src/rules/installer.ts
75145
75278
  import { existsSync as existsSync23, mkdirSync as mkdirSync13, readFileSync as readFileSync22, unlinkSync as unlinkSync4, writeFileSync as writeFileSync14 } from "node:fs";
75146
75279
  import { homedir as homedir10 } from "node:os";
75147
- import { dirname as dirname9, join as join38 } from "node:path";
75280
+ import { dirname as dirname10, join as join38 } from "node:path";
75148
75281
  function claudeConfigDir() {
75149
75282
  return process.env.CLAUDE_CONFIG_DIR || join38(homedir10(), ".claude");
75150
75283
  }
@@ -75175,7 +75308,7 @@ async function fetchDosuRule(fetchImpl = globalThis.fetch) {
75175
75308
  return normalizeRule(FALLBACK_DOSU_RULE);
75176
75309
  }
75177
75310
  function ensureParent(path3) {
75178
- mkdirSync13(dirname9(path3), { recursive: true });
75311
+ mkdirSync13(dirname10(path3), { recursive: true });
75179
75312
  }
75180
75313
  function writeIfChanged(path3, content) {
75181
75314
  if (!existsSync23(path3)) {
@@ -76996,6 +77129,7 @@ async function stepOfferInitialSync(cfg) {
76996
77129
  const outcome = await runKnowledgeSync({ bootstrap: true });
76997
77130
  if (outcome.status !== "backlog" || outcome.readySessions === 0) {
76998
77131
  s.stop("No past agent sessions to study. Dosu will learn from new ones as you work.");
77132
+ recordCommandFacets({ backfill_offer: "not-offered" });
76999
77133
  return;
77000
77134
  }
77001
77135
  const n5 = outcome.readySessions;
@@ -77015,10 +77149,12 @@ async function stepOfferInitialSync(cfg) {
77015
77149
  initialValue: true
77016
77150
  });
77017
77151
  if (isCancel2(mineNow) || !mineNow) {
77152
+ recordCommandFacets({ backfill_offer: isCancel2(mineNow) ? "cancelled" : "declined" });
77018
77153
  log2.info(`Skipped. Dosu studies new sessions in the background as you work; run ${info("dosu knowledge sync")} anytime to study these too.`);
77019
77154
  return;
77020
77155
  }
77021
77156
  if (spawnDetachedSelf(["knowledge", "sync", "--quiet", "--bootstrap"])) {
77157
+ recordCommandFacets({ backfill_offer: "accepted" });
77022
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.`);
77023
77159
  const watch = await confirm2({
77024
77160
  message: "What next?",
@@ -77029,6 +77165,7 @@ async function stepOfferInitialSync(cfg) {
77029
77165
  if (!isCancel2(watch) && watch)
77030
77166
  await runActivityView();
77031
77167
  } else {
77168
+ recordCommandFacets({ backfill_offer: "spawn-failed" });
77032
77169
  log2.warn(`Could not start the background sync. Run ${info("dosu knowledge sync")} manually.`);
77033
77170
  }
77034
77171
  }
@@ -77766,6 +77903,7 @@ var init_flow2 = __esm(() => {
77766
77903
  init_providers();
77767
77904
  init_detach();
77768
77905
  init_sync();
77906
+ init_telemetry();
77769
77907
  init_activity_view();
77770
77908
  init_layout();
77771
77909
  init_prompts();
@@ -82613,8 +82751,9 @@ init_detach();
82613
82751
  init_status();
82614
82752
  init_sync();
82615
82753
  init_watermark();
82754
+ init_telemetry();
82616
82755
  var import_picocolors18 = __toESM(require_picocolors(), 1);
82617
- import { existsSync as existsSync16 } from "node:fs";
82756
+ import { existsSync as existsSync17 } from "node:fs";
82618
82757
  import { homedir as homedir5 } from "node:os";
82619
82758
  import { delimiter as delimiter4, join as join19 } from "node:path";
82620
82759
  function requireConfig7() {
@@ -82724,7 +82863,9 @@ ${results.length - opts.limit} more results not shown.`));
82724
82863
  }
82725
82864
  });
82726
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";
82727
82867
  if (opts.status) {
82868
+ recordCommandFacets({ sync_trigger: trigger, sync_status: "status-only" });
82728
82869
  const status = getSyncStatus();
82729
82870
  if (opts.json) {
82730
82871
  printResult(status, opts);
@@ -82734,7 +82875,7 @@ ${results.length - opts.limit} more results not shown.`));
82734
82875
  return;
82735
82876
  }
82736
82877
  if (opts.detach) {
82737
- spawnDetachedSelf([
82878
+ const spawned = spawnDetachedSelf([
82738
82879
  "knowledge",
82739
82880
  "sync",
82740
82881
  ...opts.quiet ? ["--quiet"] : [],
@@ -82742,6 +82883,10 @@ ${results.length - opts.limit} more results not shown.`));
82742
82883
  ...opts.report ? ["--report"] : [],
82743
82884
  ...opts.out ? ["--out", opts.out] : []
82744
82885
  ]);
82886
+ recordCommandFacets({
82887
+ sync_trigger: trigger,
82888
+ sync_status: spawned ? "detached" : "detach-failed"
82889
+ });
82745
82890
  return;
82746
82891
  }
82747
82892
  const deps = { mine: buildLearner(opts.quiet ? "hook" : "manual") };
@@ -82750,14 +82895,25 @@ ${results.length - opts.limit} more results not shown.`));
82750
82895
  bootstrap: opts.bootstrap,
82751
82896
  deps
82752
82897
  });
82898
+ let sessionsStudied = outcome.studiedSessions ?? 0;
82899
+ let notesWritten = outcome.learner?.notesWritten ?? 0;
82753
82900
  if (opts.bootstrap && deps.mine) {
82754
82901
  const maxRounds = Math.ceil(outcome.readySessions / MINE_BATCH_LIMIT) + 2;
82755
82902
  for (let round = 1;outcome.status === "studied" && round < maxRounds; round++) {
82756
82903
  if (!opts.quiet && !opts.json)
82757
82904
  printSyncOutcome(outcome);
82758
82905
  outcome = await runKnowledgeSync({ quiet: opts.quiet, bootstrap: true, deps });
82906
+ sessionsStudied += outcome.studiedSessions ?? 0;
82907
+ notesWritten += outcome.learner?.notesWritten ?? 0;
82759
82908
  }
82760
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
+ });
82761
82917
  if (opts.quiet)
82762
82918
  return;
82763
82919
  if (opts.json) {
@@ -83027,7 +83183,7 @@ function reportHookFailure(agent, err) {
83027
83183
  }
83028
83184
  function dosuOnPath() {
83029
83185
  const bin = process.platform === "win32" ? "dosu.cmd" : "dosu";
83030
- 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)));
83031
83187
  }
83032
83188
 
83033
83189
  // src/commands/libraries.ts
@@ -83819,8 +83975,8 @@ function sourcesCommand() {
83819
83975
  }
83820
83976
  console.log(import_picocolors25.default.dim("Waiting for the install to complete..."));
83821
83977
  }
83822
- const timedOut = new Promise((resolve6) => {
83823
- timeoutId = setTimeout(() => resolve6(null), timeoutSeconds * 1000);
83978
+ const timedOut = new Promise((resolve7) => {
83979
+ timeoutId = setTimeout(() => resolve7(null), timeoutSeconds * 1000);
83824
83980
  });
83825
83981
  const result = await Promise.race([installationPromise, timedOut]);
83826
83982
  if (result === null) {
@@ -84936,4 +85092,4 @@ execute().catch((err) => {
84936
85092
  process.exit(1);
84937
85093
  });
84938
85094
 
84939
- //# debugId=e98d2bf5-99f8-402d-6475-6e2164756e21
85095
+ //# debugId=d4304c43-c40e-f448-6475-6e2164756e21