@dosu/cli 0.13.2 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/bin/dosu.js +175 -55
  2. package/package.json +3 -2
package/bin/dosu.js CHANGED
@@ -4341,7 +4341,7 @@ function getSupabaseAnonKey() {
4341
4341
  function getVersionString() {
4342
4342
  return `v${VERSION}`;
4343
4343
  }
4344
- var VERSION = "0.13.2";
4344
+ var VERSION = "0.14.0";
4345
4345
 
4346
4346
  // src/debug/logger.ts
4347
4347
  import {
@@ -4353,6 +4353,17 @@ import {
4353
4353
  writeFileSync as writeFileSync2
4354
4354
  } from "node:fs";
4355
4355
  import { join as join2 } from "node:path";
4356
+ function redactSecrets(message) {
4357
+ let redacted = message;
4358
+ for (const key of SECRET_QUERY_KEYS) {
4359
+ const queryPattern = new RegExp(`([?&]${key}=)[^\\s&#]+`, "gi");
4360
+ redacted = redacted.replace(queryPattern, `$1${REDACTED}`);
4361
+ }
4362
+ redacted = redacted.replace(/(\b(?:access_token|refresh_token|api_key|apikey|id_token|token)\b\s*[:=]\s*)("[^"]+"|'[^']+'|[^\s,]+)/gi, `$1${REDACTED}`);
4363
+ redacted = redacted.replace(/(X-Dosu-API-Key:\s*)[^\s,]+/gi, `$1${REDACTED}`);
4364
+ redacted = redacted.replace(/(Authorization:\s*Bearer\s+)[^\s,]+/gi, `$1${REDACTED}`);
4365
+ return redacted;
4366
+ }
4356
4367
  function ensureInit() {
4357
4368
  if (!initialized) {
4358
4369
  initLogger({});
@@ -4409,7 +4420,8 @@ function initLogger(opts) {
4409
4420
  function writeEntry(level, mod, message) {
4410
4421
  ensureInit();
4411
4422
  const timestamp = new Date().toISOString();
4412
- const line = `[${timestamp}] [${level}] [${mod}] ${message}
4423
+ const safeMessage = redactSecrets(message);
4424
+ const line = `[${timestamp}] [${level}] [${mod}] ${safeMessage}
4413
4425
  `;
4414
4426
  try {
4415
4427
  appendFileSync(resolveLogPath(), line, { mode: 384 });
@@ -4424,10 +4436,19 @@ function writeEntry(level, mod, message) {
4424
4436
  console.error(colorize(line.trimEnd()));
4425
4437
  }
4426
4438
  }
4427
- var import_picocolors, MAX_LOG_SIZE = 1048576, TRUNCATE_KEEP = 524288, LOG_FILENAME = "debug.log", initialized = false, debugToConsole = false, logFilePath = null, logger;
4439
+ 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, logger;
4428
4440
  var init_logger = __esm(() => {
4429
4441
  init_config();
4430
4442
  import_picocolors = __toESM(require_picocolors(), 1);
4443
+ SECRET_QUERY_KEYS = [
4444
+ "access_token",
4445
+ "refresh_token",
4446
+ "api_key",
4447
+ "apikey",
4448
+ "token",
4449
+ "id_token",
4450
+ "code"
4451
+ ];
4431
4452
  logger = {
4432
4453
  init: initLogger,
4433
4454
  getLogPath() {
@@ -6635,7 +6656,8 @@ var init_skill = __esm(() => {
6635
6656
  });
6636
6657
 
6637
6658
  // src/mcp/config-helpers.ts
6638
- import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "node:fs";
6659
+ import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync5 } from "node:fs";
6660
+ import { createRequire as createRequire2 } from "node:module";
6639
6661
  import { dirname } from "node:path";
6640
6662
  function mcpURL(deploymentID) {
6641
6663
  return `${getBackendURL()}/v1/mcp/deployments/${deploymentID}`;
@@ -6644,6 +6666,9 @@ function mcpBaseURL() {
6644
6666
  return `${getBackendURL()}/v1/mcp`;
6645
6667
  }
6646
6668
  function mcpHeaders(apiKey) {
6669
+ if (!apiKey) {
6670
+ throw new Error("API key is required. Run 'dosu setup' to create one.");
6671
+ }
6647
6672
  return { "X-Dosu-API-Key": apiKey };
6648
6673
  }
6649
6674
  function loadJSONConfig(path) {
@@ -6708,11 +6733,14 @@ function stripJSONComments(data) {
6708
6733
  return result.join("");
6709
6734
  }
6710
6735
  function saveJSONConfig(path, cfg) {
6736
+ writeSecureFile(path, JSON.stringify(cfg, null, 2));
6737
+ }
6738
+ function writeSecureFile(path, content) {
6711
6739
  const dir = dirname(path);
6712
6740
  if (!existsSync4(dir)) {
6713
- mkdirSync4(dir, { recursive: true });
6741
+ mkdirSync4(dir, { recursive: true, mode: 448 });
6714
6742
  }
6715
- writeFileSync4(path, JSON.stringify(cfg, null, 2));
6743
+ writeFileAtomic.sync(path, content, { mode: 384, chown: false });
6716
6744
  }
6717
6745
  function isJSONKeyConfigured(configPath, topLevelKey) {
6718
6746
  const cfg = loadJSONConfig(configPath);
@@ -6744,7 +6772,11 @@ function removeJSONServer(configPath, topKey) {
6744
6772
  }
6745
6773
  saveJSONConfig(configPath, jsonCfg);
6746
6774
  }
6747
- var init_config_helpers = () => {};
6775
+ var require2, writeFileAtomic;
6776
+ var init_config_helpers = __esm(() => {
6777
+ require2 = createRequire2(import.meta.url);
6778
+ writeFileAtomic = require2("write-file-atomic");
6779
+ });
6748
6780
 
6749
6781
  // src/mcp/detect.ts
6750
6782
  import { existsSync as existsSync5 } from "node:fs";
@@ -6938,8 +6970,8 @@ var init_cline_cli = __esm(() => {
6938
6970
  });
6939
6971
 
6940
6972
  // src/mcp/providers/codex.ts
6941
- import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "node:fs";
6942
- import { dirname as dirname2, join as join9 } from "node:path";
6973
+ import { existsSync as existsSync6, readFileSync as readFileSync6 } from "node:fs";
6974
+ import { join as join9 } from "node:path";
6943
6975
  function codexHome() {
6944
6976
  return process.env.CODEX_HOME ?? expandHome("~/.codex");
6945
6977
  }
@@ -6954,10 +6986,7 @@ function readTOML(path) {
6954
6986
  return readFileSync6(path, "utf-8");
6955
6987
  }
6956
6988
  function writeTOML(path, content) {
6957
- const dir = dirname2(path);
6958
- if (!existsSync6(dir))
6959
- mkdirSync5(dir, { recursive: true });
6960
- writeFileSync5(path, content);
6989
+ writeSecureFile(path, content);
6961
6990
  }
6962
6991
  function mcpEndpoint(cfg) {
6963
6992
  if (cfg.mode === MODE_OSS)
@@ -7139,17 +7168,29 @@ function mcpEndpoint3(cfg) {
7139
7168
  throw new Error("deployment ID is required");
7140
7169
  return mcpURL(cfg.deployment_id);
7141
7170
  }
7171
+ function maskSecret(secret) {
7172
+ if (secret.length <= 8)
7173
+ return "[hidden]";
7174
+ const visibleChars = Math.min(4, Math.floor(secret.length / 4));
7175
+ return `${secret.slice(0, visibleChars)}...${secret.slice(-visibleChars)}`;
7176
+ }
7142
7177
  var ManualProvider = () => ({
7143
7178
  name: () => "Manual Configuration",
7144
7179
  id: () => "manual",
7145
7180
  supportsLocal: () => false,
7146
- install(cfg) {
7181
+ install(cfg, _global, opts = {}) {
7147
7182
  const url = mcpEndpoint3(cfg);
7183
+ const apiKey = mcpHeaders(cfg.api_key)["X-Dosu-API-Key"];
7184
+ const headerValue = opts.showSecret ? apiKey : maskSecret(apiKey);
7148
7185
  console.log("Use these details to configure the Dosu MCP server in your client:");
7149
7186
  console.log();
7150
7187
  console.log(` Transport: HTTP`);
7151
7188
  console.log(` Endpoint: ${url}`);
7152
- console.log(` Header: X-Dosu-API-Key: ${cfg.api_key}`);
7189
+ console.log(` Header: X-Dosu-API-Key: ${headerValue}`);
7190
+ if (!opts.showSecret) {
7191
+ console.log();
7192
+ console.log(" Secret hidden. Re-run with --show-secret to print the full API key.");
7193
+ }
7153
7194
  console.log();
7154
7195
  },
7155
7196
  remove() {
@@ -9686,7 +9727,7 @@ async function startCallbackServer() {
9686
9727
  const url = new URL(req.url ?? "/", `http://localhost`);
9687
9728
  const cookieLen = (req.headers.cookie ?? "").length;
9688
9729
  const ua = req.headers["user-agent"] ?? "none";
9689
- logger.debug("auth.server", `Request: ${req.method} ${req.url} cookie-len=${cookieLen} ua=${ua} has-token=${url.searchParams.has("access_token")}`);
9730
+ logger.debug("auth.server", `Request: ${req.method} ${url.pathname} cookie-len=${cookieLen} ua=${ua} has-token=${url.searchParams.has("access_token")}`);
9690
9731
  if (url.pathname !== "/callback") {
9691
9732
  logger.debug("auth.server", `404: ${url.pathname}`);
9692
9733
  res.writeHead(404, { "Content-Type": "text/plain" });
@@ -9824,7 +9865,7 @@ var exports_flow = {};
9824
9865
  __export(exports_flow, {
9825
9866
  startOAuthFlow: () => startOAuthFlow
9826
9867
  });
9827
- async function startOAuthFlow(signal, path2 = "/cli/auth", params = {}) {
9868
+ async function startOAuthFlow(signal, path2 = "/cli/auth", params = {}, opts = {}) {
9828
9869
  const { server, tokenPromise } = await startCallbackServer();
9829
9870
  let timeoutId;
9830
9871
  try {
@@ -9832,9 +9873,12 @@ async function startOAuthFlow(signal, path2 = "/cli/auth", params = {}) {
9832
9873
  logger.debug("auth.flow", `Callback URL: ${callbackURL}`);
9833
9874
  const authURL = buildAuthURL(callbackURL, path2, params);
9834
9875
  logger.info("auth.flow", `Auth URL: ${authURL}`);
9835
- const open2 = await Promise.resolve().then(() => (init_open(), exports_open));
9836
- await open2.default(authURL);
9837
- logger.info("auth.flow", "Browser open command executed");
9876
+ opts.onAuthURL?.(authURL);
9877
+ if (opts.openBrowser !== false) {
9878
+ const open2 = await Promise.resolve().then(() => (init_open(), exports_open));
9879
+ await open2.default(authURL);
9880
+ logger.info("auth.flow", "Browser open command executed");
9881
+ }
9838
9882
  const timeout = new Promise((_3, reject) => {
9839
9883
  timeoutId = setTimeout(() => {
9840
9884
  logger.warn("auth.flow", "Authentication timed out (8min)");
@@ -9894,7 +9938,7 @@ async function runSetup(opts = {}) {
9894
9938
  cfg.mode = undefined;
9895
9939
  saveConfig(cfg);
9896
9940
  }
9897
- const authedCfg = await stepAuthenticate(cfg, onboardingRunID);
9941
+ const authedCfg = await stepAuthenticate(cfg, onboardingRunID, opts);
9898
9942
  if (!authedCfg)
9899
9943
  return;
9900
9944
  cfg = authedCfg;
@@ -9944,7 +9988,8 @@ async function runSetup(opts = {}) {
9944
9988
  cfg.api_key = apiKey;
9945
9989
  saveConfig(cfg);
9946
9990
  const choices = await stepOneShotConfirm({
9947
- includeGitHub: cloudSetupContext?.kind === "onboarding"
9991
+ includeGitHub: cloudSetupContext?.kind === "onboarding",
9992
+ setupOptions: opts
9948
9993
  });
9949
9994
  if (!choices) {
9950
9995
  await trackCliOnboardingEvent(cfg, onboardingRunID, "cli_onboarding_cancelled", {
@@ -9962,7 +10007,7 @@ async function runSetup(opts = {}) {
9962
10007
  let skillCompleted = false;
9963
10008
  let docsImported = false;
9964
10009
  if (choices.configureMcp) {
9965
- const configured = await stepConfigureMcpTools(cfg);
10010
+ const configured = await stepConfigureMcpTools(cfg, opts.toolIDs);
9966
10011
  if (configured === null) {
9967
10012
  await trackCliOnboardingEvent(cfg, onboardingRunID, "cli_onboarding_cancelled", {
9968
10013
  reason: "mcp_selection_cancelled"
@@ -10085,6 +10130,13 @@ function applyModeOverride(cfg, opts) {
10085
10130
  saveConfig(cfg);
10086
10131
  }
10087
10132
  async function stepOneShotConfirm(opts) {
10133
+ if (opts.setupOptions?.yes) {
10134
+ return {
10135
+ configureMcp: !opts.setupOptions.skipMcp,
10136
+ installSkill: !opts.setupOptions.skipSkill,
10137
+ connectGitHub: opts.includeGitHub && !opts.setupOptions.skipGitHub
10138
+ };
10139
+ }
10088
10140
  const items = [
10089
10141
  { value: "configureMcp", label: "Install Dosu MCP" },
10090
10142
  { value: "installSkill", label: "Install Dosu skill" }
@@ -10112,7 +10164,15 @@ async function stepOneShotConfirm(opts) {
10112
10164
  connectGitHub: chosen.has("connectGitHub")
10113
10165
  };
10114
10166
  }
10115
- async function stepConfigureMcpTools(cfg) {
10167
+ async function stepConfigureMcpTools(cfg, requestedToolIDs) {
10168
+ if (requestedToolIDs && requestedToolIDs.length > 0) {
10169
+ const selection2 = selectRequestedTools(requestedToolIDs);
10170
+ if (!selection2)
10171
+ return null;
10172
+ const results2 = stepConfigureTools(cfg, selection2);
10173
+ stepShowSummary(results2);
10174
+ return results2;
10175
+ }
10116
10176
  const detected = stepDetectTools();
10117
10177
  if (detected.length === 0) {
10118
10178
  M2.warn(`No supported AI agents detected on your system.
@@ -10144,7 +10204,7 @@ async function runInstallSkill() {
10144
10204
  return false;
10145
10205
  }
10146
10206
  }
10147
- async function stepAuthenticate(existingCfg, onboardingRunID) {
10207
+ async function stepAuthenticate(existingCfg, onboardingRunID, opts = {}) {
10148
10208
  logger.info("setup", "Step: authenticate");
10149
10209
  const cfg = existingCfg ?? loadConfig();
10150
10210
  if (cfg.access_token) {
@@ -10174,26 +10234,36 @@ async function stepAuthenticate(existingCfg, onboardingRunID) {
10174
10234
  s.stop("Session verification failed");
10175
10235
  }
10176
10236
  }
10177
- const shouldLogin = await ye({ message: "Open browser to log in?" });
10178
- if (pD(shouldLogin) || !shouldLogin) {
10179
- if (onboardingRunID) {
10180
- await trackCliOnboardingPreAuthEvent(onboardingRunID, "cli_onboarding_auth_cancelled", {
10181
- reason: pD(shouldLogin) ? "prompt_cancelled" : "login_declined"
10182
- });
10237
+ if (!opts.yes && opts.openBrowser !== false) {
10238
+ const shouldLogin = await ye({ message: "Open browser to log in?" });
10239
+ if (pD(shouldLogin) || !shouldLogin) {
10240
+ if (onboardingRunID) {
10241
+ await trackCliOnboardingPreAuthEvent(onboardingRunID, "cli_onboarding_auth_cancelled", {
10242
+ reason: pD(shouldLogin) ? "prompt_cancelled" : "login_declined"
10243
+ });
10244
+ }
10245
+ return null;
10183
10246
  }
10184
- return null;
10185
10247
  }
10186
10248
  if (onboardingRunID) {
10187
10249
  await trackCliOnboardingPreAuthEvent(onboardingRunID, "cli_onboarding_auth_started");
10188
10250
  }
10189
- return await openBrowserForSetup(cfg, onboardingRunID);
10251
+ return await openBrowserForSetup(cfg, onboardingRunID, opts);
10190
10252
  }
10191
- async function openBrowserForSetup(cfg, onboardingRunID) {
10253
+ async function openBrowserForSetup(cfg, onboardingRunID, opts = {}) {
10192
10254
  try {
10193
10255
  const { startOAuthFlow: startOAuthFlow2 } = await Promise.resolve().then(() => (init_flow(), exports_flow));
10194
10256
  const s = Y2();
10195
10257
  s.start("Waiting for authentication...");
10196
- const token = await startOAuthFlow2(undefined, "/cli/auth", onboardingRunID ? { onboarding_run_id: onboardingRunID } : {});
10258
+ const token = await startOAuthFlow2(undefined, "/cli/auth", onboardingRunID ? { onboarding_run_id: onboardingRunID } : {}, {
10259
+ openBrowser: opts.openBrowser !== false,
10260
+ onAuthURL: opts.openBrowser === false ? (url) => {
10261
+ M2.info(`Open this URL to authenticate:
10262
+ ${url}
10263
+
10264
+ Waiting for login to complete...`);
10265
+ } : undefined
10266
+ });
10197
10267
  s.stop("Authenticated");
10198
10268
  logger.info("setup", "Browser auth completed");
10199
10269
  cfg.access_token = token.access_token;
@@ -10302,10 +10372,10 @@ async function resolveDeployment(apiClient, cfg, opts) {
10302
10372
  }
10303
10373
  return true;
10304
10374
  }
10305
- const org = await stepSelectOrg(apiClient);
10375
+ const org = await stepSelectOrg(apiClient, opts.yes === true);
10306
10376
  if (!org)
10307
10377
  return false;
10308
- const d3 = await stepSelectDeployment(apiClient, org);
10378
+ const d3 = await stepSelectDeployment(apiClient, org, opts.yes === true);
10309
10379
  if (!d3)
10310
10380
  return false;
10311
10381
  cfg.mode = undefined;
@@ -10319,7 +10389,7 @@ async function fetchDeployments(apiClient) {
10319
10389
  return [];
10320
10390
  }
10321
10391
  }
10322
- async function stepSelectOrg(apiClient) {
10392
+ async function stepSelectOrg(apiClient, noPrompt = false) {
10323
10393
  try {
10324
10394
  const orgs = await apiClient.getOrgs();
10325
10395
  if (orgs.length === 0) {
@@ -10332,6 +10402,10 @@ async function stepSelectOrg(apiClient) {
10332
10402
  ${dim(orgs[0].name)}`);
10333
10403
  return orgs[0];
10334
10404
  }
10405
+ if (noPrompt) {
10406
+ M2.error("Multiple organizations found. Run `dosu deployments list --json`, choose a deployment, then re-run setup with `--deployment <id>`.");
10407
+ return null;
10408
+ }
10335
10409
  const selected = await ve({
10336
10410
  message: "Select an organization",
10337
10411
  options: orgs.map((o2) => ({ label: o2.name, value: o2.org_id }))
@@ -10369,7 +10443,7 @@ ${dim(d3.name)}`);
10369
10443
  return null;
10370
10444
  }
10371
10445
  }
10372
- async function stepSelectDeployment(apiClient, org) {
10446
+ async function stepSelectDeployment(apiClient, org, noPrompt = false) {
10373
10447
  try {
10374
10448
  const allDeployments = await apiClient.getDeployments();
10375
10449
  const deployments = allDeployments.filter((d4) => d4.org_id === org.org_id);
@@ -10383,6 +10457,10 @@ async function stepSelectDeployment(apiClient, org) {
10383
10457
  ${dim(deployments[0].name)}`);
10384
10458
  return deployments[0];
10385
10459
  }
10460
+ if (noPrompt) {
10461
+ M2.error("Multiple MCPs found. Run `dosu deployments list --json`, choose one, then re-run setup with `--deployment <id>`.");
10462
+ return null;
10463
+ }
10386
10464
  const selected = await ve({
10387
10465
  message: "Select an MCP",
10388
10466
  options: deployments.map((d4) => ({ label: d4.name, value: d4.deployment_id }))
@@ -10430,6 +10508,24 @@ function isStdioOnly(p2) {
10430
10508
  function stepDetectTools() {
10431
10509
  return allSetupProviders().filter((p2) => p2.isInstalled() && !isStdioOnly(p2));
10432
10510
  }
10511
+ function selectRequestedTools(requestedToolIDs) {
10512
+ const normalized = [...new Set(requestedToolIDs.map((id) => id.toLowerCase()))];
10513
+ const providers = allSetupProviders();
10514
+ const selected = [];
10515
+ for (const id of normalized) {
10516
+ const provider = providers.find((p2) => p2.id() === id);
10517
+ if (!provider) {
10518
+ M2.error(`Unknown AI tool '${id}'. Run 'dosu mcp list' to see available tools.`);
10519
+ return null;
10520
+ }
10521
+ if (isStdioOnly(provider)) {
10522
+ M2.error(`${provider.name()} is not supported by setup. Use 'dosu mcp add ${id}'.`);
10523
+ return null;
10524
+ }
10525
+ selected.push(provider);
10526
+ }
10527
+ return { toInstall: selected, toRemove: [], skipped: [] };
10528
+ }
10433
10529
  async function stepSelectTools(detected) {
10434
10530
  const configuredMap = new Map;
10435
10531
  for (const p2 of detected) {
@@ -10545,8 +10641,8 @@ var init_flow2 = __esm(() => {
10545
10641
  });
10546
10642
 
10547
10643
  // src/commands/insights.ts
10548
- import { existsSync as existsSync9, mkdirSync as mkdirSync6, readdirSync, unlinkSync, writeFileSync as writeFileSync6 } from "node:fs";
10549
- import { dirname as dirname3, join as join19 } from "node:path";
10644
+ import { existsSync as existsSync9, mkdirSync as mkdirSync5, readdirSync, unlinkSync, writeFileSync as writeFileSync4 } from "node:fs";
10645
+ import { dirname as dirname2, join as join19 } from "node:path";
10550
10646
  function shuffled(arr) {
10551
10647
  const copy2 = [...arr];
10552
10648
  for (let i = copy2.length - 1;i > 0; i--) {
@@ -10744,10 +10840,10 @@ function defaultRunner(cfg) {
10744
10840
  build: buildInsights,
10745
10841
  render: renderHTML,
10746
10842
  writeFile: (path2, content) => {
10747
- const dir = dirname3(path2);
10843
+ const dir = dirname2(path2);
10748
10844
  if (!existsSync9(dir))
10749
- mkdirSync6(dir, { recursive: true, mode: 448 });
10750
- writeFileSync6(path2, content, { mode: 384 });
10845
+ mkdirSync5(dir, { recursive: true, mode: 448 });
10846
+ writeFileSync4(path2, content, { mode: 384 });
10751
10847
  },
10752
10848
  prune: pruneOldReports,
10753
10849
  openInBrowser: async (path2) => {
@@ -12378,7 +12474,7 @@ init_skill_update_check();
12378
12474
  init_config();
12379
12475
  init_logger();
12380
12476
  var import_picocolors24 = __toESM(require_picocolors(), 1);
12381
- import { existsSync as existsSync10, mkdirSync as mkdirSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync7 } from "node:fs";
12477
+ import { existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "node:fs";
12382
12478
  import { join as join20 } from "node:path";
12383
12479
  var CACHE_FILENAME2 = "update-check.json";
12384
12480
  var CHECK_INTERVAL_MS2 = 24 * 60 * 60 * 1000;
@@ -12422,9 +12518,9 @@ function writeCache(cache) {
12422
12518
  try {
12423
12519
  const dir = getConfigDir();
12424
12520
  if (!existsSync10(dir)) {
12425
- mkdirSync7(dir, { recursive: true, mode: 448 });
12521
+ mkdirSync6(dir, { recursive: true, mode: 448 });
12426
12522
  }
12427
- writeFileSync7(getCachePath2(), JSON.stringify(cache), { mode: 384 });
12523
+ writeFileSync5(getCachePath2(), JSON.stringify(cache), { mode: 384 });
12428
12524
  } catch {}
12429
12525
  }
12430
12526
  async function fetchLatestVersion() {
@@ -12487,19 +12583,27 @@ function createProgram() {
12487
12583
  const { runTUI: runTUI2 } = await Promise.resolve().then(() => (init_tui(), exports_tui));
12488
12584
  await runTUI2();
12489
12585
  });
12490
- program2.command("login").description("Authenticate with Dosu via OAuth").action(async () => {
12586
+ program2.command("login").description("Authenticate with Dosu via OAuth").option("--no-open", "Print the login URL instead of opening a browser").action(async (opts) => {
12491
12587
  const cfg = loadConfig();
12492
12588
  if (isAuthenticated(cfg) && !isTokenExpired(cfg)) {
12493
12589
  console.log("You are already logged in.");
12494
12590
  console.log("Run 'dosu logout' first to re-authenticate.");
12495
12591
  return;
12496
12592
  }
12497
- console.log("Opening browser for authentication...");
12593
+ console.log(opts.open === false ? "Starting authentication..." : "Opening browser for authentication...");
12498
12594
  const { startOAuthFlow: startOAuthFlow2 } = await Promise.resolve().then(() => (init_flow(), exports_flow));
12499
12595
  const { OAuthCallbackError: OAuthCallbackError2 } = await Promise.resolve().then(() => (init_errors(), exports_errors));
12500
12596
  let token;
12501
12597
  try {
12502
- token = await startOAuthFlow2();
12598
+ token = await startOAuthFlow2(undefined, "/cli/auth", {}, {
12599
+ openBrowser: opts.open !== false,
12600
+ onAuthURL: opts.open === false ? (url) => {
12601
+ console.log("Open this URL to authenticate:");
12602
+ console.log(url);
12603
+ console.log(`
12604
+ Waiting for login to complete...`);
12605
+ } : undefined
12606
+ });
12503
12607
  } catch (err) {
12504
12608
  if (err instanceof OAuthCallbackError2) {
12505
12609
  console.error(err.userMessage);
@@ -12557,7 +12661,7 @@ function createProgram() {
12557
12661
  }
12558
12662
  });
12559
12663
  const mcp = program2.command("mcp").description("Manage MCP server integrations");
12560
- mcp.command("add <agent>").description("Add Dosu MCP to an AI tool").option("-g, --global", "Add globally (all projects) instead of project-local", false).action((toolId, opts) => {
12664
+ 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) => {
12561
12665
  let provider;
12562
12666
  try {
12563
12667
  provider = getProvider(toolId.toLowerCase());
@@ -12574,8 +12678,11 @@ function createProgram() {
12574
12678
  if (cfg.mode !== MODE_OSS && !cfg.deployment_id) {
12575
12679
  throw new Error("no MCP selected. Run 'dosu' to open the TUI and select an MCP");
12576
12680
  }
12681
+ if (!cfg.api_key) {
12682
+ throw new Error("no API key available. Run 'dosu setup' to create one");
12683
+ }
12577
12684
  if (provider.id() === "manual") {
12578
- provider.install(cfg, false);
12685
+ provider.install(cfg, false, { showSecret: opts.showSecret });
12579
12686
  return;
12580
12687
  }
12581
12688
  let global = opts.global;
@@ -12586,7 +12693,7 @@ function createProgram() {
12586
12693
  }
12587
12694
  const scope = global ? "global (all projects)" : "project-local";
12588
12695
  console.log(`Adding Dosu MCP to ${provider.name()} (${scope})...`);
12589
- provider.install(cfg, global);
12696
+ provider.install(cfg, global, { showSecret: opts.showSecret });
12590
12697
  console.log(`
12591
12698
  ✓ Successfully added Dosu MCP to ${provider.name()}!`);
12592
12699
  if (global) {
@@ -12626,7 +12733,7 @@ Use 'dosu mcp add <agent>' to add Dosu MCP to a tool.`);
12626
12733
  program2.addCommand(tagsCommand());
12627
12734
  program2.addCommand(threadsCommand());
12628
12735
  program2.addCommand(skillCommand());
12629
- program2.command("setup").description("Set up Dosu MCP for your AI tools").option("--deployment <id>", "Skip to tool configuration for a specific MCP").option("--mode <mode>", "Force OSS or Cloud mode, skipping the interactive prompt (oss|cloud)").action(async (opts) => {
12736
+ program2.command("setup").description("Set up Dosu MCP for your AI tools").option("--deployment <id>", "Skip to tool configuration for a specific MCP").option("--mode <mode>", "Force OSS or Cloud mode, skipping the interactive prompt (oss|cloud)").option("--agent", "Run an agent-assisted setup with safe defaults", false).option("-y, --yes", "Accept setup defaults and skip confirmation prompts", false).option("--no-open", "Print browser login URLs instead of opening them").option("--tool <agent>", "Configure a specific AI tool; may be repeated", collectValues, []).option("--skip-mcp", "Do not configure AI tool MCP entries", false).option("--skip-skill", "Do not install the Dosu agent skill", false).option("--skip-github", "Do not run GitHub repository/doc onboarding", false).action(async (opts) => {
12630
12737
  const { runSetup: runSetup2 } = await Promise.resolve().then(() => (init_flow2(), exports_flow2));
12631
12738
  let mode;
12632
12739
  if (opts.mode !== undefined) {
@@ -12636,7 +12743,16 @@ Use 'dosu mcp add <agent>' to add Dosu MCP to a tool.`);
12636
12743
  }
12637
12744
  mode = normalized;
12638
12745
  }
12639
- await runSetup2({ deploymentID: opts.deployment, mode });
12746
+ await runSetup2({
12747
+ deploymentID: opts.deployment,
12748
+ mode,
12749
+ yes: opts.agent === true || opts.yes === true,
12750
+ openBrowser: opts.agent === true ? false : opts.open !== false,
12751
+ toolIDs: opts.tool ?? [],
12752
+ skipMcp: opts.skipMcp === true,
12753
+ skipSkill: opts.skipSkill === true,
12754
+ skipGitHub: opts.agent === true || opts.skipGithub === true
12755
+ });
12640
12756
  });
12641
12757
  program2.command("logs").description("View or manage debug logs").option("-t, --tail [n]", "Show last N lines (default: 50)").option("--clear", "Delete the log file").action((opts) => {
12642
12758
  const logPath = logger.getLogPath();
@@ -12666,6 +12782,10 @@ Use 'dosu mcp add <agent>' to add Dosu MCP to a tool.`);
12666
12782
  });
12667
12783
  return program2;
12668
12784
  }
12785
+ function collectValues(value, previous) {
12786
+ previous.push(value);
12787
+ return previous;
12788
+ }
12669
12789
  async function execute() {
12670
12790
  const program2 = createProgram();
12671
12791
  await program2.parseAsync(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dosu/cli",
3
- "version": "0.13.2",
3
+ "version": "0.14.0",
4
4
  "type": "module",
5
5
  "description": "Dosu CLI - Manage MCP servers for AI tools",
6
6
  "license": "MIT",
@@ -52,6 +52,7 @@
52
52
  "commander": "^13.1.0",
53
53
  "open": "^10.1.0",
54
54
  "picocolors": "^1.1.1",
55
- "superjson": "^2.2.6"
55
+ "superjson": "^2.2.6",
56
+ "write-file-atomic": "^5.0.1"
56
57
  }
57
58
  }