@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.
Files changed (3) hide show
  1. package/README.md +0 -1
  2. package/bin/dosu.js +1366 -2566
  3. 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.46.0", INSTALL_CHANNEL = "npm";
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/hooks/prompts.ts
8190
- var exports_prompts = {};
8191
- __export(exports_prompts, {
8192
- buildReadyEnvelope: () => buildReadyEnvelope,
8193
- STOP_PREFIX: () => STOP_PREFIX,
8194
- LOOKUP_STARTED_NOTE: () => LOOKUP_STARTED_NOTE,
8195
- LATE_RESULT_NOTE: () => LATE_RESULT_NOTE
8196
- });
8197
- function buildReadyEnvelope(context, saveRecommended = false) {
8198
- const trimmed = context.trim();
8199
- const blocks = [];
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
- return data;
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
- var TICKETS_PATH = "/v1/tickets/knowledge", KNOWN_STATUSES, TicketHttpError;
8263
- var init_ticket_client = __esm(() => {
8264
- init_client();
8265
- KNOWN_STATUSES = new Set(["pending", "ready", "failed", "expired"]);
8266
- TicketHttpError = class TicketHttpError extends Error {
8267
- status;
8268
- constructor(status) {
8269
- super(`ticket request failed with status ${status}`);
8270
- this.name = "TicketHttpError";
8271
- this.status = status;
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
- get transient() {
8274
- return this.status >= 500 || this.status === 429;
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 emitNeedUserAction(opts) {
8291
- emitJSONLine({
8292
- step: opts.step,
8293
- status: "need_user_action",
8294
- url: opts.url,
8295
- ticket: opts.ticket,
8296
- resume_command: opts.resume_command,
8297
- expires_in: opts.expires_in,
8298
- agent_next_steps: opts.agent_next_steps
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 emitError(opts) {
8302
- const { step, reason, agent_next_steps, ...rest } = opts;
8303
- emitJSONLine({
8304
- step,
8305
- status: "error",
8306
- reason,
8307
- ...rest,
8308
- agent_next_steps
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 emitStep(opts) {
8312
- const { step, status = "ok", ...rest } = opts;
8313
- emitJSONLine({ step, status, ...rest });
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
- // node_modules/signal-exit/dist/cjs/signals.js
8317
- var require_signals = __commonJS((exports) => {
8318
- Object.defineProperty(exports, "__esModule", { value: true });
8319
- exports.signals = undefined;
8320
- exports.signals = [];
8321
- exports.signals.push("SIGHUP", "SIGINT", "SIGTERM");
8322
- if (process.platform !== "win32") {
8323
- exports.signals.push("SIGALRM", "SIGABRT", "SIGVTALRM", "SIGXCPU", "SIGXFSZ", "SIGUSR2", "SIGTRAP", "SIGSYS", "SIGQUIT", "SIGIOT");
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
- if (process.platform === "linux") {
8326
- exports.signals.push("SIGIO", "SIGPOLL", "SIGPWR", "SIGSTKFLT");
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
- // node_modules/signal-exit/dist/cjs/index.js
8331
- var require_cjs = __commonJS((exports) => {
8332
- var _a;
8333
- Object.defineProperty(exports, "__esModule", { value: true });
8334
- exports.unload = exports.load = exports.onExit = exports.signals = undefined;
8335
- var signals_js_1 = require_signals();
8336
- Object.defineProperty(exports, "signals", { enumerable: true, get: function() {
8337
- return signals_js_1.signals;
8338
- } });
8339
- 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";
8340
- var kExitEmitter = Symbol.for("signal-exit emitter");
8341
- var global = globalThis;
8342
- var ObjectDefineProperty = Object.defineProperty.bind(Object);
8343
-
8344
- class Emitter {
8345
- emitted = {
8346
- afterExit: false,
8347
- exit: false
8348
- };
8349
- listeners = {
8350
- afterExit: [],
8351
- exit: []
8352
- };
8353
- count = 0;
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
- class SignalExitBase {
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
- var signalExitWrap = (handler) => {
8400
- return {
8401
- onExit(cb, opts) {
8402
- return handler.onExit(cb, opts);
8403
- },
8404
- load() {
8405
- return handler.load();
8406
- },
8407
- unload() {
8408
- return handler.unload();
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
- class SignalExitFallback extends SignalExitBase {
8414
- onExit() {
8415
- return () => {};
8416
- }
8417
- load() {}
8418
- unload() {}
8419
- }
8420
-
8421
- class SignalExit extends SignalExitBase {
8422
- #hupSig = process10.platform === "win32" ? "SIGINT" : "SIGHUP";
8423
- #emitter = new Emitter;
8424
- #process;
8425
- #originalProcessEmit;
8426
- #originalProcessReallyExit;
8427
- #sigListeners = {};
8428
- #loaded = false;
8429
- constructor(process11) {
8430
- super();
8431
- this.#process = process11;
8432
- this.#sigListeners = {};
8433
- for (const sig of signals_js_1.signals) {
8434
- this.#sigListeners[sig] = () => {
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
- .fun-path {
10352
- font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
10353
- font-size: 11px;
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
- .headline {
10359
- transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease;
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
- .headline:hover {
10362
- border-color: #fcd34d;
10363
- box-shadow: 0 6px 18px rgba(245, 158, 11, 0.12);
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
- .scorecard {
10366
- transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease;
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
- .scorecard:hover {
10369
- border-color: #fcd34d;
10370
- box-shadow: 0 6px 18px rgba(245, 158, 11, 0.12);
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
- @media (prefers-reduced-motion: reduce) {
10373
- .brand .cursor { animation: none; }
10374
- .suggestion, .stacked-bar-card, .compare-card, .headline, .scorecard {
10375
- transition: none;
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
- .suggestion:hover, .stacked-bar-card:hover, .compare-card:hover { transform: none; }
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
- /* Responsive */
10381
- @media (max-width: 840px) {
10382
- .stats-row { grid-template-columns: repeat(3, 1fr); }
10383
- .scorecard { grid-template-columns: 1fr; }
10384
- }
10385
- @media (max-width: 640px) {
10386
- body { padding: 32px 16px 60px; }
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
- // src/insights/index.ts
10396
- var init_insights2 = __esm(() => {
10397
- init_insights();
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
- // src/auth/errors.ts
10401
- var exports_errors = {};
10402
- __export(exports_errors, {
10403
- OAuthCallbackError: () => OAuthCallbackError
10404
- });
10405
- var OAuthCallbackError;
10406
- var init_errors = __esm(() => {
10407
- OAuthCallbackError = class OAuthCallbackError extends Error {
10408
- error;
10409
- errorCode;
10410
- errorDescription;
10411
- constructor(message, details = {}) {
10412
- super(message);
10413
- this.name = "OAuthCallbackError";
10414
- this.error = details.error;
10415
- this.errorCode = details.errorCode;
10416
- this.errorDescription = details.errorDescription;
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
- get userMessage() {
10419
- const desc = this.errorDescription ?? this.message;
10420
- if (this.errorCode === "bad_oauth_state" || /state/i.test(desc)) {
10421
- return `Authentication failed: ${desc}. Run \`dosu login\` again.`;
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/version/skill-update-check.ts
10429
- import { existsSync as existsSync12, mkdirSync as mkdirSync7, readFileSync as readFileSync12, writeFileSync as writeFileSync6 } from "node:fs";
10430
- import { join as join10 } from "node:path";
10431
- function getCachePath2() {
10432
- return join10(getConfigDir(), CACHE_FILENAME2);
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 readSkillCache() {
10435
- try {
10436
- const path3 = getCachePath2();
10437
- if (!existsSync12(path3))
10438
- return null;
10439
- const data = JSON.parse(readFileSync12(path3, "utf-8"));
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 writeSkillCache(cache) {
10449
- try {
10450
- const dir = getConfigDir();
10451
- if (!existsSync12(dir)) {
10452
- mkdirSync7(dir, { recursive: true, mode: 448 });
10453
- }
10454
- writeFileSync6(getCachePath2(), JSON.stringify(cache), { mode: 384 });
10455
- } catch {}
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
- async function fetchLatestSha() {
10458
- const controller = new AbortController;
10459
- const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS2);
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
- const resp = await fetch(GITHUB_API_URL, {
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 null;
10475
- } finally {
10476
- clearTimeout(timeout);
10025
+ return {};
10477
10026
  }
10478
10027
  }
10479
- function displayNotice() {
10480
- const msg = `
10481
- ${import_picocolors12.default.yellow(" Dosu skill update available")}
10482
- ` + `${import_picocolors12.default.dim(' Run "dosu skill update" to upgrade')}
10483
- `;
10484
- console.error(msg);
10485
- }
10486
- function checkForSkillUpdates() {
10487
- try {
10488
- const cache = readSkillCache();
10489
- if (cache?.installedSha && cache.latestSha && cache.latestSha !== cache.installedSha) {
10490
- displayNotice();
10491
- }
10492
- const isStale = !cache || Date.now() - cache.lastCheck > CHECK_INTERVAL_MS2;
10493
- if (isStale) {
10494
- fetchLatestSha().then((latest) => {
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
- }).catch((err) => {
10505
- logger.error("skill-update-check", `Background fetch failed: ${err}`);
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
- } catch (err) {
10509
- logger.error("skill-update-check", `Skill update check failed: ${err}`);
10069
+ result.push(data[i2]);
10070
+ i2++;
10510
10071
  }
10072
+ return result.join("");
10511
10073
  }
10512
- 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";
10513
- var init_skill_update_check = __esm(() => {
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 skillInstallTargetForProvider(providerID) {
10530
- const agentID = SKILL_AGENT_BY_PROVIDER[providerID];
10531
- if (!agentID)
10532
- return null;
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
- return {
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 execQuiet(command) {
10553
- return new Promise((resolve, reject) => {
10554
- exec2(command, { windowsHide: true }, (error) => {
10555
- if (error)
10556
- reject(error);
10557
- else
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
- async function installSkill(providerIDs, options = {}) {
10563
- const agentArgs = skillAgentArgs(providerIDs);
10564
- if (!agentArgs) {
10565
- logger.debug("skill", "No selected providers support the Dosu skill");
10566
- return { success: true };
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
- const command = `npx skills add ${SKILL_REPO} -g ${agentArgs} -s "*" -y`;
10570
- if (options.quiet)
10571
- await execQuiet(command);
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 sha = await fetchLatestSha();
10579
- if (sha) {
10580
- writeSkillCache({
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
- logger.debug("skill", "Skill installed but could not fetch latest SHA");
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 import_picocolors13, SKILL_REPO = "dosu-ai/dosu-skill", SKILL_NAME = "dosu", SUPPORTED_SKILL_AGENTS, SKILL_AGENT_BY_PROVIDER;
10632
- var init_skill = __esm(() => {
10633
- init_commander();
10634
- init_logger();
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 existsSync13 } from "node:fs";
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 dirname5, join as join12 } from "node:path";
10123
+ import { delimiter, dirname as dirname3, join as join7 } from "node:path";
10672
10124
  function isInstalled(paths) {
10673
- return paths.some((p2) => existsSync13(expandHome(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 join12(homedir2(), path3.slice(1));
10130
+ return join7(homedir2(), path3.slice(1));
10679
10131
  }
10680
10132
  function appSupportDir() {
10681
10133
  switch (platform2()) {
10682
10134
  case "darwin": {
10683
- return join12(homedir2(), "Library", "Application Support");
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 join12(homedir2(), ".config");
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 = join12(dir, bin);
10702
- if (existsSync13(candidate))
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 [dirname5(npx), "/usr/bin", "/bin"].join(delimiter);
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 join13 } from "node:path";
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) => join13(cwd, ".mcp.json")
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 join14 } from "node:path";
10255
+ import { join as join9 } from "node:path";
10804
10256
  function configPath() {
10805
- return join14(appSupportDir(), "Claude", "claude_desktop_config.json");
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: () => [join14(appSupportDir(), "Claude")],
10813
- isInstalled: () => isInstalled([join14(appSupportDir(), "Claude")]),
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 join15 } from "node:path";
10844
- var extensionDir = () => join15(appSupportDir(), "Code", "User", "globalStorage", "saoudrizwan.claude-dev"), ClineProvider = () => createJSONProvider({
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: join15(extensionDir(), "settings", "cline_mcp_settings.json"),
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 join16 } from "node:path";
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: join16(clineDir(), "data", "settings", "cline_mcp_settings.json"),
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 existsSync14, readFileSync as readFileSync13 } from "node:fs";
10893
- import { join as join17 } from "node:path";
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 join17(codexHome(), "config.toml");
10900
- return join17(process.cwd(), ".codex", "config.toml");
10351
+ return join12(codexHome(), "config.toml");
10352
+ return join12(process.cwd(), ".codex", "config.toml");
10901
10353
  }
10902
10354
  function readTOML(path3) {
10903
- if (!existsSync14(path3))
10355
+ if (!existsSync8(path3))
10904
10356
  return "";
10905
- return readFileSync13(path3, "utf-8");
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: () => join17(codexHome(), "config.toml"),
10420
+ globalConfigPath: () => join12(codexHome(), "config.toml"),
10969
10421
  isConfigured: () => {
10970
- const content = readTOML(join17(codexHome(), "config.toml"));
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 init_codex2 = __esm(() => {
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 join18 } from "node:path";
10444
+ import { join as join13 } from "node:path";
10993
10445
  function globalPath() {
10994
10446
  if (process.env.XDG_CONFIG_HOME) {
10995
- return join18(process.env.XDG_CONFIG_HOME, "mcp-config.json");
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 = join18(process.cwd(), ".vscode", "mcp.json");
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 = join18(process.cwd(), ".vscode", "mcp.json");
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 join19 } from "node:path";
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) => join19(cwd, ".cursor", "mcp.json")
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 join20 } from "node:path";
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) => join20(cwd, ".factory", "mcp.json")
10533
+ localConfigPath: (cwd) => join15(cwd, ".factory", "mcp.json")
11082
10534
  });
11083
- var init_factory2 = __esm(() => {
10535
+ var init_factory = __esm(() => {
11084
10536
  init_base();
11085
10537
  });
11086
10538
 
11087
10539
  // src/mcp/providers/gemini.ts
11088
- import { join as join21 } from "node:path";
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) => join21(cwd, ".gemini", "settings.json")
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 existsSync15 } from "node:fs";
11148
- import { join as join22 } from "node:path";
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 (existsSync15(jsonPath))
10603
+ if (existsSync9(jsonPath))
11152
10604
  return jsonPath;
11153
10605
  const jsoncPath = expandHome("~/.mcporter/mcporter.jsonc");
11154
- if (existsSync15(jsoncPath))
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() : join22(process.cwd(), "config", "mcporter.json");
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() : join22(process.cwd(), "config", "mcporter.json");
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 join23 } from "node:path";
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) => join23(cwd, "opencode.json")
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 join24 } from "node:path";
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: [join24(appSupportDir(), "Code")],
11225
- globalPath: join24(appSupportDir(), "Code", "User", "mcp.json"),
10676
+ paths: [join19(appSupportDir(), "Code")],
10677
+ globalPath: join19(appSupportDir(), "Code", "User", "mcp.json"),
11226
10678
  topKey: "servers",
11227
- localConfigPath: (cwd) => join24(cwd, ".vscode", "mcp.json")
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 join25 } from "node:path";
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: [join25(homedir3(), ".codeium", "windsurf")],
11243
- globalPath: join25(homedir3(), ".codeium", "windsurf", "mcp_config.json"),
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 join26 } from "node:path";
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 join26(appSupportDir(), "Zed");
11257
- return join26(appSupportDir(), "zed");
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: join26(zedConfigDir(), "settings.json"),
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) => join26(cwd, ".zed", "settings.json")
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
- init_codex2();
10770
+ init_codex();
11319
10771
  init_copilot();
11320
10772
  init_cursor();
11321
- init_factory2();
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 existsSync16, mkdirSync as mkdirSync8, readFileSync as readFileSync14, unlinkSync, writeFileSync as writeFileSync7 } from "node:fs";
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 dirname6, join as join27 } from "node:path";
10786
+ import { dirname as dirname4, join as join22 } from "node:path";
11335
10787
  function claudeConfigDir() {
11336
- return process.env.CLAUDE_CONFIG_DIR || join27(homedir4(), ".claude");
10788
+ return process.env.CLAUDE_CONFIG_DIR || join22(homedir4(), ".claude");
11337
10789
  }
11338
10790
  function codexHome2() {
11339
- return process.env.CODEX_HOME || join27(homedir4(), ".codex");
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
- mkdirSync8(dirname6(path3), { recursive: true });
10817
+ mkdirSync6(dirname4(path3), { recursive: true });
11366
10818
  }
11367
10819
  function writeIfChanged(path3, content) {
11368
- if (!existsSync16(path3)) {
10820
+ if (!existsSync10(path3)) {
11369
10821
  ensureParent(path3);
11370
- writeFileSync7(path3, content, "utf-8");
10822
+ writeFileSync6(path3, content, "utf-8");
11371
10823
  return "created";
11372
10824
  }
11373
- if (readFileSync14(path3, "utf-8") === content)
10825
+ if (readFileSync9(path3, "utf-8") === content)
11374
10826
  return "unchanged";
11375
- writeFileSync7(path3, content, "utf-8");
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 (!existsSync16(path3)) {
10853
+ if (!existsSync10(path3)) {
11402
10854
  const section2 = buildSection(content);
11403
10855
  ensureParent(path3);
11404
- writeFileSync7(path3, `${section2}
10856
+ writeFileSync6(path3, `${section2}
11405
10857
  `, "utf-8");
11406
10858
  return "created";
11407
10859
  }
11408
- const existing = readFileSync14(path3, "utf-8");
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
- writeFileSync7(path3, next, "utf-8");
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 (!existsSync16(path3))
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 = readFileSync14(path3, "utf-8");
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
- writeFileSync7(path3, `${next}${lineEnding}`, "utf-8");
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: () => join27(claudeConfigDir(), "rules", "dosu.md")
10933
+ path: () => join22(claudeConfigDir(), "rules", "dosu.md")
11482
10934
  },
11483
10935
  cursor: {
11484
10936
  kind: "file",
11485
- path: () => join27(homedir4(), ".cursor", "rules", "dosu.mdc"),
10937
+ path: () => join22(homedir4(), ".cursor", "rules", "dosu.mdc"),
11486
10938
  cursorFrontmatter: true
11487
10939
  },
11488
10940
  codex: {
11489
10941
  kind: "section",
11490
- path: () => join27(codexHome2(), "AGENTS.md")
10942
+ path: () => join22(codexHome2(), "AGENTS.md")
11491
10943
  },
11492
10944
  opencode: {
11493
10945
  kind: "section",
11494
- path: () => join27(homedir4(), ".config", "opencode", "AGENTS.md")
10946
+ path: () => join22(homedir4(), ".config", "opencode", "AGENTS.md")
11495
10947
  },
11496
10948
  gemini: {
11497
10949
  kind: "section",
11498
- path: () => join27(homedir4(), ".gemini", "GEMINI.md")
10950
+ path: () => join22(homedir4(), ".gemini", "GEMINI.md")
11499
10951
  },
11500
10952
  antigravity: {
11501
10953
  kind: "section",
11502
- path: () => join27(homedir4(), ".gemini", "GEMINI.md")
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 existsSync17, readFileSync as readFileSync15, writeFileSync as writeFileSync8 } from "node:fs";
11510
- import { join as join28 } from "node:path";
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 = join28(cwd, "AGENTS.md");
11549
- if (!existsSync17(path3)) {
11000
+ const path3 = join23(cwd, "AGENTS.md");
11001
+ if (!existsSync11(path3)) {
11550
11002
  const section2 = buildDosuAgentsSection(content);
11551
- writeFileSync8(path3, `${section2}
11003
+ writeFileSync7(path3, `${section2}
11552
11004
  `);
11553
11005
  return { path: path3, action: "created" };
11554
11006
  }
11555
- const existing = readFileSync15(path3, "utf-8");
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
- writeFileSync8(path3, next);
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 existsSync18,
11050
+ existsSync as existsSync12,
11599
11051
  lstatSync,
11600
- mkdirSync as mkdirSync9,
11601
- readFileSync as readFileSync16,
11602
- renameSync as renameSync3,
11052
+ mkdirSync as mkdirSync7,
11053
+ readFileSync as readFileSync11,
11054
+ renameSync as renameSync2,
11603
11055
  unlinkSync as unlinkSync2,
11604
- writeFileSync as writeFileSync9
11056
+ writeFileSync as writeFileSync8
11605
11057
  } from "node:fs";
11606
- import { join as join29 } from "node:path";
11058
+ import { join as join24 } from "node:path";
11607
11059
  function getTelemetrySettingsPath() {
11608
- return join29(getConfigDir(), TELEMETRY_SETTINGS_FILENAME);
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 = readFileSync16(path3, "utf-8");
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 (!existsSync18(dir))
11709
- mkdirSync9(dir, { recursive: true, mode: 448 });
11710
- writeFileSync9(temporaryPath, `${JSON.stringify(settings2, null, 2)}
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
- renameSync3(temporaryPath, path3);
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 = "2facad12-755f-3f33-6475-6e2164756e21";
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 existsSync19 } from "node:fs";
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 (!existsSync19(resolvedPath))
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 as spawnSync2 } from "node:child_process";
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 spawnSync2(cmd, [bin], { stdio: "ignore" }).status === 0;
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 = spawnSync2("claude", ["--model", AUDIT_AGENT_MODEL, buildAuditHandoffPrompt()], {
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 existsSync20, mkdirSync as mkdirSync10, readdirSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync10 } from "node:fs";
13840
- import { dirname as dirname7, join as join30 } from "node:path";
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 join30(getConfigDir(), "insights");
13395
+ return join25(getConfigDir(), "insights");
13945
13396
  }
13946
13397
  function reportPath(timestamp) {
13947
13398
  const dir = insightsDir();
13948
13399
  if (!timestamp)
13949
- return join30(dir, "latest.html");
13400
+ return join25(dir, "latest.html");
13950
13401
  const iso = timestamp.toISOString().slice(0, 19).replace(/:/g, "-");
13951
- return join30(dir, `report-${iso}Z.html`);
13402
+ return join25(dir, `report-${iso}Z.html`);
13952
13403
  }
13953
13404
  function pruneOldReports(dir, keepN) {
13954
- if (!existsSync20(dir))
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(join30(dir, f2));
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 = dirname7(path3);
14039
- if (!existsSync20(dir))
14040
- mkdirSync10(dir, { recursive: true, mode: 448 });
14041
- writeFileSync10(path3, content, { mode: 384 });
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
- async function readErrorBody2(resp) {
14294
- try {
14295
- return (await resp.text()).slice(0, 512);
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 sleep5(POLL_INTERVAL_MS);
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 sleep5(ms) {
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 readFileSync19,
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 readFileSync17 } from "node:fs";
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 = readFileSync17(opts.bodyFile, "utf-8");
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 spawnSync3 } from "node:child_process";
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 = spawnSync3(invocation.command, invocation.args, {
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 = spawnSync3(plan.command, plan.args, {
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 existsSync21, mkdirSync as mkdirSync11, readFileSync as readFileSync18, writeFileSync as writeFileSync11 } from "node:fs";
17965
- import { join as join31 } from "node:path";
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 join31(getConfigDir(), CACHE_FILENAME3);
16805
+ return join26(getConfigDir(), CACHE_FILENAME3);
17993
16806
  }
17994
16807
  function readCache() {
17995
16808
  try {
17996
16809
  const path3 = getCachePath3();
17997
- if (!existsSync21(path3))
16810
+ if (!existsSync15(path3))
17998
16811
  return null;
17999
- const data = JSON.parse(readFileSync18(path3, "utf-8"));
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 (!existsSync21(dir)) {
18012
- mkdirSync11(dir, { recursive: true, mode: 448 });
16824
+ if (!existsSync15(dir)) {
16825
+ mkdirSync9(dir, { recursive: true, mode: 448 });
18013
16826
  }
18014
- writeFileSync11(getCachePath3(), JSON.stringify(cache), { mode: 384 });
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
- var HOOK_ENTRYPOINTS = new Set(["user-prompt-submit", "post-tool-use", "stop"]);
18099
- function isHookEntrypointInvocation(argv) {
18100
- const i2 = argv.indexOf("hooks");
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
- if (command === "telemetry" || command.startsWith("telemetry "))
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(), process.argv)) {
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 = readFileSync19(logPath, "utf-8");
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 = isHookEntrypointInvocation(process.argv) ? undefined : processCommandTelemetry();
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=2facad12-755f-3f33-6475-6e2164756e21
17417
+ //# debugId=692e32c2-f562-301a-6475-6e2164756e21