@dosu/cli 0.53.0-beta.9 → 0.53.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.
- package/README.md +1 -1
- package/bin/dosu.js +1214 -654
- package/package.json +1 -1
package/bin/dosu.js
CHANGED
|
@@ -5018,7 +5018,7 @@ function isNpxInvocation(channel = INSTALL_CHANNEL, env = process.env) {
|
|
|
5018
5018
|
function getVersionString() {
|
|
5019
5019
|
return `v${VERSION}`;
|
|
5020
5020
|
}
|
|
5021
|
-
var VERSION = "0.53.
|
|
5021
|
+
var VERSION = "0.53.1", INSTALL_CHANNEL = "npm";
|
|
5022
5022
|
|
|
5023
5023
|
// src/debug/logger.ts
|
|
5024
5024
|
import {
|
|
@@ -5712,12 +5712,34 @@ function answeredEcho(message, answer) {
|
|
|
5712
5712
|
function optionLabel(option) {
|
|
5713
5713
|
return option.label ?? String(option.value);
|
|
5714
5714
|
}
|
|
5715
|
+
function viewport(count, cursor3, top, rows, chrome) {
|
|
5716
|
+
const visible = Math.min(count, Math.max(4, (rows ?? 24) - chrome));
|
|
5717
|
+
let next = top;
|
|
5718
|
+
if (cursor3 < next)
|
|
5719
|
+
next = cursor3;
|
|
5720
|
+
if (cursor3 >= next + visible)
|
|
5721
|
+
next = cursor3 - visible + 1;
|
|
5722
|
+
next = Math.max(0, Math.min(next, count - visible));
|
|
5723
|
+
return { top: next, visible, above: next, below: count - next - visible };
|
|
5724
|
+
}
|
|
5725
|
+
function moreAbove(n2) {
|
|
5726
|
+
return n2 > 0 ? [import_picocolors11.default.dim(` ↑ ${n2} more`)] : [];
|
|
5727
|
+
}
|
|
5728
|
+
function moreBelow(n2) {
|
|
5729
|
+
return n2 > 0 ? [import_picocolors11.default.dim(` ↓ ${n2} more`)] : [];
|
|
5730
|
+
}
|
|
5715
5731
|
function select(opts, io = {}) {
|
|
5732
|
+
const { output } = resolveIO(io);
|
|
5716
5733
|
let selected = 0;
|
|
5734
|
+
let top = 0;
|
|
5717
5735
|
const labelWidth = Math.max(...opts.options.map((o2) => optionLabel(o2).length));
|
|
5718
5736
|
return runInteractive({
|
|
5719
5737
|
render() {
|
|
5720
|
-
const
|
|
5738
|
+
const count = opts.options.length;
|
|
5739
|
+
const view = viewport(count, selected, top, output.rows, 8);
|
|
5740
|
+
top = view.top;
|
|
5741
|
+
const rows = opts.options.slice(top, top + view.visible).map((option, offset) => {
|
|
5742
|
+
const index = top + offset;
|
|
5721
5743
|
const label = optionLabel(option).padEnd(labelWidth);
|
|
5722
5744
|
const hint = option.hint ? ` ${import_picocolors11.default.dim(option.hint)}` : "";
|
|
5723
5745
|
return index === selected ? `${brand(POINTER2)} ${import_picocolors11.default.bold(brand(label))}${hint}` : ` ${import_picocolors11.default.dim(label)}${hint}`;
|
|
@@ -5725,7 +5747,9 @@ function select(opts, io = {}) {
|
|
|
5725
5747
|
return [
|
|
5726
5748
|
import_picocolors11.default.bold(opts.message),
|
|
5727
5749
|
"",
|
|
5750
|
+
...moreAbove(view.above),
|
|
5728
5751
|
...rows,
|
|
5752
|
+
...moreBelow(view.below),
|
|
5729
5753
|
"",
|
|
5730
5754
|
import_picocolors11.default.dim(`↑↓ move ${DOT} enter select ${DOT} esc cancel`)
|
|
5731
5755
|
];
|
|
@@ -5763,13 +5787,9 @@ function multiselect2(opts, io = {}) {
|
|
|
5763
5787
|
return runInteractive({
|
|
5764
5788
|
render() {
|
|
5765
5789
|
const count = opts.options.length;
|
|
5766
|
-
const
|
|
5767
|
-
|
|
5768
|
-
|
|
5769
|
-
if (cursor3 >= top + visible)
|
|
5770
|
-
top = cursor3 - visible + 1;
|
|
5771
|
-
top = Math.max(0, Math.min(top, count - visible));
|
|
5772
|
-
const rows = opts.options.slice(top, top + visible).map((option, offset) => {
|
|
5790
|
+
const view = viewport(count, cursor3, top, output.rows, 9);
|
|
5791
|
+
top = view.top;
|
|
5792
|
+
const rows = opts.options.slice(top, top + view.visible).map((option, offset) => {
|
|
5773
5793
|
const index = top + offset;
|
|
5774
5794
|
const isPicked = picked.has(option.value);
|
|
5775
5795
|
const box = isPicked ? brand(BOX_ON) : import_picocolors11.default.dim(BOX_OFF);
|
|
@@ -5778,15 +5798,13 @@ function multiselect2(opts, io = {}) {
|
|
|
5778
5798
|
const suffix = status ? ` ${status}` : "";
|
|
5779
5799
|
return index === cursor3 ? `${brand(POINTER2)} ${box} ${import_picocolors11.default.bold(brand(label))}${suffix}` : ` ${box} ${import_picocolors11.default.dim(label)}${suffix}`;
|
|
5780
5800
|
});
|
|
5781
|
-
const above = top;
|
|
5782
|
-
const below = count - top - visible;
|
|
5783
5801
|
const summary = opts.summary?.(pickedValues());
|
|
5784
5802
|
return [
|
|
5785
5803
|
import_picocolors11.default.bold(opts.message),
|
|
5786
5804
|
"",
|
|
5787
|
-
...
|
|
5805
|
+
...moreAbove(view.above),
|
|
5788
5806
|
...rows,
|
|
5789
|
-
...
|
|
5807
|
+
...moreBelow(view.below),
|
|
5790
5808
|
"",
|
|
5791
5809
|
...error ? [import_picocolors11.default.yellow(error)] : summary ? [import_picocolors11.default.dim(summary)] : [],
|
|
5792
5810
|
import_picocolors11.default.dim(`↑↓ move ${DOT} space toggle ${DOT} a all ${DOT} enter confirm ${DOT} esc cancel`)
|
|
@@ -7982,6 +8000,14 @@ function mcpURL(deploymentID) {
|
|
|
7982
8000
|
function mcpBaseURL() {
|
|
7983
8001
|
return `${getBackendURL()}/v1/mcp`;
|
|
7984
8002
|
}
|
|
8003
|
+
function mcpEndpoint(cfg) {
|
|
8004
|
+
if (cfg.mode === MODE_OSS)
|
|
8005
|
+
return mcpBaseURL();
|
|
8006
|
+
const deploymentID = cfg.active_account?.target?.deployment_id;
|
|
8007
|
+
if (!deploymentID)
|
|
8008
|
+
throw new Error("deployment ID is required");
|
|
8009
|
+
return mcpURL(deploymentID);
|
|
8010
|
+
}
|
|
7985
8011
|
function mcpHeaders(apiKey) {
|
|
7986
8012
|
if (!apiKey) {
|
|
7987
8013
|
throw new Error("API key is required. Run 'dosu setup' to create one.");
|
|
@@ -8008,18 +8034,23 @@ function mcpRemoteServer(url, apiKey) {
|
|
|
8008
8034
|
};
|
|
8009
8035
|
}
|
|
8010
8036
|
function loadJSONConfig(path3) {
|
|
8037
|
+
try {
|
|
8038
|
+
return readJSONConfig(path3);
|
|
8039
|
+
} catch {
|
|
8040
|
+
return {};
|
|
8041
|
+
}
|
|
8042
|
+
}
|
|
8043
|
+
function readJSONConfig(path3) {
|
|
8011
8044
|
if (!existsSync7(path3))
|
|
8012
8045
|
return {};
|
|
8013
|
-
|
|
8014
|
-
if (!
|
|
8046
|
+
const raw = readFileSync7(path3, "utf-8").trim();
|
|
8047
|
+
if (!raw)
|
|
8015
8048
|
return {};
|
|
8016
|
-
if (path3.endsWith(".jsonc")) {
|
|
8017
|
-
data = stripJSONComments(data);
|
|
8018
|
-
}
|
|
8019
8049
|
try {
|
|
8020
|
-
return JSON.parse(
|
|
8021
|
-
} catch {
|
|
8022
|
-
|
|
8050
|
+
return JSON.parse(stripTrailingCommas(stripJSONComments(raw)));
|
|
8051
|
+
} catch (err) {
|
|
8052
|
+
const detail = err.message;
|
|
8053
|
+
throw new Error(`Could not parse ${path3} as JSON (${detail}). Fix the file and retry.`);
|
|
8023
8054
|
}
|
|
8024
8055
|
}
|
|
8025
8056
|
function stripJSONComments(data) {
|
|
@@ -8027,25 +8058,7 @@ function stripJSONComments(data) {
|
|
|
8027
8058
|
let i2 = 0;
|
|
8028
8059
|
while (i2 < data.length) {
|
|
8029
8060
|
if (data[i2] === '"') {
|
|
8030
|
-
|
|
8031
|
-
i2++;
|
|
8032
|
-
while (i2 < data.length && data[i2] !== '"') {
|
|
8033
|
-
if (data[i2] === "\\") {
|
|
8034
|
-
result.push(data[i2]);
|
|
8035
|
-
i2++;
|
|
8036
|
-
if (i2 < data.length) {
|
|
8037
|
-
result.push(data[i2]);
|
|
8038
|
-
i2++;
|
|
8039
|
-
}
|
|
8040
|
-
continue;
|
|
8041
|
-
}
|
|
8042
|
-
result.push(data[i2]);
|
|
8043
|
-
i2++;
|
|
8044
|
-
}
|
|
8045
|
-
if (i2 < data.length) {
|
|
8046
|
-
result.push(data[i2]);
|
|
8047
|
-
i2++;
|
|
8048
|
-
}
|
|
8061
|
+
i2 = copyStringLiteral(data, i2, result);
|
|
8049
8062
|
continue;
|
|
8050
8063
|
}
|
|
8051
8064
|
if (i2 + 1 < data.length && data[i2] === "/" && data[i2 + 1] === "/") {
|
|
@@ -8059,8 +8072,7 @@ function stripJSONComments(data) {
|
|
|
8059
8072
|
i2 += 2;
|
|
8060
8073
|
while (i2 + 1 < data.length && !(data[i2] === "*" && data[i2 + 1] === "/"))
|
|
8061
8074
|
i2++;
|
|
8062
|
-
|
|
8063
|
-
i2 += 2;
|
|
8075
|
+
i2 = i2 + 1 < data.length ? i2 + 2 : data.length;
|
|
8064
8076
|
continue;
|
|
8065
8077
|
}
|
|
8066
8078
|
result.push(data[i2]);
|
|
@@ -8068,6 +8080,43 @@ function stripJSONComments(data) {
|
|
|
8068
8080
|
}
|
|
8069
8081
|
return result.join("");
|
|
8070
8082
|
}
|
|
8083
|
+
function copyStringLiteral(data, start, out) {
|
|
8084
|
+
let i2 = start + 1;
|
|
8085
|
+
out.push('"');
|
|
8086
|
+
while (i2 < data.length && data[i2] !== '"') {
|
|
8087
|
+
out.push(data[i2]);
|
|
8088
|
+
if (data[i2] === "\\" && i2 + 1 < data.length)
|
|
8089
|
+
out.push(data[++i2]);
|
|
8090
|
+
i2++;
|
|
8091
|
+
}
|
|
8092
|
+
if (i2 < data.length) {
|
|
8093
|
+
out.push('"');
|
|
8094
|
+
i2++;
|
|
8095
|
+
}
|
|
8096
|
+
return i2;
|
|
8097
|
+
}
|
|
8098
|
+
function stripTrailingCommas(data) {
|
|
8099
|
+
const result = [];
|
|
8100
|
+
let i2 = 0;
|
|
8101
|
+
while (i2 < data.length) {
|
|
8102
|
+
if (data[i2] === '"') {
|
|
8103
|
+
i2 = copyStringLiteral(data, i2, result);
|
|
8104
|
+
continue;
|
|
8105
|
+
}
|
|
8106
|
+
if (data[i2] === ",") {
|
|
8107
|
+
let j = i2 + 1;
|
|
8108
|
+
while (j < data.length && /\s/.test(data[j]))
|
|
8109
|
+
j++;
|
|
8110
|
+
if (data[j] === "}" || data[j] === "]") {
|
|
8111
|
+
i2++;
|
|
8112
|
+
continue;
|
|
8113
|
+
}
|
|
8114
|
+
}
|
|
8115
|
+
result.push(data[i2]);
|
|
8116
|
+
i2++;
|
|
8117
|
+
}
|
|
8118
|
+
return result.join("");
|
|
8119
|
+
}
|
|
8071
8120
|
function saveJSONConfig(path3, cfg) {
|
|
8072
8121
|
writeSecureFile(path3, JSON.stringify(cfg, null, 2));
|
|
8073
8122
|
}
|
|
@@ -8086,7 +8135,7 @@ function isJSONKeyConfigured(configPath, topLevelKey) {
|
|
|
8086
8135
|
return "dosu" in section;
|
|
8087
8136
|
}
|
|
8088
8137
|
function installJSONServer(configPath, topKey, server) {
|
|
8089
|
-
const jsonCfg =
|
|
8138
|
+
const jsonCfg = readJSONConfig(configPath);
|
|
8090
8139
|
let section = jsonCfg[topKey];
|
|
8091
8140
|
if (typeof section !== "object" || section === null) {
|
|
8092
8141
|
section = {};
|
|
@@ -8098,18 +8147,19 @@ function installJSONServer(configPath, topKey, server) {
|
|
|
8098
8147
|
function removeJSONServer(configPath, topKey) {
|
|
8099
8148
|
let jsonCfg;
|
|
8100
8149
|
try {
|
|
8101
|
-
jsonCfg =
|
|
8150
|
+
jsonCfg = readJSONConfig(configPath);
|
|
8102
8151
|
} catch {
|
|
8103
8152
|
return;
|
|
8104
8153
|
}
|
|
8105
8154
|
const section = jsonCfg[topKey];
|
|
8106
|
-
if (typeof section
|
|
8107
|
-
|
|
8108
|
-
|
|
8155
|
+
if (typeof section !== "object" || section === null || !("dosu" in section))
|
|
8156
|
+
return;
|
|
8157
|
+
delete section.dosu;
|
|
8109
8158
|
saveJSONConfig(configPath, jsonCfg);
|
|
8110
8159
|
}
|
|
8111
8160
|
var import_write_file_atomic, writeFileAtomic, MCP_REMOTE_VERSION = "0.1.38";
|
|
8112
8161
|
var init_config_helpers = __esm(() => {
|
|
8162
|
+
init_config();
|
|
8113
8163
|
import_write_file_atomic = __toESM(require_lib(), 1);
|
|
8114
8164
|
writeFileAtomic = import_write_file_atomic.default;
|
|
8115
8165
|
});
|
|
@@ -8358,16 +8408,164 @@ var init_agents = __esm(() => {
|
|
|
8358
8408
|
init_formats();
|
|
8359
8409
|
});
|
|
8360
8410
|
|
|
8411
|
+
// src/sessions/project-dir.ts
|
|
8412
|
+
import {
|
|
8413
|
+
closeSync as closeSync2,
|
|
8414
|
+
existsSync as existsSync9,
|
|
8415
|
+
mkdirSync as mkdirSync6,
|
|
8416
|
+
openSync as openSync2,
|
|
8417
|
+
readFileSync as readFileSync9,
|
|
8418
|
+
readSync,
|
|
8419
|
+
renameSync as renameSync2,
|
|
8420
|
+
statSync as statSync3,
|
|
8421
|
+
writeFileSync as writeFileSync6
|
|
8422
|
+
} from "node:fs";
|
|
8423
|
+
import { join as join8 } from "node:path";
|
|
8424
|
+
function cachePath(configDir) {
|
|
8425
|
+
return join8(configDir, CACHE_FILENAME2);
|
|
8426
|
+
}
|
|
8427
|
+
function loadCacheFile(configDir) {
|
|
8428
|
+
try {
|
|
8429
|
+
const raw = JSON.parse(readFileSync9(cachePath(configDir), "utf-8"));
|
|
8430
|
+
if (raw.schema_version !== CACHE_SCHEMA_VERSION || typeof raw.entries !== "object")
|
|
8431
|
+
return {};
|
|
8432
|
+
return raw.entries;
|
|
8433
|
+
} catch {
|
|
8434
|
+
return {};
|
|
8435
|
+
}
|
|
8436
|
+
}
|
|
8437
|
+
function saveCacheFile(configDir, entries) {
|
|
8438
|
+
try {
|
|
8439
|
+
if (!existsSync9(configDir))
|
|
8440
|
+
mkdirSync6(configDir, { recursive: true, mode: 448 });
|
|
8441
|
+
const path3 = cachePath(configDir);
|
|
8442
|
+
const tmp = `${path3}.${process.pid}.tmp`;
|
|
8443
|
+
const file = { schema_version: CACHE_SCHEMA_VERSION, entries };
|
|
8444
|
+
writeFileSync6(tmp, JSON.stringify(file), { mode: 384 });
|
|
8445
|
+
renameSync2(tmp, path3);
|
|
8446
|
+
} catch {}
|
|
8447
|
+
}
|
|
8448
|
+
function readHead(path3, bytes = HEAD_BYTES) {
|
|
8449
|
+
let fd;
|
|
8450
|
+
try {
|
|
8451
|
+
fd = openSync2(path3, "r");
|
|
8452
|
+
} catch {
|
|
8453
|
+
return null;
|
|
8454
|
+
}
|
|
8455
|
+
try {
|
|
8456
|
+
const buf = Buffer.alloc(bytes);
|
|
8457
|
+
const read = readSync(fd, buf, 0, bytes, 0);
|
|
8458
|
+
return buf.subarray(0, read).toString("utf-8");
|
|
8459
|
+
} catch {
|
|
8460
|
+
return null;
|
|
8461
|
+
} finally {
|
|
8462
|
+
closeSync2(fd);
|
|
8463
|
+
}
|
|
8464
|
+
}
|
|
8465
|
+
function cwdFromJsonlHead(text) {
|
|
8466
|
+
for (const line of text.split(`
|
|
8467
|
+
`).slice(0, HEAD_LINES)) {
|
|
8468
|
+
if (!line.includes('"cwd"'))
|
|
8469
|
+
continue;
|
|
8470
|
+
try {
|
|
8471
|
+
const obj = JSON.parse(line);
|
|
8472
|
+
const cwd = obj.cwd ?? obj.payload?.cwd;
|
|
8473
|
+
if (typeof cwd === "string" && cwd.startsWith("/"))
|
|
8474
|
+
return cwd;
|
|
8475
|
+
} catch {}
|
|
8476
|
+
}
|
|
8477
|
+
return null;
|
|
8478
|
+
}
|
|
8479
|
+
function unmungeSlug(slug, exists = existsSync9) {
|
|
8480
|
+
const tokens = slug.replace(/^-/, "").split("-");
|
|
8481
|
+
if (tokens.length === 0 || tokens[0] === "")
|
|
8482
|
+
return null;
|
|
8483
|
+
let budget = 5000;
|
|
8484
|
+
const walk = (prefix, index) => {
|
|
8485
|
+
if (budget-- <= 0)
|
|
8486
|
+
return null;
|
|
8487
|
+
if (index === tokens.length)
|
|
8488
|
+
return exists(prefix) ? prefix : null;
|
|
8489
|
+
const asSegment = prefix === "" || exists(prefix) ? walk(`${prefix}/${tokens[index]}`, index + 1) : null;
|
|
8490
|
+
if (asSegment)
|
|
8491
|
+
return asSegment;
|
|
8492
|
+
return prefix === "" ? null : walk(`${prefix}-${tokens[index]}`, index + 1);
|
|
8493
|
+
};
|
|
8494
|
+
return walk("", 0);
|
|
8495
|
+
}
|
|
8496
|
+
function fileMtime(path3) {
|
|
8497
|
+
try {
|
|
8498
|
+
return statSync3(path3).mtime.toISOString();
|
|
8499
|
+
} catch {
|
|
8500
|
+
return "";
|
|
8501
|
+
}
|
|
8502
|
+
}
|
|
8503
|
+
function createProjectDirResolver(configDir = getConfigDir(), deps = {}) {
|
|
8504
|
+
const exists = deps.exists ?? existsSync9;
|
|
8505
|
+
const head = deps.readHead ?? readHead;
|
|
8506
|
+
const mtime = deps.mtime ?? fileMtime;
|
|
8507
|
+
const entries = loadCacheFile(configDir);
|
|
8508
|
+
let dirty = false;
|
|
8509
|
+
const compute = (session) => {
|
|
8510
|
+
switch (session.harness) {
|
|
8511
|
+
case "opencode":
|
|
8512
|
+
return session.project ?? null;
|
|
8513
|
+
case "claude": {
|
|
8514
|
+
const text = head(session.path);
|
|
8515
|
+
const cwd = text ? cwdFromJsonlHead(text) : null;
|
|
8516
|
+
if (cwd)
|
|
8517
|
+
return cwd;
|
|
8518
|
+
return session.project ? unmungeSlug(session.project, exists) : null;
|
|
8519
|
+
}
|
|
8520
|
+
case "codex": {
|
|
8521
|
+
const text = head(session.path);
|
|
8522
|
+
return text ? cwdFromJsonlHead(text) : null;
|
|
8523
|
+
}
|
|
8524
|
+
case "cursor":
|
|
8525
|
+
return session.project ? unmungeSlug(session.project, exists) : null;
|
|
8526
|
+
default:
|
|
8527
|
+
return null;
|
|
8528
|
+
}
|
|
8529
|
+
};
|
|
8530
|
+
return {
|
|
8531
|
+
cached(key) {
|
|
8532
|
+
return entries[key]?.dir ?? null;
|
|
8533
|
+
},
|
|
8534
|
+
resolve(session) {
|
|
8535
|
+
const key = `${session.harness}/${session.id}`;
|
|
8536
|
+
const cached = entries[key];
|
|
8537
|
+
if (cached && (cached.dir !== null || cached.mtime === mtime(session.path))) {
|
|
8538
|
+
return cached.dir;
|
|
8539
|
+
}
|
|
8540
|
+
const dir = compute(session);
|
|
8541
|
+
entries[key] = { dir, mtime: mtime(session.path) };
|
|
8542
|
+
dirty = true;
|
|
8543
|
+
return dir;
|
|
8544
|
+
},
|
|
8545
|
+
flush() {
|
|
8546
|
+
if (!dirty)
|
|
8547
|
+
return;
|
|
8548
|
+
saveCacheFile(configDir, entries);
|
|
8549
|
+
dirty = false;
|
|
8550
|
+
}
|
|
8551
|
+
};
|
|
8552
|
+
}
|
|
8553
|
+
var CACHE_FILENAME2 = "project-dirs.json", CACHE_SCHEMA_VERSION = 1, HEAD_BYTES, HEAD_LINES = 50;
|
|
8554
|
+
var init_project_dir = __esm(() => {
|
|
8555
|
+
init_config();
|
|
8556
|
+
HEAD_BYTES = 128 * 1024;
|
|
8557
|
+
});
|
|
8558
|
+
|
|
8361
8559
|
// src/sessions/scan.ts
|
|
8362
|
-
import { existsSync as
|
|
8560
|
+
import { existsSync as existsSync10, readdirSync, statSync as statSync4 } from "node:fs";
|
|
8363
8561
|
import { createRequire as createRequire2 } from "node:module";
|
|
8364
8562
|
import { homedir as homedir3 } from "node:os";
|
|
8365
|
-
import { basename, join as
|
|
8563
|
+
import { basename, join as join9 } from "node:path";
|
|
8366
8564
|
function listDir(dir) {
|
|
8367
8565
|
try {
|
|
8368
8566
|
return readdirSync(dir, { withFileTypes: true }).map((entry) => ({
|
|
8369
8567
|
name: entry.name,
|
|
8370
|
-
path:
|
|
8568
|
+
path: join9(dir, entry.name),
|
|
8371
8569
|
isDir: entry.isDirectory()
|
|
8372
8570
|
}));
|
|
8373
8571
|
} catch {
|
|
@@ -8380,7 +8578,7 @@ function sessionFromFile(filePath, harness, project) {
|
|
|
8380
8578
|
return null;
|
|
8381
8579
|
let mtime;
|
|
8382
8580
|
try {
|
|
8383
|
-
mtime =
|
|
8581
|
+
mtime = statSync4(filePath).mtime;
|
|
8384
8582
|
} catch {
|
|
8385
8583
|
return null;
|
|
8386
8584
|
}
|
|
@@ -8394,7 +8592,7 @@ function sessionFromFile(filePath, harness, project) {
|
|
|
8394
8592
|
}
|
|
8395
8593
|
function scanClaude(home) {
|
|
8396
8594
|
const sessions = [];
|
|
8397
|
-
for (const project of listDir(
|
|
8595
|
+
for (const project of listDir(join9(home, ".claude", "projects"))) {
|
|
8398
8596
|
if (!project.isDir)
|
|
8399
8597
|
continue;
|
|
8400
8598
|
for (const entry of listDir(project.path)) {
|
|
@@ -8409,10 +8607,10 @@ function scanClaude(home) {
|
|
|
8409
8607
|
}
|
|
8410
8608
|
function scanCursor(home) {
|
|
8411
8609
|
const sessions = [];
|
|
8412
|
-
for (const project of listDir(
|
|
8610
|
+
for (const project of listDir(join9(home, ".cursor", "projects"))) {
|
|
8413
8611
|
if (!project.isDir)
|
|
8414
8612
|
continue;
|
|
8415
|
-
for (const entry of listDir(
|
|
8613
|
+
for (const entry of listDir(join9(project.path, "agent-transcripts"))) {
|
|
8416
8614
|
const files = entry.isDir ? listDir(entry.path).filter((f2) => !f2.isDir) : [entry];
|
|
8417
8615
|
for (const file of files) {
|
|
8418
8616
|
const session = sessionFromFile(file.path, "cursor", project.name);
|
|
@@ -8424,9 +8622,9 @@ function scanCursor(home) {
|
|
|
8424
8622
|
return sessions;
|
|
8425
8623
|
}
|
|
8426
8624
|
function scanCodex(home, env) {
|
|
8427
|
-
const codexHome2 = env.CODEX_HOME ??
|
|
8625
|
+
const codexHome2 = env.CODEX_HOME ?? join9(home, ".codex");
|
|
8428
8626
|
const sessions = [];
|
|
8429
|
-
for (const year of listDir(
|
|
8627
|
+
for (const year of listDir(join9(codexHome2, "sessions"))) {
|
|
8430
8628
|
if (!year.isDir)
|
|
8431
8629
|
continue;
|
|
8432
8630
|
for (const month of listDir(year.path)) {
|
|
@@ -8482,9 +8680,9 @@ function querySqlite(dbPath, sql) {
|
|
|
8482
8680
|
}
|
|
8483
8681
|
}
|
|
8484
8682
|
function scanOpencode(home, env) {
|
|
8485
|
-
const dataDir = env.XDG_DATA_HOME ??
|
|
8486
|
-
const dbPath =
|
|
8487
|
-
if (!
|
|
8683
|
+
const dataDir = env.XDG_DATA_HOME ?? join9(home, ".local", "share");
|
|
8684
|
+
const dbPath = join9(dataDir, "opencode", "opencode.db");
|
|
8685
|
+
if (!existsSync10(dbPath))
|
|
8488
8686
|
return [];
|
|
8489
8687
|
const rows = querySqlite(dbPath, "SELECT id, directory, time_updated FROM session WHERE parent_id IS NULL");
|
|
8490
8688
|
if (!rows)
|
|
@@ -8522,10 +8720,10 @@ function scanAgentSessions(options = {}) {
|
|
|
8522
8720
|
var init_scan = () => {};
|
|
8523
8721
|
|
|
8524
8722
|
// src/sync/watermark.ts
|
|
8525
|
-
import { existsSync as
|
|
8526
|
-
import { join as
|
|
8723
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync7, readFileSync as readFileSync10, renameSync as renameSync3, writeFileSync as writeFileSync7 } from "node:fs";
|
|
8724
|
+
import { join as join10 } from "node:path";
|
|
8527
8725
|
function syncStatePath(configDir = getConfigDir()) {
|
|
8528
|
-
return
|
|
8726
|
+
return join10(configDir, STATE_FILENAME);
|
|
8529
8727
|
}
|
|
8530
8728
|
function loadSyncState(configDir = getConfigDir()) {
|
|
8531
8729
|
const empty = {
|
|
@@ -8534,13 +8732,13 @@ function loadSyncState(configDir = getConfigDir()) {
|
|
|
8534
8732
|
consecutive_failures: 0
|
|
8535
8733
|
};
|
|
8536
8734
|
const path3 = syncStatePath(configDir);
|
|
8537
|
-
if (!
|
|
8735
|
+
if (!existsSync11(path3))
|
|
8538
8736
|
return empty;
|
|
8539
8737
|
try {
|
|
8540
|
-
const raw = JSON.parse(
|
|
8738
|
+
const raw = JSON.parse(readFileSync10(path3, "utf-8"));
|
|
8541
8739
|
if (raw.schema_version !== STATE_SCHEMA_VERSION)
|
|
8542
8740
|
return empty;
|
|
8543
|
-
const
|
|
8741
|
+
const studiedSessions = Array.isArray(raw.mined_sessions) ? raw.mined_sessions.filter((record) => typeof record === "object" && record !== null && typeof record.at === "string" && typeof record.session === "string").map((record) => ({
|
|
8544
8742
|
at: record.at,
|
|
8545
8743
|
session: record.session,
|
|
8546
8744
|
...typeof record.project === "string" ? { project: record.project } : {}
|
|
@@ -8554,8 +8752,8 @@ function loadSyncState(configDir = getConfigDir()) {
|
|
|
8554
8752
|
watermark: typeof raw.watermark === "string" ? raw.watermark : null,
|
|
8555
8753
|
last_attempt_at: typeof raw.last_attempt_at === "string" ? raw.last_attempt_at : undefined,
|
|
8556
8754
|
consecutive_failures: typeof raw.consecutive_failures === "number" && raw.consecutive_failures >= 0 ? raw.consecutive_failures : 0,
|
|
8557
|
-
mined_sessions:
|
|
8558
|
-
total_mined: typeof raw.total_mined === "number" && raw.total_mined >= 0 ? raw.total_mined :
|
|
8755
|
+
mined_sessions: studiedSessions,
|
|
8756
|
+
total_mined: typeof raw.total_mined === "number" && raw.total_mined >= 0 ? raw.total_mined : studiedSessions.length,
|
|
8559
8757
|
total_notes: typeof raw.total_notes === "number" && raw.total_notes >= 0 ? raw.total_notes : 0,
|
|
8560
8758
|
total_learning_tokens: typeof raw.total_learning_tokens === "number" && raw.total_learning_tokens >= 0 ? raw.total_learning_tokens : 0,
|
|
8561
8759
|
...lastRefusal ? { last_refusal: lastRefusal } : {},
|
|
@@ -8577,13 +8775,24 @@ function setSyncPaused(paused, configDir = getConfigDir()) {
|
|
|
8577
8775
|
delete state.paused;
|
|
8578
8776
|
saveSyncState(state, configDir);
|
|
8579
8777
|
}
|
|
8778
|
+
function resetSyncState(configDir = getConfigDir()) {
|
|
8779
|
+
const previous = loadSyncState(configDir);
|
|
8780
|
+
const fresh = {
|
|
8781
|
+
schema_version: STATE_SCHEMA_VERSION,
|
|
8782
|
+
watermark: null,
|
|
8783
|
+
consecutive_failures: 0,
|
|
8784
|
+
...previous.project_filter ? { project_filter: previous.project_filter } : {},
|
|
8785
|
+
...previous.paused ? { paused: true } : {}
|
|
8786
|
+
};
|
|
8787
|
+
saveSyncState(fresh, configDir);
|
|
8788
|
+
}
|
|
8580
8789
|
function saveSyncState(state, configDir = getConfigDir()) {
|
|
8581
|
-
if (!
|
|
8582
|
-
|
|
8790
|
+
if (!existsSync11(configDir))
|
|
8791
|
+
mkdirSync7(configDir, { recursive: true, mode: 448 });
|
|
8583
8792
|
const path3 = syncStatePath(configDir);
|
|
8584
8793
|
const tmp = `${path3}.${process.pid}.tmp`;
|
|
8585
|
-
|
|
8586
|
-
|
|
8794
|
+
writeFileSync7(tmp, JSON.stringify(state, null, 2), { mode: 384 });
|
|
8795
|
+
renameSync3(tmp, path3);
|
|
8587
8796
|
}
|
|
8588
8797
|
function backoffUntil(state) {
|
|
8589
8798
|
if (state.consecutive_failures === 0 || !state.last_attempt_at)
|
|
@@ -8627,7 +8836,7 @@ function gateSessions(sessions, watermark, now = new Date, quietPeriodMs = DEFAU
|
|
|
8627
8836
|
}
|
|
8628
8837
|
return { ready, open: open2 };
|
|
8629
8838
|
}
|
|
8630
|
-
var STATE_FILENAME = "knowledge-sync.json", STATE_SCHEMA_VERSION = 1, DEFAULT_QUIET_PERIOD_MS, BACKOFF_BASE_MS, BACKOFF_MAX_MS,
|
|
8839
|
+
var STATE_FILENAME = "knowledge-sync.json", STATE_SCHEMA_VERSION = 1, DEFAULT_QUIET_PERIOD_MS, BACKOFF_BASE_MS, BACKOFF_MAX_MS, STUDIED_HISTORY_LIMIT = 500, UNKNOWN_PROJECT = "(unknown)";
|
|
8631
8840
|
var init_watermark = __esm(() => {
|
|
8632
8841
|
init_config();
|
|
8633
8842
|
DEFAULT_QUIET_PERIOD_MS = 5 * 60 * 1000;
|
|
@@ -8643,20 +8852,34 @@ async function fetchReportNotes(cfg) {
|
|
|
8643
8852
|
}
|
|
8644
8853
|
const { createTypedClient: createTypedClient2 } = await Promise.resolve().then(() => (init_trpc(), exports_trpc));
|
|
8645
8854
|
const client = createTypedClient2(cfg);
|
|
8646
|
-
const
|
|
8647
|
-
|
|
8648
|
-
|
|
8649
|
-
|
|
8650
|
-
|
|
8651
|
-
|
|
8652
|
-
|
|
8653
|
-
|
|
8654
|
-
|
|
8655
|
-
...
|
|
8656
|
-
|
|
8657
|
-
|
|
8855
|
+
const notes = [];
|
|
8856
|
+
let cursor3;
|
|
8857
|
+
let truncated = false;
|
|
8858
|
+
for (let page = 0;page < REPORT_NOTES_MAX_PAGES; page++) {
|
|
8859
|
+
const result = await client.notes.listMine.query({
|
|
8860
|
+
org_id: orgId,
|
|
8861
|
+
limit: REPORT_NOTES_LIMIT,
|
|
8862
|
+
...cursor3 ? { cursor: cursor3 } : {}
|
|
8863
|
+
});
|
|
8864
|
+
notes.push(...result.notes.map((note) => ({
|
|
8865
|
+
title: note.title,
|
|
8866
|
+
content: note.body,
|
|
8867
|
+
at: note.created_at,
|
|
8868
|
+
...note.transcript_id ? { transcript_id: note.transcript_id } : {},
|
|
8869
|
+
...note.repo ? { repo: note.repo } : {},
|
|
8870
|
+
...note.branch ? { branch: note.branch } : {}
|
|
8871
|
+
})));
|
|
8872
|
+
cursor3 = result.next_cursor;
|
|
8873
|
+
if (!cursor3) {
|
|
8874
|
+
truncated = result.notes.length === REPORT_NOTES_LIMIT;
|
|
8875
|
+
break;
|
|
8876
|
+
}
|
|
8877
|
+
if (page === REPORT_NOTES_MAX_PAGES - 1)
|
|
8878
|
+
truncated = true;
|
|
8879
|
+
}
|
|
8880
|
+
return { notes, truncated };
|
|
8658
8881
|
}
|
|
8659
|
-
var REPORT_NOTES_LIMIT = 500;
|
|
8882
|
+
var REPORT_NOTES_LIMIT = 500, REPORT_NOTES_MAX_PAGES = 40;
|
|
8660
8883
|
|
|
8661
8884
|
// src/report/css.ts
|
|
8662
8885
|
var REPORT_CSS = `
|
|
@@ -8831,38 +9054,6 @@ function applyStatusDefaults(candidates, dryRun) {
|
|
|
8831
9054
|
return { ...c2, status: known.has(current) ? current : fallback };
|
|
8832
9055
|
});
|
|
8833
9056
|
}
|
|
8834
|
-
function tokenTotalsFromCandidates(candidates, inventory) {
|
|
8835
|
-
if (candidates.length === 0)
|
|
8836
|
-
return null;
|
|
8837
|
-
let replaced = 0;
|
|
8838
|
-
let hasRediscovery = false;
|
|
8839
|
-
let readCost = 0;
|
|
8840
|
-
for (const c2 of candidates) {
|
|
8841
|
-
const raw = c2.approx_rediscovery_tokens;
|
|
8842
|
-
if (raw != null) {
|
|
8843
|
-
hasRediscovery = true;
|
|
8844
|
-
replaced += Math.max(0, Number(raw) || 0);
|
|
8845
|
-
}
|
|
8846
|
-
const blob = `${c2.title ?? ""}
|
|
8847
|
-
${c2.content ?? ""}`;
|
|
8848
|
-
readCost += Math.round(blob.length / CHARS_PER_TOKEN);
|
|
8849
|
-
}
|
|
8850
|
-
if (!hasRediscovery)
|
|
8851
|
-
return null;
|
|
8852
|
-
let baseline = inventory.totals?.learning_tokens ?? 0;
|
|
8853
|
-
if (!baseline) {
|
|
8854
|
-
baseline = inventory.transcripts.reduce((sum, t2) => sum + (t2.learning_tokens || 0), 0);
|
|
8855
|
-
}
|
|
8856
|
-
const saved = Math.max(0, replaced - readCost);
|
|
8857
|
-
const pct = baseline ? Math.round(1000 * saved / baseline) / 10 : 0;
|
|
8858
|
-
return {
|
|
8859
|
-
baseline_tokens: baseline,
|
|
8860
|
-
replaced_baseline_tokens: replaced,
|
|
8861
|
-
read_knowledge_tokens: readCost,
|
|
8862
|
-
tokens_saved: saved,
|
|
8863
|
-
pct_saved: pct
|
|
8864
|
-
};
|
|
8865
|
-
}
|
|
8866
9057
|
function presentationCopy(c2) {
|
|
8867
9058
|
const idea = (c2.plain_english || "").trim() || (c2.content || "").trim();
|
|
8868
9059
|
const how = (c2.how_found || "").trim();
|
|
@@ -9236,11 +9427,9 @@ function buildReportHtml(options) {
|
|
|
9236
9427
|
return bv - av;
|
|
9237
9428
|
});
|
|
9238
9429
|
const org = options.orgName || "Your team";
|
|
9239
|
-
const
|
|
9240
|
-
const
|
|
9241
|
-
const summary = options.summary || "Local agent session logs were mined into Dosu notes so the next task can reuse them — reducing rediscovery cost.";
|
|
9430
|
+
const projects = options.projects ?? [];
|
|
9431
|
+
const summary = options.summary || "Local agent session logs were studied and distilled into Dosu notes so the next task can reuse them — reducing rediscovery cost.";
|
|
9242
9432
|
const generated = `${(options.generatedAt ?? new Date).toISOString().replace("T", " ").slice(0, 16)} UTC`;
|
|
9243
|
-
const derived = tokenTotalsFromCandidates(candidates, inventory);
|
|
9244
9433
|
const [notesHeading, notesLede, footerTitle, footerBody] = notesSectionCopy(candidates);
|
|
9245
9434
|
const candidateRows = candidates.map((c2, i2) => {
|
|
9246
9435
|
const [idea, work] = presentationCopy(c2);
|
|
@@ -9280,27 +9469,19 @@ function buildReportHtml(options) {
|
|
|
9280
9469
|
<td>${esc(query)}</td>
|
|
9281
9470
|
</tr>`;
|
|
9282
9471
|
}).join("");
|
|
9283
|
-
|
|
9284
|
-
|
|
9285
|
-
|
|
9472
|
+
const scannedTokens = inventory.totals?.learning_tokens || transcripts.reduce((sum, t2) => sum + (t2.learning_tokens || 0), 0);
|
|
9473
|
+
const writtenCount = candidates.filter((c2) => (c2.status || "written").toLowerCase() === "written").length;
|
|
9474
|
+
const writtenLabel = options.dryRun ? "Notes proposed (dry run)" : "Notes written to Dosu";
|
|
9475
|
+
const writtenValue = options.dryRun ? candidates.length : writtenCount;
|
|
9476
|
+
const tokenSection = `
|
|
9286
9477
|
<section>
|
|
9287
|
-
<h2>
|
|
9288
|
-
<p class="lede">Counterfactual: replace rediscovery stretches with a Dosu <code>read_knowledge</code> hit.</p>
|
|
9478
|
+
<h2>This run</h2>
|
|
9289
9479
|
<div class="stats">
|
|
9290
|
-
<div class="stat"><div class="label">
|
|
9291
|
-
<div class="stat"><div class="label"
|
|
9292
|
-
<div class="stat"><div class="label">Read cost</div><div class="value">${fmtInt(derived.read_knowledge_tokens)}</div></div>
|
|
9293
|
-
<div class="stat highlight"><div class="label">Est. tokens saved</div><div class="value">${fmtInt(derived.tokens_saved)} <span class="pct">(${esc(`${derived.pct_saved}%`)})</span></div></div>
|
|
9480
|
+
<div class="stat"><div class="label">Tokens scanned</div><div class="value">${fmtInt(scannedTokens)}</div></div>
|
|
9481
|
+
<div class="stat highlight"><div class="label">${esc(writtenLabel)}</div><div class="value">${fmtInt(writtenValue)}</div></div>
|
|
9294
9482
|
</div>
|
|
9295
9483
|
</section>`;
|
|
9296
|
-
|
|
9297
|
-
tokenSection = `
|
|
9298
|
-
<section>
|
|
9299
|
-
<h2>Estimated context savings</h2>
|
|
9300
|
-
<p class="muted">No rediscovery estimates on the notes yet — each candidate needs <code>approx_rediscovery_tokens</code>.</p>
|
|
9301
|
-
</section>`;
|
|
9302
|
-
}
|
|
9303
|
-
const notesBody = candidateRows.join("") || '<p class="muted">No write_knowledge payloads yet. Run knowledge sync so the miner extracts learnings.</p>';
|
|
9484
|
+
const notesBody = candidateRows.join("") || '<p class="muted">No write_knowledge payloads yet. Run knowledge sync so Dosu studies your sessions and extracts learnings.</p>';
|
|
9304
9485
|
const lede = notesLede ? `<p class="lede">${esc(notesLede)}</p>` : "";
|
|
9305
9486
|
return `<!DOCTYPE html>
|
|
9306
9487
|
<html lang="en">
|
|
@@ -9319,9 +9500,8 @@ ${REPORT_CSS}
|
|
|
9319
9500
|
<h1>${esc(org)}</h1>
|
|
9320
9501
|
<p class="lede">${esc(summary)}</p>
|
|
9321
9502
|
<p class="meta-line">
|
|
9322
|
-
|
|
9323
|
-
·
|
|
9324
|
-
· generated ${esc(generated)}
|
|
9503
|
+
${projects.length > 0 ? `Projects ${projects.map((p2) => `<code>${esc(p2)}</code>`).join(", ")} · ` : ""}${transcripts.length} session${transcripts.length === 1 ? "" : "s"} with notes
|
|
9504
|
+
· generated ${esc(generated)}${options.truncated ? " · newest notes only (server capped the fetch)" : ""}
|
|
9325
9505
|
</p>
|
|
9326
9506
|
<div class="toolbar no-print">
|
|
9327
9507
|
<button type="button" onclick="window.print()">Print / Save as PDF</button>
|
|
@@ -9367,7 +9547,7 @@ ${REPORT_CSS}
|
|
|
9367
9547
|
</html>
|
|
9368
9548
|
`;
|
|
9369
9549
|
}
|
|
9370
|
-
var
|
|
9550
|
+
var MAX_TRACE_STEPS = 50, PREVIEW_CHARS = 80, SQL_DISPLAY, LOGS_DISPLAY, SKIP_TOOL_NAMES, TAG_RE, WS_RE;
|
|
9371
9551
|
var init_html = __esm(() => {
|
|
9372
9552
|
SQL_DISPLAY = new Set(["execute_sql", "query_run", "list_tables"]);
|
|
9373
9553
|
LOGS_DISPLAY = new Set(["query_logs"]);
|
|
@@ -9377,7 +9557,7 @@ var init_html = __esm(() => {
|
|
|
9377
9557
|
});
|
|
9378
9558
|
|
|
9379
9559
|
// src/sessions/read.ts
|
|
9380
|
-
import { readFileSync as
|
|
9560
|
+
import { readFileSync as readFileSync11 } from "node:fs";
|
|
9381
9561
|
function asRecord(value) {
|
|
9382
9562
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
9383
9563
|
}
|
|
@@ -9493,11 +9673,11 @@ function readSessionTurns(session) {
|
|
|
9493
9673
|
try {
|
|
9494
9674
|
switch (session.harness) {
|
|
9495
9675
|
case "claude":
|
|
9496
|
-
return readClaude(
|
|
9676
|
+
return readClaude(readFileSync11(session.path, "utf8"));
|
|
9497
9677
|
case "cursor":
|
|
9498
|
-
return readCursor(
|
|
9678
|
+
return readCursor(readFileSync11(session.path, "utf8"));
|
|
9499
9679
|
case "codex":
|
|
9500
|
-
return readCodex(
|
|
9680
|
+
return readCodex(readFileSync11(session.path, "utf8"));
|
|
9501
9681
|
case "opencode":
|
|
9502
9682
|
return readOpencode(session.path, session.id);
|
|
9503
9683
|
}
|
|
@@ -9525,7 +9705,7 @@ function countRediscoveryToolCalls(session) {
|
|
|
9525
9705
|
case "claude":
|
|
9526
9706
|
case "cursor": {
|
|
9527
9707
|
let n2 = 0;
|
|
9528
|
-
for (const record of jsonlRecords(
|
|
9708
|
+
for (const record of jsonlRecords(readFileSync11(session.path, "utf8"))) {
|
|
9529
9709
|
const message = asRecord(record.message);
|
|
9530
9710
|
for (const name of toolNamesFromContent(message?.content)) {
|
|
9531
9711
|
if (isRediscoveryTool(name))
|
|
@@ -9536,7 +9716,7 @@ function countRediscoveryToolCalls(session) {
|
|
|
9536
9716
|
}
|
|
9537
9717
|
case "codex": {
|
|
9538
9718
|
let n2 = 0;
|
|
9539
|
-
for (const record of jsonlRecords(
|
|
9719
|
+
for (const record of jsonlRecords(readFileSync11(session.path, "utf8"))) {
|
|
9540
9720
|
const payload = asRecord(record.payload);
|
|
9541
9721
|
if (payload?.type === "function_call" && typeof payload.name === "string") {
|
|
9542
9722
|
if (isRediscoveryTool(payload.name))
|
|
@@ -9556,9 +9736,9 @@ function estimateSessionTokens(session) {
|
|
|
9556
9736
|
let chars = 0;
|
|
9557
9737
|
for (const turn of readSessionTurns(session))
|
|
9558
9738
|
chars += turn.text.length;
|
|
9559
|
-
return Math.round(chars /
|
|
9739
|
+
return Math.round(chars / CHARS_PER_TOKEN);
|
|
9560
9740
|
}
|
|
9561
|
-
function
|
|
9741
|
+
function isWorthStudying(session) {
|
|
9562
9742
|
const turns = readSessionTurns(session);
|
|
9563
9743
|
if (turns.length < MIN_WORTH_TURNS)
|
|
9564
9744
|
return false;
|
|
@@ -9570,7 +9750,7 @@ function isWorthMining(session) {
|
|
|
9570
9750
|
}
|
|
9571
9751
|
return false;
|
|
9572
9752
|
}
|
|
9573
|
-
var PLAIN_TEXT, CODEX_TEXT, REDISCOVERY_TOOLS,
|
|
9753
|
+
var PLAIN_TEXT, CODEX_TEXT, REDISCOVERY_TOOLS, CHARS_PER_TOKEN = 4, MIN_WORTH_TURNS = 4, MIN_WORTH_CHARS = 2000;
|
|
9574
9754
|
var init_read = __esm(() => {
|
|
9575
9755
|
init_scan();
|
|
9576
9756
|
PLAIN_TEXT = new Set(["text"]);
|
|
@@ -9627,12 +9807,12 @@ var init_queries = __esm(() => {
|
|
|
9627
9807
|
});
|
|
9628
9808
|
|
|
9629
9809
|
// src/report/digest.ts
|
|
9630
|
-
import { readFileSync as
|
|
9810
|
+
import { readFileSync as readFileSync12 } from "node:fs";
|
|
9631
9811
|
function asRecord2(value) {
|
|
9632
9812
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
9633
9813
|
}
|
|
9634
9814
|
function estimateTokens(text) {
|
|
9635
|
-
return Math.round(text.length /
|
|
9815
|
+
return Math.round(text.length / CHARS_PER_TOKEN2);
|
|
9636
9816
|
}
|
|
9637
9817
|
function digestEstTokens(texts, tools, resultChars) {
|
|
9638
9818
|
const countable = tools.filter((t2) => t2.name !== "tool_result");
|
|
@@ -9642,7 +9822,7 @@ function digestEstTokens(texts, tools, resultChars) {
|
|
|
9642
9822
|
payload += JSON.stringify(countable);
|
|
9643
9823
|
let tokens = estimateTokens(payload);
|
|
9644
9824
|
if (resultChars > 0)
|
|
9645
|
-
tokens += Math.max(1, Math.round(resultChars /
|
|
9825
|
+
tokens += Math.max(1, Math.round(resultChars / CHARS_PER_TOKEN2));
|
|
9646
9826
|
return tokens;
|
|
9647
9827
|
}
|
|
9648
9828
|
function copyToolInputFields(entry, inp) {
|
|
@@ -9858,16 +10038,16 @@ function sessionToDigest(session) {
|
|
|
9858
10038
|
try {
|
|
9859
10039
|
switch (session.harness) {
|
|
9860
10040
|
case "claude":
|
|
9861
|
-
return { turns: digestClaude(
|
|
10041
|
+
return { turns: digestClaude(readFileSync12(session.path, "utf8")) };
|
|
9862
10042
|
case "cursor":
|
|
9863
|
-
return { turns: digestCursor(
|
|
10043
|
+
return { turns: digestCursor(readFileSync12(session.path, "utf8")) };
|
|
9864
10044
|
case "codex":
|
|
9865
|
-
return { turns: digestCodex(
|
|
10045
|
+
return { turns: digestCodex(readFileSync12(session.path, "utf8")) };
|
|
9866
10046
|
case "opencode": {
|
|
9867
10047
|
const turns = readSessionTurns(session).map((turn, index) => ({
|
|
9868
10048
|
role: turn.role,
|
|
9869
10049
|
line: index + 1,
|
|
9870
|
-
est_tokens: Math.round(turn.text.length /
|
|
10050
|
+
est_tokens: Math.round(turn.text.length / CHARS_PER_TOKEN2),
|
|
9871
10051
|
text: [turn.text],
|
|
9872
10052
|
tools: []
|
|
9873
10053
|
}));
|
|
@@ -9892,7 +10072,7 @@ function digestTurnText(turn) {
|
|
|
9892
10072
|
return texts.filter(Boolean).join(`
|
|
9893
10073
|
`);
|
|
9894
10074
|
}
|
|
9895
|
-
var
|
|
10075
|
+
var CHARS_PER_TOKEN2 = 4, MAX_ASSISTANT_CHARS = 4000, MCP_DIGEST_TOOLS;
|
|
9896
10076
|
var init_digest = __esm(() => {
|
|
9897
10077
|
init_read();
|
|
9898
10078
|
init_queries();
|
|
@@ -10083,17 +10263,17 @@ var init_notes = __esm(() => {
|
|
|
10083
10263
|
});
|
|
10084
10264
|
|
|
10085
10265
|
// src/report/write.ts
|
|
10086
|
-
import { mkdirSync as
|
|
10266
|
+
import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync8 } from "node:fs";
|
|
10087
10267
|
import { tmpdir } from "node:os";
|
|
10088
|
-
import { dirname as dirname4, join as
|
|
10268
|
+
import { dirname as dirname4, join as join11, resolve } from "node:path";
|
|
10089
10269
|
import { pathToFileURL } from "node:url";
|
|
10090
10270
|
function defaultReportPath() {
|
|
10091
|
-
return
|
|
10271
|
+
return join11(tmpdir(), "dosu-knowledge-report.html");
|
|
10092
10272
|
}
|
|
10093
10273
|
async function writeAndOpenReport(options) {
|
|
10094
10274
|
const out = resolve(options.out ?? defaultReportPath());
|
|
10095
|
-
|
|
10096
|
-
|
|
10275
|
+
mkdirSync8(dirname4(out), { recursive: true });
|
|
10276
|
+
writeFileSync8(out, options.html, "utf8");
|
|
10097
10277
|
if (options.open !== false) {
|
|
10098
10278
|
const url = pathToFileURL(out).href;
|
|
10099
10279
|
if (options.openUrl) {
|
|
@@ -10108,17 +10288,26 @@ async function writeAndOpenReport(options) {
|
|
|
10108
10288
|
var init_write = () => {};
|
|
10109
10289
|
|
|
10110
10290
|
// src/report/generate.ts
|
|
10291
|
+
import { basename as basename2 } from "node:path";
|
|
10111
10292
|
function sessionsForNotes(notes, sessions) {
|
|
10112
10293
|
const ids = new Set(notes.map((n2) => n2.transcript_id).filter((id) => Boolean(id)));
|
|
10113
10294
|
return sessions.filter((s) => ids.has(s.id));
|
|
10114
10295
|
}
|
|
10296
|
+
function defaultProjectName() {
|
|
10297
|
+
const resolver = createProjectDirResolver();
|
|
10298
|
+
return (session) => {
|
|
10299
|
+
const dir = resolver.resolve(session);
|
|
10300
|
+
return dir ? basename2(dir) : null;
|
|
10301
|
+
};
|
|
10302
|
+
}
|
|
10115
10303
|
function noteTime(note) {
|
|
10116
10304
|
const parsed = Date.parse(note.at ?? "");
|
|
10117
10305
|
return Number.isNaN(parsed) ? 0 : parsed;
|
|
10118
10306
|
}
|
|
10119
10307
|
async function emitKnowledgeReport(options = {}) {
|
|
10120
10308
|
const cfg = loadConfig();
|
|
10121
|
-
const
|
|
10309
|
+
const fetched = options.notes ? { notes: options.notes, truncated: false } : await (options.fetchNotes ?? fetchReportNotes)(cfg);
|
|
10310
|
+
const notes = [...fetched.notes].sort((a3, b2) => noteTime(a3) - noteTime(b2));
|
|
10122
10311
|
const scanned = options.sessions ?? scanAgentSessions();
|
|
10123
10312
|
const sessions = sessionsForNotes(notes, scanned);
|
|
10124
10313
|
const candidates = attributeRediscovery(notes, sessions);
|
|
@@ -10127,12 +10316,16 @@ async function emitKnowledgeReport(options = {}) {
|
|
|
10127
10316
|
if (inventory.totals && state.total_learning_tokens && !options.sessions) {
|
|
10128
10317
|
inventory.totals.learning_tokens = state.total_learning_tokens;
|
|
10129
10318
|
}
|
|
10319
|
+
const projectName = options.projectName ?? defaultProjectName();
|
|
10320
|
+
const projects = [
|
|
10321
|
+
...new Set(sessions.map((s) => projectName(s) ?? s.project).filter((name) => Boolean(name)))
|
|
10322
|
+
];
|
|
10130
10323
|
const html = buildReportHtml({
|
|
10131
10324
|
inventory,
|
|
10132
10325
|
candidates,
|
|
10133
10326
|
orgName: options.orgName ?? cfg.active_account?.target?.org_name ?? "Your team",
|
|
10134
|
-
|
|
10135
|
-
|
|
10327
|
+
projects,
|
|
10328
|
+
truncated: fetched.truncated,
|
|
10136
10329
|
generatedAt: options.generatedAt,
|
|
10137
10330
|
digests: digestsForSessions(sessions)
|
|
10138
10331
|
});
|
|
@@ -10145,6 +10338,7 @@ async function emitKnowledgeReport(options = {}) {
|
|
|
10145
10338
|
}
|
|
10146
10339
|
var init_generate = __esm(() => {
|
|
10147
10340
|
init_config();
|
|
10341
|
+
init_project_dir();
|
|
10148
10342
|
init_scan();
|
|
10149
10343
|
init_watermark();
|
|
10150
10344
|
init_html();
|
|
@@ -10152,151 +10346,6 @@ var init_generate = __esm(() => {
|
|
|
10152
10346
|
init_write();
|
|
10153
10347
|
});
|
|
10154
10348
|
|
|
10155
|
-
// src/sessions/project-dir.ts
|
|
10156
|
-
import {
|
|
10157
|
-
closeSync as closeSync2,
|
|
10158
|
-
existsSync as existsSync11,
|
|
10159
|
-
mkdirSync as mkdirSync8,
|
|
10160
|
-
openSync as openSync2,
|
|
10161
|
-
readFileSync as readFileSync12,
|
|
10162
|
-
readSync,
|
|
10163
|
-
renameSync as renameSync3,
|
|
10164
|
-
statSync as statSync4,
|
|
10165
|
-
writeFileSync as writeFileSync8
|
|
10166
|
-
} from "node:fs";
|
|
10167
|
-
import { join as join11 } from "node:path";
|
|
10168
|
-
function cachePath(configDir) {
|
|
10169
|
-
return join11(configDir, CACHE_FILENAME2);
|
|
10170
|
-
}
|
|
10171
|
-
function loadCacheFile(configDir) {
|
|
10172
|
-
try {
|
|
10173
|
-
const raw = JSON.parse(readFileSync12(cachePath(configDir), "utf-8"));
|
|
10174
|
-
if (raw.schema_version !== CACHE_SCHEMA_VERSION || typeof raw.entries !== "object")
|
|
10175
|
-
return {};
|
|
10176
|
-
return raw.entries;
|
|
10177
|
-
} catch {
|
|
10178
|
-
return {};
|
|
10179
|
-
}
|
|
10180
|
-
}
|
|
10181
|
-
function saveCacheFile(configDir, entries) {
|
|
10182
|
-
try {
|
|
10183
|
-
if (!existsSync11(configDir))
|
|
10184
|
-
mkdirSync8(configDir, { recursive: true, mode: 448 });
|
|
10185
|
-
const path3 = cachePath(configDir);
|
|
10186
|
-
const tmp = `${path3}.${process.pid}.tmp`;
|
|
10187
|
-
const file = { schema_version: CACHE_SCHEMA_VERSION, entries };
|
|
10188
|
-
writeFileSync8(tmp, JSON.stringify(file), { mode: 384 });
|
|
10189
|
-
renameSync3(tmp, path3);
|
|
10190
|
-
} catch {}
|
|
10191
|
-
}
|
|
10192
|
-
function readHead(path3, bytes = HEAD_BYTES) {
|
|
10193
|
-
let fd;
|
|
10194
|
-
try {
|
|
10195
|
-
fd = openSync2(path3, "r");
|
|
10196
|
-
} catch {
|
|
10197
|
-
return null;
|
|
10198
|
-
}
|
|
10199
|
-
try {
|
|
10200
|
-
const buf = Buffer.alloc(bytes);
|
|
10201
|
-
const read = readSync(fd, buf, 0, bytes, 0);
|
|
10202
|
-
return buf.subarray(0, read).toString("utf-8");
|
|
10203
|
-
} catch {
|
|
10204
|
-
return null;
|
|
10205
|
-
} finally {
|
|
10206
|
-
closeSync2(fd);
|
|
10207
|
-
}
|
|
10208
|
-
}
|
|
10209
|
-
function cwdFromJsonlHead(text) {
|
|
10210
|
-
for (const line of text.split(`
|
|
10211
|
-
`).slice(0, HEAD_LINES)) {
|
|
10212
|
-
if (!line.includes('"cwd"'))
|
|
10213
|
-
continue;
|
|
10214
|
-
try {
|
|
10215
|
-
const obj = JSON.parse(line);
|
|
10216
|
-
const cwd = obj.cwd ?? obj.payload?.cwd;
|
|
10217
|
-
if (typeof cwd === "string" && cwd.startsWith("/"))
|
|
10218
|
-
return cwd;
|
|
10219
|
-
} catch {}
|
|
10220
|
-
}
|
|
10221
|
-
return null;
|
|
10222
|
-
}
|
|
10223
|
-
function unmungeSlug(slug, exists = existsSync11) {
|
|
10224
|
-
const tokens = slug.replace(/^-/, "").split("-");
|
|
10225
|
-
if (tokens.length === 0 || tokens[0] === "")
|
|
10226
|
-
return null;
|
|
10227
|
-
let budget = 5000;
|
|
10228
|
-
const walk = (prefix, index) => {
|
|
10229
|
-
if (budget-- <= 0)
|
|
10230
|
-
return null;
|
|
10231
|
-
if (index === tokens.length)
|
|
10232
|
-
return exists(prefix) ? prefix : null;
|
|
10233
|
-
const asSegment = prefix === "" || exists(prefix) ? walk(`${prefix}/${tokens[index]}`, index + 1) : null;
|
|
10234
|
-
if (asSegment)
|
|
10235
|
-
return asSegment;
|
|
10236
|
-
return prefix === "" ? null : walk(`${prefix}-${tokens[index]}`, index + 1);
|
|
10237
|
-
};
|
|
10238
|
-
return walk("", 0);
|
|
10239
|
-
}
|
|
10240
|
-
function fileMtime(path3) {
|
|
10241
|
-
try {
|
|
10242
|
-
return statSync4(path3).mtime.toISOString();
|
|
10243
|
-
} catch {
|
|
10244
|
-
return "";
|
|
10245
|
-
}
|
|
10246
|
-
}
|
|
10247
|
-
function createProjectDirResolver(configDir = getConfigDir(), deps = {}) {
|
|
10248
|
-
const exists = deps.exists ?? existsSync11;
|
|
10249
|
-
const head = deps.readHead ?? readHead;
|
|
10250
|
-
const mtime = deps.mtime ?? fileMtime;
|
|
10251
|
-
const entries = loadCacheFile(configDir);
|
|
10252
|
-
let dirty = false;
|
|
10253
|
-
const compute = (session) => {
|
|
10254
|
-
switch (session.harness) {
|
|
10255
|
-
case "opencode":
|
|
10256
|
-
return session.project ?? null;
|
|
10257
|
-
case "claude": {
|
|
10258
|
-
const text = head(session.path);
|
|
10259
|
-
const cwd = text ? cwdFromJsonlHead(text) : null;
|
|
10260
|
-
if (cwd)
|
|
10261
|
-
return cwd;
|
|
10262
|
-
return session.project ? unmungeSlug(session.project, exists) : null;
|
|
10263
|
-
}
|
|
10264
|
-
case "codex": {
|
|
10265
|
-
const text = head(session.path);
|
|
10266
|
-
return text ? cwdFromJsonlHead(text) : null;
|
|
10267
|
-
}
|
|
10268
|
-
case "cursor":
|
|
10269
|
-
return session.project ? unmungeSlug(session.project, exists) : null;
|
|
10270
|
-
default:
|
|
10271
|
-
return null;
|
|
10272
|
-
}
|
|
10273
|
-
};
|
|
10274
|
-
return {
|
|
10275
|
-
resolve(session) {
|
|
10276
|
-
const key = `${session.harness}/${session.id}`;
|
|
10277
|
-
const cached = entries[key];
|
|
10278
|
-
if (cached && (cached.dir !== null || cached.mtime === mtime(session.path))) {
|
|
10279
|
-
return cached.dir;
|
|
10280
|
-
}
|
|
10281
|
-
const dir = compute(session);
|
|
10282
|
-
entries[key] = { dir, mtime: mtime(session.path) };
|
|
10283
|
-
dirty = true;
|
|
10284
|
-
return dir;
|
|
10285
|
-
},
|
|
10286
|
-
flush() {
|
|
10287
|
-
if (!dirty)
|
|
10288
|
-
return;
|
|
10289
|
-
saveCacheFile(configDir, entries);
|
|
10290
|
-
dirty = false;
|
|
10291
|
-
}
|
|
10292
|
-
};
|
|
10293
|
-
}
|
|
10294
|
-
var CACHE_FILENAME2 = "project-dirs.json", CACHE_SCHEMA_VERSION = 1, HEAD_BYTES, HEAD_LINES = 50;
|
|
10295
|
-
var init_project_dir = __esm(() => {
|
|
10296
|
-
init_config();
|
|
10297
|
-
HEAD_BYTES = 128 * 1024;
|
|
10298
|
-
});
|
|
10299
|
-
|
|
10300
10349
|
// src/sync/backlog.ts
|
|
10301
10350
|
function listSessionBacklog() {
|
|
10302
10351
|
try {
|
|
@@ -10441,7 +10490,7 @@ function readLockFile(configDir) {
|
|
|
10441
10490
|
}
|
|
10442
10491
|
function recentSyncActivity(log3) {
|
|
10443
10492
|
return log3.split(`
|
|
10444
|
-
`).filter((line) => line.includes("[sync]") || line.includes("[
|
|
10493
|
+
`).filter((line) => line.includes("[sync]") || line.includes("[learner]")).slice(-ACTIVITY_LINES).map((line) => line.replace(/ \[(DEBUG|INFO|WARN|ERROR)\]/, ""));
|
|
10445
10494
|
}
|
|
10446
10495
|
function getSyncStatus(deps = {}) {
|
|
10447
10496
|
const configDir = deps.configDir ?? getConfigDir();
|
|
@@ -10490,7 +10539,7 @@ async function runKnowledgeSync(options = {}) {
|
|
|
10490
10539
|
const state = loadState();
|
|
10491
10540
|
if (options.quiet) {
|
|
10492
10541
|
if (state.paused) {
|
|
10493
|
-
logger.debug("sync", "skipping quiet sync:
|
|
10542
|
+
logger.debug("sync", "skipping quiet sync: studying is paused");
|
|
10494
10543
|
return { status: "skipped-paused", readySessions: 0, inFlightSessions: 0, sessions: [] };
|
|
10495
10544
|
}
|
|
10496
10545
|
const retryAt = backoffUntil(state);
|
|
@@ -10500,7 +10549,7 @@ async function runKnowledgeSync(options = {}) {
|
|
|
10500
10549
|
}
|
|
10501
10550
|
} else if (state.paused) {
|
|
10502
10551
|
delete state.paused;
|
|
10503
|
-
logger.debug("sync", "manual sync resumes paused
|
|
10552
|
+
logger.debug("sync", "manual sync resumes paused studying");
|
|
10504
10553
|
}
|
|
10505
10554
|
let ready;
|
|
10506
10555
|
let open2;
|
|
@@ -10562,14 +10611,14 @@ async function runKnowledgeSync(options = {}) {
|
|
|
10562
10611
|
baseline_mined: state.total_mined ?? 0
|
|
10563
10612
|
};
|
|
10564
10613
|
}
|
|
10565
|
-
const
|
|
10614
|
+
const worthStudying = deps.worthStudying ?? isWorthStudying;
|
|
10566
10615
|
const examined = [];
|
|
10567
10616
|
const batch = [];
|
|
10568
10617
|
let trivial = 0;
|
|
10569
10618
|
for (let i2 = ready.length - 1;i2 >= 0 && batch.length < MINE_BATCH_LIMIT; i2--) {
|
|
10570
10619
|
const candidate = ready[i2];
|
|
10571
10620
|
examined.push(candidate);
|
|
10572
|
-
if (
|
|
10621
|
+
if (worthStudying(candidate)) {
|
|
10573
10622
|
batch.push(candidate);
|
|
10574
10623
|
} else {
|
|
10575
10624
|
trivial += 1;
|
|
@@ -10586,26 +10635,26 @@ async function runKnowledgeSync(options = {}) {
|
|
|
10586
10635
|
return {
|
|
10587
10636
|
status: "nothing-new",
|
|
10588
10637
|
...base,
|
|
10589
|
-
|
|
10638
|
+
studiedSessions: 0,
|
|
10590
10639
|
trivialSessions: trivial
|
|
10591
10640
|
};
|
|
10592
10641
|
}
|
|
10593
|
-
logger.debug("sync", `
|
|
10594
|
-
const
|
|
10595
|
-
switch (
|
|
10642
|
+
logger.debug("sync", `studying ${batch.length} of ${ready.length} ready sessions (${trivial} trivial skipped)`);
|
|
10643
|
+
const learner = await deps.mine(batch);
|
|
10644
|
+
switch (learner.outcome) {
|
|
10596
10645
|
case "completed": {
|
|
10597
10646
|
for (const s of batch) {
|
|
10598
|
-
logger.debug("sync", `
|
|
10647
|
+
logger.debug("sync", `studied session ${s.harness}/${s.id}`);
|
|
10599
10648
|
}
|
|
10600
|
-
const
|
|
10649
|
+
const studiedAt = now().toISOString();
|
|
10601
10650
|
const history = [
|
|
10602
10651
|
...state.mined_sessions ?? [],
|
|
10603
10652
|
...batch.map((s) => ({
|
|
10604
|
-
at:
|
|
10653
|
+
at: studiedAt,
|
|
10605
10654
|
session: `${s.harness}/${s.id}`,
|
|
10606
10655
|
...s.project ? { project: s.project } : {}
|
|
10607
10656
|
}))
|
|
10608
|
-
].slice(-
|
|
10657
|
+
].slice(-STUDIED_HISTORY_LIMIT);
|
|
10609
10658
|
const watermark = batchWatermark(examined);
|
|
10610
10659
|
const sessionTokens = deps.sessionTokens ?? estimateSessionTokens;
|
|
10611
10660
|
let batchTokens = 0;
|
|
@@ -10614,21 +10663,21 @@ async function runKnowledgeSync(options = {}) {
|
|
|
10614
10663
|
saveState({
|
|
10615
10664
|
...state,
|
|
10616
10665
|
watermark,
|
|
10617
|
-
last_attempt_at:
|
|
10666
|
+
last_attempt_at: studiedAt,
|
|
10618
10667
|
consecutive_failures: 0,
|
|
10619
10668
|
mined_sessions: history,
|
|
10620
10669
|
total_mined: (state.total_mined ?? 0) + batch.length,
|
|
10621
|
-
total_notes: (state.total_notes ?? 0) +
|
|
10670
|
+
total_notes: (state.total_notes ?? 0) + learner.notesWritten,
|
|
10622
10671
|
total_learning_tokens: (state.total_learning_tokens ?? 0) + batchTokens,
|
|
10623
10672
|
last_refusal: undefined
|
|
10624
10673
|
});
|
|
10625
|
-
logger.debug("sync", `
|
|
10674
|
+
logger.debug("sync", `studied ${batch.length} sessions, ${learner.notesWritten} suggested pages; watermark → ${watermark}`);
|
|
10626
10675
|
return {
|
|
10627
|
-
status: "
|
|
10676
|
+
status: "studied",
|
|
10628
10677
|
...base,
|
|
10629
|
-
|
|
10678
|
+
studiedSessions: batch.length,
|
|
10630
10679
|
trivialSessions: trivial,
|
|
10631
|
-
|
|
10680
|
+
learner
|
|
10632
10681
|
};
|
|
10633
10682
|
}
|
|
10634
10683
|
case "consent_off":
|
|
@@ -10641,12 +10690,12 @@ async function runKnowledgeSync(options = {}) {
|
|
|
10641
10690
|
consecutive_failures: 0,
|
|
10642
10691
|
last_refusal: {
|
|
10643
10692
|
at,
|
|
10644
|
-
outcome:
|
|
10645
|
-
message:
|
|
10693
|
+
outcome: learner.outcome,
|
|
10694
|
+
message: learner.message ?? "Studying unavailable right now."
|
|
10646
10695
|
}
|
|
10647
10696
|
});
|
|
10648
|
-
logger.debug("sync", `
|
|
10649
|
-
return { status: "skipped-gateway", ...base,
|
|
10697
|
+
logger.debug("sync", `studying skipped by gateway: ${learner.outcome}`);
|
|
10698
|
+
return { status: "skipped-gateway", ...base, studiedSessions: 0, learner };
|
|
10650
10699
|
}
|
|
10651
10700
|
default: {
|
|
10652
10701
|
saveState({
|
|
@@ -10654,13 +10703,13 @@ async function runKnowledgeSync(options = {}) {
|
|
|
10654
10703
|
last_attempt_at: now().toISOString(),
|
|
10655
10704
|
consecutive_failures: state.consecutive_failures + 1
|
|
10656
10705
|
});
|
|
10657
|
-
logger.debug("sync", `
|
|
10706
|
+
logger.debug("sync", `studying failed: ${learner.outcome}; ${learner.message ?? ""}`);
|
|
10658
10707
|
return {
|
|
10659
10708
|
status: "mine-failed",
|
|
10660
10709
|
...base,
|
|
10661
|
-
|
|
10662
|
-
|
|
10663
|
-
error:
|
|
10710
|
+
studiedSessions: 0,
|
|
10711
|
+
learner,
|
|
10712
|
+
error: learner.message
|
|
10664
10713
|
};
|
|
10665
10714
|
}
|
|
10666
10715
|
}
|
|
@@ -10806,12 +10855,12 @@ var init_backfill_run = __esm(() => {
|
|
|
10806
10855
|
init_backfill();
|
|
10807
10856
|
});
|
|
10808
10857
|
|
|
10809
|
-
// src/
|
|
10858
|
+
// src/learner/config-dir.ts
|
|
10810
10859
|
import { mkdtempSync, rmSync as rmSync3, writeFileSync as writeFileSync10 } from "node:fs";
|
|
10811
10860
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
10812
10861
|
import { join as join13 } from "node:path";
|
|
10813
10862
|
function createRunConfigDir() {
|
|
10814
|
-
const path3 = mkdtempSync(join13(tmpdir2(), "dosu-
|
|
10863
|
+
const path3 = mkdtempSync(join13(tmpdir2(), "dosu-learner-"));
|
|
10815
10864
|
writeFileSync10(join13(path3, ".claude.json"), JSON.stringify({ hasCompletedOnboarding: true }));
|
|
10816
10865
|
return {
|
|
10817
10866
|
path: path3,
|
|
@@ -10822,7 +10871,7 @@ function createRunConfigDir() {
|
|
|
10822
10871
|
}
|
|
10823
10872
|
var init_config_dir = () => {};
|
|
10824
10873
|
|
|
10825
|
-
// src/
|
|
10874
|
+
// src/learner/conflicts.ts
|
|
10826
10875
|
import { existsSync as existsSync13, readFileSync as readFileSync15 } from "node:fs";
|
|
10827
10876
|
function managedSettingsPaths(platform3 = process.platform) {
|
|
10828
10877
|
switch (platform3) {
|
|
@@ -10880,11 +10929,11 @@ var init_conflicts = __esm(() => {
|
|
|
10880
10929
|
CONFLICTING_ENV_PREFIXES = ["ANTHROPIC_", "CLAUDE_CODE_", "AWS_"];
|
|
10881
10930
|
});
|
|
10882
10931
|
|
|
10883
|
-
// src/
|
|
10932
|
+
// src/learner/env.ts
|
|
10884
10933
|
function isStripped(name) {
|
|
10885
10934
|
return STRIPPED_NAMES.has(name) || STRIPPED_PREFIXES.some((p2) => name.startsWith(p2));
|
|
10886
10935
|
}
|
|
10887
|
-
function
|
|
10936
|
+
function buildLearnerEnv(options) {
|
|
10888
10937
|
const base = options.baseEnv ?? process.env;
|
|
10889
10938
|
const env = {};
|
|
10890
10939
|
for (const [name, value] of Object.entries(base)) {
|
|
@@ -10914,7 +10963,7 @@ var init_env = __esm(() => {
|
|
|
10914
10963
|
STRIPPED_NAMES = new Set(["CLAUDE_CONFIG_DIR", "CLAUDECODE", "AWS_BEARER_TOKEN_BEDROCK"]);
|
|
10915
10964
|
});
|
|
10916
10965
|
|
|
10917
|
-
// src/
|
|
10966
|
+
// src/learner/executable.ts
|
|
10918
10967
|
import { accessSync, constants, existsSync as existsSync14 } from "node:fs";
|
|
10919
10968
|
import { createRequire as createRequire3 } from "node:module";
|
|
10920
10969
|
import { homedir as homedir4 } from "node:os";
|
|
@@ -10961,9 +11010,9 @@ function resolveClaudeExecutable(options = {}) {
|
|
|
10961
11010
|
}
|
|
10962
11011
|
var init_executable = () => {};
|
|
10963
11012
|
|
|
10964
|
-
// src/
|
|
10965
|
-
function
|
|
10966
|
-
return `You are Dosu's knowledge
|
|
11013
|
+
// src/learner/prompt.ts
|
|
11014
|
+
function buildLearnerSystemPrompt(coreRules) {
|
|
11015
|
+
return `You are Dosu's knowledge learner. You read a developer's recent coding-agent sessions and save the durable, non-obvious findings to the team's shared knowledge base so future teammates and agents do not have to rediscover them.
|
|
10967
11016
|
|
|
10968
11017
|
Available tools (the only tools you may use, by these exact names):
|
|
10969
11018
|
- mcp__sessions__list_sessions / mcp__sessions__read_session: the session transcripts in scope for this run.
|
|
@@ -10971,7 +11020,7 @@ Available tools (the only tools you may use, by these exact names):
|
|
|
10971
11020
|
|
|
10972
11021
|
${coreRules}`;
|
|
10973
11022
|
}
|
|
10974
|
-
function
|
|
11023
|
+
function buildLearnerPrompt(sessions) {
|
|
10975
11024
|
const list = sessions.map((s) => `- ${s.id} (${s.harness})`).join(`
|
|
10976
11025
|
`);
|
|
10977
11026
|
return `Mine the following ${sessions.length} coding-agent session(s) for durable knowledge:
|
|
@@ -10981,8 +11030,8 @@ ${list}
|
|
|
10981
11030
|
Work through them ONE AT A TIME: read a single session with read_session, decide what (if anything) is durable per your rules, dedupe against read_knowledge, and write that session's notes with write_knowledge BEFORE moving on to the next session. Each note is attributed to the session you read just before writing it, so do not read the next session until you have written this one's notes. When you are done, reply with a one-line summary: how many sessions you read and how many notes you wrote.`;
|
|
10982
11031
|
}
|
|
10983
11032
|
|
|
10984
|
-
// src/
|
|
10985
|
-
var
|
|
11033
|
+
// src/learner/prompt-core.ts
|
|
11034
|
+
var LEARNER_CORE_RULES = `Rules — non-negotiable:
|
|
10986
11035
|
1. Before writing anything, call read_knowledge with the candidate topic. If the same fact is
|
|
10987
11036
|
already recorded, skip it — never write a duplicate or near-duplicate. If the topic exists but
|
|
10988
11037
|
the session shows it has changed or been superseded, do write: a note stating the current state
|
|
@@ -11010,10 +11059,9 @@ useful.
|
|
|
11010
11059
|
6. Only pass repo/branch to write_knowledge when the session itself verifies them (an explicit
|
|
11011
11060
|
cwd, git remote, or branch mentioned in the transcript). Never infer or guess a repo. When not
|
|
11012
11061
|
verified, omit both.
|
|
11013
|
-
7.
|
|
11014
|
-
8. Never quote credentials, tokens, or secrets — even redacted placeholders — and never include
|
|
11062
|
+
7. Never quote credentials, tokens, or secrets — even redacted placeholders — and never include
|
|
11015
11063
|
long verbatim transcript spans. Summarize in your own words.
|
|
11016
|
-
|
|
11064
|
+
8. A trivial session with no real user query is normal: skip it silently.`;
|
|
11017
11065
|
|
|
11018
11066
|
// node_modules/@anthropic-ai/claude-agent-sdk/sdk.mjs
|
|
11019
11067
|
var exports_sdk = {};
|
|
@@ -72764,7 +72812,7 @@ var init_redact = __esm(() => {
|
|
|
72764
72812
|
]);
|
|
72765
72813
|
});
|
|
72766
72814
|
|
|
72767
|
-
// src/
|
|
72815
|
+
// src/learner/tools.ts
|
|
72768
72816
|
function textResult(text) {
|
|
72769
72817
|
return { content: [{ type: "text", text }] };
|
|
72770
72818
|
}
|
|
@@ -72823,7 +72871,7 @@ function createSessionToolsServer(sessions) {
|
|
|
72823
72871
|
name: SESSIONS_SERVER_NAME,
|
|
72824
72872
|
version: "1.0.0",
|
|
72825
72873
|
tools: [
|
|
72826
|
-
O6e("list_sessions", "List the coding-agent sessions in scope for this
|
|
72874
|
+
O6e("list_sessions", "List the coding-agent sessions in scope for this study run.", {}, async () => textResult(formatSessionList(sessions))),
|
|
72827
72875
|
O6e("read_session", "Read the conversation turns of one in-scope session. Responses are " + "paginated; pass the returned offset to continue reading.", {
|
|
72828
72876
|
id: exports_external.string().describe("Session id from list_sessions"),
|
|
72829
72877
|
offset: exports_external.number().int().min(0).optional().describe("Turn index to start from")
|
|
@@ -72849,11 +72897,11 @@ var init_tools = __esm(() => {
|
|
|
72849
72897
|
init_redact();
|
|
72850
72898
|
});
|
|
72851
72899
|
|
|
72852
|
-
// src/
|
|
72900
|
+
// src/learner/runner.ts
|
|
72853
72901
|
var exports_runner = {};
|
|
72854
72902
|
__export(exports_runner, {
|
|
72855
72903
|
traceAgentMessage: () => traceAgentMessage,
|
|
72856
|
-
|
|
72904
|
+
runLearner: () => runLearner,
|
|
72857
72905
|
classifyGatewayError: () => classifyGatewayError
|
|
72858
72906
|
});
|
|
72859
72907
|
function classifyGatewayError(text) {
|
|
@@ -72875,12 +72923,12 @@ function traceAgentMessage(message) {
|
|
|
72875
72923
|
return;
|
|
72876
72924
|
for (const block2 of content) {
|
|
72877
72925
|
if (msg.type === "assistant" && block2.type === "text") {
|
|
72878
|
-
logger.debug("
|
|
72926
|
+
logger.debug("learner", `[agent] ${snippet(block2.text)}`);
|
|
72879
72927
|
} else if (msg.type === "assistant" && block2.type === "tool_use") {
|
|
72880
|
-
logger.debug("
|
|
72928
|
+
logger.debug("learner", `[agent] → ${block2.name} ${snippet(block2.input)}`);
|
|
72881
72929
|
} else if (msg.type === "user" && block2.type === "tool_result") {
|
|
72882
72930
|
const full = JSON.stringify(block2.content ?? "");
|
|
72883
|
-
logger.debug("
|
|
72931
|
+
logger.debug("learner", `[agent] ← result ${full.length} chars${block2.is_error ? " (error)" : ""}: ${snippet(block2.content).slice(0, 120)}`);
|
|
72884
72932
|
}
|
|
72885
72933
|
}
|
|
72886
72934
|
}
|
|
@@ -72893,7 +72941,7 @@ function allowedToolNames() {
|
|
|
72893
72941
|
`mcp__${KNOWLEDGE_SERVER_NAME}__finalize_session_knowledge`
|
|
72894
72942
|
]);
|
|
72895
72943
|
}
|
|
72896
|
-
async function
|
|
72944
|
+
async function runLearner(options) {
|
|
72897
72945
|
const conflicts = detectSettingsConflicts();
|
|
72898
72946
|
if (conflicts.length > 0) {
|
|
72899
72947
|
const detail = conflicts.map((c5) => `${c5.file} (${c5.keys.join(", ")})`).join("; ");
|
|
@@ -72901,7 +72949,7 @@ async function runMiner(options) {
|
|
|
72901
72949
|
outcome: "settings_conflict",
|
|
72902
72950
|
notesWritten: 0,
|
|
72903
72951
|
turns: 0,
|
|
72904
|
-
message: `Refusing to run: conflicting Claude Code settings would override the
|
|
72952
|
+
message: `Refusing to run: conflicting Claude Code settings would override the study run's auth (${detail})`
|
|
72905
72953
|
};
|
|
72906
72954
|
}
|
|
72907
72955
|
const gatewayURL = options.gatewayURL ?? getLlmGatewayURL();
|
|
@@ -72916,7 +72964,7 @@ async function runMiner(options) {
|
|
|
72916
72964
|
const { query } = await Promise.resolve().then(() => (init_sdk(), exports_sdk));
|
|
72917
72965
|
const claudeExecutable = resolveClaudeExecutable();
|
|
72918
72966
|
if (claudeExecutable) {
|
|
72919
|
-
logger.debug("
|
|
72967
|
+
logger.debug("learner", `using system Claude Code executable: ${claudeExecutable}`);
|
|
72920
72968
|
}
|
|
72921
72969
|
const configDir = createRunConfigDir();
|
|
72922
72970
|
const runID = options.runID ?? crypto.randomUUID();
|
|
@@ -72929,7 +72977,7 @@ async function runMiner(options) {
|
|
|
72929
72977
|
let currentSession;
|
|
72930
72978
|
const readsSinceWrite = new Set;
|
|
72931
72979
|
let turns = 0;
|
|
72932
|
-
const env =
|
|
72980
|
+
const env = buildLearnerEnv({
|
|
72933
72981
|
apiKey: options.apiKey,
|
|
72934
72982
|
gatewayURL,
|
|
72935
72983
|
configDir: configDir.path,
|
|
@@ -72940,9 +72988,9 @@ async function runMiner(options) {
|
|
|
72940
72988
|
});
|
|
72941
72989
|
try {
|
|
72942
72990
|
const run2 = query({
|
|
72943
|
-
prompt:
|
|
72991
|
+
prompt: buildLearnerPrompt(options.sessions),
|
|
72944
72992
|
options: {
|
|
72945
|
-
systemPrompt:
|
|
72993
|
+
systemPrompt: buildLearnerSystemPrompt(LEARNER_CORE_RULES),
|
|
72946
72994
|
env,
|
|
72947
72995
|
abortController: abort,
|
|
72948
72996
|
maxTurns: options.maxTurns ?? DEFAULT_MAX_TURNS,
|
|
@@ -72958,17 +73006,17 @@ async function runMiner(options) {
|
|
|
72958
73006
|
headers: {
|
|
72959
73007
|
...mcpHeaders(options.apiKey),
|
|
72960
73008
|
"X-Dosu-Session-Id": runID,
|
|
72961
|
-
"X-Dosu-Client": `dosu-cli-
|
|
73009
|
+
"X-Dosu-Client": `dosu-cli-learner/${getVersionString()}`,
|
|
72962
73010
|
...sessionStartedAt ? { "X-Dosu-Session-Started-At": sessionStartedAt } : {}
|
|
72963
73011
|
}
|
|
72964
73012
|
}
|
|
72965
73013
|
},
|
|
72966
|
-
stderr: (data) => logger.debug("
|
|
73014
|
+
stderr: (data) => logger.debug("learner", `[sdk] ${data}`),
|
|
72967
73015
|
canUseTool: async (toolName, input2) => {
|
|
72968
73016
|
if (!allowed.has(toolName)) {
|
|
72969
73017
|
return {
|
|
72970
73018
|
behavior: "deny",
|
|
72971
|
-
message: `Tool ${toolName} is not permitted in
|
|
73019
|
+
message: `Tool ${toolName} is not permitted in study runs.`
|
|
72972
73020
|
};
|
|
72973
73021
|
}
|
|
72974
73022
|
if (toolName === `mcp__${SESSIONS_SERVER_NAME}__read_session`) {
|
|
@@ -73020,15 +73068,15 @@ async function runMiner(options) {
|
|
|
73020
73068
|
};
|
|
73021
73069
|
}
|
|
73022
73070
|
if (message.subtype !== "success" || message.is_error) {
|
|
73023
|
-
logger.debug("
|
|
73071
|
+
logger.debug("learner", `run ${runID} failed: ${text}`);
|
|
73024
73072
|
return {
|
|
73025
73073
|
outcome: "error",
|
|
73026
73074
|
notesWritten,
|
|
73027
73075
|
turns,
|
|
73028
|
-
message: "
|
|
73076
|
+
message: "Study run failed; see debug log for details."
|
|
73029
73077
|
};
|
|
73030
73078
|
}
|
|
73031
|
-
logger.debug("
|
|
73079
|
+
logger.debug("learner", `run ${runID} completed: ${turns} turns, ${notesWritten} suggested pages`);
|
|
73032
73080
|
return { outcome: "completed", notesWritten, turns, message: text };
|
|
73033
73081
|
}
|
|
73034
73082
|
}
|
|
@@ -73036,7 +73084,7 @@ async function runMiner(options) {
|
|
|
73036
73084
|
outcome: "error",
|
|
73037
73085
|
notesWritten,
|
|
73038
73086
|
turns,
|
|
73039
|
-
message: "
|
|
73087
|
+
message: "Study run ended without a result."
|
|
73040
73088
|
};
|
|
73041
73089
|
} catch (error61) {
|
|
73042
73090
|
const text = error61 instanceof Error ? error61.message : String(error61);
|
|
@@ -73044,12 +73092,12 @@ async function runMiner(options) {
|
|
|
73044
73092
|
if (gatewayError) {
|
|
73045
73093
|
return { outcome: gatewayError.outcome, notesWritten, turns, message: gatewayError.message };
|
|
73046
73094
|
}
|
|
73047
|
-
logger.debug("
|
|
73095
|
+
logger.debug("learner", `run ${runID} threw: ${text}`);
|
|
73048
73096
|
return {
|
|
73049
73097
|
outcome: "error",
|
|
73050
73098
|
notesWritten,
|
|
73051
73099
|
turns,
|
|
73052
|
-
message: abort.signal.aborted ? "
|
|
73100
|
+
message: abort.signal.aborted ? "Study run timed out and was aborted." : "Study run failed; see debug log for details."
|
|
73053
73101
|
};
|
|
73054
73102
|
} finally {
|
|
73055
73103
|
clearTimeout(timer);
|
|
@@ -73519,7 +73567,7 @@ var init_settings = __esm(() => {
|
|
|
73519
73567
|
});
|
|
73520
73568
|
|
|
73521
73569
|
// src/telemetry/debug-id.ts
|
|
73522
|
-
var BUNDLE_DEBUG_ID_PLACEHOLDER = "
|
|
73570
|
+
var BUNDLE_DEBUG_ID_PLACEHOLDER = "5776df20-501e-ff5f-6475-6e2164756e21";
|
|
73523
73571
|
|
|
73524
73572
|
// src/telemetry/telemetry.ts
|
|
73525
73573
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
@@ -73548,11 +73596,13 @@ function normalizeContext(context) {
|
|
|
73548
73596
|
const isAuthenticated2 = context?.isAuthenticated === true;
|
|
73549
73597
|
const id2 = safeRead(context?.user, "id");
|
|
73550
73598
|
const email3 = safeRead(context?.user, "email");
|
|
73551
|
-
const
|
|
73599
|
+
const orgId = safeRead(context, "orgId");
|
|
73600
|
+
const user = isAuthenticated2 && isUUID2(id2) ? { id: id2, ...validEmail(email3) ? { email: email3 } : {} } : undefined;
|
|
73552
73601
|
return {
|
|
73553
73602
|
mode: context?.mode === "oss" ? "oss" : "cloud",
|
|
73554
73603
|
isAuthenticated: isAuthenticated2,
|
|
73555
|
-
...user ? { user } : {}
|
|
73604
|
+
...user ? { user } : {},
|
|
73605
|
+
...user && isUUID2(orgId) ? { orgId } : {}
|
|
73556
73606
|
};
|
|
73557
73607
|
}
|
|
73558
73608
|
function normalizeExitCode(value, fallback = 1) {
|
|
@@ -73572,12 +73622,9 @@ function normalizeRuntime(runtime) {
|
|
|
73572
73622
|
isTty: runtime.isTty === true
|
|
73573
73623
|
};
|
|
73574
73624
|
}
|
|
73575
|
-
function
|
|
73625
|
+
function isUUID2(value) {
|
|
73576
73626
|
return typeof value === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value);
|
|
73577
73627
|
}
|
|
73578
|
-
function validUserId(value) {
|
|
73579
|
-
return validInstallId(value);
|
|
73580
|
-
}
|
|
73581
73628
|
function validEmail(value) {
|
|
73582
73629
|
return typeof value === "string" && value.length <= 254 && /^[A-Za-z0-9.!#$%&'*+/=?^_`{|}~-]+@[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?(?:\.[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)+$/.test(value);
|
|
73583
73630
|
}
|
|
@@ -73727,7 +73774,7 @@ function buildPostHogPayload(input2) {
|
|
|
73727
73774
|
const result = input2.result === "success" || input2.result === "validation_error" ? input2.result : "failure";
|
|
73728
73775
|
return {
|
|
73729
73776
|
api_key: input2.apiKey,
|
|
73730
|
-
distinct_id: context.user?.id ?? (
|
|
73777
|
+
distinct_id: context.user?.id ?? (isUUID2(input2.installId) ? input2.installId : FALLBACK_INSTALL_ID),
|
|
73731
73778
|
event: "cli_command_completed",
|
|
73732
73779
|
properties: {
|
|
73733
73780
|
$geoip_disable: true,
|
|
@@ -73745,6 +73792,10 @@ function buildPostHogPayload(input2) {
|
|
|
73745
73792
|
is_ci: runtime.isCi,
|
|
73746
73793
|
is_tty: runtime.isTty,
|
|
73747
73794
|
mode: context.mode,
|
|
73795
|
+
...context.orgId ? {
|
|
73796
|
+
org_id: context.orgId,
|
|
73797
|
+
$groups: { organization: context.orgId }
|
|
73798
|
+
} : {},
|
|
73748
73799
|
is_authenticated: context.isAuthenticated,
|
|
73749
73800
|
exit_code: normalizeExitCode(input2.exitCode),
|
|
73750
73801
|
...errorCode ? { error_code: errorCode } : {}
|
|
@@ -73859,7 +73910,12 @@ function buildSentryEnvelope(input2) {
|
|
|
73859
73910
|
level: "error",
|
|
73860
73911
|
release: `dosu-cli@${runtime.version}`,
|
|
73861
73912
|
tags: sentryTags(command, context, runtime, error61),
|
|
73862
|
-
...context.user ? {
|
|
73913
|
+
...context.user ? {
|
|
73914
|
+
user: {
|
|
73915
|
+
id: context.user.id,
|
|
73916
|
+
...context.user.email ? { email: context.user.email } : {}
|
|
73917
|
+
}
|
|
73918
|
+
} : {},
|
|
73863
73919
|
...mappedBundle ? {
|
|
73864
73920
|
debug_meta: {
|
|
73865
73921
|
images: [{ type: "sourcemap", code_file: BUNDLE_CODE_FILE, debug_id: debugId }]
|
|
@@ -74053,7 +74109,7 @@ function safeNow(now) {
|
|
|
74053
74109
|
function safeUuid(generate) {
|
|
74054
74110
|
try {
|
|
74055
74111
|
const value = generate();
|
|
74056
|
-
return
|
|
74112
|
+
return isUUID2(value) ? value : undefined;
|
|
74057
74113
|
} catch {
|
|
74058
74114
|
return;
|
|
74059
74115
|
}
|
|
@@ -74085,7 +74141,7 @@ function createCommandTelemetry(settings2, dependencies = {}) {
|
|
|
74085
74141
|
let installIdResolved = false;
|
|
74086
74142
|
let resolvedInstallId = settings2.install_id;
|
|
74087
74143
|
function installId() {
|
|
74088
|
-
if (
|
|
74144
|
+
if (isUUID2(resolvedInstallId))
|
|
74089
74145
|
return resolvedInstallId;
|
|
74090
74146
|
if (installIdResolved)
|
|
74091
74147
|
return;
|
|
@@ -74095,7 +74151,7 @@ function createCommandTelemetry(settings2, dependencies = {}) {
|
|
|
74095
74151
|
} catch {
|
|
74096
74152
|
resolvedInstallId = undefined;
|
|
74097
74153
|
}
|
|
74098
|
-
return
|
|
74154
|
+
return isUUID2(resolvedInstallId) ? resolvedInstallId : undefined;
|
|
74099
74155
|
}
|
|
74100
74156
|
function debugPayload(payload) {
|
|
74101
74157
|
try {
|
|
@@ -74314,16 +74370,6 @@ var init_follow = () => {};
|
|
|
74314
74370
|
|
|
74315
74371
|
// src/mcp/providers/base.ts
|
|
74316
74372
|
function createJSONProvider(opts) {
|
|
74317
|
-
const defaultBuildServer = (cfg) => ({
|
|
74318
|
-
type: "http",
|
|
74319
|
-
url: mcpURL(cfg.active_account.target.deployment_id),
|
|
74320
|
-
headers: mcpHeaders(cfg.active_account?.target?.api_key)
|
|
74321
|
-
});
|
|
74322
|
-
const defaultBuildOSSServer = (cfg) => ({
|
|
74323
|
-
type: "http",
|
|
74324
|
-
url: mcpBaseURL(),
|
|
74325
|
-
headers: mcpHeaders(cfg.active_account?.target?.api_key)
|
|
74326
|
-
});
|
|
74327
74373
|
const buildServer = opts.buildServer ?? defaultBuildServer;
|
|
74328
74374
|
return {
|
|
74329
74375
|
name: () => opts.providerName,
|
|
@@ -74335,8 +74381,10 @@ function createJSONProvider(opts) {
|
|
|
74335
74381
|
globalConfigPath: () => expandHome(opts.globalPath),
|
|
74336
74382
|
isConfigured: () => isJSONKeyConfigured(expandHome(opts.globalPath), opts.topKey),
|
|
74337
74383
|
install(cfg, global2) {
|
|
74338
|
-
|
|
74339
|
-
|
|
74384
|
+
const endpoint = {
|
|
74385
|
+
url: mcpEndpoint(cfg),
|
|
74386
|
+
headers: mcpHeaders(cfg.active_account?.target?.api_key)
|
|
74387
|
+
};
|
|
74340
74388
|
let configPath;
|
|
74341
74389
|
if (global2) {
|
|
74342
74390
|
configPath = expandHome(opts.globalPath);
|
|
@@ -74345,8 +74393,7 @@ function createJSONProvider(opts) {
|
|
|
74345
74393
|
} else {
|
|
74346
74394
|
throw new Error(`${opts.providerName} does not support local installation`);
|
|
74347
74395
|
}
|
|
74348
|
-
|
|
74349
|
-
installJSONServer(configPath, opts.topKey, serverBuilder(cfg));
|
|
74396
|
+
installJSONServer(configPath, opts.topKey, buildServer(endpoint, cfg));
|
|
74350
74397
|
},
|
|
74351
74398
|
remove(global2) {
|
|
74352
74399
|
let configPath;
|
|
@@ -74361,8 +74408,12 @@ function createJSONProvider(opts) {
|
|
|
74361
74408
|
}
|
|
74362
74409
|
};
|
|
74363
74410
|
}
|
|
74411
|
+
var defaultBuildServer = ({ url: url2, headers }) => ({
|
|
74412
|
+
type: "http",
|
|
74413
|
+
url: url2,
|
|
74414
|
+
headers
|
|
74415
|
+
});
|
|
74364
74416
|
var init_base = __esm(() => {
|
|
74365
|
-
init_config();
|
|
74366
74417
|
init_config_helpers();
|
|
74367
74418
|
init_detect();
|
|
74368
74419
|
});
|
|
@@ -74376,13 +74427,12 @@ var AntigravityProvider = () => createJSONProvider({
|
|
|
74376
74427
|
paths: ["~/.gemini"],
|
|
74377
74428
|
globalPath: "~/.gemini/antigravity/mcp_config.json",
|
|
74378
74429
|
topKey: "mcpServers",
|
|
74379
|
-
buildServer: (
|
|
74380
|
-
serverUrl:
|
|
74381
|
-
headers
|
|
74430
|
+
buildServer: ({ url: url2, headers }) => ({
|
|
74431
|
+
serverUrl: url2,
|
|
74432
|
+
headers
|
|
74382
74433
|
})
|
|
74383
74434
|
});
|
|
74384
74435
|
var init_antigravity = __esm(() => {
|
|
74385
|
-
init_config_helpers();
|
|
74386
74436
|
init_base();
|
|
74387
74437
|
});
|
|
74388
74438
|
|
|
@@ -74419,9 +74469,7 @@ var ClaudeDesktopProvider = () => ({
|
|
|
74419
74469
|
install(cfg, global2) {
|
|
74420
74470
|
if (!global2)
|
|
74421
74471
|
throw new Error("Claude Desktop does not support local installation");
|
|
74422
|
-
|
|
74423
|
-
throw new Error("deployment ID is required");
|
|
74424
|
-
const url2 = cfg.mode === MODE_OSS ? mcpBaseURL() : mcpURL(cfg.active_account.target.deployment_id);
|
|
74472
|
+
const url2 = mcpEndpoint(cfg);
|
|
74425
74473
|
const npx = findNpx();
|
|
74426
74474
|
const remote = mcpRemoteServer(url2, cfg.active_account?.target?.api_key);
|
|
74427
74475
|
installJSONServer(configPath(), "mcpServers", {
|
|
@@ -74437,7 +74485,6 @@ var ClaudeDesktopProvider = () => ({
|
|
|
74437
74485
|
}
|
|
74438
74486
|
});
|
|
74439
74487
|
var init_claude_desktop = __esm(() => {
|
|
74440
|
-
init_config();
|
|
74441
74488
|
init_config_helpers();
|
|
74442
74489
|
init_detect();
|
|
74443
74490
|
});
|
|
@@ -74452,15 +74499,14 @@ var extensionDir = () => join25(appSupportDir(), "Code", "User", "globalStorage"
|
|
|
74452
74499
|
paths: [extensionDir()],
|
|
74453
74500
|
globalPath: join25(extensionDir(), "settings", "cline_mcp_settings.json"),
|
|
74454
74501
|
topKey: "mcpServers",
|
|
74455
|
-
buildServer: (
|
|
74456
|
-
url:
|
|
74502
|
+
buildServer: ({ url: url2, headers }) => ({
|
|
74503
|
+
url: url2,
|
|
74457
74504
|
type: "streamableHttp",
|
|
74458
74505
|
disabled: false,
|
|
74459
|
-
headers
|
|
74506
|
+
headers
|
|
74460
74507
|
})
|
|
74461
74508
|
});
|
|
74462
74509
|
var init_cline = __esm(() => {
|
|
74463
|
-
init_config_helpers();
|
|
74464
74510
|
init_detect();
|
|
74465
74511
|
init_base();
|
|
74466
74512
|
});
|
|
@@ -74478,22 +74524,21 @@ var ClineCliProvider = () => createJSONProvider({
|
|
|
74478
74524
|
paths: [clineDir()],
|
|
74479
74525
|
globalPath: join26(clineDir(), "data", "settings", "cline_mcp_settings.json"),
|
|
74480
74526
|
topKey: "mcpServers",
|
|
74481
|
-
buildServer: (
|
|
74482
|
-
url:
|
|
74527
|
+
buildServer: ({ url: url2, headers }) => ({
|
|
74528
|
+
url: url2,
|
|
74483
74529
|
type: "streamableHttp",
|
|
74484
74530
|
disabled: false,
|
|
74485
|
-
headers
|
|
74531
|
+
headers
|
|
74486
74532
|
})
|
|
74487
74533
|
});
|
|
74488
74534
|
var init_cline_cli = __esm(() => {
|
|
74489
|
-
init_config_helpers();
|
|
74490
74535
|
init_detect();
|
|
74491
74536
|
init_base();
|
|
74492
74537
|
});
|
|
74493
74538
|
|
|
74494
74539
|
// src/mcp/providers/codex.ts
|
|
74495
74540
|
import { existsSync as existsSync20, readFileSync as readFileSync20 } from "node:fs";
|
|
74496
|
-
import { join as join27 } from "node:path";
|
|
74541
|
+
import { dirname as dirname9, join as join27 } from "node:path";
|
|
74497
74542
|
function codexHome2() {
|
|
74498
74543
|
return process.env.CODEX_HOME ?? expandHome("~/.codex");
|
|
74499
74544
|
}
|
|
@@ -74510,13 +74555,6 @@ function readTOML(path3) {
|
|
|
74510
74555
|
function writeTOML(path3, content) {
|
|
74511
74556
|
writeSecureFile(path3, content);
|
|
74512
74557
|
}
|
|
74513
|
-
function mcpEndpoint(cfg) {
|
|
74514
|
-
if (cfg.mode === MODE_OSS)
|
|
74515
|
-
return mcpBaseURL();
|
|
74516
|
-
if (!cfg.active_account?.target?.deployment_id)
|
|
74517
|
-
throw new Error("deployment ID is required");
|
|
74518
|
-
return mcpURL(cfg.active_account?.target?.deployment_id);
|
|
74519
|
-
}
|
|
74520
74558
|
function tomlString(value) {
|
|
74521
74559
|
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
|
|
74522
74560
|
}
|
|
@@ -74540,19 +74578,24 @@ ${envEntries}
|
|
|
74540
74578
|
content += section;
|
|
74541
74579
|
writeTOML(path3, content);
|
|
74542
74580
|
}
|
|
74581
|
+
function sectionName(line) {
|
|
74582
|
+
const header = line.trim();
|
|
74583
|
+
const match = header.match(/^\[\[([^\]]*)]]\s*(?:#.*)?$/) ?? header.match(/^\[([^\]]*)]\s*(?:#.*)?$/);
|
|
74584
|
+
return match ? match[1].trim() : null;
|
|
74585
|
+
}
|
|
74586
|
+
function isDosuSection(name) {
|
|
74587
|
+
return name === "mcp_servers.dosu" || name.startsWith("mcp_servers.dosu.");
|
|
74588
|
+
}
|
|
74543
74589
|
function removeDosuFromTOML(content) {
|
|
74544
|
-
const lines = content.split(`
|
|
74545
|
-
`);
|
|
74546
74590
|
const result = [];
|
|
74547
74591
|
let inDosuSection = false;
|
|
74548
|
-
for (const line of
|
|
74549
|
-
|
|
74550
|
-
|
|
74551
|
-
|
|
74552
|
-
|
|
74553
|
-
|
|
74554
|
-
|
|
74555
|
-
inDosuSection = false;
|
|
74592
|
+
for (const line of content.split(`
|
|
74593
|
+
`)) {
|
|
74594
|
+
const name = sectionName(line);
|
|
74595
|
+
if (name !== null) {
|
|
74596
|
+
inDosuSection = isDosuSection(name);
|
|
74597
|
+
if (inDosuSection)
|
|
74598
|
+
continue;
|
|
74556
74599
|
}
|
|
74557
74600
|
if (!inDosuSection) {
|
|
74558
74601
|
result.push(line);
|
|
@@ -74561,7 +74604,45 @@ function removeDosuFromTOML(content) {
|
|
|
74561
74604
|
return result.join(`
|
|
74562
74605
|
`);
|
|
74563
74606
|
}
|
|
74564
|
-
|
|
74607
|
+
function hasRemoteHTTPForm(content) {
|
|
74608
|
+
let inDosuRoot = false;
|
|
74609
|
+
for (const line of content.split(`
|
|
74610
|
+
`)) {
|
|
74611
|
+
const name = sectionName(line);
|
|
74612
|
+
if (name !== null) {
|
|
74613
|
+
if (name === "mcp_servers.dosu.http_headers")
|
|
74614
|
+
return true;
|
|
74615
|
+
inDosuRoot = name === "mcp_servers.dosu";
|
|
74616
|
+
continue;
|
|
74617
|
+
}
|
|
74618
|
+
if (inDosuRoot && REMOTE_HTTP_KEY.test(line.trim()))
|
|
74619
|
+
return true;
|
|
74620
|
+
}
|
|
74621
|
+
return false;
|
|
74622
|
+
}
|
|
74623
|
+
function projectConfigPaths() {
|
|
74624
|
+
const cwd = process.cwd();
|
|
74625
|
+
const chain = [];
|
|
74626
|
+
for (let dir = cwd;; ) {
|
|
74627
|
+
chain.push(dir);
|
|
74628
|
+
if (existsSync20(join27(dir, ".git")))
|
|
74629
|
+
break;
|
|
74630
|
+
const parent = dirname9(dir);
|
|
74631
|
+
if (parent === dir)
|
|
74632
|
+
return [join27(cwd, ".codex", "config.toml")];
|
|
74633
|
+
dir = parent;
|
|
74634
|
+
}
|
|
74635
|
+
return chain.reverse().map((dir) => join27(dir, ".codex", "config.toml"));
|
|
74636
|
+
}
|
|
74637
|
+
function pruneLegacyRemoteEntry(path3) {
|
|
74638
|
+
if (!existsSync20(path3))
|
|
74639
|
+
return;
|
|
74640
|
+
const content = readTOML(path3);
|
|
74641
|
+
if (!hasRemoteHTTPForm(content))
|
|
74642
|
+
return;
|
|
74643
|
+
writeTOML(path3, removeDosuFromTOML(content));
|
|
74644
|
+
}
|
|
74645
|
+
var REMOTE_HTTP_KEY, CodexProvider = () => ({
|
|
74565
74646
|
name: () => "Codex (CLI + Desktop)",
|
|
74566
74647
|
id: () => "codex",
|
|
74567
74648
|
supportsLocal: () => true,
|
|
@@ -74576,7 +74657,15 @@ var CodexProvider = () => ({
|
|
|
74576
74657
|
install(cfg, global2) {
|
|
74577
74658
|
if (cfg.mode !== MODE_OSS && !cfg.active_account?.target?.deployment_id)
|
|
74578
74659
|
throw new Error("deployment ID is required");
|
|
74579
|
-
|
|
74660
|
+
if (!global2 && hasRemoteHTTPForm(readTOML(getConfigPath2(true)))) {
|
|
74661
|
+
throw new Error("the global Codex config still holds the legacy remote-HTTP Dosu entry, which Codex would " + "merge with a project-local entry and reject. Migrate it first with " + "`dosu mcp add codex --global`, then re-run this.");
|
|
74662
|
+
}
|
|
74663
|
+
const written = getConfigPath2(global2);
|
|
74664
|
+
installDosuToTOML(written, cfg);
|
|
74665
|
+
for (const path3 of projectConfigPaths()) {
|
|
74666
|
+
if (path3 !== written)
|
|
74667
|
+
pruneLegacyRemoteEntry(path3);
|
|
74668
|
+
}
|
|
74580
74669
|
},
|
|
74581
74670
|
remove(global2) {
|
|
74582
74671
|
const path3 = getConfigPath2(global2);
|
|
@@ -74589,6 +74678,7 @@ var init_codex = __esm(() => {
|
|
|
74589
74678
|
init_config();
|
|
74590
74679
|
init_config_helpers();
|
|
74591
74680
|
init_detect();
|
|
74681
|
+
REMOTE_HTTP_KEY = /^(url|type|bearer_token_env_var)\s*=/;
|
|
74592
74682
|
});
|
|
74593
74683
|
|
|
74594
74684
|
// src/mcp/providers/copilot.ts
|
|
@@ -74599,13 +74689,6 @@ function globalPath() {
|
|
|
74599
74689
|
}
|
|
74600
74690
|
return expandHome("~/.copilot/mcp-config.json");
|
|
74601
74691
|
}
|
|
74602
|
-
function mcpEndpoint2(cfg) {
|
|
74603
|
-
if (cfg.mode === MODE_OSS)
|
|
74604
|
-
return mcpBaseURL();
|
|
74605
|
-
if (!cfg.active_account?.target?.deployment_id)
|
|
74606
|
-
throw new Error("deployment ID is required");
|
|
74607
|
-
return mcpURL(cfg.active_account?.target?.deployment_id);
|
|
74608
|
-
}
|
|
74609
74692
|
var CopilotProvider = () => ({
|
|
74610
74693
|
name: () => "GitHub Copilot CLI",
|
|
74611
74694
|
id: () => "copilot",
|
|
@@ -74616,7 +74699,7 @@ var CopilotProvider = () => ({
|
|
|
74616
74699
|
globalConfigPath: () => globalPath(),
|
|
74617
74700
|
isConfigured: () => isJSONKeyConfigured(globalPath(), "mcpServers"),
|
|
74618
74701
|
install(cfg, global2) {
|
|
74619
|
-
const url2 =
|
|
74702
|
+
const url2 = mcpEndpoint(cfg);
|
|
74620
74703
|
if (global2) {
|
|
74621
74704
|
const server = {
|
|
74622
74705
|
type: "http",
|
|
@@ -74645,7 +74728,6 @@ var CopilotProvider = () => ({
|
|
|
74645
74728
|
}
|
|
74646
74729
|
});
|
|
74647
74730
|
var init_copilot = __esm(() => {
|
|
74648
|
-
init_config();
|
|
74649
74731
|
init_config_helpers();
|
|
74650
74732
|
init_detect();
|
|
74651
74733
|
});
|
|
@@ -74660,14 +74742,13 @@ var CursorProvider = () => createJSONProvider({
|
|
|
74660
74742
|
paths: ["~/.cursor"],
|
|
74661
74743
|
globalPath: "~/.cursor/mcp.json",
|
|
74662
74744
|
topKey: "mcpServers",
|
|
74663
|
-
buildServer: (
|
|
74664
|
-
url:
|
|
74665
|
-
headers
|
|
74745
|
+
buildServer: ({ url: url2, headers }) => ({
|
|
74746
|
+
url: url2,
|
|
74747
|
+
headers
|
|
74666
74748
|
}),
|
|
74667
74749
|
localConfigPath: (cwd) => join29(cwd, ".cursor", "mcp.json")
|
|
74668
74750
|
});
|
|
74669
74751
|
var init_cursor = __esm(() => {
|
|
74670
|
-
init_config_helpers();
|
|
74671
74752
|
init_base();
|
|
74672
74753
|
});
|
|
74673
74754
|
|
|
@@ -74704,13 +74785,6 @@ var init_gemini = __esm(() => {
|
|
|
74704
74785
|
});
|
|
74705
74786
|
|
|
74706
74787
|
// src/mcp/providers/manual.ts
|
|
74707
|
-
function mcpEndpoint3(cfg) {
|
|
74708
|
-
if (cfg.mode === MODE_OSS)
|
|
74709
|
-
return mcpBaseURL();
|
|
74710
|
-
if (!cfg.active_account?.target?.deployment_id)
|
|
74711
|
-
throw new Error("deployment ID is required");
|
|
74712
|
-
return mcpURL(cfg.active_account?.target?.deployment_id);
|
|
74713
|
-
}
|
|
74714
74788
|
function maskSecret(secret) {
|
|
74715
74789
|
if (secret.length <= 8)
|
|
74716
74790
|
return "[hidden]";
|
|
@@ -74722,7 +74796,7 @@ var ManualProvider = () => ({
|
|
|
74722
74796
|
id: () => "manual",
|
|
74723
74797
|
supportsLocal: () => false,
|
|
74724
74798
|
install(cfg, _global, opts = {}) {
|
|
74725
|
-
const url2 =
|
|
74799
|
+
const url2 = mcpEndpoint(cfg);
|
|
74726
74800
|
const apiKey = mcpHeaders(cfg.active_account?.target?.api_key)["X-Dosu-API-Key"];
|
|
74727
74801
|
const headerValue = opts.showSecret ? apiKey : maskSecret(apiKey);
|
|
74728
74802
|
console.log("Use these details to configure the Dosu MCP server in your client:");
|
|
@@ -74742,7 +74816,6 @@ To remove the Dosu MCP server, manually delete the configuration from your clien
|
|
|
74742
74816
|
}
|
|
74743
74817
|
});
|
|
74744
74818
|
var init_manual = __esm(() => {
|
|
74745
|
-
init_config();
|
|
74746
74819
|
init_config_helpers();
|
|
74747
74820
|
});
|
|
74748
74821
|
|
|
@@ -74758,13 +74831,6 @@ function resolveGlobalConfigPath() {
|
|
|
74758
74831
|
return jsoncPath;
|
|
74759
74832
|
return jsonPath;
|
|
74760
74833
|
}
|
|
74761
|
-
function mcpEndpoint4(cfg) {
|
|
74762
|
-
if (cfg.mode === MODE_OSS)
|
|
74763
|
-
return mcpBaseURL();
|
|
74764
|
-
if (!cfg.active_account?.target?.deployment_id)
|
|
74765
|
-
throw new Error("deployment ID is required");
|
|
74766
|
-
return mcpURL(cfg.active_account?.target?.deployment_id);
|
|
74767
|
-
}
|
|
74768
74834
|
var MCPorterProvider = () => ({
|
|
74769
74835
|
name: () => "MCPorter",
|
|
74770
74836
|
id: () => "mcporter",
|
|
@@ -74778,7 +74844,7 @@ var MCPorterProvider = () => ({
|
|
|
74778
74844
|
const configPath2 = global2 ? resolveGlobalConfigPath() : join32(process.cwd(), "config", "mcporter.json");
|
|
74779
74845
|
const server = {
|
|
74780
74846
|
type: "http",
|
|
74781
|
-
url:
|
|
74847
|
+
url: mcpEndpoint(cfg),
|
|
74782
74848
|
headers: mcpHeaders(cfg.active_account.target.api_key)
|
|
74783
74849
|
};
|
|
74784
74850
|
installJSONServer(configPath2, "mcpServers", server);
|
|
@@ -74789,7 +74855,6 @@ var MCPorterProvider = () => ({
|
|
|
74789
74855
|
}
|
|
74790
74856
|
});
|
|
74791
74857
|
var init_mcporter = __esm(() => {
|
|
74792
|
-
init_config();
|
|
74793
74858
|
init_config_helpers();
|
|
74794
74859
|
init_detect();
|
|
74795
74860
|
});
|
|
@@ -74804,16 +74869,15 @@ var OpenCodeProvider = () => createJSONProvider({
|
|
|
74804
74869
|
paths: ["~/.config/opencode"],
|
|
74805
74870
|
globalPath: "~/.config/opencode/opencode.json",
|
|
74806
74871
|
topKey: "mcp",
|
|
74807
|
-
buildServer: (
|
|
74872
|
+
buildServer: ({ url: url2, headers }) => ({
|
|
74808
74873
|
type: "remote",
|
|
74809
|
-
url:
|
|
74874
|
+
url: url2,
|
|
74810
74875
|
enabled: true,
|
|
74811
|
-
headers
|
|
74876
|
+
headers
|
|
74812
74877
|
}),
|
|
74813
74878
|
localConfigPath: (cwd) => join33(cwd, "opencode.json")
|
|
74814
74879
|
});
|
|
74815
74880
|
var init_opencode = __esm(() => {
|
|
74816
|
-
init_config_helpers();
|
|
74817
74881
|
init_base();
|
|
74818
74882
|
});
|
|
74819
74883
|
|
|
@@ -74851,30 +74915,73 @@ var init_windsurf = __esm(() => {
|
|
|
74851
74915
|
});
|
|
74852
74916
|
|
|
74853
74917
|
// src/mcp/providers/zed.ts
|
|
74854
|
-
import {
|
|
74918
|
+
import { rmSync as rmSync5 } from "node:fs";
|
|
74919
|
+
import { homedir as homedir9, platform as platform3 } from "node:os";
|
|
74855
74920
|
import { join as join36 } from "node:path";
|
|
74856
74921
|
function zedConfigDir() {
|
|
74857
74922
|
const os3 = platform3();
|
|
74858
|
-
if (os3 === "
|
|
74923
|
+
if (os3 === "win32")
|
|
74859
74924
|
return join36(appSupportDir(), "Zed");
|
|
74925
|
+
if (os3 === "darwin")
|
|
74926
|
+
return join36(homedir9(), ".config", "zed");
|
|
74860
74927
|
return join36(appSupportDir(), "zed");
|
|
74861
74928
|
}
|
|
74862
|
-
|
|
74863
|
-
|
|
74864
|
-
|
|
74865
|
-
|
|
74866
|
-
|
|
74867
|
-
|
|
74868
|
-
|
|
74869
|
-
|
|
74870
|
-
|
|
74871
|
-
|
|
74872
|
-
|
|
74873
|
-
|
|
74874
|
-
|
|
74875
|
-
|
|
74876
|
-
|
|
74877
|
-
}
|
|
74929
|
+
function zedDataDir() {
|
|
74930
|
+
const os3 = platform3();
|
|
74931
|
+
if (os3 === "darwin")
|
|
74932
|
+
return join36(appSupportDir(), "Zed");
|
|
74933
|
+
if (os3 === "win32")
|
|
74934
|
+
return join36(process.env.LOCALAPPDATA ?? "", "Zed");
|
|
74935
|
+
return join36(process.env.XDG_DATA_HOME ?? join36(homedir9(), ".local", "share"), "zed");
|
|
74936
|
+
}
|
|
74937
|
+
function legacyDarwinSettingsPath() {
|
|
74938
|
+
return platform3() === "darwin" ? join36(appSupportDir(), "Zed", "settings.json") : null;
|
|
74939
|
+
}
|
|
74940
|
+
function removeLegacyDosuEntry(path3) {
|
|
74941
|
+
let cfg;
|
|
74942
|
+
try {
|
|
74943
|
+
cfg = readJSONConfig(path3);
|
|
74944
|
+
} catch {
|
|
74945
|
+
return;
|
|
74946
|
+
}
|
|
74947
|
+
const servers = cfg.context_servers;
|
|
74948
|
+
if (typeof servers !== "object" || servers === null || !("dosu" in servers))
|
|
74949
|
+
return;
|
|
74950
|
+
delete servers.dosu;
|
|
74951
|
+
const nothingElse = Object.keys(cfg).length === 1 && Object.keys(servers).length === 0;
|
|
74952
|
+
if (nothingElse)
|
|
74953
|
+
rmSync5(path3, { force: true });
|
|
74954
|
+
else
|
|
74955
|
+
saveJSONConfig(path3, cfg);
|
|
74956
|
+
}
|
|
74957
|
+
var ZedProvider = (legacyPath = legacyDarwinSettingsPath()) => {
|
|
74958
|
+
const provider = createJSONProvider({
|
|
74959
|
+
providerName: "Zed",
|
|
74960
|
+
providerID: "zed",
|
|
74961
|
+
local: true,
|
|
74962
|
+
priorityValue: 10,
|
|
74963
|
+
paths: [zedConfigDir(), zedDataDir()],
|
|
74964
|
+
globalPath: join36(zedConfigDir(), "settings.json"),
|
|
74965
|
+
topKey: "context_servers",
|
|
74966
|
+
buildServer: ({ url: url2, headers }) => ({ url: url2, headers }),
|
|
74967
|
+
localConfigPath: (cwd) => join36(cwd, ".zed", "settings.json")
|
|
74968
|
+
});
|
|
74969
|
+
if (!legacyPath)
|
|
74970
|
+
return provider;
|
|
74971
|
+
return {
|
|
74972
|
+
...provider,
|
|
74973
|
+
install(cfg, global2) {
|
|
74974
|
+
provider.install(cfg, global2);
|
|
74975
|
+
if (global2)
|
|
74976
|
+
removeLegacyDosuEntry(legacyPath);
|
|
74977
|
+
},
|
|
74978
|
+
remove(global2) {
|
|
74979
|
+
provider.remove(global2);
|
|
74980
|
+
if (global2)
|
|
74981
|
+
removeLegacyDosuEntry(legacyPath);
|
|
74982
|
+
}
|
|
74983
|
+
};
|
|
74984
|
+
};
|
|
74878
74985
|
var init_zed = __esm(() => {
|
|
74879
74986
|
init_config_helpers();
|
|
74880
74987
|
init_detect();
|
|
@@ -75088,13 +75195,13 @@ var init_update_check = __esm(() => {
|
|
|
75088
75195
|
|
|
75089
75196
|
// src/rules/installer.ts
|
|
75090
75197
|
import { existsSync as existsSync23, mkdirSync as mkdirSync13, readFileSync as readFileSync22, unlinkSync as unlinkSync4, writeFileSync as writeFileSync14 } from "node:fs";
|
|
75091
|
-
import { homedir as
|
|
75092
|
-
import { dirname as
|
|
75198
|
+
import { homedir as homedir10 } from "node:os";
|
|
75199
|
+
import { dirname as dirname10, join as join38 } from "node:path";
|
|
75093
75200
|
function claudeConfigDir() {
|
|
75094
|
-
return process.env.CLAUDE_CONFIG_DIR || join38(
|
|
75201
|
+
return process.env.CLAUDE_CONFIG_DIR || join38(homedir10(), ".claude");
|
|
75095
75202
|
}
|
|
75096
75203
|
function codexHome3() {
|
|
75097
|
-
return process.env.CODEX_HOME || join38(
|
|
75204
|
+
return process.env.CODEX_HOME || join38(homedir10(), ".codex");
|
|
75098
75205
|
}
|
|
75099
75206
|
function isRuleAgent(agent) {
|
|
75100
75207
|
return Object.hasOwn(RULE_TARGETS, agent);
|
|
@@ -75120,7 +75227,7 @@ async function fetchDosuRule(fetchImpl = globalThis.fetch) {
|
|
|
75120
75227
|
return normalizeRule(FALLBACK_DOSU_RULE);
|
|
75121
75228
|
}
|
|
75122
75229
|
function ensureParent(path3) {
|
|
75123
|
-
mkdirSync13(
|
|
75230
|
+
mkdirSync13(dirname10(path3), { recursive: true });
|
|
75124
75231
|
}
|
|
75125
75232
|
function writeIfChanged(path3, content) {
|
|
75126
75233
|
if (!existsSync23(path3)) {
|
|
@@ -75240,7 +75347,7 @@ var init_installer = __esm(() => {
|
|
|
75240
75347
|
},
|
|
75241
75348
|
cursor: {
|
|
75242
75349
|
kind: "file",
|
|
75243
|
-
path: () => join38(
|
|
75350
|
+
path: () => join38(homedir10(), ".cursor", "rules", "dosu.mdc"),
|
|
75244
75351
|
cursorFrontmatter: true
|
|
75245
75352
|
},
|
|
75246
75353
|
codex: {
|
|
@@ -75249,15 +75356,15 @@ var init_installer = __esm(() => {
|
|
|
75249
75356
|
},
|
|
75250
75357
|
opencode: {
|
|
75251
75358
|
kind: "section",
|
|
75252
|
-
path: () => join38(
|
|
75359
|
+
path: () => join38(homedir10(), ".config", "opencode", "AGENTS.md")
|
|
75253
75360
|
},
|
|
75254
75361
|
gemini: {
|
|
75255
75362
|
kind: "section",
|
|
75256
|
-
path: () => join38(
|
|
75363
|
+
path: () => join38(homedir10(), ".gemini", "GEMINI.md")
|
|
75257
75364
|
},
|
|
75258
75365
|
antigravity: {
|
|
75259
75366
|
kind: "section",
|
|
75260
|
-
path: () => join38(
|
|
75367
|
+
path: () => join38(homedir10(), ".gemini", "GEMINI.md")
|
|
75261
75368
|
}
|
|
75262
75369
|
};
|
|
75263
75370
|
});
|
|
@@ -75395,6 +75502,140 @@ var init_errors3 = __esm(() => {
|
|
|
75395
75502
|
// src/mcp/constants.ts
|
|
75396
75503
|
var MCP_PROVIDER_SLUG2 = "dosu_mcp";
|
|
75397
75504
|
|
|
75505
|
+
// src/sessions/session-title.ts
|
|
75506
|
+
import { existsSync as existsSync25, mkdirSync as mkdirSync14, readFileSync as readFileSync24, renameSync as renameSync6, statSync as statSync9, writeFileSync as writeFileSync16 } from "node:fs";
|
|
75507
|
+
import { homedir as homedir11 } from "node:os";
|
|
75508
|
+
import { join as join40 } from "node:path";
|
|
75509
|
+
function cachePath2(configDir) {
|
|
75510
|
+
return join40(configDir, CACHE_FILENAME5);
|
|
75511
|
+
}
|
|
75512
|
+
function loadCacheFile2(configDir) {
|
|
75513
|
+
try {
|
|
75514
|
+
const raw = JSON.parse(readFileSync24(cachePath2(configDir), "utf-8"));
|
|
75515
|
+
if (raw.schema_version !== CACHE_SCHEMA_VERSION2 || typeof raw.entries !== "object")
|
|
75516
|
+
return {};
|
|
75517
|
+
return raw.entries;
|
|
75518
|
+
} catch {
|
|
75519
|
+
return {};
|
|
75520
|
+
}
|
|
75521
|
+
}
|
|
75522
|
+
function saveCacheFile2(configDir, entries) {
|
|
75523
|
+
try {
|
|
75524
|
+
if (!existsSync25(configDir))
|
|
75525
|
+
mkdirSync14(configDir, { recursive: true, mode: 448 });
|
|
75526
|
+
const path3 = cachePath2(configDir);
|
|
75527
|
+
const tmp = `${path3}.${process.pid}.tmp`;
|
|
75528
|
+
const file2 = { schema_version: CACHE_SCHEMA_VERSION2, entries };
|
|
75529
|
+
writeFileSync16(tmp, JSON.stringify(file2), { mode: 384 });
|
|
75530
|
+
renameSync6(tmp, path3);
|
|
75531
|
+
} catch {}
|
|
75532
|
+
}
|
|
75533
|
+
function extractUserQuery(text) {
|
|
75534
|
+
const tagged = text.match(/<user_query>([\s\S]*?)<\/user_query>/);
|
|
75535
|
+
if (tagged)
|
|
75536
|
+
return tagged[1];
|
|
75537
|
+
const named = text.match(/^\s*<[\w-]+\s[^>]*\bname="([^"]+)"/);
|
|
75538
|
+
if (named)
|
|
75539
|
+
return named[1];
|
|
75540
|
+
return text.replace(/^(\s*<([\w-]+)(?:\s[^>]*)?>[\s\S]*?<\/\2>\s*)+/, "");
|
|
75541
|
+
}
|
|
75542
|
+
function cleanSessionName(text) {
|
|
75543
|
+
const line = text.replace(/\s+/g, " ").trim();
|
|
75544
|
+
if (!line)
|
|
75545
|
+
return null;
|
|
75546
|
+
return line.length > SESSION_NAME_MAX ? `${line.slice(0, SESSION_NAME_MAX - 1)}…` : line;
|
|
75547
|
+
}
|
|
75548
|
+
function claudeSummaryFromHead(text) {
|
|
75549
|
+
for (const line of text.split(`
|
|
75550
|
+
`).slice(0, HEAD_LINES2)) {
|
|
75551
|
+
if (!line.includes('"summary"'))
|
|
75552
|
+
continue;
|
|
75553
|
+
try {
|
|
75554
|
+
const obj = JSON.parse(line);
|
|
75555
|
+
if (obj.type === "summary" && typeof obj.summary === "string") {
|
|
75556
|
+
return cleanSessionName(obj.summary);
|
|
75557
|
+
}
|
|
75558
|
+
} catch {}
|
|
75559
|
+
}
|
|
75560
|
+
return null;
|
|
75561
|
+
}
|
|
75562
|
+
function reconstructSession(harness, id2, slug, home = homedir11(), exists = existsSync25) {
|
|
75563
|
+
if (!slug)
|
|
75564
|
+
return null;
|
|
75565
|
+
const candidates = [];
|
|
75566
|
+
if (harness === "claude") {
|
|
75567
|
+
candidates.push(join40(home, ".claude", "projects", slug, `${id2}.jsonl`));
|
|
75568
|
+
} else if (harness === "cursor") {
|
|
75569
|
+
const project = join40(home, ".cursor", "projects", slug);
|
|
75570
|
+
candidates.push(join40(project, "agent-transcripts", id2, `${id2}.jsonl`), join40(project, "agent-transcripts", `${id2}.jsonl`));
|
|
75571
|
+
} else {
|
|
75572
|
+
return null;
|
|
75573
|
+
}
|
|
75574
|
+
for (const path3 of candidates) {
|
|
75575
|
+
if (exists(path3))
|
|
75576
|
+
return { id: id2, harness, path: path3, project: slug, updated: "" };
|
|
75577
|
+
}
|
|
75578
|
+
return null;
|
|
75579
|
+
}
|
|
75580
|
+
function readHead2(path3) {
|
|
75581
|
+
try {
|
|
75582
|
+
return readFileSync24(path3, "utf-8").slice(0, 128 * 1024);
|
|
75583
|
+
} catch {
|
|
75584
|
+
return null;
|
|
75585
|
+
}
|
|
75586
|
+
}
|
|
75587
|
+
function fileMtime2(path3) {
|
|
75588
|
+
try {
|
|
75589
|
+
return statSync9(path3).mtime.toISOString();
|
|
75590
|
+
} catch {
|
|
75591
|
+
return "";
|
|
75592
|
+
}
|
|
75593
|
+
}
|
|
75594
|
+
function createSessionTitleResolver(configDir = getConfigDir(), deps = {}) {
|
|
75595
|
+
const head = deps.readHead ?? readHead2;
|
|
75596
|
+
const turns = deps.readTurns ?? readSessionTurns;
|
|
75597
|
+
const mtime = deps.mtime ?? fileMtime2;
|
|
75598
|
+
const entries = loadCacheFile2(configDir);
|
|
75599
|
+
let dirty = false;
|
|
75600
|
+
const compute = (session) => {
|
|
75601
|
+
if (session.harness === "claude") {
|
|
75602
|
+
const text = head(session.path);
|
|
75603
|
+
const summary = text ? claudeSummaryFromHead(text) : null;
|
|
75604
|
+
if (summary)
|
|
75605
|
+
return summary;
|
|
75606
|
+
}
|
|
75607
|
+
const firstUser = turns(session).find((t9) => t9.role === "user");
|
|
75608
|
+
return firstUser ? cleanSessionName(extractUserQuery(firstUser.text)) : null;
|
|
75609
|
+
};
|
|
75610
|
+
return {
|
|
75611
|
+
cached(key) {
|
|
75612
|
+
return entries[key]?.title ?? null;
|
|
75613
|
+
},
|
|
75614
|
+
resolve(session) {
|
|
75615
|
+
const key = `${session.harness}/${session.id}`;
|
|
75616
|
+
const cached2 = entries[key];
|
|
75617
|
+
if (cached2 && (cached2.title !== null || cached2.mtime === mtime(session.path))) {
|
|
75618
|
+
return cached2.title;
|
|
75619
|
+
}
|
|
75620
|
+
const title = compute(session);
|
|
75621
|
+
entries[key] = { title, mtime: mtime(session.path) };
|
|
75622
|
+
dirty = true;
|
|
75623
|
+
return title;
|
|
75624
|
+
},
|
|
75625
|
+
flush() {
|
|
75626
|
+
if (!dirty)
|
|
75627
|
+
return;
|
|
75628
|
+
saveCacheFile2(configDir, entries);
|
|
75629
|
+
dirty = false;
|
|
75630
|
+
}
|
|
75631
|
+
};
|
|
75632
|
+
}
|
|
75633
|
+
var CACHE_FILENAME5 = "session-titles.json", CACHE_SCHEMA_VERSION2 = 3, HEAD_LINES2 = 50, SESSION_NAME_MAX = 80;
|
|
75634
|
+
var init_session_title = __esm(() => {
|
|
75635
|
+
init_config();
|
|
75636
|
+
init_read();
|
|
75637
|
+
});
|
|
75638
|
+
|
|
75398
75639
|
// src/tui/alt-screen.ts
|
|
75399
75640
|
function enterAltScreen(output2) {
|
|
75400
75641
|
depth += 1;
|
|
@@ -75418,7 +75659,8 @@ var init_alt_screen = __esm(() => {
|
|
|
75418
75659
|
});
|
|
75419
75660
|
|
|
75420
75661
|
// src/tui/activity-view.ts
|
|
75421
|
-
import { readFileSync as
|
|
75662
|
+
import { existsSync as existsSync26, readFileSync as readFileSync25 } from "node:fs";
|
|
75663
|
+
import { basename as basename5 } from "node:path";
|
|
75422
75664
|
function reduceActivityViewKey(key) {
|
|
75423
75665
|
if (key === "q" || key === ESC7 || key === CTRL_C4)
|
|
75424
75666
|
return "back";
|
|
@@ -75432,6 +75674,10 @@ function reduceActivityViewKey(key) {
|
|
|
75432
75674
|
return "down";
|
|
75433
75675
|
if (key === "s")
|
|
75434
75676
|
return "sync";
|
|
75677
|
+
if (key === "f")
|
|
75678
|
+
return "full";
|
|
75679
|
+
if (key === "c")
|
|
75680
|
+
return "clear";
|
|
75435
75681
|
return "none";
|
|
75436
75682
|
}
|
|
75437
75683
|
function cycleTab(tab, delta = 1) {
|
|
@@ -75454,33 +75700,49 @@ function syncConfirmMode(status) {
|
|
|
75454
75700
|
return "resume";
|
|
75455
75701
|
return "start";
|
|
75456
75702
|
}
|
|
75457
|
-
function formatActivityLine(line, width) {
|
|
75703
|
+
function formatActivityLine(line, width, full = false) {
|
|
75458
75704
|
const compact = stripAnsiCodes(line).replace(/^\[(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2}:\d{2})[^\]]*\]/, (_match, _date2, time3) => time3);
|
|
75459
|
-
if (compact.length <= width)
|
|
75705
|
+
if (full || compact.length <= width)
|
|
75460
75706
|
return compact;
|
|
75461
75707
|
return `${compact.slice(0, Math.max(0, width - 1))}…`;
|
|
75462
75708
|
}
|
|
75463
75709
|
function appendSyncActivity(buffer, chunk, max = ACTIVITY_VIEW_BUFFER_LINES) {
|
|
75464
75710
|
const fresh = chunk.split(`
|
|
75465
|
-
`).filter((line) => line.includes("[sync]") || line.includes("[
|
|
75711
|
+
`).filter((line) => line.includes("[sync]") || line.includes("[learner]")).map((line) => line.replace(/ \[(DEBUG|INFO|WARN|ERROR)\]/, ""));
|
|
75466
75712
|
return [...buffer, ...fresh].slice(-max);
|
|
75467
75713
|
}
|
|
75468
75714
|
function clip(text, max) {
|
|
75469
75715
|
return text.length <= max ? text : `${text.slice(0, max - 1)}…`;
|
|
75470
75716
|
}
|
|
75471
|
-
function
|
|
75717
|
+
function formatStudiedRow(record2, full = false, name) {
|
|
75472
75718
|
const match = record2.at.match(/^\d{4}-(\d{2}-\d{2})T(\d{2}:\d{2})/);
|
|
75473
75719
|
const stamp = match ? `${match[1]} ${match[2]}` : record2.at;
|
|
75474
75720
|
const slash = record2.session.indexOf("/");
|
|
75475
75721
|
const harness = slash > 0 ? record2.session.slice(0, slash) : "-";
|
|
75476
75722
|
const id2 = slash > 0 ? record2.session.slice(slash + 1) : record2.session;
|
|
75477
|
-
const
|
|
75478
|
-
|
|
75723
|
+
const rawProject = record2.project ?? "-";
|
|
75724
|
+
const project = full ? rawProject : clip(rawProject, 28);
|
|
75725
|
+
const label = full ? name ? `${name} · ${id2}` : id2 : clip(name ?? id2, 44);
|
|
75726
|
+
return `${harness.padEnd(8)} ${stamp} ${project} ${label}`;
|
|
75479
75727
|
}
|
|
75480
|
-
function formatQueuedRow(session) {
|
|
75728
|
+
function formatQueuedRow(session, full = false, name) {
|
|
75481
75729
|
const stamp = session.updated.replace("T", " ").slice(5, 16);
|
|
75482
|
-
const
|
|
75483
|
-
|
|
75730
|
+
const rawProject = session.project ?? "-";
|
|
75731
|
+
const project = full ? rawProject : clip(rawProject, 28);
|
|
75732
|
+
const label = full ? name ? `${name} · ${session.id}` : session.id : clip(name ?? session.id, 44);
|
|
75733
|
+
return `${session.harness.padEnd(8)} ${stamp} ${project} ${label}`;
|
|
75734
|
+
}
|
|
75735
|
+
function wrapRow(text, width, indent = " ") {
|
|
75736
|
+
if (text.length <= width)
|
|
75737
|
+
return [text];
|
|
75738
|
+
const out = [text.slice(0, width)];
|
|
75739
|
+
let rest = text.slice(width);
|
|
75740
|
+
const inner = Math.max(1, width - indent.length);
|
|
75741
|
+
while (rest.length > 0) {
|
|
75742
|
+
out.push(indent + rest.slice(0, inner));
|
|
75743
|
+
rest = rest.slice(inner);
|
|
75744
|
+
}
|
|
75745
|
+
return out;
|
|
75484
75746
|
}
|
|
75485
75747
|
function windowList(lines, scroll, height = ACTIVITY_VIEW_LIST_LINES) {
|
|
75486
75748
|
const clamped = Math.max(0, Math.min(scroll, Math.max(0, lines.length - height)));
|
|
@@ -75508,23 +75770,23 @@ function foldRunProgress(progress, chunk) {
|
|
|
75508
75770
|
let current = progress;
|
|
75509
75771
|
for (const line of chunk.split(`
|
|
75510
75772
|
`)) {
|
|
75511
|
-
const start = line.match(/\[sync\] mining (\d+) of \d+ ready sessions/);
|
|
75773
|
+
const start = line.match(/\[sync\] (?:studying|mining) (\d+) of \d+ ready sessions/);
|
|
75512
75774
|
if (start) {
|
|
75513
75775
|
current = { batch: Number.parseInt(start[1], 10), read: new Set, notes: 0 };
|
|
75514
75776
|
continue;
|
|
75515
75777
|
}
|
|
75516
|
-
if (/\[sync\] (mined \d+ sessions|mining failed|
|
|
75778
|
+
if (/\[sync\] (?:studied|mined) \d+ sessions|\[sync\] (?:studying|mining) (?:failed|skipped)/.test(line)) {
|
|
75517
75779
|
current = null;
|
|
75518
75780
|
continue;
|
|
75519
75781
|
}
|
|
75520
75782
|
if (!current)
|
|
75521
75783
|
continue;
|
|
75522
|
-
const read = line.match(/\[
|
|
75784
|
+
const read = line.match(/\[learner\] \[agent\] → mcp__sessions__read_session .*?"id":"([^"]+)"/);
|
|
75523
75785
|
if (read) {
|
|
75524
75786
|
current.read.add(read[1]);
|
|
75525
75787
|
continue;
|
|
75526
75788
|
}
|
|
75527
|
-
if (line.includes("[
|
|
75789
|
+
if (line.includes("[learner] [agent] → mcp__dosu__write_knowledge")) {
|
|
75528
75790
|
current.notes += 1;
|
|
75529
75791
|
}
|
|
75530
75792
|
}
|
|
@@ -75538,15 +75800,15 @@ function statusLine(status) {
|
|
|
75538
75800
|
if (status.running) {
|
|
75539
75801
|
const since = status.startedAt ? ` · since ${localTime(status.startedAt)}` : "";
|
|
75540
75802
|
const pid = status.pid !== undefined ? ` (pid ${status.pid})` : "";
|
|
75541
|
-
return
|
|
75803
|
+
return `\uD83D\uDCDA ${import_picocolors30.default.bold(brand("Studying sessions..."))}${import_picocolors30.default.dim(`${pid}${since}`)}`;
|
|
75542
75804
|
}
|
|
75543
75805
|
if (status.state.paused) {
|
|
75544
|
-
return `${import_picocolors30.default.yellow("○")} ${import_picocolors30.default.bold("Paused")} ${import_picocolors30.default.dim("·
|
|
75806
|
+
return `${import_picocolors30.default.yellow("○")} ${import_picocolors30.default.bold("Paused")} ${import_picocolors30.default.dim("· studying stays off until you resume")}`;
|
|
75545
75807
|
}
|
|
75546
75808
|
if (status.staleLock) {
|
|
75547
75809
|
return `${import_picocolors30.default.yellow("○")} ${import_picocolors30.default.bold("Not running")} ${import_picocolors30.default.dim("· a previous run exited without cleaning up")}`;
|
|
75548
75810
|
}
|
|
75549
|
-
return `${import_picocolors30.default.dim("○")} ${import_picocolors30.default.bold("Idle")} ${import_picocolors30.default.dim("· hooks
|
|
75811
|
+
return `${import_picocolors30.default.dim("○")} ${import_picocolors30.default.bold("Idle")} ${import_picocolors30.default.dim("· hooks study new sessions automatically")}`;
|
|
75550
75812
|
}
|
|
75551
75813
|
function progressLine(done, ready, width, notes = 0) {
|
|
75552
75814
|
const total = done + ready;
|
|
@@ -75558,14 +75820,14 @@ function progressLine(done, ready, width, notes = 0) {
|
|
|
75558
75820
|
const bar = brand("█".repeat(filled)) + import_picocolors30.default.dim("░".repeat(cells - filled));
|
|
75559
75821
|
const pct = Math.floor(ratio * 100);
|
|
75560
75822
|
const suffix = notes > 0 ? ` · ${notes} suggested page${notes === 1 ? "" : "s"}` : "";
|
|
75561
|
-
return `${bar} ${done}/${total}
|
|
75823
|
+
return `${bar} ${done}/${total} studied · ${pct}%${suffix}`;
|
|
75562
75824
|
}
|
|
75563
|
-
function tabBar(tab, queuedCount, openCount,
|
|
75825
|
+
function tabBar(tab, queuedCount, openCount, studiedCount, width) {
|
|
75564
75826
|
return tabStrip([
|
|
75565
|
-
["activity", "
|
|
75566
|
-
["
|
|
75567
|
-
["queued", `
|
|
75568
|
-
["open", `
|
|
75827
|
+
["activity", "activity"],
|
|
75828
|
+
["studied", `studied (${studiedCount})`],
|
|
75829
|
+
["queued", `queued (${queuedCount})`],
|
|
75830
|
+
["open", `open (${openCount})`]
|
|
75569
75831
|
], tab, width);
|
|
75570
75832
|
}
|
|
75571
75833
|
function activityWidth(columns) {
|
|
@@ -75587,10 +75849,11 @@ function wrapLine(text, width, indent = " ") {
|
|
|
75587
75849
|
return out;
|
|
75588
75850
|
}
|
|
75589
75851
|
function confirmBox(queuedCount, backlog, width, mode = "start") {
|
|
75590
|
-
const inFlight = backlog && backlog.inFlight > 0 ? ` (+${backlog.inFlight} open
|
|
75591
|
-
const
|
|
75592
|
-
const
|
|
75593
|
-
const
|
|
75852
|
+
const inFlight = backlog && backlog.inFlight > 0 ? ` (+${backlog.inFlight} still open; ${backlog.inFlight === 1 ? "it joins" : "they join"} once quiet)` : "";
|
|
75853
|
+
const sessions = `${queuedCount} session${queuedCount === 1 ? "" : "s"}`;
|
|
75854
|
+
const scope = mode === "clear" ? "Forgets which local sessions Dosu has already studied, so the next run reads them all again. Use this to rebuild knowledge from scratch. Notes already saved in Dosu are kept." : mode === "stop" ? "Kills the run mid-batch and pauses studying, so new sessions pile up in the queue instead of being read. Nothing is lost; press s again to resume from where it left off." : mode === "resume" ? `Studying picks up where it stopped: ${sessions} queued${inFlight}. Runs in the background; new notes appear in Dosu as each batch finishes.` : queuedCount > 0 ? `Dosu reads ${sessions}${inFlight} and distills the durable decisions and gotchas into team knowledge. Runs in the background; new notes appear in Dosu as each batch finishes.` : `Queue is empty${inFlight}. A run now would only pick up sessions that finish from here; hooks already do that automatically.`;
|
|
75855
|
+
const title = mode === "clear" ? "Clear study history?" : mode === "stop" ? "Stop studying?" : mode === "resume" ? "Resume studying?" : "Start studying now?";
|
|
75856
|
+
const verb = mode === "clear" ? "clear" : mode === "stop" ? "stop" : mode === "resume" ? "resume" : "start";
|
|
75594
75857
|
const maxInner = Math.max(20, Math.min(width, contentWidth()) - 4);
|
|
75595
75858
|
const rows = [
|
|
75596
75859
|
import_picocolors30.default.bold(brand(title)),
|
|
@@ -75606,64 +75869,143 @@ function confirmBox(queuedCount, backlog, width, mode = "start") {
|
|
|
75606
75869
|
import_picocolors30.default.dim(`╰${"─".repeat(inner + 2)}╯`)
|
|
75607
75870
|
];
|
|
75608
75871
|
}
|
|
75609
|
-
function renderActivityFrame(status, activity, width, backlog = null, pane = DEFAULT_PANE, queued = [],
|
|
75610
|
-
const
|
|
75872
|
+
function renderActivityFrame(status, activity, width, backlog = null, pane = DEFAULT_PANE, queued = [], studiedBeforeRun = 0, open5 = [], runProgress = null, projectNames = {}, sessionNames = {}) {
|
|
75873
|
+
const studied = status.state.watermark ? `Studied sessions up to ${localTime(status.state.watermark)}` : "Nothing studied yet";
|
|
75611
75874
|
const queueDetail = [];
|
|
75612
75875
|
if (status.backoffUntil) {
|
|
75613
75876
|
queueDetail.push(`retrying after ${localTime(status.backoffUntil)} · s syncs now`);
|
|
75614
75877
|
}
|
|
75615
75878
|
let progress = null;
|
|
75616
75879
|
if (status.running && backlog) {
|
|
75617
|
-
const
|
|
75880
|
+
const studiedDelta = Math.max(0, (status.state.total_mined ?? 0) - studiedBeforeRun);
|
|
75618
75881
|
const stepDone = runProgress ? Math.min(Math.max(0, runProgress.read.size - 1), runProgress.batch - 1, backlog.ready) : 0;
|
|
75619
|
-
progress = progressLine(
|
|
75882
|
+
progress = progressLine(studiedDelta + stepDone, Math.max(0, backlog.ready - stepDone), width, runProgress?.notes ?? 0);
|
|
75620
75883
|
}
|
|
75621
75884
|
const refusal = !status.running && status.state.last_refusal;
|
|
75622
|
-
const refusalLines = refusal ? wrapLine(`!
|
|
75623
|
-
const
|
|
75624
|
-
const
|
|
75625
|
-
const
|
|
75626
|
-
const
|
|
75627
|
-
|
|
75628
|
-
const
|
|
75629
|
-
|
|
75630
|
-
|
|
75885
|
+
const refusalLines = refusal ? wrapLine(`! Studying paused: ${refusal.message} (${localTime(refusal.at)})`, width).map((line) => import_picocolors30.default.yellow(line)) : [];
|
|
75886
|
+
const fullRows = Boolean(pane.fullRows);
|
|
75887
|
+
const withName = (item, key) => projectNames[key] ? { ...item, project: projectNames[key] } : item;
|
|
75888
|
+
const latestPass = new Map;
|
|
75889
|
+
for (const r9 of status.state.mined_sessions ?? [])
|
|
75890
|
+
latestPass.set(r9.session, r9);
|
|
75891
|
+
const studiedRows = [...latestPass.values()].map((r9) => {
|
|
75892
|
+
const record2 = withName(r9, r9.session);
|
|
75893
|
+
const name = sessionNames[r9.session];
|
|
75894
|
+
return fullRows ? formatStudiedRow(record2, true, name) : formatActivityLine(formatStudiedRow(record2, false, name), width);
|
|
75895
|
+
});
|
|
75896
|
+
const sessionRow = (session) => {
|
|
75897
|
+
const key = `${session.harness}/${session.id}`;
|
|
75898
|
+
const s = withName(session, key);
|
|
75899
|
+
const name = sessionNames[key];
|
|
75900
|
+
return fullRows ? formatQueuedRow(s, true, name) : formatActivityLine(formatQueuedRow(s, false, name), width);
|
|
75901
|
+
};
|
|
75902
|
+
const queuedRows = queued.map(sessionRow);
|
|
75903
|
+
const openRows = open5.map(sessionRow);
|
|
75904
|
+
const source = pane.tab === "activity" ? activity.map((line) => formatActivityLine(line, width, fullRows)) : pane.tab === "queued" ? queuedRows : pane.tab === "open" ? openRows : studiedRows;
|
|
75905
|
+
const emptyStudied = status.state.watermark ? "No sessions recorded yet. History starts with the next study run." : "No studied sessions yet.";
|
|
75906
|
+
const empty = pane.tab === "activity" ? "No sync activity in the log yet." : pane.tab === "queued" ? "Queue empty. Finished agent sessions appear here." : pane.tab === "open" ? "No open sessions. Live agent sessions sit here until they go quiet." : emptyStudied;
|
|
75907
|
+
const height = fullRows ? ACTIVITY_VIEW_FULL_LIST_ROWS : ACTIVITY_VIEW_LIST_LINES;
|
|
75908
|
+
const { visible, above, below } = windowList(source, pane.scroll, height);
|
|
75909
|
+
const display = fullRows ? visible.flatMap((row) => wrapRow(row, width)) : visible;
|
|
75910
|
+
const listRows = display.length > 0 ? display.map((row) => import_picocolors30.default.dim(row)) : [import_picocolors30.default.dim(empty)];
|
|
75631
75911
|
const scrollParts = [];
|
|
75632
75912
|
if (above > 0)
|
|
75633
75913
|
scrollParts.push(`↑ ${above} earlier`);
|
|
75634
75914
|
if (below > 0)
|
|
75635
75915
|
scrollParts.push(`↓ ${below} newer`);
|
|
75636
|
-
if (pane.tab !== "activity" && visible.length > 0) {
|
|
75637
|
-
scrollParts.push(`full rows: dosu knowledge sessions --${pane.tab}`);
|
|
75638
|
-
}
|
|
75639
75916
|
const lines = [
|
|
75640
|
-
breadcrumb(["
|
|
75917
|
+
breadcrumb(["home", "activity"], width),
|
|
75641
75918
|
"",
|
|
75642
75919
|
statusLine(status),
|
|
75643
|
-
import_picocolors30.default.dim(
|
|
75920
|
+
import_picocolors30.default.dim(studied),
|
|
75644
75921
|
...queueDetail.length > 0 ? wrapLine(queueDetail.join(" · "), width).map((line) => import_picocolors30.default.dim(line)) : [],
|
|
75645
75922
|
...progress ? [progress] : [],
|
|
75646
75923
|
...refusalLines,
|
|
75647
75924
|
"",
|
|
75648
|
-
...tabBar(pane.tab, queued.length, open5.length,
|
|
75925
|
+
...tabBar(pane.tab, queued.length, open5.length, latestPass.size, width),
|
|
75649
75926
|
...listRows,
|
|
75650
|
-
...scrollParts.length > 0 ? [import_picocolors30.default.dim(scrollParts.join(" · "))] : [],
|
|
75927
|
+
...scrollParts.length > 0 ? [import_picocolors30.default.dim(clip(scrollParts.join(" · "), width))] : [],
|
|
75651
75928
|
"",
|
|
75652
75929
|
...pane.confirm ? centerBlock(confirmBox(queued.length, backlog, width, pane.confirm), width) : [
|
|
75653
|
-
|
|
75930
|
+
...wrapLine([
|
|
75654
75931
|
"tab switch",
|
|
75655
75932
|
"↑↓ scroll",
|
|
75933
|
+
fullRows ? "f clip" : "f full rows",
|
|
75656
75934
|
status.running ? "s stop" : status.state.paused ? "s resume" : "s sync now",
|
|
75935
|
+
...!status.running && status.state.watermark ? ["c clear"] : [],
|
|
75657
75936
|
"esc back"
|
|
75658
|
-
].join(" · "))
|
|
75937
|
+
].join(" · "), width, "").map((line) => import_picocolors30.default.dim(line))
|
|
75659
75938
|
]
|
|
75660
75939
|
];
|
|
75661
75940
|
return lines.join(`
|
|
75662
75941
|
`);
|
|
75663
75942
|
}
|
|
75943
|
+
function createRowNamer() {
|
|
75944
|
+
const dirs = createProjectDirResolver();
|
|
75945
|
+
const titles = createSessionTitleResolver();
|
|
75946
|
+
const bySlug = new Map;
|
|
75947
|
+
const fromSlug = (slug) => {
|
|
75948
|
+
if (!bySlug.has(slug))
|
|
75949
|
+
bySlug.set(slug, unmungeSlug(slug));
|
|
75950
|
+
return bySlug.get(slug) ?? null;
|
|
75951
|
+
};
|
|
75952
|
+
return (status, backlog) => {
|
|
75953
|
+
const names = { projects: {}, titles: {} };
|
|
75954
|
+
let budget = TITLE_READS_PER_DRAW;
|
|
75955
|
+
let resolved = false;
|
|
75956
|
+
const title = (key, session) => {
|
|
75957
|
+
const hit = titles.cached(key);
|
|
75958
|
+
if (hit) {
|
|
75959
|
+
names.titles[key] = hit;
|
|
75960
|
+
return;
|
|
75961
|
+
}
|
|
75962
|
+
if (budget <= 0)
|
|
75963
|
+
return;
|
|
75964
|
+
budget--;
|
|
75965
|
+
const t9 = titles.resolve(session);
|
|
75966
|
+
resolved = true;
|
|
75967
|
+
if (t9)
|
|
75968
|
+
names.titles[key] = t9;
|
|
75969
|
+
};
|
|
75970
|
+
for (const s of [...backlog.queued, ...backlog.open]) {
|
|
75971
|
+
if (!existsSync26(s.path))
|
|
75972
|
+
continue;
|
|
75973
|
+
const key = `${s.harness}/${s.id}`;
|
|
75974
|
+
const dir = dirs.resolve(s);
|
|
75975
|
+
if (dir)
|
|
75976
|
+
names.projects[key] = basename5(dir);
|
|
75977
|
+
title(key, s);
|
|
75978
|
+
resolved = true;
|
|
75979
|
+
}
|
|
75980
|
+
for (const r9 of [...status.state.mined_sessions ?? []].reverse()) {
|
|
75981
|
+
const key = r9.session;
|
|
75982
|
+
if (!names.projects[key]) {
|
|
75983
|
+
const dir = dirs.cached(key) ?? (r9.project ? fromSlug(r9.project) : null);
|
|
75984
|
+
if (dir)
|
|
75985
|
+
names.projects[key] = basename5(dir);
|
|
75986
|
+
}
|
|
75987
|
+
if (!names.titles[key]) {
|
|
75988
|
+
const hit = titles.cached(key);
|
|
75989
|
+
if (hit) {
|
|
75990
|
+
names.titles[key] = hit;
|
|
75991
|
+
} else if (budget > 0) {
|
|
75992
|
+
const slash = key.indexOf("/");
|
|
75993
|
+
const session = reconstructSession(key.slice(0, slash), key.slice(slash + 1), r9.project);
|
|
75994
|
+
if (session)
|
|
75995
|
+
title(key, session);
|
|
75996
|
+
}
|
|
75997
|
+
}
|
|
75998
|
+
}
|
|
75999
|
+
if (resolved) {
|
|
76000
|
+
dirs.flush();
|
|
76001
|
+
titles.flush();
|
|
76002
|
+
}
|
|
76003
|
+
return names;
|
|
76004
|
+
};
|
|
76005
|
+
}
|
|
75664
76006
|
function defaultReadLog2() {
|
|
75665
76007
|
try {
|
|
75666
|
-
return
|
|
76008
|
+
return readFileSync25(logger.getLogPath(), "utf-8");
|
|
75667
76009
|
} catch {
|
|
75668
76010
|
return "";
|
|
75669
76011
|
}
|
|
@@ -75679,7 +76021,9 @@ function runActivityView(io2 = {}) {
|
|
|
75679
76021
|
const startSync = io2.startSync ?? (() => spawnDetachedSelf(["knowledge", "sync", "--bootstrap"]));
|
|
75680
76022
|
const stopSync = io2.stopSync ?? stopSyncRun;
|
|
75681
76023
|
const setPaused = io2.setPaused ?? setSyncPaused;
|
|
76024
|
+
const clearHistory = io2.clearHistory ?? resetSyncState;
|
|
75682
76025
|
const listBacklog = io2.listBacklog ?? listSessionBacklog;
|
|
76026
|
+
const rowNamesFor = io2.rowNames ?? createRowNamer();
|
|
75683
76027
|
const pollMs = io2.pollMs ?? ACTIVITY_VIEW_POLL_MS;
|
|
75684
76028
|
const seed = readLog();
|
|
75685
76029
|
let activity = appendSyncActivity([], seed);
|
|
@@ -75687,6 +76031,7 @@ function runActivityView(io2 = {}) {
|
|
|
75687
76031
|
let runProgress = foldRunProgress(null, seed);
|
|
75688
76032
|
let tab = "activity";
|
|
75689
76033
|
let scroll = 0;
|
|
76034
|
+
let fullRows = false;
|
|
75690
76035
|
let confirmSync = null;
|
|
75691
76036
|
let status = getStatus();
|
|
75692
76037
|
let sessions = listBacklog();
|
|
@@ -75705,30 +76050,32 @@ function runActivityView(io2 = {}) {
|
|
|
75705
76050
|
return sessions.open.length;
|
|
75706
76051
|
return (status.state.mined_sessions ?? []).length;
|
|
75707
76052
|
};
|
|
76053
|
+
const listHeight = () => fullRows ? ACTIVITY_VIEW_FULL_LIST_ROWS : ACTIVITY_VIEW_LIST_LINES;
|
|
75708
76054
|
let lastFrame = null;
|
|
75709
|
-
let
|
|
76055
|
+
let studiedBeforeRun = null;
|
|
75710
76056
|
const draw = () => {
|
|
75711
76057
|
status = getStatus();
|
|
75712
76058
|
if (status.running) {
|
|
75713
76059
|
const run2 = status.state.run;
|
|
75714
76060
|
if (run2 && run2.pid === status.pid) {
|
|
75715
|
-
|
|
76061
|
+
studiedBeforeRun = run2.baseline_mined;
|
|
75716
76062
|
} else {
|
|
75717
|
-
|
|
76063
|
+
studiedBeforeRun ??= status.state.total_mined ?? 0;
|
|
75718
76064
|
}
|
|
75719
76065
|
} else {
|
|
75720
|
-
|
|
76066
|
+
studiedBeforeRun = null;
|
|
75721
76067
|
}
|
|
75722
76068
|
if (status.state.watermark !== queuedWatermark) {
|
|
75723
76069
|
queuedWatermark = status.state.watermark;
|
|
75724
76070
|
sessions = listBacklog();
|
|
75725
76071
|
}
|
|
75726
|
-
if (
|
|
76072
|
+
if (confirmSync !== null && confirmSync !== "stop" && status.running)
|
|
75727
76073
|
confirmSync = null;
|
|
75728
76074
|
if (confirmSync === "stop" && !status.running)
|
|
75729
76075
|
confirmSync = null;
|
|
75730
76076
|
const width = activityWidth(output2.columns ?? 80);
|
|
75731
|
-
const
|
|
76077
|
+
const rowNames = rowNamesFor(status, sessions);
|
|
76078
|
+
const frame = renderActivityFrame(status, activity, width, backlog, { tab, scroll, confirm: confirmSync ?? undefined, fullRows }, sessions.queued, studiedBeforeRun ?? 0, sessions.open, runProgress, rowNames.projects, rowNames.titles);
|
|
75732
76079
|
if (frame === lastFrame)
|
|
75733
76080
|
return;
|
|
75734
76081
|
lastFrame = frame;
|
|
@@ -75775,11 +76122,15 @@ ${CLEAR_BELOW}`);
|
|
|
75775
76122
|
const mode = confirmSync;
|
|
75776
76123
|
confirmSync = null;
|
|
75777
76124
|
let note;
|
|
75778
|
-
if (mode === "
|
|
76125
|
+
if (mode === "clear") {
|
|
76126
|
+
clearHistory();
|
|
76127
|
+
studiedBeforeRun = null;
|
|
76128
|
+
note = "[sync] study history cleared · the next run reads every local session again";
|
|
76129
|
+
} else if (mode === "stop") {
|
|
75779
76130
|
const ok2 = status.pid !== undefined && stopSync(status.pid);
|
|
75780
76131
|
if (ok2)
|
|
75781
76132
|
setPaused(true);
|
|
75782
|
-
note = ok2 ? "[sync]
|
|
76133
|
+
note = ok2 ? "[sync] studying stopped · paused until you resume" : "[sync] could not stop the run · it may have just finished";
|
|
75783
76134
|
} else {
|
|
75784
76135
|
if (mode === "resume")
|
|
75785
76136
|
setPaused(false);
|
|
@@ -75803,14 +76154,23 @@ ${CLEAR_BELOW}`);
|
|
|
75803
76154
|
if (action === "sync") {
|
|
75804
76155
|
confirmSync = syncConfirmMode(status);
|
|
75805
76156
|
draw();
|
|
76157
|
+
} else if (action === "clear") {
|
|
76158
|
+
if (!status.running && status.state.watermark) {
|
|
76159
|
+
confirmSync = "clear";
|
|
76160
|
+
draw();
|
|
76161
|
+
}
|
|
75806
76162
|
} else if (action === "tab" || action === "tab-back") {
|
|
75807
76163
|
tab = cycleTab(tab, action === "tab" ? 1 : -1);
|
|
75808
76164
|
scroll = 0;
|
|
75809
76165
|
if (tab === "queued" || tab === "open")
|
|
75810
76166
|
sessions = listBacklog();
|
|
75811
76167
|
draw();
|
|
76168
|
+
} else if (action === "full") {
|
|
76169
|
+
fullRows = !fullRows;
|
|
76170
|
+
scroll = 0;
|
|
76171
|
+
draw();
|
|
75812
76172
|
} else if (action === "up") {
|
|
75813
|
-
const maxScroll = Math.max(0, activeListLength() -
|
|
76173
|
+
const maxScroll = Math.max(0, activeListLength() - listHeight());
|
|
75814
76174
|
if (scroll < maxScroll) {
|
|
75815
76175
|
scroll += 1;
|
|
75816
76176
|
draw();
|
|
@@ -75826,10 +76186,12 @@ ${CLEAR_BELOW}`);
|
|
|
75826
76186
|
input2.on("data", onData);
|
|
75827
76187
|
});
|
|
75828
76188
|
}
|
|
75829
|
-
var import_picocolors30, ESC7, HIDE_CURSOR3, SHOW_CURSOR3, CTRL_C4, KEY_UP4, KEY_DOWN4, KEY_RIGHT3, KEY_LEFT3, CURSOR_HOME, CLEAR_BELOW, CLEAR_EOL, ACTIVITY_VIEW_POLL_MS = 500, ACTIVITY_VIEW_LIST_LINES = 10, ACTIVITY_VIEW_BUFFER_LINES = 200, ACTIVITY_VIEW_TABS, DEFAULT_PANE;
|
|
76189
|
+
var import_picocolors30, ESC7, HIDE_CURSOR3, SHOW_CURSOR3, CTRL_C4, KEY_UP4, KEY_DOWN4, KEY_RIGHT3, KEY_LEFT3, CURSOR_HOME, CLEAR_BELOW, CLEAR_EOL, ACTIVITY_VIEW_POLL_MS = 500, ACTIVITY_VIEW_LIST_LINES = 10, ACTIVITY_VIEW_FULL_LIST_ROWS = 5, ACTIVITY_VIEW_BUFFER_LINES = 200, ACTIVITY_VIEW_TABS, DEFAULT_PANE, TITLE_READS_PER_DRAW = 25;
|
|
75830
76190
|
var init_activity_view = __esm(() => {
|
|
75831
76191
|
init_follow();
|
|
75832
76192
|
init_logger();
|
|
76193
|
+
init_project_dir();
|
|
76194
|
+
init_session_title();
|
|
75833
76195
|
init_styles();
|
|
75834
76196
|
init_backlog();
|
|
75835
76197
|
init_detach();
|
|
@@ -75851,7 +76213,7 @@ var init_activity_view = __esm(() => {
|
|
|
75851
76213
|
CURSOR_HOME = `${ESC7}[H`;
|
|
75852
76214
|
CLEAR_BELOW = `${ESC7}[0J`;
|
|
75853
76215
|
CLEAR_EOL = `${ESC7}[K`;
|
|
75854
|
-
ACTIVITY_VIEW_TABS = ["activity", "
|
|
76216
|
+
ACTIVITY_VIEW_TABS = ["activity", "studied", "queued", "open"];
|
|
75855
76217
|
DEFAULT_PANE = { tab: "activity", scroll: 0 };
|
|
75856
76218
|
});
|
|
75857
76219
|
|
|
@@ -75864,7 +76226,7 @@ async function trackCliOnboardingEvent(cfg, onboardingRunID, event, properties =
|
|
|
75864
76226
|
return;
|
|
75865
76227
|
if (!cfg.active_account?.session.access_token)
|
|
75866
76228
|
return;
|
|
75867
|
-
if (!
|
|
76229
|
+
if (!isUUID3(onboardingRunID))
|
|
75868
76230
|
return;
|
|
75869
76231
|
const input2 = {
|
|
75870
76232
|
event,
|
|
@@ -75887,7 +76249,7 @@ async function trackCliOnboardingPreAuthEvent(onboardingRunID, event, properties
|
|
|
75887
76249
|
return;
|
|
75888
76250
|
if (!analyticsReleaseEnabled())
|
|
75889
76251
|
return;
|
|
75890
|
-
if (!
|
|
76252
|
+
if (!isUUID3(onboardingRunID))
|
|
75891
76253
|
return;
|
|
75892
76254
|
const input2 = {
|
|
75893
76255
|
event,
|
|
@@ -75920,13 +76282,14 @@ function analyticsReleaseEnabled() {
|
|
|
75920
76282
|
return Boolean(parsePostHogProjectToken(process.env.DOSU_POSTHOG_PROJECT_TOKEN_OVERRIDE ?? "phc_Uo9RxhN0HRk8gZkPlT18BCr7eqsM0MPWVW88HZB95V5"));
|
|
75921
76283
|
}
|
|
75922
76284
|
function baseProperties(cfg) {
|
|
76285
|
+
const orgId = cfg.active_account?.target?.org_id;
|
|
75923
76286
|
return {
|
|
75924
76287
|
cli_version: safeIdentifier(VERSION, 32),
|
|
75925
76288
|
install_channel: safeIdentifier(INSTALL_CHANNEL, 32),
|
|
75926
76289
|
platform: safeIdentifier(process.platform, 24),
|
|
75927
76290
|
arch: safeIdentifier(process.arch, 24),
|
|
75928
76291
|
mode: cfg.mode ?? "cloud",
|
|
75929
|
-
org_id:
|
|
76292
|
+
...orgId && isUUID3(orgId) ? { org_id: orgId, $groups: { organization: orgId } } : {},
|
|
75930
76293
|
deployment_id: safeIdentifier(cfg.active_account?.target?.deployment_id, 128),
|
|
75931
76294
|
space_id: safeIdentifier(cfg.active_account?.target?.space_id, 128)
|
|
75932
76295
|
};
|
|
@@ -75961,7 +76324,7 @@ function allowlistedWorkflowProperties(properties) {
|
|
|
75961
76324
|
function safeIdentifier(value, maxLength) {
|
|
75962
76325
|
return typeof value === "string" && value.length <= maxLength && /^[A-Za-z0-9][A-Za-z0-9._+-]*$/.test(value) ? value : undefined;
|
|
75963
76326
|
}
|
|
75964
|
-
function
|
|
76327
|
+
function isUUID3(value) {
|
|
75965
76328
|
return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value);
|
|
75966
76329
|
}
|
|
75967
76330
|
function createAnalyticsClient(accessToken, requestSignal) {
|
|
@@ -76684,23 +77047,31 @@ async function stepOfferInitialSync(cfg) {
|
|
|
76684
77047
|
s.start("Checking for recent agent sessions...");
|
|
76685
77048
|
const outcome = await runKnowledgeSync({ bootstrap: true });
|
|
76686
77049
|
if (outcome.status !== "backlog" || outcome.readySessions === 0) {
|
|
76687
|
-
s.stop("No
|
|
77050
|
+
s.stop("No past agent sessions to study. Dosu will learn from new ones as you work.");
|
|
76688
77051
|
return;
|
|
76689
77052
|
}
|
|
76690
77053
|
const n5 = outcome.readySessions;
|
|
76691
|
-
|
|
77054
|
+
const them = n5 === 1 ? "it" : "them";
|
|
77055
|
+
s.stop(`Found ${n5} past agent session${n5 === 1 ? "" : "s"} on this machine that Dosu hasn't studied.`);
|
|
77056
|
+
log2.message([
|
|
77057
|
+
`Dosu can read ${them} and pull out the durable knowledge: decisions, gotchas, and`,
|
|
77058
|
+
"how-things-work that your team and agents would otherwise rediscover.",
|
|
77059
|
+
"Only distilled notes are saved to Dosu; your session logs stay on this machine.",
|
|
77060
|
+
"It runs in the background a few sessions at a time; notes appear in Dosu as each batch finishes."
|
|
77061
|
+
].join(`
|
|
77062
|
+
`));
|
|
76692
77063
|
const mineNow = await confirm2({
|
|
76693
|
-
message: `
|
|
76694
|
-
active: "
|
|
76695
|
-
inactive: "Skip",
|
|
77064
|
+
message: `Study ${them} now?`,
|
|
77065
|
+
active: "Study now \uD83D\uDCDA",
|
|
77066
|
+
inactive: "Skip for now",
|
|
76696
77067
|
initialValue: true
|
|
76697
77068
|
});
|
|
76698
77069
|
if (isCancel2(mineNow) || !mineNow) {
|
|
76699
|
-
log2.info(`Skipped. Dosu
|
|
77070
|
+
log2.info(`Skipped. Dosu studies new sessions in the background as you work; run ${info("dosu knowledge sync")} anytime to study these too.`);
|
|
76700
77071
|
return;
|
|
76701
77072
|
}
|
|
76702
77073
|
if (spawnDetachedSelf(["knowledge", "sync", "--quiet", "--bootstrap"])) {
|
|
76703
|
-
log2.success("
|
|
77074
|
+
log2.success(`\uD83D\uDCDA Studying ${n5} session${n5 === 1 ? "" : "s"} in the background. Watch progress on the Activity screen; when it finishes, the new notes are in Dosu and agents connected to this MCP can use them.`);
|
|
76704
77075
|
const watch = await confirm2({
|
|
76705
77076
|
message: "What next?",
|
|
76706
77077
|
active: "Watch it work",
|
|
@@ -77525,12 +77896,15 @@ function loadPageStatsFromConfig() {
|
|
|
77525
77896
|
}
|
|
77526
77897
|
}
|
|
77527
77898
|
function overviewRows(state) {
|
|
77528
|
-
const
|
|
77899
|
+
const studiedTotal = state.total_mined ?? 0;
|
|
77529
77900
|
const notes = state.total_notes ?? 0;
|
|
77530
77901
|
const tokens = state.total_learning_tokens ?? 0;
|
|
77531
|
-
if (
|
|
77902
|
+
if (studiedTotal === 0 && notes === 0)
|
|
77532
77903
|
return [];
|
|
77533
|
-
const rows = [
|
|
77904
|
+
const rows = [
|
|
77905
|
+
`${label("Sessions studied")}${studiedTotal}`,
|
|
77906
|
+
`${label("Suggested pages")}${notes}`
|
|
77907
|
+
];
|
|
77534
77908
|
if (tokens > 0) {
|
|
77535
77909
|
rows.push(`${label("Investigation distilled")}${formatTokenCount(tokens)} tokens`);
|
|
77536
77910
|
}
|
|
@@ -77579,7 +77953,7 @@ function analyticsTabRows(tab, state, pageStats) {
|
|
|
77579
77953
|
function renderAnalyticsFrame(state, tab, scroll, pageStats = null, pagesPending = false, width = contentWidth()) {
|
|
77580
77954
|
const rows = analyticsTabRows(tab, state, pageStats);
|
|
77581
77955
|
const { visible, above, below } = windowReport(rows, scroll);
|
|
77582
|
-
const empty = tab === "overview" ? "No analytics yet. They appear after the first
|
|
77956
|
+
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.";
|
|
77583
77957
|
const listRows = visible.length > 0 ? visible.map((row) => import_picocolors32.default.dim(row)) : [import_picocolors32.default.dim(empty)];
|
|
77584
77958
|
const scrollParts = [];
|
|
77585
77959
|
if (above > 0)
|
|
@@ -77587,12 +77961,12 @@ function renderAnalyticsFrame(state, tab, scroll, pageStats = null, pagesPending
|
|
|
77587
77961
|
if (below > 0)
|
|
77588
77962
|
scrollParts.push(`↓ ${below} more`);
|
|
77589
77963
|
const lines = [
|
|
77590
|
-
breadcrumb(["
|
|
77964
|
+
breadcrumb(["home", "analytics"], width),
|
|
77591
77965
|
"",
|
|
77592
77966
|
...tabStrip([
|
|
77593
|
-
["overview", "
|
|
77594
|
-
["projects", "
|
|
77595
|
-
["pages", "
|
|
77967
|
+
["overview", "overview"],
|
|
77968
|
+
["projects", "projects"],
|
|
77969
|
+
["pages", "pages"]
|
|
77596
77970
|
], tab, width, { spread: false }),
|
|
77597
77971
|
"",
|
|
77598
77972
|
...listRows,
|
|
@@ -77731,7 +78105,7 @@ function checklistRows(ctx) {
|
|
|
77731
78105
|
const rows = [["workspace", ctx.directory]];
|
|
77732
78106
|
rows.push([
|
|
77733
78107
|
"account",
|
|
77734
|
-
ctx.signedIn ? `${on2} signed in` : `${off} ${import_picocolors33.default.dim(
|
|
78108
|
+
ctx.sessionExpired ? `${off} ${import_picocolors33.default.yellow("session expired")} ${import_picocolors33.default.dim(`${DOT3} run Log in`)}` : ctx.signedIn ? `${on2} signed in` : `${off} ${import_picocolors33.default.dim(`not signed in ${DOT3} run Setup`)}`
|
|
77735
78109
|
]);
|
|
77736
78110
|
const missing = new Set(ctx.setupMissing ?? []);
|
|
77737
78111
|
const warnRow = `${off} ${import_picocolors33.default.yellow("not configured")} ${import_picocolors33.default.dim(`${DOT3} run Setup`)}`;
|
|
@@ -77755,10 +78129,10 @@ function checklistRows(ctx) {
|
|
|
77755
78129
|
rows.push(["agents", `${on2} ${ctx.agents.join(` ${DOT3} `)}`]);
|
|
77756
78130
|
if (missing.has("hooks"))
|
|
77757
78131
|
rows.push(["hooks", warnRow]);
|
|
77758
|
-
if (ctx.
|
|
78132
|
+
if (ctx.studying) {
|
|
77759
78133
|
rows.push([
|
|
77760
78134
|
"sync",
|
|
77761
|
-
|
|
78135
|
+
`\uD83D\uDCDA ${brand("studying sessions...")} ${import_picocolors33.default.dim(`${DOT3} see activity`)}`
|
|
77762
78136
|
]);
|
|
77763
78137
|
}
|
|
77764
78138
|
if (ctx.update) {
|
|
@@ -77935,7 +78309,7 @@ function renderPagesFrame(model, width, selected) {
|
|
|
77935
78309
|
}
|
|
77936
78310
|
const count = model.phase === "ready" ? ` ${import_picocolors34.default.dim(model.pages.length < model.total ? `(${model.pages.length} of ${model.total})` : `(${model.pages.length})`)}` : "";
|
|
77937
78311
|
const lines = [
|
|
77938
|
-
`${breadcrumb(["
|
|
78312
|
+
`${breadcrumb(["home", "pages"], width)}${count}`,
|
|
77939
78313
|
"",
|
|
77940
78314
|
...body,
|
|
77941
78315
|
...scrollHint ? [import_picocolors34.default.dim(scrollHint)] : [],
|
|
@@ -77965,7 +78339,7 @@ function renderPageFrame(page, content, width, scroll) {
|
|
|
77965
78339
|
}
|
|
77966
78340
|
const meta3 = `${page.type} · ${page.published ? "published" : "draft"} · updated ${page.updated_at.slice(0, 10)}`;
|
|
77967
78341
|
const lines = [
|
|
77968
|
-
breadcrumb(["
|
|
78342
|
+
breadcrumb(["home", "pages", page.title], width),
|
|
77969
78343
|
import_picocolors34.default.dim(clip3(meta3, width)),
|
|
77970
78344
|
"",
|
|
77971
78345
|
...body,
|
|
@@ -78148,9 +78522,19 @@ __export(exports_tui, {
|
|
|
78148
78522
|
runTUI: () => runTUI,
|
|
78149
78523
|
handleLogout: () => handleLogout
|
|
78150
78524
|
});
|
|
78151
|
-
import { homedir as
|
|
78152
|
-
import { basename as
|
|
78153
|
-
function
|
|
78525
|
+
import { homedir as homedir12 } from "node:os";
|
|
78526
|
+
import { basename as basename6 } from "node:path";
|
|
78527
|
+
async function probeSession(cfg) {
|
|
78528
|
+
if (!isAuthenticated(cfg) || !isTokenExpired(cfg))
|
|
78529
|
+
return { expired: false };
|
|
78530
|
+
try {
|
|
78531
|
+
await new Client(cfg).refreshToken();
|
|
78532
|
+
return { expired: false };
|
|
78533
|
+
} catch (err) {
|
|
78534
|
+
return { expired: err instanceof SessionExpiredError };
|
|
78535
|
+
}
|
|
78536
|
+
}
|
|
78537
|
+
function bannerContext(cfg, session) {
|
|
78154
78538
|
let webAppHost = "app.dosu.dev";
|
|
78155
78539
|
try {
|
|
78156
78540
|
webAppHost = new URL(getWebAppURL()).host;
|
|
@@ -78166,14 +78550,15 @@ function bannerContext(cfg) {
|
|
|
78166
78550
|
return {
|
|
78167
78551
|
version: getVersionString(),
|
|
78168
78552
|
webAppHost,
|
|
78169
|
-
directory:
|
|
78553
|
+
directory: basename6(process.cwd()),
|
|
78170
78554
|
signedIn: isAuthenticated(cfg),
|
|
78555
|
+
sessionExpired: session.expired,
|
|
78171
78556
|
deploymentName: cfg.active_account?.target?.deployment_name,
|
|
78172
78557
|
libraryName: cfg.active_account?.target?.library_name,
|
|
78173
|
-
setupMissing: isAuthenticated(cfg) ? missingSetupSteps(cfg) : [],
|
|
78558
|
+
setupMissing: isAuthenticated(cfg) && !session.expired ? missingSetupSteps(cfg) : [],
|
|
78174
78559
|
...inGitWorkTree() ? { repoAgentsMd: dosuAgentsSectionState() } : {},
|
|
78175
78560
|
agents,
|
|
78176
|
-
|
|
78561
|
+
studying: isStudying(),
|
|
78177
78562
|
...latest ? { update: { version: latest, hint: buildUpdateHint(INSTALL_CHANNEL, isNpxInvocation()) } } : {}
|
|
78178
78563
|
};
|
|
78179
78564
|
}
|
|
@@ -78240,24 +78625,25 @@ function setupHint(cfg) {
|
|
|
78240
78625
|
const started = Boolean(cfg.active_account?.target);
|
|
78241
78626
|
return import_picocolors35.default.yellow(started ? `incomplete · missing ${missingSetupSteps(cfg).join(" + ")}` : "not set up yet");
|
|
78242
78627
|
}
|
|
78243
|
-
function
|
|
78628
|
+
function isStudying() {
|
|
78244
78629
|
try {
|
|
78245
78630
|
return getSyncStatus({ readLog: () => "" }).running;
|
|
78246
78631
|
} catch {
|
|
78247
78632
|
return false;
|
|
78248
78633
|
}
|
|
78249
78634
|
}
|
|
78250
|
-
function drawHome(cfg) {
|
|
78635
|
+
function drawHome(cfg, session) {
|
|
78251
78636
|
if (process.stdout.isTTY) {
|
|
78252
78637
|
process.stdout.write(CLEAR_SCREEN);
|
|
78253
78638
|
process.stdout.write(`
|
|
78254
78639
|
`.repeat(frameTopMargin()));
|
|
78255
78640
|
}
|
|
78256
|
-
process.stdout.write(`${renderBanner(bannerContext(cfg))}
|
|
78641
|
+
process.stdout.write(`${renderBanner(bannerContext(cfg, session))}
|
|
78257
78642
|
`);
|
|
78258
78643
|
}
|
|
78259
78644
|
async function runMainMenu() {
|
|
78260
78645
|
const cfg = loadConfig();
|
|
78646
|
+
const session = await probeSession(cfg);
|
|
78261
78647
|
const runSetupAndReload = async () => {
|
|
78262
78648
|
await runSetup();
|
|
78263
78649
|
const fresh = loadConfig();
|
|
@@ -78265,34 +78651,44 @@ async function runMainMenu() {
|
|
|
78265
78651
|
cfg.active_account = fresh.active_account;
|
|
78266
78652
|
};
|
|
78267
78653
|
const buildOptions = () => {
|
|
78654
|
+
if (session.expired) {
|
|
78655
|
+
return [
|
|
78656
|
+
{
|
|
78657
|
+
label: "log in again",
|
|
78658
|
+
hint: "(session expired · opens your browser)",
|
|
78659
|
+
value: "auth"
|
|
78660
|
+
},
|
|
78661
|
+
{ label: "exit", value: "exit" }
|
|
78662
|
+
];
|
|
78663
|
+
}
|
|
78268
78664
|
if (!isAuthenticated(cfg)) {
|
|
78269
78665
|
return [
|
|
78270
|
-
{ label: "
|
|
78271
|
-
{ label: "
|
|
78666
|
+
{ label: "log in / sign up", hint: "(opens your browser)", value: "auth" },
|
|
78667
|
+
{ label: "exit", value: "exit" }
|
|
78272
78668
|
];
|
|
78273
78669
|
}
|
|
78274
78670
|
if (!isSetUp(cfg)) {
|
|
78275
78671
|
return [
|
|
78276
|
-
{ label: "
|
|
78277
|
-
{ label: "
|
|
78672
|
+
{ label: "setup", hint: setupHint(cfg), value: "setup" },
|
|
78673
|
+
{ label: "exit", value: "exit" }
|
|
78278
78674
|
];
|
|
78279
78675
|
}
|
|
78280
|
-
const
|
|
78676
|
+
const studying = isStudying();
|
|
78281
78677
|
return [
|
|
78282
78678
|
{
|
|
78283
|
-
label:
|
|
78679
|
+
label: studying ? `activity \uD83D\uDCDA ${brand("studying sessions...")}` : "activity",
|
|
78284
78680
|
value: "sync"
|
|
78285
78681
|
},
|
|
78286
|
-
{ label: "
|
|
78287
|
-
{ label: "
|
|
78288
|
-
{ label: "
|
|
78289
|
-
{ label: "
|
|
78290
|
-
{ label: "
|
|
78682
|
+
{ label: "knowledge report", hint: "(opens in browser)", value: "report" },
|
|
78683
|
+
{ label: "analytics", value: "analytics" },
|
|
78684
|
+
{ label: "pages", value: "pages" },
|
|
78685
|
+
{ label: "settings", value: "settings" },
|
|
78686
|
+
{ label: "exit", value: "exit" }
|
|
78291
78687
|
];
|
|
78292
78688
|
};
|
|
78293
|
-
const home = () => drawHome(cfg);
|
|
78689
|
+
const home = () => drawHome(cfg, session);
|
|
78294
78690
|
home();
|
|
78295
|
-
if (isAuthenticated(cfg) && !isSetUp(cfg)) {
|
|
78691
|
+
if (isAuthenticated(cfg) && !session.expired && !isSetUp(cfg)) {
|
|
78296
78692
|
await runSetupAndReload();
|
|
78297
78693
|
home();
|
|
78298
78694
|
}
|
|
@@ -78326,6 +78722,7 @@ async function runMainMenu() {
|
|
|
78326
78722
|
break;
|
|
78327
78723
|
case "auth":
|
|
78328
78724
|
await handleAuthenticate(cfg);
|
|
78725
|
+
session.expired = session.expired && isTokenExpired(cfg);
|
|
78329
78726
|
home();
|
|
78330
78727
|
if (isAuthenticated(cfg) && !isSetUp(cfg)) {
|
|
78331
78728
|
await runSetupAndReload();
|
|
@@ -78355,19 +78752,19 @@ async function runSettings(cfg) {
|
|
|
78355
78752
|
const target = cfg.active_account?.target;
|
|
78356
78753
|
const library = target?.library_name ?? target?.deployment_name ?? "not configured";
|
|
78357
78754
|
const filter = loadSyncState().project_filter;
|
|
78358
|
-
const scope = !filter?.length ? "all projects" : filter.length <= 2 ? filter.map((dir) => dir === UNKNOWN_PROJECT ? "unknown" :
|
|
78359
|
-
const action = await menuSelect("
|
|
78360
|
-
{ label: "
|
|
78361
|
-
{ label: "
|
|
78362
|
-
{ label: "
|
|
78363
|
-
{ label: "
|
|
78364
|
-
{ label: "
|
|
78365
|
-
{ label: "
|
|
78755
|
+
const scope = !filter?.length ? "all projects" : filter.length <= 2 ? filter.map((dir) => dir === UNKNOWN_PROJECT ? "unknown" : basename6(dir)).join(", ") : `${filter.length} projects`;
|
|
78756
|
+
const action = await menuSelect("settings", [
|
|
78757
|
+
{ label: "switch organization", hint: target?.org_name, value: "switch-org" },
|
|
78758
|
+
{ label: "switch library", hint: library, value: "switch-library" },
|
|
78759
|
+
{ label: "study scope", hint: scope, value: "projects" },
|
|
78760
|
+
{ label: "run setup", hint: "rerun the setup wizard", value: "setup" },
|
|
78761
|
+
{ label: "log out", hint: "clear stored credentials", value: "logout" },
|
|
78762
|
+
{ label: "back", value: "back" }
|
|
78366
78763
|
]);
|
|
78367
78764
|
if (action === null || action === "back")
|
|
78368
78765
|
return;
|
|
78369
78766
|
if (action === "projects") {
|
|
78370
|
-
await
|
|
78767
|
+
await runStudyingProjectsSetting();
|
|
78371
78768
|
continue;
|
|
78372
78769
|
}
|
|
78373
78770
|
if (action === "setup") {
|
|
@@ -78390,7 +78787,7 @@ async function runSettings(cfg) {
|
|
|
78390
78787
|
}
|
|
78391
78788
|
}
|
|
78392
78789
|
function displayDir(dir) {
|
|
78393
|
-
const home =
|
|
78790
|
+
const home = homedir12();
|
|
78394
78791
|
return dir === home ? "~" : dir.startsWith(`${home}/`) ? `~${dir.slice(home.length)}` : dir;
|
|
78395
78792
|
}
|
|
78396
78793
|
function discoverProjectDirs() {
|
|
@@ -78405,7 +78802,7 @@ function discoverProjectDirs() {
|
|
|
78405
78802
|
} catch {}
|
|
78406
78803
|
return [...counts.entries()].sort((a9, b2) => b2[1] - a9[1]).map(([dir]) => dir);
|
|
78407
78804
|
}
|
|
78408
|
-
async function
|
|
78805
|
+
async function runStudyingProjectsSetting() {
|
|
78409
78806
|
const dirs = discoverProjectDirs();
|
|
78410
78807
|
if (dirs.length === 0) {
|
|
78411
78808
|
log2.info("No local agent sessions found yet; nothing to scope.");
|
|
@@ -78413,7 +78810,7 @@ async function runMiningProjectsSetting() {
|
|
|
78413
78810
|
}
|
|
78414
78811
|
const current = loadSyncState().project_filter;
|
|
78415
78812
|
const selected = await multiselect2({
|
|
78416
|
-
message: "
|
|
78813
|
+
message: "Study sessions from which folders?",
|
|
78417
78814
|
options: dirs.map((dir) => ({
|
|
78418
78815
|
label: dir === UNKNOWN_PROJECT ? "(unknown folder)" : displayDir(dir),
|
|
78419
78816
|
value: dir
|
|
@@ -78428,7 +78825,7 @@ async function runMiningProjectsSetting() {
|
|
|
78428
78825
|
const all = selected.length === dirs.length;
|
|
78429
78826
|
saveSyncState(all ? state : { ...state, project_filter: [...selected] });
|
|
78430
78827
|
const scope = all ? "all folders" : selected.map(displayDir).join(", ");
|
|
78431
|
-
log2.success(`
|
|
78828
|
+
log2.success(`Study scope ${dim(`· ${scope}`)}`);
|
|
78432
78829
|
}
|
|
78433
78830
|
async function handleAuthenticate(cfg) {
|
|
78434
78831
|
const shouldLogin = await confirm2({ message: "Open browser to log in?" });
|
|
@@ -78474,6 +78871,7 @@ function handleLogout(cfg) {
|
|
|
78474
78871
|
}
|
|
78475
78872
|
var import_picocolors35, ESC10, CLEAR_SCREEN;
|
|
78476
78873
|
var init_tui = __esm(() => {
|
|
78874
|
+
init_client();
|
|
78477
78875
|
init_config();
|
|
78478
78876
|
init_agents();
|
|
78479
78877
|
init_providers();
|
|
@@ -79131,7 +79529,7 @@ import {
|
|
|
79131
79529
|
constants as constants3,
|
|
79132
79530
|
fstatSync,
|
|
79133
79531
|
openSync as openSync5,
|
|
79134
|
-
readFileSync as
|
|
79532
|
+
readFileSync as readFileSync26,
|
|
79135
79533
|
readSync as readSync4,
|
|
79136
79534
|
unlinkSync as unlinkSync5
|
|
79137
79535
|
} from "node:fs";
|
|
@@ -82333,23 +82731,23 @@ ${results.length - opts.limit} more results not shown.`));
|
|
|
82333
82731
|
console.log(` ID: ${store.id}`);
|
|
82334
82732
|
console.log(` Space ID: ${store.space_id}`);
|
|
82335
82733
|
});
|
|
82336
|
-
cmd.command("sessions").description("List local agent sessions with full project and session ids (the untruncated view of the Activity screen's tabs)").option("--queued", "Only sessions queued for
|
|
82337
|
-
const all = !opts.queued && !opts.open && !opts.
|
|
82734
|
+
cmd.command("sessions").description("List local agent sessions with full project and session ids (the untruncated view of the Activity screen's tabs)").option("--queued", "Only sessions queued for studying").option("--open", "Only live sessions still inside the quiet period").option("--studied", "Only recent studied-session history").option("--json", "Output as JSON").action((opts) => {
|
|
82735
|
+
const all = !opts.queued && !opts.open && !opts.studied;
|
|
82338
82736
|
const wantQueued = all || Boolean(opts.queued);
|
|
82339
82737
|
const wantOpen = all || Boolean(opts.open);
|
|
82340
|
-
const
|
|
82738
|
+
const wantStudied = all || Boolean(opts.studied);
|
|
82341
82739
|
const backlog = wantQueued || wantOpen ? listSessionBacklog() : { queued: [], open: [] };
|
|
82342
|
-
const
|
|
82740
|
+
const studied = wantStudied ? loadSyncState().mined_sessions ?? [] : [];
|
|
82343
82741
|
if (opts.json) {
|
|
82344
82742
|
printResult({
|
|
82345
82743
|
...wantQueued ? { queued: backlog.queued } : {},
|
|
82346
82744
|
...wantOpen ? { open: backlog.open } : {},
|
|
82347
|
-
...
|
|
82745
|
+
...wantStudied ? { studied } : {}
|
|
82348
82746
|
}, opts);
|
|
82349
82747
|
return;
|
|
82350
82748
|
}
|
|
82351
82749
|
const sessionRows = (sessions) => sessions.map((s) => [s.harness, s.updated, s.project ?? "-", s.id]);
|
|
82352
|
-
const
|
|
82750
|
+
const studiedRows = studied.map((record2) => {
|
|
82353
82751
|
const slash = record2.session.indexOf("/");
|
|
82354
82752
|
const harness = slash > 0 ? record2.session.slice(0, slash) : "-";
|
|
82355
82753
|
const id2 = slash > 0 ? record2.session.slice(slash + 1) : record2.session;
|
|
@@ -82373,11 +82771,11 @@ ${results.length - opts.limit} more results not shown.`));
|
|
|
82373
82771
|
if (wantOpen) {
|
|
82374
82772
|
section("Open", sessionRows(backlog.open), "Updated", "No open sessions. Live agent sessions sit here until they go quiet.");
|
|
82375
82773
|
}
|
|
82376
|
-
if (
|
|
82377
|
-
section("
|
|
82774
|
+
if (wantStudied) {
|
|
82775
|
+
section("Studied", studiedRows, "Studied at", "No studied sessions recorded yet.");
|
|
82378
82776
|
}
|
|
82379
82777
|
});
|
|
82380
|
-
cmd.command("sync").description("Scan local agent session history and report the
|
|
82778
|
+
cmd.command("sync").description("Scan local agent session history and report the study backlog").option("--quiet", "Background mode for hooks: honor backoff, exit 0, print nothing").option("--detach", "Re-spawn detached and return immediately (used by agent hooks)").option("--bootstrap", "Backfill mode: study the full local session history regardless of age and drain the backlog (used by setup)").option("--status", "Show whether a sync is running now, plus watermark and recent activity").option("--report", "Write the same HTML harvest report as the log-to-dosu-knowledge skill and open it").option("--out <path>", "HTML report path (default: tmp/dosu-knowledge-report.html)").option("--json", "Output as JSON").action(async (opts) => {
|
|
82381
82779
|
if (opts.status) {
|
|
82382
82780
|
const status = getSyncStatus();
|
|
82383
82781
|
if (opts.json) {
|
|
@@ -82398,7 +82796,7 @@ ${results.length - opts.limit} more results not shown.`));
|
|
|
82398
82796
|
]);
|
|
82399
82797
|
return;
|
|
82400
82798
|
}
|
|
82401
|
-
const deps = { mine:
|
|
82799
|
+
const deps = { mine: buildLearner(opts.quiet ? "hook" : "manual") };
|
|
82402
82800
|
let outcome = await runKnowledgeSync({
|
|
82403
82801
|
quiet: opts.quiet,
|
|
82404
82802
|
bootstrap: opts.bootstrap,
|
|
@@ -82406,7 +82804,7 @@ ${results.length - opts.limit} more results not shown.`));
|
|
|
82406
82804
|
});
|
|
82407
82805
|
if (opts.bootstrap && deps.mine) {
|
|
82408
82806
|
const maxRounds = Math.ceil(outcome.readySessions / MINE_BATCH_LIMIT) + 2;
|
|
82409
|
-
for (let round = 1;outcome.status === "
|
|
82807
|
+
for (let round = 1;outcome.status === "studied" && round < maxRounds; round++) {
|
|
82410
82808
|
if (!opts.quiet && !opts.json)
|
|
82411
82809
|
printSyncOutcome(outcome);
|
|
82412
82810
|
outcome = await runKnowledgeSync({ quiet: opts.quiet, bootstrap: true, deps });
|
|
@@ -82463,12 +82861,12 @@ ${results.length - opts.limit} more results not shown.`));
|
|
|
82463
82861
|
console.log("All your notes already have a transcript — nothing to backfill.");
|
|
82464
82862
|
return;
|
|
82465
82863
|
}
|
|
82466
|
-
console.log(`Attributed ${result.updated} of ${result.candidates} notes (${result.ambiguous} ambiguous, ${result.noBatch} without a local
|
|
82864
|
+
console.log(`Attributed ${result.updated} of ${result.candidates} notes (${result.ambiguous} ambiguous, ${result.noBatch} without a local study batch). Run 'dosu knowledge report' to see their traces.`);
|
|
82467
82865
|
});
|
|
82468
82866
|
cmd.addCommand(hooksCommand());
|
|
82469
82867
|
return cmd;
|
|
82470
82868
|
}
|
|
82471
|
-
function
|
|
82869
|
+
function buildLearner(trigger) {
|
|
82472
82870
|
const cfg = loadConfig();
|
|
82473
82871
|
if (cfg.mode === "oss")
|
|
82474
82872
|
return;
|
|
@@ -82477,8 +82875,8 @@ function buildMiner(trigger) {
|
|
|
82477
82875
|
return;
|
|
82478
82876
|
const { api_key, deployment_id } = target;
|
|
82479
82877
|
return async (sessions) => {
|
|
82480
|
-
const {
|
|
82481
|
-
return
|
|
82878
|
+
const { runLearner: runLearner2 } = await Promise.resolve().then(() => (init_runner(), exports_runner));
|
|
82879
|
+
return runLearner2({ sessions, apiKey: api_key, deploymentID: deployment_id, trigger });
|
|
82482
82880
|
};
|
|
82483
82881
|
}
|
|
82484
82882
|
function formatAge(iso, now) {
|
|
@@ -82504,14 +82902,14 @@ function printSyncStatus(status, now = new Date) {
|
|
|
82504
82902
|
console.log("○ No sync running.");
|
|
82505
82903
|
}
|
|
82506
82904
|
if (status.state.paused) {
|
|
82507
|
-
console.log(import_picocolors18.default.yellow("
|
|
82905
|
+
console.log(import_picocolors18.default.yellow(" Studying paused: stopped by you. Resume from the Activity screen or run 'dosu knowledge sync'."));
|
|
82508
82906
|
}
|
|
82509
82907
|
const wm2 = status.state.watermark;
|
|
82510
|
-
console.log(`
|
|
82908
|
+
console.log(` Studied through: ${wm2 ? `${wm2} (${formatAge(wm2, now)})` : "nothing studied yet"}`);
|
|
82511
82909
|
if (status.state.project_filter?.length) {
|
|
82512
82910
|
const home = homedir5();
|
|
82513
82911
|
const scope = status.state.project_filter.map((dir) => dir.startsWith(`${home}/`) ? `~${dir.slice(home.length)}` : dir).join(", ");
|
|
82514
|
-
console.log(`
|
|
82912
|
+
console.log(` Study scope: ${scope}`);
|
|
82515
82913
|
}
|
|
82516
82914
|
if ((status.state.total_notes ?? 0) > 0) {
|
|
82517
82915
|
const tokens = status.state.total_learning_tokens ?? 0;
|
|
@@ -82526,7 +82924,7 @@ function printSyncStatus(status, now = new Date) {
|
|
|
82526
82924
|
console.log(import_picocolors18.default.yellow(` Backing off after ${n5} failure${n5 === 1 ? "" : "s"}; background syncs retry after ${status.backoffUntil}.`));
|
|
82527
82925
|
}
|
|
82528
82926
|
if (status.state.last_refusal) {
|
|
82529
|
-
console.log(import_picocolors18.default.yellow(`
|
|
82927
|
+
console.log(import_picocolors18.default.yellow(` Studying paused: ${status.state.last_refusal.message} (${formatAge(status.state.last_refusal.at, now)})`));
|
|
82530
82928
|
}
|
|
82531
82929
|
if (status.recentActivity.length > 0) {
|
|
82532
82930
|
console.log(`
|
|
@@ -82542,25 +82940,25 @@ function printSyncOutcome(outcome) {
|
|
|
82542
82940
|
switch (outcome.status) {
|
|
82543
82941
|
case "backlog": {
|
|
82544
82942
|
const inFlight = outcome.inFlightSessions > 0 ? import_picocolors18.default.dim(` (${outcome.inFlightSessions} more still in progress)`) : "";
|
|
82545
|
-
console.log(`✓ Scanned. ${outcome.readySessions} new session${outcome.readySessions === 1 ? "" : "s"} ready to
|
|
82546
|
-
console.log(import_picocolors18.default.dim("Sign in with 'dosu setup' to enable
|
|
82943
|
+
console.log(`✓ Scanned. ${outcome.readySessions} new session${outcome.readySessions === 1 ? "" : "s"} ready to study${inFlight}.`);
|
|
82944
|
+
console.log(import_picocolors18.default.dim("Sign in with 'dosu setup' to enable studying."));
|
|
82547
82945
|
break;
|
|
82548
82946
|
}
|
|
82549
|
-
case "
|
|
82550
|
-
const notes = outcome.
|
|
82551
|
-
const remaining = outcome.readySessions - (outcome.
|
|
82552
|
-
console.log(`✓
|
|
82947
|
+
case "studied": {
|
|
82948
|
+
const notes = outcome.learner?.notesWritten ?? 0;
|
|
82949
|
+
const remaining = outcome.readySessions - (outcome.studiedSessions ?? 0);
|
|
82950
|
+
console.log(`✓ Studied ${outcome.studiedSessions} session${outcome.studiedSessions === 1 ? "" : "s"}, ${notes} suggested page${notes === 1 ? "" : "s"} created.`);
|
|
82553
82951
|
if (remaining > 0) {
|
|
82554
82952
|
console.log(import_picocolors18.default.dim(`${remaining} more in the backlog; run sync again to continue.`));
|
|
82555
82953
|
}
|
|
82556
82954
|
break;
|
|
82557
82955
|
}
|
|
82558
82956
|
case "skipped-gateway": {
|
|
82559
|
-
console.log(import_picocolors18.default.yellow(outcome.
|
|
82957
|
+
console.log(import_picocolors18.default.yellow(outcome.learner?.message ?? "Studying unavailable right now."));
|
|
82560
82958
|
break;
|
|
82561
82959
|
}
|
|
82562
82960
|
case "mine-failed": {
|
|
82563
|
-
console.error(import_picocolors18.default.red(outcome.
|
|
82961
|
+
console.error(import_picocolors18.default.red(outcome.learner?.message ?? "Study run failed."));
|
|
82564
82962
|
process.exitCode = 1;
|
|
82565
82963
|
break;
|
|
82566
82964
|
}
|
|
@@ -82583,7 +82981,7 @@ function printSyncOutcome(outcome) {
|
|
|
82583
82981
|
break;
|
|
82584
82982
|
}
|
|
82585
82983
|
case "skipped-paused": {
|
|
82586
|
-
console.log(import_picocolors18.default.dim("Skipped:
|
|
82984
|
+
console.log(import_picocolors18.default.dim("Skipped: studying is paused. Run 'dosu knowledge sync' to resume."));
|
|
82587
82985
|
break;
|
|
82588
82986
|
}
|
|
82589
82987
|
}
|
|
@@ -83335,7 +83733,10 @@ init_skill();
|
|
|
83335
83733
|
// src/commands/sources.ts
|
|
83336
83734
|
init_commander();
|
|
83337
83735
|
init_trpc();
|
|
83736
|
+
init_github_step();
|
|
83737
|
+
init_installation_server();
|
|
83338
83738
|
var import_picocolors25 = __toESM(require_picocolors(), 1);
|
|
83739
|
+
var CONNECT_TIMEOUT_DEFAULT_SECONDS = 600;
|
|
83339
83740
|
function requireConfig11() {
|
|
83340
83741
|
const cfg = requireLoginConfig();
|
|
83341
83742
|
if (!cfg.active_account?.target?.org_id) {
|
|
@@ -83344,6 +83745,15 @@ function requireConfig11() {
|
|
|
83344
83745
|
}
|
|
83345
83746
|
return cfg;
|
|
83346
83747
|
}
|
|
83748
|
+
function rejectWebOnlyProvider(provider, json2) {
|
|
83749
|
+
const url2 = getWebAppURL();
|
|
83750
|
+
if (json2) {
|
|
83751
|
+
printResult({ provider, cli_supported: false, connect_via: "web", url: url2 }, { json: true });
|
|
83752
|
+
} else {
|
|
83753
|
+
console.error(import_picocolors25.default.yellow(`Connecting '${provider}' sources is web-only for now. Open ${url2}, add the source ` + `under Data Sources, then re-run 'dosu sources list'.`));
|
|
83754
|
+
}
|
|
83755
|
+
process.exit(1);
|
|
83756
|
+
}
|
|
83347
83757
|
function sourcesCommand() {
|
|
83348
83758
|
const cmd = new Command("sources").description("Manage connected data sources");
|
|
83349
83759
|
cmd.command("list").description("List all connected data sources").option("--json", "Output as JSON").action(async (opts) => {
|
|
@@ -83426,6 +83836,154 @@ function sourcesCommand() {
|
|
|
83426
83836
|
}
|
|
83427
83837
|
console.log(import_picocolors25.default.green("Data source deleted."));
|
|
83428
83838
|
});
|
|
83839
|
+
cmd.command("connect").description("Install or update a provider integration (currently GitHub) via the browser").argument("[provider]", "Provider to connect (github)", "github").option("--json", "Emit machine-readable NDJSON progress events").option("--timeout <seconds>", "Seconds to wait for the browser install to complete", String(CONNECT_TIMEOUT_DEFAULT_SECONDS)).option("--no-open", "Print the URL without opening a browser").action(async (provider, opts) => {
|
|
83840
|
+
const providerName = provider.toLowerCase();
|
|
83841
|
+
if (providerName !== "github") {
|
|
83842
|
+
rejectWebOnlyProvider(providerName, opts.json);
|
|
83843
|
+
return;
|
|
83844
|
+
}
|
|
83845
|
+
const cfg = requireConfig11();
|
|
83846
|
+
const client = createTypedClient(cfg);
|
|
83847
|
+
const orgID = cfg.active_account.target.org_id;
|
|
83848
|
+
const timeoutSeconds = Math.max(1, Number.parseInt(opts.timeout ?? "", 10)) || CONNECT_TIMEOUT_DEFAULT_SECONDS;
|
|
83849
|
+
const before = await fetchListForOrg(client, orgID);
|
|
83850
|
+
const { server, installationPromise } = await startInstallationCallbackServer();
|
|
83851
|
+
let timeoutId;
|
|
83852
|
+
try {
|
|
83853
|
+
const connectURL = new URL("/cli/connect-github", getWebAppURL());
|
|
83854
|
+
connectURL.searchParams.set("callback", `http://localhost:${server.port}/callback`);
|
|
83855
|
+
const url2 = connectURL.toString();
|
|
83856
|
+
if (opts.json) {
|
|
83857
|
+
console.log(JSON.stringify({
|
|
83858
|
+
event: "awaiting_install",
|
|
83859
|
+
provider: "github",
|
|
83860
|
+
url: url2,
|
|
83861
|
+
timeout_seconds: timeoutSeconds
|
|
83862
|
+
}));
|
|
83863
|
+
} else {
|
|
83864
|
+
console.log(`Open this URL to install or update the Dosu GitHub App:
|
|
83865
|
+
${import_picocolors25.default.cyan(url2)}`);
|
|
83866
|
+
if (opts.open !== false) {
|
|
83867
|
+
try {
|
|
83868
|
+
const open5 = await Promise.resolve().then(() => (init_open(), exports_open));
|
|
83869
|
+
await open5.default(url2);
|
|
83870
|
+
} catch {}
|
|
83871
|
+
}
|
|
83872
|
+
console.log(import_picocolors25.default.dim("Waiting for the install to complete..."));
|
|
83873
|
+
}
|
|
83874
|
+
const timedOut = new Promise((resolve6) => {
|
|
83875
|
+
timeoutId = setTimeout(() => resolve6(null), timeoutSeconds * 1000);
|
|
83876
|
+
});
|
|
83877
|
+
const result = await Promise.race([installationPromise, timedOut]);
|
|
83878
|
+
if (result === null) {
|
|
83879
|
+
if (opts.json) {
|
|
83880
|
+
console.log(JSON.stringify({ event: "timeout", timeout_seconds: timeoutSeconds }));
|
|
83881
|
+
} else {
|
|
83882
|
+
console.error(import_picocolors25.default.red(`Timed out after ${timeoutSeconds}s. Finish the install in the browser, then re-run 'dosu sources connect github'.`));
|
|
83883
|
+
}
|
|
83884
|
+
process.exit(1);
|
|
83885
|
+
return;
|
|
83886
|
+
}
|
|
83887
|
+
const refresh = await waitForRepositoryRefresh(client, orgID, before);
|
|
83888
|
+
const beforeIds = new Set(before.map((repo) => repo.repository_id));
|
|
83889
|
+
const newRepositories = refresh.repos.filter((repo) => !beforeIds.has(repo.repository_id)).map((repo) => ({ slug: repo.slug, repository_id: repo.repository_id }));
|
|
83890
|
+
if (opts.json) {
|
|
83891
|
+
console.log(JSON.stringify({
|
|
83892
|
+
event: "installed",
|
|
83893
|
+
installation_id: result.installation_id,
|
|
83894
|
+
new_repositories: newRepositories
|
|
83895
|
+
}));
|
|
83896
|
+
return;
|
|
83897
|
+
}
|
|
83898
|
+
console.log(import_picocolors25.default.green(`GitHub App connected (installation ${result.installation_id}).`));
|
|
83899
|
+
if (newRepositories.length > 0) {
|
|
83900
|
+
for (const repo of newRepositories) {
|
|
83901
|
+
console.log(` ${repo.slug}`);
|
|
83902
|
+
}
|
|
83903
|
+
console.log(import_picocolors25.default.dim("Next: dosu sources create github --repo <owner/name> --library <library-id>"));
|
|
83904
|
+
} else {
|
|
83905
|
+
console.log(import_picocolors25.default.dim("No new repositories visible yet — GitHub may still be syncing. " + "Re-run 'dosu sources connect github' or check 'dosu sources list' shortly."));
|
|
83906
|
+
}
|
|
83907
|
+
} finally {
|
|
83908
|
+
clearTimeout(timeoutId);
|
|
83909
|
+
server.close();
|
|
83910
|
+
}
|
|
83911
|
+
});
|
|
83912
|
+
cmd.command("create").description("Create a data source from an installed provider and attach it to a library (requires confirmation)").argument("<provider>", "Provider (currently: github)").requiredOption("--repo <owner/name>", "GitHub repository slug visible to the Dosu app").option("--library <id>", "Library (space) ID to attach to; defaults to the active library").option("--confirm", "Apply without the interactive prompt").option("--json", "Output as JSON").action(async (provider, opts) => {
|
|
83913
|
+
const providerName = provider.toLowerCase();
|
|
83914
|
+
if (providerName !== "github") {
|
|
83915
|
+
rejectWebOnlyProvider(providerName, opts.json);
|
|
83916
|
+
return;
|
|
83917
|
+
}
|
|
83918
|
+
const cfg = requireConfig11();
|
|
83919
|
+
const orgID = cfg.active_account.target.org_id;
|
|
83920
|
+
const libraryID = opts.library ?? cfg.active_account?.target?.space_id;
|
|
83921
|
+
if (!libraryID) {
|
|
83922
|
+
console.error(import_picocolors25.default.red("No library specified and no active library. Pass --library <id>."));
|
|
83923
|
+
process.exit(1);
|
|
83924
|
+
return;
|
|
83925
|
+
}
|
|
83926
|
+
const client = createTypedClient(cfg);
|
|
83927
|
+
const repos = await fetchListForOrg(client, orgID);
|
|
83928
|
+
const repo = repos.find((r9) => r9.slug.toLowerCase() === opts.repo.toLowerCase());
|
|
83929
|
+
if (!repo) {
|
|
83930
|
+
console.error(import_picocolors25.default.red(`Repository '${opts.repo}' is not visible to Dosu. Run ` + `'dosu sources connect github' to grant the GitHub App access, then retry.`));
|
|
83931
|
+
process.exit(1);
|
|
83932
|
+
return;
|
|
83933
|
+
}
|
|
83934
|
+
if (repo.is_fork === true) {
|
|
83935
|
+
console.error(import_picocolors25.default.red(`'${repo.slug}' is a fork and can't be synced.` + (repo.fork_parent_slug ? ` Connect '${repo.fork_parent_slug}' instead.` : "")));
|
|
83936
|
+
process.exit(1);
|
|
83937
|
+
return;
|
|
83938
|
+
}
|
|
83939
|
+
if (!await confirmAction({
|
|
83940
|
+
confirmed: opts.confirm,
|
|
83941
|
+
json: opts.json,
|
|
83942
|
+
message: `Create a data source for ${repo.slug} and attach it to library ${libraryID}?`,
|
|
83943
|
+
preview: {
|
|
83944
|
+
action: "create_source",
|
|
83945
|
+
provider: "github",
|
|
83946
|
+
repository: repo.slug,
|
|
83947
|
+
repository_id: repo.repository_id,
|
|
83948
|
+
library_id: libraryID
|
|
83949
|
+
}
|
|
83950
|
+
}))
|
|
83951
|
+
return;
|
|
83952
|
+
const existingDeployments = await fetchOrgGithubDeployments(client, orgID);
|
|
83953
|
+
const existingDataSources = await fetchOrgGithubDataSources(client, orgID);
|
|
83954
|
+
const created = await createDeploymentForRepo(client, orgID, libraryID, repo, {
|
|
83955
|
+
deploymentID: existingDeployments.get(repo.repository_id),
|
|
83956
|
+
dataSourceID: existingDataSources.get(repo.repository_id)
|
|
83957
|
+
});
|
|
83958
|
+
if (!created) {
|
|
83959
|
+
console.error(import_picocolors25.default.red("Could not create the data source. Check 'dosu logs --tail 50' for details."));
|
|
83960
|
+
process.exit(1);
|
|
83961
|
+
return;
|
|
83962
|
+
}
|
|
83963
|
+
const survivors = await verifyDataSourcesPersist(client, orgID, [created.data_source_id]);
|
|
83964
|
+
if (survivors.dropped.size > 0) {
|
|
83965
|
+
await deleteOrphanDeployment(client, created.deployment_id, repo.slug);
|
|
83966
|
+
console.error(import_picocolors25.default.red(`Dosu couldn't sync '${repo.slug}' (the GitHub App may not have access to it). ` + "Nothing was created."));
|
|
83967
|
+
process.exit(1);
|
|
83968
|
+
return;
|
|
83969
|
+
}
|
|
83970
|
+
const receipt = {
|
|
83971
|
+
provider: "github",
|
|
83972
|
+
repository: repo.slug,
|
|
83973
|
+
repository_id: repo.repository_id,
|
|
83974
|
+
data_source_id: created.data_source_id,
|
|
83975
|
+
deployment_id: created.deployment_id,
|
|
83976
|
+
library_id: libraryID,
|
|
83977
|
+
attached: true
|
|
83978
|
+
};
|
|
83979
|
+
if (opts.json) {
|
|
83980
|
+
printResult(receipt, opts);
|
|
83981
|
+
return;
|
|
83982
|
+
}
|
|
83983
|
+
console.log(import_picocolors25.default.green(`Connected ${repo.slug} to library ${libraryID}.`));
|
|
83984
|
+
console.log(import_picocolors25.default.dim(` data source ${created.data_source_id}`));
|
|
83985
|
+
console.log(import_picocolors25.default.dim(` deployment ${created.deployment_id}`));
|
|
83986
|
+
});
|
|
83429
83987
|
return cmd;
|
|
83430
83988
|
}
|
|
83431
83989
|
|
|
@@ -83939,10 +84497,12 @@ function commandTelemetryContext() {
|
|
|
83939
84497
|
const tokenUserID = getAccessTokenUserID(accessToken);
|
|
83940
84498
|
const userID = configUserID && configUserID === tokenUserID ? configUserID : undefined;
|
|
83941
84499
|
const email3 = userID ? getAccessTokenEmail(accessToken) : undefined;
|
|
84500
|
+
const orgId = userID ? cfg.active_account?.target?.org_id : undefined;
|
|
83942
84501
|
return {
|
|
83943
84502
|
mode: cfg.mode === MODE_OSS ? "oss" : "cloud",
|
|
83944
84503
|
isAuthenticated: authenticated,
|
|
83945
|
-
...userID ? { user: { id: userID, ...email3 ? { email: email3 } : {} } } : {}
|
|
84504
|
+
...userID ? { user: { id: userID, ...email3 ? { email: email3 } : {} } } : {},
|
|
84505
|
+
...orgId ? { orgId } : {}
|
|
83946
84506
|
};
|
|
83947
84507
|
} catch {
|
|
83948
84508
|
return { mode: "cloud", isAuthenticated: false };
|
|
@@ -84341,7 +84901,7 @@ Use 'dosu mcp add <agent>' to add Dosu MCP to a tool.`);
|
|
|
84341
84901
|
}
|
|
84342
84902
|
if (opts.follow) {
|
|
84343
84903
|
try {
|
|
84344
|
-
const lines =
|
|
84904
|
+
const lines = readFileSync26(logPath, "utf-8").split(`
|
|
84345
84905
|
`);
|
|
84346
84906
|
if (lines.at(-1) === "")
|
|
84347
84907
|
lines.pop();
|
|
@@ -84357,7 +84917,7 @@ Use 'dosu mcp add <agent>' to add Dosu MCP to a tool.`);
|
|
|
84357
84917
|
if (opts.tail !== undefined) {
|
|
84358
84918
|
const n5 = typeof opts.tail === "string" ? parseInt(opts.tail, 10) || 50 : 50;
|
|
84359
84919
|
try {
|
|
84360
|
-
const content =
|
|
84920
|
+
const content = readFileSync26(logPath, "utf-8");
|
|
84361
84921
|
const lines = content.split(`
|
|
84362
84922
|
`);
|
|
84363
84923
|
console.log(lines.slice(-n5).join(`
|
|
@@ -84428,4 +84988,4 @@ execute().catch((err) => {
|
|
|
84428
84988
|
process.exit(1);
|
|
84429
84989
|
});
|
|
84430
84990
|
|
|
84431
|
-
//# debugId=
|
|
84991
|
+
//# debugId=5776df20-501e-ff5f-6475-6e2164756e21
|