@dosu/cli 0.66.1 → 0.67.1

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 +1 -1
  2. package/bin/dosu.js +403 -206
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -119,7 +119,7 @@ Once authenticated against a deployment, you can drive the Dosu platform without
119
119
  | `dosu knowledge` | Search and browse your knowledge base |
120
120
  | `dosu docs` | Manage documents (list, create, update, import, publish) |
121
121
  | `dosu threads` | List and manage conversation threads |
122
- | `dosu review` | Document review workflow, plus `review notifications` to route a Library's or Agent's reviews to a Slack channel |
122
+ | `dosu review` | Document review workflow for the selected Library, plus `review notifications` to route a Library's or Agent's reviews to a Slack channel |
123
123
  | `dosu sources` | Manage connected data sources (list, connect, create, sync, update) |
124
124
  | `dosu integrations` | List and inspect platform integrations (Slack, GitHub, …) |
125
125
  | `dosu topics` | List knowledge base topics and their pages |
package/bin/dosu.js CHANGED
@@ -47,6 +47,111 @@ var __esm = (fn, res, err) => () => {
47
47
  };
48
48
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
49
49
 
50
+ // node_modules/picocolors/picocolors.js
51
+ var require_picocolors = __commonJS(function(exports, module) {
52
+ var p = process || {};
53
+ var argv = p.argv || [];
54
+ var env = p.env || {};
55
+ var isColorSupported = !(!!env.NO_COLOR || argv.includes("--no-color")) && (!!env.FORCE_COLOR || argv.includes("--color") || p.platform === "win32" || (p.stdout || {}).isTTY && env.TERM !== "dumb" || !!env.CI);
56
+ var formatter = (open, close, replace = open) => (input) => {
57
+ let string = "" + input, index = string.indexOf(close, open.length);
58
+ return ~index ? open + replaceClose(string, close, replace, index) + close : open + string + close;
59
+ };
60
+ var replaceClose = (string, close, replace, index) => {
61
+ let result = "", cursor = 0;
62
+ do {
63
+ result += string.substring(cursor, index) + replace;
64
+ cursor = index + close.length;
65
+ index = string.indexOf(close, cursor);
66
+ } while (~index);
67
+ return result + string.substring(cursor);
68
+ };
69
+ var createColors = (enabled = isColorSupported) => {
70
+ let f = enabled ? formatter : () => String;
71
+ return {
72
+ isColorSupported: enabled,
73
+ reset: f("\x1B[0m", "\x1B[0m"),
74
+ bold: f("\x1B[1m", "\x1B[22m", "\x1B[22m\x1B[1m"),
75
+ dim: f("\x1B[2m", "\x1B[22m", "\x1B[22m\x1B[2m"),
76
+ italic: f("\x1B[3m", "\x1B[23m"),
77
+ underline: f("\x1B[4m", "\x1B[24m"),
78
+ inverse: f("\x1B[7m", "\x1B[27m"),
79
+ hidden: f("\x1B[8m", "\x1B[28m"),
80
+ strikethrough: f("\x1B[9m", "\x1B[29m"),
81
+ black: f("\x1B[30m", "\x1B[39m"),
82
+ red: f("\x1B[31m", "\x1B[39m"),
83
+ green: f("\x1B[32m", "\x1B[39m"),
84
+ yellow: f("\x1B[33m", "\x1B[39m"),
85
+ blue: f("\x1B[34m", "\x1B[39m"),
86
+ magenta: f("\x1B[35m", "\x1B[39m"),
87
+ cyan: f("\x1B[36m", "\x1B[39m"),
88
+ white: f("\x1B[37m", "\x1B[39m"),
89
+ gray: f("\x1B[90m", "\x1B[39m"),
90
+ bgBlack: f("\x1B[40m", "\x1B[49m"),
91
+ bgRed: f("\x1B[41m", "\x1B[49m"),
92
+ bgGreen: f("\x1B[42m", "\x1B[49m"),
93
+ bgYellow: f("\x1B[43m", "\x1B[49m"),
94
+ bgBlue: f("\x1B[44m", "\x1B[49m"),
95
+ bgMagenta: f("\x1B[45m", "\x1B[49m"),
96
+ bgCyan: f("\x1B[46m", "\x1B[49m"),
97
+ bgWhite: f("\x1B[47m", "\x1B[49m"),
98
+ blackBright: f("\x1B[90m", "\x1B[39m"),
99
+ redBright: f("\x1B[91m", "\x1B[39m"),
100
+ greenBright: f("\x1B[92m", "\x1B[39m"),
101
+ yellowBright: f("\x1B[93m", "\x1B[39m"),
102
+ blueBright: f("\x1B[94m", "\x1B[39m"),
103
+ magentaBright: f("\x1B[95m", "\x1B[39m"),
104
+ cyanBright: f("\x1B[96m", "\x1B[39m"),
105
+ whiteBright: f("\x1B[97m", "\x1B[39m"),
106
+ bgBlackBright: f("\x1B[100m", "\x1B[49m"),
107
+ bgRedBright: f("\x1B[101m", "\x1B[49m"),
108
+ bgGreenBright: f("\x1B[102m", "\x1B[49m"),
109
+ bgYellowBright: f("\x1B[103m", "\x1B[49m"),
110
+ bgBlueBright: f("\x1B[104m", "\x1B[49m"),
111
+ bgMagentaBright: f("\x1B[105m", "\x1B[49m"),
112
+ bgCyanBright: f("\x1B[106m", "\x1B[49m"),
113
+ bgWhiteBright: f("\x1B[107m", "\x1B[49m")
114
+ };
115
+ };
116
+ module.exports = createColors();
117
+ module.exports.createColors = createColors;
118
+ });
119
+
120
+ // src/cli/command-error.ts
121
+ function isCommandError(error) {
122
+ return error instanceof CommandError;
123
+ }
124
+ var COMMAND_ERROR_CODES, CommandError;
125
+ var init_command_error = __esm(() => {
126
+ COMMAND_ERROR_CODES = [
127
+ "NOT_LOGGED_IN",
128
+ "NO_ORG_SELECTED",
129
+ "NO_API_KEY",
130
+ "NO_LIBRARY_SELECTED",
131
+ "NO_DEPLOYMENT_SELECTED",
132
+ "ORG_UNAVAILABLE",
133
+ "LIBRARY_UNAVAILABLE",
134
+ "DEPLOYMENT_UNAVAILABLE",
135
+ "SCOPE_MISMATCH",
136
+ "DEPLOYMENT_NOT_FOUND",
137
+ "DEPLOYMENT_AMBIGUOUS",
138
+ "NOT_MCP_DEPLOYMENT",
139
+ "REVIEW_ITEM_NOT_FOUND",
140
+ "INVALID_ARGUMENT"
141
+ ];
142
+ CommandError = class CommandError extends Error {
143
+ code;
144
+ details;
145
+ exitCode = 1;
146
+ constructor(code, message, details = []) {
147
+ super(message);
148
+ this.code = code;
149
+ this.details = details;
150
+ this.name = "CommandError";
151
+ }
152
+ };
153
+ });
154
+
50
155
  // src/statusline/argv.ts
51
156
  function agentFromArgv(argv) {
52
157
  for (let i = 0;i < argv.length; i++) {
@@ -1006,76 +1111,6 @@ var init_config_helpers = __esm(() => {
1006
1111
  writeFileAtomic = import_write_file_atomic.default;
1007
1112
  });
1008
1113
 
1009
- // node_modules/picocolors/picocolors.js
1010
- var require_picocolors = __commonJS(function(exports, module) {
1011
- var p = process || {};
1012
- var argv = p.argv || [];
1013
- var env = p.env || {};
1014
- var isColorSupported = !(!!env.NO_COLOR || argv.includes("--no-color")) && (!!env.FORCE_COLOR || argv.includes("--color") || p.platform === "win32" || (p.stdout || {}).isTTY && env.TERM !== "dumb" || !!env.CI);
1015
- var formatter = (open, close, replace = open) => (input) => {
1016
- let string = "" + input, index = string.indexOf(close, open.length);
1017
- return ~index ? open + replaceClose(string, close, replace, index) + close : open + string + close;
1018
- };
1019
- var replaceClose = (string, close, replace, index) => {
1020
- let result = "", cursor = 0;
1021
- do {
1022
- result += string.substring(cursor, index) + replace;
1023
- cursor = index + close.length;
1024
- index = string.indexOf(close, cursor);
1025
- } while (~index);
1026
- return result + string.substring(cursor);
1027
- };
1028
- var createColors = (enabled = isColorSupported) => {
1029
- let f = enabled ? formatter : () => String;
1030
- return {
1031
- isColorSupported: enabled,
1032
- reset: f("\x1B[0m", "\x1B[0m"),
1033
- bold: f("\x1B[1m", "\x1B[22m", "\x1B[22m\x1B[1m"),
1034
- dim: f("\x1B[2m", "\x1B[22m", "\x1B[22m\x1B[2m"),
1035
- italic: f("\x1B[3m", "\x1B[23m"),
1036
- underline: f("\x1B[4m", "\x1B[24m"),
1037
- inverse: f("\x1B[7m", "\x1B[27m"),
1038
- hidden: f("\x1B[8m", "\x1B[28m"),
1039
- strikethrough: f("\x1B[9m", "\x1B[29m"),
1040
- black: f("\x1B[30m", "\x1B[39m"),
1041
- red: f("\x1B[31m", "\x1B[39m"),
1042
- green: f("\x1B[32m", "\x1B[39m"),
1043
- yellow: f("\x1B[33m", "\x1B[39m"),
1044
- blue: f("\x1B[34m", "\x1B[39m"),
1045
- magenta: f("\x1B[35m", "\x1B[39m"),
1046
- cyan: f("\x1B[36m", "\x1B[39m"),
1047
- white: f("\x1B[37m", "\x1B[39m"),
1048
- gray: f("\x1B[90m", "\x1B[39m"),
1049
- bgBlack: f("\x1B[40m", "\x1B[49m"),
1050
- bgRed: f("\x1B[41m", "\x1B[49m"),
1051
- bgGreen: f("\x1B[42m", "\x1B[49m"),
1052
- bgYellow: f("\x1B[43m", "\x1B[49m"),
1053
- bgBlue: f("\x1B[44m", "\x1B[49m"),
1054
- bgMagenta: f("\x1B[45m", "\x1B[49m"),
1055
- bgCyan: f("\x1B[46m", "\x1B[49m"),
1056
- bgWhite: f("\x1B[47m", "\x1B[49m"),
1057
- blackBright: f("\x1B[90m", "\x1B[39m"),
1058
- redBright: f("\x1B[91m", "\x1B[39m"),
1059
- greenBright: f("\x1B[92m", "\x1B[39m"),
1060
- yellowBright: f("\x1B[93m", "\x1B[39m"),
1061
- blueBright: f("\x1B[94m", "\x1B[39m"),
1062
- magentaBright: f("\x1B[95m", "\x1B[39m"),
1063
- cyanBright: f("\x1B[96m", "\x1B[39m"),
1064
- whiteBright: f("\x1B[97m", "\x1B[39m"),
1065
- bgBlackBright: f("\x1B[100m", "\x1B[49m"),
1066
- bgRedBright: f("\x1B[101m", "\x1B[49m"),
1067
- bgGreenBright: f("\x1B[102m", "\x1B[49m"),
1068
- bgYellowBright: f("\x1B[103m", "\x1B[49m"),
1069
- bgBlueBright: f("\x1B[104m", "\x1B[49m"),
1070
- bgMagentaBright: f("\x1B[105m", "\x1B[49m"),
1071
- bgCyanBright: f("\x1B[106m", "\x1B[49m"),
1072
- bgWhiteBright: f("\x1B[107m", "\x1B[49m")
1073
- };
1074
- };
1075
- module.exports = createColors();
1076
- module.exports.createColors = createColors;
1077
- });
1078
-
1079
1114
  // src/version/version.ts
1080
1115
  function isNpxInvocation(channel = INSTALL_CHANNEL, env = process.env) {
1081
1116
  return channel === "npm" && (env.npm_lifecycle_event === "npx" || env.npm_command === "exec");
@@ -1083,7 +1118,7 @@ function isNpxInvocation(channel = INSTALL_CHANNEL, env = process.env) {
1083
1118
  function getVersionString() {
1084
1119
  return `v${VERSION}`;
1085
1120
  }
1086
- var VERSION = "0.66.1", INSTALL_CHANNEL = "npm";
1121
+ var VERSION = "0.67.1", INSTALL_CHANNEL = "npm";
1087
1122
 
1088
1123
  // src/debug/logger.ts
1089
1124
  import {
@@ -1173,18 +1208,18 @@ function writeEntry(level, mod, message) {
1173
1208
  } catch {}
1174
1209
  if (debugToConsole) {
1175
1210
  const colorize = {
1176
- DEBUG: import_picocolors.default.dim,
1177
- INFO: import_picocolors.default.cyan,
1178
- WARN: import_picocolors.default.yellow,
1179
- ERROR: import_picocolors.default.red
1211
+ DEBUG: import_picocolors2.default.dim,
1212
+ INFO: import_picocolors2.default.cyan,
1213
+ WARN: import_picocolors2.default.yellow,
1214
+ ERROR: import_picocolors2.default.red
1180
1215
  }[level];
1181
1216
  console.error(colorize(line.trimEnd()));
1182
1217
  }
1183
1218
  }
1184
- var import_picocolors, MAX_LOG_SIZE = 1048576, TRUNCATE_KEEP = 524288, LOG_FILENAME = "debug.log", REDACTED = "[REDACTED]", SECRET_QUERY_KEYS, initialized = false, debugToConsole = false, logFilePath = null, ANSI_PATTERN, logger;
1219
+ var import_picocolors2, MAX_LOG_SIZE = 1048576, TRUNCATE_KEEP = 524288, LOG_FILENAME = "debug.log", REDACTED = "[REDACTED]", SECRET_QUERY_KEYS, initialized = false, debugToConsole = false, logFilePath = null, ANSI_PATTERN, logger;
1185
1220
  var init_logger = __esm(() => {
1186
1221
  init_config();
1187
- import_picocolors = __toESM(require_picocolors(), 1);
1222
+ import_picocolors2 = __toESM(require_picocolors(), 1);
1188
1223
  SECRET_QUERY_KEYS = [
1189
1224
  "access_token",
1190
1225
  "refresh_token",
@@ -7187,8 +7222,7 @@ var init_arguments = __esm(() => {
7187
7222
  function requireLoginConfig() {
7188
7223
  const cfg = loadConfig();
7189
7224
  if (!cfg.active_account?.session.access_token) {
7190
- console.error(import_picocolors2.default.red("Not logged in. Run 'dosu login' first."));
7191
- process.exit(1);
7225
+ throw new CommandError("NOT_LOGGED_IN", "Not logged in. Run 'dosu login' first.");
7192
7226
  }
7193
7227
  return cfg;
7194
7228
  }
@@ -7196,22 +7230,20 @@ function requireOrgConfig() {
7196
7230
  const cfg = requireLoginConfig();
7197
7231
  const orgId = cfg.active_account?.target?.org_id;
7198
7232
  if (!orgId) {
7199
- console.error(import_picocolors2.default.red("Missing org config. Run 'dosu setup' to reconfigure."));
7200
- process.exit(1);
7233
+ throw new CommandError("NO_ORG_SELECTED", "Missing org config. Run 'dosu setup' to reconfigure.");
7201
7234
  }
7202
7235
  return { cfg, orgId };
7203
7236
  }
7204
7237
  function requireAPIKey(cfg) {
7205
- if (!cfg.active_account?.target?.api_key) {
7206
- console.error(import_picocolors2.default.red("API key not configured. Run 'dosu setup' first."));
7207
- process.exit(1);
7238
+ const apiKey = cfg.active_account?.target?.api_key;
7239
+ if (!apiKey) {
7240
+ throw new CommandError("NO_API_KEY", "API key not configured. Run 'dosu setup' first.");
7208
7241
  }
7209
- return cfg.active_account?.target?.api_key;
7242
+ return apiKey;
7210
7243
  }
7211
- var import_picocolors2;
7212
7244
  var init_auth = __esm(() => {
7245
+ init_command_error();
7213
7246
  init_config();
7214
- import_picocolors2 = __toESM(require_picocolors(), 1);
7215
7247
  });
7216
7248
 
7217
7249
  // node_modules/fast-string-truncated-width/dist/utils.js
@@ -11430,8 +11462,16 @@ function requireConfig4() {
11430
11462
  return requireLoginConfig();
11431
11463
  }
11432
11464
  async function listAccessibleDeployments(client, activeOrgId) {
11433
- if (activeOrgId)
11434
- return client.workspaces.listForOrg.query(activeOrgId);
11465
+ if (activeOrgId) {
11466
+ const deployments = await client.workspaces.listForOrg.query(activeOrgId);
11467
+ if (deployments.length > 0)
11468
+ return deployments;
11469
+ const orgs = await client.organization.getOrganizations.query({});
11470
+ if (!orgs.some((org) => org.org_id === activeOrgId)) {
11471
+ throw new CommandError("ORG_UNAVAILABLE", "The saved organization is not available to this account.", ["Run 'dosu setup' to choose an organization and MCP deployment."]);
11472
+ }
11473
+ return deployments;
11474
+ }
11435
11475
  const orgs = await client.organization.getOrganizations.query({});
11436
11476
  const deployments = await Promise.all(orgs.map((org) => client.workspaces.listForOrg.query(org.org_id)));
11437
11477
  return deployments.flat();
@@ -11445,15 +11485,11 @@ async function resolveDeploymentId(client, activeOrgId, id) {
11445
11485
  if (matches.length === 1)
11446
11486
  return matches[0].deployment_id;
11447
11487
  if (matches.length === 0) {
11448
- console.error(import_picocolors15.default.red(`Deployment not found: ${id}`));
11449
- console.error(import_picocolors15.default.dim("Expected a full deployment ID (UUID). Run 'dosu deployments list --json' to see full IDs."));
11450
- process.exit(1);
11451
- }
11452
- console.error(import_picocolors15.default.red(`Ambiguous deployment ID prefix: ${id} matches ${matches.length}:`));
11453
- for (const d of matches) {
11454
- console.error(import_picocolors15.default.dim(` ${d.deployment_id} ${d.name}`));
11488
+ throw new CommandError("DEPLOYMENT_NOT_FOUND", `Deployment not found: ${id}`, [
11489
+ "Expected a full deployment ID (UUID). Run 'dosu deployments list --json' to see full IDs."
11490
+ ]);
11455
11491
  }
11456
- process.exit(1);
11492
+ throw new CommandError("DEPLOYMENT_AMBIGUOUS", `Ambiguous deployment ID prefix: ${id} matches ${matches.length}:`, matches.map((d) => ` ${d.deployment_id} ${d.name}`));
11457
11493
  }
11458
11494
  function deploymentsCommand() {
11459
11495
  const cmd = new Command("deployments").description("Select the MCP deployment (and its Library) this CLI uses; not for Agents");
@@ -11484,14 +11520,12 @@ ${import_picocolors15.default.dim(`Current: ${cfg.active_account?.target?.deploy
11484
11520
  cmd.command("info").description("Show the selected MCP deployment").option("--json", "Output as JSON").action(async (opts) => {
11485
11521
  const cfg = requireConfig4();
11486
11522
  if (!cfg.active_account?.target?.deployment_id) {
11487
- console.error(import_picocolors15.default.red("No deployment selected. Run 'dosu setup' or 'dosu deployments switch'."));
11488
- process.exit(1);
11523
+ throw new CommandError("NO_DEPLOYMENT_SELECTED", "No deployment selected. Run 'dosu setup' or 'dosu deployments switch'.");
11489
11524
  }
11490
11525
  const client = createTypedClient(cfg);
11491
11526
  const deployment = await client.workspaces.get.query(cfg.active_account?.target?.deployment_id);
11492
11527
  if (!deployment) {
11493
- console.error(import_picocolors15.default.red(`Deployment not found: ${cfg.active_account?.target?.deployment_id}`));
11494
- process.exit(1);
11528
+ throw new CommandError("DEPLOYMENT_UNAVAILABLE", `Deployment not found: ${cfg.active_account?.target?.deployment_id}`);
11495
11529
  }
11496
11530
  if (opts.json) {
11497
11531
  printResult(deployment, opts);
@@ -11514,12 +11548,10 @@ ${import_picocolors15.default.dim(`Current: ${cfg.active_account?.target?.deploy
11514
11548
  const id = await resolveDeploymentId(client, cfg.active_account?.target?.org_id, rawId);
11515
11549
  const deployment = await client.workspaces.get.query(id);
11516
11550
  if (!deployment) {
11517
- console.error(import_picocolors15.default.red(`Deployment not found: ${id}`));
11518
- process.exit(1);
11551
+ throw new CommandError("DEPLOYMENT_NOT_FOUND", `Deployment not found: ${id}`);
11519
11552
  }
11520
11553
  if (deployment.provider_slug !== MCP_PROVIDER_SLUG) {
11521
- console.error(import_picocolors15.default.red(`Not a Dosu MCP deployment: ${id}`));
11522
- process.exit(1);
11554
+ throw new CommandError("NOT_MCP_DEPLOYMENT", `Not a Dosu MCP deployment: ${id}`);
11523
11555
  }
11524
11556
  const apiKey = await new Client(cfg).createAPIKey(deployment.deployment_id, "dosu-cli");
11525
11557
  updateTarget(cfg, {
@@ -11535,12 +11567,14 @@ ${import_picocolors15.default.dim(`Current: ${cfg.active_account?.target?.deploy
11535
11567
  return;
11536
11568
  }
11537
11569
  console.log(import_picocolors15.default.green(`Switched to deployment: ${deployment.name}`));
11570
+ console.log(import_picocolors15.default.dim("This changes the CLI's saved selection. An AI tool with a running Dosu MCP connection " + "keeps its previous deployment until you run 'dosu mcp refresh' and restart or " + "reconnect it."));
11538
11571
  });
11539
11572
  return cmd;
11540
11573
  }
11541
11574
  var import_picocolors15, MCP_PROVIDER_SLUG = "dosu_mcp";
11542
11575
  var init_deployments = __esm(() => {
11543
11576
  init_commander();
11577
+ init_command_error();
11544
11578
  init_client();
11545
11579
  init_trpc();
11546
11580
  init_config();
@@ -14289,7 +14323,7 @@ var init_conflicts = __esm(() => {
14289
14323
  });
14290
14324
 
14291
14325
  // src/telemetry/debug-id.ts
14292
- var BUNDLE_DEBUG_ID_PLACEHOLDER = "0891ed1f-f060-16b6-6475-6e2164756e21";
14326
+ var BUNDLE_DEBUG_ID_PLACEHOLDER = "152ccbaa-13cc-83fc-6475-6e2164756e21";
14293
14327
 
14294
14328
  // src/telemetry/telemetry.ts
14295
14329
  import { randomUUID as randomUUID2 } from "node:crypto";
@@ -15088,10 +15122,11 @@ function createCommandTelemetry(settings, dependencies = {}) {
15088
15122
  };
15089
15123
  }
15090
15124
  function shouldSendToSentry(error) {
15091
- return !["SESSION_EXPIRED", "SESSION_PERSISTENCE_ERROR"].includes(error.code ?? "");
15125
+ return !EXPECTED_STATE_ERROR_CODES.has(error.code ?? "");
15092
15126
  }
15093
- var REQUEST_TIMEOUT_MS = 500, MAX_RESPONSE_BYTES, MAX_STACK_FRAMES = 20, UNKNOWN_COMMAND = "unknown", FALLBACK_INSTALL_ID = "00000000-0000-4000-8000-000000000000", BUNDLE_CODE_FILE = "app:///bin/dosu.js", CLI_PACKAGE_ROOT, SAFE_ERROR_TYPES, SAFE_ERROR_CODES, SYNC_TRIGGERS, SYNC_STATUSES, LEARNER_OUTCOMES, GATEWAY_REASONS, CLAUDE_CODE_SOURCES, SETTINGS_CONFLICT_KEYS, SETTINGS_CONFLICT_FAMILIES, MAX_CONFLICT_KEYS_LENGTH = 200, CLAUDE_CODE_VERSION_PATTERN, LEARNER_MODEL_PATTERN, BACKFILL_OFFERS, SENTRY_FACET_TAGS, pendingFacets;
15127
+ var REQUEST_TIMEOUT_MS = 500, MAX_RESPONSE_BYTES, MAX_STACK_FRAMES = 20, UNKNOWN_COMMAND = "unknown", FALLBACK_INSTALL_ID = "00000000-0000-4000-8000-000000000000", BUNDLE_CODE_FILE = "app:///bin/dosu.js", CLI_PACKAGE_ROOT, SAFE_ERROR_TYPES, SAFE_ERROR_CODES, EXPECTED_STATE_ERROR_CODES, SYNC_TRIGGERS, SYNC_STATUSES, LEARNER_OUTCOMES, GATEWAY_REASONS, CLAUDE_CODE_SOURCES, SETTINGS_CONFLICT_KEYS, SETTINGS_CONFLICT_FAMILIES, MAX_CONFLICT_KEYS_LENGTH = 200, CLAUDE_CODE_VERSION_PATTERN, LEARNER_MODEL_PATTERN, BACKFILL_OFFERS, SENTRY_FACET_TAGS, pendingFacets;
15094
15128
  var init_telemetry = __esm(() => {
15129
+ init_command_error();
15095
15130
  init_conflicts();
15096
15131
  MAX_RESPONSE_BYTES = 1024 * 1024;
15097
15132
  CLI_PACKAGE_ROOT = detectCliPackageRoot();
@@ -15172,7 +15207,13 @@ var init_telemetry = __esm(() => {
15172
15207
  "commander.missingMandatoryOptionValue",
15173
15208
  "commander.optionMissingArgument",
15174
15209
  "commander.unknownCommand",
15175
- "commander.unknownOption"
15210
+ "commander.unknownOption",
15211
+ ...COMMAND_ERROR_CODES
15212
+ ]);
15213
+ EXPECTED_STATE_ERROR_CODES = new Set([
15214
+ "SESSION_EXPIRED",
15215
+ "SESSION_PERSISTENCE_ERROR",
15216
+ ...COMMAND_ERROR_CODES
15176
15217
  ]);
15177
15218
  SYNC_TRIGGERS = new Set(["hook", "manual", "bootstrap"]);
15178
15219
  SYNC_STATUSES = new Set([
@@ -60316,25 +60357,27 @@ function bareMessageId(id) {
60316
60357
  function truncateId(id) {
60317
60358
  return (isDraftId(id) ? bareMessageId(id) : id).slice(0, 8);
60318
60359
  }
60360
+ function reviewItemNotFound(id) {
60361
+ return new CommandError("REVIEW_ITEM_NOT_FOUND", `No review item found for '${id}'. Run 'dosu review list' to see pending items.`);
60362
+ }
60319
60363
  async function requireChange(client, id) {
60320
60364
  try {
60321
60365
  return await client.review.getChange.query({ id });
60322
60366
  } catch (err) {
60323
- if (isNotFound(err) || isInvalidId(err)) {
60324
- console.error(import_picocolors27.default.red(`No review item found for '${id}'. Run 'dosu review list' to see pending items.`));
60325
- process.exit(1);
60326
- }
60367
+ if (isNotFound(err) || isInvalidId(err))
60368
+ throw reviewItemNotFound(id);
60327
60369
  throw err;
60328
60370
  }
60329
60371
  }
60330
60372
  async function requireDraft(client, id) {
60331
60373
  const draft = await client.messages.getMessage.query(bareMessageId(id));
60332
- if (!draft) {
60333
- console.error(import_picocolors27.default.red(`No review item found for '${id}'. Run 'dosu review list' to see pending items.`));
60334
- process.exit(1);
60335
- }
60374
+ if (!draft)
60375
+ throw reviewItemNotFound(id);
60336
60376
  return draft;
60337
60377
  }
60378
+ function invalidArgument(message) {
60379
+ return new CommandError("INVALID_ARGUMENT", message);
60380
+ }
60338
60381
  function humanizeSource(origin, version) {
60339
60382
  switch (origin) {
60340
60383
  case "manual_update":
@@ -60386,46 +60429,95 @@ function printDraftPreview(draft) {
60386
60429
  console.log();
60387
60430
  console.log(draft.body ?? "");
60388
60431
  }
60389
- async function getKnowledgeStoreId2(client, spaceId) {
60390
- const store = await client.knowledgeStore.getBySpaceId.query({ space_id: spaceId });
60391
- if (!store) {
60392
- console.error(import_picocolors27.default.red("No knowledge store found for this deployment."));
60393
- process.exit(1);
60432
+ function failScope(code, message, hint = SELECT_LIBRARY_HINT) {
60433
+ throw new CommandError(code, message, [hint]);
60434
+ }
60435
+ async function lookupLibrary(client, spaceId) {
60436
+ try {
60437
+ const library = await client.libraries.info.query(spaceId);
60438
+ return { found: true, name: library.name || null };
60439
+ } catch (err) {
60440
+ if (isNotFound(err))
60441
+ return { found: false };
60442
+ return { found: true, name: null };
60394
60443
  }
60395
- return store.id;
60444
+ }
60445
+ async function resolveReviewScope(client, spaceId, deploymentId) {
60446
+ const [store, library, deployment] = await Promise.all([
60447
+ client.knowledgeStore.getBySpaceId.query({ space_id: spaceId }),
60448
+ lookupLibrary(client, spaceId),
60449
+ deploymentId ? client.workspaces.get.query(deploymentId) : Promise.resolve(undefined)
60450
+ ]);
60451
+ if (deploymentId && !deployment) {
60452
+ failScope("DEPLOYMENT_UNAVAILABLE", `The selected MCP deployment (${deploymentId}) is unavailable: it was deleted, or the ` + "signed-in account cannot access it.");
60453
+ }
60454
+ if (deployment && deployment.space_id !== spaceId) {
60455
+ failScope("SCOPE_MISMATCH", `The saved Library (${spaceId}) does not match the Library of the selected MCP ` + `deployment (${deployment.space_id}).`, `Run 'dosu deployments switch ${deployment.deployment_id}' to save that deployment's ` + "Library again, or choose another MCP deployment with 'dosu deployments list'.");
60456
+ }
60457
+ if (!library.found || !store) {
60458
+ failScope("LIBRARY_UNAVAILABLE", `The selected Library (${spaceId}) is unavailable: it was deleted, or the signed-in ` + "account cannot access it.");
60459
+ }
60460
+ return {
60461
+ knowledgeStoreId: store.id,
60462
+ library: { id: spaceId, name: library.name },
60463
+ deployment: deployment ? { id: deployment.deployment_id, name: deployment.name || null } : null
60464
+ };
60465
+ }
60466
+ function describeScopeEntry(entry) {
60467
+ return entry.name ? `${entry.name} (${entry.id})` : entry.id;
60468
+ }
60469
+ function printScope(scope) {
60470
+ printInfo([
60471
+ ["Library", describeScopeEntry(scope.library)],
60472
+ [
60473
+ "MCP deployment",
60474
+ scope.deployment ? describeScopeEntry(scope.deployment) : "none selected (draft replies not listed)"
60475
+ ]
60476
+ ]);
60477
+ console.log();
60396
60478
  }
60397
60479
  function reviewCommand() {
60398
60480
  const cmd = new Command("review").description("Review workflow (doc changes and draft replies)");
60399
- cmd.command("list").description("List pending review items (doc changes and draft replies)").addOption(new Option("--since <when>", "Only items created at or after <when>: a duration back from now (24h, 7d, 2w), " + "a UTC date (2026-09-01), or an ISO-8601 datetime").argParser(timeBound)).addOption(new Option("--until <when>", "Only items created before <when> (same forms; a date includes that whole day)").argParser(timeBound)).option("--json", "Output as JSON").action(async (opts) => {
60481
+ cmd.command("list").description("List pending review items in the selected Library (doc changes and draft replies)").addOption(new Option("--since <when>", "Only items created at or after <when>: a duration back from now (24h, 7d, 2w), " + "a UTC date (2026-09-01), or an ISO-8601 datetime").argParser(timeBound)).addOption(new Option("--until <when>", "Only items created before <when> (same forms; a date includes that whole day)").argParser(timeBound)).option("--json", "Output as JSON").action(async (opts) => {
60400
60482
  let range;
60401
60483
  try {
60402
60484
  range = resolveTimeRange(opts.since, opts.until);
60403
60485
  } catch (err) {
60404
- console.error(import_picocolors27.default.red(`${err.message}.`));
60405
- process.exit(1);
60486
+ throw invalidArgument(`${err.message}.`);
60406
60487
  }
60407
60488
  const rangeLabel = describeTimeRange(range);
60408
60489
  const inRange = rangeLabel ? ` ${rangeLabel}` : "";
60409
60490
  const cfg = requireConfig10();
60410
- if (!cfg.active_account?.target?.space_id) {
60411
- console.error(import_picocolors27.default.red("Missing space config. Run 'dosu setup' to reconfigure."));
60412
- process.exit(1);
60491
+ const target = cfg.active_account?.target;
60492
+ if (!target?.space_id) {
60493
+ failScope("NO_LIBRARY_SELECTED", "No Library selected. The review list reads the Library of the selected MCP deployment.");
60413
60494
  }
60414
60495
  const client = createTypedClient(cfg);
60415
- const ksId = await getKnowledgeStoreId2(client, cfg.active_account?.target?.space_id);
60496
+ const scope = await resolveReviewScope(client, target.space_id, target.deployment_id);
60416
60497
  const result = await client.review.listPending.query({
60417
- knowledgeStoreId: ksId,
60418
- deploymentId: cfg.active_account?.target?.deployment_id,
60498
+ knowledgeStoreId: scope.knowledgeStoreId,
60499
+ ...scope.deployment && { deploymentId: scope.deployment.id },
60419
60500
  ...range.since && { since: range.since.toISOString() },
60420
60501
  ...range.until && { until: range.until.toISOString() }
60421
60502
  });
60422
60503
  const { items, truncated, total } = result;
60423
60504
  if (opts.json) {
60424
- printResult(result, opts);
60505
+ printResult({
60506
+ ...result,
60507
+ scope: {
60508
+ library: scope.library,
60509
+ deployment: scope.deployment,
60510
+ kinds: scope.deployment ? ["doc_change", "draft_message"] : ["doc_change"],
60511
+ since: range.since?.toISOString() ?? null,
60512
+ until: range.until?.toISOString() ?? null
60513
+ }
60514
+ }, opts);
60425
60515
  return;
60426
60516
  }
60517
+ printScope(scope);
60427
60518
  if (!items || items.length === 0) {
60428
- console.log(import_picocolors27.default.dim(`No pending review items${inRange}.`));
60519
+ console.log(import_picocolors27.default.dim(`No pending review items in this scope${inRange}.`));
60520
+ console.log(import_picocolors27.default.dim("Only the selected Library is listed. To review another Library, find its MCP " + "deployment with 'dosu deployments list' and select it with " + "'dosu deployments switch <deployment-id>'."));
60429
60521
  return;
60430
60522
  }
60431
60523
  if (rangeLabel)
@@ -60479,30 +60571,22 @@ function reviewCommand() {
60479
60571
  const cfg = requireConfig10();
60480
60572
  const client = createTypedClient(cfg);
60481
60573
  if (opts.body !== undefined && opts.bodyFile !== undefined) {
60482
- console.error(import_picocolors27.default.red("Pass only one of --body or --body-file."));
60483
- process.exit(1);
60574
+ throw invalidArgument("Pass only one of --body or --body-file.");
60484
60575
  }
60485
60576
  let body = opts.body;
60486
60577
  if (opts.bodyFile) {
60487
60578
  try {
60488
60579
  body = readFileSync19(opts.bodyFile, "utf-8");
60489
60580
  } catch (err) {
60490
- console.error(import_picocolors27.default.red(`Failed to read --body-file: ${err.message}`));
60491
- process.exit(1);
60581
+ throw invalidArgument(`Failed to read --body-file: ${err.message}`);
60492
60582
  }
60493
60583
  }
60494
60584
  if (body === undefined && opts.title === undefined) {
60495
- console.error(import_picocolors27.default.red("Nothing to edit. Pass --title and/or --body/--body-file."));
60496
- process.exit(1);
60585
+ throw invalidArgument("Nothing to edit. Pass --title and/or --body/--body-file.");
60497
60586
  }
60498
60587
  if (isDraftId(id)) {
60499
- if (opts.title !== undefined) {
60500
- console.error(import_picocolors27.default.red("Draft replies support --body only (no --title)."));
60501
- process.exit(1);
60502
- }
60503
- if (body === undefined) {
60504
- console.error(import_picocolors27.default.red("Draft replies require --body or --body-file."));
60505
- process.exit(1);
60588
+ if (opts.title !== undefined || body === undefined) {
60589
+ throw invalidArgument("Draft replies support --body only (no --title).");
60506
60590
  }
60507
60591
  await requireDraft(client, id);
60508
60592
  await client.messages.saveDraft.mutate({
@@ -60518,8 +60602,7 @@ function reviewCommand() {
60518
60602
  });
60519
60603
  } catch (err) {
60520
60604
  if (isNotFound(err)) {
60521
- console.error(import_picocolors27.default.red(`No pending review item found for '${id}'. Run 'dosu review list' to see editable items.`));
60522
- process.exit(1);
60605
+ throw new CommandError("REVIEW_ITEM_NOT_FOUND", `No pending review item found for '${id}'. Run 'dosu review list' to see editable items.`);
60523
60606
  }
60524
60607
  throw err;
60525
60608
  }
@@ -60600,8 +60683,7 @@ function reviewCommand() {
60600
60683
  const client = createTypedClient(cfg);
60601
60684
  if (isDraftId(id)) {
60602
60685
  await requireDraft(client, id);
60603
- console.error(import_picocolors27.default.red("Revert is not supported for draft replies. A rejected draft is regenerated on the next agent run."));
60604
- process.exit(1);
60686
+ throw invalidArgument("Revert is not supported for draft replies. A rejected draft is regenerated on the next agent run.");
60605
60687
  }
60606
60688
  await requireChange(client, id);
60607
60689
  await client.page.updatePublicationStatus.mutate({
@@ -60617,10 +60699,11 @@ function reviewCommand() {
60617
60699
  cmd.addCommand(reviewNotificationsCommand());
60618
60700
  return cmd;
60619
60701
  }
60620
- var import_picocolors27, DRAFT_MESSAGE_ID_PREFIX = "draft_message:";
60702
+ var import_picocolors27, DRAFT_MESSAGE_ID_PREFIX = "draft_message:", SELECT_LIBRARY_HINT;
60621
60703
  var init_review = __esm(() => {
60622
60704
  init_dist();
60623
60705
  init_commander();
60706
+ init_command_error();
60624
60707
  init_trpc();
60625
60708
  init_auth();
60626
60709
  init_confirmation();
@@ -60628,6 +60711,7 @@ var init_review = __esm(() => {
60628
60711
  init_review_notifications();
60629
60712
  init_time_range();
60630
60713
  import_picocolors27 = __toESM(require_picocolors(), 1);
60714
+ SELECT_LIBRARY_HINT = "Run 'dosu deployments list' to see the MCP deployments you can select, then " + "'dosu deployments switch <deployment-id>' for the Library to review, or run 'dosu setup' " + "to choose the organization and Library again.";
60631
60715
  });
60632
60716
 
60633
60717
  // src/generated/skills.ts
@@ -60713,7 +60797,7 @@ Use [commands.md](references/commands.md) as the sole detailed command and flag
60713
60797
  ## Handle failures literally
60714
60798
 
60715
60799
  - \`Not logged in\` or an unrecoverable expired session: run \`dosu login\` (agent setup may instead return a ticket flow).
60716
- - Missing organization, Library, deployment, or API-key context: run the appropriate \`dosu setup\` flow.
60800
+ - Missing or unavailable organization, Library, deployment, or API-key context: tell the user which \`dosu setup\` flow fixes it. Plain \`dosu setup\` is interactive, so don't start it for them unless they ask.
60717
60801
  - Wrong or unexpected Library context: read \`dosu status --json\` and \`dosu deployments info --json\` before changing anything. Switch only to an MCP deployment the user named or confirmed; never pick the first one listed, and never switch just because a list came back empty.
60718
60802
  - Skill guidance out of date: \`dosu skill update\` rewrites the installed skill from the running CLI version. Some agents read skills only when a session starts, so start a new session if the change does not appear. Installed files show the skill is on disk, not that the agent loaded it.
60719
60803
  - \`confirmRequired\`: no write occurred.
@@ -61089,7 +61173,7 @@ dosu review notifications set (--library <library-id> | --agent <agent-id>)
61089
61173
  dosu review notifications clear (--library <library-id> | --agent <agent-id>) [--confirm] [--json]
61090
61174
  \`\`\`
61091
61175
 
61092
- - \`list\` reads the selected Library's pending doc changes plus the selected MCP deployment's draft replies. JSON is \`{items, truncated, total}\`: at most 50 items, newest first; \`total\` is a lower bound when \`truncated\` is true. \`--since\`/\`--until\` take \`24h\`/\`7d\`/\`2w\`, a UTC date, or an ISO-8601 datetime.
61176
+ - \`list\` reads the selected Library's pending doc changes plus the selected MCP deployment's draft replies. JSON is \`{items, truncated, total, scope}\`: at most 50 items, newest first; \`total\` is a lower bound when \`truncated\` is true; \`scope\` names the Library and MCP deployment searched (see [review-workflow.md](review-workflow.md)). A missing, deleted, or inaccessible Library or deployment is an error, not an empty list. \`--since\`/\`--until\` take \`24h\`/\`7d\`/\`2w\`, a UTC date, or an ISO-8601 datetime.
61093
61177
  - IDs are opaque: pass each \`id\` from \`list\` verbatim (draft replies carry a \`draft_message:\` prefix). \`revert\` takes a decided doc change's page-version ID and does not apply to drafts.
61094
61178
  - \`edit\` requires at least one field; drafts accept only \`--body\`/\`--body-file\`. \`approve\` and \`reject\` do not write without interactive confirmation or \`--confirm\`.
61095
61179
  - \`notifications\` manages the Slack channel that receives review notifications for one Library (doc reviews) or one Agent (its draft replies). Unlike the queue commands it takes an explicit target, not the selected MCP deployment. Pass exactly one of \`--library\`/\`--agent\` (UUID v4; Agent IDs come from \`dosu agents list\`).
@@ -61169,6 +61253,7 @@ dosu deployments switch <id> [--json]
61169
61253
 
61170
61254
  dosu mcp list
61171
61255
  dosu mcp add <agent> [--global] [--show-secret]
61256
+ dosu mcp refresh
61172
61257
  dosu skill install | remove | update
61173
61258
  dosu telemetry status [--json]
61174
61259
  dosu telemetry enable | disable | reset
@@ -61177,7 +61262,7 @@ dosu upgrade [--auto on|off]
61177
61262
  dosu logs [--tail [n]] [--clear]
61178
61263
  \`\`\`
61179
61264
 
61180
- \`deployments\` selects the MCP deployment stored in local config; it is distinct from \`agents\`. \`insights\` opens an interactive visual report. \`logs --clear\` deletes the CLI log file. The CLI updates itself in the background by default; a \`[dosu:update] Installing ...\` notice on stderr needs no action. \`upgrade --auto off\` turns that off.
61265
+ \`deployments\` selects the MCP deployment stored in local config; it is distinct from \`agents\`. \`deployments switch\` does not repoint an AI tool's running MCP connection; \`mcp refresh\` rewrites the global Dosu MCP entry of each configured tool from the current selection (project-level entries change on the next \`dosu setup\` or \`dosu mcp add\`), and the tool then needs a restart or reconnect. \`insights\` opens an interactive visual report. \`logs --clear\` deletes the CLI log file. The CLI updates itself in the background by default; a \`[dosu:update] Installing ...\` notice on stderr needs no action. \`upgrade --auto off\` turns that off.
61181
61266
 
61182
61267
  ## Codebase audit
61183
61268
 
@@ -61192,7 +61277,7 @@ The coding agent writes the findings; Dosu generates the selected docs. Follow [
61192
61277
  },
61193
61278
  {
61194
61279
  path: "references/review-workflow.md",
61195
- content: '# Review workflow\n\nHow to work Dosu\'s review queue from the CLI: triage pending items, read the\ndiff, optionally edit, and apply a decision — safely.\n\nThe queue holds **document versions** (AI-generated, user-edited, synced from\nsource, or API-created) and **draft messages**, all surfaced through the same\n`dosu review` commands. Doc changes come from the selected Library; draft\nreplies come only from threads of the selected MCP deployment.\n\n## Read the list result\n\n`dosu review list --json` returns `{ "items": [...], "truncated": <bool>, "total": <n> }`.\n\n- `items` holds at most 50 entries, newest first, doc changes and draft\n replies merged.\n- `truncated: true` means more pending items may exist than `items` shows.\n- `total` counts what the server found before that cap. When `truncated` is\n true it is a lower bound: report "at least `total`", never an exact backlog.\n- Each `id` is opaque. A doc change\'s `id` is its page-version UUID; a draft\n reply\'s `id` looks like `draft_message:<uuid>` and has `kind: "draft_message"`.\n Pass the `id` exactly as listed to `diff`, `edit`, `approve`, and `reject`;\n do not strip the prefix or shorten it.\n- `--since <when>` and `--until <when>` narrow the list by creation time. Each\n takes a duration back from now (`24h`, `7d`, `2w`), a UTC date\n (`2026-09-01`; an `--until` date includes that whole day), or an ISO-8601\n datetime. When `truncated` is true, `--until` reaches older items.\n\nAn empty `items` array means no pending items in the current scope. It is not\na reason to switch deployments.\n\n## Check the context when the scope looks wrong\n\nRun this only when the user says the list is for the wrong Library, names a\ndifferent Library, or a command reports missing context:\n\n1. `dosu status --json`: login state and the selected MCP deployment.\n2. `dosu deployments info --json`: the selected MCP deployment. The list reads\n the Library saved in the CLI\'s config, which `dosu setup` and\n `dosu deployments switch` set to this deployment\'s `space_id`\n (`dosu libraries info <space_id> --json` gives its name).\n3. If that is not the Library the user means, run\n `dosu deployments list --json` and find the MCP deployment whose `space_id`\n matches it (`dosu libraries list --json` maps names to IDs).\n4. Switch only to a deployment the user named or confirmed:\n `dosu deployments switch <deployment-id> --json`. It changes the CLI\'s\n selection for later commands and mints a new API key, so say that it did.\n Never pick the first deployment, and ask when several match or none does.\n5. Run `dosu review list --json` again.\n\n## Draft reply limits\n\n- `edit` on a draft accepts `--body` or `--body-file` only (no `--title`).\n- `approve` publishes the draft to its thread; `reject` discards it.\n- Drafts have no `revert`. A rejected draft is regenerated on the next agent run.\n\n## Scope tool access for a review session\n\nApproving and rejecting are destructive and outward-facing. When you spin up an\nagent session dedicated to reviewing, scope it to just the review commands:\n\n```yaml\nallowed-tools: Bash(dosu review:*)\n```\n\nThis lets the agent triage, diff, and edit, but keeps it from running unrelated\nCLI commands during a review pass.\n\n## The flow\n\n```bash\n# 1. Triage — what\'s pending? Columns: ID, Kind, Title, Source, Status, Created.\ndosu review list --json\ndosu review list --since 7d --json # optional creation-time window\n\n# 2. Inspect — read the exact change before deciding anything.\ndosu review diff <id> --json\n\n# 3. (optional) Edit in place instead of rejecting a near-miss.\ndosu review edit <id> --body-file ./revised.md # or --body / --title (docs only)\n\n# 4. Decide — only on the item the user named, only after they OK it.\ndosu review approve <id> --confirm --json\ndosu review reject <id> --confirm --json\n\n# Undo a doc-change decision — back to pending (not for draft replies).\ndosu review revert <page-version-id> --json\n```\n\n### `--confirm` is mandatory for agents\n\n`approve` and `reject` only prompt interactively in a TTY *without* `--json`.\nThe skill always passes `--json`, which suppresses both the diff preview and\nthe prompt — so without `--confirm` the command changes nothing and returns\n`{ "applied": false, "confirmRequired": true }`. Run `dosu review diff <id>` to\nsee the change, then pass `--confirm` to apply — only after the user has\napproved that specific item.\n\n## Reading the `Source` column\n\n`Source` is the humanized `origin` field:\n\n| `origin` | Source | Meaning |\n|---|---|---|\n| `manual_update` | User created / User updated | A teammate authored or edited the doc |\n| `llm_generated` | AI generated | Dosu drafted the change |\n| `sync_upstream` | Synced from source | Came in from a connected source (Notion/Confluence/GitHub) |\n| `api_update` | Created via API | Pushed in programmatically |\n\n## Feeding PR / thread context\n\nA review often originates from a conversation thread (a GitHub issue, a Slack\nmessage, a sync-back PR). Pull that context before deciding:\n\n```bash\ndosu review context <thread-id> --json\n```\n\nIt returns the review `type`, the review/published page IDs, and — when the\nchange round-trips to an external system — a **Sync PR URL**. Use it to:\n\n- show the user where the change came from and where approval will land, and\n- read the originating discussion so the decision matches intent.\n\n## Safety rules\n\n- **Explicit item only.** Decide on the `id` the user named. "Review my queue"\n → run `list` and show it; never decide on their behalf.\n- **Listing is read-only.** A request to show the list authorizes no approve,\n reject, edit, API-key creation, or deployment switch.\n- **Diff before deciding.** Always `diff` and surface the change first.\n- **Never batch-accept.** No loop over `list` that approves everything. One\n item, one confirmation.\n- **Extra care for sync/PR origins.** `Synced from source` items and anything\n with a Sync PR URL push back to an upstream system on approval. Call it out\n and get explicit sign-off.\n',
61280
+ content: '# Review workflow\n\nHow to work Dosu\'s review queue from the CLI: triage pending items, read the\ndiff, optionally edit, and apply a decision — safely.\n\nThe queue holds **document versions** (AI-generated, user-edited, synced from\nsource, or API-created) and **draft messages**, all surfaced through the same\n`dosu review` commands. Doc changes come from the selected Library; draft\nreplies come only from threads of the selected MCP deployment.\n\n## Read the list result\n\n`dosu review list --json` returns\n`{ "items": [...], "truncated": <bool>, "total": <n>, "scope": {...} }`.\n\n- `items` holds at most 50 entries, newest first, doc changes and draft\n replies merged.\n- `truncated: true` means more pending items may exist than `items` shows.\n- `total` counts what the server found before that cap. When `truncated` is\n true it is a lower bound: report "at least `total`", never an exact backlog.\n- Each `id` is opaque. A doc change\'s `id` is its page-version UUID; a draft\n reply\'s `id` looks like `draft_message:<uuid>` and has `kind: "draft_message"`.\n Pass the `id` exactly as listed to `diff`, `edit`, `approve`, and `reject`;\n do not strip the prefix or shorten it.\n- `--since <when>` and `--until <when>` narrow the list by creation time. Each\n takes a duration back from now (`24h`, `7d`, `2w`), a UTC date\n (`2026-09-01`; an `--until` date includes that whole day), or an ISO-8601\n datetime. When `truncated` is true, `--until` reaches older items.\n\n`scope` says what was searched. It was added alongside the older fields,\nwhich keep their meaning:\n\n- `library`: `{ "id", "name" }` of the Library whose pending doc changes are\n listed.\n- `deployment`: `{ "id", "name" }` of the MCP deployment whose draft replies\n are listed, or `null` when none is saved. Then drafts are not listed.\n- `kinds`: the item kinds searched, `["doc_change", "draft_message"]` or\n `["doc_change"]`.\n- `since` / `until`: the resolved ISO-8601 bounds, or `null` when unbounded.\n\nA `name` is `null` when the server could not provide one; report the ID then\nand do not guess a name. Tell the user which Library the list covers.\n\nAn empty `items` array means no pending items in this scope. It is not a\nreason to switch deployments.\n\n`review list` checks the saved context before listing and fails (exit 1,\nnothing on stdout) instead of returning an empty list when:\n\n- `Not logged in`: run `dosu login`.\n- `No Library selected`: no Library is saved. Follow the context check below.\n- `... is unavailable`: the saved MCP deployment or Library was deleted, or\n the signed-in account cannot access it. Follow the context check below; if\n `dosu deployments list --json` returns nothing, the saved organization may\n be the inaccessible one, and `dosu setup` chooses it again.\n- `... does not match ...`: the saved Library is not the MCP deployment\'s\n Library. The message names the `dosu deployments switch` command that saves\n them together again; run it only after the user confirms.\n\n## Check the context when the scope looks wrong\n\nRun this only when the user says the list is for the wrong Library, names a\ndifferent Library, or a command reports missing context:\n\n1. `dosu status --json`: login state and the selected MCP deployment.\n2. `dosu deployments info --json`: the selected MCP deployment. The list reads\n the Library saved in the CLI\'s config, which `dosu setup` and\n `dosu deployments switch` set to this deployment\'s `space_id`\n (`dosu libraries info <space_id> --json` gives its name). A successful list\n already reports both in `scope`, and a saved Library that does not match\n the deployment\'s is an error.\n3. If that is not the Library the user means, run\n `dosu deployments list --json` and find the MCP deployment whose `space_id`\n matches it (`dosu libraries list --json` maps names to IDs).\n4. Switch only to a deployment the user named or confirmed:\n `dosu deployments switch <deployment-id> --json`. It changes the CLI\'s\n selection for later commands and mints a new API key, so say that it did.\n Never pick the first deployment, and ask when several match or none does.\n5. Run `dosu review list --json` again.\n\n`deployments switch` changes only the CLI\'s saved selection. An AI tool with a\nrunning Dosu MCP connection still points at its previous deployment until\n`dosu mcp refresh` rewrites its MCP entry and the tool restarts or reconnects.\nSay so after a switch; do not run `dosu mcp refresh` unless the user asks.\n\n## Draft reply limits\n\n- `edit` on a draft accepts `--body` or `--body-file` only (no `--title`).\n- `approve` publishes the draft to its thread; `reject` discards it.\n- Drafts have no `revert`. A rejected draft is regenerated on the next agent run.\n\n## Scope tool access for a review session\n\nApproving and rejecting are destructive and outward-facing. When you spin up an\nagent session dedicated to reviewing, scope it to just the review commands:\n\n```yaml\nallowed-tools: Bash(dosu review:*)\n```\n\nThis lets the agent triage, diff, and edit, but keeps it from running unrelated\nCLI commands during a review pass.\n\n## The flow\n\n```bash\n# 1. Triage — what\'s pending? Columns: ID, Kind, Title, Source, Status, Created.\ndosu review list --json\ndosu review list --since 7d --json # optional creation-time window\n\n# 2. Inspect — read the exact change before deciding anything.\ndosu review diff <id> --json\n\n# 3. (optional) Edit in place instead of rejecting a near-miss.\ndosu review edit <id> --body-file ./revised.md # or --body / --title (docs only)\n\n# 4. Decide — only on the item the user named, only after they OK it.\ndosu review approve <id> --confirm --json\ndosu review reject <id> --confirm --json\n\n# Undo a doc-change decision — back to pending (not for draft replies).\ndosu review revert <page-version-id> --json\n```\n\n### `--confirm` is mandatory for agents\n\n`approve` and `reject` only prompt interactively in a TTY *without* `--json`.\nThe skill always passes `--json`, which suppresses both the diff preview and\nthe prompt — so without `--confirm` the command changes nothing and returns\n`{ "applied": false, "confirmRequired": true }`. Run `dosu review diff <id>` to\nsee the change, then pass `--confirm` to apply — only after the user has\napproved that specific item.\n\n## Reading the `Source` column\n\n`Source` is the humanized `origin` field:\n\n| `origin` | Source | Meaning |\n|---|---|---|\n| `manual_update` | User created / User updated | A teammate authored or edited the doc |\n| `llm_generated` | AI generated | Dosu drafted the change |\n| `sync_upstream` | Synced from source | Came in from a connected source (Notion/Confluence/GitHub) |\n| `api_update` | Created via API | Pushed in programmatically |\n\n## Feeding PR / thread context\n\nA review often originates from a conversation thread (a GitHub issue, a Slack\nmessage, a sync-back PR). Pull that context before deciding:\n\n```bash\ndosu review context <thread-id> --json\n```\n\nIt returns the review `type`, the review/published page IDs, and — when the\nchange round-trips to an external system — a **Sync PR URL**. Use it to:\n\n- show the user where the change came from and where approval will land, and\n- read the originating discussion so the decision matches intent.\n\n## Safety rules\n\n- **Explicit item only.** Decide on the `id` the user named. "Review my queue"\n → run `list` and show it; never decide on their behalf.\n- **Listing is read-only.** A request to show the list authorizes no approve,\n reject, edit, API-key creation, or deployment switch.\n- **Diff before deciding.** Always `diff` and surface the change first.\n- **Never batch-accept.** No loop over `list` that approves everything. One\n item, one confirmation.\n- **Extra care for sync/PR origins.** `Synced from source` items and anything\n with a Sync PR URL push back to an upstream system on approval. Call it out\n and get explicit sign-off.\n',
61196
61281
  executable: false
61197
61282
  },
61198
61283
  {
@@ -62328,7 +62413,7 @@ function requireConfig13() {
62328
62413
  }
62329
62414
  return cfg;
62330
62415
  }
62331
- async function getKnowledgeStoreId3(client, spaceId) {
62416
+ async function getKnowledgeStoreId2(client, spaceId) {
62332
62417
  const store = await client.knowledgeStore.getBySpaceId.query({
62333
62418
  space_id: spaceId
62334
62419
  });
@@ -62343,7 +62428,7 @@ function topicsCommand() {
62343
62428
  cmd.command("list").description("List all topics").option("--json", "Output as JSON").action(async (opts) => {
62344
62429
  const cfg = requireConfig13();
62345
62430
  const client = createTypedClient(cfg);
62346
- const ksId = await getKnowledgeStoreId3(client, cfg.active_account.target.space_id);
62431
+ const ksId = await getKnowledgeStoreId2(client, cfg.active_account.target.space_id);
62347
62432
  const topics = await client.topic.listTopicsByKnowledgeStore.query({
62348
62433
  knowledge_store_id: ksId
62349
62434
  });
@@ -62364,7 +62449,7 @@ function topicsCommand() {
62364
62449
  cmd.command("pages").description("List pages with a specific topic").argument("<topic-id>", "Topic ID").option("--search <query>", "Search within the topic's pages").addOption(new Option("--limit <n>", "Maximum results").argParser(positiveInteger).default(10)).option("--json", "Output as JSON").action(async (topicId, opts) => {
62365
62450
  const cfg = requireConfig13();
62366
62451
  const client = createTypedClient(cfg);
62367
- const ksId = await getKnowledgeStoreId3(client, cfg.active_account.target.space_id);
62452
+ const ksId = await getKnowledgeStoreId2(client, cfg.active_account.target.space_id);
62368
62453
  const result = await client.topic.getPagesByTopicId.query({
62369
62454
  knowledge_store_id: ksId,
62370
62455
  topic_id: topicId,
@@ -66640,6 +66725,63 @@ var init_incognito_backfill_check = __esm(() => {
66640
66725
  import_picocolors38 = __toESM(require_picocolors(), 1);
66641
66726
  });
66642
66727
 
66728
+ // src/cli/fatal-error.ts
66729
+ function read2(value, key) {
66730
+ if (typeof value !== "object" && typeof value !== "function" || value === null) {
66731
+ return;
66732
+ }
66733
+ try {
66734
+ return Reflect.get(value, key);
66735
+ } catch {
66736
+ return;
66737
+ }
66738
+ }
66739
+ function tokenOf2(data, key) {
66740
+ const value = read2(data, key);
66741
+ if (typeof value !== "string")
66742
+ return;
66743
+ const trimmed = value.trim();
66744
+ return REQUEST_ID_PATTERN2.test(trimmed) ? trimmed : undefined;
66745
+ }
66746
+ function statusOf2(data) {
66747
+ const value = read2(data, "httpStatus");
66748
+ return typeof value === "number" && Number.isInteger(value) && value >= 100 && value <= 599 ? value : undefined;
66749
+ }
66750
+ function fatalErrorDiagnostics2(err) {
66751
+ const data = read2(err, "data");
66752
+ if (!data)
66753
+ return;
66754
+ const code = tokenOf2(data, "code");
66755
+ const path = tokenOf2(data, "path");
66756
+ const status = statusOf2(data);
66757
+ const requestId = tokenOf2(data, "requestId");
66758
+ const parts = [
66759
+ code && `code=${code}`,
66760
+ path && `path=${path}`,
66761
+ status && `status=${status}`,
66762
+ requestId && `request_id=${requestId}`
66763
+ ].filter(Boolean);
66764
+ return parts.length > 0 ? parts.join(" ") : undefined;
66765
+ }
66766
+ function printFatalError2(err) {
66767
+ if (isCommandError(err)) {
66768
+ console.error(import_picocolors39.default.red(err.message));
66769
+ for (const line of err.details)
66770
+ console.error(import_picocolors39.default.dim(line));
66771
+ return;
66772
+ }
66773
+ console.error(read2(err, "message") ?? err);
66774
+ const diagnostics = fatalErrorDiagnostics2(err);
66775
+ if (diagnostics)
66776
+ console.error(diagnostics);
66777
+ }
66778
+ var import_picocolors39, REQUEST_ID_PATTERN2;
66779
+ var init_fatal_error = __esm(() => {
66780
+ init_command_error();
66781
+ import_picocolors39 = __toESM(require_picocolors(), 1);
66782
+ REQUEST_ID_PATTERN2 = /^[A-Za-z0-9][A-Za-z0-9:._-]{0,127}$/;
66783
+ });
66784
+
66643
66785
  // src/tui/analytics-view.ts
66644
66786
  function analyticsListHeight(rows) {
66645
66787
  const room = frameMaxLines(rows) - ANALYTICS_VIEW_CHROME_LINES;
@@ -66758,7 +66900,7 @@ function renderAnalyticsFrame(state, tab, scroll, pageStats = null, pagesPending
66758
66900
  const rows = analyticsTabRows(tab, state, pageStats);
66759
66901
  const { visible, above, below } = windowReport(rows, scroll, height);
66760
66902
  const empty = tab === "overview" ? "No analytics yet. They appear after the first study run." : tab === "projects" ? "No per-project history yet. It fills in as sessions are studied." : pagesPending ? "Loading page analytics..." : "No page analytics yet.";
66761
- const listRows = visible.length > 0 ? visible.map((row) => import_picocolors39.default.dim(row)) : [import_picocolors39.default.dim(empty)];
66903
+ const listRows = visible.length > 0 ? visible.map((row) => import_picocolors40.default.dim(row)) : [import_picocolors40.default.dim(empty)];
66762
66904
  const scrollParts = [];
66763
66905
  if (above > 0)
66764
66906
  scrollParts.push(`↑ ${above} earlier`);
@@ -66774,9 +66916,9 @@ function renderAnalyticsFrame(state, tab, scroll, pageStats = null, pagesPending
66774
66916
  ], tab, width, { spread: false }),
66775
66917
  "",
66776
66918
  ...listRows,
66777
- ...scrollParts.length > 0 ? [import_picocolors39.default.dim(scrollParts.join(" · "))] : [],
66919
+ ...scrollParts.length > 0 ? [import_picocolors40.default.dim(scrollParts.join(" · "))] : [],
66778
66920
  "",
66779
- import_picocolors39.default.dim("tab switch · ↑↓ scroll · esc back")
66921
+ import_picocolors40.default.dim("tab switch · ↑↓ scroll · esc back")
66780
66922
  ];
66781
66923
  return lines.join(`
66782
66924
  `);
@@ -66873,7 +67015,7 @@ ${CLEAR_BELOW2}`);
66873
67015
  input.on("data", onData);
66874
67016
  });
66875
67017
  }
66876
- var import_picocolors39, ESC8, HIDE_CURSOR4, SHOW_CURSOR4, CTRL_C5, KEY_UP5, KEY_DOWN5, KEY_RIGHT4, KEY_LEFT4, CURSOR_HOME2, CLEAR_BELOW2, CLEAR_EOL2, ANALYTICS_VIEW_POLL_MS = 1000, ANALYTICS_VIEW_LINES = 14, ANALYTICS_VIEW_CHROME_LINES = 8, ANALYTICS_VIEW_TABS, TOP_PAGES_LIMIT = 5, TOP_CITED_DAYS = 30, label = (text) => text.padEnd(26);
67018
+ var import_picocolors40, ESC8, HIDE_CURSOR4, SHOW_CURSOR4, CTRL_C5, KEY_UP5, KEY_DOWN5, KEY_RIGHT4, KEY_LEFT4, CURSOR_HOME2, CLEAR_BELOW2, CLEAR_EOL2, ANALYTICS_VIEW_POLL_MS = 1000, ANALYTICS_VIEW_LINES = 14, ANALYTICS_VIEW_CHROME_LINES = 8, ANALYTICS_VIEW_TABS, TOP_PAGES_LIMIT = 5, TOP_CITED_DAYS = 30, label = (text) => text.padEnd(26);
66877
67019
  var init_analytics_view = __esm(() => {
66878
67020
  init_trpc();
66879
67021
  init_config();
@@ -66881,7 +67023,7 @@ var init_analytics_view = __esm(() => {
66881
67023
  init_alt_screen();
66882
67024
  init_layout();
66883
67025
  init_menu();
66884
- import_picocolors39 = __toESM(require_picocolors(), 1);
67026
+ import_picocolors40 = __toESM(require_picocolors(), 1);
66885
67027
  ESC8 = String.fromCharCode(27);
66886
67028
  HIDE_CURSOR4 = `${ESC8}[?25l`;
66887
67029
  SHOW_CURSOR4 = `${ESC8}[?25h`;
@@ -66899,7 +67041,7 @@ var init_analytics_view = __esm(() => {
66899
67041
  // src/tui/banner.ts
66900
67042
  function paintLogoRow(row) {
66901
67043
  return row.map(([tone, art]) => {
66902
- if (!import_picocolors40.default.isColorSupported)
67044
+ if (!import_picocolors41.default.isColorSupported)
66903
67045
  return art;
66904
67046
  const { fg, fallback } = LOGO_TONES[tone];
66905
67047
  return hasTruecolor() ? `${fg}${art}\x1B[39m` : fallback(art);
@@ -66930,14 +67072,14 @@ function wrapValue(text, room) {
66930
67072
  }
66931
67073
  function checklistRows(ctx, width) {
66932
67074
  const on = brand(CHECK2);
66933
- const off = import_picocolors40.default.dim(CIRCLE);
67075
+ const off = import_picocolors41.default.dim(CIRCLE);
66934
67076
  const rows = [["workspace", ctx.directory]];
66935
67077
  rows.push([
66936
67078
  "account",
66937
- ctx.sessionExpired ? `${off} ${import_picocolors40.default.yellow("session expired")} ${import_picocolors40.default.dim(`${DOT3} run Log in`)}` : ctx.signedIn ? `${on} signed in` : `${off} ${import_picocolors40.default.dim(`not signed in ${DOT3} run Setup`)}`
67079
+ ctx.sessionExpired ? `${off} ${import_picocolors41.default.yellow("session expired")} ${import_picocolors41.default.dim(`${DOT3} run Log in`)}` : ctx.signedIn ? `${on} signed in` : `${off} ${import_picocolors41.default.dim(`not signed in ${DOT3} run Setup`)}`
66938
67080
  ]);
66939
67081
  const missing = new Set(ctx.setupMissing ?? []);
66940
- const warnRow = `${off} ${import_picocolors40.default.yellow("not configured")} ${import_picocolors40.default.dim(`${DOT3} run Setup`)}`;
67082
+ const warnRow = `${off} ${import_picocolors41.default.yellow("not configured")} ${import_picocolors41.default.dim(`${DOT3} run Setup`)}`;
66941
67083
  if (missing.has("MCP"))
66942
67084
  rows.push(["mcp", warnRow]);
66943
67085
  else if (ctx.deploymentName)
@@ -66949,7 +67091,7 @@ function checklistRows(ctx, width) {
66949
67091
  if (ctx.repoAgentsMd) {
66950
67092
  rows.push([
66951
67093
  "repo",
66952
- ctx.repoAgentsMd === "current" ? `${on} AGENTS.md has the Dosu section` : `${off} ${import_picocolors40.default.yellow(ctx.repoAgentsMd === "outdated" ? "AGENTS.md Dosu section outdated" : "AGENTS.md missing the Dosu section")} ${import_picocolors40.default.dim(`${DOT3} run Setup`)}`
67094
+ ctx.repoAgentsMd === "current" ? `${on} AGENTS.md has the Dosu section` : `${off} ${import_picocolors41.default.yellow(ctx.repoAgentsMd === "outdated" ? "AGENTS.md Dosu section outdated" : "AGENTS.md missing the Dosu section")} ${import_picocolors41.default.dim(`${DOT3} run Setup`)}`
66953
67095
  ]);
66954
67096
  }
66955
67097
  if (missing.has("agents"))
@@ -66961,20 +67103,20 @@ function checklistRows(ctx, width) {
66961
67103
  if (ctx.studying) {
66962
67104
  rows.push([
66963
67105
  "sync",
66964
- `\uD83D\uDCDA ${brand("studying sessions...")} ${import_picocolors40.default.dim(`${DOT3} see activity`)}`
67106
+ `\uD83D\uDCDA ${brand("studying sessions...")} ${import_picocolors41.default.dim(`${DOT3} see activity`)}`
66965
67107
  ]);
66966
67108
  }
66967
67109
  if (ctx.update) {
66968
67110
  rows.push([
66969
67111
  "update",
66970
- `${import_picocolors40.default.yellow(`↑ ${ctx.update.version} available`)} ${import_picocolors40.default.dim(`${DOT3} ${ctx.update.hint}`)}`
67112
+ `${import_picocolors41.default.yellow(`↑ ${ctx.update.version} available`)} ${import_picocolors41.default.dim(`${DOT3} ${ctx.update.hint}`)}`
66971
67113
  ]);
66972
67114
  }
66973
67115
  const labelWidth = Math.max(...rows.map(([label]) => label.length));
66974
67116
  const valueColumn = LOGO_WIDTH + COLUMN_GAP.length + labelWidth + LABEL_GAP.length;
66975
67117
  const room = Math.max(1, width - valueColumn);
66976
67118
  const indent = " ".repeat(labelWidth + LABEL_GAP.length);
66977
- return rows.flatMap(([label, value]) => wrapValue(value, room).map((line, i) => i === 0 ? `${import_picocolors40.default.dim(label.padEnd(labelWidth))}${LABEL_GAP}${line}` : `${indent}${line}`));
67119
+ return rows.flatMap(([label, value]) => wrapValue(value, room).map((line, i) => i === 0 ? `${import_picocolors41.default.dim(label.padEnd(labelWidth))}${LABEL_GAP}${line}` : `${indent}${line}`));
66978
67120
  }
66979
67121
  function bannerWidth(columns = process.stdout.columns ?? 80) {
66980
67122
  return columns - layoutMargin(columns) - 1;
@@ -66984,7 +67126,7 @@ function renderBanner(ctx, width = bannerWidth()) {
66984
67126
  const text = [
66985
67127
  ...checklistRows(ctx, width),
66986
67128
  "",
66987
- `${brandBadge("dosu-cli")} ${import_picocolors40.default.dim(`${ctx.version} ${DOT3} ${ctx.webAppHost}`)}`
67129
+ `${brandBadge("dosu-cli")} ${import_picocolors41.default.dim(`${ctx.version} ${DOT3} ${ctx.webAppHost}`)}`
66988
67130
  ];
66989
67131
  const height = Math.max(logo.length, text.length);
66990
67132
  const combined = [];
@@ -66997,11 +67139,11 @@ function renderBanner(ctx, width = bannerWidth()) {
66997
67139
  return ["", ...combined, ""].join(`
66998
67140
  `);
66999
67141
  }
67000
- var import_picocolors40, CHECK2 = "✔", CIRCLE = "○", DOT3 = "·", LOGO_ROWS, LOGO_MARK, LOGO_WIDTH, LOGO_TONES, COLUMN_GAP = " ", LABEL_GAP = " ", LIST_SEP;
67142
+ var import_picocolors41, CHECK2 = "✔", CIRCLE = "○", DOT3 = "·", LOGO_ROWS, LOGO_MARK, LOGO_WIDTH, LOGO_TONES, COLUMN_GAP = " ", LABEL_GAP = " ", LIST_SEP;
67001
67143
  var init_banner = __esm(() => {
67002
67144
  init_styles();
67003
67145
  init_layout();
67004
- import_picocolors40 = __toESM(require_picocolors(), 1);
67146
+ import_picocolors41 = __toESM(require_picocolors(), 1);
67005
67147
  LOGO_ROWS = [
67006
67148
  [["sage", "▄▄▄▄▄▄"]],
67007
67149
  [["sage", "██ ▀▄"]],
@@ -67012,8 +67154,8 @@ var init_banner = __esm(() => {
67012
67154
  LOGO_MARK = LOGO_ROWS.map((row) => row.map(([, art]) => art).join(""));
67013
67155
  LOGO_WIDTH = Math.max(...LOGO_MARK.map((row) => row.length));
67014
67156
  LOGO_TONES = {
67015
- sage: { fg: "\x1B[38;2;180;187;145m", fallback: import_picocolors40.default.green },
67016
- moss: { fg: "\x1B[38;2;119;133;97m", fallback: (art) => import_picocolors40.default.dim(import_picocolors40.default.green(art)) }
67157
+ sage: { fg: "\x1B[38;2;180;187;145m", fallback: import_picocolors41.default.green },
67158
+ moss: { fg: "\x1B[38;2;119;133;97m", fallback: (art) => import_picocolors41.default.dim(import_picocolors41.default.green(art)) }
67017
67159
  };
67018
67160
  LIST_SEP = ` ${DOT3} `;
67019
67161
  });
@@ -67126,16 +67268,16 @@ function renderPagesFrame(model, width, selected) {
67126
67268
  const body = [];
67127
67269
  let scrollHint = "";
67128
67270
  if (model.phase === "loading") {
67129
- body.push(import_picocolors41.default.dim("Loading pages…"));
67271
+ body.push(import_picocolors42.default.dim("Loading pages…"));
67130
67272
  } else if (model.phase === "error") {
67131
- body.push(import_picocolors41.default.yellow(clip3(model.message, width)));
67273
+ body.push(import_picocolors42.default.yellow(clip3(model.message, width)));
67132
67274
  } else if (model.pages.length === 0) {
67133
- body.push(import_picocolors41.default.dim("No pages in this Library yet."));
67275
+ body.push(import_picocolors42.default.dim("No pages in this Library yet."));
67134
67276
  } else {
67135
67277
  const rows = pageRows2(model.pages, width - 2);
67136
67278
  const { start, above } = windowSelection(rows.length, selected);
67137
67279
  const visible = rows.slice(start, start + PAGES_VIEW_LINES);
67138
- body.push(...visible.map((row, i) => start + i === selected ? `${brand("▸")} ${import_picocolors41.default.bold(row)}` : ` ${import_picocolors41.default.dim(row)}`));
67280
+ body.push(...visible.map((row, i) => start + i === selected ? `${brand("▸")} ${import_picocolors42.default.bold(row)}` : ` ${import_picocolors42.default.dim(row)}`));
67139
67281
  const below = Math.max(0, Math.max(model.total, rows.length) - start - PAGES_VIEW_LINES);
67140
67282
  const parts = [];
67141
67283
  if (above > 0)
@@ -67144,14 +67286,14 @@ function renderPagesFrame(model, width, selected) {
67144
67286
  parts.push(`↓ ${below} more`);
67145
67287
  scrollHint = parts.join(" · ");
67146
67288
  }
67147
- const count = model.phase === "ready" ? ` ${import_picocolors41.default.dim(model.pages.length < model.total ? `(${model.pages.length} of ${model.total})` : `(${model.pages.length})`)}` : "";
67289
+ const count = model.phase === "ready" ? ` ${import_picocolors42.default.dim(model.pages.length < model.total ? `(${model.pages.length} of ${model.total})` : `(${model.pages.length})`)}` : "";
67148
67290
  const lines = [
67149
67291
  `${breadcrumb(["home", "pages"], width)}${count}`,
67150
67292
  "",
67151
67293
  ...body,
67152
- ...scrollHint ? [import_picocolors41.default.dim(scrollHint)] : [],
67294
+ ...scrollHint ? [import_picocolors42.default.dim(scrollHint)] : [],
67153
67295
  "",
67154
- import_picocolors41.default.dim("↑↓ move · enter open · esc back")
67296
+ import_picocolors42.default.dim("↑↓ move · enter open · esc back")
67155
67297
  ];
67156
67298
  return lines.join(`
67157
67299
  `);
@@ -67160,9 +67302,9 @@ function renderPageFrame(page, content, width, scroll) {
67160
67302
  const body = [];
67161
67303
  let scrollHint = "";
67162
67304
  if (content.phase === "loading") {
67163
- body.push(import_picocolors41.default.dim("Loading page…"));
67305
+ body.push(import_picocolors42.default.dim("Loading page…"));
67164
67306
  } else if (content.phase === "error") {
67165
- body.push(import_picocolors41.default.yellow(clip3(content.message, width)));
67307
+ body.push(import_picocolors42.default.yellow(clip3(content.message, width)));
67166
67308
  } else {
67167
67309
  const rows = wrapBody(content.body, width);
67168
67310
  const { visible, above, below } = windowReport(rows, scroll, PAGE_BODY_LINES);
@@ -67177,12 +67319,12 @@ function renderPageFrame(page, content, width, scroll) {
67177
67319
  const meta = `${page.type} · ${page.published ? "published" : "draft"} · updated ${page.updated_at.slice(0, 10)}`;
67178
67320
  const lines = [
67179
67321
  breadcrumb(["home", "pages", page.title], width),
67180
- import_picocolors41.default.dim(clip3(meta, width)),
67322
+ import_picocolors42.default.dim(clip3(meta, width)),
67181
67323
  "",
67182
67324
  ...body,
67183
- ...scrollHint ? [import_picocolors41.default.dim(scrollHint)] : [],
67325
+ ...scrollHint ? [import_picocolors42.default.dim(scrollHint)] : [],
67184
67326
  "",
67185
- import_picocolors41.default.dim("↑↓ scroll · esc back to pages")
67327
+ import_picocolors42.default.dim("↑↓ scroll · esc back to pages")
67186
67328
  ];
67187
67329
  return lines.join(`
67188
67330
  `);
@@ -67331,7 +67473,7 @@ ${CLEAR_BELOW3}`);
67331
67473
  input.on("data", onData);
67332
67474
  });
67333
67475
  }
67334
- var import_picocolors41, ESC9, HIDE_CURSOR5, SHOW_CURSOR5, CTRL_C6, KEY_UP6, KEY_DOWN6, CURSOR_HOME3, CLEAR_BELOW3, CLEAR_EOL3, PAGES_VIEW_LINES = 12, PAGE_BODY_LINES = 14, PAGES_FETCH_BATCH = 100;
67476
+ var import_picocolors42, ESC9, HIDE_CURSOR5, SHOW_CURSOR5, CTRL_C6, KEY_UP6, KEY_DOWN6, CURSOR_HOME3, CLEAR_BELOW3, CLEAR_EOL3, PAGES_VIEW_LINES = 12, PAGE_BODY_LINES = 14, PAGES_FETCH_BATCH = 100;
67335
67477
  var init_pages_view = __esm(() => {
67336
67478
  init_trpc();
67337
67479
  init_config();
@@ -67341,7 +67483,7 @@ var init_pages_view = __esm(() => {
67341
67483
  init_analytics_view();
67342
67484
  init_layout();
67343
67485
  init_menu();
67344
- import_picocolors41 = __toESM(require_picocolors(), 1);
67486
+ import_picocolors42 = __toESM(require_picocolors(), 1);
67345
67487
  ESC9 = String.fromCharCode(27);
67346
67488
  HIDE_CURSOR5 = `${ESC9}[?25l`;
67347
67489
  SHOW_CURSOR5 = `${ESC9}[?25h`;
@@ -67454,7 +67596,7 @@ function isSetUp(cfg) {
67454
67596
  }
67455
67597
  function setupHint(cfg) {
67456
67598
  const started = Boolean(cfg.active_account?.target);
67457
- return import_picocolors42.default.yellow(started ? `incomplete · missing ${missingSetupSteps(cfg).join(" + ")}` : "not set up yet");
67599
+ return import_picocolors43.default.yellow(started ? `incomplete · missing ${missingSetupSteps(cfg).join(" + ")}` : "not set up yet");
67458
67600
  }
67459
67601
  function isStudying() {
67460
67602
  try {
@@ -67699,7 +67841,7 @@ function handleLogout(cfg) {
67699
67841
  saveConfig(cfg);
67700
67842
  log2.success("Credentials cleared.");
67701
67843
  }
67702
- var import_picocolors42, ESC10, CLEAR_SCREEN;
67844
+ var import_picocolors43, ESC10, CLEAR_SCREEN;
67703
67845
  var init_tui = __esm(() => {
67704
67846
  init_client();
67705
67847
  init_config();
@@ -67723,7 +67865,7 @@ var init_tui = __esm(() => {
67723
67865
  init_menu();
67724
67866
  init_pages_view();
67725
67867
  init_prompts();
67726
- import_picocolors42 = __toESM(require_picocolors(), 1);
67868
+ import_picocolors43 = __toESM(require_picocolors(), 1);
67727
67869
  ESC10 = String.fromCharCode(27);
67728
67870
  CLEAR_SCREEN = `${ESC10}[2J${ESC10}[H`;
67729
67871
  });
@@ -68826,11 +68968,20 @@ async function execute() {
68826
68968
  try {
68827
68969
  await program.parseAsync(process.argv);
68828
68970
  } catch (err) {
68971
+ printFatalError2(err);
68972
+ logFatalError(err);
68829
68973
  if (telemetry)
68830
68974
  await finishTelemetry(() => telemetry.fail(err));
68831
- throw err;
68975
+ process.exit(1);
68832
68976
  }
68833
68977
  }
68978
+ function logFatalError(err) {
68979
+ try {
68980
+ const message = err instanceof Error ? `${err.name}: ${err.message}` : String(err);
68981
+ const diagnostics = fatalErrorDiagnostics2(err);
68982
+ logger.error("cli", diagnostics ? `${message} (${diagnostics})` : message);
68983
+ } catch {}
68984
+ }
68834
68985
  function processCommandTelemetry() {
68835
68986
  try {
68836
68987
  const settings = loadTelemetrySettings();
@@ -68879,8 +69030,63 @@ var init_cli = __esm(() => {
68879
69030
  init_skill_update_check();
68880
69031
  init_update_check();
68881
69032
  init_errors();
69033
+ init_fatal_error();
68882
69034
  });
68883
69035
 
69036
+ // src/cli/fatal-error.ts
69037
+ init_command_error();
69038
+ var import_picocolors = __toESM(require_picocolors(), 1);
69039
+ var REQUEST_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9:._-]{0,127}$/;
69040
+ function read(value, key) {
69041
+ if (typeof value !== "object" && typeof value !== "function" || value === null) {
69042
+ return;
69043
+ }
69044
+ try {
69045
+ return Reflect.get(value, key);
69046
+ } catch {
69047
+ return;
69048
+ }
69049
+ }
69050
+ function tokenOf(data, key) {
69051
+ const value = read(data, key);
69052
+ if (typeof value !== "string")
69053
+ return;
69054
+ const trimmed = value.trim();
69055
+ return REQUEST_ID_PATTERN.test(trimmed) ? trimmed : undefined;
69056
+ }
69057
+ function statusOf(data) {
69058
+ const value = read(data, "httpStatus");
69059
+ return typeof value === "number" && Number.isInteger(value) && value >= 100 && value <= 599 ? value : undefined;
69060
+ }
69061
+ function fatalErrorDiagnostics(err) {
69062
+ const data = read(err, "data");
69063
+ if (!data)
69064
+ return;
69065
+ const code = tokenOf(data, "code");
69066
+ const path = tokenOf(data, "path");
69067
+ const status = statusOf(data);
69068
+ const requestId = tokenOf(data, "requestId");
69069
+ const parts = [
69070
+ code && `code=${code}`,
69071
+ path && `path=${path}`,
69072
+ status && `status=${status}`,
69073
+ requestId && `request_id=${requestId}`
69074
+ ].filter(Boolean);
69075
+ return parts.length > 0 ? parts.join(" ") : undefined;
69076
+ }
69077
+ function printFatalError(err) {
69078
+ if (isCommandError(err)) {
69079
+ console.error(import_picocolors.default.red(err.message));
69080
+ for (const line of err.details)
69081
+ console.error(import_picocolors.default.dim(line));
69082
+ return;
69083
+ }
69084
+ console.error(read(err, "message") ?? err);
69085
+ const diagnostics = fatalErrorDiagnostics(err);
69086
+ if (diagnostics)
69087
+ console.error(diagnostics);
69088
+ }
69089
+
68884
69090
  // src/statusline/argv.ts
68885
69091
  var RENDER_ARGV = ["knowledge", "statusline", "render"];
68886
69092
  function isStatuslineRenderArgv(argv) {
@@ -68899,17 +69105,8 @@ async function main() {
68899
69105
  await execute();
68900
69106
  }
68901
69107
  main().catch((err) => {
68902
- console.error(err.message ?? err);
68903
- const data = err?.data;
68904
- if (data && (data.code || data.path || data.httpStatus)) {
68905
- const parts = [
68906
- data.code && `code=${data.code}`,
68907
- data.path && `path=${data.path}`,
68908
- data.httpStatus && `status=${data.httpStatus}`
68909
- ].filter(Boolean);
68910
- console.error(parts.join(" "));
68911
- }
69108
+ printFatalError(err);
68912
69109
  process.exit(1);
68913
69110
  });
68914
69111
 
68915
- //# debugId=0891ed1f-f060-16b6-6475-6e2164756e21
69112
+ //# debugId=152ccbaa-13cc-83fc-6475-6e2164756e21
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dosu/cli",
3
- "version": "0.66.1",
3
+ "version": "0.67.1",
4
4
  "type": "module",
5
5
  "description": "Dosu CLI - Set up Dosu MCP for AI tools and work with Dosu Libraries, docs, and reviews",
6
6
  "license": "MIT",