@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.
- package/bin/dosu.js +1386 -1230
- 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.
|
|
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
|
|
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 (!
|
|
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
|
|
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) =>
|
|
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
|
-
|
|
73614
|
-
|
|
73615
|
-
|
|
73616
|
-
|
|
73617
|
-
|
|
73618
|
-
|
|
73619
|
-
|
|
73620
|
-
|
|
73621
|
-
|
|
73622
|
-
|
|
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
|
|
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
|
-
|
|
73654
|
-
|
|
73655
|
-
|
|
73656
|
-
|
|
73657
|
-
|
|
73658
|
-
|
|
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
|
-
|
|
73997
|
+
return null;
|
|
73998
|
+
} catch {
|
|
73999
|
+
return null;
|
|
73675
74000
|
}
|
|
73676
|
-
return frames;
|
|
73677
74001
|
}
|
|
73678
|
-
function
|
|
74002
|
+
function writeSkillCache(cache) {
|
|
73679
74003
|
try {
|
|
73680
|
-
const
|
|
73681
|
-
|
|
73682
|
-
|
|
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
|
|
73689
|
-
const
|
|
73690
|
-
|
|
73691
|
-
|
|
73692
|
-
|
|
73693
|
-
|
|
73694
|
-
|
|
73695
|
-
|
|
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
|
-
|
|
73713
|
-
|
|
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
|
|
73720
|
-
const
|
|
73721
|
-
|
|
73722
|
-
|
|
73723
|
-
|
|
73724
|
-
|
|
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
|
|
73742
|
-
|
|
73743
|
-
|
|
73744
|
-
|
|
73745
|
-
|
|
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
|
-
|
|
73753
|
-
|
|
73754
|
-
|
|
73755
|
-
|
|
73756
|
-
|
|
73757
|
-
|
|
73758
|
-
|
|
73759
|
-
|
|
73760
|
-
|
|
73761
|
-
|
|
73762
|
-
|
|
73763
|
-
|
|
73764
|
-
|
|
73765
|
-
|
|
73766
|
-
|
|
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
|
-
|
|
73806
|
-
|
|
73807
|
-
|
|
73808
|
-
|
|
73809
|
-
|
|
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
|
|
73812
|
-
|
|
73813
|
-
|
|
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
|
-
|
|
73846
|
-
|
|
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
|
-
|
|
73857
|
-
|
|
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
|
|
73874
|
-
const
|
|
73875
|
-
|
|
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
|
|
73940
|
-
|
|
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
|
-
|
|
73943
|
-
if (
|
|
73944
|
-
|
|
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
|
-
|
|
73947
|
-
return Promise.reject(error61);
|
|
74172
|
+
names.push(entry.name);
|
|
73948
74173
|
}
|
|
73949
|
-
return
|
|
73950
|
-
|
|
73951
|
-
|
|
73952
|
-
|
|
73953
|
-
|
|
73954
|
-
|
|
73955
|
-
const
|
|
73956
|
-
|
|
73957
|
-
|
|
73958
|
-
|
|
73959
|
-
|
|
73960
|
-
|
|
73961
|
-
|
|
73962
|
-
|
|
73963
|
-
|
|
73964
|
-
|
|
73965
|
-
|
|
73966
|
-
|
|
73967
|
-
|
|
73968
|
-
|
|
73969
|
-
|
|
73970
|
-
|
|
73971
|
-
|
|
73972
|
-
|
|
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
|
-
|
|
73982
|
-
|
|
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
|
-
|
|
74033
|
-
|
|
74034
|
-
|
|
74035
|
-
} catch
|
|
74036
|
-
|
|
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
|
-
|
|
74041
|
-
|
|
74042
|
-
|
|
74043
|
-
|
|
74044
|
-
|
|
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
|
-
|
|
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
|
-
|
|
74062
|
-
|
|
74063
|
-
|
|
74064
|
-
|
|
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
|
|
74068
|
-
if (
|
|
74298
|
+
function isTelemetryEnabled(settings2 = loadTelemetrySettings()) {
|
|
74299
|
+
if (telemetryDisabledByEnvironment() !== undefined)
|
|
74069
74300
|
return false;
|
|
74070
|
-
|
|
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
|
|
74080
|
-
const
|
|
74081
|
-
|
|
74082
|
-
|
|
74083
|
-
|
|
74084
|
-
|
|
74085
|
-
|
|
74086
|
-
|
|
74087
|
-
|
|
74088
|
-
|
|
74089
|
-
|
|
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
|
|
74093
|
-
|
|
74094
|
-
|
|
74095
|
-
|
|
74096
|
-
|
|
74097
|
-
|
|
74098
|
-
|
|
74099
|
-
return
|
|
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
|
|
74324
|
+
function readTelemetrySettings() {
|
|
74325
|
+
const path3 = getTelemetrySettingsPath();
|
|
74326
|
+
let file2;
|
|
74102
74327
|
try {
|
|
74103
|
-
|
|
74104
|
-
|
|
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
|
|
74342
|
+
return { status: "error", settings: disabledTelemetrySettings() };
|
|
74107
74343
|
}
|
|
74108
|
-
|
|
74109
|
-
function safeUuid(generate) {
|
|
74344
|
+
let raw;
|
|
74110
74345
|
try {
|
|
74111
|
-
|
|
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
|
-
|
|
74118
|
-
|
|
74119
|
-
|
|
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
|
-
|
|
74157
|
-
|
|
74158
|
-
|
|
74159
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
74250
|
-
return
|
|
74388
|
+
function emptyTelemetrySettings() {
|
|
74389
|
+
return { schema_version: TELEMETRY_SCHEMA_VERSION };
|
|
74251
74390
|
}
|
|
74252
|
-
|
|
74253
|
-
|
|
74254
|
-
|
|
74255
|
-
|
|
74256
|
-
|
|
74257
|
-
|
|
74258
|
-
|
|
74259
|
-
|
|
74260
|
-
|
|
74261
|
-
|
|
74262
|
-
|
|
74263
|
-
|
|
74264
|
-
|
|
74265
|
-
|
|
74266
|
-
|
|
74267
|
-
|
|
74268
|
-
|
|
74269
|
-
|
|
74270
|
-
|
|
74271
|
-
|
|
74272
|
-
|
|
74273
|
-
|
|
74274
|
-
|
|
74275
|
-
|
|
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
|
|
74587
|
-
|
|
74588
|
-
|
|
74589
|
-
|
|
74590
|
-
|
|
74591
|
-
|
|
74592
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
|
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 !== "" &&
|
|
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((
|
|
83823
|
-
timeoutId = setTimeout(() =>
|
|
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=
|
|
85095
|
+
//# debugId=d4304c43-c40e-f448-6475-6e2164756e21
|