@dosu/cli 0.46.0 → 0.47.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -1
- package/bin/dosu.js +1366 -2566
- package/package.json +1 -1
package/bin/dosu.js
CHANGED
|
@@ -2345,12 +2345,6 @@ function getSupabaseAnonKey() {
|
|
|
2345
2345
|
}
|
|
2346
2346
|
|
|
2347
2347
|
// src/client/client.ts
|
|
2348
|
-
var exports_client = {};
|
|
2349
|
-
__export(exports_client, {
|
|
2350
|
-
SessionExpiredError: () => SessionExpiredError,
|
|
2351
|
-
Client: () => Client
|
|
2352
|
-
});
|
|
2353
|
-
|
|
2354
2348
|
class Client {
|
|
2355
2349
|
baseURL;
|
|
2356
2350
|
config;
|
|
@@ -2410,34 +2404,6 @@ class Client {
|
|
|
2410
2404
|
async delete(path2) {
|
|
2411
2405
|
return this.doRequest("DELETE", path2);
|
|
2412
2406
|
}
|
|
2413
|
-
async getWithApiKey(path2) {
|
|
2414
|
-
return this.apiKeyRequest("GET", path2);
|
|
2415
|
-
}
|
|
2416
|
-
async postWithApiKey(path2, body) {
|
|
2417
|
-
return this.apiKeyRequest("POST", path2, body);
|
|
2418
|
-
}
|
|
2419
|
-
async apiKeyRequest(method, path2, body) {
|
|
2420
|
-
if (!this.config.active_account?.target?.api_key) {
|
|
2421
|
-
throw new Error("no API key available");
|
|
2422
|
-
}
|
|
2423
|
-
const url = this.baseURL + path2;
|
|
2424
|
-
const headers = {
|
|
2425
|
-
"Content-Type": "application/json",
|
|
2426
|
-
"X-Dosu-API-Key": this.config.active_account?.target?.api_key
|
|
2427
|
-
};
|
|
2428
|
-
const options = { method, headers };
|
|
2429
|
-
if (body !== undefined) {
|
|
2430
|
-
options.body = JSON.stringify(body);
|
|
2431
|
-
}
|
|
2432
|
-
const controller = new AbortController;
|
|
2433
|
-
const timeout = setTimeout(() => controller.abort(), 1e4);
|
|
2434
|
-
options.signal = controller.signal;
|
|
2435
|
-
try {
|
|
2436
|
-
return await fetch(url, options);
|
|
2437
|
-
} finally {
|
|
2438
|
-
clearTimeout(timeout);
|
|
2439
|
-
}
|
|
2440
|
-
}
|
|
2441
2407
|
async refreshToken() {
|
|
2442
2408
|
this.adoptNewerDiskTokens();
|
|
2443
2409
|
if (!this.config.active_account?.session.refresh_token) {
|
|
@@ -4773,7 +4739,7 @@ function isNpxInvocation(channel = INSTALL_CHANNEL, env = process.env) {
|
|
|
4773
4739
|
function getVersionString() {
|
|
4774
4740
|
return `v${VERSION}`;
|
|
4775
4741
|
}
|
|
4776
|
-
var VERSION = "0.
|
|
4742
|
+
var VERSION = "0.47.0", INSTALL_CHANNEL = "npm";
|
|
4777
4743
|
|
|
4778
4744
|
// src/debug/logger.ts
|
|
4779
4745
|
import {
|
|
@@ -8186,1416 +8152,292 @@ var init_github_step = __esm(() => {
|
|
|
8186
8152
|
INSTALLATION_TIMEOUT_MS = 10 * 60 * 1000;
|
|
8187
8153
|
});
|
|
8188
8154
|
|
|
8189
|
-
// src/
|
|
8190
|
-
|
|
8191
|
-
|
|
8192
|
-
|
|
8193
|
-
|
|
8194
|
-
|
|
8195
|
-
|
|
8196
|
-
|
|
8197
|
-
|
|
8198
|
-
|
|
8199
|
-
|
|
8200
|
-
if (trimmed) {
|
|
8201
|
-
blocks.push([
|
|
8202
|
-
"Dosu knowledge context for this task:",
|
|
8203
|
-
"",
|
|
8204
|
-
trimmed,
|
|
8205
|
-
"",
|
|
8206
|
-
"How to use this:",
|
|
8207
|
-
"- Use it quietly to answer faster and with fewer broad searches. Keep working normally.",
|
|
8208
|
-
"- This is the known likely path, not the full boundary. Before finalizing, verify adjacent " + "public/API entrypoints and edge cases that this route map may not cover.",
|
|
8209
|
-
"- Attribution: this context is your team's existing knowledge (docs, past PRs, discussions) " + "surfaced by Dosu — not the live code you're reading. When it informs, confirms, or narrows " + "a main finding, mention Dosu briefly near that finding. Keep the note natural and short. " + "Say what role the Dosu context played for that finding, such as confirming it, narrowing " + "the path, adding useful detail, or flagging something worth checking. " + "Include a source title or link when it is available and useful, but do not force a link " + "when the context itself is enough.",
|
|
8210
|
-
"- Do not create a separate Dosu section unless the user asks. Do not mention Dosu for every " + "paragraph. Mention it only beside findings where the context actually helped, confirmed " + 'an important point, or supplied a non-obvious nuance. Never open with "Dosu told me…", ' + "never add a closing praise paragraph, and never reference any ticket or lookup id."
|
|
8211
|
-
].join(`
|
|
8212
|
-
`));
|
|
8213
|
-
}
|
|
8214
|
-
if (saveRecommended) {
|
|
8215
|
-
blocks.push(SAVE_NUDGE);
|
|
8216
|
-
}
|
|
8217
|
-
return blocks.join(`
|
|
8218
|
-
|
|
8219
|
-
`);
|
|
8220
|
-
}
|
|
8221
|
-
var LOOKUP_STARTED_NOTE, LATE_RESULT_NOTE, STOP_PREFIX, SAVE_NUDGE;
|
|
8222
|
-
var init_prompts = __esm(() => {
|
|
8223
|
-
LOOKUP_STARTED_NOTE = "Dosu started a background knowledge lookup for this request. Keep working normally — " + "do not wait or pause for it. If Dosu context arrives during this session, fold it in " + "quietly to work faster and avoid wrong assumptions. Mention Dosu when its context " + "helps you.";
|
|
8224
|
-
LATE_RESULT_NOTE = "Heads-up: the context below answers your PREVIOUS prompt — its lookup finished after " + "that turn had already ended. Apply it only where it is still relevant to the current " + "work; do not treat it as a response to the new prompt.";
|
|
8225
|
-
STOP_PREFIX = "Dosu knowledge finished after your last action. Re-check your current conclusion against " + "it, then continue or finish — do not redo work it merely confirms.";
|
|
8226
|
-
SAVE_NUDGE = "Dosu has little or no existing knowledge on this topic. If you uncover something durable — " + "a non-obvious mechanism, a gotcha, or a route through the code — save it with the Dosu " + "`save_topic` tool when you finish, so future sessions start ahead. Only save genuinely " + "reusable knowledge; skip trivial or one-off details.";
|
|
8227
|
-
});
|
|
8228
|
-
|
|
8229
|
-
// src/hooks/ticket-client.ts
|
|
8230
|
-
var exports_ticket_client = {};
|
|
8231
|
-
__export(exports_ticket_client, {
|
|
8232
|
-
requestGetTicket: () => requestGetTicket,
|
|
8233
|
-
requestCreateTicket: () => requestCreateTicket,
|
|
8234
|
-
isDefinitiveError: () => isDefinitiveError,
|
|
8235
|
-
TicketHttpError: () => TicketHttpError
|
|
8236
|
-
});
|
|
8237
|
-
function isDefinitiveError(err) {
|
|
8238
|
-
if (err instanceof TicketHttpError)
|
|
8239
|
-
return !err.transient;
|
|
8240
|
-
return false;
|
|
8241
|
-
}
|
|
8242
|
-
async function requestCreateTicket(cfg, req) {
|
|
8243
|
-
const client = new Client(cfg);
|
|
8244
|
-
const resp = await client.postWithApiKey(TICKETS_PATH, req);
|
|
8245
|
-
if (resp.status !== 202 && resp.status !== 200 && resp.status !== 201) {
|
|
8246
|
-
throw new TicketHttpError(resp.status);
|
|
8247
|
-
}
|
|
8248
|
-
return await resp.json();
|
|
8249
|
-
}
|
|
8250
|
-
async function requestGetTicket(cfg, ticketId) {
|
|
8251
|
-
const client = new Client(cfg);
|
|
8252
|
-
const resp = await client.getWithApiKey(`${TICKETS_PATH}/${encodeURIComponent(ticketId)}`);
|
|
8253
|
-
if (resp.status !== 200 && resp.status !== 202) {
|
|
8254
|
-
throw new TicketHttpError(resp.status);
|
|
8255
|
-
}
|
|
8256
|
-
const data = await resp.json();
|
|
8257
|
-
if (!KNOWN_STATUSES.has(data.status)) {
|
|
8258
|
-
return { ...data, status: "failed", result: null };
|
|
8155
|
+
// src/insights/insights.ts
|
|
8156
|
+
async function buildInsights({
|
|
8157
|
+
client,
|
|
8158
|
+
cfg,
|
|
8159
|
+
ask,
|
|
8160
|
+
windowDays = 30,
|
|
8161
|
+
now = () => new Date,
|
|
8162
|
+
onProgress
|
|
8163
|
+
}) {
|
|
8164
|
+
if (!cfg.active_account?.target?.space_id) {
|
|
8165
|
+
throw new Error("space_id missing. Run 'dosu setup' first");
|
|
8259
8166
|
}
|
|
8260
|
-
|
|
8167
|
+
const spaceId = cfg.active_account?.target?.space_id;
|
|
8168
|
+
onProgress?.("stats");
|
|
8169
|
+
const [currentRaw, combinedRaw] = await Promise.all([
|
|
8170
|
+
client.analytics.getUsageStats.query({ spaceId, days: windowDays }),
|
|
8171
|
+
client.analytics.getUsageStats.query({ spaceId, days: windowDays * 2 })
|
|
8172
|
+
]);
|
|
8173
|
+
const current = normalizeStats(currentRaw);
|
|
8174
|
+
const combined = normalizeStats(combinedRaw);
|
|
8175
|
+
const previous = subtractStats(combined, current);
|
|
8176
|
+
const derived = computeDerived(current, previous);
|
|
8177
|
+
const cheers = computeCheers(current, derived);
|
|
8178
|
+
const investigate = computeInvestigate(current, previous, derived);
|
|
8179
|
+
const suggestions = computeSuggestions(current, previous, derived);
|
|
8180
|
+
onProgress?.("narrative");
|
|
8181
|
+
const atAGlanceFromAsk = await ask(buildAtAGlancePrompt(current, derived, windowDays));
|
|
8182
|
+
const atAGlance = atAGlanceFromAsk ?? fallbackAtAGlance(current, derived, windowDays);
|
|
8183
|
+
return {
|
|
8184
|
+
generatedAt: now().toISOString(),
|
|
8185
|
+
windowDays,
|
|
8186
|
+
spaceName: cfg.active_account?.target?.deployment_name ?? "your space",
|
|
8187
|
+
current,
|
|
8188
|
+
previous,
|
|
8189
|
+
derived,
|
|
8190
|
+
atAGlance,
|
|
8191
|
+
cheers,
|
|
8192
|
+
investigate,
|
|
8193
|
+
suggestions
|
|
8194
|
+
};
|
|
8261
8195
|
}
|
|
8262
|
-
|
|
8263
|
-
|
|
8264
|
-
|
|
8265
|
-
|
|
8266
|
-
|
|
8267
|
-
|
|
8268
|
-
|
|
8269
|
-
|
|
8270
|
-
|
|
8271
|
-
|
|
8196
|
+
function normalizeStats(raw) {
|
|
8197
|
+
if (!raw)
|
|
8198
|
+
return { ...EMPTY_STATS };
|
|
8199
|
+
const totalResponses = raw.totalResponses ?? 0;
|
|
8200
|
+
const reactionMsgs = raw.reactions?.messagesWithReactions ?? 0;
|
|
8201
|
+
return {
|
|
8202
|
+
totalResponses,
|
|
8203
|
+
byConfidence: {
|
|
8204
|
+
high: raw.byConfidence?.high ?? 0,
|
|
8205
|
+
medium: raw.byConfidence?.medium ?? 0,
|
|
8206
|
+
low: raw.byConfidence?.low ?? 0
|
|
8207
|
+
},
|
|
8208
|
+
reactions: {
|
|
8209
|
+
totalPositive: raw.reactions?.totalPositive ?? 0,
|
|
8210
|
+
totalNegative: raw.reactions?.totalNegative ?? 0,
|
|
8211
|
+
messagesWithReactions: reactionMsgs,
|
|
8212
|
+
reactionRate: raw.reactions?.reactionRate ?? (totalResponses > 0 ? reactionMsgs / totalResponses : 0),
|
|
8213
|
+
positiveRate: raw.reactions?.positiveRate ?? 0
|
|
8272
8214
|
}
|
|
8273
|
-
|
|
8274
|
-
|
|
8215
|
+
};
|
|
8216
|
+
}
|
|
8217
|
+
function subtractStats(combined, current) {
|
|
8218
|
+
const totalResponses = Math.max(0, combined.totalResponses - current.totalResponses);
|
|
8219
|
+
const positive = Math.max(0, combined.reactions.totalPositive - current.reactions.totalPositive);
|
|
8220
|
+
const negative = Math.max(0, combined.reactions.totalNegative - current.reactions.totalNegative);
|
|
8221
|
+
const reactionMsgs = Math.max(0, combined.reactions.messagesWithReactions - current.reactions.messagesWithReactions);
|
|
8222
|
+
const totalReactions = positive + negative;
|
|
8223
|
+
return {
|
|
8224
|
+
totalResponses,
|
|
8225
|
+
byConfidence: {
|
|
8226
|
+
high: Math.max(0, combined.byConfidence.high - current.byConfidence.high),
|
|
8227
|
+
medium: Math.max(0, combined.byConfidence.medium - current.byConfidence.medium),
|
|
8228
|
+
low: Math.max(0, combined.byConfidence.low - current.byConfidence.low)
|
|
8229
|
+
},
|
|
8230
|
+
reactions: {
|
|
8231
|
+
totalPositive: positive,
|
|
8232
|
+
totalNegative: negative,
|
|
8233
|
+
messagesWithReactions: reactionMsgs,
|
|
8234
|
+
reactionRate: totalResponses > 0 ? reactionMsgs / totalResponses : 0,
|
|
8235
|
+
positiveRate: totalReactions > 0 ? positive / totalReactions : 0
|
|
8275
8236
|
}
|
|
8276
8237
|
};
|
|
8277
|
-
});
|
|
8278
|
-
|
|
8279
|
-
// src/agent/output.ts
|
|
8280
|
-
var exports_output = {};
|
|
8281
|
-
__export(exports_output, {
|
|
8282
|
-
emitStep: () => emitStep,
|
|
8283
|
-
emitNeedUserAction: () => emitNeedUserAction,
|
|
8284
|
-
emitJSONLine: () => emitJSONLine,
|
|
8285
|
-
emitError: () => emitError
|
|
8286
|
-
});
|
|
8287
|
-
function emitJSONLine(value) {
|
|
8288
|
-
console.log(JSON.stringify(value));
|
|
8289
8238
|
}
|
|
8290
|
-
function
|
|
8291
|
-
|
|
8292
|
-
|
|
8293
|
-
|
|
8294
|
-
|
|
8295
|
-
|
|
8296
|
-
|
|
8297
|
-
|
|
8298
|
-
|
|
8299
|
-
|
|
8239
|
+
function computeDerived(current, previous) {
|
|
8240
|
+
const highConfidenceRate = current.totalResponses > 0 ? current.byConfidence.high / current.totalResponses : null;
|
|
8241
|
+
const prevHighConfidenceRate = previous.totalResponses > 0 ? previous.byConfidence.high / previous.totalResponses : null;
|
|
8242
|
+
const highConfidenceRateDelta = highConfidenceRate !== null && prevHighConfidenceRate !== null ? highConfidenceRate - prevHighConfidenceRate : null;
|
|
8243
|
+
const positiveRateDelta = current.reactions.totalPositive + current.reactions.totalNegative > 0 && previous.reactions.totalPositive + previous.reactions.totalNegative > 0 ? current.reactions.positiveRate - previous.reactions.positiveRate : null;
|
|
8244
|
+
return {
|
|
8245
|
+
highConfidenceRate,
|
|
8246
|
+
highConfidenceRateDelta,
|
|
8247
|
+
responsesDelta: current.totalResponses - previous.totalResponses,
|
|
8248
|
+
positiveRateDelta,
|
|
8249
|
+
hasPriorWindow: previous.totalResponses > 0
|
|
8250
|
+
};
|
|
8300
8251
|
}
|
|
8301
|
-
function
|
|
8302
|
-
const
|
|
8303
|
-
|
|
8304
|
-
|
|
8305
|
-
|
|
8306
|
-
|
|
8307
|
-
|
|
8308
|
-
|
|
8309
|
-
}
|
|
8252
|
+
function computeCheers(stats, derived) {
|
|
8253
|
+
const out = [];
|
|
8254
|
+
if (stats.totalResponses === 0) {
|
|
8255
|
+
out.push("Your space is brand new and ready to roll. Share `dosu setup` with your team to start asking questions.");
|
|
8256
|
+
return out;
|
|
8257
|
+
}
|
|
8258
|
+
if (derived.highConfidenceRate !== null && derived.highConfidenceRate >= 0.8) {
|
|
8259
|
+
out.push(`${pct(derived.highConfidenceRate)} of responses came back high-confidence. Your sources are covering most of what your team's asking about.`);
|
|
8260
|
+
}
|
|
8261
|
+
if (stats.byConfidence.high > stats.byConfidence.low * 2 && stats.byConfidence.high > 0) {
|
|
8262
|
+
out.push(`${stats.byConfidence.high} high-confidence answers vs ${stats.byConfidence.low} low. Most questions have a clear source to point to.`);
|
|
8263
|
+
}
|
|
8264
|
+
if (stats.reactions.totalPositive + stats.reactions.totalNegative > 0 && stats.reactions.positiveRate >= 0.8) {
|
|
8265
|
+
out.push(`${pct(stats.reactions.positiveRate)} of reactions were positive. The answers your team's engaging with are landing.`);
|
|
8266
|
+
}
|
|
8267
|
+
if (derived.hasPriorWindow && derived.responsesDelta > 0) {
|
|
8268
|
+
out.push(`Volume is up ${derived.responsesDelta} responses vs the prior window. Your team is leaning on Dosu more.`);
|
|
8269
|
+
}
|
|
8270
|
+
if (out.length === 0) {
|
|
8271
|
+
out.push(`${stats.totalResponses} responses logged this window. Every one is a chance to learn what your team needs.`);
|
|
8272
|
+
}
|
|
8273
|
+
return out;
|
|
8310
8274
|
}
|
|
8311
|
-
function
|
|
8312
|
-
const
|
|
8313
|
-
|
|
8275
|
+
function computeInvestigate(current, previous, derived) {
|
|
8276
|
+
const out = [];
|
|
8277
|
+
if (current.totalResponses === 0)
|
|
8278
|
+
return out;
|
|
8279
|
+
if (derived.highConfidenceRateDelta !== null && derived.highConfidenceRateDelta <= -0.05 && previous.totalResponses > 0) {
|
|
8280
|
+
const before = pct(previous.byConfidence.high / previous.totalResponses);
|
|
8281
|
+
const now = pct(current.byConfidence.high / current.totalResponses);
|
|
8282
|
+
out.push(`High-confidence share dropped from ${before} to ${now}. Usually a sign of a new product area Dosu doesn't have docs for yet.`);
|
|
8283
|
+
}
|
|
8284
|
+
const lowDelta = current.byConfidence.low - previous.byConfidence.low;
|
|
8285
|
+
if (derived.hasPriorWindow && lowDelta >= 5 && current.byConfidence.low > 0) {
|
|
8286
|
+
out.push(`Low-confidence answers grew by ${lowDelta} (now ${current.byConfidence.low} this window). Each one is a hint that your knowledge base has gaps.`);
|
|
8287
|
+
}
|
|
8288
|
+
if (derived.positiveRateDelta !== null && derived.positiveRateDelta <= -0.05) {
|
|
8289
|
+
const before = pct(previous.reactions.positiveRate);
|
|
8290
|
+
const now = pct(current.reactions.positiveRate);
|
|
8291
|
+
out.push(`Positive feedback fell from ${before} to ${now}. The negative reactions are the most actionable signal. Open them first.`);
|
|
8292
|
+
}
|
|
8293
|
+
if (current.reactions.totalNegative > current.reactions.totalPositive && current.reactions.totalNegative >= 3) {
|
|
8294
|
+
out.push(`${current.reactions.totalNegative} negative reactions vs ${current.reactions.totalPositive} positive. Worth a look. What changed?`);
|
|
8295
|
+
}
|
|
8296
|
+
if (derived.hasPriorWindow && derived.responsesDelta <= -10) {
|
|
8297
|
+
out.push(`Volume is down by ${Math.abs(derived.responsesDelta)} responses. If your team stopped asking, find out why.`);
|
|
8298
|
+
}
|
|
8299
|
+
if (derived.highConfidenceRate !== null && derived.highConfidenceRate < 0.5 && current.totalResponses >= 5) {
|
|
8300
|
+
const lowAndMed = current.byConfidence.low + current.byConfidence.medium;
|
|
8301
|
+
out.push(`${lowAndMed} of ${current.totalResponses} responses were medium- or low-confidence. Open the threads to see where the knowledge base needs more material.`);
|
|
8302
|
+
}
|
|
8303
|
+
return out;
|
|
8314
8304
|
}
|
|
8315
|
-
|
|
8316
|
-
|
|
8317
|
-
|
|
8318
|
-
|
|
8319
|
-
|
|
8320
|
-
|
|
8321
|
-
|
|
8322
|
-
|
|
8323
|
-
|
|
8305
|
+
function computeSuggestions(current, previous, derived) {
|
|
8306
|
+
const out = [];
|
|
8307
|
+
if (current.totalResponses === 0) {
|
|
8308
|
+
out.push({
|
|
8309
|
+
headline: "Get your team using Dosu",
|
|
8310
|
+
detail: "No responses logged yet. The fastest unlock is wiring Dosu into the tools your team already uses. Ask Dosu in Slack, your editor, or anywhere else.",
|
|
8311
|
+
command: "dosu setup"
|
|
8312
|
+
});
|
|
8313
|
+
out.push({
|
|
8314
|
+
headline: "Try a question yourself",
|
|
8315
|
+
detail: "See what Dosu does without configuring anything else. Replace the example with whatever you're curious about.",
|
|
8316
|
+
command: 'dosu ask "what is Dosu?"'
|
|
8317
|
+
});
|
|
8318
|
+
return out;
|
|
8324
8319
|
}
|
|
8325
|
-
|
|
8326
|
-
|
|
8320
|
+
const lowDelta = current.byConfidence.low - previous.byConfidence.low;
|
|
8321
|
+
const lowGrewSignificantly = derived.hasPriorWindow && lowDelta >= 5;
|
|
8322
|
+
const lowHighInAbsoluteTerms = current.byConfidence.low >= Math.max(5, current.totalResponses * 0.3);
|
|
8323
|
+
if (lowGrewSignificantly || lowHighInAbsoluteTerms) {
|
|
8324
|
+
out.push({
|
|
8325
|
+
headline: "Audit recent low-confidence answers",
|
|
8326
|
+
detail: `${current.byConfidence.low} answers had low confidence this window${lowGrewSignificantly ? ` (+${lowDelta} vs prior)` : ""}. Open them to see exactly what knowledge is missing.`,
|
|
8327
|
+
command: "dosu threads list"
|
|
8328
|
+
});
|
|
8327
8329
|
}
|
|
8328
|
-
|
|
8329
|
-
|
|
8330
|
-
|
|
8331
|
-
|
|
8332
|
-
|
|
8333
|
-
|
|
8334
|
-
|
|
8335
|
-
|
|
8336
|
-
|
|
8337
|
-
|
|
8338
|
-
|
|
8339
|
-
|
|
8340
|
-
|
|
8341
|
-
|
|
8342
|
-
|
|
8343
|
-
|
|
8344
|
-
|
|
8345
|
-
|
|
8346
|
-
|
|
8347
|
-
|
|
8348
|
-
|
|
8349
|
-
|
|
8350
|
-
|
|
8351
|
-
|
|
8352
|
-
|
|
8353
|
-
|
|
8354
|
-
id = Math.random();
|
|
8355
|
-
constructor() {
|
|
8356
|
-
if (global[kExitEmitter]) {
|
|
8357
|
-
return global[kExitEmitter];
|
|
8358
|
-
}
|
|
8359
|
-
ObjectDefineProperty(global, kExitEmitter, {
|
|
8360
|
-
value: this,
|
|
8361
|
-
writable: false,
|
|
8362
|
-
enumerable: false,
|
|
8363
|
-
configurable: false
|
|
8364
|
-
});
|
|
8365
|
-
}
|
|
8366
|
-
on(ev, fn) {
|
|
8367
|
-
this.listeners[ev].push(fn);
|
|
8368
|
-
}
|
|
8369
|
-
removeListener(ev, fn) {
|
|
8370
|
-
const list = this.listeners[ev];
|
|
8371
|
-
const i2 = list.indexOf(fn);
|
|
8372
|
-
if (i2 === -1) {
|
|
8373
|
-
return;
|
|
8374
|
-
}
|
|
8375
|
-
if (i2 === 0 && list.length === 1) {
|
|
8376
|
-
list.length = 0;
|
|
8377
|
-
} else {
|
|
8378
|
-
list.splice(i2, 1);
|
|
8379
|
-
}
|
|
8380
|
-
}
|
|
8381
|
-
emit(ev, code, signal) {
|
|
8382
|
-
if (this.emitted[ev]) {
|
|
8383
|
-
return false;
|
|
8384
|
-
}
|
|
8385
|
-
this.emitted[ev] = true;
|
|
8386
|
-
let ret = false;
|
|
8387
|
-
for (const fn of this.listeners[ev]) {
|
|
8388
|
-
ret = fn(code, signal) === true || ret;
|
|
8389
|
-
}
|
|
8390
|
-
if (ev === "exit") {
|
|
8391
|
-
ret = this.emit("afterExit", code, signal) || ret;
|
|
8392
|
-
}
|
|
8393
|
-
return ret;
|
|
8394
|
-
}
|
|
8330
|
+
if (derived.positiveRateDelta !== null && derived.positiveRateDelta <= -0.05) {
|
|
8331
|
+
out.push({
|
|
8332
|
+
headline: "Refresh your sources",
|
|
8333
|
+
detail: "Positive feedback is trending down. Usually a stale doc, a moved page, or a new product area Dosu hasn't seen.",
|
|
8334
|
+
command: "dosu sources list"
|
|
8335
|
+
});
|
|
8336
|
+
}
|
|
8337
|
+
if (current.totalResponses >= 50 && derived.highConfidenceRate !== null && derived.highConfidenceRate >= 0.7 && out.length === 0) {
|
|
8338
|
+
out.push({
|
|
8339
|
+
headline: "Share the win with your team",
|
|
8340
|
+
detail: `${current.byConfidence.high} of ${current.totalResponses} responses landed with high confidence (${pct(derived.highConfidenceRate)}). People are getting unblocked. Make sure your team knows it's working.`
|
|
8341
|
+
});
|
|
8342
|
+
}
|
|
8343
|
+
if (derived.hasPriorWindow && derived.responsesDelta >= 10) {
|
|
8344
|
+
out.push({
|
|
8345
|
+
headline: "See what your team is asking",
|
|
8346
|
+
detail: `Volume is up by ${derived.responsesDelta} responses vs the prior window. Browsing recent threads is the fastest way to spot patterns and gaps.`,
|
|
8347
|
+
command: "dosu threads list"
|
|
8348
|
+
});
|
|
8349
|
+
}
|
|
8350
|
+
if (out.length === 0) {
|
|
8351
|
+
out.push({
|
|
8352
|
+
headline: "Find your knowledge gaps",
|
|
8353
|
+
detail: "Dosu surfaces document suggestions from real questions. A quick scan often reveals what's worth writing next.",
|
|
8354
|
+
command: "dosu suggest list"
|
|
8355
|
+
});
|
|
8395
8356
|
}
|
|
8357
|
+
const seen = new Set;
|
|
8358
|
+
const deduped = out.filter((s) => {
|
|
8359
|
+
if (!s.command)
|
|
8360
|
+
return true;
|
|
8361
|
+
if (seen.has(s.command))
|
|
8362
|
+
return false;
|
|
8363
|
+
seen.add(s.command);
|
|
8364
|
+
return true;
|
|
8365
|
+
});
|
|
8366
|
+
return deduped.slice(0, 4);
|
|
8367
|
+
}
|
|
8368
|
+
function pct(v) {
|
|
8369
|
+
return `${(v * 100).toFixed(0)}%`;
|
|
8370
|
+
}
|
|
8371
|
+
function statsBlock(stats, derived) {
|
|
8372
|
+
const hc = derived.highConfidenceRate !== null ? pct(derived.highConfidenceRate) : "n/a";
|
|
8373
|
+
const pr = stats.reactions.totalPositive + stats.reactions.totalNegative > 0 ? pct(stats.reactions.positiveRate) : "n/a";
|
|
8374
|
+
return [
|
|
8375
|
+
`- ${stats.totalResponses} total responses`,
|
|
8376
|
+
`- Confidence: ${stats.byConfidence.high} high, ${stats.byConfidence.medium} medium, ${stats.byConfidence.low} low (${hc} high-confidence share)`,
|
|
8377
|
+
`- Reactions: ${stats.reactions.totalPositive} positive, ${stats.reactions.totalNegative} negative (${pr} positive rate)`
|
|
8378
|
+
].join(`
|
|
8379
|
+
`);
|
|
8380
|
+
}
|
|
8381
|
+
function buildAtAGlancePrompt(stats, derived, days) {
|
|
8382
|
+
const priorNote = derived.hasPriorWindow ? "" : `
|
|
8396
8383
|
|
|
8397
|
-
|
|
8384
|
+
Note: this is the space's first ${days}-day window, so do NOT compare to a prior period. Focus on what's present.`;
|
|
8385
|
+
return `You're writing the "At a Glance" panel for a Dosu space insights report. Here are the real stats from the last ${days} days:
|
|
8386
|
+
|
|
8387
|
+
${statsBlock(stats, derived)}${priorNote}
|
|
8388
|
+
|
|
8389
|
+
Write 2-3 short sentences (no bullets, no headers, no markdown, no em dashes) that synthesize what's interesting or worth celebrating. Be warm, specific, and a little fun. Don't just list the numbers; interpret them. Speak directly to the reader ("you" / "your team").`;
|
|
8390
|
+
}
|
|
8391
|
+
function fallbackAtAGlance(stats, derived, days) {
|
|
8392
|
+
if (stats.totalResponses === 0) {
|
|
8393
|
+
return `Your space is brand new. The next ${days} days will give us something to talk about, so let's see what your team asks first.`;
|
|
8398
8394
|
}
|
|
8399
|
-
|
|
8400
|
-
|
|
8401
|
-
|
|
8402
|
-
|
|
8403
|
-
|
|
8404
|
-
|
|
8405
|
-
|
|
8406
|
-
|
|
8407
|
-
|
|
8408
|
-
|
|
8409
|
-
|
|
8410
|
-
|
|
8395
|
+
const hc = derived.highConfidenceRate !== null ? pct(derived.highConfidenceRate) : "n/a";
|
|
8396
|
+
const reactions = stats.reactions.totalPositive + stats.reactions.totalNegative;
|
|
8397
|
+
const pr = reactions > 0 ? `, with ${pct(stats.reactions.positiveRate)} positive feedback` : "";
|
|
8398
|
+
if (!derived.hasPriorWindow) {
|
|
8399
|
+
return `In your first ${days}-day window you logged ${stats.totalResponses} responses, ${hc} of them high-confidence${pr}. Check back after another ${days} days for trend comparisons.`;
|
|
8400
|
+
}
|
|
8401
|
+
const trend = derived.responsesDelta > 0 ? ` Volume is up ${derived.responsesDelta} vs the prior ${days} days. The team is leaning on Dosu more.` : derived.responsesDelta < 0 ? ` Volume is down ${Math.abs(derived.responsesDelta)} vs the prior ${days} days. Worth a quick look.` : "";
|
|
8402
|
+
return `In the last ${days} days you logged ${stats.totalResponses} responses, ${hc} of them high-confidence${pr}.${trend}`;
|
|
8403
|
+
}
|
|
8404
|
+
var EMPTY_STATS;
|
|
8405
|
+
var init_insights = __esm(() => {
|
|
8406
|
+
EMPTY_STATS = {
|
|
8407
|
+
totalResponses: 0,
|
|
8408
|
+
byConfidence: { high: 0, medium: 0, low: 0 },
|
|
8409
|
+
reactions: {
|
|
8410
|
+
totalPositive: 0,
|
|
8411
|
+
totalNegative: 0,
|
|
8412
|
+
messagesWithReactions: 0,
|
|
8413
|
+
reactionRate: 0,
|
|
8414
|
+
positiveRate: 0
|
|
8415
|
+
}
|
|
8411
8416
|
};
|
|
8417
|
+
});
|
|
8412
8418
|
|
|
8413
|
-
|
|
8414
|
-
|
|
8415
|
-
|
|
8416
|
-
|
|
8417
|
-
|
|
8418
|
-
|
|
8419
|
-
|
|
8420
|
-
|
|
8421
|
-
|
|
8422
|
-
|
|
8423
|
-
|
|
8424
|
-
|
|
8425
|
-
|
|
8426
|
-
|
|
8427
|
-
|
|
8428
|
-
|
|
8429
|
-
|
|
8430
|
-
|
|
8431
|
-
|
|
8432
|
-
|
|
8433
|
-
|
|
8434
|
-
|
|
8435
|
-
const listeners = this.#process.listeners(sig);
|
|
8436
|
-
let { count } = this.#emitter;
|
|
8437
|
-
const p2 = process11;
|
|
8438
|
-
if (typeof p2.__signal_exit_emitter__ === "object" && typeof p2.__signal_exit_emitter__.count === "number") {
|
|
8439
|
-
count += p2.__signal_exit_emitter__.count;
|
|
8440
|
-
}
|
|
8441
|
-
if (listeners.length === count) {
|
|
8442
|
-
this.unload();
|
|
8443
|
-
const ret = this.#emitter.emit("exit", null, sig);
|
|
8444
|
-
const s = sig === "SIGHUP" ? this.#hupSig : sig;
|
|
8445
|
-
if (!ret)
|
|
8446
|
-
process11.kill(process11.pid, s);
|
|
8447
|
-
}
|
|
8448
|
-
};
|
|
8449
|
-
}
|
|
8450
|
-
this.#originalProcessReallyExit = process11.reallyExit;
|
|
8451
|
-
this.#originalProcessEmit = process11.emit;
|
|
8452
|
-
}
|
|
8453
|
-
onExit(cb, opts) {
|
|
8454
|
-
if (!processOk(this.#process)) {
|
|
8455
|
-
return () => {};
|
|
8456
|
-
}
|
|
8457
|
-
if (this.#loaded === false) {
|
|
8458
|
-
this.load();
|
|
8459
|
-
}
|
|
8460
|
-
const ev = opts?.alwaysLast ? "afterExit" : "exit";
|
|
8461
|
-
this.#emitter.on(ev, cb);
|
|
8462
|
-
return () => {
|
|
8463
|
-
this.#emitter.removeListener(ev, cb);
|
|
8464
|
-
if (this.#emitter.listeners["exit"].length === 0 && this.#emitter.listeners["afterExit"].length === 0) {
|
|
8465
|
-
this.unload();
|
|
8466
|
-
}
|
|
8467
|
-
};
|
|
8468
|
-
}
|
|
8469
|
-
load() {
|
|
8470
|
-
if (this.#loaded) {
|
|
8471
|
-
return;
|
|
8472
|
-
}
|
|
8473
|
-
this.#loaded = true;
|
|
8474
|
-
this.#emitter.count += 1;
|
|
8475
|
-
for (const sig of signals_js_1.signals) {
|
|
8476
|
-
try {
|
|
8477
|
-
const fn = this.#sigListeners[sig];
|
|
8478
|
-
if (fn)
|
|
8479
|
-
this.#process.on(sig, fn);
|
|
8480
|
-
} catch (_2) {}
|
|
8481
|
-
}
|
|
8482
|
-
this.#process.emit = (ev, ...a3) => {
|
|
8483
|
-
return this.#processEmit(ev, ...a3);
|
|
8484
|
-
};
|
|
8485
|
-
this.#process.reallyExit = (code) => {
|
|
8486
|
-
return this.#processReallyExit(code);
|
|
8487
|
-
};
|
|
8488
|
-
}
|
|
8489
|
-
unload() {
|
|
8490
|
-
if (!this.#loaded) {
|
|
8491
|
-
return;
|
|
8492
|
-
}
|
|
8493
|
-
this.#loaded = false;
|
|
8494
|
-
signals_js_1.signals.forEach((sig) => {
|
|
8495
|
-
const listener = this.#sigListeners[sig];
|
|
8496
|
-
if (!listener) {
|
|
8497
|
-
throw new Error("Listener not defined for signal: " + sig);
|
|
8498
|
-
}
|
|
8499
|
-
try {
|
|
8500
|
-
this.#process.removeListener(sig, listener);
|
|
8501
|
-
} catch (_2) {}
|
|
8502
|
-
});
|
|
8503
|
-
this.#process.emit = this.#originalProcessEmit;
|
|
8504
|
-
this.#process.reallyExit = this.#originalProcessReallyExit;
|
|
8505
|
-
this.#emitter.count -= 1;
|
|
8506
|
-
}
|
|
8507
|
-
#processReallyExit(code) {
|
|
8508
|
-
if (!processOk(this.#process)) {
|
|
8509
|
-
return 0;
|
|
8510
|
-
}
|
|
8511
|
-
this.#process.exitCode = code || 0;
|
|
8512
|
-
this.#emitter.emit("exit", this.#process.exitCode, null);
|
|
8513
|
-
return this.#originalProcessReallyExit.call(this.#process, this.#process.exitCode);
|
|
8514
|
-
}
|
|
8515
|
-
#processEmit(ev, ...args) {
|
|
8516
|
-
const og = this.#originalProcessEmit;
|
|
8517
|
-
if (ev === "exit" && processOk(this.#process)) {
|
|
8518
|
-
if (typeof args[0] === "number") {
|
|
8519
|
-
this.#process.exitCode = args[0];
|
|
8520
|
-
}
|
|
8521
|
-
const ret = og.call(this.#process, ev, ...args);
|
|
8522
|
-
this.#emitter.emit("exit", this.#process.exitCode, null);
|
|
8523
|
-
return ret;
|
|
8524
|
-
} else {
|
|
8525
|
-
return og.call(this.#process, ev, ...args);
|
|
8526
|
-
}
|
|
8527
|
-
}
|
|
8528
|
-
}
|
|
8529
|
-
var process10 = globalThis.process;
|
|
8530
|
-
_a = signalExitWrap(processOk(process10) ? new SignalExit(process10) : new SignalExitFallback), exports.onExit = _a.onExit, exports.load = _a.load, exports.unload = _a.unload;
|
|
8531
|
-
});
|
|
8532
|
-
|
|
8533
|
-
// node_modules/write-file-atomic/lib/index.js
|
|
8534
|
-
var require_lib = __commonJS((exports, module) => {
|
|
8535
|
-
var __filename = "/home/runner/work/dosu-cli/dosu-cli/node_modules/write-file-atomic/lib/index.js";
|
|
8536
|
-
module.exports = writeFile;
|
|
8537
|
-
module.exports.sync = writeFileSync6;
|
|
8538
|
-
module.exports._getTmpname = getTmpname;
|
|
8539
|
-
module.exports._cleanupOnExit = cleanupOnExit;
|
|
8540
|
-
var fs7 = __require("fs");
|
|
8541
|
-
var crypto = __require("node:crypto");
|
|
8542
|
-
var { onExit } = require_cjs();
|
|
8543
|
-
var path3 = __require("path");
|
|
8544
|
-
var { promisify: promisify7 } = __require("util");
|
|
8545
|
-
var activeFiles = {};
|
|
8546
|
-
var threadId = function getId() {
|
|
8547
|
-
try {
|
|
8548
|
-
const workerThreads = __require("worker_threads");
|
|
8549
|
-
return workerThreads.threadId;
|
|
8550
|
-
} catch (e) {
|
|
8551
|
-
return 0;
|
|
8552
|
-
}
|
|
8553
|
-
}();
|
|
8554
|
-
var invocations = 0;
|
|
8555
|
-
function getTmpname(filename) {
|
|
8556
|
-
return filename + "." + crypto.createHash("sha1").update(__filename).update(String(process.pid)).update(String(threadId)).update(String(++invocations)).digest().readUInt32BE(0);
|
|
8557
|
-
}
|
|
8558
|
-
function cleanupOnExit(tmpfile) {
|
|
8559
|
-
return () => {
|
|
8560
|
-
try {
|
|
8561
|
-
fs7.unlinkSync(typeof tmpfile === "function" ? tmpfile() : tmpfile);
|
|
8562
|
-
} catch {}
|
|
8563
|
-
};
|
|
8564
|
-
}
|
|
8565
|
-
function serializeActiveFile(absoluteName) {
|
|
8566
|
-
return new Promise((resolve) => {
|
|
8567
|
-
if (!activeFiles[absoluteName]) {
|
|
8568
|
-
activeFiles[absoluteName] = [];
|
|
8569
|
-
}
|
|
8570
|
-
activeFiles[absoluteName].push(resolve);
|
|
8571
|
-
if (activeFiles[absoluteName].length === 1) {
|
|
8572
|
-
resolve();
|
|
8573
|
-
}
|
|
8574
|
-
});
|
|
8575
|
-
}
|
|
8576
|
-
function isChownErrOk(err) {
|
|
8577
|
-
if (err.code === "ENOSYS") {
|
|
8578
|
-
return true;
|
|
8579
|
-
}
|
|
8580
|
-
const nonroot = !process.getuid || process.getuid() !== 0;
|
|
8581
|
-
if (nonroot) {
|
|
8582
|
-
if (err.code === "EINVAL" || err.code === "EPERM") {
|
|
8583
|
-
return true;
|
|
8584
|
-
}
|
|
8585
|
-
}
|
|
8586
|
-
return false;
|
|
8587
|
-
}
|
|
8588
|
-
async function writeFileAsync(filename, data, options = {}) {
|
|
8589
|
-
if (typeof options === "string") {
|
|
8590
|
-
options = { encoding: options };
|
|
8591
|
-
}
|
|
8592
|
-
let fd;
|
|
8593
|
-
let tmpfile;
|
|
8594
|
-
const removeOnExitHandler = onExit(cleanupOnExit(() => tmpfile));
|
|
8595
|
-
const absoluteName = path3.resolve(filename);
|
|
8596
|
-
try {
|
|
8597
|
-
await serializeActiveFile(absoluteName);
|
|
8598
|
-
const truename = await promisify7(fs7.realpath)(filename).catch(() => filename);
|
|
8599
|
-
tmpfile = getTmpname(truename);
|
|
8600
|
-
if (!options.mode || !options.chown) {
|
|
8601
|
-
const stats = await promisify7(fs7.stat)(truename).catch(() => {});
|
|
8602
|
-
if (stats) {
|
|
8603
|
-
if (options.mode == null) {
|
|
8604
|
-
options.mode = stats.mode;
|
|
8605
|
-
}
|
|
8606
|
-
if (options.chown == null && process.getuid) {
|
|
8607
|
-
options.chown = { uid: stats.uid, gid: stats.gid };
|
|
8608
|
-
}
|
|
8609
|
-
}
|
|
8610
|
-
}
|
|
8611
|
-
fd = await promisify7(fs7.open)(tmpfile, "w", options.mode);
|
|
8612
|
-
if (options.tmpfileCreated) {
|
|
8613
|
-
await options.tmpfileCreated(tmpfile);
|
|
8614
|
-
}
|
|
8615
|
-
if (ArrayBuffer.isView(data)) {
|
|
8616
|
-
await promisify7(fs7.write)(fd, data, 0, data.length, 0);
|
|
8617
|
-
} else if (data != null) {
|
|
8618
|
-
await promisify7(fs7.write)(fd, String(data), 0, String(options.encoding || "utf8"));
|
|
8619
|
-
}
|
|
8620
|
-
if (options.fsync !== false) {
|
|
8621
|
-
await promisify7(fs7.fsync)(fd);
|
|
8622
|
-
}
|
|
8623
|
-
await promisify7(fs7.close)(fd);
|
|
8624
|
-
fd = null;
|
|
8625
|
-
if (options.chown) {
|
|
8626
|
-
await promisify7(fs7.chown)(tmpfile, options.chown.uid, options.chown.gid).catch((err) => {
|
|
8627
|
-
if (!isChownErrOk(err)) {
|
|
8628
|
-
throw err;
|
|
8629
|
-
}
|
|
8630
|
-
});
|
|
8631
|
-
}
|
|
8632
|
-
if (options.mode) {
|
|
8633
|
-
await promisify7(fs7.chmod)(tmpfile, options.mode).catch((err) => {
|
|
8634
|
-
if (!isChownErrOk(err)) {
|
|
8635
|
-
throw err;
|
|
8636
|
-
}
|
|
8637
|
-
});
|
|
8638
|
-
}
|
|
8639
|
-
await promisify7(fs7.rename)(tmpfile, truename);
|
|
8640
|
-
} finally {
|
|
8641
|
-
if (fd) {
|
|
8642
|
-
await promisify7(fs7.close)(fd).catch(() => {});
|
|
8643
|
-
}
|
|
8644
|
-
removeOnExitHandler();
|
|
8645
|
-
await promisify7(fs7.unlink)(tmpfile).catch(() => {});
|
|
8646
|
-
activeFiles[absoluteName].shift();
|
|
8647
|
-
if (activeFiles[absoluteName].length > 0) {
|
|
8648
|
-
activeFiles[absoluteName][0]();
|
|
8649
|
-
} else {
|
|
8650
|
-
delete activeFiles[absoluteName];
|
|
8651
|
-
}
|
|
8652
|
-
}
|
|
8653
|
-
}
|
|
8654
|
-
async function writeFile(filename, data, options, callback) {
|
|
8655
|
-
if (options instanceof Function) {
|
|
8656
|
-
callback = options;
|
|
8657
|
-
options = {};
|
|
8658
|
-
}
|
|
8659
|
-
const promise = writeFileAsync(filename, data, options);
|
|
8660
|
-
if (callback) {
|
|
8661
|
-
try {
|
|
8662
|
-
const result = await promise;
|
|
8663
|
-
return callback(result);
|
|
8664
|
-
} catch (err) {
|
|
8665
|
-
return callback(err);
|
|
8666
|
-
}
|
|
8667
|
-
}
|
|
8668
|
-
return promise;
|
|
8669
|
-
}
|
|
8670
|
-
function writeFileSync6(filename, data, options) {
|
|
8671
|
-
if (typeof options === "string") {
|
|
8672
|
-
options = { encoding: options };
|
|
8673
|
-
} else if (!options) {
|
|
8674
|
-
options = {};
|
|
8675
|
-
}
|
|
8676
|
-
try {
|
|
8677
|
-
filename = fs7.realpathSync(filename);
|
|
8678
|
-
} catch (ex) {}
|
|
8679
|
-
const tmpfile = getTmpname(filename);
|
|
8680
|
-
if (!options.mode || !options.chown) {
|
|
8681
|
-
try {
|
|
8682
|
-
const stats = fs7.statSync(filename);
|
|
8683
|
-
options = Object.assign({}, options);
|
|
8684
|
-
if (!options.mode) {
|
|
8685
|
-
options.mode = stats.mode;
|
|
8686
|
-
}
|
|
8687
|
-
if (!options.chown && process.getuid) {
|
|
8688
|
-
options.chown = { uid: stats.uid, gid: stats.gid };
|
|
8689
|
-
}
|
|
8690
|
-
} catch (ex) {}
|
|
8691
|
-
}
|
|
8692
|
-
let fd;
|
|
8693
|
-
const cleanup = cleanupOnExit(tmpfile);
|
|
8694
|
-
const removeOnExitHandler = onExit(cleanup);
|
|
8695
|
-
let threw = true;
|
|
8696
|
-
try {
|
|
8697
|
-
fd = fs7.openSync(tmpfile, "w", options.mode || 438);
|
|
8698
|
-
if (options.tmpfileCreated) {
|
|
8699
|
-
options.tmpfileCreated(tmpfile);
|
|
8700
|
-
}
|
|
8701
|
-
if (ArrayBuffer.isView(data)) {
|
|
8702
|
-
fs7.writeSync(fd, data, 0, data.length, 0);
|
|
8703
|
-
} else if (data != null) {
|
|
8704
|
-
fs7.writeSync(fd, String(data), 0, String(options.encoding || "utf8"));
|
|
8705
|
-
}
|
|
8706
|
-
if (options.fsync !== false) {
|
|
8707
|
-
fs7.fsyncSync(fd);
|
|
8708
|
-
}
|
|
8709
|
-
fs7.closeSync(fd);
|
|
8710
|
-
fd = null;
|
|
8711
|
-
if (options.chown) {
|
|
8712
|
-
try {
|
|
8713
|
-
fs7.chownSync(tmpfile, options.chown.uid, options.chown.gid);
|
|
8714
|
-
} catch (err) {
|
|
8715
|
-
if (!isChownErrOk(err)) {
|
|
8716
|
-
throw err;
|
|
8717
|
-
}
|
|
8718
|
-
}
|
|
8719
|
-
}
|
|
8720
|
-
if (options.mode) {
|
|
8721
|
-
try {
|
|
8722
|
-
fs7.chmodSync(tmpfile, options.mode);
|
|
8723
|
-
} catch (err) {
|
|
8724
|
-
if (!isChownErrOk(err)) {
|
|
8725
|
-
throw err;
|
|
8726
|
-
}
|
|
8727
|
-
}
|
|
8728
|
-
}
|
|
8729
|
-
fs7.renameSync(tmpfile, filename);
|
|
8730
|
-
threw = false;
|
|
8731
|
-
} finally {
|
|
8732
|
-
if (fd) {
|
|
8733
|
-
try {
|
|
8734
|
-
fs7.closeSync(fd);
|
|
8735
|
-
} catch (ex) {}
|
|
8736
|
-
}
|
|
8737
|
-
removeOnExitHandler();
|
|
8738
|
-
if (threw) {
|
|
8739
|
-
cleanup();
|
|
8740
|
-
}
|
|
8741
|
-
}
|
|
8742
|
-
}
|
|
8743
|
-
});
|
|
8744
|
-
|
|
8745
|
-
// src/mcp/config-helpers.ts
|
|
8746
|
-
import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync7 } from "node:fs";
|
|
8747
|
-
import { dirname as dirname3 } from "node:path";
|
|
8748
|
-
function mcpURL(deploymentID) {
|
|
8749
|
-
return `${getBackendURL()}/v1/mcp/deployments/${deploymentID}`;
|
|
8750
|
-
}
|
|
8751
|
-
function mcpBaseURL() {
|
|
8752
|
-
return `${getBackendURL()}/v1/mcp`;
|
|
8753
|
-
}
|
|
8754
|
-
function mcpHeaders(apiKey) {
|
|
8755
|
-
if (!apiKey) {
|
|
8756
|
-
throw new Error("API key is required. Run 'dosu setup' to create one.");
|
|
8757
|
-
}
|
|
8758
|
-
return { "X-Dosu-API-Key": apiKey };
|
|
8759
|
-
}
|
|
8760
|
-
function mcpRemoteServer(url, apiKey) {
|
|
8761
|
-
const env = {};
|
|
8762
|
-
const headerArgs = Object.entries(mcpHeaders(apiKey)).flatMap(([key, value]) => {
|
|
8763
|
-
const envKey = key.toUpperCase().replace(/[^A-Z0-9]/g, "_");
|
|
8764
|
-
env[envKey] = value;
|
|
8765
|
-
return ["--header", `${key}:\${${envKey}}`];
|
|
8766
|
-
});
|
|
8767
|
-
return {
|
|
8768
|
-
args: [
|
|
8769
|
-
"-y",
|
|
8770
|
-
`mcp-remote@${MCP_REMOTE_VERSION}`,
|
|
8771
|
-
url,
|
|
8772
|
-
...headerArgs,
|
|
8773
|
-
"--transport",
|
|
8774
|
-
"http-only"
|
|
8775
|
-
],
|
|
8776
|
-
env
|
|
8777
|
-
};
|
|
8778
|
-
}
|
|
8779
|
-
function loadJSONConfig(path3) {
|
|
8780
|
-
if (!existsSync6(path3))
|
|
8781
|
-
return {};
|
|
8782
|
-
let data = readFileSync7(path3, "utf-8").trim();
|
|
8783
|
-
if (!data)
|
|
8784
|
-
return {};
|
|
8785
|
-
if (path3.endsWith(".jsonc")) {
|
|
8786
|
-
data = stripJSONComments(data);
|
|
8787
|
-
}
|
|
8788
|
-
try {
|
|
8789
|
-
return JSON.parse(data);
|
|
8790
|
-
} catch {
|
|
8791
|
-
return {};
|
|
8792
|
-
}
|
|
8793
|
-
}
|
|
8794
|
-
function stripJSONComments(data) {
|
|
8795
|
-
const result = [];
|
|
8796
|
-
let i2 = 0;
|
|
8797
|
-
while (i2 < data.length) {
|
|
8798
|
-
if (data[i2] === '"') {
|
|
8799
|
-
result.push(data[i2]);
|
|
8800
|
-
i2++;
|
|
8801
|
-
while (i2 < data.length && data[i2] !== '"') {
|
|
8802
|
-
if (data[i2] === "\\") {
|
|
8803
|
-
result.push(data[i2]);
|
|
8804
|
-
i2++;
|
|
8805
|
-
if (i2 < data.length) {
|
|
8806
|
-
result.push(data[i2]);
|
|
8807
|
-
i2++;
|
|
8808
|
-
}
|
|
8809
|
-
continue;
|
|
8810
|
-
}
|
|
8811
|
-
result.push(data[i2]);
|
|
8812
|
-
i2++;
|
|
8813
|
-
}
|
|
8814
|
-
if (i2 < data.length) {
|
|
8815
|
-
result.push(data[i2]);
|
|
8816
|
-
i2++;
|
|
8817
|
-
}
|
|
8818
|
-
continue;
|
|
8819
|
-
}
|
|
8820
|
-
if (i2 + 1 < data.length && data[i2] === "/" && data[i2 + 1] === "/") {
|
|
8821
|
-
i2 += 2;
|
|
8822
|
-
while (i2 < data.length && data[i2] !== `
|
|
8823
|
-
`)
|
|
8824
|
-
i2++;
|
|
8825
|
-
continue;
|
|
8826
|
-
}
|
|
8827
|
-
if (i2 + 1 < data.length && data[i2] === "/" && data[i2 + 1] === "*") {
|
|
8828
|
-
i2 += 2;
|
|
8829
|
-
while (i2 + 1 < data.length && !(data[i2] === "*" && data[i2 + 1] === "/"))
|
|
8830
|
-
i2++;
|
|
8831
|
-
if (i2 + 1 < data.length)
|
|
8832
|
-
i2 += 2;
|
|
8833
|
-
continue;
|
|
8834
|
-
}
|
|
8835
|
-
result.push(data[i2]);
|
|
8836
|
-
i2++;
|
|
8837
|
-
}
|
|
8838
|
-
return result.join("");
|
|
8839
|
-
}
|
|
8840
|
-
function saveJSONConfig(path3, cfg) {
|
|
8841
|
-
writeSecureFile(path3, JSON.stringify(cfg, null, 2));
|
|
8842
|
-
}
|
|
8843
|
-
function writeSecureFile(path3, content) {
|
|
8844
|
-
const dir = dirname3(path3);
|
|
8845
|
-
if (!existsSync6(dir)) {
|
|
8846
|
-
mkdirSync5(dir, { recursive: true, mode: 448 });
|
|
8847
|
-
}
|
|
8848
|
-
writeFileAtomic.sync(path3, content, { mode: 384, chown: false });
|
|
8849
|
-
}
|
|
8850
|
-
function isJSONKeyConfigured(configPath, topLevelKey) {
|
|
8851
|
-
const cfg = loadJSONConfig(configPath);
|
|
8852
|
-
const section = cfg[topLevelKey];
|
|
8853
|
-
if (typeof section !== "object" || section === null)
|
|
8854
|
-
return false;
|
|
8855
|
-
return "dosu" in section;
|
|
8856
|
-
}
|
|
8857
|
-
function installJSONServer(configPath, topKey, server) {
|
|
8858
|
-
const jsonCfg = loadJSONConfig(configPath);
|
|
8859
|
-
let section = jsonCfg[topKey];
|
|
8860
|
-
if (typeof section !== "object" || section === null) {
|
|
8861
|
-
section = {};
|
|
8862
|
-
}
|
|
8863
|
-
section.dosu = server;
|
|
8864
|
-
jsonCfg[topKey] = section;
|
|
8865
|
-
saveJSONConfig(configPath, jsonCfg);
|
|
8866
|
-
}
|
|
8867
|
-
function removeJSONServer(configPath, topKey) {
|
|
8868
|
-
let jsonCfg;
|
|
8869
|
-
try {
|
|
8870
|
-
jsonCfg = loadJSONConfig(configPath);
|
|
8871
|
-
} catch {
|
|
8872
|
-
return;
|
|
8873
|
-
}
|
|
8874
|
-
const section = jsonCfg[topKey];
|
|
8875
|
-
if (typeof section === "object" && section !== null) {
|
|
8876
|
-
delete section.dosu;
|
|
8877
|
-
}
|
|
8878
|
-
saveJSONConfig(configPath, jsonCfg);
|
|
8879
|
-
}
|
|
8880
|
-
var import_write_file_atomic, writeFileAtomic, MCP_REMOTE_VERSION = "0.1.38";
|
|
8881
|
-
var init_config_helpers = __esm(() => {
|
|
8882
|
-
import_write_file_atomic = __toESM(require_lib(), 1);
|
|
8883
|
-
writeFileAtomic = import_write_file_atomic.default;
|
|
8884
|
-
});
|
|
8885
|
-
|
|
8886
|
-
// src/hooks/runtime.ts
|
|
8887
|
-
var exports_runtime = {};
|
|
8888
|
-
__export(exports_runtime, {
|
|
8889
|
-
resolveHookCommandPrefix: () => resolveHookCommandPrefix,
|
|
8890
|
-
materializedRuntimePath: () => materializedRuntimePath,
|
|
8891
|
-
dosuOnPath: () => dosuOnPath
|
|
8892
|
-
});
|
|
8893
|
-
import { spawnSync } from "node:child_process";
|
|
8894
|
-
import { copyFileSync, existsSync as existsSync7, mkdirSync as mkdirSync6 } from "node:fs";
|
|
8895
|
-
import { dirname as dirname4, join as join6 } from "node:path";
|
|
8896
|
-
function dosuOnPath() {
|
|
8897
|
-
const cmd = process.platform === "win32" ? "where" : "which";
|
|
8898
|
-
try {
|
|
8899
|
-
return spawnSync(cmd, ["dosu"], { stdio: "ignore" }).status === 0;
|
|
8900
|
-
} catch {
|
|
8901
|
-
return false;
|
|
8902
|
-
}
|
|
8903
|
-
}
|
|
8904
|
-
function materializedRuntimePath() {
|
|
8905
|
-
return join6(getConfigDir(), "bin", "dosu.js");
|
|
8906
|
-
}
|
|
8907
|
-
function currentBundlePath() {
|
|
8908
|
-
const entry = process.argv[1];
|
|
8909
|
-
if (!entry || !/\.(c|m)?js$/.test(entry) || !existsSync7(entry))
|
|
8910
|
-
return null;
|
|
8911
|
-
return entry;
|
|
8912
|
-
}
|
|
8913
|
-
function resolveHookCommandPrefix() {
|
|
8914
|
-
if (dosuOnPath())
|
|
8915
|
-
return "dosu";
|
|
8916
|
-
const bundle = currentBundlePath();
|
|
8917
|
-
if (!bundle) {
|
|
8918
|
-
logger.warn("hooks", "dosu not on PATH and no bundle to materialize; using bare 'dosu'");
|
|
8919
|
-
return "dosu";
|
|
8920
|
-
}
|
|
8921
|
-
const target = materializedRuntimePath();
|
|
8922
|
-
try {
|
|
8923
|
-
mkdirSync6(dirname4(target), { recursive: true, mode: 448 });
|
|
8924
|
-
copyFileSync(bundle, target);
|
|
8925
|
-
} catch (err) {
|
|
8926
|
-
logger.warn("hooks", `failed to materialize runtime: ${err instanceof Error ? err.message : String(err)}`);
|
|
8927
|
-
return "dosu";
|
|
8928
|
-
}
|
|
8929
|
-
logger.info("hooks", `materialized hook runtime at ${target}`);
|
|
8930
|
-
return `node "${target}"`;
|
|
8931
|
-
}
|
|
8932
|
-
var init_runtime = __esm(() => {
|
|
8933
|
-
init_config();
|
|
8934
|
-
init_logger();
|
|
8935
|
-
});
|
|
8936
|
-
|
|
8937
|
-
// src/hooks/claude-code.ts
|
|
8938
|
-
var exports_claude_code = {};
|
|
8939
|
-
__export(exports_claude_code, {
|
|
8940
|
-
removeClaudeHooks: () => removeClaudeHooks,
|
|
8941
|
-
isDosuGroup: () => isDosuGroup,
|
|
8942
|
-
installClaudeHooks: () => installClaudeHooks,
|
|
8943
|
-
inspectClaudeHooks: () => inspectClaudeHooks,
|
|
8944
|
-
hookCommand: () => hookCommand,
|
|
8945
|
-
claudeLocalSettingsPath: () => claudeLocalSettingsPath,
|
|
8946
|
-
DEFAULT_HOOK_EVENTS: () => DEFAULT_HOOK_EVENTS
|
|
8947
|
-
});
|
|
8948
|
-
import { existsSync as existsSync8, readFileSync as readFileSync8 } from "node:fs";
|
|
8949
|
-
import { join as join7 } from "node:path";
|
|
8950
|
-
function claudeLocalSettingsPath(dir) {
|
|
8951
|
-
return join7(dir, ".claude", "settings.local.json");
|
|
8952
|
-
}
|
|
8953
|
-
function hookCommand(subcommand, prefix = resolveHookCommandPrefix()) {
|
|
8954
|
-
return `${prefix} hooks ${subcommand}`;
|
|
8955
|
-
}
|
|
8956
|
-
function dosuGroup(event) {
|
|
8957
|
-
const entry = {
|
|
8958
|
-
type: "command",
|
|
8959
|
-
command: hookCommand(EVENT_SUBCOMMAND[event]),
|
|
8960
|
-
[MARKER]: true
|
|
8961
|
-
};
|
|
8962
|
-
return event === "PostToolUse" ? { matcher: "*", hooks: [entry] } : { hooks: [entry] };
|
|
8963
|
-
}
|
|
8964
|
-
function isDosuHookCommand(command) {
|
|
8965
|
-
if (!/\bhooks\b/.test(command))
|
|
8966
|
-
return false;
|
|
8967
|
-
if (/@dosu\/cli/.test(command))
|
|
8968
|
-
return true;
|
|
8969
|
-
if (/^([^\s]*[/\\])?node(\.exe)?\s+("[^"]*dosu\.(c|m)?js"|[^\s]*dosu\.(c|m)?js)\s/.test(command.trimStart())) {
|
|
8970
|
-
return true;
|
|
8971
|
-
}
|
|
8972
|
-
return /^([^\s]*\/)?dosu\s/.test(command.trimStart());
|
|
8973
|
-
}
|
|
8974
|
-
function isDosuGroup(group) {
|
|
8975
|
-
if (!group || typeof group !== "object")
|
|
8976
|
-
return false;
|
|
8977
|
-
const hooks = group.hooks;
|
|
8978
|
-
if (!Array.isArray(hooks))
|
|
8979
|
-
return false;
|
|
8980
|
-
return hooks.some((h2) => !!h2 && typeof h2 === "object" && (h2[MARKER] === true || typeof h2.command === "string" && isDosuHookCommand(h2.command)));
|
|
8981
|
-
}
|
|
8982
|
-
function readSettingsOrThrow(path3) {
|
|
8983
|
-
if (!existsSync8(path3))
|
|
8984
|
-
return {};
|
|
8985
|
-
const raw = readFileSync8(path3, "utf-8").trim();
|
|
8986
|
-
if (!raw)
|
|
8987
|
-
return {};
|
|
8988
|
-
try {
|
|
8989
|
-
return JSON.parse(raw);
|
|
8990
|
-
} catch {
|
|
8991
|
-
throw new Error(`refusing to modify ${path3}: file exists but is not valid JSON`);
|
|
8992
|
-
}
|
|
8993
|
-
}
|
|
8994
|
-
function installClaudeHooks(configPath, opts = {}) {
|
|
8995
|
-
const cfg = readSettingsOrThrow(configPath);
|
|
8996
|
-
if (typeof cfg.hooks !== "object" || cfg.hooks === null) {
|
|
8997
|
-
cfg.hooks = {};
|
|
8998
|
-
}
|
|
8999
|
-
const includeStop = opts.stop !== false;
|
|
9000
|
-
const events = DEFAULT_HOOK_EVENTS.filter((event) => event !== "Stop" || includeStop);
|
|
9001
|
-
for (const event of events) {
|
|
9002
|
-
const existing = Array.isArray(cfg.hooks[event]) ? cfg.hooks[event] : [];
|
|
9003
|
-
const preserved = existing.filter((g2) => !isDosuGroup(g2));
|
|
9004
|
-
preserved.push(dosuGroup(event));
|
|
9005
|
-
cfg.hooks[event] = preserved;
|
|
9006
|
-
}
|
|
9007
|
-
saveJSONConfig(configPath, cfg);
|
|
9008
|
-
return { events };
|
|
9009
|
-
}
|
|
9010
|
-
function removeClaudeHooks(configPath) {
|
|
9011
|
-
let cfg;
|
|
9012
|
-
try {
|
|
9013
|
-
cfg = readSettingsOrThrow(configPath);
|
|
9014
|
-
} catch {
|
|
9015
|
-
return { removed: false };
|
|
9016
|
-
}
|
|
9017
|
-
if (typeof cfg.hooks !== "object" || cfg.hooks === null) {
|
|
9018
|
-
return { removed: false };
|
|
9019
|
-
}
|
|
9020
|
-
let removed = false;
|
|
9021
|
-
for (const event of Object.keys(cfg.hooks)) {
|
|
9022
|
-
const arr = cfg.hooks[event];
|
|
9023
|
-
if (!Array.isArray(arr))
|
|
9024
|
-
continue;
|
|
9025
|
-
const preserved = arr.filter((g2) => {
|
|
9026
|
-
if (isDosuGroup(g2)) {
|
|
9027
|
-
removed = true;
|
|
9028
|
-
return false;
|
|
9029
|
-
}
|
|
9030
|
-
return true;
|
|
9031
|
-
});
|
|
9032
|
-
if (preserved.length === 0) {
|
|
9033
|
-
delete cfg.hooks[event];
|
|
9034
|
-
} else {
|
|
9035
|
-
cfg.hooks[event] = preserved;
|
|
9036
|
-
}
|
|
9037
|
-
}
|
|
9038
|
-
if (Object.keys(cfg.hooks).length === 0) {
|
|
9039
|
-
delete cfg.hooks;
|
|
9040
|
-
}
|
|
9041
|
-
saveJSONConfig(configPath, cfg);
|
|
9042
|
-
return { removed };
|
|
9043
|
-
}
|
|
9044
|
-
function inspectClaudeHooks(configPath) {
|
|
9045
|
-
if (!existsSync8(configPath))
|
|
9046
|
-
return { fileExists: false, parseError: false, events: [] };
|
|
9047
|
-
let cfg;
|
|
9048
|
-
try {
|
|
9049
|
-
cfg = readSettingsOrThrow(configPath);
|
|
9050
|
-
} catch {
|
|
9051
|
-
return { fileExists: true, parseError: true, events: [] };
|
|
9052
|
-
}
|
|
9053
|
-
const hooks = typeof cfg.hooks === "object" && cfg.hooks !== null ? cfg.hooks : {};
|
|
9054
|
-
const events = Object.keys(hooks).filter((event) => Array.isArray(hooks[event]) && hooks[event].some(isDosuGroup));
|
|
9055
|
-
return { fileExists: true, parseError: false, events };
|
|
9056
|
-
}
|
|
9057
|
-
var DEFAULT_HOOK_EVENTS, MARKER = "__dosu", EVENT_SUBCOMMAND;
|
|
9058
|
-
var init_claude_code = __esm(() => {
|
|
9059
|
-
init_config_helpers();
|
|
9060
|
-
init_runtime();
|
|
9061
|
-
DEFAULT_HOOK_EVENTS = ["UserPromptSubmit", "PostToolUse", "Stop"];
|
|
9062
|
-
EVENT_SUBCOMMAND = {
|
|
9063
|
-
UserPromptSubmit: "user-prompt-submit",
|
|
9064
|
-
PostToolUse: "post-tool-use",
|
|
9065
|
-
Stop: "stop"
|
|
9066
|
-
};
|
|
9067
|
-
});
|
|
9068
|
-
|
|
9069
|
-
// src/hooks/codex.ts
|
|
9070
|
-
var exports_codex = {};
|
|
9071
|
-
__export(exports_codex, {
|
|
9072
|
-
removeCodexHooks: () => removeCodexHooks,
|
|
9073
|
-
installCodexHooks: () => installCodexHooks,
|
|
9074
|
-
inspectCodexHooks: () => inspectCodexHooks,
|
|
9075
|
-
codexHooksPath: () => codexHooksPath
|
|
9076
|
-
});
|
|
9077
|
-
import { existsSync as existsSync9, readFileSync as readFileSync9 } from "node:fs";
|
|
9078
|
-
import { join as join8 } from "node:path";
|
|
9079
|
-
function codexHooksPath(dir) {
|
|
9080
|
-
return join8(dir, ".codex", "hooks.json");
|
|
9081
|
-
}
|
|
9082
|
-
function codexHookCommand(subcommand, prefix = resolveHookCommandPrefix()) {
|
|
9083
|
-
return `${prefix} hooks ${subcommand} --agent codex`;
|
|
9084
|
-
}
|
|
9085
|
-
function dosuGroup2(event) {
|
|
9086
|
-
return {
|
|
9087
|
-
hooks: [
|
|
9088
|
-
{
|
|
9089
|
-
type: "command",
|
|
9090
|
-
command: codexHookCommand(EVENT_SUBCOMMAND2[event]),
|
|
9091
|
-
timeout: EVENT_TIMEOUT_SEC[event],
|
|
9092
|
-
statusMessage: EVENT_STATUS_MESSAGE[event]
|
|
9093
|
-
}
|
|
9094
|
-
]
|
|
9095
|
-
};
|
|
9096
|
-
}
|
|
9097
|
-
function readHooksFileOrThrow(path3) {
|
|
9098
|
-
if (!existsSync9(path3))
|
|
9099
|
-
return {};
|
|
9100
|
-
const raw = readFileSync9(path3, "utf-8").trim();
|
|
9101
|
-
if (!raw)
|
|
9102
|
-
return {};
|
|
9103
|
-
try {
|
|
9104
|
-
return JSON.parse(raw);
|
|
9105
|
-
} catch {
|
|
9106
|
-
throw new Error(`refusing to modify ${path3}: file exists but is not valid JSON`);
|
|
9107
|
-
}
|
|
9108
|
-
}
|
|
9109
|
-
function installCodexHooks(configPath, opts = {}) {
|
|
9110
|
-
const config = readHooksFileOrThrow(configPath);
|
|
9111
|
-
const events = DEFAULT_HOOK_EVENTS.filter((e) => e !== "Stop" || opts.stop !== false);
|
|
9112
|
-
const hooks = typeof config.hooks === "object" && config.hooks ? config.hooks : {};
|
|
9113
|
-
for (const event of events) {
|
|
9114
|
-
const groups = Array.isArray(hooks[event]) ? hooks[event] : [];
|
|
9115
|
-
const kept = groups.filter((g2) => !isDosuGroup(g2));
|
|
9116
|
-
kept.push(dosuGroup2(event));
|
|
9117
|
-
hooks[event] = kept;
|
|
9118
|
-
}
|
|
9119
|
-
for (const event of DEFAULT_HOOK_EVENTS) {
|
|
9120
|
-
if (!events.includes(event) && Array.isArray(hooks[event])) {
|
|
9121
|
-
const kept = hooks[event].filter((g2) => !isDosuGroup(g2));
|
|
9122
|
-
if (kept.length > 0)
|
|
9123
|
-
hooks[event] = kept;
|
|
9124
|
-
else
|
|
9125
|
-
delete hooks[event];
|
|
9126
|
-
}
|
|
9127
|
-
}
|
|
9128
|
-
config.hooks = hooks;
|
|
9129
|
-
saveJSONConfig(configPath, config);
|
|
9130
|
-
return { events: [...events] };
|
|
9131
|
-
}
|
|
9132
|
-
function removeCodexHooks(configPath) {
|
|
9133
|
-
if (!existsSync9(configPath))
|
|
9134
|
-
return { removed: false };
|
|
9135
|
-
let config;
|
|
9136
|
-
try {
|
|
9137
|
-
config = readHooksFileOrThrow(configPath);
|
|
9138
|
-
} catch {
|
|
9139
|
-
return { removed: false };
|
|
9140
|
-
}
|
|
9141
|
-
const hooks = config.hooks;
|
|
9142
|
-
if (typeof hooks !== "object" || !hooks)
|
|
9143
|
-
return { removed: false };
|
|
9144
|
-
let removed = false;
|
|
9145
|
-
for (const event of Object.keys(hooks)) {
|
|
9146
|
-
if (!Array.isArray(hooks[event]))
|
|
9147
|
-
continue;
|
|
9148
|
-
const kept = hooks[event].filter((g2) => !isDosuGroup(g2));
|
|
9149
|
-
if (kept.length !== hooks[event].length) {
|
|
9150
|
-
removed = true;
|
|
9151
|
-
if (kept.length > 0)
|
|
9152
|
-
hooks[event] = kept;
|
|
9153
|
-
else
|
|
9154
|
-
delete hooks[event];
|
|
9155
|
-
}
|
|
9156
|
-
}
|
|
9157
|
-
if (removed)
|
|
9158
|
-
saveJSONConfig(configPath, config);
|
|
9159
|
-
return { removed };
|
|
9160
|
-
}
|
|
9161
|
-
function inspectCodexHooks(configPath) {
|
|
9162
|
-
if (!existsSync9(configPath))
|
|
9163
|
-
return { fileExists: false, events: [] };
|
|
9164
|
-
let config;
|
|
9165
|
-
try {
|
|
9166
|
-
config = readHooksFileOrThrow(configPath);
|
|
9167
|
-
} catch {
|
|
9168
|
-
return { fileExists: true, parseError: true, events: [] };
|
|
9169
|
-
}
|
|
9170
|
-
const hooks = config.hooks;
|
|
9171
|
-
if (typeof hooks !== "object" || !hooks)
|
|
9172
|
-
return { fileExists: true, events: [] };
|
|
9173
|
-
const events = Object.keys(hooks).filter((event) => Array.isArray(hooks[event]) && hooks[event].some((g2) => isDosuGroup(g2)));
|
|
9174
|
-
return { fileExists: true, events };
|
|
9175
|
-
}
|
|
9176
|
-
var EVENT_SUBCOMMAND2, EVENT_TIMEOUT_SEC, EVENT_STATUS_MESSAGE;
|
|
9177
|
-
var init_codex = __esm(() => {
|
|
9178
|
-
init_config_helpers();
|
|
9179
|
-
init_claude_code();
|
|
9180
|
-
init_runtime();
|
|
9181
|
-
EVENT_SUBCOMMAND2 = {
|
|
9182
|
-
UserPromptSubmit: "user-prompt-submit",
|
|
9183
|
-
PostToolUse: "post-tool-use",
|
|
9184
|
-
Stop: "stop"
|
|
9185
|
-
};
|
|
9186
|
-
EVENT_TIMEOUT_SEC = {
|
|
9187
|
-
UserPromptSubmit: 10,
|
|
9188
|
-
PostToolUse: 10,
|
|
9189
|
-
Stop: 30
|
|
9190
|
-
};
|
|
9191
|
-
EVENT_STATUS_MESSAGE = {
|
|
9192
|
-
UserPromptSubmit: "Dosu knowledge lookup",
|
|
9193
|
-
PostToolUse: "Dosu knowledge delivery",
|
|
9194
|
-
Stop: "Dosu final knowledge check"
|
|
9195
|
-
};
|
|
9196
|
-
});
|
|
9197
|
-
|
|
9198
|
-
// src/hooks/factory.ts
|
|
9199
|
-
var exports_factory = {};
|
|
9200
|
-
__export(exports_factory, {
|
|
9201
|
-
removeFactoryHooks: () => removeFactoryHooks,
|
|
9202
|
-
installFactoryHooks: () => installFactoryHooks,
|
|
9203
|
-
inspectFactoryHooks: () => inspectFactoryHooks,
|
|
9204
|
-
factoryHooksPath: () => factoryHooksPath
|
|
9205
|
-
});
|
|
9206
|
-
import { existsSync as existsSync10, readFileSync as readFileSync10 } from "node:fs";
|
|
9207
|
-
import { join as join9 } from "node:path";
|
|
9208
|
-
function factoryHooksPath(dir) {
|
|
9209
|
-
return join9(dir, ".factory", "hooks.json");
|
|
9210
|
-
}
|
|
9211
|
-
function factoryHookCommand(subcommand, prefix = resolveHookCommandPrefix()) {
|
|
9212
|
-
return `${prefix} hooks ${subcommand} --agent factory`;
|
|
9213
|
-
}
|
|
9214
|
-
function dosuGroup3(event) {
|
|
9215
|
-
const group = {
|
|
9216
|
-
hooks: [{ type: "command", command: factoryHookCommand(EVENT_SUBCOMMAND3[event]) }]
|
|
9217
|
-
};
|
|
9218
|
-
if (event === "PostToolUse")
|
|
9219
|
-
group.matcher = "*";
|
|
9220
|
-
return group;
|
|
9221
|
-
}
|
|
9222
|
-
function readHooksFileOrThrow2(path3) {
|
|
9223
|
-
if (!existsSync10(path3))
|
|
9224
|
-
return {};
|
|
9225
|
-
const raw = readFileSync10(path3, "utf-8").trim();
|
|
9226
|
-
if (!raw)
|
|
9227
|
-
return {};
|
|
9228
|
-
try {
|
|
9229
|
-
return JSON.parse(raw);
|
|
9230
|
-
} catch {
|
|
9231
|
-
throw new Error(`refusing to modify ${path3}: file exists but is not valid JSON`);
|
|
9232
|
-
}
|
|
9233
|
-
}
|
|
9234
|
-
function installFactoryHooks(configPath, opts = {}) {
|
|
9235
|
-
const config = readHooksFileOrThrow2(configPath);
|
|
9236
|
-
const events = DEFAULT_HOOK_EVENTS.filter((e) => e !== "Stop" || opts.stop !== false);
|
|
9237
|
-
const hooks = typeof config.hooks === "object" && config.hooks ? config.hooks : {};
|
|
9238
|
-
for (const event of events) {
|
|
9239
|
-
const groups = Array.isArray(hooks[event]) ? hooks[event] : [];
|
|
9240
|
-
const kept = groups.filter((g2) => !isDosuGroup(g2));
|
|
9241
|
-
kept.push(dosuGroup3(event));
|
|
9242
|
-
hooks[event] = kept;
|
|
9243
|
-
}
|
|
9244
|
-
for (const event of DEFAULT_HOOK_EVENTS) {
|
|
9245
|
-
if (!events.includes(event) && Array.isArray(hooks[event])) {
|
|
9246
|
-
const kept = hooks[event].filter((g2) => !isDosuGroup(g2));
|
|
9247
|
-
if (kept.length > 0)
|
|
9248
|
-
hooks[event] = kept;
|
|
9249
|
-
else
|
|
9250
|
-
delete hooks[event];
|
|
9251
|
-
}
|
|
9252
|
-
}
|
|
9253
|
-
config.hooks = hooks;
|
|
9254
|
-
saveJSONConfig(configPath, config);
|
|
9255
|
-
return { events: [...events] };
|
|
9256
|
-
}
|
|
9257
|
-
function removeFactoryHooks(configPath) {
|
|
9258
|
-
if (!existsSync10(configPath))
|
|
9259
|
-
return { removed: false };
|
|
9260
|
-
let config;
|
|
9261
|
-
try {
|
|
9262
|
-
config = readHooksFileOrThrow2(configPath);
|
|
9263
|
-
} catch {
|
|
9264
|
-
return { removed: false };
|
|
9265
|
-
}
|
|
9266
|
-
const hooks = config.hooks;
|
|
9267
|
-
if (typeof hooks !== "object" || !hooks)
|
|
9268
|
-
return { removed: false };
|
|
9269
|
-
let removed = false;
|
|
9270
|
-
for (const event of Object.keys(hooks)) {
|
|
9271
|
-
if (!Array.isArray(hooks[event]))
|
|
9272
|
-
continue;
|
|
9273
|
-
const kept = hooks[event].filter((g2) => !isDosuGroup(g2));
|
|
9274
|
-
if (kept.length !== hooks[event].length) {
|
|
9275
|
-
removed = true;
|
|
9276
|
-
if (kept.length > 0)
|
|
9277
|
-
hooks[event] = kept;
|
|
9278
|
-
else
|
|
9279
|
-
delete hooks[event];
|
|
9280
|
-
}
|
|
9281
|
-
}
|
|
9282
|
-
if (removed)
|
|
9283
|
-
saveJSONConfig(configPath, config);
|
|
9284
|
-
return { removed };
|
|
9285
|
-
}
|
|
9286
|
-
function inspectFactoryHooks(configPath) {
|
|
9287
|
-
if (!existsSync10(configPath))
|
|
9288
|
-
return { fileExists: false, events: [] };
|
|
9289
|
-
let config;
|
|
9290
|
-
try {
|
|
9291
|
-
config = readHooksFileOrThrow2(configPath);
|
|
9292
|
-
} catch {
|
|
9293
|
-
return { fileExists: true, parseError: true, events: [] };
|
|
9294
|
-
}
|
|
9295
|
-
const hooks = config.hooks;
|
|
9296
|
-
if (typeof hooks !== "object" || !hooks)
|
|
9297
|
-
return { fileExists: true, events: [] };
|
|
9298
|
-
const events = Object.keys(hooks).filter((event) => Array.isArray(hooks[event]) && hooks[event].some((g2) => isDosuGroup(g2)));
|
|
9299
|
-
return { fileExists: true, events };
|
|
9300
|
-
}
|
|
9301
|
-
var EVENT_SUBCOMMAND3;
|
|
9302
|
-
var init_factory = __esm(() => {
|
|
9303
|
-
init_config_helpers();
|
|
9304
|
-
init_claude_code();
|
|
9305
|
-
init_runtime();
|
|
9306
|
-
EVENT_SUBCOMMAND3 = {
|
|
9307
|
-
UserPromptSubmit: "user-prompt-submit",
|
|
9308
|
-
PostToolUse: "post-tool-use",
|
|
9309
|
-
Stop: "stop"
|
|
9310
|
-
};
|
|
9311
|
-
});
|
|
9312
|
-
|
|
9313
|
-
// src/insights/insights.ts
|
|
9314
|
-
async function buildInsights({
|
|
9315
|
-
client,
|
|
9316
|
-
cfg,
|
|
9317
|
-
ask,
|
|
9318
|
-
windowDays = 30,
|
|
9319
|
-
now = () => new Date,
|
|
9320
|
-
onProgress
|
|
9321
|
-
}) {
|
|
9322
|
-
if (!cfg.active_account?.target?.space_id) {
|
|
9323
|
-
throw new Error("space_id missing. Run 'dosu setup' first");
|
|
9324
|
-
}
|
|
9325
|
-
const spaceId = cfg.active_account?.target?.space_id;
|
|
9326
|
-
onProgress?.("stats");
|
|
9327
|
-
const [currentRaw, combinedRaw] = await Promise.all([
|
|
9328
|
-
client.analytics.getUsageStats.query({ spaceId, days: windowDays }),
|
|
9329
|
-
client.analytics.getUsageStats.query({ spaceId, days: windowDays * 2 })
|
|
9330
|
-
]);
|
|
9331
|
-
const current = normalizeStats(currentRaw);
|
|
9332
|
-
const combined = normalizeStats(combinedRaw);
|
|
9333
|
-
const previous = subtractStats(combined, current);
|
|
9334
|
-
const derived = computeDerived(current, previous);
|
|
9335
|
-
const cheers = computeCheers(current, derived);
|
|
9336
|
-
const investigate = computeInvestigate(current, previous, derived);
|
|
9337
|
-
const suggestions = computeSuggestions(current, previous, derived);
|
|
9338
|
-
onProgress?.("narrative");
|
|
9339
|
-
const atAGlanceFromAsk = await ask(buildAtAGlancePrompt(current, derived, windowDays));
|
|
9340
|
-
const atAGlance = atAGlanceFromAsk ?? fallbackAtAGlance(current, derived, windowDays);
|
|
9341
|
-
return {
|
|
9342
|
-
generatedAt: now().toISOString(),
|
|
9343
|
-
windowDays,
|
|
9344
|
-
spaceName: cfg.active_account?.target?.deployment_name ?? "your space",
|
|
9345
|
-
current,
|
|
9346
|
-
previous,
|
|
9347
|
-
derived,
|
|
9348
|
-
atAGlance,
|
|
9349
|
-
cheers,
|
|
9350
|
-
investigate,
|
|
9351
|
-
suggestions
|
|
9352
|
-
};
|
|
9353
|
-
}
|
|
9354
|
-
function normalizeStats(raw) {
|
|
9355
|
-
if (!raw)
|
|
9356
|
-
return { ...EMPTY_STATS };
|
|
9357
|
-
const totalResponses = raw.totalResponses ?? 0;
|
|
9358
|
-
const reactionMsgs = raw.reactions?.messagesWithReactions ?? 0;
|
|
9359
|
-
return {
|
|
9360
|
-
totalResponses,
|
|
9361
|
-
byConfidence: {
|
|
9362
|
-
high: raw.byConfidence?.high ?? 0,
|
|
9363
|
-
medium: raw.byConfidence?.medium ?? 0,
|
|
9364
|
-
low: raw.byConfidence?.low ?? 0
|
|
9365
|
-
},
|
|
9366
|
-
reactions: {
|
|
9367
|
-
totalPositive: raw.reactions?.totalPositive ?? 0,
|
|
9368
|
-
totalNegative: raw.reactions?.totalNegative ?? 0,
|
|
9369
|
-
messagesWithReactions: reactionMsgs,
|
|
9370
|
-
reactionRate: raw.reactions?.reactionRate ?? (totalResponses > 0 ? reactionMsgs / totalResponses : 0),
|
|
9371
|
-
positiveRate: raw.reactions?.positiveRate ?? 0
|
|
9372
|
-
}
|
|
9373
|
-
};
|
|
9374
|
-
}
|
|
9375
|
-
function subtractStats(combined, current) {
|
|
9376
|
-
const totalResponses = Math.max(0, combined.totalResponses - current.totalResponses);
|
|
9377
|
-
const positive = Math.max(0, combined.reactions.totalPositive - current.reactions.totalPositive);
|
|
9378
|
-
const negative = Math.max(0, combined.reactions.totalNegative - current.reactions.totalNegative);
|
|
9379
|
-
const reactionMsgs = Math.max(0, combined.reactions.messagesWithReactions - current.reactions.messagesWithReactions);
|
|
9380
|
-
const totalReactions = positive + negative;
|
|
9381
|
-
return {
|
|
9382
|
-
totalResponses,
|
|
9383
|
-
byConfidence: {
|
|
9384
|
-
high: Math.max(0, combined.byConfidence.high - current.byConfidence.high),
|
|
9385
|
-
medium: Math.max(0, combined.byConfidence.medium - current.byConfidence.medium),
|
|
9386
|
-
low: Math.max(0, combined.byConfidence.low - current.byConfidence.low)
|
|
9387
|
-
},
|
|
9388
|
-
reactions: {
|
|
9389
|
-
totalPositive: positive,
|
|
9390
|
-
totalNegative: negative,
|
|
9391
|
-
messagesWithReactions: reactionMsgs,
|
|
9392
|
-
reactionRate: totalResponses > 0 ? reactionMsgs / totalResponses : 0,
|
|
9393
|
-
positiveRate: totalReactions > 0 ? positive / totalReactions : 0
|
|
9394
|
-
}
|
|
9395
|
-
};
|
|
9396
|
-
}
|
|
9397
|
-
function computeDerived(current, previous) {
|
|
9398
|
-
const highConfidenceRate = current.totalResponses > 0 ? current.byConfidence.high / current.totalResponses : null;
|
|
9399
|
-
const prevHighConfidenceRate = previous.totalResponses > 0 ? previous.byConfidence.high / previous.totalResponses : null;
|
|
9400
|
-
const highConfidenceRateDelta = highConfidenceRate !== null && prevHighConfidenceRate !== null ? highConfidenceRate - prevHighConfidenceRate : null;
|
|
9401
|
-
const positiveRateDelta = current.reactions.totalPositive + current.reactions.totalNegative > 0 && previous.reactions.totalPositive + previous.reactions.totalNegative > 0 ? current.reactions.positiveRate - previous.reactions.positiveRate : null;
|
|
9402
|
-
return {
|
|
9403
|
-
highConfidenceRate,
|
|
9404
|
-
highConfidenceRateDelta,
|
|
9405
|
-
responsesDelta: current.totalResponses - previous.totalResponses,
|
|
9406
|
-
positiveRateDelta,
|
|
9407
|
-
hasPriorWindow: previous.totalResponses > 0
|
|
9408
|
-
};
|
|
9409
|
-
}
|
|
9410
|
-
function computeCheers(stats, derived) {
|
|
9411
|
-
const out = [];
|
|
9412
|
-
if (stats.totalResponses === 0) {
|
|
9413
|
-
out.push("Your space is brand new and ready to roll. Share `dosu setup` with your team to start asking questions.");
|
|
9414
|
-
return out;
|
|
9415
|
-
}
|
|
9416
|
-
if (derived.highConfidenceRate !== null && derived.highConfidenceRate >= 0.8) {
|
|
9417
|
-
out.push(`${pct(derived.highConfidenceRate)} of responses came back high-confidence. Your sources are covering most of what your team's asking about.`);
|
|
9418
|
-
}
|
|
9419
|
-
if (stats.byConfidence.high > stats.byConfidence.low * 2 && stats.byConfidence.high > 0) {
|
|
9420
|
-
out.push(`${stats.byConfidence.high} high-confidence answers vs ${stats.byConfidence.low} low. Most questions have a clear source to point to.`);
|
|
9421
|
-
}
|
|
9422
|
-
if (stats.reactions.totalPositive + stats.reactions.totalNegative > 0 && stats.reactions.positiveRate >= 0.8) {
|
|
9423
|
-
out.push(`${pct(stats.reactions.positiveRate)} of reactions were positive. The answers your team's engaging with are landing.`);
|
|
9424
|
-
}
|
|
9425
|
-
if (derived.hasPriorWindow && derived.responsesDelta > 0) {
|
|
9426
|
-
out.push(`Volume is up ${derived.responsesDelta} responses vs the prior window. Your team is leaning on Dosu more.`);
|
|
9427
|
-
}
|
|
9428
|
-
if (out.length === 0) {
|
|
9429
|
-
out.push(`${stats.totalResponses} responses logged this window. Every one is a chance to learn what your team needs.`);
|
|
9430
|
-
}
|
|
9431
|
-
return out;
|
|
9432
|
-
}
|
|
9433
|
-
function computeInvestigate(current, previous, derived) {
|
|
9434
|
-
const out = [];
|
|
9435
|
-
if (current.totalResponses === 0)
|
|
9436
|
-
return out;
|
|
9437
|
-
if (derived.highConfidenceRateDelta !== null && derived.highConfidenceRateDelta <= -0.05 && previous.totalResponses > 0) {
|
|
9438
|
-
const before = pct(previous.byConfidence.high / previous.totalResponses);
|
|
9439
|
-
const now = pct(current.byConfidence.high / current.totalResponses);
|
|
9440
|
-
out.push(`High-confidence share dropped from ${before} to ${now}. Usually a sign of a new product area Dosu doesn't have docs for yet.`);
|
|
9441
|
-
}
|
|
9442
|
-
const lowDelta = current.byConfidence.low - previous.byConfidence.low;
|
|
9443
|
-
if (derived.hasPriorWindow && lowDelta >= 5 && current.byConfidence.low > 0) {
|
|
9444
|
-
out.push(`Low-confidence answers grew by ${lowDelta} (now ${current.byConfidence.low} this window). Each one is a hint that your knowledge base has gaps.`);
|
|
9445
|
-
}
|
|
9446
|
-
if (derived.positiveRateDelta !== null && derived.positiveRateDelta <= -0.05) {
|
|
9447
|
-
const before = pct(previous.reactions.positiveRate);
|
|
9448
|
-
const now = pct(current.reactions.positiveRate);
|
|
9449
|
-
out.push(`Positive feedback fell from ${before} to ${now}. The negative reactions are the most actionable signal. Open them first.`);
|
|
9450
|
-
}
|
|
9451
|
-
if (current.reactions.totalNegative > current.reactions.totalPositive && current.reactions.totalNegative >= 3) {
|
|
9452
|
-
out.push(`${current.reactions.totalNegative} negative reactions vs ${current.reactions.totalPositive} positive. Worth a look. What changed?`);
|
|
9453
|
-
}
|
|
9454
|
-
if (derived.hasPriorWindow && derived.responsesDelta <= -10) {
|
|
9455
|
-
out.push(`Volume is down by ${Math.abs(derived.responsesDelta)} responses. If your team stopped asking, find out why.`);
|
|
9456
|
-
}
|
|
9457
|
-
if (derived.highConfidenceRate !== null && derived.highConfidenceRate < 0.5 && current.totalResponses >= 5) {
|
|
9458
|
-
const lowAndMed = current.byConfidence.low + current.byConfidence.medium;
|
|
9459
|
-
out.push(`${lowAndMed} of ${current.totalResponses} responses were medium- or low-confidence. Open the threads to see where the knowledge base needs more material.`);
|
|
9460
|
-
}
|
|
9461
|
-
return out;
|
|
9462
|
-
}
|
|
9463
|
-
function computeSuggestions(current, previous, derived) {
|
|
9464
|
-
const out = [];
|
|
9465
|
-
if (current.totalResponses === 0) {
|
|
9466
|
-
out.push({
|
|
9467
|
-
headline: "Get your team using Dosu",
|
|
9468
|
-
detail: "No responses logged yet. The fastest unlock is wiring Dosu into the tools your team already uses. Ask Dosu in Slack, your editor, or anywhere else.",
|
|
9469
|
-
command: "dosu setup"
|
|
9470
|
-
});
|
|
9471
|
-
out.push({
|
|
9472
|
-
headline: "Try a question yourself",
|
|
9473
|
-
detail: "See what Dosu does without configuring anything else. Replace the example with whatever you're curious about.",
|
|
9474
|
-
command: 'dosu ask "what is Dosu?"'
|
|
9475
|
-
});
|
|
9476
|
-
return out;
|
|
9477
|
-
}
|
|
9478
|
-
const lowDelta = current.byConfidence.low - previous.byConfidence.low;
|
|
9479
|
-
const lowGrewSignificantly = derived.hasPriorWindow && lowDelta >= 5;
|
|
9480
|
-
const lowHighInAbsoluteTerms = current.byConfidence.low >= Math.max(5, current.totalResponses * 0.3);
|
|
9481
|
-
if (lowGrewSignificantly || lowHighInAbsoluteTerms) {
|
|
9482
|
-
out.push({
|
|
9483
|
-
headline: "Audit recent low-confidence answers",
|
|
9484
|
-
detail: `${current.byConfidence.low} answers had low confidence this window${lowGrewSignificantly ? ` (+${lowDelta} vs prior)` : ""}. Open them to see exactly what knowledge is missing.`,
|
|
9485
|
-
command: "dosu threads list"
|
|
9486
|
-
});
|
|
9487
|
-
}
|
|
9488
|
-
if (derived.positiveRateDelta !== null && derived.positiveRateDelta <= -0.05) {
|
|
9489
|
-
out.push({
|
|
9490
|
-
headline: "Refresh your sources",
|
|
9491
|
-
detail: "Positive feedback is trending down. Usually a stale doc, a moved page, or a new product area Dosu hasn't seen.",
|
|
9492
|
-
command: "dosu sources list"
|
|
9493
|
-
});
|
|
9494
|
-
}
|
|
9495
|
-
if (current.totalResponses >= 50 && derived.highConfidenceRate !== null && derived.highConfidenceRate >= 0.7 && out.length === 0) {
|
|
9496
|
-
out.push({
|
|
9497
|
-
headline: "Share the win with your team",
|
|
9498
|
-
detail: `${current.byConfidence.high} of ${current.totalResponses} responses landed with high confidence (${pct(derived.highConfidenceRate)}). People are getting unblocked. Make sure your team knows it's working.`
|
|
9499
|
-
});
|
|
9500
|
-
}
|
|
9501
|
-
if (derived.hasPriorWindow && derived.responsesDelta >= 10) {
|
|
9502
|
-
out.push({
|
|
9503
|
-
headline: "See what your team is asking",
|
|
9504
|
-
detail: `Volume is up by ${derived.responsesDelta} responses vs the prior window. Browsing recent threads is the fastest way to spot patterns and gaps.`,
|
|
9505
|
-
command: "dosu threads list"
|
|
9506
|
-
});
|
|
9507
|
-
}
|
|
9508
|
-
if (out.length === 0) {
|
|
9509
|
-
out.push({
|
|
9510
|
-
headline: "Find your knowledge gaps",
|
|
9511
|
-
detail: "Dosu surfaces document suggestions from real questions. A quick scan often reveals what's worth writing next.",
|
|
9512
|
-
command: "dosu suggest list"
|
|
9513
|
-
});
|
|
9514
|
-
}
|
|
9515
|
-
const seen = new Set;
|
|
9516
|
-
const deduped = out.filter((s) => {
|
|
9517
|
-
if (!s.command)
|
|
9518
|
-
return true;
|
|
9519
|
-
if (seen.has(s.command))
|
|
9520
|
-
return false;
|
|
9521
|
-
seen.add(s.command);
|
|
9522
|
-
return true;
|
|
9523
|
-
});
|
|
9524
|
-
return deduped.slice(0, 4);
|
|
9525
|
-
}
|
|
9526
|
-
function pct(v) {
|
|
9527
|
-
return `${(v * 100).toFixed(0)}%`;
|
|
9528
|
-
}
|
|
9529
|
-
function statsBlock(stats, derived) {
|
|
9530
|
-
const hc = derived.highConfidenceRate !== null ? pct(derived.highConfidenceRate) : "n/a";
|
|
9531
|
-
const pr = stats.reactions.totalPositive + stats.reactions.totalNegative > 0 ? pct(stats.reactions.positiveRate) : "n/a";
|
|
9532
|
-
return [
|
|
9533
|
-
`- ${stats.totalResponses} total responses`,
|
|
9534
|
-
`- Confidence: ${stats.byConfidence.high} high, ${stats.byConfidence.medium} medium, ${stats.byConfidence.low} low (${hc} high-confidence share)`,
|
|
9535
|
-
`- Reactions: ${stats.reactions.totalPositive} positive, ${stats.reactions.totalNegative} negative (${pr} positive rate)`
|
|
9536
|
-
].join(`
|
|
9537
|
-
`);
|
|
9538
|
-
}
|
|
9539
|
-
function buildAtAGlancePrompt(stats, derived, days) {
|
|
9540
|
-
const priorNote = derived.hasPriorWindow ? "" : `
|
|
9541
|
-
|
|
9542
|
-
Note: this is the space's first ${days}-day window, so do NOT compare to a prior period. Focus on what's present.`;
|
|
9543
|
-
return `You're writing the "At a Glance" panel for a Dosu space insights report. Here are the real stats from the last ${days} days:
|
|
9544
|
-
|
|
9545
|
-
${statsBlock(stats, derived)}${priorNote}
|
|
9546
|
-
|
|
9547
|
-
Write 2-3 short sentences (no bullets, no headers, no markdown, no em dashes) that synthesize what's interesting or worth celebrating. Be warm, specific, and a little fun. Don't just list the numbers; interpret them. Speak directly to the reader ("you" / "your team").`;
|
|
9548
|
-
}
|
|
9549
|
-
function fallbackAtAGlance(stats, derived, days) {
|
|
9550
|
-
if (stats.totalResponses === 0) {
|
|
9551
|
-
return `Your space is brand new. The next ${days} days will give us something to talk about, so let's see what your team asks first.`;
|
|
9552
|
-
}
|
|
9553
|
-
const hc = derived.highConfidenceRate !== null ? pct(derived.highConfidenceRate) : "n/a";
|
|
9554
|
-
const reactions = stats.reactions.totalPositive + stats.reactions.totalNegative;
|
|
9555
|
-
const pr = reactions > 0 ? `, with ${pct(stats.reactions.positiveRate)} positive feedback` : "";
|
|
9556
|
-
if (!derived.hasPriorWindow) {
|
|
9557
|
-
return `In your first ${days}-day window you logged ${stats.totalResponses} responses, ${hc} of them high-confidence${pr}. Check back after another ${days} days for trend comparisons.`;
|
|
9558
|
-
}
|
|
9559
|
-
const trend = derived.responsesDelta > 0 ? ` Volume is up ${derived.responsesDelta} vs the prior ${days} days. The team is leaning on Dosu more.` : derived.responsesDelta < 0 ? ` Volume is down ${Math.abs(derived.responsesDelta)} vs the prior ${days} days. Worth a quick look.` : "";
|
|
9560
|
-
return `In the last ${days} days you logged ${stats.totalResponses} responses, ${hc} of them high-confidence${pr}.${trend}`;
|
|
9561
|
-
}
|
|
9562
|
-
var EMPTY_STATS;
|
|
9563
|
-
var init_insights = __esm(() => {
|
|
9564
|
-
EMPTY_STATS = {
|
|
9565
|
-
totalResponses: 0,
|
|
9566
|
-
byConfidence: { high: 0, medium: 0, low: 0 },
|
|
9567
|
-
reactions: {
|
|
9568
|
-
totalPositive: 0,
|
|
9569
|
-
totalNegative: 0,
|
|
9570
|
-
messagesWithReactions: 0,
|
|
9571
|
-
reactionRate: 0,
|
|
9572
|
-
positiveRate: 0
|
|
9573
|
-
}
|
|
9574
|
-
};
|
|
9575
|
-
});
|
|
9576
|
-
|
|
9577
|
-
// src/insights/render-html.ts
|
|
9578
|
-
function renderHTML(report) {
|
|
9579
|
-
const safeName = escapeHTML(report.spaceName);
|
|
9580
|
-
const generated = formatDate2(report.generatedAt);
|
|
9581
|
-
const greeting = pickGreeting(report.generatedAt);
|
|
9582
|
-
return `<!DOCTYPE html>
|
|
9583
|
-
<html lang="en">
|
|
9584
|
-
<head>
|
|
9585
|
-
<meta charset="utf-8">
|
|
9586
|
-
<title>Dosu Insights · ${safeName}</title>
|
|
9587
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
9588
|
-
<style>${CSS}</style>
|
|
9589
|
-
</head>
|
|
9590
|
-
<body>
|
|
9591
|
-
<main class="container">
|
|
9592
|
-
<header class="hero">
|
|
9593
|
-
<div class="brand">
|
|
9594
|
-
<span class="prompt">❯</span> dosu insights<span class="cursor">▌</span>
|
|
9595
|
-
</div>
|
|
9596
|
-
<h1>${safeName}</h1>
|
|
9597
|
-
<p class="subtitle">${greeting} · last ${report.windowDays} days · generated ${generated}</p>
|
|
9598
|
-
</header>
|
|
8419
|
+
// src/insights/render-html.ts
|
|
8420
|
+
function renderHTML(report) {
|
|
8421
|
+
const safeName = escapeHTML(report.spaceName);
|
|
8422
|
+
const generated = formatDate2(report.generatedAt);
|
|
8423
|
+
const greeting = pickGreeting(report.generatedAt);
|
|
8424
|
+
return `<!DOCTYPE html>
|
|
8425
|
+
<html lang="en">
|
|
8426
|
+
<head>
|
|
8427
|
+
<meta charset="utf-8">
|
|
8428
|
+
<title>Dosu Insights · ${safeName}</title>
|
|
8429
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
8430
|
+
<style>${CSS}</style>
|
|
8431
|
+
</head>
|
|
8432
|
+
<body>
|
|
8433
|
+
<main class="container">
|
|
8434
|
+
<header class="hero">
|
|
8435
|
+
<div class="brand">
|
|
8436
|
+
<span class="prompt">❯</span> dosu insights<span class="cursor">▌</span>
|
|
8437
|
+
</div>
|
|
8438
|
+
<h1>${safeName}</h1>
|
|
8439
|
+
<p class="subtitle">${greeting} · last ${report.windowDays} days · generated ${generated}</p>
|
|
8440
|
+
</header>
|
|
9599
8441
|
|
|
9600
8442
|
${renderHeadline(report)}
|
|
9601
8443
|
${renderAtAGlance(report)}
|
|
@@ -10339,348 +9181,958 @@ section { margin-top: 36px; }
|
|
|
10339
9181
|
margin-top: 44px;
|
|
10340
9182
|
text-align: center;
|
|
10341
9183
|
}
|
|
10342
|
-
.fun-headline { font-size: 18px; font-weight: 700; color: #78350f; margin-bottom: 6px; }
|
|
10343
|
-
.fun-detail { font-size: 14px; color: #92400e; }
|
|
10344
|
-
.fun-detail code {
|
|
10345
|
-
background: rgba(255,255,255,0.7);
|
|
10346
|
-
padding: 2px 8px;
|
|
10347
|
-
border-radius: 4px;
|
|
10348
|
-
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
10349
|
-
font-size: 13px;
|
|
9184
|
+
.fun-headline { font-size: 18px; font-weight: 700; color: #78350f; margin-bottom: 6px; }
|
|
9185
|
+
.fun-detail { font-size: 14px; color: #92400e; }
|
|
9186
|
+
.fun-detail code {
|
|
9187
|
+
background: rgba(255,255,255,0.7);
|
|
9188
|
+
padding: 2px 8px;
|
|
9189
|
+
border-radius: 4px;
|
|
9190
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
9191
|
+
font-size: 13px;
|
|
9192
|
+
}
|
|
9193
|
+
.fun-path {
|
|
9194
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
9195
|
+
font-size: 11px;
|
|
9196
|
+
color: #b45309;
|
|
9197
|
+
margin-top: 14px;
|
|
9198
|
+
opacity: 0.7;
|
|
9199
|
+
}
|
|
9200
|
+
.headline {
|
|
9201
|
+
transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease;
|
|
9202
|
+
}
|
|
9203
|
+
.headline:hover {
|
|
9204
|
+
border-color: #fcd34d;
|
|
9205
|
+
box-shadow: 0 6px 18px rgba(245, 158, 11, 0.12);
|
|
9206
|
+
}
|
|
9207
|
+
.scorecard {
|
|
9208
|
+
transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease;
|
|
9209
|
+
}
|
|
9210
|
+
.scorecard:hover {
|
|
9211
|
+
border-color: #fcd34d;
|
|
9212
|
+
box-shadow: 0 6px 18px rgba(245, 158, 11, 0.12);
|
|
9213
|
+
}
|
|
9214
|
+
@media (prefers-reduced-motion: reduce) {
|
|
9215
|
+
.brand .cursor { animation: none; }
|
|
9216
|
+
.suggestion, .stacked-bar-card, .compare-card, .headline, .scorecard {
|
|
9217
|
+
transition: none;
|
|
9218
|
+
}
|
|
9219
|
+
.suggestion:hover, .stacked-bar-card:hover, .compare-card:hover { transform: none; }
|
|
9220
|
+
}
|
|
9221
|
+
|
|
9222
|
+
/* Responsive */
|
|
9223
|
+
@media (max-width: 840px) {
|
|
9224
|
+
.stats-row { grid-template-columns: repeat(3, 1fr); }
|
|
9225
|
+
.scorecard { grid-template-columns: 1fr; }
|
|
9226
|
+
}
|
|
9227
|
+
@media (max-width: 640px) {
|
|
9228
|
+
body { padding: 32px 16px 60px; }
|
|
9229
|
+
h1 { font-size: 26px; }
|
|
9230
|
+
.headline-number { font-size: 52px; }
|
|
9231
|
+
.stats-row { grid-template-columns: repeat(2, 1fr); }
|
|
9232
|
+
.signals { grid-template-columns: 1fr; }
|
|
9233
|
+
.suggestions { grid-template-columns: 1fr; }
|
|
9234
|
+
}
|
|
9235
|
+
`;
|
|
9236
|
+
|
|
9237
|
+
// src/insights/index.ts
|
|
9238
|
+
var init_insights2 = __esm(() => {
|
|
9239
|
+
init_insights();
|
|
9240
|
+
});
|
|
9241
|
+
|
|
9242
|
+
// src/auth/errors.ts
|
|
9243
|
+
var exports_errors = {};
|
|
9244
|
+
__export(exports_errors, {
|
|
9245
|
+
OAuthCallbackError: () => OAuthCallbackError
|
|
9246
|
+
});
|
|
9247
|
+
var OAuthCallbackError;
|
|
9248
|
+
var init_errors = __esm(() => {
|
|
9249
|
+
OAuthCallbackError = class OAuthCallbackError extends Error {
|
|
9250
|
+
error;
|
|
9251
|
+
errorCode;
|
|
9252
|
+
errorDescription;
|
|
9253
|
+
constructor(message, details = {}) {
|
|
9254
|
+
super(message);
|
|
9255
|
+
this.name = "OAuthCallbackError";
|
|
9256
|
+
this.error = details.error;
|
|
9257
|
+
this.errorCode = details.errorCode;
|
|
9258
|
+
this.errorDescription = details.errorDescription;
|
|
9259
|
+
}
|
|
9260
|
+
get userMessage() {
|
|
9261
|
+
const desc = this.errorDescription ?? this.message;
|
|
9262
|
+
if (this.errorCode === "bad_oauth_state" || /state/i.test(desc)) {
|
|
9263
|
+
return `Authentication failed: ${desc}. Run \`dosu login\` again.`;
|
|
9264
|
+
}
|
|
9265
|
+
return `Authentication failed: ${desc}`;
|
|
9266
|
+
}
|
|
9267
|
+
};
|
|
9268
|
+
});
|
|
9269
|
+
|
|
9270
|
+
// src/version/skill-update-check.ts
|
|
9271
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "node:fs";
|
|
9272
|
+
import { join as join5 } from "node:path";
|
|
9273
|
+
function getCachePath2() {
|
|
9274
|
+
return join5(getConfigDir(), CACHE_FILENAME2);
|
|
9275
|
+
}
|
|
9276
|
+
function readSkillCache() {
|
|
9277
|
+
try {
|
|
9278
|
+
const path3 = getCachePath2();
|
|
9279
|
+
if (!existsSync5(path3))
|
|
9280
|
+
return null;
|
|
9281
|
+
const data = JSON.parse(readFileSync6(path3, "utf-8"));
|
|
9282
|
+
if (typeof data.lastCheck === "number" && typeof data.latestSha === "string" && typeof data.installedSha === "string") {
|
|
9283
|
+
return data;
|
|
9284
|
+
}
|
|
9285
|
+
return null;
|
|
9286
|
+
} catch {
|
|
9287
|
+
return null;
|
|
9288
|
+
}
|
|
9289
|
+
}
|
|
9290
|
+
function writeSkillCache(cache) {
|
|
9291
|
+
try {
|
|
9292
|
+
const dir = getConfigDir();
|
|
9293
|
+
if (!existsSync5(dir)) {
|
|
9294
|
+
mkdirSync4(dir, { recursive: true, mode: 448 });
|
|
9295
|
+
}
|
|
9296
|
+
writeFileSync5(getCachePath2(), JSON.stringify(cache), { mode: 384 });
|
|
9297
|
+
} catch {}
|
|
9298
|
+
}
|
|
9299
|
+
function clearInstalledSha() {
|
|
9300
|
+
const cache = readSkillCache();
|
|
9301
|
+
if (cache)
|
|
9302
|
+
writeSkillCache({ ...cache, installedSha: "" });
|
|
9303
|
+
}
|
|
9304
|
+
async function fetchLatestSha() {
|
|
9305
|
+
const controller = new AbortController;
|
|
9306
|
+
const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS2);
|
|
9307
|
+
try {
|
|
9308
|
+
const resp = await fetch(GITHUB_API_URL, {
|
|
9309
|
+
signal: controller.signal,
|
|
9310
|
+
headers: {
|
|
9311
|
+
"User-Agent": `dosu-cli/${VERSION}`,
|
|
9312
|
+
Accept: "application/vnd.github+json"
|
|
9313
|
+
}
|
|
9314
|
+
});
|
|
9315
|
+
if (!resp.ok)
|
|
9316
|
+
return null;
|
|
9317
|
+
const data = await resp.json();
|
|
9318
|
+
const sha = data.sha;
|
|
9319
|
+
return typeof sha === "string" ? sha : null;
|
|
9320
|
+
} catch {
|
|
9321
|
+
return null;
|
|
9322
|
+
} finally {
|
|
9323
|
+
clearTimeout(timeout);
|
|
9324
|
+
}
|
|
9325
|
+
}
|
|
9326
|
+
function displayNotice() {
|
|
9327
|
+
const msg = `
|
|
9328
|
+
${import_picocolors12.default.yellow(" Dosu skill update available")}
|
|
9329
|
+
` + `${import_picocolors12.default.dim(' Run "dosu skill update" to upgrade')}
|
|
9330
|
+
`;
|
|
9331
|
+
console.error(msg);
|
|
9332
|
+
}
|
|
9333
|
+
function checkForSkillUpdates() {
|
|
9334
|
+
try {
|
|
9335
|
+
const cache = readSkillCache();
|
|
9336
|
+
if (cache?.installedSha && cache.latestSha && cache.latestSha !== cache.installedSha) {
|
|
9337
|
+
displayNotice();
|
|
9338
|
+
}
|
|
9339
|
+
const isStale = !cache || Date.now() - cache.lastCheck > CHECK_INTERVAL_MS2;
|
|
9340
|
+
if (isStale) {
|
|
9341
|
+
fetchLatestSha().then((latest) => {
|
|
9342
|
+
const fresh = readSkillCache();
|
|
9343
|
+
writeSkillCache({
|
|
9344
|
+
lastCheck: Date.now(),
|
|
9345
|
+
latestSha: latest ?? fresh?.latestSha ?? "",
|
|
9346
|
+
installedSha: fresh?.installedSha ?? ""
|
|
9347
|
+
});
|
|
9348
|
+
if (latest) {
|
|
9349
|
+
logger.debug("skill-update-check", `Cached latest SHA: ${latest}`);
|
|
9350
|
+
}
|
|
9351
|
+
}).catch((err) => {
|
|
9352
|
+
logger.error("skill-update-check", `Background fetch failed: ${err}`);
|
|
9353
|
+
});
|
|
9354
|
+
}
|
|
9355
|
+
} catch (err) {
|
|
9356
|
+
logger.error("skill-update-check", `Skill update check failed: ${err}`);
|
|
9357
|
+
}
|
|
9358
|
+
}
|
|
9359
|
+
var import_picocolors12, CACHE_FILENAME2 = "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";
|
|
9360
|
+
var init_skill_update_check = __esm(() => {
|
|
9361
|
+
init_config();
|
|
9362
|
+
init_logger();
|
|
9363
|
+
import_picocolors12 = __toESM(require_picocolors(), 1);
|
|
9364
|
+
CHECK_INTERVAL_MS2 = 24 * 60 * 60 * 1000;
|
|
9365
|
+
});
|
|
9366
|
+
|
|
9367
|
+
// src/commands/skill.ts
|
|
9368
|
+
import { exec as exec2, execSync as execSync2 } from "node:child_process";
|
|
9369
|
+
import { homedir } from "node:os";
|
|
9370
|
+
import { join as join6 } from "node:path";
|
|
9371
|
+
function skillAgentIDsForProviders(providerIDs) {
|
|
9372
|
+
return [
|
|
9373
|
+
...new Set(providerIDs.map((providerID) => SKILL_AGENT_BY_PROVIDER[providerID]).filter((agent) => Boolean(agent)))
|
|
9374
|
+
];
|
|
9375
|
+
}
|
|
9376
|
+
function skillInstallTargetForProvider(providerID) {
|
|
9377
|
+
const agentID = SKILL_AGENT_BY_PROVIDER[providerID];
|
|
9378
|
+
if (!agentID)
|
|
9379
|
+
return null;
|
|
9380
|
+
if (agentID === "claude-code") {
|
|
9381
|
+
const claudeConfigDir = process.env.CLAUDE_CONFIG_DIR?.trim() || join6(homedir(), ".claude");
|
|
9382
|
+
return { path: join6(claudeConfigDir, "skills", SKILL_NAME), symlink: true };
|
|
9383
|
+
}
|
|
9384
|
+
if (agentID === "windsurf") {
|
|
9385
|
+
return {
|
|
9386
|
+
path: join6(homedir(), ".codeium", "windsurf", "skills", SKILL_NAME),
|
|
9387
|
+
symlink: true
|
|
9388
|
+
};
|
|
9389
|
+
}
|
|
9390
|
+
return {
|
|
9391
|
+
path: join6(homedir(), ".agents", "skills", SKILL_NAME),
|
|
9392
|
+
symlink: false
|
|
9393
|
+
};
|
|
10350
9394
|
}
|
|
10351
|
-
|
|
10352
|
-
|
|
10353
|
-
|
|
10354
|
-
color: #b45309;
|
|
10355
|
-
margin-top: 14px;
|
|
10356
|
-
opacity: 0.7;
|
|
9395
|
+
function skillAgentArgs(providerIDs) {
|
|
9396
|
+
const agents = providerIDs === undefined ? SUPPORTED_SKILL_AGENTS : skillAgentIDsForProviders(providerIDs);
|
|
9397
|
+
return agents.map((agent) => `-a ${agent}`).join(" ");
|
|
10357
9398
|
}
|
|
10358
|
-
|
|
10359
|
-
|
|
9399
|
+
function execQuiet(command) {
|
|
9400
|
+
return new Promise((resolve, reject) => {
|
|
9401
|
+
exec2(command, { windowsHide: true }, (error) => {
|
|
9402
|
+
if (error)
|
|
9403
|
+
reject(error);
|
|
9404
|
+
else
|
|
9405
|
+
resolve();
|
|
9406
|
+
});
|
|
9407
|
+
});
|
|
10360
9408
|
}
|
|
10361
|
-
|
|
10362
|
-
|
|
10363
|
-
|
|
9409
|
+
async function installSkill(providerIDs, options = {}) {
|
|
9410
|
+
const agentArgs = skillAgentArgs(providerIDs);
|
|
9411
|
+
if (!agentArgs) {
|
|
9412
|
+
logger.debug("skill", "No selected providers support the Dosu skill");
|
|
9413
|
+
return { success: true };
|
|
9414
|
+
}
|
|
9415
|
+
try {
|
|
9416
|
+
const command = `npx skills add ${SKILL_REPO} -g ${agentArgs} -s "*" -y`;
|
|
9417
|
+
if (options.quiet)
|
|
9418
|
+
await execQuiet(command);
|
|
9419
|
+
else
|
|
9420
|
+
execSync2(command, { stdio: "inherit" });
|
|
9421
|
+
} catch (err) {
|
|
9422
|
+
logger.error("skill", `Failed to install skill: ${err}`);
|
|
9423
|
+
return { success: false };
|
|
9424
|
+
}
|
|
9425
|
+
const sha = await fetchLatestSha();
|
|
9426
|
+
if (sha) {
|
|
9427
|
+
writeSkillCache({
|
|
9428
|
+
lastCheck: Date.now(),
|
|
9429
|
+
latestSha: sha,
|
|
9430
|
+
installedSha: sha
|
|
9431
|
+
});
|
|
9432
|
+
return { success: true, sha };
|
|
9433
|
+
}
|
|
9434
|
+
logger.debug("skill", "Skill installed but could not fetch latest SHA");
|
|
9435
|
+
return { success: true };
|
|
10364
9436
|
}
|
|
10365
|
-
|
|
10366
|
-
|
|
9437
|
+
function installedSkillNames() {
|
|
9438
|
+
let entries;
|
|
9439
|
+
try {
|
|
9440
|
+
const json = execSync2("npx skills list -g --json", {
|
|
9441
|
+
encoding: "utf-8",
|
|
9442
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
9443
|
+
});
|
|
9444
|
+
const parsed = JSON.parse(json);
|
|
9445
|
+
if (!Array.isArray(parsed))
|
|
9446
|
+
throw new Error("expected a JSON array");
|
|
9447
|
+
entries = parsed;
|
|
9448
|
+
} catch (err) {
|
|
9449
|
+
logger.debug("skill", `Could not list installed skills: ${err}`);
|
|
9450
|
+
return null;
|
|
9451
|
+
}
|
|
9452
|
+
const names = [];
|
|
9453
|
+
for (const entry of entries) {
|
|
9454
|
+
if (entry.source !== SKILL_REPO || typeof entry.name !== "string")
|
|
9455
|
+
continue;
|
|
9456
|
+
if (!SAFE_SKILL_NAME.test(entry.name)) {
|
|
9457
|
+
logger.warn("skill", `Skipping skill with an unsupported name: ${entry.name}`);
|
|
9458
|
+
continue;
|
|
9459
|
+
}
|
|
9460
|
+
names.push(entry.name);
|
|
9461
|
+
}
|
|
9462
|
+
return names;
|
|
10367
9463
|
}
|
|
10368
|
-
|
|
10369
|
-
|
|
10370
|
-
|
|
9464
|
+
function skillCommand() {
|
|
9465
|
+
const cmd = new Command("skill").description("Manage the Dosu agent skill");
|
|
9466
|
+
cmd.command("install").description("Install the Dosu skill for AI coding agents").action(async () => {
|
|
9467
|
+
console.log(`Installing skills from ${SKILL_REPO}...`);
|
|
9468
|
+
const result = await installSkill();
|
|
9469
|
+
if (result.success) {
|
|
9470
|
+
console.log(import_picocolors13.default.green(`
|
|
9471
|
+
✓ Skills installed successfully.`));
|
|
9472
|
+
} else {
|
|
9473
|
+
console.error(import_picocolors13.default.red(`
|
|
9474
|
+
Failed to install skill. Make sure npx is available.`));
|
|
9475
|
+
process.exit(1);
|
|
9476
|
+
}
|
|
9477
|
+
});
|
|
9478
|
+
cmd.command("remove").description("Remove the Dosu skills").action(() => {
|
|
9479
|
+
const installed = installedSkillNames();
|
|
9480
|
+
if (installed?.length === 0) {
|
|
9481
|
+
console.log(`No skills from ${SKILL_REPO} are installed.`);
|
|
9482
|
+
return;
|
|
9483
|
+
}
|
|
9484
|
+
const targets = installed ?? [SKILL_NAME];
|
|
9485
|
+
console.log(`Removing skills from ${SKILL_REPO}...`);
|
|
9486
|
+
try {
|
|
9487
|
+
execSync2(`npx skills remove -g ${targets.join(" ")} -y`, {
|
|
9488
|
+
stdio: "inherit"
|
|
9489
|
+
});
|
|
9490
|
+
clearInstalledSha();
|
|
9491
|
+
console.log(import_picocolors13.default.green(`
|
|
9492
|
+
✓ Skills removed.`));
|
|
9493
|
+
} catch {
|
|
9494
|
+
console.error(import_picocolors13.default.red(`
|
|
9495
|
+
Failed to remove skills.`));
|
|
9496
|
+
process.exit(1);
|
|
9497
|
+
}
|
|
9498
|
+
});
|
|
9499
|
+
cmd.command("update").description("Update the Dosu skill to the latest version").action(async () => {
|
|
9500
|
+
console.log(`Updating skills from ${SKILL_REPO}...`);
|
|
9501
|
+
const result = await installSkill();
|
|
9502
|
+
if (!result.success) {
|
|
9503
|
+
console.error(import_picocolors13.default.red(`
|
|
9504
|
+
Failed to update skill.`));
|
|
9505
|
+
process.exit(1);
|
|
9506
|
+
}
|
|
9507
|
+
console.log(import_picocolors13.default.green(`
|
|
9508
|
+
✓ Skills updated.`));
|
|
9509
|
+
});
|
|
9510
|
+
return cmd;
|
|
10371
9511
|
}
|
|
10372
|
-
|
|
10373
|
-
|
|
10374
|
-
|
|
10375
|
-
|
|
9512
|
+
var import_picocolors13, SKILL_REPO = "dosu-ai/dosu-skill", SKILL_NAME = "dosu", SAFE_SKILL_NAME, SUPPORTED_SKILL_AGENTS, SKILL_AGENT_BY_PROVIDER;
|
|
9513
|
+
var init_skill = __esm(() => {
|
|
9514
|
+
init_commander();
|
|
9515
|
+
init_logger();
|
|
9516
|
+
init_skill_update_check();
|
|
9517
|
+
import_picocolors13 = __toESM(require_picocolors(), 1);
|
|
9518
|
+
SAFE_SKILL_NAME = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
|
|
9519
|
+
SUPPORTED_SKILL_AGENTS = [
|
|
9520
|
+
"claude-code",
|
|
9521
|
+
"cursor",
|
|
9522
|
+
"gemini-cli",
|
|
9523
|
+
"codex",
|
|
9524
|
+
"windsurf",
|
|
9525
|
+
"zed",
|
|
9526
|
+
"cline",
|
|
9527
|
+
"github-copilot",
|
|
9528
|
+
"opencode",
|
|
9529
|
+
"antigravity"
|
|
9530
|
+
];
|
|
9531
|
+
SKILL_AGENT_BY_PROVIDER = {
|
|
9532
|
+
claude: "claude-code",
|
|
9533
|
+
cursor: "cursor",
|
|
9534
|
+
vscode: "github-copilot",
|
|
9535
|
+
gemini: "gemini-cli",
|
|
9536
|
+
codex: "codex",
|
|
9537
|
+
windsurf: "windsurf",
|
|
9538
|
+
zed: "zed",
|
|
9539
|
+
cline: "cline",
|
|
9540
|
+
"cline-cli": "cline",
|
|
9541
|
+
copilot: "github-copilot",
|
|
9542
|
+
opencode: "opencode",
|
|
9543
|
+
antigravity: "antigravity"
|
|
9544
|
+
};
|
|
9545
|
+
});
|
|
9546
|
+
|
|
9547
|
+
// src/mcp/constants.ts
|
|
9548
|
+
var MCP_PROVIDER_SLUG = "dosu_mcp";
|
|
9549
|
+
|
|
9550
|
+
// node_modules/signal-exit/dist/cjs/signals.js
|
|
9551
|
+
var require_signals = __commonJS((exports) => {
|
|
9552
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9553
|
+
exports.signals = undefined;
|
|
9554
|
+
exports.signals = [];
|
|
9555
|
+
exports.signals.push("SIGHUP", "SIGINT", "SIGTERM");
|
|
9556
|
+
if (process.platform !== "win32") {
|
|
9557
|
+
exports.signals.push("SIGALRM", "SIGABRT", "SIGVTALRM", "SIGXCPU", "SIGXFSZ", "SIGUSR2", "SIGTRAP", "SIGSYS", "SIGQUIT", "SIGIOT");
|
|
10376
9558
|
}
|
|
10377
|
-
|
|
10378
|
-
|
|
9559
|
+
if (process.platform === "linux") {
|
|
9560
|
+
exports.signals.push("SIGIO", "SIGPOLL", "SIGPWR", "SIGSTKFLT");
|
|
9561
|
+
}
|
|
9562
|
+
});
|
|
9563
|
+
|
|
9564
|
+
// node_modules/signal-exit/dist/cjs/index.js
|
|
9565
|
+
var require_cjs = __commonJS((exports) => {
|
|
9566
|
+
var _a;
|
|
9567
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9568
|
+
exports.unload = exports.load = exports.onExit = exports.signals = undefined;
|
|
9569
|
+
var signals_js_1 = require_signals();
|
|
9570
|
+
Object.defineProperty(exports, "signals", { enumerable: true, get: function() {
|
|
9571
|
+
return signals_js_1.signals;
|
|
9572
|
+
} });
|
|
9573
|
+
var processOk = (process11) => !!process11 && typeof process11 === "object" && typeof process11.removeListener === "function" && typeof process11.emit === "function" && typeof process11.reallyExit === "function" && typeof process11.listeners === "function" && typeof process11.kill === "function" && typeof process11.pid === "number" && typeof process11.on === "function";
|
|
9574
|
+
var kExitEmitter = Symbol.for("signal-exit emitter");
|
|
9575
|
+
var global = globalThis;
|
|
9576
|
+
var ObjectDefineProperty = Object.defineProperty.bind(Object);
|
|
9577
|
+
|
|
9578
|
+
class Emitter {
|
|
9579
|
+
emitted = {
|
|
9580
|
+
afterExit: false,
|
|
9581
|
+
exit: false
|
|
9582
|
+
};
|
|
9583
|
+
listeners = {
|
|
9584
|
+
afterExit: [],
|
|
9585
|
+
exit: []
|
|
9586
|
+
};
|
|
9587
|
+
count = 0;
|
|
9588
|
+
id = Math.random();
|
|
9589
|
+
constructor() {
|
|
9590
|
+
if (global[kExitEmitter]) {
|
|
9591
|
+
return global[kExitEmitter];
|
|
9592
|
+
}
|
|
9593
|
+
ObjectDefineProperty(global, kExitEmitter, {
|
|
9594
|
+
value: this,
|
|
9595
|
+
writable: false,
|
|
9596
|
+
enumerable: false,
|
|
9597
|
+
configurable: false
|
|
9598
|
+
});
|
|
9599
|
+
}
|
|
9600
|
+
on(ev, fn) {
|
|
9601
|
+
this.listeners[ev].push(fn);
|
|
9602
|
+
}
|
|
9603
|
+
removeListener(ev, fn) {
|
|
9604
|
+
const list = this.listeners[ev];
|
|
9605
|
+
const i2 = list.indexOf(fn);
|
|
9606
|
+
if (i2 === -1) {
|
|
9607
|
+
return;
|
|
9608
|
+
}
|
|
9609
|
+
if (i2 === 0 && list.length === 1) {
|
|
9610
|
+
list.length = 0;
|
|
9611
|
+
} else {
|
|
9612
|
+
list.splice(i2, 1);
|
|
9613
|
+
}
|
|
9614
|
+
}
|
|
9615
|
+
emit(ev, code, signal) {
|
|
9616
|
+
if (this.emitted[ev]) {
|
|
9617
|
+
return false;
|
|
9618
|
+
}
|
|
9619
|
+
this.emitted[ev] = true;
|
|
9620
|
+
let ret = false;
|
|
9621
|
+
for (const fn of this.listeners[ev]) {
|
|
9622
|
+
ret = fn(code, signal) === true || ret;
|
|
9623
|
+
}
|
|
9624
|
+
if (ev === "exit") {
|
|
9625
|
+
ret = this.emit("afterExit", code, signal) || ret;
|
|
9626
|
+
}
|
|
9627
|
+
return ret;
|
|
9628
|
+
}
|
|
9629
|
+
}
|
|
9630
|
+
|
|
9631
|
+
class SignalExitBase {
|
|
9632
|
+
}
|
|
9633
|
+
var signalExitWrap = (handler) => {
|
|
9634
|
+
return {
|
|
9635
|
+
onExit(cb, opts) {
|
|
9636
|
+
return handler.onExit(cb, opts);
|
|
9637
|
+
},
|
|
9638
|
+
load() {
|
|
9639
|
+
return handler.load();
|
|
9640
|
+
},
|
|
9641
|
+
unload() {
|
|
9642
|
+
return handler.unload();
|
|
9643
|
+
}
|
|
9644
|
+
};
|
|
9645
|
+
};
|
|
10379
9646
|
|
|
10380
|
-
|
|
10381
|
-
|
|
10382
|
-
|
|
10383
|
-
|
|
10384
|
-
}
|
|
10385
|
-
|
|
10386
|
-
|
|
10387
|
-
h1 { font-size: 26px; }
|
|
10388
|
-
.headline-number { font-size: 52px; }
|
|
10389
|
-
.stats-row { grid-template-columns: repeat(2, 1fr); }
|
|
10390
|
-
.signals { grid-template-columns: 1fr; }
|
|
10391
|
-
.suggestions { grid-template-columns: 1fr; }
|
|
10392
|
-
}
|
|
10393
|
-
`;
|
|
9647
|
+
class SignalExitFallback extends SignalExitBase {
|
|
9648
|
+
onExit() {
|
|
9649
|
+
return () => {};
|
|
9650
|
+
}
|
|
9651
|
+
load() {}
|
|
9652
|
+
unload() {}
|
|
9653
|
+
}
|
|
10394
9654
|
|
|
10395
|
-
|
|
10396
|
-
|
|
10397
|
-
|
|
9655
|
+
class SignalExit extends SignalExitBase {
|
|
9656
|
+
#hupSig = process10.platform === "win32" ? "SIGINT" : "SIGHUP";
|
|
9657
|
+
#emitter = new Emitter;
|
|
9658
|
+
#process;
|
|
9659
|
+
#originalProcessEmit;
|
|
9660
|
+
#originalProcessReallyExit;
|
|
9661
|
+
#sigListeners = {};
|
|
9662
|
+
#loaded = false;
|
|
9663
|
+
constructor(process11) {
|
|
9664
|
+
super();
|
|
9665
|
+
this.#process = process11;
|
|
9666
|
+
this.#sigListeners = {};
|
|
9667
|
+
for (const sig of signals_js_1.signals) {
|
|
9668
|
+
this.#sigListeners[sig] = () => {
|
|
9669
|
+
const listeners = this.#process.listeners(sig);
|
|
9670
|
+
let { count } = this.#emitter;
|
|
9671
|
+
const p2 = process11;
|
|
9672
|
+
if (typeof p2.__signal_exit_emitter__ === "object" && typeof p2.__signal_exit_emitter__.count === "number") {
|
|
9673
|
+
count += p2.__signal_exit_emitter__.count;
|
|
9674
|
+
}
|
|
9675
|
+
if (listeners.length === count) {
|
|
9676
|
+
this.unload();
|
|
9677
|
+
const ret = this.#emitter.emit("exit", null, sig);
|
|
9678
|
+
const s = sig === "SIGHUP" ? this.#hupSig : sig;
|
|
9679
|
+
if (!ret)
|
|
9680
|
+
process11.kill(process11.pid, s);
|
|
9681
|
+
}
|
|
9682
|
+
};
|
|
9683
|
+
}
|
|
9684
|
+
this.#originalProcessReallyExit = process11.reallyExit;
|
|
9685
|
+
this.#originalProcessEmit = process11.emit;
|
|
9686
|
+
}
|
|
9687
|
+
onExit(cb, opts) {
|
|
9688
|
+
if (!processOk(this.#process)) {
|
|
9689
|
+
return () => {};
|
|
9690
|
+
}
|
|
9691
|
+
if (this.#loaded === false) {
|
|
9692
|
+
this.load();
|
|
9693
|
+
}
|
|
9694
|
+
const ev = opts?.alwaysLast ? "afterExit" : "exit";
|
|
9695
|
+
this.#emitter.on(ev, cb);
|
|
9696
|
+
return () => {
|
|
9697
|
+
this.#emitter.removeListener(ev, cb);
|
|
9698
|
+
if (this.#emitter.listeners["exit"].length === 0 && this.#emitter.listeners["afterExit"].length === 0) {
|
|
9699
|
+
this.unload();
|
|
9700
|
+
}
|
|
9701
|
+
};
|
|
9702
|
+
}
|
|
9703
|
+
load() {
|
|
9704
|
+
if (this.#loaded) {
|
|
9705
|
+
return;
|
|
9706
|
+
}
|
|
9707
|
+
this.#loaded = true;
|
|
9708
|
+
this.#emitter.count += 1;
|
|
9709
|
+
for (const sig of signals_js_1.signals) {
|
|
9710
|
+
try {
|
|
9711
|
+
const fn = this.#sigListeners[sig];
|
|
9712
|
+
if (fn)
|
|
9713
|
+
this.#process.on(sig, fn);
|
|
9714
|
+
} catch (_2) {}
|
|
9715
|
+
}
|
|
9716
|
+
this.#process.emit = (ev, ...a3) => {
|
|
9717
|
+
return this.#processEmit(ev, ...a3);
|
|
9718
|
+
};
|
|
9719
|
+
this.#process.reallyExit = (code) => {
|
|
9720
|
+
return this.#processReallyExit(code);
|
|
9721
|
+
};
|
|
9722
|
+
}
|
|
9723
|
+
unload() {
|
|
9724
|
+
if (!this.#loaded) {
|
|
9725
|
+
return;
|
|
9726
|
+
}
|
|
9727
|
+
this.#loaded = false;
|
|
9728
|
+
signals_js_1.signals.forEach((sig) => {
|
|
9729
|
+
const listener = this.#sigListeners[sig];
|
|
9730
|
+
if (!listener) {
|
|
9731
|
+
throw new Error("Listener not defined for signal: " + sig);
|
|
9732
|
+
}
|
|
9733
|
+
try {
|
|
9734
|
+
this.#process.removeListener(sig, listener);
|
|
9735
|
+
} catch (_2) {}
|
|
9736
|
+
});
|
|
9737
|
+
this.#process.emit = this.#originalProcessEmit;
|
|
9738
|
+
this.#process.reallyExit = this.#originalProcessReallyExit;
|
|
9739
|
+
this.#emitter.count -= 1;
|
|
9740
|
+
}
|
|
9741
|
+
#processReallyExit(code) {
|
|
9742
|
+
if (!processOk(this.#process)) {
|
|
9743
|
+
return 0;
|
|
9744
|
+
}
|
|
9745
|
+
this.#process.exitCode = code || 0;
|
|
9746
|
+
this.#emitter.emit("exit", this.#process.exitCode, null);
|
|
9747
|
+
return this.#originalProcessReallyExit.call(this.#process, this.#process.exitCode);
|
|
9748
|
+
}
|
|
9749
|
+
#processEmit(ev, ...args) {
|
|
9750
|
+
const og = this.#originalProcessEmit;
|
|
9751
|
+
if (ev === "exit" && processOk(this.#process)) {
|
|
9752
|
+
if (typeof args[0] === "number") {
|
|
9753
|
+
this.#process.exitCode = args[0];
|
|
9754
|
+
}
|
|
9755
|
+
const ret = og.call(this.#process, ev, ...args);
|
|
9756
|
+
this.#emitter.emit("exit", this.#process.exitCode, null);
|
|
9757
|
+
return ret;
|
|
9758
|
+
} else {
|
|
9759
|
+
return og.call(this.#process, ev, ...args);
|
|
9760
|
+
}
|
|
9761
|
+
}
|
|
9762
|
+
}
|
|
9763
|
+
var process10 = globalThis.process;
|
|
9764
|
+
_a = signalExitWrap(processOk(process10) ? new SignalExit(process10) : new SignalExitFallback), exports.onExit = _a.onExit, exports.load = _a.load, exports.unload = _a.unload;
|
|
10398
9765
|
});
|
|
10399
9766
|
|
|
10400
|
-
//
|
|
10401
|
-
var
|
|
10402
|
-
|
|
10403
|
-
|
|
10404
|
-
|
|
10405
|
-
|
|
10406
|
-
|
|
10407
|
-
|
|
10408
|
-
|
|
10409
|
-
|
|
10410
|
-
|
|
10411
|
-
|
|
10412
|
-
|
|
10413
|
-
|
|
10414
|
-
|
|
10415
|
-
|
|
10416
|
-
|
|
9767
|
+
// node_modules/write-file-atomic/lib/index.js
|
|
9768
|
+
var require_lib = __commonJS((exports, module) => {
|
|
9769
|
+
var __filename = "/home/runner/work/dosu-cli/dosu-cli/node_modules/write-file-atomic/lib/index.js";
|
|
9770
|
+
module.exports = writeFile;
|
|
9771
|
+
module.exports.sync = writeFileSync6;
|
|
9772
|
+
module.exports._getTmpname = getTmpname;
|
|
9773
|
+
module.exports._cleanupOnExit = cleanupOnExit;
|
|
9774
|
+
var fs7 = __require("fs");
|
|
9775
|
+
var crypto = __require("node:crypto");
|
|
9776
|
+
var { onExit } = require_cjs();
|
|
9777
|
+
var path3 = __require("path");
|
|
9778
|
+
var { promisify: promisify7 } = __require("util");
|
|
9779
|
+
var activeFiles = {};
|
|
9780
|
+
var threadId = function getId() {
|
|
9781
|
+
try {
|
|
9782
|
+
const workerThreads = __require("worker_threads");
|
|
9783
|
+
return workerThreads.threadId;
|
|
9784
|
+
} catch (e) {
|
|
9785
|
+
return 0;
|
|
9786
|
+
}
|
|
9787
|
+
}();
|
|
9788
|
+
var invocations = 0;
|
|
9789
|
+
function getTmpname(filename) {
|
|
9790
|
+
return filename + "." + crypto.createHash("sha1").update(__filename).update(String(process.pid)).update(String(threadId)).update(String(++invocations)).digest().readUInt32BE(0);
|
|
9791
|
+
}
|
|
9792
|
+
function cleanupOnExit(tmpfile) {
|
|
9793
|
+
return () => {
|
|
9794
|
+
try {
|
|
9795
|
+
fs7.unlinkSync(typeof tmpfile === "function" ? tmpfile() : tmpfile);
|
|
9796
|
+
} catch {}
|
|
9797
|
+
};
|
|
9798
|
+
}
|
|
9799
|
+
function serializeActiveFile(absoluteName) {
|
|
9800
|
+
return new Promise((resolve) => {
|
|
9801
|
+
if (!activeFiles[absoluteName]) {
|
|
9802
|
+
activeFiles[absoluteName] = [];
|
|
9803
|
+
}
|
|
9804
|
+
activeFiles[absoluteName].push(resolve);
|
|
9805
|
+
if (activeFiles[absoluteName].length === 1) {
|
|
9806
|
+
resolve();
|
|
9807
|
+
}
|
|
9808
|
+
});
|
|
9809
|
+
}
|
|
9810
|
+
function isChownErrOk(err) {
|
|
9811
|
+
if (err.code === "ENOSYS") {
|
|
9812
|
+
return true;
|
|
9813
|
+
}
|
|
9814
|
+
const nonroot = !process.getuid || process.getuid() !== 0;
|
|
9815
|
+
if (nonroot) {
|
|
9816
|
+
if (err.code === "EINVAL" || err.code === "EPERM") {
|
|
9817
|
+
return true;
|
|
9818
|
+
}
|
|
9819
|
+
}
|
|
9820
|
+
return false;
|
|
9821
|
+
}
|
|
9822
|
+
async function writeFileAsync(filename, data, options = {}) {
|
|
9823
|
+
if (typeof options === "string") {
|
|
9824
|
+
options = { encoding: options };
|
|
9825
|
+
}
|
|
9826
|
+
let fd;
|
|
9827
|
+
let tmpfile;
|
|
9828
|
+
const removeOnExitHandler = onExit(cleanupOnExit(() => tmpfile));
|
|
9829
|
+
const absoluteName = path3.resolve(filename);
|
|
9830
|
+
try {
|
|
9831
|
+
await serializeActiveFile(absoluteName);
|
|
9832
|
+
const truename = await promisify7(fs7.realpath)(filename).catch(() => filename);
|
|
9833
|
+
tmpfile = getTmpname(truename);
|
|
9834
|
+
if (!options.mode || !options.chown) {
|
|
9835
|
+
const stats = await promisify7(fs7.stat)(truename).catch(() => {});
|
|
9836
|
+
if (stats) {
|
|
9837
|
+
if (options.mode == null) {
|
|
9838
|
+
options.mode = stats.mode;
|
|
9839
|
+
}
|
|
9840
|
+
if (options.chown == null && process.getuid) {
|
|
9841
|
+
options.chown = { uid: stats.uid, gid: stats.gid };
|
|
9842
|
+
}
|
|
9843
|
+
}
|
|
9844
|
+
}
|
|
9845
|
+
fd = await promisify7(fs7.open)(tmpfile, "w", options.mode);
|
|
9846
|
+
if (options.tmpfileCreated) {
|
|
9847
|
+
await options.tmpfileCreated(tmpfile);
|
|
9848
|
+
}
|
|
9849
|
+
if (ArrayBuffer.isView(data)) {
|
|
9850
|
+
await promisify7(fs7.write)(fd, data, 0, data.length, 0);
|
|
9851
|
+
} else if (data != null) {
|
|
9852
|
+
await promisify7(fs7.write)(fd, String(data), 0, String(options.encoding || "utf8"));
|
|
9853
|
+
}
|
|
9854
|
+
if (options.fsync !== false) {
|
|
9855
|
+
await promisify7(fs7.fsync)(fd);
|
|
9856
|
+
}
|
|
9857
|
+
await promisify7(fs7.close)(fd);
|
|
9858
|
+
fd = null;
|
|
9859
|
+
if (options.chown) {
|
|
9860
|
+
await promisify7(fs7.chown)(tmpfile, options.chown.uid, options.chown.gid).catch((err) => {
|
|
9861
|
+
if (!isChownErrOk(err)) {
|
|
9862
|
+
throw err;
|
|
9863
|
+
}
|
|
9864
|
+
});
|
|
9865
|
+
}
|
|
9866
|
+
if (options.mode) {
|
|
9867
|
+
await promisify7(fs7.chmod)(tmpfile, options.mode).catch((err) => {
|
|
9868
|
+
if (!isChownErrOk(err)) {
|
|
9869
|
+
throw err;
|
|
9870
|
+
}
|
|
9871
|
+
});
|
|
9872
|
+
}
|
|
9873
|
+
await promisify7(fs7.rename)(tmpfile, truename);
|
|
9874
|
+
} finally {
|
|
9875
|
+
if (fd) {
|
|
9876
|
+
await promisify7(fs7.close)(fd).catch(() => {});
|
|
9877
|
+
}
|
|
9878
|
+
removeOnExitHandler();
|
|
9879
|
+
await promisify7(fs7.unlink)(tmpfile).catch(() => {});
|
|
9880
|
+
activeFiles[absoluteName].shift();
|
|
9881
|
+
if (activeFiles[absoluteName].length > 0) {
|
|
9882
|
+
activeFiles[absoluteName][0]();
|
|
9883
|
+
} else {
|
|
9884
|
+
delete activeFiles[absoluteName];
|
|
9885
|
+
}
|
|
10417
9886
|
}
|
|
10418
|
-
|
|
10419
|
-
|
|
10420
|
-
|
|
10421
|
-
|
|
9887
|
+
}
|
|
9888
|
+
async function writeFile(filename, data, options, callback) {
|
|
9889
|
+
if (options instanceof Function) {
|
|
9890
|
+
callback = options;
|
|
9891
|
+
options = {};
|
|
9892
|
+
}
|
|
9893
|
+
const promise = writeFileAsync(filename, data, options);
|
|
9894
|
+
if (callback) {
|
|
9895
|
+
try {
|
|
9896
|
+
const result = await promise;
|
|
9897
|
+
return callback(result);
|
|
9898
|
+
} catch (err) {
|
|
9899
|
+
return callback(err);
|
|
10422
9900
|
}
|
|
10423
|
-
return `Authentication failed: ${desc}`;
|
|
10424
9901
|
}
|
|
10425
|
-
|
|
9902
|
+
return promise;
|
|
9903
|
+
}
|
|
9904
|
+
function writeFileSync6(filename, data, options) {
|
|
9905
|
+
if (typeof options === "string") {
|
|
9906
|
+
options = { encoding: options };
|
|
9907
|
+
} else if (!options) {
|
|
9908
|
+
options = {};
|
|
9909
|
+
}
|
|
9910
|
+
try {
|
|
9911
|
+
filename = fs7.realpathSync(filename);
|
|
9912
|
+
} catch (ex) {}
|
|
9913
|
+
const tmpfile = getTmpname(filename);
|
|
9914
|
+
if (!options.mode || !options.chown) {
|
|
9915
|
+
try {
|
|
9916
|
+
const stats = fs7.statSync(filename);
|
|
9917
|
+
options = Object.assign({}, options);
|
|
9918
|
+
if (!options.mode) {
|
|
9919
|
+
options.mode = stats.mode;
|
|
9920
|
+
}
|
|
9921
|
+
if (!options.chown && process.getuid) {
|
|
9922
|
+
options.chown = { uid: stats.uid, gid: stats.gid };
|
|
9923
|
+
}
|
|
9924
|
+
} catch (ex) {}
|
|
9925
|
+
}
|
|
9926
|
+
let fd;
|
|
9927
|
+
const cleanup = cleanupOnExit(tmpfile);
|
|
9928
|
+
const removeOnExitHandler = onExit(cleanup);
|
|
9929
|
+
let threw = true;
|
|
9930
|
+
try {
|
|
9931
|
+
fd = fs7.openSync(tmpfile, "w", options.mode || 438);
|
|
9932
|
+
if (options.tmpfileCreated) {
|
|
9933
|
+
options.tmpfileCreated(tmpfile);
|
|
9934
|
+
}
|
|
9935
|
+
if (ArrayBuffer.isView(data)) {
|
|
9936
|
+
fs7.writeSync(fd, data, 0, data.length, 0);
|
|
9937
|
+
} else if (data != null) {
|
|
9938
|
+
fs7.writeSync(fd, String(data), 0, String(options.encoding || "utf8"));
|
|
9939
|
+
}
|
|
9940
|
+
if (options.fsync !== false) {
|
|
9941
|
+
fs7.fsyncSync(fd);
|
|
9942
|
+
}
|
|
9943
|
+
fs7.closeSync(fd);
|
|
9944
|
+
fd = null;
|
|
9945
|
+
if (options.chown) {
|
|
9946
|
+
try {
|
|
9947
|
+
fs7.chownSync(tmpfile, options.chown.uid, options.chown.gid);
|
|
9948
|
+
} catch (err) {
|
|
9949
|
+
if (!isChownErrOk(err)) {
|
|
9950
|
+
throw err;
|
|
9951
|
+
}
|
|
9952
|
+
}
|
|
9953
|
+
}
|
|
9954
|
+
if (options.mode) {
|
|
9955
|
+
try {
|
|
9956
|
+
fs7.chmodSync(tmpfile, options.mode);
|
|
9957
|
+
} catch (err) {
|
|
9958
|
+
if (!isChownErrOk(err)) {
|
|
9959
|
+
throw err;
|
|
9960
|
+
}
|
|
9961
|
+
}
|
|
9962
|
+
}
|
|
9963
|
+
fs7.renameSync(tmpfile, filename);
|
|
9964
|
+
threw = false;
|
|
9965
|
+
} finally {
|
|
9966
|
+
if (fd) {
|
|
9967
|
+
try {
|
|
9968
|
+
fs7.closeSync(fd);
|
|
9969
|
+
} catch (ex) {}
|
|
9970
|
+
}
|
|
9971
|
+
removeOnExitHandler();
|
|
9972
|
+
if (threw) {
|
|
9973
|
+
cleanup();
|
|
9974
|
+
}
|
|
9975
|
+
}
|
|
9976
|
+
}
|
|
10426
9977
|
});
|
|
10427
9978
|
|
|
10428
|
-
// src/
|
|
10429
|
-
import { existsSync as
|
|
10430
|
-
import {
|
|
10431
|
-
function
|
|
10432
|
-
return
|
|
9979
|
+
// src/mcp/config-helpers.ts
|
|
9980
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync7 } from "node:fs";
|
|
9981
|
+
import { dirname as dirname2 } from "node:path";
|
|
9982
|
+
function mcpURL(deploymentID) {
|
|
9983
|
+
return `${getBackendURL()}/v1/mcp/deployments/${deploymentID}`;
|
|
10433
9984
|
}
|
|
10434
|
-
function
|
|
10435
|
-
|
|
10436
|
-
|
|
10437
|
-
|
|
10438
|
-
|
|
10439
|
-
|
|
10440
|
-
if (typeof data.lastCheck === "number" && typeof data.latestSha === "string" && typeof data.installedSha === "string") {
|
|
10441
|
-
return data;
|
|
10442
|
-
}
|
|
10443
|
-
return null;
|
|
10444
|
-
} catch {
|
|
10445
|
-
return null;
|
|
9985
|
+
function mcpBaseURL() {
|
|
9986
|
+
return `${getBackendURL()}/v1/mcp`;
|
|
9987
|
+
}
|
|
9988
|
+
function mcpHeaders(apiKey) {
|
|
9989
|
+
if (!apiKey) {
|
|
9990
|
+
throw new Error("API key is required. Run 'dosu setup' to create one.");
|
|
10446
9991
|
}
|
|
9992
|
+
return { "X-Dosu-API-Key": apiKey };
|
|
10447
9993
|
}
|
|
10448
|
-
function
|
|
10449
|
-
|
|
10450
|
-
|
|
10451
|
-
|
|
10452
|
-
|
|
10453
|
-
}
|
|
10454
|
-
|
|
10455
|
-
|
|
9994
|
+
function mcpRemoteServer(url, apiKey) {
|
|
9995
|
+
const env = {};
|
|
9996
|
+
const headerArgs = Object.entries(mcpHeaders(apiKey)).flatMap(([key, value]) => {
|
|
9997
|
+
const envKey = key.toUpperCase().replace(/[^A-Z0-9]/g, "_");
|
|
9998
|
+
env[envKey] = value;
|
|
9999
|
+
return ["--header", `${key}:\${${envKey}}`];
|
|
10000
|
+
});
|
|
10001
|
+
return {
|
|
10002
|
+
args: [
|
|
10003
|
+
"-y",
|
|
10004
|
+
`mcp-remote@${MCP_REMOTE_VERSION}`,
|
|
10005
|
+
url,
|
|
10006
|
+
...headerArgs,
|
|
10007
|
+
"--transport",
|
|
10008
|
+
"http-only"
|
|
10009
|
+
],
|
|
10010
|
+
env
|
|
10011
|
+
};
|
|
10456
10012
|
}
|
|
10457
|
-
|
|
10458
|
-
|
|
10459
|
-
|
|
10013
|
+
function loadJSONConfig(path3) {
|
|
10014
|
+
if (!existsSync6(path3))
|
|
10015
|
+
return {};
|
|
10016
|
+
let data = readFileSync7(path3, "utf-8").trim();
|
|
10017
|
+
if (!data)
|
|
10018
|
+
return {};
|
|
10019
|
+
if (path3.endsWith(".jsonc")) {
|
|
10020
|
+
data = stripJSONComments(data);
|
|
10021
|
+
}
|
|
10460
10022
|
try {
|
|
10461
|
-
|
|
10462
|
-
signal: controller.signal,
|
|
10463
|
-
headers: {
|
|
10464
|
-
"User-Agent": `dosu-cli/${VERSION}`,
|
|
10465
|
-
Accept: "application/vnd.github+json"
|
|
10466
|
-
}
|
|
10467
|
-
});
|
|
10468
|
-
if (!resp.ok)
|
|
10469
|
-
return null;
|
|
10470
|
-
const data = await resp.json();
|
|
10471
|
-
const sha = data.sha;
|
|
10472
|
-
return typeof sha === "string" ? sha : null;
|
|
10023
|
+
return JSON.parse(data);
|
|
10473
10024
|
} catch {
|
|
10474
|
-
return
|
|
10475
|
-
} finally {
|
|
10476
|
-
clearTimeout(timeout);
|
|
10025
|
+
return {};
|
|
10477
10026
|
}
|
|
10478
10027
|
}
|
|
10479
|
-
function
|
|
10480
|
-
const
|
|
10481
|
-
|
|
10482
|
-
|
|
10483
|
-
|
|
10484
|
-
|
|
10485
|
-
|
|
10486
|
-
|
|
10487
|
-
|
|
10488
|
-
|
|
10489
|
-
|
|
10490
|
-
|
|
10491
|
-
|
|
10492
|
-
|
|
10493
|
-
|
|
10494
|
-
|
|
10495
|
-
const fresh = readSkillCache();
|
|
10496
|
-
writeSkillCache({
|
|
10497
|
-
lastCheck: Date.now(),
|
|
10498
|
-
latestSha: latest ?? fresh?.latestSha ?? "",
|
|
10499
|
-
installedSha: fresh?.installedSha ?? ""
|
|
10500
|
-
});
|
|
10501
|
-
if (latest) {
|
|
10502
|
-
logger.debug("skill-update-check", `Cached latest SHA: ${latest}`);
|
|
10028
|
+
function stripJSONComments(data) {
|
|
10029
|
+
const result = [];
|
|
10030
|
+
let i2 = 0;
|
|
10031
|
+
while (i2 < data.length) {
|
|
10032
|
+
if (data[i2] === '"') {
|
|
10033
|
+
result.push(data[i2]);
|
|
10034
|
+
i2++;
|
|
10035
|
+
while (i2 < data.length && data[i2] !== '"') {
|
|
10036
|
+
if (data[i2] === "\\") {
|
|
10037
|
+
result.push(data[i2]);
|
|
10038
|
+
i2++;
|
|
10039
|
+
if (i2 < data.length) {
|
|
10040
|
+
result.push(data[i2]);
|
|
10041
|
+
i2++;
|
|
10042
|
+
}
|
|
10043
|
+
continue;
|
|
10503
10044
|
}
|
|
10504
|
-
|
|
10505
|
-
|
|
10506
|
-
}
|
|
10045
|
+
result.push(data[i2]);
|
|
10046
|
+
i2++;
|
|
10047
|
+
}
|
|
10048
|
+
if (i2 < data.length) {
|
|
10049
|
+
result.push(data[i2]);
|
|
10050
|
+
i2++;
|
|
10051
|
+
}
|
|
10052
|
+
continue;
|
|
10053
|
+
}
|
|
10054
|
+
if (i2 + 1 < data.length && data[i2] === "/" && data[i2 + 1] === "/") {
|
|
10055
|
+
i2 += 2;
|
|
10056
|
+
while (i2 < data.length && data[i2] !== `
|
|
10057
|
+
`)
|
|
10058
|
+
i2++;
|
|
10059
|
+
continue;
|
|
10060
|
+
}
|
|
10061
|
+
if (i2 + 1 < data.length && data[i2] === "/" && data[i2 + 1] === "*") {
|
|
10062
|
+
i2 += 2;
|
|
10063
|
+
while (i2 + 1 < data.length && !(data[i2] === "*" && data[i2 + 1] === "/"))
|
|
10064
|
+
i2++;
|
|
10065
|
+
if (i2 + 1 < data.length)
|
|
10066
|
+
i2 += 2;
|
|
10067
|
+
continue;
|
|
10507
10068
|
}
|
|
10508
|
-
|
|
10509
|
-
|
|
10069
|
+
result.push(data[i2]);
|
|
10070
|
+
i2++;
|
|
10510
10071
|
}
|
|
10072
|
+
return result.join("");
|
|
10511
10073
|
}
|
|
10512
|
-
|
|
10513
|
-
|
|
10514
|
-
init_config();
|
|
10515
|
-
init_logger();
|
|
10516
|
-
import_picocolors12 = __toESM(require_picocolors(), 1);
|
|
10517
|
-
CHECK_INTERVAL_MS2 = 24 * 60 * 60 * 1000;
|
|
10518
|
-
});
|
|
10519
|
-
|
|
10520
|
-
// src/commands/skill.ts
|
|
10521
|
-
import { exec as exec2, execSync as execSync2 } from "node:child_process";
|
|
10522
|
-
import { homedir } from "node:os";
|
|
10523
|
-
import { join as join11 } from "node:path";
|
|
10524
|
-
function skillAgentIDsForProviders(providerIDs) {
|
|
10525
|
-
return [
|
|
10526
|
-
...new Set(providerIDs.map((providerID) => SKILL_AGENT_BY_PROVIDER[providerID]).filter((agent) => Boolean(agent)))
|
|
10527
|
-
];
|
|
10074
|
+
function saveJSONConfig(path3, cfg) {
|
|
10075
|
+
writeSecureFile(path3, JSON.stringify(cfg, null, 2));
|
|
10528
10076
|
}
|
|
10529
|
-
function
|
|
10530
|
-
const
|
|
10531
|
-
if (!
|
|
10532
|
-
|
|
10533
|
-
if (agentID === "claude-code") {
|
|
10534
|
-
const claudeConfigDir = process.env.CLAUDE_CONFIG_DIR?.trim() || join11(homedir(), ".claude");
|
|
10535
|
-
return { path: join11(claudeConfigDir, "skills", SKILL_NAME), symlink: true };
|
|
10536
|
-
}
|
|
10537
|
-
if (agentID === "windsurf") {
|
|
10538
|
-
return {
|
|
10539
|
-
path: join11(homedir(), ".codeium", "windsurf", "skills", SKILL_NAME),
|
|
10540
|
-
symlink: true
|
|
10541
|
-
};
|
|
10077
|
+
function writeSecureFile(path3, content) {
|
|
10078
|
+
const dir = dirname2(path3);
|
|
10079
|
+
if (!existsSync6(dir)) {
|
|
10080
|
+
mkdirSync5(dir, { recursive: true, mode: 448 });
|
|
10542
10081
|
}
|
|
10543
|
-
|
|
10544
|
-
path: join11(homedir(), ".agents", "skills", SKILL_NAME),
|
|
10545
|
-
symlink: false
|
|
10546
|
-
};
|
|
10547
|
-
}
|
|
10548
|
-
function skillAgentArgs(providerIDs) {
|
|
10549
|
-
const agents = providerIDs === undefined ? SUPPORTED_SKILL_AGENTS : skillAgentIDsForProviders(providerIDs);
|
|
10550
|
-
return agents.map((agent) => `-a ${agent}`).join(" ");
|
|
10082
|
+
writeFileAtomic.sync(path3, content, { mode: 384, chown: false });
|
|
10551
10083
|
}
|
|
10552
|
-
function
|
|
10553
|
-
|
|
10554
|
-
|
|
10555
|
-
|
|
10556
|
-
|
|
10557
|
-
|
|
10558
|
-
resolve();
|
|
10559
|
-
});
|
|
10560
|
-
});
|
|
10084
|
+
function isJSONKeyConfigured(configPath, topLevelKey) {
|
|
10085
|
+
const cfg = loadJSONConfig(configPath);
|
|
10086
|
+
const section = cfg[topLevelKey];
|
|
10087
|
+
if (typeof section !== "object" || section === null)
|
|
10088
|
+
return false;
|
|
10089
|
+
return "dosu" in section;
|
|
10561
10090
|
}
|
|
10562
|
-
|
|
10563
|
-
const
|
|
10564
|
-
|
|
10565
|
-
|
|
10566
|
-
|
|
10091
|
+
function installJSONServer(configPath, topKey, server) {
|
|
10092
|
+
const jsonCfg = loadJSONConfig(configPath);
|
|
10093
|
+
let section = jsonCfg[topKey];
|
|
10094
|
+
if (typeof section !== "object" || section === null) {
|
|
10095
|
+
section = {};
|
|
10567
10096
|
}
|
|
10097
|
+
section.dosu = server;
|
|
10098
|
+
jsonCfg[topKey] = section;
|
|
10099
|
+
saveJSONConfig(configPath, jsonCfg);
|
|
10100
|
+
}
|
|
10101
|
+
function removeJSONServer(configPath, topKey) {
|
|
10102
|
+
let jsonCfg;
|
|
10568
10103
|
try {
|
|
10569
|
-
|
|
10570
|
-
|
|
10571
|
-
|
|
10572
|
-
else
|
|
10573
|
-
execSync2(command, { stdio: "inherit" });
|
|
10574
|
-
} catch (err) {
|
|
10575
|
-
logger.error("skill", `Failed to install skill: ${err}`);
|
|
10576
|
-
return { success: false };
|
|
10104
|
+
jsonCfg = loadJSONConfig(configPath);
|
|
10105
|
+
} catch {
|
|
10106
|
+
return;
|
|
10577
10107
|
}
|
|
10578
|
-
const
|
|
10579
|
-
if (
|
|
10580
|
-
|
|
10581
|
-
lastCheck: Date.now(),
|
|
10582
|
-
latestSha: sha,
|
|
10583
|
-
installedSha: sha
|
|
10584
|
-
});
|
|
10585
|
-
return { success: true, sha };
|
|
10108
|
+
const section = jsonCfg[topKey];
|
|
10109
|
+
if (typeof section === "object" && section !== null) {
|
|
10110
|
+
delete section.dosu;
|
|
10586
10111
|
}
|
|
10587
|
-
|
|
10588
|
-
return { success: true };
|
|
10589
|
-
}
|
|
10590
|
-
function skillCommand() {
|
|
10591
|
-
const cmd = new Command("skill").description("Manage the Dosu agent skill");
|
|
10592
|
-
cmd.command("install").description("Install the Dosu skill for AI coding agents").action(async () => {
|
|
10593
|
-
console.log(`Installing skills from ${SKILL_REPO}...`);
|
|
10594
|
-
const result = await installSkill();
|
|
10595
|
-
if (result.success) {
|
|
10596
|
-
console.log(import_picocolors13.default.green(`
|
|
10597
|
-
✓ Skills installed successfully.`));
|
|
10598
|
-
} else {
|
|
10599
|
-
console.error(import_picocolors13.default.red(`
|
|
10600
|
-
Failed to install skill. Make sure npx is available.`));
|
|
10601
|
-
process.exit(1);
|
|
10602
|
-
}
|
|
10603
|
-
});
|
|
10604
|
-
cmd.command("remove").description("Remove the Dosu skill").action(() => {
|
|
10605
|
-
console.log(`Removing ${SKILL_NAME} skill...`);
|
|
10606
|
-
try {
|
|
10607
|
-
execSync2(`npx skills remove -g -s ${SKILL_NAME} -y`, {
|
|
10608
|
-
stdio: "inherit"
|
|
10609
|
-
});
|
|
10610
|
-
console.log(import_picocolors13.default.green(`
|
|
10611
|
-
✓ Skill "${SKILL_NAME}" removed.`));
|
|
10612
|
-
} catch {
|
|
10613
|
-
console.error(import_picocolors13.default.red(`
|
|
10614
|
-
Failed to remove skill.`));
|
|
10615
|
-
process.exit(1);
|
|
10616
|
-
}
|
|
10617
|
-
});
|
|
10618
|
-
cmd.command("update").description("Update the Dosu skill to the latest version").action(async () => {
|
|
10619
|
-
console.log(`Updating skills from ${SKILL_REPO}...`);
|
|
10620
|
-
const result = await installSkill();
|
|
10621
|
-
if (!result.success) {
|
|
10622
|
-
console.error(import_picocolors13.default.red(`
|
|
10623
|
-
Failed to update skill.`));
|
|
10624
|
-
process.exit(1);
|
|
10625
|
-
}
|
|
10626
|
-
console.log(import_picocolors13.default.green(`
|
|
10627
|
-
✓ Skills updated.`));
|
|
10628
|
-
});
|
|
10629
|
-
return cmd;
|
|
10112
|
+
saveJSONConfig(configPath, jsonCfg);
|
|
10630
10113
|
}
|
|
10631
|
-
var
|
|
10632
|
-
var
|
|
10633
|
-
|
|
10634
|
-
|
|
10635
|
-
init_skill_update_check();
|
|
10636
|
-
import_picocolors13 = __toESM(require_picocolors(), 1);
|
|
10637
|
-
SUPPORTED_SKILL_AGENTS = [
|
|
10638
|
-
"claude-code",
|
|
10639
|
-
"cursor",
|
|
10640
|
-
"gemini-cli",
|
|
10641
|
-
"codex",
|
|
10642
|
-
"windsurf",
|
|
10643
|
-
"zed",
|
|
10644
|
-
"cline",
|
|
10645
|
-
"github-copilot",
|
|
10646
|
-
"opencode",
|
|
10647
|
-
"antigravity"
|
|
10648
|
-
];
|
|
10649
|
-
SKILL_AGENT_BY_PROVIDER = {
|
|
10650
|
-
claude: "claude-code",
|
|
10651
|
-
cursor: "cursor",
|
|
10652
|
-
vscode: "github-copilot",
|
|
10653
|
-
gemini: "gemini-cli",
|
|
10654
|
-
codex: "codex",
|
|
10655
|
-
windsurf: "windsurf",
|
|
10656
|
-
zed: "zed",
|
|
10657
|
-
cline: "cline",
|
|
10658
|
-
"cline-cli": "cline",
|
|
10659
|
-
copilot: "github-copilot",
|
|
10660
|
-
opencode: "opencode",
|
|
10661
|
-
antigravity: "antigravity"
|
|
10662
|
-
};
|
|
10114
|
+
var import_write_file_atomic, writeFileAtomic, MCP_REMOTE_VERSION = "0.1.38";
|
|
10115
|
+
var init_config_helpers = __esm(() => {
|
|
10116
|
+
import_write_file_atomic = __toESM(require_lib(), 1);
|
|
10117
|
+
writeFileAtomic = import_write_file_atomic.default;
|
|
10663
10118
|
});
|
|
10664
10119
|
|
|
10665
|
-
// src/mcp/constants.ts
|
|
10666
|
-
var MCP_PROVIDER_SLUG = "dosu_mcp";
|
|
10667
|
-
|
|
10668
10120
|
// src/mcp/detect.ts
|
|
10669
|
-
import { existsSync as
|
|
10121
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
10670
10122
|
import { homedir as homedir2, platform as platform2 } from "node:os";
|
|
10671
|
-
import { delimiter, dirname as
|
|
10123
|
+
import { delimiter, dirname as dirname3, join as join7 } from "node:path";
|
|
10672
10124
|
function isInstalled(paths) {
|
|
10673
|
-
return paths.some((p2) =>
|
|
10125
|
+
return paths.some((p2) => existsSync7(expandHome(p2)));
|
|
10674
10126
|
}
|
|
10675
10127
|
function expandHome(path3) {
|
|
10676
10128
|
if (!path3.startsWith("~"))
|
|
10677
10129
|
return path3;
|
|
10678
|
-
return
|
|
10130
|
+
return join7(homedir2(), path3.slice(1));
|
|
10679
10131
|
}
|
|
10680
10132
|
function appSupportDir() {
|
|
10681
10133
|
switch (platform2()) {
|
|
10682
10134
|
case "darwin": {
|
|
10683
|
-
return
|
|
10135
|
+
return join7(homedir2(), "Library", "Application Support");
|
|
10684
10136
|
}
|
|
10685
10137
|
case "win32": {
|
|
10686
10138
|
return process.env.APPDATA ?? "";
|
|
@@ -10689,7 +10141,7 @@ function appSupportDir() {
|
|
|
10689
10141
|
const xdg = process.env.XDG_CONFIG_HOME;
|
|
10690
10142
|
if (xdg)
|
|
10691
10143
|
return xdg;
|
|
10692
|
-
return
|
|
10144
|
+
return join7(homedir2(), ".config");
|
|
10693
10145
|
}
|
|
10694
10146
|
}
|
|
10695
10147
|
}
|
|
@@ -10698,14 +10150,14 @@ function findNpx() {
|
|
|
10698
10150
|
for (const dir of (process.env.PATH ?? "").split(delimiter)) {
|
|
10699
10151
|
if (!dir)
|
|
10700
10152
|
continue;
|
|
10701
|
-
const candidate =
|
|
10702
|
-
if (
|
|
10153
|
+
const candidate = join7(dir, bin);
|
|
10154
|
+
if (existsSync7(candidate))
|
|
10703
10155
|
return candidate;
|
|
10704
10156
|
}
|
|
10705
10157
|
throw new Error("npx not found on PATH — Node.js is required (the MCP entry runs `npx mcp-remote`).");
|
|
10706
10158
|
}
|
|
10707
10159
|
function npxPathEnv(npx) {
|
|
10708
|
-
return [
|
|
10160
|
+
return [dirname3(npx), "/usr/bin", "/bin"].join(delimiter);
|
|
10709
10161
|
}
|
|
10710
10162
|
var init_detect = () => {};
|
|
10711
10163
|
|
|
@@ -10784,7 +10236,7 @@ var init_antigravity = __esm(() => {
|
|
|
10784
10236
|
});
|
|
10785
10237
|
|
|
10786
10238
|
// src/mcp/providers/claude.ts
|
|
10787
|
-
import { join as
|
|
10239
|
+
import { join as join8 } from "node:path";
|
|
10788
10240
|
var ClaudeProvider = () => createJSONProvider({
|
|
10789
10241
|
providerName: "Claude Code",
|
|
10790
10242
|
providerID: "claude",
|
|
@@ -10793,24 +10245,24 @@ var ClaudeProvider = () => createJSONProvider({
|
|
|
10793
10245
|
paths: ["~/.claude"],
|
|
10794
10246
|
globalPath: "~/.claude.json",
|
|
10795
10247
|
topKey: "mcpServers",
|
|
10796
|
-
localConfigPath: (cwd) =>
|
|
10248
|
+
localConfigPath: (cwd) => join8(cwd, ".mcp.json")
|
|
10797
10249
|
});
|
|
10798
10250
|
var init_claude = __esm(() => {
|
|
10799
10251
|
init_base();
|
|
10800
10252
|
});
|
|
10801
10253
|
|
|
10802
10254
|
// src/mcp/providers/claude-desktop.ts
|
|
10803
|
-
import { join as
|
|
10255
|
+
import { join as join9 } from "node:path";
|
|
10804
10256
|
function configPath() {
|
|
10805
|
-
return
|
|
10257
|
+
return join9(appSupportDir(), "Claude", "claude_desktop_config.json");
|
|
10806
10258
|
}
|
|
10807
10259
|
var ClaudeDesktopProvider = () => ({
|
|
10808
10260
|
name: () => "Claude Desktop",
|
|
10809
10261
|
id: () => "claude-desktop",
|
|
10810
10262
|
supportsLocal: () => false,
|
|
10811
10263
|
priority: () => 2,
|
|
10812
|
-
detectPaths: () => [
|
|
10813
|
-
isInstalled: () => isInstalled([
|
|
10264
|
+
detectPaths: () => [join9(appSupportDir(), "Claude")],
|
|
10265
|
+
isInstalled: () => isInstalled([join9(appSupportDir(), "Claude")]),
|
|
10814
10266
|
globalConfigPath: () => configPath(),
|
|
10815
10267
|
isConfigured: () => isJSONKeyConfigured(configPath(), "mcpServers"),
|
|
10816
10268
|
install(cfg, global) {
|
|
@@ -10840,14 +10292,14 @@ var init_claude_desktop = __esm(() => {
|
|
|
10840
10292
|
});
|
|
10841
10293
|
|
|
10842
10294
|
// src/mcp/providers/cline.ts
|
|
10843
|
-
import { join as
|
|
10844
|
-
var extensionDir = () =>
|
|
10295
|
+
import { join as join10 } from "node:path";
|
|
10296
|
+
var extensionDir = () => join10(appSupportDir(), "Code", "User", "globalStorage", "saoudrizwan.claude-dev"), ClineProvider = () => createJSONProvider({
|
|
10845
10297
|
providerName: "Cline",
|
|
10846
10298
|
providerID: "cline",
|
|
10847
10299
|
local: false,
|
|
10848
10300
|
priorityValue: 11,
|
|
10849
10301
|
paths: [extensionDir()],
|
|
10850
|
-
globalPath:
|
|
10302
|
+
globalPath: join10(extensionDir(), "settings", "cline_mcp_settings.json"),
|
|
10851
10303
|
topKey: "mcpServers",
|
|
10852
10304
|
buildServer: (cfg) => ({
|
|
10853
10305
|
url: mcpURL(cfg.active_account.target.deployment_id),
|
|
@@ -10863,7 +10315,7 @@ var init_cline = __esm(() => {
|
|
|
10863
10315
|
});
|
|
10864
10316
|
|
|
10865
10317
|
// src/mcp/providers/cline-cli.ts
|
|
10866
|
-
import { join as
|
|
10318
|
+
import { join as join11 } from "node:path";
|
|
10867
10319
|
function clineDir() {
|
|
10868
10320
|
return process.env.CLINE_DIR ?? expandHome("~/.cline");
|
|
10869
10321
|
}
|
|
@@ -10873,7 +10325,7 @@ var ClineCliProvider = () => createJSONProvider({
|
|
|
10873
10325
|
local: false,
|
|
10874
10326
|
priorityValue: 12,
|
|
10875
10327
|
paths: [clineDir()],
|
|
10876
|
-
globalPath:
|
|
10328
|
+
globalPath: join11(clineDir(), "data", "settings", "cline_mcp_settings.json"),
|
|
10877
10329
|
topKey: "mcpServers",
|
|
10878
10330
|
buildServer: (cfg) => ({
|
|
10879
10331
|
url: mcpURL(cfg.active_account.target.deployment_id),
|
|
@@ -10889,20 +10341,20 @@ var init_cline_cli = __esm(() => {
|
|
|
10889
10341
|
});
|
|
10890
10342
|
|
|
10891
10343
|
// src/mcp/providers/codex.ts
|
|
10892
|
-
import { existsSync as
|
|
10893
|
-
import { join as
|
|
10344
|
+
import { existsSync as existsSync8, readFileSync as readFileSync8 } from "node:fs";
|
|
10345
|
+
import { join as join12 } from "node:path";
|
|
10894
10346
|
function codexHome() {
|
|
10895
10347
|
return process.env.CODEX_HOME ?? expandHome("~/.codex");
|
|
10896
10348
|
}
|
|
10897
10349
|
function getConfigPath2(global) {
|
|
10898
10350
|
if (global)
|
|
10899
|
-
return
|
|
10900
|
-
return
|
|
10351
|
+
return join12(codexHome(), "config.toml");
|
|
10352
|
+
return join12(process.cwd(), ".codex", "config.toml");
|
|
10901
10353
|
}
|
|
10902
10354
|
function readTOML(path3) {
|
|
10903
|
-
if (!
|
|
10355
|
+
if (!existsSync8(path3))
|
|
10904
10356
|
return "";
|
|
10905
|
-
return
|
|
10357
|
+
return readFileSync8(path3, "utf-8");
|
|
10906
10358
|
}
|
|
10907
10359
|
function writeTOML(path3, content) {
|
|
10908
10360
|
writeSecureFile(path3, content);
|
|
@@ -10965,9 +10417,9 @@ var CodexProvider = () => ({
|
|
|
10965
10417
|
priority: () => 8,
|
|
10966
10418
|
detectPaths: () => ["~/.codex"],
|
|
10967
10419
|
isInstalled: () => isInstalled(["~/.codex"]),
|
|
10968
|
-
globalConfigPath: () =>
|
|
10420
|
+
globalConfigPath: () => join12(codexHome(), "config.toml"),
|
|
10969
10421
|
isConfigured: () => {
|
|
10970
|
-
const content = readTOML(
|
|
10422
|
+
const content = readTOML(join12(codexHome(), "config.toml"));
|
|
10971
10423
|
return content.includes("[mcp_servers.dosu]");
|
|
10972
10424
|
},
|
|
10973
10425
|
install(cfg, global) {
|
|
@@ -10982,17 +10434,17 @@ var CodexProvider = () => ({
|
|
|
10982
10434
|
writeTOML(path3, removeDosuFromTOML(content));
|
|
10983
10435
|
}
|
|
10984
10436
|
});
|
|
10985
|
-
var
|
|
10437
|
+
var init_codex = __esm(() => {
|
|
10986
10438
|
init_config();
|
|
10987
10439
|
init_config_helpers();
|
|
10988
10440
|
init_detect();
|
|
10989
10441
|
});
|
|
10990
10442
|
|
|
10991
10443
|
// src/mcp/providers/copilot.ts
|
|
10992
|
-
import { join as
|
|
10444
|
+
import { join as join13 } from "node:path";
|
|
10993
10445
|
function globalPath() {
|
|
10994
10446
|
if (process.env.XDG_CONFIG_HOME) {
|
|
10995
|
-
return
|
|
10447
|
+
return join13(process.env.XDG_CONFIG_HOME, "mcp-config.json");
|
|
10996
10448
|
}
|
|
10997
10449
|
return expandHome("~/.copilot/mcp-config.json");
|
|
10998
10450
|
}
|
|
@@ -11023,7 +10475,7 @@ var CopilotProvider = () => ({
|
|
|
11023
10475
|
};
|
|
11024
10476
|
installJSONServer(globalPath(), "mcpServers", server);
|
|
11025
10477
|
} else {
|
|
11026
|
-
const configPath2 =
|
|
10478
|
+
const configPath2 = join13(process.cwd(), ".vscode", "mcp.json");
|
|
11027
10479
|
const server = {
|
|
11028
10480
|
type: "http",
|
|
11029
10481
|
url,
|
|
@@ -11036,7 +10488,7 @@ var CopilotProvider = () => ({
|
|
|
11036
10488
|
if (global) {
|
|
11037
10489
|
removeJSONServer(globalPath(), "mcpServers");
|
|
11038
10490
|
} else {
|
|
11039
|
-
const configPath2 =
|
|
10491
|
+
const configPath2 = join13(process.cwd(), ".vscode", "mcp.json");
|
|
11040
10492
|
removeJSONServer(configPath2, "servers");
|
|
11041
10493
|
}
|
|
11042
10494
|
}
|
|
@@ -11048,7 +10500,7 @@ var init_copilot = __esm(() => {
|
|
|
11048
10500
|
});
|
|
11049
10501
|
|
|
11050
10502
|
// src/mcp/providers/cursor.ts
|
|
11051
|
-
import { join as
|
|
10503
|
+
import { join as join14 } from "node:path";
|
|
11052
10504
|
var CursorProvider = () => createJSONProvider({
|
|
11053
10505
|
providerName: "Cursor",
|
|
11054
10506
|
providerID: "cursor",
|
|
@@ -11061,7 +10513,7 @@ var CursorProvider = () => createJSONProvider({
|
|
|
11061
10513
|
url: mcpURL(cfg.active_account.target.deployment_id),
|
|
11062
10514
|
headers: mcpHeaders(cfg.active_account.target.api_key)
|
|
11063
10515
|
}),
|
|
11064
|
-
localConfigPath: (cwd) =>
|
|
10516
|
+
localConfigPath: (cwd) => join14(cwd, ".cursor", "mcp.json")
|
|
11065
10517
|
});
|
|
11066
10518
|
var init_cursor = __esm(() => {
|
|
11067
10519
|
init_config_helpers();
|
|
@@ -11069,7 +10521,7 @@ var init_cursor = __esm(() => {
|
|
|
11069
10521
|
});
|
|
11070
10522
|
|
|
11071
10523
|
// src/mcp/providers/factory.ts
|
|
11072
|
-
import { join as
|
|
10524
|
+
import { join as join15 } from "node:path";
|
|
11073
10525
|
var FactoryProvider = () => createJSONProvider({
|
|
11074
10526
|
providerName: "Factory",
|
|
11075
10527
|
providerID: "factory",
|
|
@@ -11078,14 +10530,14 @@ var FactoryProvider = () => createJSONProvider({
|
|
|
11078
10530
|
paths: ["~/.factory"],
|
|
11079
10531
|
globalPath: "~/.factory/mcp.json",
|
|
11080
10532
|
topKey: "mcpServers",
|
|
11081
|
-
localConfigPath: (cwd) =>
|
|
10533
|
+
localConfigPath: (cwd) => join15(cwd, ".factory", "mcp.json")
|
|
11082
10534
|
});
|
|
11083
|
-
var
|
|
10535
|
+
var init_factory = __esm(() => {
|
|
11084
10536
|
init_base();
|
|
11085
10537
|
});
|
|
11086
10538
|
|
|
11087
10539
|
// src/mcp/providers/gemini.ts
|
|
11088
|
-
import { join as
|
|
10540
|
+
import { join as join16 } from "node:path";
|
|
11089
10541
|
var GeminiProvider = () => createJSONProvider({
|
|
11090
10542
|
providerName: "Gemini CLI",
|
|
11091
10543
|
providerID: "gemini",
|
|
@@ -11094,7 +10546,7 @@ var GeminiProvider = () => createJSONProvider({
|
|
|
11094
10546
|
paths: ["~/.gemini"],
|
|
11095
10547
|
globalPath: "~/.gemini/settings.json",
|
|
11096
10548
|
topKey: "mcpServers",
|
|
11097
|
-
localConfigPath: (cwd) =>
|
|
10549
|
+
localConfigPath: (cwd) => join16(cwd, ".gemini", "settings.json")
|
|
11098
10550
|
});
|
|
11099
10551
|
var init_gemini = __esm(() => {
|
|
11100
10552
|
init_base();
|
|
@@ -11144,14 +10596,14 @@ var init_manual = __esm(() => {
|
|
|
11144
10596
|
});
|
|
11145
10597
|
|
|
11146
10598
|
// src/mcp/providers/mcporter.ts
|
|
11147
|
-
import { existsSync as
|
|
11148
|
-
import { join as
|
|
10599
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
10600
|
+
import { join as join17 } from "node:path";
|
|
11149
10601
|
function resolveGlobalConfigPath() {
|
|
11150
10602
|
const jsonPath = expandHome("~/.mcporter/mcporter.json");
|
|
11151
|
-
if (
|
|
10603
|
+
if (existsSync9(jsonPath))
|
|
11152
10604
|
return jsonPath;
|
|
11153
10605
|
const jsoncPath = expandHome("~/.mcporter/mcporter.jsonc");
|
|
11154
|
-
if (
|
|
10606
|
+
if (existsSync9(jsoncPath))
|
|
11155
10607
|
return jsoncPath;
|
|
11156
10608
|
return jsonPath;
|
|
11157
10609
|
}
|
|
@@ -11172,7 +10624,7 @@ var MCPorterProvider = () => ({
|
|
|
11172
10624
|
globalConfigPath: () => resolveGlobalConfigPath(),
|
|
11173
10625
|
isConfigured: () => isJSONKeyConfigured(resolveGlobalConfigPath(), "mcpServers"),
|
|
11174
10626
|
install(cfg, global) {
|
|
11175
|
-
const configPath2 = global ? resolveGlobalConfigPath() :
|
|
10627
|
+
const configPath2 = global ? resolveGlobalConfigPath() : join17(process.cwd(), "config", "mcporter.json");
|
|
11176
10628
|
const server = {
|
|
11177
10629
|
type: "http",
|
|
11178
10630
|
url: mcpEndpoint4(cfg),
|
|
@@ -11181,7 +10633,7 @@ var MCPorterProvider = () => ({
|
|
|
11181
10633
|
installJSONServer(configPath2, "mcpServers", server);
|
|
11182
10634
|
},
|
|
11183
10635
|
remove(global) {
|
|
11184
|
-
const configPath2 = global ? resolveGlobalConfigPath() :
|
|
10636
|
+
const configPath2 = global ? resolveGlobalConfigPath() : join17(process.cwd(), "config", "mcporter.json");
|
|
11185
10637
|
removeJSONServer(configPath2, "mcpServers");
|
|
11186
10638
|
}
|
|
11187
10639
|
});
|
|
@@ -11192,7 +10644,7 @@ var init_mcporter = __esm(() => {
|
|
|
11192
10644
|
});
|
|
11193
10645
|
|
|
11194
10646
|
// src/mcp/providers/opencode.ts
|
|
11195
|
-
import { join as
|
|
10647
|
+
import { join as join18 } from "node:path";
|
|
11196
10648
|
var OpenCodeProvider = () => createJSONProvider({
|
|
11197
10649
|
providerName: "OpenCode",
|
|
11198
10650
|
providerID: "opencode",
|
|
@@ -11207,7 +10659,7 @@ var OpenCodeProvider = () => createJSONProvider({
|
|
|
11207
10659
|
enabled: true,
|
|
11208
10660
|
headers: mcpHeaders(cfg.active_account.target.api_key)
|
|
11209
10661
|
}),
|
|
11210
|
-
localConfigPath: (cwd) =>
|
|
10662
|
+
localConfigPath: (cwd) => join18(cwd, "opencode.json")
|
|
11211
10663
|
});
|
|
11212
10664
|
var init_opencode = __esm(() => {
|
|
11213
10665
|
init_config_helpers();
|
|
@@ -11215,16 +10667,16 @@ var init_opencode = __esm(() => {
|
|
|
11215
10667
|
});
|
|
11216
10668
|
|
|
11217
10669
|
// src/mcp/providers/vscode.ts
|
|
11218
|
-
import { join as
|
|
10670
|
+
import { join as join19 } from "node:path";
|
|
11219
10671
|
var VSCodeProvider = () => createJSONProvider({
|
|
11220
10672
|
providerName: "VS Code",
|
|
11221
10673
|
providerID: "vscode",
|
|
11222
10674
|
local: true,
|
|
11223
10675
|
priorityValue: 6,
|
|
11224
|
-
paths: [
|
|
11225
|
-
globalPath:
|
|
10676
|
+
paths: [join19(appSupportDir(), "Code")],
|
|
10677
|
+
globalPath: join19(appSupportDir(), "Code", "User", "mcp.json"),
|
|
11226
10678
|
topKey: "servers",
|
|
11227
|
-
localConfigPath: (cwd) =>
|
|
10679
|
+
localConfigPath: (cwd) => join19(cwd, ".vscode", "mcp.json")
|
|
11228
10680
|
});
|
|
11229
10681
|
var init_vscode = __esm(() => {
|
|
11230
10682
|
init_detect();
|
|
@@ -11233,14 +10685,14 @@ var init_vscode = __esm(() => {
|
|
|
11233
10685
|
|
|
11234
10686
|
// src/mcp/providers/windsurf.ts
|
|
11235
10687
|
import { homedir as homedir3 } from "node:os";
|
|
11236
|
-
import { join as
|
|
10688
|
+
import { join as join20 } from "node:path";
|
|
11237
10689
|
var WindsurfProvider = () => createJSONProvider({
|
|
11238
10690
|
providerName: "Windsurf",
|
|
11239
10691
|
providerID: "windsurf",
|
|
11240
10692
|
local: false,
|
|
11241
10693
|
priorityValue: 9,
|
|
11242
|
-
paths: [
|
|
11243
|
-
globalPath:
|
|
10694
|
+
paths: [join20(homedir3(), ".codeium", "windsurf")],
|
|
10695
|
+
globalPath: join20(homedir3(), ".codeium", "windsurf", "mcp_config.json"),
|
|
11244
10696
|
topKey: "mcpServers"
|
|
11245
10697
|
});
|
|
11246
10698
|
var init_windsurf = __esm(() => {
|
|
@@ -11249,12 +10701,12 @@ var init_windsurf = __esm(() => {
|
|
|
11249
10701
|
|
|
11250
10702
|
// src/mcp/providers/zed.ts
|
|
11251
10703
|
import { platform as platform3 } from "node:os";
|
|
11252
|
-
import { join as
|
|
10704
|
+
import { join as join21 } from "node:path";
|
|
11253
10705
|
function zedConfigDir() {
|
|
11254
10706
|
const os2 = platform3();
|
|
11255
10707
|
if (os2 === "darwin" || os2 === "win32")
|
|
11256
|
-
return
|
|
11257
|
-
return
|
|
10708
|
+
return join21(appSupportDir(), "Zed");
|
|
10709
|
+
return join21(appSupportDir(), "zed");
|
|
11258
10710
|
}
|
|
11259
10711
|
var ZedProvider = () => createJSONProvider({
|
|
11260
10712
|
providerName: "Zed",
|
|
@@ -11262,7 +10714,7 @@ var ZedProvider = () => createJSONProvider({
|
|
|
11262
10714
|
local: true,
|
|
11263
10715
|
priorityValue: 10,
|
|
11264
10716
|
paths: [zedConfigDir()],
|
|
11265
|
-
globalPath:
|
|
10717
|
+
globalPath: join21(zedConfigDir(), "settings.json"),
|
|
11266
10718
|
topKey: "context_servers",
|
|
11267
10719
|
buildServer: (cfg) => ({
|
|
11268
10720
|
source: "custom",
|
|
@@ -11270,7 +10722,7 @@ var ZedProvider = () => createJSONProvider({
|
|
|
11270
10722
|
url: mcpURL(cfg.active_account.target.deployment_id),
|
|
11271
10723
|
headers: mcpHeaders(cfg.active_account.target.api_key)
|
|
11272
10724
|
}),
|
|
11273
|
-
localConfigPath: (cwd) =>
|
|
10725
|
+
localConfigPath: (cwd) => join21(cwd, ".zed", "settings.json")
|
|
11274
10726
|
});
|
|
11275
10727
|
var init_zed = __esm(() => {
|
|
11276
10728
|
init_config_helpers();
|
|
@@ -11315,10 +10767,10 @@ var init_providers = __esm(() => {
|
|
|
11315
10767
|
init_claude_desktop();
|
|
11316
10768
|
init_cline();
|
|
11317
10769
|
init_cline_cli();
|
|
11318
|
-
|
|
10770
|
+
init_codex();
|
|
11319
10771
|
init_copilot();
|
|
11320
10772
|
init_cursor();
|
|
11321
|
-
|
|
10773
|
+
init_factory();
|
|
11322
10774
|
init_gemini();
|
|
11323
10775
|
init_manual();
|
|
11324
10776
|
init_mcporter();
|
|
@@ -11329,14 +10781,14 @@ var init_providers = __esm(() => {
|
|
|
11329
10781
|
});
|
|
11330
10782
|
|
|
11331
10783
|
// src/rules/installer.ts
|
|
11332
|
-
import { existsSync as
|
|
10784
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync9, unlinkSync, writeFileSync as writeFileSync6 } from "node:fs";
|
|
11333
10785
|
import { homedir as homedir4 } from "node:os";
|
|
11334
|
-
import { dirname as
|
|
10786
|
+
import { dirname as dirname4, join as join22 } from "node:path";
|
|
11335
10787
|
function claudeConfigDir() {
|
|
11336
|
-
return process.env.CLAUDE_CONFIG_DIR ||
|
|
10788
|
+
return process.env.CLAUDE_CONFIG_DIR || join22(homedir4(), ".claude");
|
|
11337
10789
|
}
|
|
11338
10790
|
function codexHome2() {
|
|
11339
|
-
return process.env.CODEX_HOME ||
|
|
10791
|
+
return process.env.CODEX_HOME || join22(homedir4(), ".codex");
|
|
11340
10792
|
}
|
|
11341
10793
|
function isRuleAgent(agent) {
|
|
11342
10794
|
return Object.hasOwn(RULE_TARGETS, agent);
|
|
@@ -11362,17 +10814,17 @@ async function fetchDosuRule(fetchImpl = globalThis.fetch) {
|
|
|
11362
10814
|
return normalizeRule(FALLBACK_DOSU_RULE);
|
|
11363
10815
|
}
|
|
11364
10816
|
function ensureParent(path3) {
|
|
11365
|
-
|
|
10817
|
+
mkdirSync6(dirname4(path3), { recursive: true });
|
|
11366
10818
|
}
|
|
11367
10819
|
function writeIfChanged(path3, content) {
|
|
11368
|
-
if (!
|
|
10820
|
+
if (!existsSync10(path3)) {
|
|
11369
10821
|
ensureParent(path3);
|
|
11370
|
-
|
|
10822
|
+
writeFileSync6(path3, content, "utf-8");
|
|
11371
10823
|
return "created";
|
|
11372
10824
|
}
|
|
11373
|
-
if (
|
|
10825
|
+
if (readFileSync9(path3, "utf-8") === content)
|
|
11374
10826
|
return "unchanged";
|
|
11375
|
-
|
|
10827
|
+
writeFileSync6(path3, content, "utf-8");
|
|
11376
10828
|
return "updated";
|
|
11377
10829
|
}
|
|
11378
10830
|
function findSection(content) {
|
|
@@ -11398,14 +10850,14 @@ function buildSection(content, lineEnding = `
|
|
|
11398
10850
|
return `${DOSU_RULE_SECTION_START}${lineEnding}${body}${lineEnding}${DOSU_RULE_SECTION_END}`;
|
|
11399
10851
|
}
|
|
11400
10852
|
function upsertSection(path3, content) {
|
|
11401
|
-
if (!
|
|
10853
|
+
if (!existsSync10(path3)) {
|
|
11402
10854
|
const section2 = buildSection(content);
|
|
11403
10855
|
ensureParent(path3);
|
|
11404
|
-
|
|
10856
|
+
writeFileSync6(path3, `${section2}
|
|
11405
10857
|
`, "utf-8");
|
|
11406
10858
|
return "created";
|
|
11407
10859
|
}
|
|
11408
|
-
const existing =
|
|
10860
|
+
const existing = readFileSync9(path3, "utf-8");
|
|
11409
10861
|
const lineEnding = lineEndingFor(existing);
|
|
11410
10862
|
const section = buildSection(content, lineEnding);
|
|
11411
10863
|
const location = findSection(existing);
|
|
@@ -11418,7 +10870,7 @@ function upsertSection(path3, content) {
|
|
|
11418
10870
|
}
|
|
11419
10871
|
if (next === existing)
|
|
11420
10872
|
return "unchanged";
|
|
11421
|
-
|
|
10873
|
+
writeFileSync6(path3, next, "utf-8");
|
|
11422
10874
|
return "updated";
|
|
11423
10875
|
}
|
|
11424
10876
|
function installRuleForAgent(agent, content) {
|
|
@@ -11435,13 +10887,13 @@ function removeRuleForAgent(agent) {
|
|
|
11435
10887
|
return null;
|
|
11436
10888
|
const target = RULE_TARGETS[agent];
|
|
11437
10889
|
const path3 = target.path();
|
|
11438
|
-
if (!
|
|
10890
|
+
if (!existsSync10(path3))
|
|
11439
10891
|
return { agent, path: path3, action: "not_found" };
|
|
11440
10892
|
if (target.kind === "file") {
|
|
11441
10893
|
unlinkSync(path3);
|
|
11442
10894
|
return { agent, path: path3, action: "removed" };
|
|
11443
10895
|
}
|
|
11444
|
-
const existing =
|
|
10896
|
+
const existing = readFileSync9(path3, "utf-8");
|
|
11445
10897
|
const lineEnding = lineEndingFor(existing);
|
|
11446
10898
|
const location = findSection(existing);
|
|
11447
10899
|
if (!location)
|
|
@@ -11450,7 +10902,7 @@ function removeRuleForAgent(agent) {
|
|
|
11450
10902
|
if (!next) {
|
|
11451
10903
|
unlinkSync(path3);
|
|
11452
10904
|
} else {
|
|
11453
|
-
|
|
10905
|
+
writeFileSync6(path3, `${next}${lineEnding}`, "utf-8");
|
|
11454
10906
|
}
|
|
11455
10907
|
return { agent, path: path3, action: "removed" };
|
|
11456
10908
|
}
|
|
@@ -11478,36 +10930,36 @@ var init_installer = __esm(() => {
|
|
|
11478
10930
|
RULE_TARGETS = {
|
|
11479
10931
|
claude: {
|
|
11480
10932
|
kind: "file",
|
|
11481
|
-
path: () =>
|
|
10933
|
+
path: () => join22(claudeConfigDir(), "rules", "dosu.md")
|
|
11482
10934
|
},
|
|
11483
10935
|
cursor: {
|
|
11484
10936
|
kind: "file",
|
|
11485
|
-
path: () =>
|
|
10937
|
+
path: () => join22(homedir4(), ".cursor", "rules", "dosu.mdc"),
|
|
11486
10938
|
cursorFrontmatter: true
|
|
11487
10939
|
},
|
|
11488
10940
|
codex: {
|
|
11489
10941
|
kind: "section",
|
|
11490
|
-
path: () =>
|
|
10942
|
+
path: () => join22(codexHome2(), "AGENTS.md")
|
|
11491
10943
|
},
|
|
11492
10944
|
opencode: {
|
|
11493
10945
|
kind: "section",
|
|
11494
|
-
path: () =>
|
|
10946
|
+
path: () => join22(homedir4(), ".config", "opencode", "AGENTS.md")
|
|
11495
10947
|
},
|
|
11496
10948
|
gemini: {
|
|
11497
10949
|
kind: "section",
|
|
11498
|
-
path: () =>
|
|
10950
|
+
path: () => join22(homedir4(), ".gemini", "GEMINI.md")
|
|
11499
10951
|
},
|
|
11500
10952
|
antigravity: {
|
|
11501
10953
|
kind: "section",
|
|
11502
|
-
path: () =>
|
|
10954
|
+
path: () => join22(homedir4(), ".gemini", "GEMINI.md")
|
|
11503
10955
|
}
|
|
11504
10956
|
};
|
|
11505
10957
|
});
|
|
11506
10958
|
|
|
11507
10959
|
// src/setup/agents-md-step.ts
|
|
11508
10960
|
import { execSync as execSync3 } from "node:child_process";
|
|
11509
|
-
import { existsSync as
|
|
11510
|
-
import { join as
|
|
10961
|
+
import { existsSync as existsSync11, readFileSync as readFileSync10, writeFileSync as writeFileSync7 } from "node:fs";
|
|
10962
|
+
import { join as join23 } from "node:path";
|
|
11511
10963
|
function inGitWorkTree(cwd = process.cwd()) {
|
|
11512
10964
|
try {
|
|
11513
10965
|
const stdout2 = execSync3("git rev-parse --is-inside-work-tree", {
|
|
@@ -11545,14 +10997,14 @@ function findSection2(content) {
|
|
|
11545
10997
|
return { start, end };
|
|
11546
10998
|
}
|
|
11547
10999
|
function upsertDosuAgentsSection(cwd = process.cwd(), content = FALLBACK_DOSU_RULE) {
|
|
11548
|
-
const path3 =
|
|
11549
|
-
if (!
|
|
11000
|
+
const path3 = join23(cwd, "AGENTS.md");
|
|
11001
|
+
if (!existsSync11(path3)) {
|
|
11550
11002
|
const section2 = buildDosuAgentsSection(content);
|
|
11551
|
-
|
|
11003
|
+
writeFileSync7(path3, `${section2}
|
|
11552
11004
|
`);
|
|
11553
11005
|
return { path: path3, action: "created" };
|
|
11554
11006
|
}
|
|
11555
|
-
const existing =
|
|
11007
|
+
const existing = readFileSync10(path3, "utf-8");
|
|
11556
11008
|
const lineEnding = lineEndingFor2(existing);
|
|
11557
11009
|
const section = renderDosuAgentsSection(content, lineEnding);
|
|
11558
11010
|
const location = findSection2(existing);
|
|
@@ -11564,7 +11016,7 @@ function upsertDosuAgentsSection(cwd = process.cwd(), content = FALLBACK_DOSU_RU
|
|
|
11564
11016
|
}
|
|
11565
11017
|
if (next === existing)
|
|
11566
11018
|
return { path: path3, action: "unchanged" };
|
|
11567
|
-
|
|
11019
|
+
writeFileSync7(path3, next);
|
|
11568
11020
|
return { path: path3, action: "updated" };
|
|
11569
11021
|
}
|
|
11570
11022
|
async function stepUpdateAgentsMd(cwd = process.cwd(), content) {
|
|
@@ -11595,17 +11047,17 @@ var init_agents_md_step = __esm(() => {
|
|
|
11595
11047
|
// src/telemetry/settings.ts
|
|
11596
11048
|
import { randomUUID } from "node:crypto";
|
|
11597
11049
|
import {
|
|
11598
|
-
existsSync as
|
|
11050
|
+
existsSync as existsSync12,
|
|
11599
11051
|
lstatSync,
|
|
11600
|
-
mkdirSync as
|
|
11601
|
-
readFileSync as
|
|
11602
|
-
renameSync as
|
|
11052
|
+
mkdirSync as mkdirSync7,
|
|
11053
|
+
readFileSync as readFileSync11,
|
|
11054
|
+
renameSync as renameSync2,
|
|
11603
11055
|
unlinkSync as unlinkSync2,
|
|
11604
|
-
writeFileSync as
|
|
11056
|
+
writeFileSync as writeFileSync8
|
|
11605
11057
|
} from "node:fs";
|
|
11606
|
-
import { join as
|
|
11058
|
+
import { join as join24 } from "node:path";
|
|
11607
11059
|
function getTelemetrySettingsPath() {
|
|
11608
|
-
return
|
|
11060
|
+
return join24(getConfigDir(), TELEMETRY_SETTINGS_FILENAME);
|
|
11609
11061
|
}
|
|
11610
11062
|
function loadTelemetrySettings() {
|
|
11611
11063
|
return readTelemetrySettings().settings;
|
|
@@ -11673,7 +11125,7 @@ function readTelemetrySettings() {
|
|
|
11673
11125
|
}
|
|
11674
11126
|
let content;
|
|
11675
11127
|
try {
|
|
11676
|
-
content =
|
|
11128
|
+
content = readFileSync11(path3, "utf-8");
|
|
11677
11129
|
} catch {
|
|
11678
11130
|
return { status: "error", settings: disabledTelemetrySettings() };
|
|
11679
11131
|
}
|
|
@@ -11705,14 +11157,14 @@ function writeTelemetrySettings(settings2) {
|
|
|
11705
11157
|
const dir = getConfigDir();
|
|
11706
11158
|
const temporaryPath = `${path3}.${process.pid}.${randomUUID()}.tmp`;
|
|
11707
11159
|
try {
|
|
11708
|
-
if (!
|
|
11709
|
-
|
|
11710
|
-
|
|
11160
|
+
if (!existsSync12(dir))
|
|
11161
|
+
mkdirSync7(dir, { recursive: true, mode: 448 });
|
|
11162
|
+
writeFileSync8(temporaryPath, `${JSON.stringify(settings2, null, 2)}
|
|
11711
11163
|
`, {
|
|
11712
11164
|
flag: "wx",
|
|
11713
11165
|
mode: 384
|
|
11714
11166
|
});
|
|
11715
|
-
|
|
11167
|
+
renameSync2(temporaryPath, path3);
|
|
11716
11168
|
return true;
|
|
11717
11169
|
} catch {
|
|
11718
11170
|
try {
|
|
@@ -11751,11 +11203,11 @@ var init_settings = __esm(() => {
|
|
|
11751
11203
|
});
|
|
11752
11204
|
|
|
11753
11205
|
// src/telemetry/debug-id.ts
|
|
11754
|
-
var BUNDLE_DEBUG_ID_PLACEHOLDER = "
|
|
11206
|
+
var BUNDLE_DEBUG_ID_PLACEHOLDER = "692e32c2-f562-301a-6475-6e2164756e21";
|
|
11755
11207
|
|
|
11756
11208
|
// src/telemetry/telemetry.ts
|
|
11757
11209
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
11758
|
-
import { existsSync as
|
|
11210
|
+
import { existsSync as existsSync13 } from "node:fs";
|
|
11759
11211
|
import { request as httpRequest2 } from "node:http";
|
|
11760
11212
|
import { request as httpsRequest } from "node:https";
|
|
11761
11213
|
import { isAbsolute, relative, resolve, sep } from "node:path";
|
|
@@ -11894,7 +11346,7 @@ function parseOwnedStackLine(originalLine, packageRoot) {
|
|
|
11894
11346
|
return;
|
|
11895
11347
|
}
|
|
11896
11348
|
try {
|
|
11897
|
-
if (!
|
|
11349
|
+
if (!existsSync13(resolvedPath))
|
|
11898
11350
|
return;
|
|
11899
11351
|
} catch {
|
|
11900
11352
|
return;
|
|
@@ -12422,7 +11874,6 @@ var init_telemetry = __esm(() => {
|
|
|
12422
11874
|
"ReferenceError",
|
|
12423
11875
|
"SessionExpiredError",
|
|
12424
11876
|
"SyntaxError",
|
|
12425
|
-
"TicketHttpError",
|
|
12426
11877
|
"TRPCClientError",
|
|
12427
11878
|
"TypeError"
|
|
12428
11879
|
]);
|
|
@@ -12668,7 +12119,7 @@ var init_analytics = __esm(() => {
|
|
|
12668
12119
|
});
|
|
12669
12120
|
|
|
12670
12121
|
// src/setup/audit-handoff.ts
|
|
12671
|
-
import { spawnSync
|
|
12122
|
+
import { spawnSync } from "node:child_process";
|
|
12672
12123
|
function buildAuditHandoffPrompt(dosuCmd = dosuInvocation()) {
|
|
12673
12124
|
return [
|
|
12674
12125
|
"Audit this repository to see what docs Dosu can generate for it.",
|
|
@@ -12681,7 +12132,7 @@ function buildAuditHandoffPrompt(dosuCmd = dosuInvocation()) {
|
|
|
12681
12132
|
function binOnPath(bin) {
|
|
12682
12133
|
const cmd = process.platform === "win32" ? "where" : "which";
|
|
12683
12134
|
try {
|
|
12684
|
-
return
|
|
12135
|
+
return spawnSync(cmd, [bin], { stdio: "ignore" }).status === 0;
|
|
12685
12136
|
} catch {
|
|
12686
12137
|
return false;
|
|
12687
12138
|
}
|
|
@@ -12718,7 +12169,7 @@ async function offerAuditHandoff() {
|
|
|
12718
12169
|
}
|
|
12719
12170
|
function launchAuditAgent() {
|
|
12720
12171
|
logger.info("setup", "Handing off to Claude Code for the codebase audit");
|
|
12721
|
-
const result =
|
|
12172
|
+
const result = spawnSync("claude", ["--model", AUDIT_AGENT_MODEL, buildAuditHandoffPrompt()], {
|
|
12722
12173
|
stdio: "inherit",
|
|
12723
12174
|
shell: process.platform === "win32"
|
|
12724
12175
|
});
|
|
@@ -13836,8 +13287,8 @@ var init_flow2 = __esm(() => {
|
|
|
13836
13287
|
});
|
|
13837
13288
|
|
|
13838
13289
|
// src/commands/insights.ts
|
|
13839
|
-
import { existsSync as
|
|
13840
|
-
import { dirname as
|
|
13290
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync8, readdirSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync9 } from "node:fs";
|
|
13291
|
+
import { dirname as dirname5, join as join25 } from "node:path";
|
|
13841
13292
|
function shuffled(arr) {
|
|
13842
13293
|
const copy2 = [...arr];
|
|
13843
13294
|
for (let i2 = copy2.length - 1;i2 > 0; i2--) {
|
|
@@ -13941,17 +13392,17 @@ function makeAskFn(cfg) {
|
|
|
13941
13392
|
};
|
|
13942
13393
|
}
|
|
13943
13394
|
function insightsDir() {
|
|
13944
|
-
return
|
|
13395
|
+
return join25(getConfigDir(), "insights");
|
|
13945
13396
|
}
|
|
13946
13397
|
function reportPath(timestamp) {
|
|
13947
13398
|
const dir = insightsDir();
|
|
13948
13399
|
if (!timestamp)
|
|
13949
|
-
return
|
|
13400
|
+
return join25(dir, "latest.html");
|
|
13950
13401
|
const iso = timestamp.toISOString().slice(0, 19).replace(/:/g, "-");
|
|
13951
|
-
return
|
|
13402
|
+
return join25(dir, `report-${iso}Z.html`);
|
|
13952
13403
|
}
|
|
13953
13404
|
function pruneOldReports(dir, keepN) {
|
|
13954
|
-
if (!
|
|
13405
|
+
if (!existsSync14(dir))
|
|
13955
13406
|
return;
|
|
13956
13407
|
let entries;
|
|
13957
13408
|
try {
|
|
@@ -13963,7 +13414,7 @@ function pruneOldReports(dir, keepN) {
|
|
|
13963
13414
|
const reports = entries.filter((f2) => REPORT_FILE_PATTERN.test(f2)).sort().reverse();
|
|
13964
13415
|
for (const f2 of reports.slice(keepN)) {
|
|
13965
13416
|
try {
|
|
13966
|
-
unlinkSync3(
|
|
13417
|
+
unlinkSync3(join25(dir, f2));
|
|
13967
13418
|
} catch (err) {
|
|
13968
13419
|
logger.debug("insights", `failed to prune ${f2}: ${err instanceof Error ? err.message : err}`);
|
|
13969
13420
|
}
|
|
@@ -14035,10 +13486,10 @@ function defaultRunner(cfg) {
|
|
|
14035
13486
|
build: buildInsights,
|
|
14036
13487
|
render: renderHTML,
|
|
14037
13488
|
writeFile: (path3, content) => {
|
|
14038
|
-
const dir =
|
|
14039
|
-
if (!
|
|
14040
|
-
|
|
14041
|
-
|
|
13489
|
+
const dir = dirname5(path3);
|
|
13490
|
+
if (!existsSync14(dir))
|
|
13491
|
+
mkdirSync8(dir, { recursive: true, mode: 448 });
|
|
13492
|
+
writeFileSync9(path3, content, { mode: 384 });
|
|
14042
13493
|
},
|
|
14043
13494
|
prune: pruneOldReports,
|
|
14044
13495
|
openInBrowser: async (path3) => {
|
|
@@ -14281,25 +13732,62 @@ async function exchangeTicket(ticket) {
|
|
|
14281
13732
|
email: data.email ?? undefined
|
|
14282
13733
|
};
|
|
14283
13734
|
}
|
|
14284
|
-
async function fetchWithTimeout(url, init) {
|
|
14285
|
-
const controller = new AbortController;
|
|
14286
|
-
const timeout = setTimeout(() => controller.abort(), 1e4);
|
|
14287
|
-
try {
|
|
14288
|
-
return await fetch(url, { ...init, signal: controller.signal });
|
|
14289
|
-
} finally {
|
|
14290
|
-
clearTimeout(timeout);
|
|
14291
|
-
}
|
|
13735
|
+
async function fetchWithTimeout(url, init) {
|
|
13736
|
+
const controller = new AbortController;
|
|
13737
|
+
const timeout = setTimeout(() => controller.abort(), 1e4);
|
|
13738
|
+
try {
|
|
13739
|
+
return await fetch(url, { ...init, signal: controller.signal });
|
|
13740
|
+
} finally {
|
|
13741
|
+
clearTimeout(timeout);
|
|
13742
|
+
}
|
|
13743
|
+
}
|
|
13744
|
+
async function readErrorBody2(resp) {
|
|
13745
|
+
try {
|
|
13746
|
+
return (await resp.text()).slice(0, 512);
|
|
13747
|
+
} catch {
|
|
13748
|
+
return "";
|
|
13749
|
+
}
|
|
13750
|
+
}
|
|
13751
|
+
var init_ticket = __esm(() => {
|
|
13752
|
+
init_logger();
|
|
13753
|
+
});
|
|
13754
|
+
|
|
13755
|
+
// src/agent/output.ts
|
|
13756
|
+
var exports_output = {};
|
|
13757
|
+
__export(exports_output, {
|
|
13758
|
+
emitStep: () => emitStep,
|
|
13759
|
+
emitNeedUserAction: () => emitNeedUserAction,
|
|
13760
|
+
emitJSONLine: () => emitJSONLine,
|
|
13761
|
+
emitError: () => emitError
|
|
13762
|
+
});
|
|
13763
|
+
function emitJSONLine(value) {
|
|
13764
|
+
console.log(JSON.stringify(value));
|
|
13765
|
+
}
|
|
13766
|
+
function emitNeedUserAction(opts) {
|
|
13767
|
+
emitJSONLine({
|
|
13768
|
+
step: opts.step,
|
|
13769
|
+
status: "need_user_action",
|
|
13770
|
+
url: opts.url,
|
|
13771
|
+
ticket: opts.ticket,
|
|
13772
|
+
resume_command: opts.resume_command,
|
|
13773
|
+
expires_in: opts.expires_in,
|
|
13774
|
+
agent_next_steps: opts.agent_next_steps
|
|
13775
|
+
});
|
|
13776
|
+
}
|
|
13777
|
+
function emitError(opts) {
|
|
13778
|
+
const { step, reason, agent_next_steps, ...rest } = opts;
|
|
13779
|
+
emitJSONLine({
|
|
13780
|
+
step,
|
|
13781
|
+
status: "error",
|
|
13782
|
+
reason,
|
|
13783
|
+
...rest,
|
|
13784
|
+
agent_next_steps
|
|
13785
|
+
});
|
|
14292
13786
|
}
|
|
14293
|
-
|
|
14294
|
-
|
|
14295
|
-
|
|
14296
|
-
} catch {
|
|
14297
|
-
return "";
|
|
14298
|
-
}
|
|
13787
|
+
function emitStep(opts) {
|
|
13788
|
+
const { step, status = "ok", ...rest } = opts;
|
|
13789
|
+
emitJSONLine({ step, status, ...rest });
|
|
14299
13790
|
}
|
|
14300
|
-
var init_ticket = __esm(() => {
|
|
14301
|
-
init_logger();
|
|
14302
|
-
});
|
|
14303
13791
|
|
|
14304
13792
|
// src/agent/login-commands.ts
|
|
14305
13793
|
var exports_login_commands = {};
|
|
@@ -14444,7 +13932,7 @@ Could not open a browser automatically.`);
|
|
|
14444
13932
|
while (Date.now() < deadline) {
|
|
14445
13933
|
if (signal?.aborted)
|
|
14446
13934
|
throw new Error("authentication cancelled");
|
|
14447
|
-
await
|
|
13935
|
+
await sleep4(POLL_INTERVAL_MS);
|
|
14448
13936
|
if (signal?.aborted)
|
|
14449
13937
|
throw new Error("authentication cancelled");
|
|
14450
13938
|
const result = await exchangeTicket(minted.ticket);
|
|
@@ -14461,7 +13949,7 @@ Could not open a browser automatically.`);
|
|
|
14461
13949
|
}
|
|
14462
13950
|
throw new Error("Authentication timed out. Run 'dosu login' to try again.");
|
|
14463
13951
|
}
|
|
14464
|
-
function
|
|
13952
|
+
function sleep4(ms) {
|
|
14465
13953
|
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
14466
13954
|
}
|
|
14467
13955
|
var POLL_INTERVAL_MS = 5000;
|
|
@@ -14836,7 +14324,7 @@ import {
|
|
|
14836
14324
|
constants,
|
|
14837
14325
|
fstatSync,
|
|
14838
14326
|
openSync,
|
|
14839
|
-
readFileSync as
|
|
14327
|
+
readFileSync as readFileSync14,
|
|
14840
14328
|
readSync,
|
|
14841
14329
|
unlinkSync as unlinkSync4
|
|
14842
14330
|
} from "node:fs";
|
|
@@ -15991,681 +15479,6 @@ ${page.body}`);
|
|
|
15991
15479
|
return cmd;
|
|
15992
15480
|
}
|
|
15993
15481
|
|
|
15994
|
-
// src/commands/hooks.ts
|
|
15995
|
-
init_commander();
|
|
15996
|
-
init_config();
|
|
15997
|
-
init_logger();
|
|
15998
|
-
init_prompts();
|
|
15999
|
-
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
16000
|
-
import { existsSync as existsSync11, readFileSync as readFileSync11 } from "node:fs";
|
|
16001
|
-
import { basename } from "node:path";
|
|
16002
|
-
|
|
16003
|
-
// src/hooks/state.ts
|
|
16004
|
-
init_config();
|
|
16005
|
-
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync6, renameSync as renameSync2, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "node:fs";
|
|
16006
|
-
import { dirname as dirname2, join as join5 } from "node:path";
|
|
16007
|
-
function getStateDir() {
|
|
16008
|
-
return process.env.DOSU_HOOK_STATE_DIR ?? join5(getConfigDir(), "hooks");
|
|
16009
|
-
}
|
|
16010
|
-
function sanitize(value) {
|
|
16011
|
-
return value.replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
16012
|
-
}
|
|
16013
|
-
function stateFile(sessionId) {
|
|
16014
|
-
return join5(getStateDir(), `${sanitize(sessionId)}.json`);
|
|
16015
|
-
}
|
|
16016
|
-
function loadState(sessionId) {
|
|
16017
|
-
try {
|
|
16018
|
-
return JSON.parse(readFileSync6(stateFile(sessionId), "utf8"));
|
|
16019
|
-
} catch {
|
|
16020
|
-
return null;
|
|
16021
|
-
}
|
|
16022
|
-
}
|
|
16023
|
-
function saveState(state) {
|
|
16024
|
-
try {
|
|
16025
|
-
const file = stateFile(state.sessionId);
|
|
16026
|
-
const dir = dirname2(file);
|
|
16027
|
-
if (!existsSync5(dir)) {
|
|
16028
|
-
mkdirSync4(dir, { recursive: true, mode: 448 });
|
|
16029
|
-
}
|
|
16030
|
-
writeFileSync5(file, JSON.stringify(state, null, 2), { mode: 384 });
|
|
16031
|
-
} catch {}
|
|
16032
|
-
}
|
|
16033
|
-
function claimState(sessionId) {
|
|
16034
|
-
const file = stateFile(sessionId);
|
|
16035
|
-
try {
|
|
16036
|
-
renameSync2(file, `${file}.claim`);
|
|
16037
|
-
return JSON.parse(readFileSync6(`${file}.claim`, "utf8"));
|
|
16038
|
-
} catch {
|
|
16039
|
-
return null;
|
|
16040
|
-
}
|
|
16041
|
-
}
|
|
16042
|
-
function releaseState(sessionId, state) {
|
|
16043
|
-
const file = stateFile(sessionId);
|
|
16044
|
-
if (state) {
|
|
16045
|
-
try {
|
|
16046
|
-
writeFileSync5(file, JSON.stringify(state, null, 2), { flag: "wx", mode: 384 });
|
|
16047
|
-
} catch {}
|
|
16048
|
-
}
|
|
16049
|
-
try {
|
|
16050
|
-
rmSync2(`${file}.claim`, { force: true });
|
|
16051
|
-
} catch {}
|
|
16052
|
-
}
|
|
16053
|
-
|
|
16054
|
-
// src/commands/hooks.ts
|
|
16055
|
-
var COOLDOWN_DEFAULT_MS = 3000;
|
|
16056
|
-
var TTL_DEFAULT_MS = 10 * 60 * 1000;
|
|
16057
|
-
var STOP_WAIT_DEFAULT_MS = 8000;
|
|
16058
|
-
var STOP_POLL_DEFAULT_MS = 1000;
|
|
16059
|
-
var SUPPORTED_AGENTS = ["claude-code", "codex", "factory"];
|
|
16060
|
-
var DEFAULT_AGENT = "claude-code";
|
|
16061
|
-
function cooldownMs() {
|
|
16062
|
-
const n2 = Number.parseInt(process.env.DOSU_HOOK_CHECK_COOLDOWN_MS ?? "", 10);
|
|
16063
|
-
return Number.isFinite(n2) && n2 >= 0 ? n2 : COOLDOWN_DEFAULT_MS;
|
|
16064
|
-
}
|
|
16065
|
-
function ttlMs() {
|
|
16066
|
-
const n2 = Number.parseInt(process.env.DOSU_HOOK_TTL_MS ?? "", 10);
|
|
16067
|
-
return Number.isFinite(n2) && n2 > 0 ? n2 : TTL_DEFAULT_MS;
|
|
16068
|
-
}
|
|
16069
|
-
function stopWaitMs() {
|
|
16070
|
-
const n2 = Number.parseInt(process.env.DOSU_HOOK_STOP_WAIT_MS ?? "", 10);
|
|
16071
|
-
return Number.isFinite(n2) && n2 >= 0 ? n2 : STOP_WAIT_DEFAULT_MS;
|
|
16072
|
-
}
|
|
16073
|
-
function stopPollMs() {
|
|
16074
|
-
const n2 = Number.parseInt(process.env.DOSU_HOOK_STOP_POLL_MS ?? "", 10);
|
|
16075
|
-
return Number.isFinite(n2) && n2 > 0 ? n2 : STOP_POLL_DEFAULT_MS;
|
|
16076
|
-
}
|
|
16077
|
-
function printHookContext(event, additionalContext) {
|
|
16078
|
-
console.log(JSON.stringify({ hookSpecificOutput: { hookEventName: event, additionalContext } }));
|
|
16079
|
-
}
|
|
16080
|
-
function printContinue() {
|
|
16081
|
-
console.log(JSON.stringify({ continue: true }));
|
|
16082
|
-
}
|
|
16083
|
-
var sleep4 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
16084
|
-
function sid8(sessionId) {
|
|
16085
|
-
return sessionId.slice(0, 8);
|
|
16086
|
-
}
|
|
16087
|
-
function errMsg(err) {
|
|
16088
|
-
return err instanceof Error ? err.message : String(err);
|
|
16089
|
-
}
|
|
16090
|
-
function repoSlug(cwd) {
|
|
16091
|
-
return cwd ? basename(cwd) : undefined;
|
|
16092
|
-
}
|
|
16093
|
-
function gitContext(cwd) {
|
|
16094
|
-
if (!cwd)
|
|
16095
|
-
return {};
|
|
16096
|
-
const run2 = (args) => {
|
|
16097
|
-
try {
|
|
16098
|
-
const out = execFileSync2("git", args, {
|
|
16099
|
-
cwd,
|
|
16100
|
-
timeout: 1500,
|
|
16101
|
-
encoding: "utf8",
|
|
16102
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
16103
|
-
});
|
|
16104
|
-
return out.toString().trim() || undefined;
|
|
16105
|
-
} catch {
|
|
16106
|
-
return;
|
|
16107
|
-
}
|
|
16108
|
-
};
|
|
16109
|
-
const repo = run2(["remote", "get-url", "origin"]);
|
|
16110
|
-
if (!repo)
|
|
16111
|
-
return {};
|
|
16112
|
-
return { repo, branch: run2(["branch", "--show-current"]) };
|
|
16113
|
-
}
|
|
16114
|
-
async function runUserPromptSubmit(input, now = Date.now(), agent = DEFAULT_AGENT) {
|
|
16115
|
-
const sessionId = input.session_id;
|
|
16116
|
-
if (!sessionId)
|
|
16117
|
-
return;
|
|
16118
|
-
const prompt = (input.prompt ?? "").trim();
|
|
16119
|
-
if (!prompt)
|
|
16120
|
-
return;
|
|
16121
|
-
if (prompt.startsWith(STOP_PREFIX)) {
|
|
16122
|
-
logger.debug("hooks", "submit skipped reason=own-stop-envelope");
|
|
16123
|
-
return;
|
|
16124
|
-
}
|
|
16125
|
-
const turnId = input.turn_id ?? String(now);
|
|
16126
|
-
const existing = loadState(sessionId);
|
|
16127
|
-
if (existing && existing.status === "pending" && now <= existing.expiresAt && input.turn_id !== undefined && existing.turnId === turnId) {
|
|
16128
|
-
logger.debug("hooks", `submit sid=${sid8(sessionId)} reuse tid=${existing.ticketId}`);
|
|
16129
|
-
return;
|
|
16130
|
-
}
|
|
16131
|
-
const claimed = existing ? claimState(sessionId) : null;
|
|
16132
|
-
const cfg = loadConfig();
|
|
16133
|
-
if (!cfg.active_account?.target?.api_key || !cfg.active_account?.target?.deployment_id) {
|
|
16134
|
-
if (claimed)
|
|
16135
|
-
releaseState(sessionId, null);
|
|
16136
|
-
logger.debug("hooks", "submit skipped reason=not-configured");
|
|
16137
|
-
return;
|
|
16138
|
-
}
|
|
16139
|
-
const git = gitContext(input.cwd);
|
|
16140
|
-
const tc = await Promise.resolve().then(() => (init_ticket_client(), exports_ticket_client));
|
|
16141
|
-
let lateBlock;
|
|
16142
|
-
if (claimed && claimed.status === "pending" && now <= claimed.expiresAt) {
|
|
16143
|
-
try {
|
|
16144
|
-
const old = await tc.requestGetTicket(cfg, claimed.ticketId);
|
|
16145
|
-
if (old.status === "ready" && old.result?.context.trim()) {
|
|
16146
|
-
const { buildReadyEnvelope: buildReadyEnvelope2, LATE_RESULT_NOTE: LATE_RESULT_NOTE2 } = await Promise.resolve().then(() => (init_prompts(), exports_prompts));
|
|
16147
|
-
lateBlock = `${LATE_RESULT_NOTE2}
|
|
16148
|
-
|
|
16149
|
-
${buildReadyEnvelope2(old.result.context, old.result.save_recommended ?? false)}`;
|
|
16150
|
-
logger.info("hooks", `harvest tid=${claimed.ticketId} delivered=late`);
|
|
16151
|
-
}
|
|
16152
|
-
} catch {}
|
|
16153
|
-
}
|
|
16154
|
-
if (claimed)
|
|
16155
|
-
releaseState(sessionId, null);
|
|
16156
|
-
const startedAt = Date.now();
|
|
16157
|
-
let resp;
|
|
16158
|
-
try {
|
|
16159
|
-
resp = await tc.requestCreateTicket(cfg, {
|
|
16160
|
-
deployment_id: cfg.active_account?.target?.deployment_id,
|
|
16161
|
-
agent,
|
|
16162
|
-
session_id: sessionId,
|
|
16163
|
-
turn_id: turnId,
|
|
16164
|
-
prompt,
|
|
16165
|
-
repo: git.repo ?? repoSlug(input.cwd),
|
|
16166
|
-
branch: git.branch
|
|
16167
|
-
});
|
|
16168
|
-
} catch (err) {
|
|
16169
|
-
logger.warn("hooks", `error event=user-prompt-submit reason=${errMsg(err)}`);
|
|
16170
|
-
return;
|
|
16171
|
-
}
|
|
16172
|
-
saveState({
|
|
16173
|
-
ticketId: resp.ticket_id,
|
|
16174
|
-
sessionId,
|
|
16175
|
-
turnId,
|
|
16176
|
-
status: "pending",
|
|
16177
|
-
createdAt: now,
|
|
16178
|
-
expiresAt: now + ttlMs()
|
|
16179
|
-
});
|
|
16180
|
-
logger.info("hooks", `create sid=${sid8(sessionId)} tid=${resp.ticket_id} latency=${Date.now() - startedAt}ms`);
|
|
16181
|
-
const { LOOKUP_STARTED_NOTE: LOOKUP_STARTED_NOTE2 } = await Promise.resolve().then(() => (init_prompts(), exports_prompts));
|
|
16182
|
-
printHookContext("UserPromptSubmit", lateBlock ? `${lateBlock}
|
|
16183
|
-
|
|
16184
|
-
${LOOKUP_STARTED_NOTE2}` : LOOKUP_STARTED_NOTE2);
|
|
16185
|
-
}
|
|
16186
|
-
async function runPostToolUse(input, now = Date.now()) {
|
|
16187
|
-
const sessionId = input.session_id;
|
|
16188
|
-
if (!sessionId)
|
|
16189
|
-
return;
|
|
16190
|
-
const state = loadState(sessionId);
|
|
16191
|
-
if (!state) {
|
|
16192
|
-
logger.debug("hooks", "noop reason=no-active-ticket");
|
|
16193
|
-
return;
|
|
16194
|
-
}
|
|
16195
|
-
if (state.status === "delivered" || state.status === "failed" || state.status === "expired") {
|
|
16196
|
-
return;
|
|
16197
|
-
}
|
|
16198
|
-
if (now > state.expiresAt) {
|
|
16199
|
-
saveState({ ...state, status: "expired" });
|
|
16200
|
-
logger.debug("hooks", `expire tid=${state.ticketId}`);
|
|
16201
|
-
return;
|
|
16202
|
-
}
|
|
16203
|
-
if (state.lastCheckedAt !== undefined && now - state.lastCheckedAt < cooldownMs()) {
|
|
16204
|
-
logger.debug("hooks", `poll tid=${state.ticketId} skipped=cooldown`);
|
|
16205
|
-
return;
|
|
16206
|
-
}
|
|
16207
|
-
const cfg = loadConfig();
|
|
16208
|
-
if (!cfg.active_account?.target?.api_key || !cfg.active_account?.target?.deployment_id) {
|
|
16209
|
-
saveState({ ...state, lastCheckedAt: now });
|
|
16210
|
-
return;
|
|
16211
|
-
}
|
|
16212
|
-
const claimed = claimState(sessionId);
|
|
16213
|
-
if (!claimed) {
|
|
16214
|
-
logger.debug("hooks", `poll tid=${state.ticketId} skipped=contended`);
|
|
16215
|
-
return;
|
|
16216
|
-
}
|
|
16217
|
-
const tc = await Promise.resolve().then(() => (init_ticket_client(), exports_ticket_client));
|
|
16218
|
-
const startedAt = Date.now();
|
|
16219
|
-
let resp;
|
|
16220
|
-
try {
|
|
16221
|
-
resp = await tc.requestGetTicket(cfg, claimed.ticketId);
|
|
16222
|
-
} catch (err) {
|
|
16223
|
-
if (tc.isDefinitiveError(err)) {
|
|
16224
|
-
releaseState(sessionId, { ...claimed, status: "failed", lastCheckedAt: now });
|
|
16225
|
-
logger.warn("hooks", `error event=post-tool-use reason=${errMsg(err)} definitive`);
|
|
16226
|
-
} else {
|
|
16227
|
-
releaseState(sessionId, { ...claimed, lastCheckedAt: now });
|
|
16228
|
-
logger.debug("hooks", `poll tid=${claimed.ticketId} transient-error`);
|
|
16229
|
-
}
|
|
16230
|
-
return;
|
|
16231
|
-
}
|
|
16232
|
-
if (resp.status === "pending") {
|
|
16233
|
-
releaseState(sessionId, { ...claimed, lastCheckedAt: now });
|
|
16234
|
-
logger.debug("hooks", `poll tid=${claimed.ticketId} status=pending`);
|
|
16235
|
-
return;
|
|
16236
|
-
}
|
|
16237
|
-
if (resp.status === "failed" || resp.status === "expired") {
|
|
16238
|
-
releaseState(sessionId, { ...claimed, status: resp.status, lastCheckedAt: now });
|
|
16239
|
-
return;
|
|
16240
|
-
}
|
|
16241
|
-
if (!resp.result) {
|
|
16242
|
-
releaseState(sessionId, { ...claimed, status: "failed", lastCheckedAt: now });
|
|
16243
|
-
return;
|
|
16244
|
-
}
|
|
16245
|
-
releaseState(sessionId, {
|
|
16246
|
-
...claimed,
|
|
16247
|
-
status: "delivered",
|
|
16248
|
-
deliveredAt: now,
|
|
16249
|
-
lastCheckedAt: now
|
|
16250
|
-
});
|
|
16251
|
-
const { buildReadyEnvelope: buildReadyEnvelope2 } = await Promise.resolve().then(() => (init_prompts(), exports_prompts));
|
|
16252
|
-
const context = buildReadyEnvelope2(resp.result.context, resp.result.save_recommended ?? false);
|
|
16253
|
-
if (context) {
|
|
16254
|
-
printHookContext("PostToolUse", context);
|
|
16255
|
-
logger.info("hooks", `deliver tid=${state.ticketId} latency=${Date.now() - startedAt}ms bytes=${context.length}`);
|
|
16256
|
-
} else {
|
|
16257
|
-
logger.debug("hooks", `deliver tid=${state.ticketId} nothing-to-inject`);
|
|
16258
|
-
}
|
|
16259
|
-
}
|
|
16260
|
-
async function runStop(input, now = Date.now()) {
|
|
16261
|
-
const sessionId = input.session_id;
|
|
16262
|
-
if (!sessionId)
|
|
16263
|
-
return printContinue();
|
|
16264
|
-
const state = loadState(sessionId);
|
|
16265
|
-
if (!state)
|
|
16266
|
-
return printContinue();
|
|
16267
|
-
if (state.status !== "pending" || now > state.expiresAt)
|
|
16268
|
-
return printContinue();
|
|
16269
|
-
const cfg = loadConfig();
|
|
16270
|
-
if (!cfg.active_account?.target?.api_key || !cfg.active_account?.target?.deployment_id)
|
|
16271
|
-
return printContinue();
|
|
16272
|
-
const claimed = claimState(sessionId);
|
|
16273
|
-
if (!claimed)
|
|
16274
|
-
return printContinue();
|
|
16275
|
-
const tc = await Promise.resolve().then(() => (init_ticket_client(), exports_ticket_client));
|
|
16276
|
-
const pollMs = stopPollMs();
|
|
16277
|
-
const maxWaits = pollMs > 0 ? Math.floor(stopWaitMs() / pollMs) : 0;
|
|
16278
|
-
for (let waited = 0;; waited++) {
|
|
16279
|
-
let resp;
|
|
16280
|
-
try {
|
|
16281
|
-
resp = await tc.requestGetTicket(cfg, claimed.ticketId);
|
|
16282
|
-
} catch {
|
|
16283
|
-
releaseState(sessionId, { ...claimed, lastCheckedAt: now });
|
|
16284
|
-
return printContinue();
|
|
16285
|
-
}
|
|
16286
|
-
if (resp.status === "ready" && resp.result) {
|
|
16287
|
-
releaseState(sessionId, {
|
|
16288
|
-
...claimed,
|
|
16289
|
-
status: "delivered",
|
|
16290
|
-
deliveredAt: now,
|
|
16291
|
-
lastCheckedAt: now
|
|
16292
|
-
});
|
|
16293
|
-
if (resp.result.context.trim()) {
|
|
16294
|
-
const { buildReadyEnvelope: buildReadyEnvelope2, STOP_PREFIX: STOP_PREFIX2 } = await Promise.resolve().then(() => (init_prompts(), exports_prompts));
|
|
16295
|
-
const envelope = buildReadyEnvelope2(resp.result.context, resp.result.save_recommended ?? false);
|
|
16296
|
-
console.log(JSON.stringify({ decision: "block", reason: `${STOP_PREFIX2}
|
|
16297
|
-
|
|
16298
|
-
${envelope}` }));
|
|
16299
|
-
logger.info("hooks", `stop tid=${claimed.ticketId} delivered=true`);
|
|
16300
|
-
return;
|
|
16301
|
-
}
|
|
16302
|
-
logger.debug("hooks", `stop tid=${claimed.ticketId} delivered=true gap-no-block`);
|
|
16303
|
-
return printContinue();
|
|
16304
|
-
}
|
|
16305
|
-
if (resp.status !== "pending" || waited >= maxWaits)
|
|
16306
|
-
break;
|
|
16307
|
-
await sleep4(pollMs);
|
|
16308
|
-
}
|
|
16309
|
-
releaseState(sessionId, { ...claimed, lastCheckedAt: now });
|
|
16310
|
-
logger.debug("hooks", `stop tid=${claimed.ticketId} delivered=false`);
|
|
16311
|
-
printContinue();
|
|
16312
|
-
}
|
|
16313
|
-
function runStatus(input, opts, now = Date.now()) {
|
|
16314
|
-
const sessionId = input.session_id ?? "";
|
|
16315
|
-
const state = sessionId ? loadState(sessionId) : null;
|
|
16316
|
-
const data = state ? { ...state, expired: now > state.expiresAt, delivered: state.deliveredAt !== undefined } : null;
|
|
16317
|
-
if (opts.json) {
|
|
16318
|
-
console.log(JSON.stringify(data, null, 2));
|
|
16319
|
-
return;
|
|
16320
|
-
}
|
|
16321
|
-
if (!data) {
|
|
16322
|
-
console.log("No active Dosu knowledge ticket for this session.");
|
|
16323
|
-
return;
|
|
16324
|
-
}
|
|
16325
|
-
console.log(`Ticket: ${data.ticketId}`);
|
|
16326
|
-
console.log(`Status: ${data.status}`);
|
|
16327
|
-
console.log(`Delivered: ${data.delivered ? "yes" : "no"}`);
|
|
16328
|
-
}
|
|
16329
|
-
async function runHookEntrypoint(event, raw, now = Date.now(), agent = DEFAULT_AGENT) {
|
|
16330
|
-
let input = {};
|
|
16331
|
-
try {
|
|
16332
|
-
input = raw.trim() ? JSON.parse(raw) : {};
|
|
16333
|
-
} catch {
|
|
16334
|
-
input = {};
|
|
16335
|
-
}
|
|
16336
|
-
try {
|
|
16337
|
-
if (event === "user-prompt-submit")
|
|
16338
|
-
await runUserPromptSubmit(input, now, agent);
|
|
16339
|
-
else if (event === "post-tool-use")
|
|
16340
|
-
await runPostToolUse(input, now);
|
|
16341
|
-
else
|
|
16342
|
-
await runStop(input, now);
|
|
16343
|
-
} catch (err) {
|
|
16344
|
-
logger.warn("hooks", `error event=${event} reason=${errMsg(err)}`);
|
|
16345
|
-
if (event === "stop")
|
|
16346
|
-
printContinue();
|
|
16347
|
-
}
|
|
16348
|
-
}
|
|
16349
|
-
function readStdinRaw() {
|
|
16350
|
-
try {
|
|
16351
|
-
return readFileSync11(0, "utf8");
|
|
16352
|
-
} catch {
|
|
16353
|
-
return "";
|
|
16354
|
-
}
|
|
16355
|
-
}
|
|
16356
|
-
function resolveDir(dir) {
|
|
16357
|
-
return dir ?? process.cwd();
|
|
16358
|
-
}
|
|
16359
|
-
async function emitErrorLine(step, reason, agentNextSteps) {
|
|
16360
|
-
const { emitError: emitError2 } = await Promise.resolve().then(() => exports_output);
|
|
16361
|
-
emitError2({ step, reason, agent_next_steps: agentNextSteps });
|
|
16362
|
-
}
|
|
16363
|
-
async function runInstall(agent, opts) {
|
|
16364
|
-
if (!SUPPORTED_AGENTS.includes(agent)) {
|
|
16365
|
-
process.exitCode = 2;
|
|
16366
|
-
const supported = SUPPORTED_AGENTS.join(", ");
|
|
16367
|
-
if (opts.json) {
|
|
16368
|
-
await emitErrorLine("hooks-install", "unsupported_agent", `Supported agents: ${supported}.`);
|
|
16369
|
-
} else {
|
|
16370
|
-
console.error(`Unsupported agent '${agent}'. Supported agents: ${supported}.`);
|
|
16371
|
-
}
|
|
16372
|
-
return;
|
|
16373
|
-
}
|
|
16374
|
-
if (opts.scope && opts.scope !== "local") {
|
|
16375
|
-
process.exitCode = 2;
|
|
16376
|
-
if (opts.json) {
|
|
16377
|
-
await emitErrorLine("hooks-install", "unsupported_scope", "Only --scope local is supported.");
|
|
16378
|
-
} else {
|
|
16379
|
-
console.error(`Unsupported --scope '${opts.scope}'. Supported scopes: local.`);
|
|
16380
|
-
}
|
|
16381
|
-
return;
|
|
16382
|
-
}
|
|
16383
|
-
try {
|
|
16384
|
-
if (agent === "codex") {
|
|
16385
|
-
const { installCodexHooks: installCodexHooks2, codexHooksPath: codexHooksPath2 } = await Promise.resolve().then(() => (init_codex(), exports_codex));
|
|
16386
|
-
const configPath2 = codexHooksPath2(resolveDir(opts.dir));
|
|
16387
|
-
const { events: events2 } = installCodexHooks2(configPath2, { stop: opts.stop });
|
|
16388
|
-
if (opts.json) {
|
|
16389
|
-
const { emitStep: emitStep2 } = await Promise.resolve().then(() => exports_output);
|
|
16390
|
-
emitStep2({ step: "hooks-install", agent, path: configPath2, events: events2 });
|
|
16391
|
-
} else {
|
|
16392
|
-
console.log(`✓ Installed Dosu hooks for Codex (${events2.join(", ")}).`);
|
|
16393
|
-
console.log(` → ${configPath2}`);
|
|
16394
|
-
console.log(" One-time step: open Codex in this project and run /hooks to review and");
|
|
16395
|
-
console.log(" trust the Dosu hooks — Codex skips untrusted hooks by design.");
|
|
16396
|
-
}
|
|
16397
|
-
return;
|
|
16398
|
-
}
|
|
16399
|
-
if (agent === "factory") {
|
|
16400
|
-
const { installFactoryHooks: installFactoryHooks2, factoryHooksPath: factoryHooksPath2 } = await Promise.resolve().then(() => (init_factory(), exports_factory));
|
|
16401
|
-
const configPath2 = factoryHooksPath2(resolveDir(opts.dir));
|
|
16402
|
-
const { events: events2 } = installFactoryHooks2(configPath2, { stop: opts.stop });
|
|
16403
|
-
if (opts.json) {
|
|
16404
|
-
const { emitStep: emitStep2 } = await Promise.resolve().then(() => exports_output);
|
|
16405
|
-
emitStep2({ step: "hooks-install", agent, path: configPath2, events: events2 });
|
|
16406
|
-
} else {
|
|
16407
|
-
console.log(`✓ Installed Dosu hooks for Factory (${events2.join(", ")}).`);
|
|
16408
|
-
console.log(` → ${configPath2}`);
|
|
16409
|
-
console.log(" Start a new Factory session in this project to use them.");
|
|
16410
|
-
}
|
|
16411
|
-
return;
|
|
16412
|
-
}
|
|
16413
|
-
const { installClaudeHooks: installClaudeHooks2, claudeLocalSettingsPath: claudeLocalSettingsPath2 } = await Promise.resolve().then(() => (init_claude_code(), exports_claude_code));
|
|
16414
|
-
const configPath = claudeLocalSettingsPath2(resolveDir(opts.dir));
|
|
16415
|
-
const { events } = installClaudeHooks2(configPath, { stop: opts.stop });
|
|
16416
|
-
if (opts.json) {
|
|
16417
|
-
const { emitStep: emitStep2 } = await Promise.resolve().then(() => exports_output);
|
|
16418
|
-
emitStep2({ step: "hooks-install", agent, path: configPath, events });
|
|
16419
|
-
} else {
|
|
16420
|
-
console.log(`✓ Installed Dosu hooks for Claude Code (${events.join(", ")}).`);
|
|
16421
|
-
console.log(` → ${configPath}`);
|
|
16422
|
-
console.log(" Start a new Claude Code session in this project to use them.");
|
|
16423
|
-
}
|
|
16424
|
-
} catch (err) {
|
|
16425
|
-
process.exitCode = 1;
|
|
16426
|
-
if (opts.json) {
|
|
16427
|
-
await emitErrorLine("hooks-install", "write_failed", errMsg(err));
|
|
16428
|
-
} else {
|
|
16429
|
-
console.error(`Failed to install hooks: ${errMsg(err)}`);
|
|
16430
|
-
}
|
|
16431
|
-
}
|
|
16432
|
-
}
|
|
16433
|
-
async function runUninstall(agent, opts) {
|
|
16434
|
-
if (!SUPPORTED_AGENTS.includes(agent)) {
|
|
16435
|
-
process.exitCode = 2;
|
|
16436
|
-
const supported = SUPPORTED_AGENTS.join(", ");
|
|
16437
|
-
if (opts.json) {
|
|
16438
|
-
await emitErrorLine("hooks-uninstall", "unsupported_agent", `Supported agents: ${supported}.`);
|
|
16439
|
-
} else {
|
|
16440
|
-
console.error(`Unsupported agent '${agent}'. Supported agents: ${supported}.`);
|
|
16441
|
-
}
|
|
16442
|
-
return;
|
|
16443
|
-
}
|
|
16444
|
-
let configPath;
|
|
16445
|
-
let removed;
|
|
16446
|
-
if (agent === "codex") {
|
|
16447
|
-
const { removeCodexHooks: removeCodexHooks2, codexHooksPath: codexHooksPath2 } = await Promise.resolve().then(() => (init_codex(), exports_codex));
|
|
16448
|
-
configPath = codexHooksPath2(resolveDir(opts.dir));
|
|
16449
|
-
({ removed } = removeCodexHooks2(configPath));
|
|
16450
|
-
} else if (agent === "factory") {
|
|
16451
|
-
const { removeFactoryHooks: removeFactoryHooks2, factoryHooksPath: factoryHooksPath2 } = await Promise.resolve().then(() => (init_factory(), exports_factory));
|
|
16452
|
-
configPath = factoryHooksPath2(resolveDir(opts.dir));
|
|
16453
|
-
({ removed } = removeFactoryHooks2(configPath));
|
|
16454
|
-
} else {
|
|
16455
|
-
const { removeClaudeHooks: removeClaudeHooks2, claudeLocalSettingsPath: claudeLocalSettingsPath2 } = await Promise.resolve().then(() => (init_claude_code(), exports_claude_code));
|
|
16456
|
-
configPath = claudeLocalSettingsPath2(resolveDir(opts.dir));
|
|
16457
|
-
({ removed } = removeClaudeHooks2(configPath));
|
|
16458
|
-
}
|
|
16459
|
-
if (opts.json) {
|
|
16460
|
-
const { emitStep: emitStep2 } = await Promise.resolve().then(() => exports_output);
|
|
16461
|
-
emitStep2({ step: "hooks-uninstall", path: configPath, removed });
|
|
16462
|
-
} else if (removed) {
|
|
16463
|
-
console.log(`✓ Removed Dosu hooks from ${configPath}.`);
|
|
16464
|
-
} else {
|
|
16465
|
-
console.log("No Dosu hooks were installed.");
|
|
16466
|
-
}
|
|
16467
|
-
}
|
|
16468
|
-
async function collectDoctorChecks(opts) {
|
|
16469
|
-
const checks = [];
|
|
16470
|
-
const { claudeLocalSettingsPath: claudeLocalSettingsPath2, inspectClaudeHooks: inspectClaudeHooks2 } = await Promise.resolve().then(() => (init_claude_code(), exports_claude_code));
|
|
16471
|
-
const { codexHooksPath: codexHooksPath2, inspectCodexHooks: inspectCodexHooks2 } = await Promise.resolve().then(() => (init_codex(), exports_codex));
|
|
16472
|
-
const { factoryHooksPath: factoryHooksPath2, inspectFactoryHooks: inspectFactoryHooks2 } = await Promise.resolve().then(() => (init_factory(), exports_factory));
|
|
16473
|
-
const configPath = claudeLocalSettingsPath2(resolveDir(opts.dir));
|
|
16474
|
-
const inspection = inspectClaudeHooks2(configPath);
|
|
16475
|
-
const codexPath = codexHooksPath2(resolveDir(opts.dir));
|
|
16476
|
-
const codex = inspectCodexHooks2(codexPath);
|
|
16477
|
-
const factoryPath = factoryHooksPath2(resolveDir(opts.dir));
|
|
16478
|
-
const factory = inspectFactoryHooks2(factoryPath);
|
|
16479
|
-
const codexInstalled = codex.events.includes("UserPromptSubmit") && codex.events.includes("PostToolUse");
|
|
16480
|
-
const factoryInstalled = factory.events.includes("UserPromptSubmit") && factory.events.includes("PostToolUse");
|
|
16481
|
-
const alternativeInstalled = codexInstalled || factoryInstalled;
|
|
16482
|
-
if (!inspection.fileExists) {
|
|
16483
|
-
checks.push({
|
|
16484
|
-
name: "config",
|
|
16485
|
-
status: alternativeInstalled ? "warn" : "fail",
|
|
16486
|
-
detail: `not found: ${configPath} (run 'dosu hooks install claude-code')`
|
|
16487
|
-
});
|
|
16488
|
-
} else if (inspection.parseError) {
|
|
16489
|
-
checks.push({ name: "config", status: "fail", detail: `invalid JSON: ${configPath}` });
|
|
16490
|
-
} else {
|
|
16491
|
-
checks.push({ name: "config", status: "ok", detail: configPath });
|
|
16492
|
-
}
|
|
16493
|
-
const hasSubmit = inspection.events.includes("UserPromptSubmit");
|
|
16494
|
-
const hasPostTool = inspection.events.includes("PostToolUse");
|
|
16495
|
-
if (hasSubmit && hasPostTool) {
|
|
16496
|
-
checks.push({
|
|
16497
|
-
name: "hooks",
|
|
16498
|
-
status: "ok",
|
|
16499
|
-
detail: `installed: ${inspection.events.join(", ")}`
|
|
16500
|
-
});
|
|
16501
|
-
} else {
|
|
16502
|
-
checks.push({
|
|
16503
|
-
name: "hooks",
|
|
16504
|
-
status: alternativeInstalled ? "warn" : "fail",
|
|
16505
|
-
detail: "UserPromptSubmit + PostToolUse not both installed"
|
|
16506
|
-
});
|
|
16507
|
-
}
|
|
16508
|
-
if (codex.fileExists) {
|
|
16509
|
-
if (codex.parseError) {
|
|
16510
|
-
checks.push({ name: "codex-config", status: "fail", detail: `invalid JSON: ${codexPath}` });
|
|
16511
|
-
} else if (codexInstalled) {
|
|
16512
|
-
checks.push({
|
|
16513
|
-
name: "codex-config",
|
|
16514
|
-
status: "ok",
|
|
16515
|
-
detail: `installed: ${codex.events.join(", ")} (${codexPath})`
|
|
16516
|
-
});
|
|
16517
|
-
checks.push({
|
|
16518
|
-
name: "codex-trust",
|
|
16519
|
-
status: "warn",
|
|
16520
|
-
detail: "if hooks aren't firing, run /hooks inside Codex to review + trust them (one-time)"
|
|
16521
|
-
});
|
|
16522
|
-
} else {
|
|
16523
|
-
checks.push({
|
|
16524
|
-
name: "codex-config",
|
|
16525
|
-
status: "fail",
|
|
16526
|
-
detail: "UserPromptSubmit + PostToolUse not both installed"
|
|
16527
|
-
});
|
|
16528
|
-
}
|
|
16529
|
-
}
|
|
16530
|
-
if (factory.fileExists) {
|
|
16531
|
-
if (factory.parseError) {
|
|
16532
|
-
checks.push({
|
|
16533
|
-
name: "factory-config",
|
|
16534
|
-
status: "fail",
|
|
16535
|
-
detail: `invalid JSON: ${factoryPath}`
|
|
16536
|
-
});
|
|
16537
|
-
} else if (factoryInstalled) {
|
|
16538
|
-
checks.push({
|
|
16539
|
-
name: "factory-config",
|
|
16540
|
-
status: "ok",
|
|
16541
|
-
detail: `installed: ${factory.events.join(", ")} (${factoryPath})`
|
|
16542
|
-
});
|
|
16543
|
-
} else {
|
|
16544
|
-
checks.push({
|
|
16545
|
-
name: "factory-config",
|
|
16546
|
-
status: "fail",
|
|
16547
|
-
detail: "UserPromptSubmit + PostToolUse not both installed"
|
|
16548
|
-
});
|
|
16549
|
-
}
|
|
16550
|
-
}
|
|
16551
|
-
if (hasSubmit && hasPostTool || alternativeInstalled) {
|
|
16552
|
-
const { dosuOnPath: dosuOnPath2, materializedRuntimePath: materializedRuntimePath2 } = await Promise.resolve().then(() => (init_runtime(), exports_runtime));
|
|
16553
|
-
const materialized = materializedRuntimePath2();
|
|
16554
|
-
if (dosuOnPath2()) {
|
|
16555
|
-
checks.push({ name: "runtime", status: "ok", detail: "dosu on PATH" });
|
|
16556
|
-
} else if (existsSync11(materialized)) {
|
|
16557
|
-
checks.push({ name: "runtime", status: "ok", detail: `materialized: ${materialized}` });
|
|
16558
|
-
} else {
|
|
16559
|
-
checks.push({
|
|
16560
|
-
name: "runtime",
|
|
16561
|
-
status: "fail",
|
|
16562
|
-
detail: "dosu is not on PATH and no materialized runtime exists — re-run 'npx -y @dosu/cli hooks install <agent>'"
|
|
16563
|
-
});
|
|
16564
|
-
}
|
|
16565
|
-
}
|
|
16566
|
-
const cfg = loadConfig();
|
|
16567
|
-
if (isAuthenticated(cfg)) {
|
|
16568
|
-
checks.push({ name: "auth", status: "ok", detail: "logged in" });
|
|
16569
|
-
} else {
|
|
16570
|
-
checks.push({ name: "auth", status: "fail", detail: "not logged in (run 'dosu login')" });
|
|
16571
|
-
}
|
|
16572
|
-
if (cfg.active_account?.target?.deployment_id || cfg.mode === MODE_OSS) {
|
|
16573
|
-
checks.push({
|
|
16574
|
-
name: "deployment",
|
|
16575
|
-
status: "ok",
|
|
16576
|
-
detail: cfg.active_account?.target?.deployment_name ?? cfg.active_account?.target?.deployment_id ?? "oss"
|
|
16577
|
-
});
|
|
16578
|
-
} else {
|
|
16579
|
-
checks.push({
|
|
16580
|
-
name: "deployment",
|
|
16581
|
-
status: "fail",
|
|
16582
|
-
detail: "no deployment selected (run 'dosu setup')"
|
|
16583
|
-
});
|
|
16584
|
-
}
|
|
16585
|
-
if (cfg.active_account?.target?.api_key && cfg.active_account?.target?.deployment_id) {
|
|
16586
|
-
const { Client: Client2 } = await Promise.resolve().then(() => (init_client(), exports_client));
|
|
16587
|
-
const ok = await new Client2(cfg).validateAPIKey(cfg.active_account?.target?.api_key, cfg.active_account?.target?.deployment_id);
|
|
16588
|
-
checks.push({
|
|
16589
|
-
name: "backend",
|
|
16590
|
-
status: ok ? "ok" : "fail",
|
|
16591
|
-
detail: ok ? "reachable" : "API key rejected"
|
|
16592
|
-
});
|
|
16593
|
-
} else {
|
|
16594
|
-
checks.push({
|
|
16595
|
-
name: "backend",
|
|
16596
|
-
status: "warn",
|
|
16597
|
-
detail: "skipped (no API key / no deployment)"
|
|
16598
|
-
});
|
|
16599
|
-
}
|
|
16600
|
-
return checks;
|
|
16601
|
-
}
|
|
16602
|
-
async function runDoctor(opts) {
|
|
16603
|
-
const checks = await collectDoctorChecks(opts);
|
|
16604
|
-
if (opts.json) {
|
|
16605
|
-
const { emitJSONLine: emitJSONLine2 } = await Promise.resolve().then(() => exports_output);
|
|
16606
|
-
for (const c3 of checks) {
|
|
16607
|
-
emitJSONLine2({ step: `doctor-${c3.name}`, status: c3.status, detail: c3.detail });
|
|
16608
|
-
}
|
|
16609
|
-
} else {
|
|
16610
|
-
const icon = { ok: "✓", warn: "⚠", fail: "✗" };
|
|
16611
|
-
for (const c3 of checks) {
|
|
16612
|
-
console.log(`${icon[c3.status]} ${c3.name}: ${c3.detail}`);
|
|
16613
|
-
}
|
|
16614
|
-
}
|
|
16615
|
-
if (checks.some((c3) => c3.status === "fail")) {
|
|
16616
|
-
process.exitCode = 1;
|
|
16617
|
-
}
|
|
16618
|
-
}
|
|
16619
|
-
function hooksCommand() {
|
|
16620
|
-
const cmd = new Command("hooks").description("Dosu knowledge-injection hooks for coding agents (Claude Code, Codex, Factory)");
|
|
16621
|
-
cmd.command("user-prompt-submit").description("Hook entrypoint: create a knowledge ticket on prompt submit").option("--agent <agent>", "Calling agent for ticket attribution", DEFAULT_AGENT).action(async (opts) => {
|
|
16622
|
-
await runHookEntrypoint("user-prompt-submit", readStdinRaw(), Date.now(), opts.agent);
|
|
16623
|
-
});
|
|
16624
|
-
cmd.command("post-tool-use").description("Hook entrypoint: poll and inject ready knowledge once").option("--agent <agent>", "Calling agent for ticket attribution", DEFAULT_AGENT).action(async (opts) => {
|
|
16625
|
-
await runHookEntrypoint("post-tool-use", readStdinRaw(), Date.now(), opts.agent);
|
|
16626
|
-
});
|
|
16627
|
-
cmd.command("stop").description("Hook entrypoint: last-chance knowledge delivery when the agent stops").option("--agent <agent>", "Calling agent for ticket attribution", DEFAULT_AGENT).action(async (opts) => {
|
|
16628
|
-
await runHookEntrypoint("stop", readStdinRaw(), Date.now(), opts.agent);
|
|
16629
|
-
});
|
|
16630
|
-
cmd.command("status").description("Show the active Dosu knowledge ticket for this session").option("--json", "Output JSON", false).action((opts) => {
|
|
16631
|
-
let input = {};
|
|
16632
|
-
try {
|
|
16633
|
-
const raw = readStdinRaw().trim();
|
|
16634
|
-
input = raw ? JSON.parse(raw) : {};
|
|
16635
|
-
} catch {
|
|
16636
|
-
input = {};
|
|
16637
|
-
}
|
|
16638
|
-
runStatus(input, opts);
|
|
16639
|
-
});
|
|
16640
|
-
cmd.command("install").description("Install Dosu hooks into a coding agent's local config").addArgument(new Argument("<agent>", "coding agent to configure").choices(SUPPORTED_AGENTS)).option("--scope <scope>", "Config scope (local only)", "local").option("--dir <path>", "Project root (defaults to current directory)").option("--no-stop", "Skip the Stop hook (knowledge then delivers mid-session only, less reliably)").option("--json", "Emit machine-readable JSON", false).addHelpText("after", [
|
|
16641
|
-
"",
|
|
16642
|
-
`Supported agents: ${SUPPORTED_AGENTS.join(", ")}`,
|
|
16643
|
-
"",
|
|
16644
|
-
"Examples:",
|
|
16645
|
-
" $ dosu hooks install claude-code",
|
|
16646
|
-
" $ dosu hooks install codex",
|
|
16647
|
-
" $ dosu hooks install factory",
|
|
16648
|
-
" $ dosu hooks install claude-code --no-stop",
|
|
16649
|
-
" $ dosu hooks install codex --dir ./my-project",
|
|
16650
|
-
"",
|
|
16651
|
-
"Codex requires a one-time trust step after install: open Codex in the",
|
|
16652
|
-
"project and run /hooks to review and trust the Dosu hooks."
|
|
16653
|
-
].join(`
|
|
16654
|
-
`)).action((agent, opts) => runInstall(agent, opts));
|
|
16655
|
-
cmd.command("uninstall").description("Remove Dosu hooks from a coding agent's local config (Dosu-owned entries only)").addArgument(new Argument("<agent>", "coding agent to clean up").choices(SUPPORTED_AGENTS)).option("--scope <scope>", "Config scope (local only)", "local").option("--dir <path>", "Project root (defaults to current directory)").option("--json", "Emit machine-readable JSON", false).addHelpText("after", [
|
|
16656
|
-
"",
|
|
16657
|
-
`Supported agents: ${SUPPORTED_AGENTS.join(", ")}`,
|
|
16658
|
-
"",
|
|
16659
|
-
"Examples:",
|
|
16660
|
-
" $ dosu hooks uninstall claude-code",
|
|
16661
|
-
" $ dosu hooks uninstall codex",
|
|
16662
|
-
" $ dosu hooks uninstall factory"
|
|
16663
|
-
].join(`
|
|
16664
|
-
`)).action((agent, opts) => runUninstall(agent, opts));
|
|
16665
|
-
cmd.command("doctor").description("Diagnose Dosu hook config, auth, deployment, and backend connectivity").option("--dir <path>", "Project root (defaults to current directory)").option("--json", "Emit machine-readable JSON", false).action((opts) => runDoctor(opts));
|
|
16666
|
-
return cmd;
|
|
16667
|
-
}
|
|
16668
|
-
|
|
16669
15482
|
// src/cli/cli.ts
|
|
16670
15483
|
init_insights3();
|
|
16671
15484
|
|
|
@@ -17014,7 +15827,7 @@ init_dist();
|
|
|
17014
15827
|
init_commander();
|
|
17015
15828
|
init_trpc();
|
|
17016
15829
|
var import_picocolors19 = __toESM(require_picocolors(), 1);
|
|
17017
|
-
import { readFileSync as
|
|
15830
|
+
import { readFileSync as readFileSync12 } from "node:fs";
|
|
17018
15831
|
function requireConfig10() {
|
|
17019
15832
|
return requireLoginConfig();
|
|
17020
15833
|
}
|
|
@@ -17190,7 +16003,7 @@ function reviewCommand() {
|
|
|
17190
16003
|
let body = opts.body;
|
|
17191
16004
|
if (opts.bodyFile) {
|
|
17192
16005
|
try {
|
|
17193
|
-
body =
|
|
16006
|
+
body = readFileSync12(opts.bodyFile, "utf-8");
|
|
17194
16007
|
} catch (err) {
|
|
17195
16008
|
console.error(import_picocolors19.default.red(`Failed to read --body-file: ${err.message}`));
|
|
17196
16009
|
process.exit(1);
|
|
@@ -17767,7 +16580,7 @@ function topicsCommand() {
|
|
|
17767
16580
|
// src/commands/upgrade.ts
|
|
17768
16581
|
init_commander();
|
|
17769
16582
|
var import_picocolors24 = __toESM(require_picocolors(), 1);
|
|
17770
|
-
import { spawnSync as
|
|
16583
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
17771
16584
|
import { realpathSync } from "node:fs";
|
|
17772
16585
|
import { homedir as homedir5 } from "node:os";
|
|
17773
16586
|
import { posix, win32 } from "node:path";
|
|
@@ -17825,7 +16638,7 @@ function buildPackageManagerInvocation(manager, action, platform4 = process.plat
|
|
|
17825
16638
|
}
|
|
17826
16639
|
function packageRoots(manager, platform4, comSpec, env, cwd) {
|
|
17827
16640
|
const invocation = buildPackageManagerInvocation(manager, "locate", platform4, comSpec, env);
|
|
17828
|
-
const result =
|
|
16641
|
+
const result = spawnSync2(invocation.command, invocation.args, {
|
|
17829
16642
|
cwd,
|
|
17830
16643
|
encoding: "utf8",
|
|
17831
16644
|
env: invocation.env,
|
|
@@ -17922,7 +16735,7 @@ function runUpgrade(channel = INSTALL_CHANNEL, options = {}) {
|
|
|
17922
16735
|
console.log(`Updating Dosu with ${plan.label}...`);
|
|
17923
16736
|
console.log(` ${plan.manualCommand}
|
|
17924
16737
|
`);
|
|
17925
|
-
const result =
|
|
16738
|
+
const result = spawnSync2(plan.command, plan.args, {
|
|
17926
16739
|
...channel === "npm" ? { cwd } : {},
|
|
17927
16740
|
...plan.env ? { env: plan.env } : {},
|
|
17928
16741
|
shell: false,
|
|
@@ -17961,8 +16774,8 @@ init_skill_update_check();
|
|
|
17961
16774
|
init_config();
|
|
17962
16775
|
init_logger();
|
|
17963
16776
|
var import_picocolors25 = __toESM(require_picocolors(), 1);
|
|
17964
|
-
import { existsSync as
|
|
17965
|
-
import { join as
|
|
16777
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync9, readFileSync as readFileSync13, writeFileSync as writeFileSync10 } from "node:fs";
|
|
16778
|
+
import { join as join26 } from "node:path";
|
|
17966
16779
|
var CACHE_FILENAME3 = "update-check.json";
|
|
17967
16780
|
var CHECK_INTERVAL_MS3 = 6 * 60 * 60 * 1000;
|
|
17968
16781
|
var FETCH_TIMEOUT_MS3 = 1000;
|
|
@@ -17989,14 +16802,14 @@ function isNewerVersion(latest, current) {
|
|
|
17989
16802
|
return false;
|
|
17990
16803
|
}
|
|
17991
16804
|
function getCachePath3() {
|
|
17992
|
-
return
|
|
16805
|
+
return join26(getConfigDir(), CACHE_FILENAME3);
|
|
17993
16806
|
}
|
|
17994
16807
|
function readCache() {
|
|
17995
16808
|
try {
|
|
17996
16809
|
const path3 = getCachePath3();
|
|
17997
|
-
if (!
|
|
16810
|
+
if (!existsSync15(path3))
|
|
17998
16811
|
return null;
|
|
17999
|
-
const data = JSON.parse(
|
|
16812
|
+
const data = JSON.parse(readFileSync13(path3, "utf-8"));
|
|
18000
16813
|
if (typeof data.lastCheck === "number" && isValidVersion(data.latestVersion)) {
|
|
18001
16814
|
return data;
|
|
18002
16815
|
}
|
|
@@ -18008,10 +16821,10 @@ function readCache() {
|
|
|
18008
16821
|
function writeCache(cache) {
|
|
18009
16822
|
try {
|
|
18010
16823
|
const dir = getConfigDir();
|
|
18011
|
-
if (!
|
|
18012
|
-
|
|
16824
|
+
if (!existsSync15(dir)) {
|
|
16825
|
+
mkdirSync9(dir, { recursive: true, mode: 448 });
|
|
18013
16826
|
}
|
|
18014
|
-
|
|
16827
|
+
writeFileSync10(getCachePath3(), JSON.stringify(cache), { mode: 384 });
|
|
18015
16828
|
} catch {}
|
|
18016
16829
|
}
|
|
18017
16830
|
async function fetchLatestVersion() {
|
|
@@ -18095,19 +16908,9 @@ async function checkForUpdates() {
|
|
|
18095
16908
|
}
|
|
18096
16909
|
|
|
18097
16910
|
// src/cli/cli.ts
|
|
18098
|
-
|
|
18099
|
-
|
|
18100
|
-
|
|
18101
|
-
return i2 >= 0 && HOOK_ENTRYPOINTS.has(argv[i2 + 1] ?? "");
|
|
18102
|
-
}
|
|
18103
|
-
function shouldRunBackgroundChecks(actionName, argv) {
|
|
18104
|
-
return actionName !== "upgrade" && !isHookEntrypointInvocation(argv);
|
|
18105
|
-
}
|
|
18106
|
-
var TELEMETRY_EXCLUDED_COMMANDS = new Set([
|
|
18107
|
-
"hooks user-prompt-submit",
|
|
18108
|
-
"hooks post-tool-use",
|
|
18109
|
-
"hooks stop"
|
|
18110
|
-
]);
|
|
16911
|
+
function shouldRunBackgroundChecks(actionName) {
|
|
16912
|
+
return actionName !== "upgrade";
|
|
16913
|
+
}
|
|
18111
16914
|
var TELEMETRY_FLUSH_TIMEOUT_MS = 750;
|
|
18112
16915
|
var MAX_TELEMETRY_CONFIG_BYTES = 64 * 1024;
|
|
18113
16916
|
|
|
@@ -18130,9 +16933,7 @@ function commandTelemetryName(actionCommand) {
|
|
|
18130
16933
|
return actionCommand.args.length > 0 ? "unknown" : "tui";
|
|
18131
16934
|
}
|
|
18132
16935
|
function shouldTrackCommand(command) {
|
|
18133
|
-
|
|
18134
|
-
return false;
|
|
18135
|
-
return !TELEMETRY_EXCLUDED_COMMANDS.has(command);
|
|
16936
|
+
return command !== "telemetry" && !command.startsWith("telemetry ");
|
|
18136
16937
|
}
|
|
18137
16938
|
function commandTelemetryContext() {
|
|
18138
16939
|
try {
|
|
@@ -18234,7 +17035,7 @@ function createProgram(options = {}) {
|
|
|
18234
17035
|
program2.name("dosu").description("Dosu CLI - Manage MCP servers for AI tools").version(getVersionString(), "-v, --version").helpCommand("help [command]", "Show help for a command").option("--debug", "Enable debug logging to stderr", false).hook("preAction", async (thisCommand, actionCommand) => {
|
|
18235
17036
|
const opts = thisCommand.optsWithGlobals();
|
|
18236
17037
|
logger.init({ debug: opts.debug });
|
|
18237
|
-
if (shouldRunBackgroundChecks(actionCommand.name()
|
|
17038
|
+
if (shouldRunBackgroundChecks(actionCommand.name())) {
|
|
18238
17039
|
if (!process.env.CI) {
|
|
18239
17040
|
await checkForUpdates();
|
|
18240
17041
|
}
|
|
@@ -18485,7 +17286,6 @@ Use 'dosu mcp add <agent>' to add Dosu MCP to a tool.`);
|
|
|
18485
17286
|
program2.addCommand(auditCommand());
|
|
18486
17287
|
program2.addCommand(deploymentsCommand());
|
|
18487
17288
|
program2.addCommand(docsCommand());
|
|
18488
|
-
program2.addCommand(hooksCommand());
|
|
18489
17289
|
program2.addCommand(insightsCommand());
|
|
18490
17290
|
program2.addCommand(integrationsCommand());
|
|
18491
17291
|
program2.addCommand(knowledgeCommand());
|
|
@@ -18548,7 +17348,7 @@ Use 'dosu mcp add <agent>' to add Dosu MCP to a tool.`);
|
|
|
18548
17348
|
if (opts.tail !== undefined) {
|
|
18549
17349
|
const n2 = typeof opts.tail === "string" ? parseInt(opts.tail, 10) || 50 : 50;
|
|
18550
17350
|
try {
|
|
18551
|
-
const content =
|
|
17351
|
+
const content = readFileSync14(logPath, "utf-8");
|
|
18552
17352
|
const lines = content.split(`
|
|
18553
17353
|
`);
|
|
18554
17354
|
console.log(lines.slice(-n2).join(`
|
|
@@ -18575,7 +17375,7 @@ async function ensureFreshSession(cfg) {
|
|
|
18575
17375
|
}
|
|
18576
17376
|
}
|
|
18577
17377
|
async function execute() {
|
|
18578
|
-
const telemetry =
|
|
17378
|
+
const telemetry = processCommandTelemetry();
|
|
18579
17379
|
const program2 = createProgram({ telemetry });
|
|
18580
17380
|
try {
|
|
18581
17381
|
await program2.parseAsync(process.argv);
|
|
@@ -18614,4 +17414,4 @@ execute().catch((err) => {
|
|
|
18614
17414
|
process.exit(1);
|
|
18615
17415
|
});
|
|
18616
17416
|
|
|
18617
|
-
//# debugId=
|
|
17417
|
+
//# debugId=692e32c2-f562-301a-6475-6e2164756e21
|