@dosu/cli 0.62.0 → 0.63.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/bin/dosu.js +327 -218
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -182,7 +182,8 @@ dosu knowledge incognito enable|disable [claude|cursor|codex] # the /dosu-incog
|
|
|
182
182
|
```
|
|
183
183
|
|
|
184
184
|
The status line shows one of `📚 Dosu studying…`, `👻 Dosu incognito`, `⚪ Dosu paused`,
|
|
185
|
-
`⚪ Dosu not studying this
|
|
185
|
+
`⚪ Dosu not studying this repo`, or `⚪ Dosu off`. Only sessions inside a git repo are studied;
|
|
186
|
+
pick which repos under `dosu` → settings → study scope. Neither setup nor `enable` replaces a status
|
|
186
187
|
line you already have; they print the one-liner to add to your own script instead.
|
|
187
188
|
|
|
188
189
|
Running `/dosu-incognito` inside a session marks that session's transcript so studying skips it
|
package/bin/dosu.js
CHANGED
|
@@ -1083,7 +1083,7 @@ function isNpxInvocation(channel = INSTALL_CHANNEL, env = process.env) {
|
|
|
1083
1083
|
function getVersionString() {
|
|
1084
1084
|
return `v${VERSION}`;
|
|
1085
1085
|
}
|
|
1086
|
-
var VERSION = "0.
|
|
1086
|
+
var VERSION = "0.63.0", INSTALL_CHANNEL = "npm";
|
|
1087
1087
|
|
|
1088
1088
|
// src/debug/logger.ts
|
|
1089
1089
|
import {
|
|
@@ -1472,6 +1472,59 @@ var init_agents = __esm(() => {
|
|
|
1472
1472
|
init_formats();
|
|
1473
1473
|
});
|
|
1474
1474
|
|
|
1475
|
+
// src/sessions/repo.ts
|
|
1476
|
+
import { execFileSync } from "node:child_process";
|
|
1477
|
+
function normalizeRepoRemote(remote) {
|
|
1478
|
+
const value = remote.trim();
|
|
1479
|
+
if (!value)
|
|
1480
|
+
return null;
|
|
1481
|
+
let host;
|
|
1482
|
+
let path;
|
|
1483
|
+
const scp = value.includes("://") ? null : /^(?:[^@/]+@)?([^:/]+):(.+)$/.exec(value);
|
|
1484
|
+
if (scp) {
|
|
1485
|
+
host = scp[1];
|
|
1486
|
+
path = scp[2];
|
|
1487
|
+
} else {
|
|
1488
|
+
try {
|
|
1489
|
+
const url = new URL(value);
|
|
1490
|
+
if (url.protocol === "file:")
|
|
1491
|
+
return null;
|
|
1492
|
+
host = url.hostname;
|
|
1493
|
+
path = url.pathname;
|
|
1494
|
+
} catch {
|
|
1495
|
+
return null;
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
path = path.replace(/^\/+|\/+$/g, "").replace(/\.git$/, "");
|
|
1499
|
+
if (!host || !path.includes("/"))
|
|
1500
|
+
return null;
|
|
1501
|
+
const lowerHost = host.toLowerCase();
|
|
1502
|
+
return `${lowerHost}/${lowerHost === "github.com" ? path.toLowerCase() : path}`;
|
|
1503
|
+
}
|
|
1504
|
+
function originRepoOfDir(dir) {
|
|
1505
|
+
const env = { ...process.env };
|
|
1506
|
+
for (const name of GIT_LOCATION_ENV)
|
|
1507
|
+
delete env[name];
|
|
1508
|
+
try {
|
|
1509
|
+
const out = execFileSync("git", ["-C", dir, "remote", "get-url", "origin"], {
|
|
1510
|
+
encoding: "utf-8",
|
|
1511
|
+
env,
|
|
1512
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
1513
|
+
timeout: 1000
|
|
1514
|
+
});
|
|
1515
|
+
return normalizeRepoRemote(out);
|
|
1516
|
+
} catch {
|
|
1517
|
+
return null;
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
function displayRepo(repo) {
|
|
1521
|
+
return repo.split("/").slice(1).join("/") || repo;
|
|
1522
|
+
}
|
|
1523
|
+
var GIT_LOCATION_ENV;
|
|
1524
|
+
var init_repo = __esm(() => {
|
|
1525
|
+
GIT_LOCATION_ENV = ["GIT_DIR", "GIT_WORK_TREE", "GIT_COMMON_DIR", "GIT_INDEX_FILE"];
|
|
1526
|
+
});
|
|
1527
|
+
|
|
1475
1528
|
// src/sessions/scan.ts
|
|
1476
1529
|
import { existsSync as existsSync6, readdirSync, statSync as statSync2 } from "node:fs";
|
|
1477
1530
|
import { createRequire as createRequire2 } from "node:module";
|
|
@@ -1919,6 +1972,9 @@ import { join as join6 } from "node:path";
|
|
|
1919
1972
|
function syncStatePath(configDir = getConfigDir()) {
|
|
1920
1973
|
return join6(configDir, STATE_FILENAME);
|
|
1921
1974
|
}
|
|
1975
|
+
function stringsOf(values) {
|
|
1976
|
+
return values.filter((v) => typeof v === "string");
|
|
1977
|
+
}
|
|
1922
1978
|
function loadSyncState(configDir = getConfigDir()) {
|
|
1923
1979
|
const empty = {
|
|
1924
1980
|
schema_version: STATE_SCHEMA_VERSION,
|
|
@@ -1954,9 +2010,8 @@ function loadSyncState(configDir = getConfigDir()) {
|
|
|
1954
2010
|
...lastRefusal ? { last_refusal: lastRefusal } : {},
|
|
1955
2011
|
...run ? { run } : {},
|
|
1956
2012
|
...raw.paused === true ? { paused: true } : {},
|
|
1957
|
-
...Array.isArray(raw.
|
|
1958
|
-
|
|
1959
|
-
} : {}
|
|
2013
|
+
...Array.isArray(raw.repo_filter) ? { repo_filter: stringsOf(raw.repo_filter) } : {},
|
|
2014
|
+
...Array.isArray(raw.project_filter) ? { project_filter: stringsOf(raw.project_filter) } : {}
|
|
1960
2015
|
};
|
|
1961
2016
|
} catch {
|
|
1962
2017
|
return empty;
|
|
@@ -1976,6 +2031,7 @@ function resetSyncState(configDir = getConfigDir()) {
|
|
|
1976
2031
|
schema_version: STATE_SCHEMA_VERSION,
|
|
1977
2032
|
watermark: null,
|
|
1978
2033
|
consecutive_failures: 0,
|
|
2034
|
+
...previous.repo_filter ? { repo_filter: previous.repo_filter } : {},
|
|
1979
2035
|
...previous.project_filter ? { project_filter: previous.project_filter } : {},
|
|
1980
2036
|
...previous.paused ? { paused: true } : {}
|
|
1981
2037
|
};
|
|
@@ -2002,17 +2058,31 @@ function isUnderDir(dir, base) {
|
|
|
2002
2058
|
const root = base.endsWith("/") ? base.slice(0, -1) : base;
|
|
2003
2059
|
return dir === root || dir.startsWith(`${root}/`);
|
|
2004
2060
|
}
|
|
2005
|
-
function
|
|
2006
|
-
if (
|
|
2007
|
-
return
|
|
2008
|
-
const
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2061
|
+
function studyRepoFilter(state, listSessions, locator) {
|
|
2062
|
+
if (state.repo_filter)
|
|
2063
|
+
return state.repo_filter;
|
|
2064
|
+
const folders = state.project_filter;
|
|
2065
|
+
if (!folders?.length)
|
|
2066
|
+
return null;
|
|
2067
|
+
const repos = new Set;
|
|
2068
|
+
for (const session of listSessions()) {
|
|
2069
|
+
const dir = locator.resolve(session);
|
|
2070
|
+
if (!dir || !folders.some((base) => isUnderDir(dir, base)))
|
|
2071
|
+
continue;
|
|
2072
|
+
const repo = locator.resolveRepo(session);
|
|
2073
|
+
if (repo)
|
|
2074
|
+
repos.add(repo);
|
|
2075
|
+
}
|
|
2076
|
+
return [...repos].sort();
|
|
2077
|
+
}
|
|
2078
|
+
function filterSessionsByRepo(sessions, filter, resolveRepo) {
|
|
2079
|
+
const kept = [];
|
|
2080
|
+
for (const session of sessions) {
|
|
2081
|
+
const repo = resolveRepo(session);
|
|
2082
|
+
if (repo !== null && (filter === null || filter.includes(repo)))
|
|
2083
|
+
kept.push({ ...session, repo });
|
|
2084
|
+
}
|
|
2085
|
+
return kept;
|
|
2016
2086
|
}
|
|
2017
2087
|
function gateSessions(sessions, watermark, now = new Date, quietPeriodMs = DEFAULT_QUIET_PERIOD_MS) {
|
|
2018
2088
|
const watermarkMs = watermark ? Date.parse(watermark) : Number.NEGATIVE_INFINITY;
|
|
@@ -2031,7 +2101,7 @@ function gateSessions(sessions, watermark, now = new Date, quietPeriodMs = DEFAU
|
|
|
2031
2101
|
}
|
|
2032
2102
|
return { ready, open };
|
|
2033
2103
|
}
|
|
2034
|
-
var STATE_FILENAME = "knowledge-sync.json", STATE_SCHEMA_VERSION = 1, DEFAULT_QUIET_PERIOD_MS, BACKOFF_BASE_MS, BACKOFF_MAX_MS, STUDIED_HISTORY_LIMIT = 500
|
|
2104
|
+
var STATE_FILENAME = "knowledge-sync.json", STATE_SCHEMA_VERSION = 1, DEFAULT_QUIET_PERIOD_MS, BACKOFF_BASE_MS, BACKOFF_MAX_MS, STUDIED_HISTORY_LIMIT = 500;
|
|
2035
2105
|
var init_watermark = __esm(() => {
|
|
2036
2106
|
init_config();
|
|
2037
2107
|
DEFAULT_QUIET_PERIOD_MS = 5 * 60 * 1000;
|
|
@@ -2067,12 +2137,18 @@ function defaultHookEnabled(agentId) {
|
|
|
2067
2137
|
return false;
|
|
2068
2138
|
}
|
|
2069
2139
|
}
|
|
2070
|
-
function cwdIsStudied(cwd,
|
|
2071
|
-
if (!filter || filter.length === 0)
|
|
2072
|
-
return true;
|
|
2140
|
+
function cwdIsStudied(cwd, state, repoOfDir) {
|
|
2073
2141
|
if (!cwd)
|
|
2074
|
-
return
|
|
2075
|
-
|
|
2142
|
+
return false;
|
|
2143
|
+
const repo = repoOfDir(cwd);
|
|
2144
|
+
if (!repo)
|
|
2145
|
+
return false;
|
|
2146
|
+
if (state.repo_filter)
|
|
2147
|
+
return state.repo_filter.includes(repo);
|
|
2148
|
+
if (state.project_filter?.length) {
|
|
2149
|
+
return state.project_filter.some((base) => isUnderDir(cwd, base));
|
|
2150
|
+
}
|
|
2151
|
+
return true;
|
|
2076
2152
|
}
|
|
2077
2153
|
function resolveStatuslineState(payload, agentId, deps = {}) {
|
|
2078
2154
|
const hookEnabled = deps.hookEnabled ?? defaultHookEnabled;
|
|
@@ -2085,7 +2161,7 @@ function resolveStatuslineState(payload, agentId, deps = {}) {
|
|
|
2085
2161
|
const state = (deps.loadState ?? loadSyncState)();
|
|
2086
2162
|
if (state.paused)
|
|
2087
2163
|
return "paused";
|
|
2088
|
-
if (!cwdIsStudied(payload.cwd, state.
|
|
2164
|
+
if (!cwdIsStudied(payload.cwd, state, deps.repoOfDir ?? originRepoOfDir))
|
|
2089
2165
|
return "not-studied";
|
|
2090
2166
|
return "on";
|
|
2091
2167
|
}
|
|
@@ -2099,13 +2175,14 @@ function renderStatusline(raw, agentId, deps = {}) {
|
|
|
2099
2175
|
var STATUSLINE_LABELS;
|
|
2100
2176
|
var init_render = __esm(() => {
|
|
2101
2177
|
init_agents();
|
|
2178
|
+
init_repo();
|
|
2102
2179
|
init_incognito();
|
|
2103
2180
|
init_watermark();
|
|
2104
2181
|
STATUSLINE_LABELS = {
|
|
2105
2182
|
on: "\uD83D\uDCDA Dosu studying…",
|
|
2106
2183
|
incognito: "\uD83D\uDC7B Dosu incognito",
|
|
2107
2184
|
paused: "⚪ Dosu paused",
|
|
2108
|
-
"not-studied": "⚪ Dosu not studying this
|
|
2185
|
+
"not-studied": "⚪ Dosu not studying this repo",
|
|
2109
2186
|
off: "⚪ Dosu off"
|
|
2110
2187
|
};
|
|
2111
2188
|
});
|
|
@@ -9863,7 +9940,7 @@ var init_default_browser_id = __esm(() => {
|
|
|
9863
9940
|
// node_modules/run-applescript/index.js
|
|
9864
9941
|
import process6 from "node:process";
|
|
9865
9942
|
import { promisify as promisify4 } from "node:util";
|
|
9866
|
-
import { execFile as execFile4, execFileSync } from "node:child_process";
|
|
9943
|
+
import { execFile as execFile4, execFileSync as execFileSync2 } from "node:child_process";
|
|
9867
9944
|
async function runAppleScript(script, { humanReadableOutput = true, signal } = {}) {
|
|
9868
9945
|
if (process6.platform !== "darwin") {
|
|
9869
9946
|
throw new Error("macOS only");
|
|
@@ -11195,8 +11272,8 @@ async function resolveDeploymentId(client, activeOrgId, id) {
|
|
|
11195
11272
|
process.exit(1);
|
|
11196
11273
|
}
|
|
11197
11274
|
function deploymentsCommand() {
|
|
11198
|
-
const cmd = new Command("deployments").description("
|
|
11199
|
-
cmd.command("list").description("List
|
|
11275
|
+
const cmd = new Command("deployments").description("Select the MCP deployment (and its Library) this CLI uses; not for Agents");
|
|
11276
|
+
cmd.command("list").description("List MCP deployments you can select").option("--json", "Output as JSON").action(async (opts) => {
|
|
11200
11277
|
const cfg = requireConfig4();
|
|
11201
11278
|
const client = createTypedClient(cfg);
|
|
11202
11279
|
const allDeployments = await listAccessibleDeployments(client, cfg.active_account?.target?.org_id);
|
|
@@ -11206,7 +11283,7 @@ function deploymentsCommand() {
|
|
|
11206
11283
|
return;
|
|
11207
11284
|
}
|
|
11208
11285
|
if (!deployments || deployments.length === 0) {
|
|
11209
|
-
console.log(import_picocolors15.default.dim("No deployments found."));
|
|
11286
|
+
console.log(import_picocolors15.default.dim("No MCP deployments found."));
|
|
11210
11287
|
return;
|
|
11211
11288
|
}
|
|
11212
11289
|
printTable(["ID", "Name", "Org", "Status"], deployments.map((d) => [
|
|
@@ -11220,7 +11297,7 @@ function deploymentsCommand() {
|
|
|
11220
11297
|
${import_picocolors15.default.dim(`Current: ${cfg.active_account?.target?.deployment_name ?? cfg.active_account?.target?.deployment_id}`)}`);
|
|
11221
11298
|
}
|
|
11222
11299
|
});
|
|
11223
|
-
cmd.command("info").description("Show
|
|
11300
|
+
cmd.command("info").description("Show the selected MCP deployment").option("--json", "Output as JSON").action(async (opts) => {
|
|
11224
11301
|
const cfg = requireConfig4();
|
|
11225
11302
|
if (!cfg.active_account?.target?.deployment_id) {
|
|
11226
11303
|
console.error(import_picocolors15.default.red("No deployment selected. Run 'dosu setup' or 'dosu deployments switch'."));
|
|
@@ -11247,7 +11324,7 @@ ${import_picocolors15.default.dim(`Current: ${cfg.active_account?.target?.deploy
|
|
|
11247
11324
|
["Created", formatDate(deployment.created_at)]
|
|
11248
11325
|
], { rawData: deployment });
|
|
11249
11326
|
});
|
|
11250
|
-
cmd.command("switch").description("
|
|
11327
|
+
cmd.command("switch").description("Select a different MCP deployment (mints a new API key for it)").argument("<id>", "MCP deployment ID (full UUID, or a unique prefix as shown by 'list')").option("--json", "Output as JSON").action(async (rawId, opts) => {
|
|
11251
11328
|
const cfg = requireConfig4();
|
|
11252
11329
|
const client = createTypedClient(cfg);
|
|
11253
11330
|
const id = await resolveDeploymentId(client, cfg.active_account?.target?.org_id, rawId);
|
|
@@ -12000,20 +12077,39 @@ function createProjectDirResolver(configDir = getConfigDir(), deps = {}) {
|
|
|
12000
12077
|
return null;
|
|
12001
12078
|
}
|
|
12002
12079
|
};
|
|
12080
|
+
const repoOfDir = deps.repoOfDir ?? originRepoOfDir;
|
|
12081
|
+
const repoByDir = new Map;
|
|
12082
|
+
const resolve = (session) => {
|
|
12083
|
+
const key = `${session.harness}/${session.id}`;
|
|
12084
|
+
const cached = entries[key];
|
|
12085
|
+
if (cached && (cached.dir !== null || cached.mtime === mtime(session.path))) {
|
|
12086
|
+
return cached.dir;
|
|
12087
|
+
}
|
|
12088
|
+
const dir = compute(session);
|
|
12089
|
+
entries[key] = { dir, mtime: mtime(session.path) };
|
|
12090
|
+
dirty = true;
|
|
12091
|
+
return dir;
|
|
12092
|
+
};
|
|
12003
12093
|
return {
|
|
12004
12094
|
cached(key) {
|
|
12005
12095
|
return entries[key]?.dir ?? null;
|
|
12006
12096
|
},
|
|
12007
|
-
resolve
|
|
12008
|
-
|
|
12009
|
-
const
|
|
12010
|
-
if (
|
|
12011
|
-
return
|
|
12012
|
-
}
|
|
12013
|
-
|
|
12014
|
-
|
|
12097
|
+
resolve,
|
|
12098
|
+
resolveRepo(session) {
|
|
12099
|
+
const dir = resolve(session);
|
|
12100
|
+
if (dir === null)
|
|
12101
|
+
return null;
|
|
12102
|
+
const entry = entries[`${session.harness}/${session.id}`];
|
|
12103
|
+
if (entry.repo !== undefined && (entry.repo !== null || entry.mtime === mtime(session.path))) {
|
|
12104
|
+
return entry.repo;
|
|
12105
|
+
}
|
|
12106
|
+
if (!repoByDir.has(dir))
|
|
12107
|
+
repoByDir.set(dir, repoOfDir(dir));
|
|
12108
|
+
const repo = repoByDir.get(dir) ?? null;
|
|
12109
|
+
entry.repo = repo;
|
|
12110
|
+
entry.mtime = mtime(session.path);
|
|
12015
12111
|
dirty = true;
|
|
12016
|
-
return
|
|
12112
|
+
return repo;
|
|
12017
12113
|
},
|
|
12018
12114
|
flush() {
|
|
12019
12115
|
if (!dirty)
|
|
@@ -12026,6 +12122,7 @@ function createProjectDirResolver(configDir = getConfigDir(), deps = {}) {
|
|
|
12026
12122
|
var CACHE_FILENAME2 = "project-dirs.json", CACHE_SCHEMA_VERSION = 1, HEAD_BYTES, HEAD_LINES = 50;
|
|
12027
12123
|
var init_project_dir = __esm(() => {
|
|
12028
12124
|
init_config();
|
|
12125
|
+
init_repo();
|
|
12029
12126
|
HEAD_BYTES = 128 * 1024;
|
|
12030
12127
|
});
|
|
12031
12128
|
|
|
@@ -13309,12 +13406,11 @@ var init_generate = __esm(() => {
|
|
|
13309
13406
|
function listSessionBacklog() {
|
|
13310
13407
|
try {
|
|
13311
13408
|
const state = loadSyncState();
|
|
13312
|
-
|
|
13313
|
-
|
|
13314
|
-
|
|
13315
|
-
|
|
13316
|
-
|
|
13317
|
-
}
|
|
13409
|
+
const scanned = scanAgentSessions({});
|
|
13410
|
+
const resolver = createProjectDirResolver();
|
|
13411
|
+
const filter = studyRepoFilter(state, () => scanned, resolver);
|
|
13412
|
+
const sessions = filterSessionsByRepo(scanned, filter, resolver.resolveRepo);
|
|
13413
|
+
resolver.flush();
|
|
13318
13414
|
const gate = gateSessions(sessions, state.watermark);
|
|
13319
13415
|
const { kept, skipped } = partitionIncognitoSessions(gate.ready);
|
|
13320
13416
|
return { queued: kept.reverse(), open: gate.open.reverse(), incognito: skipped.reverse() };
|
|
@@ -13540,19 +13636,24 @@ async function runKnowledgeSync(options = {}) {
|
|
|
13540
13636
|
since: new Date(now().getTime() - GATE_WINDOW_DAYS * 24 * 60 * 60 * 1000),
|
|
13541
13637
|
limit: GATE_WINDOW
|
|
13542
13638
|
}));
|
|
13543
|
-
|
|
13544
|
-
|
|
13545
|
-
|
|
13546
|
-
|
|
13547
|
-
|
|
13548
|
-
|
|
13549
|
-
|
|
13550
|
-
|
|
13551
|
-
|
|
13552
|
-
|
|
13553
|
-
|
|
13554
|
-
|
|
13555
|
-
|
|
13639
|
+
const scanned = await listSessions();
|
|
13640
|
+
let flush;
|
|
13641
|
+
let locator = deps.locator;
|
|
13642
|
+
if (!locator) {
|
|
13643
|
+
const resolver = createProjectDirResolver();
|
|
13644
|
+
locator = resolver;
|
|
13645
|
+
flush = resolver.flush;
|
|
13646
|
+
}
|
|
13647
|
+
const repoFilter = studyRepoFilter(state, () => scanAgentSessions({}), locator);
|
|
13648
|
+
if (state.project_filter) {
|
|
13649
|
+
delete state.project_filter;
|
|
13650
|
+
if (repoFilter)
|
|
13651
|
+
state.repo_filter = repoFilter;
|
|
13652
|
+
logger.debug("sync", `folder scope converted to repos: ${repoFilter?.join(", ") || "none"}`);
|
|
13653
|
+
}
|
|
13654
|
+
const sessions = filterSessionsByRepo(scanned, repoFilter, (s) => locator.resolveRepo(s));
|
|
13655
|
+
flush?.();
|
|
13656
|
+
logger.debug("sync", `study scope ${repoFilter ? repoFilter.join(", ") || "none" : "all repos"}: ${sessions.length} of ${scanned.length} sessions in scope`);
|
|
13556
13657
|
({ ready, open } = gateSessions(sessions, state.watermark, now()));
|
|
13557
13658
|
logGateResult(ready, open.length, state.watermark);
|
|
13558
13659
|
} catch (err) {
|
|
@@ -13800,7 +13901,7 @@ var init_conflicts = __esm(() => {
|
|
|
13800
13901
|
});
|
|
13801
13902
|
|
|
13802
13903
|
// src/telemetry/debug-id.ts
|
|
13803
|
-
var BUNDLE_DEBUG_ID_PLACEHOLDER = "
|
|
13904
|
+
var BUNDLE_DEBUG_ID_PLACEHOLDER = "01f74cf6-cdbb-b296-6475-6e2164756e21";
|
|
13804
13905
|
|
|
13805
13906
|
// src/telemetry/telemetry.ts
|
|
13806
13907
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
@@ -15477,9 +15578,8 @@ note cap are the volume guards.
|
|
|
15477
15578
|
5. Title is a noun-phrase topic (like "page_version UniqueViolation race"), not a sentence.
|
|
15478
15579
|
Content is a self-contained observation in plain language; include file/path pointers when
|
|
15479
15580
|
useful.
|
|
15480
|
-
6.
|
|
15481
|
-
|
|
15482
|
-
verified, omit both.
|
|
15581
|
+
6. Never pass repo or branch to write_knowledge; the client attaches each note's repo from the
|
|
15582
|
+
session it came from.
|
|
15483
15583
|
7. Never quote credentials, tokens, or secrets — even redacted placeholders — and never include
|
|
15484
15584
|
long verbatim transcript spans. Summarize in your own words.
|
|
15485
15585
|
8. A trivial session with no real user query is normal: skip it silently.`;
|
|
@@ -58494,6 +58594,7 @@ async function runLearner(options) {
|
|
|
58494
58594
|
let currentSession;
|
|
58495
58595
|
const readsSinceWrite = new Set;
|
|
58496
58596
|
const sessionKeys = new Map(options.sessions.map((s) => [s.id, `${s.harness}/${s.id}`]));
|
|
58597
|
+
const sessionRepos = new Map(options.sessions.map((s) => [s.id, s.repo]));
|
|
58497
58598
|
const notedSessions = new Set;
|
|
58498
58599
|
const pendingNotes = new Map;
|
|
58499
58600
|
let turns = 0;
|
|
@@ -58577,10 +58678,20 @@ async function runLearner(options) {
|
|
|
58577
58678
|
const noted = currentSession && sessionKeys.get(currentSession);
|
|
58578
58679
|
if (noted)
|
|
58579
58680
|
pendingNotes.set(toolUseID, noted);
|
|
58580
|
-
const {
|
|
58681
|
+
const {
|
|
58682
|
+
transcript_id: _transcriptByModel,
|
|
58683
|
+
repo: _repoByModel,
|
|
58684
|
+
branch: _branchByModel,
|
|
58685
|
+
...clean
|
|
58686
|
+
} = input;
|
|
58687
|
+
const repo = currentSession && sessionRepos.get(currentSession);
|
|
58581
58688
|
return {
|
|
58582
58689
|
behavior: "allow",
|
|
58583
|
-
updatedInput:
|
|
58690
|
+
updatedInput: {
|
|
58691
|
+
...clean,
|
|
58692
|
+
...currentSession ? { transcript_id: currentSession } : {},
|
|
58693
|
+
...repo ? { repo } : {}
|
|
58694
|
+
}
|
|
58584
58695
|
};
|
|
58585
58696
|
}
|
|
58586
58697
|
return { behavior: "allow", updatedInput: input };
|
|
@@ -58670,7 +58781,6 @@ var init_runner = __esm(() => {
|
|
|
58670
58781
|
|
|
58671
58782
|
// src/commands/knowledge.ts
|
|
58672
58783
|
import { existsSync as existsSync18 } from "node:fs";
|
|
58673
|
-
import { homedir as homedir7 } from "node:os";
|
|
58674
58784
|
import { delimiter as delimiter4, join as join21 } from "node:path";
|
|
58675
58785
|
function requireConfig7() {
|
|
58676
58786
|
const cfg = requireLoginConfig();
|
|
@@ -58938,10 +59048,11 @@ function printSyncStatus(status, now = new Date) {
|
|
|
58938
59048
|
}
|
|
58939
59049
|
const wm = status.state.watermark;
|
|
58940
59050
|
console.log(` Studied through: ${wm ? `${wm} (${formatAge(wm, now)})` : "nothing studied yet"}`);
|
|
58941
|
-
|
|
58942
|
-
|
|
58943
|
-
|
|
58944
|
-
|
|
59051
|
+
const repoFilter = status.state.repo_filter;
|
|
59052
|
+
if (repoFilter) {
|
|
59053
|
+
console.log(` Study scope: ${repoFilter.length ? repoFilter.map(displayRepo).join(", ") : "no repos"}`);
|
|
59054
|
+
} else if (status.state.project_filter?.length) {
|
|
59055
|
+
console.log(` Study scope: ${status.state.project_filter.length} folders (converted to repos on the next sync)`);
|
|
58945
59056
|
}
|
|
58946
59057
|
if ((status.state.total_notes ?? 0) > 0) {
|
|
58947
59058
|
const tokens = status.state.total_learning_tokens ?? 0;
|
|
@@ -59104,6 +59215,7 @@ var init_knowledge = __esm(() => {
|
|
|
59104
59215
|
init_agents();
|
|
59105
59216
|
init_formats();
|
|
59106
59217
|
init_generate();
|
|
59218
|
+
init_repo();
|
|
59107
59219
|
init_backlog();
|
|
59108
59220
|
init_detach();
|
|
59109
59221
|
init_status();
|
|
@@ -59898,7 +60010,94 @@ var init_skills = __esm(() => {
|
|
|
59898
60010
|
files: [
|
|
59899
60011
|
{
|
|
59900
60012
|
path: "SKILL.md",
|
|
59901
|
-
content:
|
|
60013
|
+
content: `---
|
|
60014
|
+
name: dosu
|
|
60015
|
+
description: 'Use the Dosu CLI to show the Dosu review list or review queue (items pending approval: pending doc changes and draft replies) and to review, approve, or reject one item; authenticate and configure coding agents; manage Libraries, Agents, sources, Monitor, documents, threads, topics, members, integrations, and MCP deployments; query knowledge; or audit a repository for Dosu-generated docs. Use for Dosu platform work that should be done from a terminal instead of the web app.'
|
|
60016
|
+
---
|
|
60017
|
+
|
|
60018
|
+
# Use the Dosu CLI
|
|
60019
|
+
|
|
60020
|
+
Operate Dosu through \`dosu\`. Prefer structured output and let the CLI and App validate product rules.
|
|
60021
|
+
|
|
60022
|
+
## Start correctly
|
|
60023
|
+
|
|
60024
|
+
1. Run \`dosu --version\`. Do not probe installation with unrelated package-manager commands.
|
|
60025
|
+
2. If it is unavailable and Node is present, try \`npx -y @dosu/cli --version\` and use \`npx -y @dosu/cli\` as the command prefix for this session. Otherwise ask before installing anything.
|
|
60026
|
+
3. Run \`dosu --help\` or the relevant nested \`--help\` before using a command not covered by [the command reference](references/commands.md).
|
|
60027
|
+
4. Pass \`--json\` whenever that leaf command exposes it. Parse the result; do not scrape the human table.
|
|
60028
|
+
|
|
60029
|
+
## Satisfy the right prerequisite
|
|
60030
|
+
|
|
60031
|
+
- \`dosu login\` supplies the JWT used by tRPC-backed commands.
|
|
60032
|
+
- \`dosu setup\` selects an organization, Library context, and MCP deployment, creates an API key, and can configure an agent tool.
|
|
60033
|
+
- \`dosu ask\` needs an API key. \`docs generate\`, \`docs auto-tag\`, and \`docs publish\` need both JWT login and the API key. Other platform commands generally need JWT login; commands scoped to an organization or current Library also need the corresponding setup selection.
|
|
60034
|
+
- For coding-agent setup, follow the nonblocking \`dosu setup --agent --tool <id>\` workflow in [workflows.md](references/workflows.md). Relay its URL and execute its returned \`resume_command\`; never invent ticket commands.
|
|
60035
|
+
|
|
60036
|
+
## Use current product terms
|
|
60037
|
+
|
|
60038
|
+
- **Organization** is the account and permission boundary.
|
|
60039
|
+
- **Library** is the knowledge container shown in the App. Older APIs may call it a \`space\`; do not present that internal name to users.
|
|
60040
|
+
- **Data source** is an organization-level connection such as a repository, Slack channel, or Notion workspace. A Library can use already-connected sources.
|
|
60041
|
+
- **Agent** is a configurable GitHub, GitLab, Slack, or Teams deployment serving exactly one Library.
|
|
60042
|
+
- **Monitor** is per Library and source. It supports GitHub, GitLab, and Azure DevOps sources.
|
|
60043
|
+
- **MCP deployment** is the local selection made by \`dosu setup\` or \`dosu deployments switch\`. It sets the Library that Library-scoped commands such as \`dosu review list\` read, plus the API key \`dosu ask\` uses. \`dosu deployments\` lists and switches MCP deployments; it is not an Agent command.
|
|
60044
|
+
- The **review queue** (\`dosu review list\`) holds pending doc changes in the selected Library plus draft replies from the selected MCP deployment. The App's **Review** page is a list of conversation threads, so its entries and counts can differ. When the user explicitly means that page, explain the difference and offer \`dosu threads list\` or \`dosu review context <thread-id>\`; do not substitute an Agent list.
|
|
60045
|
+
|
|
60046
|
+
## Route by intent
|
|
60047
|
+
|
|
60048
|
+
| Intent | Route |
|
|
60049
|
+
|---|---|
|
|
60050
|
+
| Set up Dosu end-to-end ("set up dosu", new Library from scratch) | Read the guided onboarding flow in [workflows.md](references/workflows.md) first; it is checkpoint-driven — ask, wait, then act |
|
|
60051
|
+
| Create or configure a Library | \`dosu libraries ...\` |
|
|
60052
|
+
| Attach existing organization sources | \`dosu sources list\` → \`dosu libraries sources attach\` |
|
|
60053
|
+
| Create or configure an Agent | \`dosu agents ...\` |
|
|
60054
|
+
| Ask for a synthesized answer | \`dosu ask\` |
|
|
60055
|
+
| Find source documents | \`dosu knowledge search\`, then \`dosu docs get\` |
|
|
60056
|
+
| Create, import, or publish docs | \`dosu docs ...\` |
|
|
60057
|
+
| Show the review list, review queue, or what needs approval | \`dosu review list --json\`; if the scope looks wrong, follow the context check in [review-workflow.md](references/review-workflow.md) |
|
|
60058
|
+
| Inspect, edit, approve, or reject one review item | Read [review-workflow.md](references/review-workflow.md) first |
|
|
60059
|
+
| Inspect conversations | \`dosu threads ...\` |
|
|
60060
|
+
| Browse managed topics | \`dosu topics ...\` |
|
|
60061
|
+
| Audit agent docs, README, architecture, or dependencies | Read [audit.md](references/audit.md) first |
|
|
60062
|
+
|
|
60063
|
+
Use [commands.md](references/commands.md) as the sole detailed command and flag reference. Use [workflows.md](references/workflows.md) only for multi-command composition.
|
|
60064
|
+
|
|
60065
|
+
## Apply Library and Agent semantics
|
|
60066
|
+
|
|
60067
|
+
- To assemble a Library, list organization sources, create the Library, attach the chosen source IDs, then verify with \`libraries info\` and \`libraries sources list\`. The CLI cannot establish a brand-new OAuth connection.
|
|
60068
|
+
- \`libraries sources config\` resolves the provider on the App side. Read the command reference before choosing provider-specific options.
|
|
60069
|
+
- When creating a Library with a GitHub repository, or newly attaching one to an existing Library, treat Monitor as part of that setup unless the user opts out. Enable it with the whole-repository and \`emoji\` defaults, verify it, and explain that Monitor reviews pull requests to keep the Library's knowledge up to date. Follow [the Library workflow](references/workflows.md); do not ask the user to choose defaults.
|
|
60070
|
+
- Create an Agent from an existing source ID and let the App choose its defaults; do not synthesize config in shell commands. Each data source can back at most one Agent: a \`CONFLICT\` (409) from \`agents create\` means the source already has one — offer a different source or an authorized \`agents move\` instead of retrying.
|
|
60071
|
+
- Read Agent config before changing one existing leaf. Values are JSON. If a concurrent write returns \`CONFLICT\`, read again, re-evaluate the requested change, and retry only if it is still correct.
|
|
60072
|
+
- Moving an Agent replaces its Library. Verify the returned \`space_id\`; do not infer migration behavior for historical data from the move receipt.
|
|
60073
|
+
|
|
60074
|
+
## Respect write boundaries
|
|
60075
|
+
|
|
60076
|
+
- Treat an explicit, unambiguous user request for the exact mutation as authorization. Otherwise show the intended change and wait for confirmation.
|
|
60077
|
+
- For commands exposing \`--confirm\`, omit it first when a preview is useful; \`{ "confirmRequired": true, "applied": false }\` means nothing changed. Pass \`--confirm\` only after authorization.
|
|
60078
|
+
- Never batch-approve or batch-reject review items. Diff one item, explain it, and decide only the ID the user authorized.
|
|
60079
|
+
- Before deleting a Library, Agent, source, or document, state the exact target and impact. Before detaching a source, read and state the impact documented in the command reference. Some older delete commands do not enforce \`--confirm\`; the absence of a CLI prompt is not user approval.
|
|
60080
|
+
- Before attaching a source to a public Library, warn that its content becomes available to everyone who can access that Library.
|
|
60081
|
+
- OAuth connection and billing remain web-only. Report the boundary instead of claiming success.
|
|
60082
|
+
|
|
60083
|
+
## Handle failures literally
|
|
60084
|
+
|
|
60085
|
+
- \`Not logged in\` or an unrecoverable expired session: run \`dosu login\` (agent setup may instead return a ticket flow).
|
|
60086
|
+
- Missing organization, Library, deployment, or API-key context: run the appropriate \`dosu setup\` flow.
|
|
60087
|
+
- 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.
|
|
60088
|
+
- 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.
|
|
60089
|
+
- \`confirmRequired\`: no write occurred.
|
|
60090
|
+
- \`CONFLICT\` on config: reread before retrying.
|
|
60091
|
+
- A tRPC or backend error is a failed operation. Surface the code/path/status and do not imply the requested state exists.
|
|
60092
|
+
|
|
60093
|
+
## References
|
|
60094
|
+
|
|
60095
|
+
- [commands.md](references/commands.md): authoritative CLI syntax, choices, defaults, conflicts, and prerequisites
|
|
60096
|
+
- [workflows.md](references/workflows.md): reusable multi-command flows
|
|
60097
|
+
- [review-workflow.md](references/review-workflow.md): review safety and context
|
|
60098
|
+
- [audit.md](references/audit.md): repository audit procedure
|
|
60099
|
+
- [audit-findings-schema.md](references/audit-findings-schema.md): \`.dosu/audit.json\` contract
|
|
60100
|
+
`,
|
|
59902
60101
|
executable: false
|
|
59903
60102
|
},
|
|
59904
60103
|
{
|
|
@@ -60249,7 +60448,7 @@ dosu docs sync-back <id> [--json]
|
|
|
60249
60448
|
Read [review-workflow.md](review-workflow.md) before mutating a review item.
|
|
60250
60449
|
|
|
60251
60450
|
\`\`\`text
|
|
60252
|
-
dosu review list [--json]
|
|
60451
|
+
dosu review list [--since <when>] [--until <when>] [--json]
|
|
60253
60452
|
dosu review diff <id> [--json]
|
|
60254
60453
|
dosu review edit <id> [--title <title>] [--body <markdown> | --body-file <path>] [--json]
|
|
60255
60454
|
dosu review context <thread-id> [--json]
|
|
@@ -60258,7 +60457,9 @@ dosu review reject <id> [--confirm] [--json]
|
|
|
60258
60457
|
dosu review revert <id> [--json]
|
|
60259
60458
|
\`\`\`
|
|
60260
60459
|
|
|
60261
|
-
\`
|
|
60460
|
+
- \`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.
|
|
60461
|
+
- 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.
|
|
60462
|
+
- \`edit\` requires at least one field; drafts accept only \`--body\`/\`--body-file\`. \`approve\` and \`reject\` do not write without interactive confirmation or \`--confirm\`.
|
|
60262
60463
|
|
|
60263
60464
|
## Sources, integrations, members, and organization
|
|
60264
60465
|
|
|
@@ -60345,98 +60546,7 @@ The coding agent writes the findings; Dosu generates the selected docs. Follow [
|
|
|
60345
60546
|
},
|
|
60346
60547
|
{
|
|
60347
60548
|
path: "references/review-workflow.md",
|
|
60348
|
-
content:
|
|
60349
|
-
|
|
60350
|
-
How to work Dosu's review queue from the CLI: triage pending items, read the
|
|
60351
|
-
diff, optionally edit, and apply a decision — safely.
|
|
60352
|
-
|
|
60353
|
-
The queue holds **document versions** (AI-generated, user-edited, synced from
|
|
60354
|
-
source, or API-created) and **draft messages**, all surfaced through the same
|
|
60355
|
-
\`dosu review\` commands. Items are keyed on a single \`page-version-id\` so
|
|
60356
|
-
\`list\` → \`diff\` → \`edit\` → \`approve\`/\`reject\` all share one ID.
|
|
60357
|
-
|
|
60358
|
-
> \`list\` returns only the most recent pending items (server-capped, currently
|
|
60359
|
-
> 50) and does not flag truncation. If the queue is large, don't assume \`list\`
|
|
60360
|
-
> shows everything.
|
|
60361
|
-
|
|
60362
|
-
## Scope tool access for a review session
|
|
60363
|
-
|
|
60364
|
-
Approving and rejecting are destructive and outward-facing. When you spin up an
|
|
60365
|
-
agent session dedicated to reviewing, scope it to just the review commands:
|
|
60366
|
-
|
|
60367
|
-
\`\`\`yaml
|
|
60368
|
-
allowed-tools: Bash(dosu review:*)
|
|
60369
|
-
\`\`\`
|
|
60370
|
-
|
|
60371
|
-
This lets the agent triage, diff, and edit, but keeps it from running unrelated
|
|
60372
|
-
CLI commands during a review pass.
|
|
60373
|
-
|
|
60374
|
-
## The flow
|
|
60375
|
-
|
|
60376
|
-
\`\`\`bash
|
|
60377
|
-
# 1. Triage — what's pending? Columns: ID, Kind, Title, Source, Status, Created.
|
|
60378
|
-
dosu review list --json
|
|
60379
|
-
|
|
60380
|
-
# 2. Inspect — read the exact change before deciding anything.
|
|
60381
|
-
dosu review diff <page-version-id> --json
|
|
60382
|
-
|
|
60383
|
-
# 3. (optional) Edit in place instead of rejecting a near-miss.
|
|
60384
|
-
dosu review edit <page-version-id> --body-file ./revised.md # or --body / --title
|
|
60385
|
-
|
|
60386
|
-
# 4. Decide — only on the item the user named, only after they OK it.
|
|
60387
|
-
dosu review approve <page-version-id> --confirm --json
|
|
60388
|
-
dosu review reject <page-version-id> --confirm --json
|
|
60389
|
-
|
|
60390
|
-
# Undo a decision — back to pending.
|
|
60391
|
-
dosu review revert <page-version-id> --json
|
|
60392
|
-
\`\`\`
|
|
60393
|
-
|
|
60394
|
-
### \`--confirm\` is mandatory for agents
|
|
60395
|
-
|
|
60396
|
-
\`approve\` and \`reject\` only prompt interactively in a TTY *without* \`--json\`.
|
|
60397
|
-
The skill always passes \`--json\`, which suppresses both the diff preview and
|
|
60398
|
-
the prompt — so without \`--confirm\` the command changes nothing and returns
|
|
60399
|
-
\`{ "applied": false, "confirmRequired": true }\`. Run \`dosu review diff <id>\` to
|
|
60400
|
-
see the change, then pass \`--confirm\` to apply — only after the user has
|
|
60401
|
-
approved that specific item.
|
|
60402
|
-
|
|
60403
|
-
## Reading the \`Source\` column
|
|
60404
|
-
|
|
60405
|
-
\`Source\` is the humanized \`origin\` field:
|
|
60406
|
-
|
|
60407
|
-
| \`origin\` | Source | Meaning |
|
|
60408
|
-
|---|---|---|
|
|
60409
|
-
| \`manual_update\` | User created / User updated | A teammate authored or edited the doc |
|
|
60410
|
-
| \`llm_generated\` | AI generated | Dosu drafted the change |
|
|
60411
|
-
| \`sync_upstream\` | Synced from source | Came in from a connected source (Notion/Confluence/GitHub) |
|
|
60412
|
-
| \`api_update\` | Created via API | Pushed in programmatically |
|
|
60413
|
-
|
|
60414
|
-
## Feeding PR / thread context
|
|
60415
|
-
|
|
60416
|
-
A review often originates from a conversation thread (a GitHub issue, a Slack
|
|
60417
|
-
message, a sync-back PR). Pull that context before deciding:
|
|
60418
|
-
|
|
60419
|
-
\`\`\`bash
|
|
60420
|
-
dosu review context <thread-id> --json
|
|
60421
|
-
\`\`\`
|
|
60422
|
-
|
|
60423
|
-
It returns the review \`type\`, the review/published page IDs, and — when the
|
|
60424
|
-
change round-trips to an external system — a **Sync PR URL**. Use it to:
|
|
60425
|
-
|
|
60426
|
-
- show the user where the change came from and where approval will land, and
|
|
60427
|
-
- read the originating discussion so the decision matches intent.
|
|
60428
|
-
|
|
60429
|
-
## Safety rules
|
|
60430
|
-
|
|
60431
|
-
- **Explicit item only.** Decide on the \`id\` the user named. "Review my queue"
|
|
60432
|
-
→ run \`list\` and show it; never decide on their behalf.
|
|
60433
|
-
- **Diff before deciding.** Always \`diff\` and surface the change first.
|
|
60434
|
-
- **Never batch-accept.** No loop over \`list\` that approves everything. One
|
|
60435
|
-
item, one confirmation.
|
|
60436
|
-
- **Extra care for sync/PR origins.** \`Synced from source\` items and anything
|
|
60437
|
-
with a Sync PR URL push back to an upstream system on approval. Call it out
|
|
60438
|
-
and get explicit sign-off.
|
|
60439
|
-
`,
|
|
60549
|
+
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',
|
|
60440
60550
|
executable: false
|
|
60441
60551
|
},
|
|
60442
60552
|
{
|
|
@@ -60676,7 +60786,7 @@ import {
|
|
|
60676
60786
|
symlinkSync,
|
|
60677
60787
|
writeFileSync as writeFileSync12
|
|
60678
60788
|
} from "node:fs";
|
|
60679
|
-
import { homedir as
|
|
60789
|
+
import { homedir as homedir7 } from "node:os";
|
|
60680
60790
|
import { dirname as dirname10, join as join22, relative as relative3 } from "node:path";
|
|
60681
60791
|
function skillAgentIDsForProviders(providerIDs) {
|
|
60682
60792
|
return [
|
|
@@ -60687,15 +60797,15 @@ function isSkillAgentID(agent) {
|
|
|
60687
60797
|
return SUPPORTED_SKILL_AGENTS.includes(agent);
|
|
60688
60798
|
}
|
|
60689
60799
|
function universalSkillsDir() {
|
|
60690
|
-
return join22(
|
|
60800
|
+
return join22(homedir7(), ".agents", "skills");
|
|
60691
60801
|
}
|
|
60692
60802
|
function linkedSkillsDir(agent) {
|
|
60693
60803
|
if (agent === "claude-code") {
|
|
60694
|
-
const claudeConfigDir = process.env.CLAUDE_CONFIG_DIR?.trim() || join22(
|
|
60804
|
+
const claudeConfigDir = process.env.CLAUDE_CONFIG_DIR?.trim() || join22(homedir7(), ".claude");
|
|
60695
60805
|
return join22(claudeConfigDir, "skills");
|
|
60696
60806
|
}
|
|
60697
60807
|
if (agent === "windsurf")
|
|
60698
|
-
return join22(
|
|
60808
|
+
return join22(homedir7(), ".codeium", "windsurf", "skills");
|
|
60699
60809
|
return null;
|
|
60700
60810
|
}
|
|
60701
60811
|
function skillInstallTargetForProvider(providerID) {
|
|
@@ -60738,7 +60848,7 @@ function clearSkillInstallState() {
|
|
|
60738
60848
|
rmSync5(installStatePath(), { force: true });
|
|
60739
60849
|
}
|
|
60740
60850
|
function pruneLegacySkillLock() {
|
|
60741
|
-
const lockPath = join22(
|
|
60851
|
+
const lockPath = join22(homedir7(), ".agents", ".skill-lock.json");
|
|
60742
60852
|
try {
|
|
60743
60853
|
if (!existsSync19(lockPath))
|
|
60744
60854
|
return [];
|
|
@@ -62173,15 +62283,15 @@ var init_vscode = __esm(() => {
|
|
|
62173
62283
|
});
|
|
62174
62284
|
|
|
62175
62285
|
// src/mcp/providers/windsurf.ts
|
|
62176
|
-
import { homedir as
|
|
62286
|
+
import { homedir as homedir8 } from "node:os";
|
|
62177
62287
|
import { join as join36 } from "node:path";
|
|
62178
62288
|
var WindsurfProvider = () => createJSONProvider({
|
|
62179
62289
|
providerName: "Windsurf",
|
|
62180
62290
|
providerID: "windsurf",
|
|
62181
62291
|
local: false,
|
|
62182
62292
|
priorityValue: 9,
|
|
62183
|
-
paths: [join36(
|
|
62184
|
-
globalPath: join36(
|
|
62293
|
+
paths: [join36(homedir8(), ".codeium", "windsurf")],
|
|
62294
|
+
globalPath: join36(homedir8(), ".codeium", "windsurf", "mcp_config.json"),
|
|
62185
62295
|
topKey: "mcpServers"
|
|
62186
62296
|
});
|
|
62187
62297
|
var init_windsurf = __esm(() => {
|
|
@@ -62190,14 +62300,14 @@ var init_windsurf = __esm(() => {
|
|
|
62190
62300
|
|
|
62191
62301
|
// src/mcp/providers/zed.ts
|
|
62192
62302
|
import { rmSync as rmSync6 } from "node:fs";
|
|
62193
|
-
import { homedir as
|
|
62303
|
+
import { homedir as homedir9, platform as platform3 } from "node:os";
|
|
62194
62304
|
import { join as join37 } from "node:path";
|
|
62195
62305
|
function zedConfigDir() {
|
|
62196
62306
|
const os = platform3();
|
|
62197
62307
|
if (os === "win32")
|
|
62198
62308
|
return join37(appSupportDir(), "Zed");
|
|
62199
62309
|
if (os === "darwin")
|
|
62200
|
-
return join37(
|
|
62310
|
+
return join37(homedir9(), ".config", "zed");
|
|
62201
62311
|
return join37(appSupportDir(), "zed");
|
|
62202
62312
|
}
|
|
62203
62313
|
function zedDataDir() {
|
|
@@ -62206,7 +62316,7 @@ function zedDataDir() {
|
|
|
62206
62316
|
return join37(appSupportDir(), "Zed");
|
|
62207
62317
|
if (os === "win32")
|
|
62208
62318
|
return join37(process.env.LOCALAPPDATA ?? "", "Zed");
|
|
62209
|
-
return join37(process.env.XDG_DATA_HOME ?? join37(
|
|
62319
|
+
return join37(process.env.XDG_DATA_HOME ?? join37(homedir9(), ".local", "share"), "zed");
|
|
62210
62320
|
}
|
|
62211
62321
|
function legacyDarwinSettingsPath() {
|
|
62212
62322
|
return platform3() === "darwin" ? join37(appSupportDir(), "Zed", "settings.json") : null;
|
|
@@ -62355,7 +62465,7 @@ import {
|
|
|
62355
62465
|
writeFileSync as writeFileSync14,
|
|
62356
62466
|
writeSync
|
|
62357
62467
|
} from "node:fs";
|
|
62358
|
-
import { homedir as
|
|
62468
|
+
import { homedir as homedir10 } from "node:os";
|
|
62359
62469
|
import { join as join38 } from "node:path";
|
|
62360
62470
|
function statePath() {
|
|
62361
62471
|
return join38(getConfigDir(), STATE_FILENAME2);
|
|
@@ -62490,7 +62600,7 @@ function startAutoUpdate(version, options = {}) {
|
|
|
62490
62600
|
return claim === "held" ? "in_progress" : "unavailable";
|
|
62491
62601
|
try {
|
|
62492
62602
|
const child = spawn3(invocation.command, invocation.args, {
|
|
62493
|
-
cwd:
|
|
62603
|
+
cwd: homedir10(),
|
|
62494
62604
|
detached: true,
|
|
62495
62605
|
stdio: "ignore",
|
|
62496
62606
|
windowsHide: true,
|
|
@@ -62891,7 +63001,7 @@ var init_follow = () => {};
|
|
|
62891
63001
|
|
|
62892
63002
|
// src/sessions/session-title.ts
|
|
62893
63003
|
import { existsSync as existsSync26, mkdirSync as mkdirSync16, readFileSync as readFileSync25, renameSync as renameSync6, statSync as statSync10, writeFileSync as writeFileSync17 } from "node:fs";
|
|
62894
|
-
import { homedir as
|
|
63004
|
+
import { homedir as homedir11 } from "node:os";
|
|
62895
63005
|
import { join as join41 } from "node:path";
|
|
62896
63006
|
function cachePath2(configDir) {
|
|
62897
63007
|
return join41(configDir, CACHE_FILENAME5);
|
|
@@ -62946,7 +63056,7 @@ function claudeSummaryFromHead(text) {
|
|
|
62946
63056
|
}
|
|
62947
63057
|
return null;
|
|
62948
63058
|
}
|
|
62949
|
-
function reconstructSession(harness, id, slug, home =
|
|
63059
|
+
function reconstructSession(harness, id, slug, home = homedir11(), exists = existsSync26) {
|
|
62950
63060
|
if (!slug)
|
|
62951
63061
|
return null;
|
|
62952
63062
|
const candidates = [];
|
|
@@ -63622,13 +63732,13 @@ var init_activity_view = __esm(() => {
|
|
|
63622
63732
|
|
|
63623
63733
|
// src/rules/installer.ts
|
|
63624
63734
|
import { existsSync as existsSync28, mkdirSync as mkdirSync17, readFileSync as readFileSync27, unlinkSync as unlinkSync5, writeFileSync as writeFileSync18 } from "node:fs";
|
|
63625
|
-
import { homedir as
|
|
63735
|
+
import { homedir as homedir12 } from "node:os";
|
|
63626
63736
|
import { dirname as dirname12, join as join42 } from "node:path";
|
|
63627
63737
|
function claudeConfigDir4() {
|
|
63628
|
-
return process.env.CLAUDE_CONFIG_DIR || join42(
|
|
63738
|
+
return process.env.CLAUDE_CONFIG_DIR || join42(homedir12(), ".claude");
|
|
63629
63739
|
}
|
|
63630
63740
|
function codexHome4() {
|
|
63631
|
-
return process.env.CODEX_HOME || join42(
|
|
63741
|
+
return process.env.CODEX_HOME || join42(homedir12(), ".codex");
|
|
63632
63742
|
}
|
|
63633
63743
|
function isRuleAgent(agent) {
|
|
63634
63744
|
return Object.hasOwn(RULE_TARGETS, agent);
|
|
@@ -63774,7 +63884,7 @@ var init_installer = __esm(() => {
|
|
|
63774
63884
|
},
|
|
63775
63885
|
cursor: {
|
|
63776
63886
|
kind: "file",
|
|
63777
|
-
path: () => join42(
|
|
63887
|
+
path: () => join42(homedir12(), ".cursor", "rules", "dosu.mdc"),
|
|
63778
63888
|
cursorFrontmatter: true
|
|
63779
63889
|
},
|
|
63780
63890
|
codex: {
|
|
@@ -63783,15 +63893,15 @@ var init_installer = __esm(() => {
|
|
|
63783
63893
|
},
|
|
63784
63894
|
opencode: {
|
|
63785
63895
|
kind: "section",
|
|
63786
|
-
path: () => join42(
|
|
63896
|
+
path: () => join42(homedir12(), ".config", "opencode", "AGENTS.md")
|
|
63787
63897
|
},
|
|
63788
63898
|
gemini: {
|
|
63789
63899
|
kind: "section",
|
|
63790
|
-
path: () => join42(
|
|
63900
|
+
path: () => join42(homedir12(), ".gemini", "GEMINI.md")
|
|
63791
63901
|
},
|
|
63792
63902
|
antigravity: {
|
|
63793
63903
|
kind: "section",
|
|
63794
|
-
path: () => join42(
|
|
63904
|
+
path: () => join42(homedir12(), ".gemini", "GEMINI.md")
|
|
63795
63905
|
}
|
|
63796
63906
|
};
|
|
63797
63907
|
});
|
|
@@ -65582,7 +65692,7 @@ var init_flow2 = __esm(() => {
|
|
|
65582
65692
|
// src/commands/upgrade.ts
|
|
65583
65693
|
import { spawnSync } from "node:child_process";
|
|
65584
65694
|
import { realpathSync as realpathSync2 } from "node:fs";
|
|
65585
|
-
import { homedir as
|
|
65695
|
+
import { homedir as homedir13 } from "node:os";
|
|
65586
65696
|
import { posix, win32 as win322 } from "node:path";
|
|
65587
65697
|
function isAbsoluteCommandProcessor(value) {
|
|
65588
65698
|
return Boolean(value && win322.isAbsolute(value) && win322.basename(value).toLowerCase() === "cmd.exe");
|
|
@@ -65636,7 +65746,7 @@ function packageRoots(manager, platform, comSpec, env, cwd) {
|
|
|
65636
65746
|
manager === "npm" ? path.join(lines[0], "@dosu", "cli") : path.join(lines[0], "node_modules", "@dosu", "cli")
|
|
65637
65747
|
];
|
|
65638
65748
|
}
|
|
65639
|
-
function owningPackageManager(entrypoint, platform, comSpec, env = process.env, cwd =
|
|
65749
|
+
function owningPackageManager(entrypoint, platform, comSpec, env = process.env, cwd = homedir13()) {
|
|
65640
65750
|
const path = platform === "win32" ? win322 : posix;
|
|
65641
65751
|
if (!entrypoint || !path.isAbsolute(entrypoint))
|
|
65642
65752
|
return null;
|
|
@@ -65689,7 +65799,7 @@ Or install Dosu globally with your package manager:`);
|
|
|
65689
65799
|
function runUpgrade(channel = INSTALL_CHANNEL, options = {}) {
|
|
65690
65800
|
const platform = options.platform ?? process.platform;
|
|
65691
65801
|
const env = options.env ?? process.env;
|
|
65692
|
-
const cwd = options.cwd ??
|
|
65802
|
+
const cwd = options.cwd ?? homedir13();
|
|
65693
65803
|
let manager = null;
|
|
65694
65804
|
if (channel === "npm") {
|
|
65695
65805
|
if (isNpxInvocation(channel, env)) {
|
|
@@ -65756,7 +65866,7 @@ function finishWithNewBinary(channel, options) {
|
|
|
65756
65866
|
return;
|
|
65757
65867
|
}
|
|
65758
65868
|
const result = spawnSync(invocation.command, invocation.args, {
|
|
65759
|
-
cwd: options.cwd ??
|
|
65869
|
+
cwd: options.cwd ?? homedir13(),
|
|
65760
65870
|
shell: false,
|
|
65761
65871
|
stdio: "inherit"
|
|
65762
65872
|
});
|
|
@@ -66554,7 +66664,6 @@ var init_pages_view = __esm(() => {
|
|
|
66554
66664
|
});
|
|
66555
66665
|
|
|
66556
66666
|
// src/tui/tui.ts
|
|
66557
|
-
import { homedir as homedir15 } from "node:os";
|
|
66558
66667
|
import { basename as basename6 } from "node:path";
|
|
66559
66668
|
async function probeSession(cfg) {
|
|
66560
66669
|
if (!isAuthenticated(cfg) || !isTokenExpired(cfg))
|
|
@@ -66783,8 +66892,9 @@ async function runSettings(cfg) {
|
|
|
66783
66892
|
while (true) {
|
|
66784
66893
|
const target = cfg.active_account?.target;
|
|
66785
66894
|
const library = target?.library_name ?? target?.deployment_name ?? "not configured";
|
|
66786
|
-
const
|
|
66787
|
-
const
|
|
66895
|
+
const sync = loadSyncState();
|
|
66896
|
+
const filter = sync.repo_filter;
|
|
66897
|
+
const scope = !filter ? sync.project_filter?.length ? `${sync.project_filter.length} folders (legacy)` : "all repos" : filter.length === 0 ? "no repos" : filter.length <= 2 ? filter.map(displayRepo).join(", ") : `${filter.length} repos`;
|
|
66788
66898
|
const action = await menuSelect("settings", [
|
|
66789
66899
|
{ label: "switch organization", hint: target?.org_name, value: "switch-org" },
|
|
66790
66900
|
{ label: "switch library", hint: library, value: "switch-library" },
|
|
@@ -66818,45 +66928,43 @@ async function runSettings(cfg) {
|
|
|
66818
66928
|
}
|
|
66819
66929
|
}
|
|
66820
66930
|
}
|
|
66821
|
-
function
|
|
66822
|
-
const home = homedir15();
|
|
66823
|
-
return dir === home ? "~" : dir.startsWith(`${home}/`) ? `~${dir.slice(home.length)}` : dir;
|
|
66824
|
-
}
|
|
66825
|
-
function discoverProjectDirs() {
|
|
66931
|
+
function discoverSessionRepos(current) {
|
|
66826
66932
|
const counts = new Map;
|
|
66827
66933
|
try {
|
|
66828
66934
|
const resolver = createProjectDirResolver();
|
|
66829
66935
|
for (const session of scanAgentSessions({})) {
|
|
66830
|
-
const
|
|
66831
|
-
|
|
66936
|
+
const repo = resolver.resolveRepo(session);
|
|
66937
|
+
if (repo)
|
|
66938
|
+
counts.set(repo, (counts.get(repo) ?? 0) + 1);
|
|
66832
66939
|
}
|
|
66833
66940
|
resolver.flush();
|
|
66834
66941
|
} catch {}
|
|
66835
|
-
|
|
66942
|
+
for (const repo of current ?? [])
|
|
66943
|
+
if (!counts.has(repo))
|
|
66944
|
+
counts.set(repo, 0);
|
|
66945
|
+
return [...counts.entries()].sort((a, b) => b[1] - a[1]).map(([repo]) => repo);
|
|
66836
66946
|
}
|
|
66837
66947
|
async function runStudyingProjectsSetting() {
|
|
66838
|
-
const
|
|
66839
|
-
|
|
66840
|
-
|
|
66948
|
+
const state = loadSyncState();
|
|
66949
|
+
const current = studyRepoFilter(state, () => scanAgentSessions({}), createProjectDirResolver());
|
|
66950
|
+
const repos = discoverSessionRepos(current);
|
|
66951
|
+
if (repos.length === 0) {
|
|
66952
|
+
log2.info("No agent sessions inside a git repo found yet; nothing to scope.");
|
|
66841
66953
|
return;
|
|
66842
66954
|
}
|
|
66843
|
-
const current = loadSyncState().project_filter;
|
|
66844
66955
|
const selected = await multiselect2({
|
|
66845
|
-
message: "Study sessions from which
|
|
66846
|
-
options:
|
|
66847
|
-
|
|
66848
|
-
|
|
66849
|
-
|
|
66850
|
-
initialValues: current?.length ? current : dirs,
|
|
66851
|
-
summary: (picked) => picked.length === dirs.length ? "all folders · new ones included automatically" : `${picked.length} of ${dirs.length} folders · subfolders included`,
|
|
66852
|
-
validate: (picked) => picked.length === 0 ? "Select at least one folder." : undefined
|
|
66956
|
+
message: "Study sessions from which repos?",
|
|
66957
|
+
options: repos.map((repo) => ({ label: displayRepo(repo), hint: repo, value: repo })),
|
|
66958
|
+
initialValues: current ?? repos,
|
|
66959
|
+
summary: (picked) => picked.length === repos.length ? "all repos · new ones included automatically" : `${picked.length} of ${repos.length} repos`,
|
|
66960
|
+
validate: (picked) => picked.length === 0 ? "Select at least one repo." : undefined
|
|
66853
66961
|
});
|
|
66854
66962
|
if (isCancel2(selected))
|
|
66855
66963
|
return;
|
|
66856
|
-
const { project_filter:
|
|
66857
|
-
const all = selected.length ===
|
|
66858
|
-
saveSyncState(all ?
|
|
66859
|
-
const scope = all ? "all
|
|
66964
|
+
const { repo_filter: _repos, project_filter: _folders, ...fresh } = loadSyncState();
|
|
66965
|
+
const all = selected.length === repos.length;
|
|
66966
|
+
saveSyncState(all ? fresh : { ...fresh, repo_filter: [...selected] });
|
|
66967
|
+
const scope = all ? "all repos" : selected.map(displayRepo).join(", ");
|
|
66860
66968
|
log2.success(`Study scope ${dim(`· ${scope}`)}`);
|
|
66861
66969
|
}
|
|
66862
66970
|
async function handleAuthenticate(cfg) {
|
|
@@ -66909,6 +67017,7 @@ var init_tui = __esm(() => {
|
|
|
66909
67017
|
init_providers();
|
|
66910
67018
|
init_generate();
|
|
66911
67019
|
init_project_dir();
|
|
67020
|
+
init_repo();
|
|
66912
67021
|
init_scan();
|
|
66913
67022
|
init_agents_md_step();
|
|
66914
67023
|
init_flow2();
|
|
@@ -67631,7 +67740,7 @@ function createProgram(options = {}) {
|
|
|
67631
67740
|
const program = new Command;
|
|
67632
67741
|
let telemetryStarted = false;
|
|
67633
67742
|
let telemetryValidationFailure = false;
|
|
67634
|
-
program.name("dosu").description("Dosu CLI -
|
|
67743
|
+
program.name("dosu").description("Dosu CLI - Set up Dosu MCP for AI tools and work with Dosu Libraries, docs, reviews, and more").version(getVersionString(), "-v, --version").helpCommand("help [command]", "Show help for a command").option("--debug", "Enable debug logging to stderr", false).hook("preAction", async (thisCommand, actionCommand) => {
|
|
67635
67744
|
const opts = thisCommand.optsWithGlobals();
|
|
67636
67745
|
logger.init({ debug: opts.debug });
|
|
67637
67746
|
if (shouldRunBackgroundChecks(actionCommand.name())) {
|
|
@@ -68117,4 +68226,4 @@ main().catch((err) => {
|
|
|
68117
68226
|
process.exit(1);
|
|
68118
68227
|
});
|
|
68119
68228
|
|
|
68120
|
-
//# debugId=
|
|
68229
|
+
//# debugId=01f74cf6-cdbb-b296-6475-6e2164756e21
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dosu/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.63.0",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "Dosu CLI -
|
|
5
|
+
"description": "Dosu CLI - Set up Dosu MCP for AI tools and work with Dosu Libraries, docs, and reviews",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|