@dosu/cli 0.16.0 → 0.16.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/bin/dosu.js +297 -248
  2. package/package.json +1 -1
package/bin/dosu.js CHANGED
@@ -2082,6 +2082,260 @@ var init_esm = __esm(() => {
2082
2082
  } = import__.default);
2083
2083
  });
2084
2084
 
2085
+ // src/config/config.ts
2086
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2087
+ import { join } from "node:path";
2088
+ function getConfigDir() {
2089
+ const dirName = process.env.DOSU_DEV === "true" ? "dosu-cli-dev" : "dosu-cli";
2090
+ const xdgConfig = process.env.XDG_CONFIG_HOME;
2091
+ if (xdgConfig)
2092
+ return join(xdgConfig, dirName);
2093
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
2094
+ return join(home, ".config", dirName);
2095
+ }
2096
+ function getConfigPath() {
2097
+ const dir = getConfigDir();
2098
+ if (!existsSync(dir)) {
2099
+ mkdirSync(dir, { recursive: true, mode: 448 });
2100
+ }
2101
+ return join(dir, "config.json");
2102
+ }
2103
+ function loadConfig() {
2104
+ const path = getConfigPath();
2105
+ if (!existsSync(path)) {
2106
+ return emptyConfig();
2107
+ }
2108
+ try {
2109
+ const data = readFileSync(path, "utf-8");
2110
+ return JSON.parse(data);
2111
+ } catch {
2112
+ return emptyConfig();
2113
+ }
2114
+ }
2115
+ function saveConfig(cfg) {
2116
+ const path = getConfigPath();
2117
+ const dir = getConfigDir();
2118
+ if (!existsSync(dir)) {
2119
+ mkdirSync(dir, { recursive: true, mode: 448 });
2120
+ }
2121
+ writeFileSync(path, JSON.stringify(cfg, null, 2), { mode: 384 });
2122
+ }
2123
+ function isAuthenticated(cfg) {
2124
+ return cfg.access_token !== "";
2125
+ }
2126
+ function isTokenExpired(cfg) {
2127
+ if (cfg.expires_at === 0)
2128
+ return false;
2129
+ return Math.floor(Date.now() / 1000) > cfg.expires_at - 300;
2130
+ }
2131
+ function emptyConfig() {
2132
+ return {
2133
+ access_token: "",
2134
+ refresh_token: "",
2135
+ expires_at: 0
2136
+ };
2137
+ }
2138
+ var MODE_OSS = "oss";
2139
+ var init_config = () => {};
2140
+
2141
+ // src/config/constants.ts
2142
+ function getWebAppURL() {
2143
+ return process.env.DOSU_WEB_APP_URL_OVERRIDE ?? "https://app.dosu.dev" ?? "";
2144
+ }
2145
+ function getBackendURL() {
2146
+ return process.env.DOSU_BACKEND_URL_OVERRIDE ?? "https://api.dosu.dev" ?? "";
2147
+ }
2148
+ function getSupabaseURL() {
2149
+ return process.env.SUPABASE_URL_OVERRIDE ?? "https://wldmetsoicvieidlsqrb.supabase.co" ?? "";
2150
+ }
2151
+ function getSupabaseAnonKey() {
2152
+ return process.env.SUPABASE_ANON_KEY_OVERRIDE ?? "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6IndsbG1ldHNvaWN2aWVpZGxzcXJiIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MzQ0NjA1NjUsImV4cCI6MjA1MDAzNjU2NX0.15LIxqSMVRnxsvLrk0GPjL1aSYPfOeaeFMdJXqgc5Xs" ?? "";
2153
+ }
2154
+
2155
+ // src/client/client.ts
2156
+ var exports_client = {};
2157
+ __export(exports_client, {
2158
+ SessionExpiredError: () => SessionExpiredError,
2159
+ Client: () => Client
2160
+ });
2161
+
2162
+ class Client {
2163
+ baseURL;
2164
+ config;
2165
+ constructor(cfg) {
2166
+ this.baseURL = getBackendURL();
2167
+ this.config = cfg;
2168
+ }
2169
+ async doRequest(method, path, body) {
2170
+ if (!isAuthenticated(this.config)) {
2171
+ throw new Error("not authenticated - please run setup first");
2172
+ }
2173
+ if (isTokenExpired(this.config)) {
2174
+ await this.refreshToken();
2175
+ }
2176
+ let resp = await this.doRequestOnce(method, path, body);
2177
+ if (resp.status === 401 || resp.status === 403) {
2178
+ try {
2179
+ await this.refreshToken();
2180
+ } catch {
2181
+ throw new SessionExpiredError;
2182
+ }
2183
+ resp = await this.doRequestOnce(method, path, body);
2184
+ }
2185
+ return resp;
2186
+ }
2187
+ async doRequestRaw(method, path) {
2188
+ return this.doRequestOnce(method, path);
2189
+ }
2190
+ async doRequestOnce(method, path, body) {
2191
+ const url = this.baseURL + path;
2192
+ const headers = {
2193
+ "Content-Type": "application/json",
2194
+ "Supabase-Access-Token": this.config.access_token
2195
+ };
2196
+ const options = { method, headers };
2197
+ if (body !== undefined) {
2198
+ options.body = JSON.stringify(body);
2199
+ }
2200
+ const controller = new AbortController;
2201
+ const timeout = setTimeout(() => controller.abort(), 1e4);
2202
+ options.signal = controller.signal;
2203
+ try {
2204
+ return await fetch(url, options);
2205
+ } finally {
2206
+ clearTimeout(timeout);
2207
+ }
2208
+ }
2209
+ async get(path) {
2210
+ return this.doRequest("GET", path);
2211
+ }
2212
+ async post(path, body) {
2213
+ return this.doRequest("POST", path, body);
2214
+ }
2215
+ async put(path, body) {
2216
+ return this.doRequest("PUT", path, body);
2217
+ }
2218
+ async delete(path) {
2219
+ return this.doRequest("DELETE", path);
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
+ }
2249
+ async refreshToken() {
2250
+ if (!this.config.refresh_token) {
2251
+ throw new Error("no refresh token available");
2252
+ }
2253
+ const supabaseURL = getSupabaseURL();
2254
+ const endpoint = `${supabaseURL}/auth/v1/token?grant_type=refresh_token`;
2255
+ const resp = await fetch(endpoint, {
2256
+ method: "POST",
2257
+ headers: {
2258
+ "Content-Type": "application/json",
2259
+ apikey: getSupabaseAnonKey()
2260
+ },
2261
+ body: JSON.stringify({ refresh_token: this.config.refresh_token })
2262
+ });
2263
+ if (resp.status !== 200) {
2264
+ throw new Error(`refresh failed with status ${resp.status}`);
2265
+ }
2266
+ const data = await resp.json();
2267
+ this.config.access_token = data.access_token;
2268
+ this.config.refresh_token = data.refresh_token;
2269
+ this.config.expires_at = Math.floor(Date.now() / 1000) + data.expires_in;
2270
+ saveConfig(this.config);
2271
+ }
2272
+ async getDeployments() {
2273
+ const resp = await this.get("/v1/mcp/deployments");
2274
+ if (resp.status !== 200) {
2275
+ let detail = await readErrorBody(resp);
2276
+ if (!detail || detail === "Internal Server Error") {
2277
+ detail = "check backend logs for details";
2278
+ }
2279
+ throw new Error(`failed to fetch deployments (status ${resp.status}): ${detail}`);
2280
+ }
2281
+ return resp.json();
2282
+ }
2283
+ async getOrgs() {
2284
+ const resp = await this.get("/v1/mcp/orgs");
2285
+ if (resp.status !== 200) {
2286
+ const detail = await readErrorBody(resp);
2287
+ throw new Error(`failed to fetch orgs (status ${resp.status}): ${detail}`);
2288
+ }
2289
+ return resp.json();
2290
+ }
2291
+ async validateAPIKey(apiKey, deploymentID) {
2292
+ try {
2293
+ const endpoint = `${this.baseURL}/v1/mcp/deployments/${encodeURIComponent(deploymentID)}`;
2294
+ const controller = new AbortController;
2295
+ const timeout = setTimeout(() => controller.abort(), 5000);
2296
+ try {
2297
+ const resp = await fetch(endpoint, {
2298
+ method: "GET",
2299
+ headers: { "X-Dosu-API-Key": apiKey },
2300
+ signal: controller.signal
2301
+ });
2302
+ return resp.status !== 401 && resp.status !== 403;
2303
+ } finally {
2304
+ clearTimeout(timeout);
2305
+ }
2306
+ } catch {
2307
+ return true;
2308
+ }
2309
+ }
2310
+ async createAPIKey(deploymentID, name) {
2311
+ const path = `/v1/mcp/deployments/${deploymentID}/api-keys`;
2312
+ const resp = await this.post(path, { name });
2313
+ if (resp.status !== 200 && resp.status !== 201) {
2314
+ const detail = await readErrorBody(resp);
2315
+ throw new Error(`failed to create API key (status ${resp.status}): ${detail}`);
2316
+ }
2317
+ return resp.json();
2318
+ }
2319
+ }
2320
+ async function readErrorBody(resp) {
2321
+ try {
2322
+ const text = await resp.text();
2323
+ return text.slice(0, 1024);
2324
+ } catch {
2325
+ return "";
2326
+ }
2327
+ }
2328
+ var SessionExpiredError;
2329
+ var init_client = __esm(() => {
2330
+ init_config();
2331
+ SessionExpiredError = class SessionExpiredError extends Error {
2332
+ constructor() {
2333
+ super("session expired");
2334
+ this.name = "SessionExpiredError";
2335
+ }
2336
+ };
2337
+ });
2338
+
2085
2339
  // node_modules/picocolors/picocolors.js
2086
2340
  var require_picocolors = __commonJS((exports, module) => {
2087
2341
  var p = process || {};
@@ -4267,81 +4521,11 @@ var init_dist4 = __esm(() => {
4267
4521
  allowErrorProps = SuperJSON.allowErrorProps;
4268
4522
  });
4269
4523
 
4270
- // src/config/config.ts
4271
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
4272
- import { join } from "node:path";
4273
- function getConfigDir() {
4274
- const dirName = process.env.DOSU_DEV === "true" ? "dosu-cli-dev" : "dosu-cli";
4275
- const xdgConfig = process.env.XDG_CONFIG_HOME;
4276
- if (xdgConfig)
4277
- return join(xdgConfig, dirName);
4278
- const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
4279
- return join(home, ".config", dirName);
4280
- }
4281
- function getConfigPath() {
4282
- const dir = getConfigDir();
4283
- if (!existsSync(dir)) {
4284
- mkdirSync(dir, { recursive: true, mode: 448 });
4285
- }
4286
- return join(dir, "config.json");
4287
- }
4288
- function loadConfig() {
4289
- const path = getConfigPath();
4290
- if (!existsSync(path)) {
4291
- return emptyConfig();
4292
- }
4293
- try {
4294
- const data = readFileSync(path, "utf-8");
4295
- return JSON.parse(data);
4296
- } catch {
4297
- return emptyConfig();
4298
- }
4299
- }
4300
- function saveConfig(cfg) {
4301
- const path = getConfigPath();
4302
- const dir = getConfigDir();
4303
- if (!existsSync(dir)) {
4304
- mkdirSync(dir, { recursive: true, mode: 448 });
4305
- }
4306
- writeFileSync(path, JSON.stringify(cfg, null, 2), { mode: 384 });
4307
- }
4308
- function isAuthenticated(cfg) {
4309
- return cfg.access_token !== "";
4310
- }
4311
- function isTokenExpired(cfg) {
4312
- if (cfg.expires_at === 0)
4313
- return false;
4314
- return Math.floor(Date.now() / 1000) > cfg.expires_at - 300;
4315
- }
4316
- function emptyConfig() {
4317
- return {
4318
- access_token: "",
4319
- refresh_token: "",
4320
- expires_at: 0
4321
- };
4322
- }
4323
- var MODE_OSS = "oss";
4324
- var init_config = () => {};
4325
-
4326
- // src/config/constants.ts
4327
- function getWebAppURL() {
4328
- return process.env.DOSU_WEB_APP_URL_OVERRIDE ?? "https://app.dosu.dev" ?? "";
4329
- }
4330
- function getBackendURL() {
4331
- return process.env.DOSU_BACKEND_URL_OVERRIDE ?? "https://api.dosu.dev" ?? "";
4332
- }
4333
- function getSupabaseURL() {
4334
- return process.env.SUPABASE_URL_OVERRIDE ?? "https://wldmetsoicvieidlsqrb.supabase.co" ?? "";
4335
- }
4336
- function getSupabaseAnonKey() {
4337
- return process.env.SUPABASE_ANON_KEY_OVERRIDE ?? "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6IndsbG1ldHNvaWN2aWVpZGxzcXJiIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MzQ0NjA1NjUsImV4cCI6MjA1MDAzNjU2NX0.15LIxqSMVRnxsvLrk0GPjL1aSYPfOeaeFMdJXqgc5Xs" ?? "";
4338
- }
4339
-
4340
4524
  // src/version/version.ts
4341
4525
  function getVersionString() {
4342
4526
  return `v${VERSION}`;
4343
4527
  }
4344
- var VERSION = "0.16.0";
4528
+ var VERSION = "0.16.2";
4345
4529
 
4346
4530
  // src/debug/logger.ts
4347
4531
  import {
@@ -4474,162 +4658,6 @@ var init_logger = __esm(() => {
4474
4658
  };
4475
4659
  });
4476
4660
 
4477
- // src/client/client.ts
4478
- var exports_client = {};
4479
- __export(exports_client, {
4480
- SessionExpiredError: () => SessionExpiredError,
4481
- Client: () => Client
4482
- });
4483
-
4484
- class Client {
4485
- baseURL;
4486
- config;
4487
- constructor(cfg) {
4488
- this.baseURL = getBackendURL();
4489
- this.config = cfg;
4490
- }
4491
- async doRequest(method, path, body) {
4492
- if (!isAuthenticated(this.config)) {
4493
- throw new Error("not authenticated - please run setup first");
4494
- }
4495
- if (isTokenExpired(this.config)) {
4496
- await this.refreshToken();
4497
- }
4498
- let resp = await this.doRequestOnce(method, path, body);
4499
- if (resp.status === 401 || resp.status === 403) {
4500
- try {
4501
- await this.refreshToken();
4502
- } catch {
4503
- throw new SessionExpiredError;
4504
- }
4505
- resp = await this.doRequestOnce(method, path, body);
4506
- }
4507
- return resp;
4508
- }
4509
- async doRequestRaw(method, path) {
4510
- return this.doRequestOnce(method, path);
4511
- }
4512
- async doRequestOnce(method, path, body) {
4513
- const url = this.baseURL + path;
4514
- const headers = {
4515
- "Content-Type": "application/json",
4516
- "Supabase-Access-Token": this.config.access_token
4517
- };
4518
- const options = { method, headers };
4519
- if (body !== undefined) {
4520
- options.body = JSON.stringify(body);
4521
- }
4522
- const controller = new AbortController;
4523
- const timeout = setTimeout(() => controller.abort(), 1e4);
4524
- options.signal = controller.signal;
4525
- try {
4526
- return await fetch(url, options);
4527
- } finally {
4528
- clearTimeout(timeout);
4529
- }
4530
- }
4531
- async get(path) {
4532
- return this.doRequest("GET", path);
4533
- }
4534
- async post(path, body) {
4535
- return this.doRequest("POST", path, body);
4536
- }
4537
- async put(path, body) {
4538
- return this.doRequest("PUT", path, body);
4539
- }
4540
- async delete(path) {
4541
- return this.doRequest("DELETE", path);
4542
- }
4543
- async refreshToken() {
4544
- if (!this.config.refresh_token) {
4545
- throw new Error("no refresh token available");
4546
- }
4547
- const supabaseURL = getSupabaseURL();
4548
- const endpoint = `${supabaseURL}/auth/v1/token?grant_type=refresh_token`;
4549
- const resp = await fetch(endpoint, {
4550
- method: "POST",
4551
- headers: {
4552
- "Content-Type": "application/json",
4553
- apikey: getSupabaseAnonKey()
4554
- },
4555
- body: JSON.stringify({ refresh_token: this.config.refresh_token })
4556
- });
4557
- if (resp.status !== 200) {
4558
- throw new Error(`refresh failed with status ${resp.status}`);
4559
- }
4560
- const data = await resp.json();
4561
- this.config.access_token = data.access_token;
4562
- this.config.refresh_token = data.refresh_token;
4563
- this.config.expires_at = Math.floor(Date.now() / 1000) + data.expires_in;
4564
- saveConfig(this.config);
4565
- }
4566
- async getDeployments() {
4567
- const resp = await this.get("/v1/mcp/deployments");
4568
- if (resp.status !== 200) {
4569
- let detail = await readErrorBody(resp);
4570
- if (!detail || detail === "Internal Server Error") {
4571
- detail = "check backend logs for details";
4572
- }
4573
- throw new Error(`failed to fetch deployments (status ${resp.status}): ${detail}`);
4574
- }
4575
- return resp.json();
4576
- }
4577
- async getOrgs() {
4578
- const resp = await this.get("/v1/mcp/orgs");
4579
- if (resp.status !== 200) {
4580
- const detail = await readErrorBody(resp);
4581
- throw new Error(`failed to fetch orgs (status ${resp.status}): ${detail}`);
4582
- }
4583
- return resp.json();
4584
- }
4585
- async validateAPIKey(apiKey, deploymentID) {
4586
- try {
4587
- const endpoint = `${this.baseURL}/v1/mcp/deployments/${encodeURIComponent(deploymentID)}`;
4588
- const controller = new AbortController;
4589
- const timeout = setTimeout(() => controller.abort(), 5000);
4590
- try {
4591
- const resp = await fetch(endpoint, {
4592
- method: "GET",
4593
- headers: { "X-Dosu-API-Key": apiKey },
4594
- signal: controller.signal
4595
- });
4596
- return resp.status !== 401 && resp.status !== 403;
4597
- } finally {
4598
- clearTimeout(timeout);
4599
- }
4600
- } catch {
4601
- return true;
4602
- }
4603
- }
4604
- async createAPIKey(deploymentID, name) {
4605
- const path = `/v1/mcp/deployments/${deploymentID}/api-keys`;
4606
- const resp = await this.post(path, { name });
4607
- if (resp.status !== 200 && resp.status !== 201) {
4608
- const detail = await readErrorBody(resp);
4609
- throw new Error(`failed to create API key (status ${resp.status}): ${detail}`);
4610
- }
4611
- return resp.json();
4612
- }
4613
- }
4614
- async function readErrorBody(resp) {
4615
- try {
4616
- const text = await resp.text();
4617
- return text.slice(0, 1024);
4618
- } catch {
4619
- return "";
4620
- }
4621
- }
4622
- var SessionExpiredError;
4623
- var init_client = __esm(() => {
4624
- init_config();
4625
- SessionExpiredError = class SessionExpiredError extends Error {
4626
- constructor() {
4627
- super("session expired");
4628
- this.name = "SessionExpiredError";
4629
- }
4630
- };
4631
- });
4632
-
4633
4661
  // src/client/trpc.ts
4634
4662
  var exports_trpc = {};
4635
4663
  __export(exports_trpc, {
@@ -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.post(TICKETS_PATH, req);
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.get(`${TICKETS_PATH}/${encodeURIComponent(ticketId)}`);
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 events = [...DEFAULT_HOOK_EVENTS, ...opts.withStop ? ["Stop"] : []];
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",
@@ -12239,6 +12268,7 @@ var init_flow3 = __esm(() => {
12239
12268
 
12240
12269
  // src/cli/cli.ts
12241
12270
  init_esm();
12271
+ init_client();
12242
12272
  import { readFileSync as readFileSync11, unlinkSync as unlinkSync2 } from "node:fs";
12243
12273
 
12244
12274
  // src/commands/analytics.ts
@@ -12998,7 +13028,7 @@ async function runUserPromptSubmit(input, now = Date.now()) {
12998
13028
  return;
12999
13029
  }
13000
13030
  const cfg = loadConfig();
13001
- if (!isAuthenticated(cfg) || !cfg.deployment_id) {
13031
+ if (!cfg.api_key || !cfg.deployment_id) {
13002
13032
  logger.debug("hooks", "submit skipped reason=not-configured");
13003
13033
  return;
13004
13034
  }
@@ -13052,7 +13082,7 @@ async function runPostToolUse(input, now = Date.now()) {
13052
13082
  return;
13053
13083
  }
13054
13084
  const cfg = loadConfig();
13055
- if (!isAuthenticated(cfg) || !cfg.deployment_id) {
13085
+ if (!cfg.api_key || !cfg.deployment_id) {
13056
13086
  saveState({ ...state, lastCheckedAt: now });
13057
13087
  return;
13058
13088
  }
@@ -13109,7 +13139,7 @@ async function runStop(input, now = Date.now()) {
13109
13139
  return printContinue();
13110
13140
  }
13111
13141
  const cfg = loadConfig();
13112
- if (!isAuthenticated(cfg) || !cfg.deployment_id)
13142
+ if (!cfg.api_key || !cfg.deployment_id)
13113
13143
  return printContinue();
13114
13144
  const tc = await Promise.resolve().then(() => (init_ticket_client(), exports_ticket_client));
13115
13145
  let resp;
@@ -13209,7 +13239,7 @@ async function runInstall(agent, opts) {
13209
13239
  const { installClaudeHooks: installClaudeHooks2, claudeLocalSettingsPath: claudeLocalSettingsPath2 } = await Promise.resolve().then(() => (init_claude_code(), exports_claude_code));
13210
13240
  const configPath = claudeLocalSettingsPath2(resolveDir(opts.dir));
13211
13241
  try {
13212
- const { events } = installClaudeHooks2(configPath, { withStop: opts.withStop });
13242
+ const { events } = installClaudeHooks2(configPath, { stop: opts.stop });
13213
13243
  if (opts.json) {
13214
13244
  const { emitStep: emitStep2 } = await Promise.resolve().then(() => exports_output);
13215
13245
  emitStep2({ step: "hooks-install", path: configPath, events });
@@ -13299,7 +13329,7 @@ async function collectDoctorChecks(opts) {
13299
13329
  detail: "no deployment selected (run 'dosu setup')"
13300
13330
  });
13301
13331
  }
13302
- if (cfg.api_key && cfg.deployment_id && isAuthenticated(cfg)) {
13332
+ if (cfg.api_key && cfg.deployment_id) {
13303
13333
  const { Client: Client2 } = await Promise.resolve().then(() => (init_client(), exports_client));
13304
13334
  const ok = await new Client2(cfg).validateAPIKey(cfg.api_key, cfg.deployment_id);
13305
13335
  checks.push({
@@ -13311,7 +13341,7 @@ async function collectDoctorChecks(opts) {
13311
13341
  checks.push({
13312
13342
  name: "backend",
13313
13343
  status: "warn",
13314
- detail: "skipped (not authenticated / no deployment)"
13344
+ detail: "skipped (no API key / no deployment)"
13315
13345
  });
13316
13346
  }
13317
13347
  return checks;
@@ -13341,7 +13371,7 @@ function hooksCommand() {
13341
13371
  cmd.command("post-tool-use").description("Hook entrypoint: poll and inject ready knowledge once").action(async () => {
13342
13372
  await runHookEntrypoint("post-tool-use", readStdinRaw());
13343
13373
  });
13344
- cmd.command("stop").description("Hook entrypoint (opt-in): last-chance non-blocking delivery").action(async () => {
13374
+ cmd.command("stop").description("Hook entrypoint: last-chance knowledge delivery when the agent stops").action(async () => {
13345
13375
  await runHookEntrypoint("stop", readStdinRaw());
13346
13376
  });
13347
13377
  cmd.command("status").description("Show the active Dosu knowledge ticket for this session").option("--json", "Output JSON", false).action((opts) => {
@@ -13354,13 +13384,13 @@ function hooksCommand() {
13354
13384
  }
13355
13385
  runStatus(input, opts);
13356
13386
  });
13357
- 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("--with-stop", "Also install the optional Stop hook", false).option("--json", "Emit machine-readable JSON", false).addHelpText("after", [
13387
+ 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", [
13358
13388
  "",
13359
13389
  `Supported agents: ${SUPPORTED_AGENTS.join(", ")}`,
13360
13390
  "",
13361
13391
  "Examples:",
13362
13392
  " $ dosu hooks install claude-code",
13363
- " $ dosu hooks install claude-code --with-stop",
13393
+ " $ dosu hooks install claude-code --no-stop",
13364
13394
  " $ dosu hooks install claude-code --dir ./my-project"
13365
13395
  ].join(`
13366
13396
  `)).action((agent, opts) => runInstall(agent, opts));
@@ -14341,10 +14371,17 @@ function createProgram() {
14341
14371
  return;
14342
14372
  }
14343
14373
  const cfg = loadConfig();
14344
- if (isAuthenticated(cfg) && !isTokenExpired(cfg)) {
14345
- console.log("You are already logged in.");
14346
- console.log("Run 'dosu logout' first to re-authenticate.");
14347
- return;
14374
+ if (isAuthenticated(cfg)) {
14375
+ if (!isTokenExpired(cfg)) {
14376
+ console.log("You are already logged in.");
14377
+ console.log("Run 'dosu logout' first to re-authenticate.");
14378
+ return;
14379
+ }
14380
+ if (await ensureFreshSession(cfg)) {
14381
+ console.log("Session refreshed.");
14382
+ console.log(`Credentials saved to ${getConfigPath()}`);
14383
+ return;
14384
+ }
14348
14385
  }
14349
14386
  console.log("Opening browser for authentication...");
14350
14387
  const { startOAuthFlow: startOAuthFlow2 } = await Promise.resolve().then(() => (init_flow(), exports_flow));
@@ -14384,14 +14421,14 @@ function createProgram() {
14384
14421
  saveConfig(cfg);
14385
14422
  console.log("Successfully logged out.");
14386
14423
  });
14387
- program2.command("status").description("Show current authentication and MCP status").action(() => {
14424
+ program2.command("status").description("Show current authentication and MCP status").action(async () => {
14388
14425
  const cfg = loadConfig();
14389
14426
  if (!isAuthenticated(cfg)) {
14390
14427
  console.log("Status: Not logged in");
14391
14428
  console.log("Run 'dosu login' to authenticate.");
14392
14429
  return;
14393
14430
  }
14394
- if (isTokenExpired(cfg)) {
14431
+ if (isTokenExpired(cfg) && !await ensureFreshSession(cfg)) {
14395
14432
  console.log("Status: Token expired");
14396
14433
  console.log("Run 'dosu login' to re-authenticate.");
14397
14434
  } else {
@@ -14409,7 +14446,7 @@ function createProgram() {
14409
14446
  }
14410
14447
  });
14411
14448
  const mcp = program2.command("mcp").description("Manage MCP server integrations");
14412
- mcp.command("add <agent>").description("Add Dosu MCP to an AI tool").option("-g, --global", "Add globally (all projects) instead of project-local", false).option("--show-secret", "Print full manual configuration secrets", false).action((toolId, opts) => {
14449
+ mcp.command("add <agent>").description("Add Dosu MCP to an AI tool").option("-g, --global", "Add globally (all projects) instead of project-local", false).option("--show-secret", "Print full manual configuration secrets", false).action(async (toolId, opts) => {
14413
14450
  let provider;
14414
14451
  try {
14415
14452
  provider = getProvider(toolId.toLowerCase());
@@ -14420,7 +14457,7 @@ function createProgram() {
14420
14457
  if (!isAuthenticated(cfg)) {
14421
14458
  throw new Error("not logged in. Run 'dosu login' first");
14422
14459
  }
14423
- if (isTokenExpired(cfg)) {
14460
+ if (isTokenExpired(cfg) && !await ensureFreshSession(cfg)) {
14424
14461
  throw new Error("session expired. Run 'dosu login' to re-authenticate");
14425
14462
  }
14426
14463
  if (cfg.mode !== MODE_OSS && !cfg.deployment_id) {
@@ -14545,6 +14582,18 @@ Use 'dosu mcp add <agent>' to add Dosu MCP to a tool.`);
14545
14582
  });
14546
14583
  return program2;
14547
14584
  }
14585
+ async function ensureFreshSession(cfg) {
14586
+ if (!isTokenExpired(cfg))
14587
+ return true;
14588
+ try {
14589
+ logger.debug("cli", "token expired, attempting refresh");
14590
+ await new Client(cfg).refreshToken();
14591
+ return true;
14592
+ } catch (err) {
14593
+ logger.debug("cli", `token refresh failed: ${err instanceof Error ? err.message : err}`);
14594
+ return false;
14595
+ }
14596
+ }
14548
14597
  async function execute() {
14549
14598
  const program2 = createProgram();
14550
14599
  await program2.parseAsync(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dosu/cli",
3
- "version": "0.16.0",
3
+ "version": "0.16.2",
4
4
  "type": "module",
5
5
  "description": "Dosu CLI - Manage MCP servers for AI tools",
6
6
  "license": "MIT",