@dosu/cli 0.16.1 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/dosu.js +85 -43
- package/package.json +1 -1
package/bin/dosu.js
CHANGED
|
@@ -2218,6 +2218,34 @@ class Client {
|
|
|
2218
2218
|
async delete(path) {
|
|
2219
2219
|
return this.doRequest("DELETE", path);
|
|
2220
2220
|
}
|
|
2221
|
+
async getWithApiKey(path) {
|
|
2222
|
+
return this.apiKeyRequest("GET", path);
|
|
2223
|
+
}
|
|
2224
|
+
async postWithApiKey(path, body) {
|
|
2225
|
+
return this.apiKeyRequest("POST", path, body);
|
|
2226
|
+
}
|
|
2227
|
+
async apiKeyRequest(method, path, body) {
|
|
2228
|
+
if (!this.config.api_key) {
|
|
2229
|
+
throw new Error("no API key available");
|
|
2230
|
+
}
|
|
2231
|
+
const url = this.baseURL + path;
|
|
2232
|
+
const headers = {
|
|
2233
|
+
"Content-Type": "application/json",
|
|
2234
|
+
"X-Dosu-API-Key": this.config.api_key
|
|
2235
|
+
};
|
|
2236
|
+
const options = { method, headers };
|
|
2237
|
+
if (body !== undefined) {
|
|
2238
|
+
options.body = JSON.stringify(body);
|
|
2239
|
+
}
|
|
2240
|
+
const controller = new AbortController;
|
|
2241
|
+
const timeout = setTimeout(() => controller.abort(), 1e4);
|
|
2242
|
+
options.signal = controller.signal;
|
|
2243
|
+
try {
|
|
2244
|
+
return await fetch(url, options);
|
|
2245
|
+
} finally {
|
|
2246
|
+
clearTimeout(timeout);
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2221
2249
|
async refreshToken() {
|
|
2222
2250
|
if (!this.config.refresh_token) {
|
|
2223
2251
|
throw new Error("no refresh token available");
|
|
@@ -4497,7 +4525,7 @@ var init_dist4 = __esm(() => {
|
|
|
4497
4525
|
function getVersionString() {
|
|
4498
4526
|
return `v${VERSION}`;
|
|
4499
4527
|
}
|
|
4500
|
-
var VERSION = "0.
|
|
4528
|
+
var VERSION = "0.17.0";
|
|
4501
4529
|
|
|
4502
4530
|
// src/debug/logger.ts
|
|
4503
4531
|
import {
|
|
@@ -5408,7 +5436,7 @@ function isDefinitiveError(err) {
|
|
|
5408
5436
|
}
|
|
5409
5437
|
async function requestCreateTicket(cfg, req) {
|
|
5410
5438
|
const client = new Client(cfg);
|
|
5411
|
-
const resp = await client.
|
|
5439
|
+
const resp = await client.postWithApiKey(TICKETS_PATH, req);
|
|
5412
5440
|
if (resp.status !== 202 && resp.status !== 200 && resp.status !== 201) {
|
|
5413
5441
|
throw new TicketHttpError(resp.status);
|
|
5414
5442
|
}
|
|
@@ -5416,7 +5444,7 @@ async function requestCreateTicket(cfg, req) {
|
|
|
5416
5444
|
}
|
|
5417
5445
|
async function requestGetTicket(cfg, ticketId) {
|
|
5418
5446
|
const client = new Client(cfg);
|
|
5419
|
-
const resp = await client.
|
|
5447
|
+
const resp = await client.getWithApiKey(`${TICKETS_PATH}/${encodeURIComponent(ticketId)}`);
|
|
5420
5448
|
if (resp.status !== 200 && resp.status !== 202) {
|
|
5421
5449
|
throw new TicketHttpError(resp.status);
|
|
5422
5450
|
}
|
|
@@ -6224,7 +6252,8 @@ function installClaudeHooks(configPath, opts = {}) {
|
|
|
6224
6252
|
if (typeof cfg.hooks !== "object" || cfg.hooks === null) {
|
|
6225
6253
|
cfg.hooks = {};
|
|
6226
6254
|
}
|
|
6227
|
-
const
|
|
6255
|
+
const includeStop = opts.stop !== false;
|
|
6256
|
+
const events = DEFAULT_HOOK_EVENTS.filter((event) => event !== "Stop" || includeStop);
|
|
6228
6257
|
for (const event of events) {
|
|
6229
6258
|
const existing = Array.isArray(cfg.hooks[event]) ? cfg.hooks[event] : [];
|
|
6230
6259
|
const preserved = existing.filter((g2) => !isDosuGroup(g2));
|
|
@@ -6284,7 +6313,7 @@ function inspectClaudeHooks(configPath) {
|
|
|
6284
6313
|
var DEFAULT_HOOK_EVENTS, MARKER = "__dosu", EVENT_SUBCOMMAND;
|
|
6285
6314
|
var init_claude_code = __esm(() => {
|
|
6286
6315
|
init_config_helpers();
|
|
6287
|
-
DEFAULT_HOOK_EVENTS = ["UserPromptSubmit", "PostToolUse"];
|
|
6316
|
+
DEFAULT_HOOK_EVENTS = ["UserPromptSubmit", "PostToolUse", "Stop"];
|
|
6288
6317
|
EVENT_SUBCOMMAND = {
|
|
6289
6318
|
UserPromptSubmit: "user-prompt-submit",
|
|
6290
6319
|
PostToolUse: "post-tool-use",
|
|
@@ -9389,7 +9418,7 @@ function hasNewVisibleRepository(previousRepos, nextRepos) {
|
|
|
9389
9418
|
const previousRepoIds = new Set(previousRepos.map((repo) => repo.repository_id));
|
|
9390
9419
|
return nextRepos.some((repo) => !previousRepoIds.has(repo.repository_id));
|
|
9391
9420
|
}
|
|
9392
|
-
function
|
|
9421
|
+
function sleep3(ms) {
|
|
9393
9422
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
9394
9423
|
}
|
|
9395
9424
|
async function waitForRepositoryRefresh(trpc, orgID, previousRepos, opts) {
|
|
@@ -9403,7 +9432,7 @@ async function waitForRepositoryRefresh(trpc, orgID, previousRepos, opts) {
|
|
|
9403
9432
|
if (hasNewVisibleRepository(previousRepos, latestRepos)) {
|
|
9404
9433
|
return { repos: latestRepos, foundNew: true };
|
|
9405
9434
|
}
|
|
9406
|
-
await
|
|
9435
|
+
await sleep3(intervalMs);
|
|
9407
9436
|
}
|
|
9408
9437
|
return { repos: latestRepos, foundNew: false };
|
|
9409
9438
|
}
|
|
@@ -9536,7 +9565,7 @@ async function verifyDataSourcesPersist(trpc, orgID, expectedDataSourceIds, opts
|
|
|
9536
9565
|
return { alive, dropped };
|
|
9537
9566
|
if (timeoutMs === 0)
|
|
9538
9567
|
break;
|
|
9539
|
-
await
|
|
9568
|
+
await sleep3(intervalMs);
|
|
9540
9569
|
}
|
|
9541
9570
|
return { alive, dropped };
|
|
9542
9571
|
}
|
|
@@ -10136,7 +10165,7 @@ async function waitForImportTaskCompletion(trpc, taskID, spinner, expectedTotal)
|
|
|
10136
10165
|
return null;
|
|
10137
10166
|
}
|
|
10138
10167
|
}
|
|
10139
|
-
await
|
|
10168
|
+
await sleep4(IMPORT_STATUS_POLL_INTERVAL_MS);
|
|
10140
10169
|
}
|
|
10141
10170
|
}
|
|
10142
10171
|
function buildImportProgressMessage(status, expectedTotal) {
|
|
@@ -10187,7 +10216,7 @@ async function waitForImportableGithubFiles(trpc, orgID, spaceID, expectedDataSo
|
|
|
10187
10216
|
spinner.stop(latestFiles.length > 0 ? "Docs ready" : "No markdown docs found");
|
|
10188
10217
|
return latestFiles;
|
|
10189
10218
|
}
|
|
10190
|
-
await
|
|
10219
|
+
await sleep4(DOC_SCAN_POLL_INTERVAL_MS);
|
|
10191
10220
|
continue;
|
|
10192
10221
|
}
|
|
10193
10222
|
latestFiles = await fetchImportableGithubFiles(trpc, spaceID);
|
|
@@ -10199,7 +10228,7 @@ async function waitForImportableGithubFiles(trpc, orgID, spaceID, expectedDataSo
|
|
|
10199
10228
|
spinner.stop("No markdown docs found");
|
|
10200
10229
|
return [];
|
|
10201
10230
|
}
|
|
10202
|
-
await
|
|
10231
|
+
await sleep4(DOC_SCAN_POLL_INTERVAL_MS);
|
|
10203
10232
|
}
|
|
10204
10233
|
spinner.stop("Timed out");
|
|
10205
10234
|
const choice = await ve({
|
|
@@ -10293,7 +10322,7 @@ async function getKnowledgeStoreID(trpc, spaceID) {
|
|
|
10293
10322
|
return null;
|
|
10294
10323
|
}
|
|
10295
10324
|
}
|
|
10296
|
-
function
|
|
10325
|
+
function sleep4(ms) {
|
|
10297
10326
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
10298
10327
|
}
|
|
10299
10328
|
var DOC_SCAN_POLL_INTERVAL_MS = 2000, DOC_SCAN_POLL_TIMEOUT_MS = 60000, IMPORT_STATUS_POLL_INTERVAL_MS = 2000, IMPORT_STATUS_MAX_ERRORS = 3;
|
|
@@ -12962,6 +12991,8 @@ function saveState(state) {
|
|
|
12962
12991
|
// src/commands/hooks.ts
|
|
12963
12992
|
var COOLDOWN_DEFAULT_MS = 3000;
|
|
12964
12993
|
var TTL_DEFAULT_MS = 10 * 60 * 1000;
|
|
12994
|
+
var STOP_WAIT_DEFAULT_MS = 8000;
|
|
12995
|
+
var STOP_POLL_DEFAULT_MS = 1000;
|
|
12965
12996
|
var SUPPORTED_AGENTS = ["claude-code"];
|
|
12966
12997
|
function cooldownMs() {
|
|
12967
12998
|
const n = Number.parseInt(process.env.DOSU_HOOK_CHECK_COOLDOWN_MS ?? "", 10);
|
|
@@ -12971,12 +13002,21 @@ function ttlMs() {
|
|
|
12971
13002
|
const n = Number.parseInt(process.env.DOSU_HOOK_TTL_MS ?? "", 10);
|
|
12972
13003
|
return Number.isFinite(n) && n > 0 ? n : TTL_DEFAULT_MS;
|
|
12973
13004
|
}
|
|
13005
|
+
function stopWaitMs() {
|
|
13006
|
+
const n = Number.parseInt(process.env.DOSU_HOOK_STOP_WAIT_MS ?? "", 10);
|
|
13007
|
+
return Number.isFinite(n) && n >= 0 ? n : STOP_WAIT_DEFAULT_MS;
|
|
13008
|
+
}
|
|
13009
|
+
function stopPollMs() {
|
|
13010
|
+
const n = Number.parseInt(process.env.DOSU_HOOK_STOP_POLL_MS ?? "", 10);
|
|
13011
|
+
return Number.isFinite(n) && n > 0 ? n : STOP_POLL_DEFAULT_MS;
|
|
13012
|
+
}
|
|
12974
13013
|
function printHookContext(event, additionalContext) {
|
|
12975
13014
|
console.log(JSON.stringify({ hookSpecificOutput: { hookEventName: event, additionalContext } }));
|
|
12976
13015
|
}
|
|
12977
13016
|
function printContinue() {
|
|
12978
13017
|
console.log(JSON.stringify({ continue: true }));
|
|
12979
13018
|
}
|
|
13019
|
+
var sleep2 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
12980
13020
|
function sid8(sessionId) {
|
|
12981
13021
|
return sessionId.slice(0, 8);
|
|
12982
13022
|
}
|
|
@@ -12999,7 +13039,7 @@ async function runUserPromptSubmit(input, now = Date.now()) {
|
|
|
12999
13039
|
return;
|
|
13000
13040
|
}
|
|
13001
13041
|
const cfg = loadConfig();
|
|
13002
|
-
if (!
|
|
13042
|
+
if (!cfg.api_key || !cfg.deployment_id) {
|
|
13003
13043
|
logger.debug("hooks", "submit skipped reason=not-configured");
|
|
13004
13044
|
return;
|
|
13005
13045
|
}
|
|
@@ -13053,7 +13093,7 @@ async function runPostToolUse(input, now = Date.now()) {
|
|
|
13053
13093
|
return;
|
|
13054
13094
|
}
|
|
13055
13095
|
const cfg = loadConfig();
|
|
13056
|
-
if (!
|
|
13096
|
+
if (!cfg.api_key || !cfg.deployment_id) {
|
|
13057
13097
|
saveState({ ...state, lastCheckedAt: now });
|
|
13058
13098
|
return;
|
|
13059
13099
|
}
|
|
@@ -13099,40 +13139,42 @@ async function runStop(input, now = Date.now()) {
|
|
|
13099
13139
|
const sessionId = input.session_id;
|
|
13100
13140
|
if (!sessionId)
|
|
13101
13141
|
return printContinue();
|
|
13102
|
-
if (input.stop_hook_active === true)
|
|
13103
|
-
return printContinue();
|
|
13104
13142
|
const state = loadState(sessionId);
|
|
13105
13143
|
if (!state)
|
|
13106
13144
|
return printContinue();
|
|
13107
13145
|
if (state.status !== "pending" || now > state.expiresAt)
|
|
13108
13146
|
return printContinue();
|
|
13109
|
-
if (state.lastCheckedAt !== undefined && now - state.lastCheckedAt < cooldownMs()) {
|
|
13110
|
-
return printContinue();
|
|
13111
|
-
}
|
|
13112
13147
|
const cfg = loadConfig();
|
|
13113
|
-
if (!
|
|
13148
|
+
if (!cfg.api_key || !cfg.deployment_id)
|
|
13114
13149
|
return printContinue();
|
|
13115
13150
|
const tc = await Promise.resolve().then(() => (init_ticket_client(), exports_ticket_client));
|
|
13116
|
-
|
|
13117
|
-
|
|
13118
|
-
|
|
13119
|
-
|
|
13120
|
-
|
|
13121
|
-
|
|
13122
|
-
|
|
13123
|
-
|
|
13124
|
-
|
|
13125
|
-
|
|
13126
|
-
|
|
13127
|
-
|
|
13128
|
-
|
|
13151
|
+
const pollMs = stopPollMs();
|
|
13152
|
+
const maxWaits = pollMs > 0 ? Math.floor(stopWaitMs() / pollMs) : 0;
|
|
13153
|
+
for (let waited = 0;; waited++) {
|
|
13154
|
+
let resp;
|
|
13155
|
+
try {
|
|
13156
|
+
resp = await tc.requestGetTicket(cfg, state.ticketId);
|
|
13157
|
+
} catch {
|
|
13158
|
+
saveState({ ...state, lastCheckedAt: now });
|
|
13159
|
+
return printContinue();
|
|
13160
|
+
}
|
|
13161
|
+
if (resp.status === "ready" && resp.result) {
|
|
13162
|
+
saveState({ ...state, status: "delivered", deliveredAt: now, lastCheckedAt: now });
|
|
13163
|
+
if (resp.result.context.trim()) {
|
|
13164
|
+
const { buildReadyEnvelope: buildReadyEnvelope2, STOP_PREFIX: STOP_PREFIX2 } = await Promise.resolve().then(() => (init_prompts(), exports_prompts));
|
|
13165
|
+
const envelope = buildReadyEnvelope2(resp.result.context, resp.result.save_recommended ?? false);
|
|
13166
|
+
console.log(JSON.stringify({ decision: "block", reason: `${STOP_PREFIX2}
|
|
13129
13167
|
|
|
13130
13168
|
${envelope}` }));
|
|
13131
|
-
|
|
13132
|
-
|
|
13169
|
+
logger.info("hooks", `stop tid=${state.ticketId} delivered=true`);
|
|
13170
|
+
return;
|
|
13171
|
+
}
|
|
13172
|
+
logger.debug("hooks", `stop tid=${state.ticketId} delivered=true gap-no-block`);
|
|
13173
|
+
return printContinue();
|
|
13133
13174
|
}
|
|
13134
|
-
|
|
13135
|
-
|
|
13175
|
+
if (resp.status !== "pending" || waited >= maxWaits)
|
|
13176
|
+
break;
|
|
13177
|
+
await sleep2(pollMs);
|
|
13136
13178
|
}
|
|
13137
13179
|
saveState({ ...state, lastCheckedAt: now });
|
|
13138
13180
|
logger.debug("hooks", `stop tid=${state.ticketId} delivered=false`);
|
|
@@ -13210,7 +13252,7 @@ async function runInstall(agent, opts) {
|
|
|
13210
13252
|
const { installClaudeHooks: installClaudeHooks2, claudeLocalSettingsPath: claudeLocalSettingsPath2 } = await Promise.resolve().then(() => (init_claude_code(), exports_claude_code));
|
|
13211
13253
|
const configPath = claudeLocalSettingsPath2(resolveDir(opts.dir));
|
|
13212
13254
|
try {
|
|
13213
|
-
const { events } = installClaudeHooks2(configPath, {
|
|
13255
|
+
const { events } = installClaudeHooks2(configPath, { stop: opts.stop });
|
|
13214
13256
|
if (opts.json) {
|
|
13215
13257
|
const { emitStep: emitStep2 } = await Promise.resolve().then(() => exports_output);
|
|
13216
13258
|
emitStep2({ step: "hooks-install", path: configPath, events });
|
|
@@ -13300,7 +13342,7 @@ async function collectDoctorChecks(opts) {
|
|
|
13300
13342
|
detail: "no deployment selected (run 'dosu setup')"
|
|
13301
13343
|
});
|
|
13302
13344
|
}
|
|
13303
|
-
if (cfg.api_key && cfg.deployment_id
|
|
13345
|
+
if (cfg.api_key && cfg.deployment_id) {
|
|
13304
13346
|
const { Client: Client2 } = await Promise.resolve().then(() => (init_client(), exports_client));
|
|
13305
13347
|
const ok = await new Client2(cfg).validateAPIKey(cfg.api_key, cfg.deployment_id);
|
|
13306
13348
|
checks.push({
|
|
@@ -13312,7 +13354,7 @@ async function collectDoctorChecks(opts) {
|
|
|
13312
13354
|
checks.push({
|
|
13313
13355
|
name: "backend",
|
|
13314
13356
|
status: "warn",
|
|
13315
|
-
detail: "skipped (
|
|
13357
|
+
detail: "skipped (no API key / no deployment)"
|
|
13316
13358
|
});
|
|
13317
13359
|
}
|
|
13318
13360
|
return checks;
|
|
@@ -13342,7 +13384,7 @@ function hooksCommand() {
|
|
|
13342
13384
|
cmd.command("post-tool-use").description("Hook entrypoint: poll and inject ready knowledge once").action(async () => {
|
|
13343
13385
|
await runHookEntrypoint("post-tool-use", readStdinRaw());
|
|
13344
13386
|
});
|
|
13345
|
-
cmd.command("stop").description("Hook entrypoint
|
|
13387
|
+
cmd.command("stop").description("Hook entrypoint: last-chance knowledge delivery when the agent stops").action(async () => {
|
|
13346
13388
|
await runHookEntrypoint("stop", readStdinRaw());
|
|
13347
13389
|
});
|
|
13348
13390
|
cmd.command("status").description("Show the active Dosu knowledge ticket for this session").option("--json", "Output JSON", false).action((opts) => {
|
|
@@ -13355,13 +13397,13 @@ function hooksCommand() {
|
|
|
13355
13397
|
}
|
|
13356
13398
|
runStatus(input, opts);
|
|
13357
13399
|
});
|
|
13358
|
-
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("--
|
|
13400
|
+
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", [
|
|
13359
13401
|
"",
|
|
13360
13402
|
`Supported agents: ${SUPPORTED_AGENTS.join(", ")}`,
|
|
13361
13403
|
"",
|
|
13362
13404
|
"Examples:",
|
|
13363
13405
|
" $ dosu hooks install claude-code",
|
|
13364
|
-
" $ dosu hooks install claude-code --
|
|
13406
|
+
" $ dosu hooks install claude-code --no-stop",
|
|
13365
13407
|
" $ dosu hooks install claude-code --dir ./my-project"
|
|
13366
13408
|
].join(`
|
|
13367
13409
|
`)).action((agent, opts) => runInstall(agent, opts));
|