@dosu/cli 0.67.0-beta.1 โ 0.67.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -63
- package/bin/dosu.js +141 -61
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -99,18 +99,15 @@ Or right-click the binary, select "Open", and click "Open" in the dialog.
|
|
|
99
99
|
| `dosu upgrade` | Update Dosu through the package manager that installed it, re-running `dosu setup` when the update changes agent config |
|
|
100
100
|
| `dosu mcp list` | List supported AI tools |
|
|
101
101
|
| `dosu mcp add <tool>` | Add the Dosu MCP server to a specific tool |
|
|
102
|
-
| `dosu mcp refresh` | Rewrite the Dosu MCP entry in every already-configured tool from the current setup
|
|
103
|
-
| `dosu mcp serve --client <agent>` | The local (stdio) Dosu MCP server that tools' MCP entries run; not run by hand |
|
|
104
|
-
| `dosu project show\|link\|unlink [dir]` | Show the project key Dosu memory scopes a directory by, or link a directory to a key |
|
|
105
|
-
| `dosu memory search <query>` / `dosu memory evidence <id>` | Search Dosu memory, or read the evidence behind one memory, as an agent's MCP tools do |
|
|
102
|
+
| `dosu mcp refresh` | Rewrite the Dosu MCP entry in every already-configured tool from the current setup |
|
|
106
103
|
| `dosu logs` | View or manage debug logs (`--tail`, `--clear`) |
|
|
107
104
|
| `dosu telemetry` | Manage usage analytics and error diagnostics (`status`, `enable`, `disable`, `reset`) |
|
|
108
105
|
|
|
109
|
-
`dosu mcp add` takes `-g, --global` to install for all projects instead of project-local, and `--show-secret` to print the full manual config.
|
|
106
|
+
`dosu mcp add` takes `-g, --global` to install for all projects instead of project-local, and `--show-secret` to print the full manual config.
|
|
110
107
|
|
|
111
|
-
`dosu upgrade` delegates to npm, pnpm, Yarn Classic, or Homebrew only after confirming which manager owns the current installation. Temporary package-runner invocations stay ephemeral, ambiguous or local installs are left unchanged, and standalone binaries receive the latest safe manual download path. After a successful update it hands off to the new version, which re-applies bundled skills and
|
|
108
|
+
`dosu upgrade` delegates to npm, pnpm, Yarn Classic, or Homebrew only after confirming which manager owns the current installation. Temporary package-runner invocations stay ephemeral, ambiguous or local installs are left unchanged, and standalone binaries receive the latest safe manual download path. After a successful update it hands off to the new version, which re-applies the bundled skills and, only when a configured agent's MCP entry is out of date, runs `dosu setup` (or the non-interactive `dosu mcp refresh` without a TTY). An entry is out of date when it doesn't match the shape the current provider code writes. Upgrades done outside `dosu upgrade` (npm, brew, `npx @dosu/cli@latest`) get a safety net: on the first command of a new version, the CLI checks each configured provider, silently rewrites only the out-of-date entries, and prompts you to run `dosu setup` for the rest; the bundled agent skills are always re-applied on the first command after any version change.
|
|
112
109
|
|
|
113
|
-
Dosu also updates itself automatically. When the update check (every six hours) finds a newer release, npm, pnpm, Yarn Classic, and Homebrew installs start the same package-manager update in a detached background process. The current command runs unchanged, and your next command uses the new version, whose first run applies the skill
|
|
110
|
+
Dosu also updates itself automatically. When the update check (every six hours) finds a newer release, npm, pnpm, Yarn Classic, and Homebrew installs start the same package-manager update in a detached background process. The current command runs unchanged, and your next command uses the new version, whose first run applies the skill and MCP refreshes above. Hooks, rules, and the status line still need `dosu setup` when a release changes them. Background updates never run for `npx` invocations, standalone binaries, CI, or `DOSU_DEV=true`. A failed install (for example, a global npm prefix that needs `sudo`) falls back to the "Run `dosu upgrade`" notice and is retried after six hours. Turn background updates off with `dosu upgrade --auto off` (and back on with `--auto on`), or with `DOSU_DISABLE_AUTOUPDATE=1` for a single environment.
|
|
114
111
|
|
|
115
112
|
### Platform commands
|
|
116
113
|
|
|
@@ -158,7 +155,6 @@ To change a skill, edit `skills/<name>/โฆ` and run `bun run embed:skills` to re
|
|
|
158
155
|
| `cline-cli` | Cline CLI |
|
|
159
156
|
| `copilot` | GitHub Copilot CLI |
|
|
160
157
|
| `opencode` | OpenCode |
|
|
161
|
-
| `pi` | Pi (installs the Dosu pi extension; global only) |
|
|
162
158
|
| `antigravity` | Antigravity |
|
|
163
159
|
| `mcporter` | MCPorter |
|
|
164
160
|
| `factory` | Factory |
|
|
@@ -174,63 +170,36 @@ dosu setup --agent --tool claude
|
|
|
174
170
|
|
|
175
171
|
Combine with `dosu login --request` / `--check <ticket>` for human-in-the-loop authentication, and `--mode oss|cloud` to skip the mode prompt.
|
|
176
172
|
|
|
177
|
-
###
|
|
178
|
-
|
|
179
|
-
With `dosu knowledge hooks enable`, finished coding-agent sessions
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
process that ran it exits, however it exits, and a pi session as soon as pi shuts it down. OpenCode
|
|
183
|
-
gets a plugin and pi an extension rather than a hook; both also add memory digests to prompts, and
|
|
184
|
-
pi's registers Dosu's MCP server (the `dosu mcp serve` proxy) with pi's built-in MCP and adds
|
|
185
|
-
`/dosu-incognito` (pi run with `--no-extensions` skips it, see
|
|
186
|
-
[docs/syncing.md](docs/syncing.md#pi)). Cursor sessions ship with a
|
|
187
|
-
later sync once they have been quiet for five minutes (its end event fires every turn). A resumed
|
|
188
|
-
session later ships only what is new. Shipping is on by default; `dosu knowledge transcripts
|
|
189
|
-
disable` turns it off.
|
|
190
|
-
`dosu knowledge sync --status` and `dosu knowledge sessions` show what shipped and why anything did
|
|
191
|
-
not (too short, rejected by the server, or from an agent not supported yet);
|
|
192
|
-
`dosu knowledge sync --retry-rejected` tries refused sessions again. On a throwaway machine (a VM
|
|
193
|
-
destroyed after its last task), run `dosu knowledge sync --flush` as the last step before teardown:
|
|
194
|
-
it ships every session not shipped yet right away, including the ones no end event named (Cursor's,
|
|
195
|
-
a Codex session killed with SIGTERM, pi run with `--no-extensions`); see
|
|
196
|
-
[docs/syncing.md](docs/syncing.md#throwaway-machines). Running `claude -p` from an eval
|
|
197
|
-
harness? Some flags (`--bare`, `--safe-mode`, `--no-session-persistence`, ...) keep sessions from
|
|
198
|
-
reaching Dosu; see [docs/syncing.md](docs/syncing.md#claude-code-in-an-eval-harness).
|
|
199
|
-
|
|
200
|
-
When the deployment has no linked repository, memory is scoped by the codebase a session worked in:
|
|
201
|
-
its `origin` remote, else the repository's root commit, else its path. Set `DOSU_PROJECT` in the
|
|
202
|
-
agent's environment, or run `dosu project link [dir] <key>`, to name it yourself
|
|
203
|
-
(`dosu project show` prints the key in use). A session keeps the key it was first resolved under. See
|
|
204
|
-
[docs/syncing.md](docs/syncing.md#project-key).
|
|
205
|
-
|
|
206
|
-
Two switches make shipping visible and controllable. `dosu setup` installs the status line and each
|
|
207
|
-
agent's incognito command alongside the hook (`dosu knowledge hooks enable` installs the incognito
|
|
208
|
-
command with each agent's hooks); they can also be managed directly:
|
|
173
|
+
### Studying sessions: status line and incognito
|
|
174
|
+
|
|
175
|
+
With `dosu knowledge hooks enable`, Dosu studies finished coding-agent sessions in the background
|
|
176
|
+
and turns what it learns into shared knowledge. Two switches make that visible and controllable per
|
|
177
|
+
session. `dosu setup` installs both alongside the hook; they can also be managed directly:
|
|
209
178
|
|
|
210
179
|
```bash
|
|
211
|
-
dosu knowledge statusline enable|disable [claude|cursor]
|
|
212
|
-
dosu knowledge incognito on|off [claude|cursor|codex
|
|
213
|
-
dosu knowledge incognito status
|
|
180
|
+
dosu knowledge statusline enable|disable [claude|cursor] # status-bar line in Claude Code / Cursor CLI
|
|
181
|
+
dosu knowledge incognito on|off [claude|cursor|codex] # stop/resume studying an agent's sessions
|
|
182
|
+
dosu knowledge incognito status # which agents are incognito
|
|
214
183
|
```
|
|
215
184
|
|
|
216
|
-
The status line shows one of `๐ Dosu
|
|
217
|
-
`๐ป Dosu incognito`, `โช Dosu paused`, `โช Dosu not
|
|
218
|
-
|
|
219
|
-
scope
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
on) and tells the model not to use Dosu
|
|
233
|
-
[docs/
|
|
185
|
+
The status line shows one of `๐ Dosu studyingโฆ` (a sync run is live), `๐ Dosu on`,
|
|
186
|
+
`๐ป Dosu incognito`, `โช Dosu paused`, `โช Dosu not studying this repo`, or `โช Dosu off`. Every
|
|
187
|
+
session is studied unless you limit studying to picked repos under `dosu` โ settings โ study
|
|
188
|
+
scope. In an interactive terminal with more than one repo, `dosu setup` asks which repos' sessions
|
|
189
|
+
to ship before offering the backfill of recent sessions. Selecting every repo clears the scope, so
|
|
190
|
+
new repos are included automatically. The choice is saved as the study scope; change it later under
|
|
191
|
+
`dosu` โ settings โ study scope or with `dosu knowledge scope set`. Notes from a session with a
|
|
192
|
+
known repo and branch are anchored to that branch; the rest are unanchored and reach topics
|
|
193
|
+
immediately. Neither setup nor `enable` replaces a status line you
|
|
194
|
+
already have; they print the one-liner to add to your own script instead.
|
|
195
|
+
|
|
196
|
+
`incognito on` is a saved per-agent setting: none of that agent's sessions are studied until you
|
|
197
|
+
turn it off. Turning it off resumes studying only new sessions going forward; sessions that
|
|
198
|
+
finished while incognito was on stay unstudied permanently, even if a later sync picks them up.
|
|
199
|
+
To keep a single chat out instead, type `/dosu-incognito` in it; setup installs that command with
|
|
200
|
+
the hook. It marks the session's transcript so studying skips it (the whole session, and for the
|
|
201
|
+
rest of it โ start a new session to turn Dosu back on) and tells the model not to use Dosu
|
|
202
|
+
tools. See [docs/studying.md](docs/studying.md).
|
|
234
203
|
|
|
235
204
|
### Telemetry and privacy
|
|
236
205
|
|
|
@@ -252,8 +221,11 @@ and may include documented coarse setup choices. `DO_NOT_TRACK=1` and
|
|
|
252
221
|
`DOSU_TELEMETRY_DISABLED=1` disable all telemetry for the process. Dosu never collects
|
|
253
222
|
prompts, raw command lines, free-form argument or option values, user source code, file contents,
|
|
254
223
|
local paths, credentials, raw error messages, or `debug.log`. Environment-variable names and values
|
|
255
|
-
are not collected
|
|
256
|
-
|
|
224
|
+
are not collected, with one exception: when a managed Claude Code settings conflict prevents a
|
|
225
|
+
study run, Dosu reports the allowlisted public Claude Code setting/variable names that caused the
|
|
226
|
+
conflict (never their values) to help diagnose why knowledge sync fails due to settings conflicts.
|
|
227
|
+
See [Telemetry and privacy](docs/telemetry.md) for the exact event fields, destinations, retention,
|
|
228
|
+
and controls.
|
|
257
229
|
|
|
258
230
|
## Configuration
|
|
259
231
|
|
package/bin/dosu.js
CHANGED
|
@@ -450,7 +450,7 @@ function isNpxInvocation(channel = INSTALL_CHANNEL, env = process.env) {
|
|
|
450
450
|
function getVersionString() {
|
|
451
451
|
return `v${VERSION}`;
|
|
452
452
|
}
|
|
453
|
-
var VERSION = "0.67.0-beta.
|
|
453
|
+
var VERSION = "0.67.0-beta.2", INSTALL_CHANNEL = "npm";
|
|
454
454
|
|
|
455
455
|
// src/debug/logger.ts
|
|
456
456
|
import {
|
|
@@ -7688,6 +7688,10 @@ function saveSyncState(state, configDir = getConfigDir()) {
|
|
|
7688
7688
|
writeFileSync3(tmp, JSON.stringify(state, null, 2), { mode: 384 });
|
|
7689
7689
|
renameSync2(tmp, path);
|
|
7690
7690
|
}
|
|
7691
|
+
function saveRepoFilter(repos, configDir = getConfigDir()) {
|
|
7692
|
+
const { repo_filter: _repos, project_filter: _folders, ...rest } = loadSyncState(configDir);
|
|
7693
|
+
saveSyncState(repos ? { ...rest, repo_filter: [...repos] } : rest, configDir);
|
|
7694
|
+
}
|
|
7691
7695
|
function setSyncPaused(paused, configDir = getConfigDir()) {
|
|
7692
7696
|
const state = loadSyncState(configDir);
|
|
7693
7697
|
if (paused)
|
|
@@ -9589,7 +9593,7 @@ var init_repo = __esm(() => {
|
|
|
9589
9593
|
});
|
|
9590
9594
|
|
|
9591
9595
|
// src/sync/lock.ts
|
|
9592
|
-
import { existsSync as existsSync12, readFileSync as readFileSync15, rmSync as rmSync2, statSync as statSync5, writeFileSync as writeFileSync6 } from "node:fs";
|
|
9596
|
+
import { existsSync as existsSync12, readFileSync as readFileSync15, rmSync as rmSync2, statSync as statSync5, utimesSync, writeFileSync as writeFileSync6 } from "node:fs";
|
|
9593
9597
|
import { join as join15 } from "node:path";
|
|
9594
9598
|
function processAlive(pid) {
|
|
9595
9599
|
try {
|
|
@@ -9652,6 +9656,16 @@ function fileLock(configDir = getConfigDir(), now = () => new Date) {
|
|
|
9652
9656
|
} catch {}
|
|
9653
9657
|
return false;
|
|
9654
9658
|
},
|
|
9659
|
+
refresh() {
|
|
9660
|
+
if (!held)
|
|
9661
|
+
return;
|
|
9662
|
+
try {
|
|
9663
|
+
if (readFileSync15(path, "utf8") === String(process.pid)) {
|
|
9664
|
+
const at = now();
|
|
9665
|
+
utimesSync(path, at, at);
|
|
9666
|
+
}
|
|
9667
|
+
} catch {}
|
|
9668
|
+
},
|
|
9655
9669
|
release() {
|
|
9656
9670
|
if (!held)
|
|
9657
9671
|
return;
|
|
@@ -21945,7 +21959,8 @@ async function runKnowledgeSync(options = {}) {
|
|
|
21945
21959
|
offRecord = gate.ready.filter((s) => agentOff(s) && isUnanswered(s, state.sessions[sessionKey(s)]));
|
|
21946
21960
|
const gatedReady = gate.ready.filter((s) => !agentOff(s));
|
|
21947
21961
|
const gatedOpen = gate.open.filter((s) => !agentOff(s));
|
|
21948
|
-
const
|
|
21962
|
+
const scope = options.everyRepo && !deps.ship ? null : repoFilter;
|
|
21963
|
+
const inScope = (sessions) => filterSessionsByRepo(sessions, scope, (s) => locator.resolveRepo(s));
|
|
21949
21964
|
ready = inScope(holdBack === "backoff" ? gatedReady.filter(isEnded) : gatedReady);
|
|
21950
21965
|
open = inScope(gatedOpen);
|
|
21951
21966
|
if (holdBack === "backoff")
|
|
@@ -22019,22 +22034,47 @@ async function runKnowledgeSync(options = {}) {
|
|
|
22019
22034
|
failed: 0
|
|
22020
22035
|
};
|
|
22021
22036
|
const rank = (s) => ended.some((e) => namesSession(e, s)) ? 0 : isEnded(s) ? 1 : 2;
|
|
22022
|
-
const
|
|
22023
|
-
|
|
22024
|
-
logger.debug("sync", `shipping ${batch.length} of ${todo.length} ready sessions`);
|
|
22037
|
+
const batch = [...todo].sort((a, b) => rank(a) - rank(b) || Date.parse(a.updated) - Date.parse(b.updated));
|
|
22038
|
+
logger.debug("sync", `shipping ${batch.length} ready sessions`);
|
|
22025
22039
|
const hold = (s) => {
|
|
22026
22040
|
const current = loadState();
|
|
22027
22041
|
if (!isShippingEnabled(current))
|
|
22028
22042
|
return "stop";
|
|
22029
22043
|
return isAgentIncognito(current, s, lineage) ? "agent" : null;
|
|
22030
22044
|
};
|
|
22045
|
+
const saved = new Map;
|
|
22046
|
+
const reported = [];
|
|
22047
|
+
let savedShipped = 0;
|
|
22048
|
+
const settle = (result) => {
|
|
22049
|
+
reported.push(result);
|
|
22050
|
+
lock.refresh?.();
|
|
22051
|
+
const key = sessionKey(result.session);
|
|
22052
|
+
const entry = ledgerEntry(result, locked.sessions[key], now().toISOString(), cliVersion);
|
|
22053
|
+
if (!entry)
|
|
22054
|
+
return;
|
|
22055
|
+
saved.set(key, entry);
|
|
22056
|
+
try {
|
|
22057
|
+
const counted = result.outcome === "shipped" && !result.session.parentId ? 1 : 0;
|
|
22058
|
+
const next = loadState();
|
|
22059
|
+
next.sessions[key] = entry;
|
|
22060
|
+
next.total_shipped = (next.total_shipped ?? 0) + counted;
|
|
22061
|
+
next.run = run;
|
|
22062
|
+
saveState(next);
|
|
22063
|
+
savedShipped += counted;
|
|
22064
|
+
} catch {}
|
|
22065
|
+
};
|
|
22031
22066
|
let results = [];
|
|
22032
22067
|
if (batch.length > 0) {
|
|
22033
22068
|
try {
|
|
22034
|
-
results = await deps.ship(batch, (s) => shippedPrefix(locked.sessions[sessionKey(s)]), hold);
|
|
22069
|
+
results = await deps.ship(batch, (s) => shippedPrefix(locked.sessions[sessionKey(s)]), hold, settle);
|
|
22035
22070
|
} catch (err) {
|
|
22036
22071
|
const message = err instanceof Error ? err.message : String(err);
|
|
22037
|
-
|
|
22072
|
+
const answered = new Set(reported.map((r) => sessionKey(r.session)));
|
|
22073
|
+
const next = batch.find((s) => !answered.has(sessionKey(s)));
|
|
22074
|
+
results = [
|
|
22075
|
+
...reported,
|
|
22076
|
+
...next ? [{ session: next, outcome: "failed", message }] : []
|
|
22077
|
+
];
|
|
22038
22078
|
}
|
|
22039
22079
|
}
|
|
22040
22080
|
const resultOf = new Map(results.map((r) => [sessionKey(r.session), r]));
|
|
@@ -22050,7 +22090,7 @@ async function runKnowledgeSync(options = {}) {
|
|
|
22050
22090
|
counts[result.outcome] += 1;
|
|
22051
22091
|
else
|
|
22052
22092
|
counts.subagents[result.outcome] += 1;
|
|
22053
|
-
const entry = ledgerEntry(result, locked.sessions[key], at, cliVersion);
|
|
22093
|
+
const entry = saved.get(key) ?? ledgerEntry(result, locked.sessions[key], at, cliVersion);
|
|
22054
22094
|
if (!entry) {
|
|
22055
22095
|
error = result.message ?? "unknown error";
|
|
22056
22096
|
logger.debug("sync", `shipping failed at ${key}: ${error}`);
|
|
@@ -22077,7 +22117,7 @@ async function runKnowledgeSync(options = {}) {
|
|
|
22077
22117
|
if (batch.length > 0) {
|
|
22078
22118
|
next.last_attempt_at = at;
|
|
22079
22119
|
next.consecutive_failures = counts.failed > 0 ? next.consecutive_failures + 1 : 0;
|
|
22080
|
-
next.total_shipped = (next.total_shipped ?? 0) + counts.shipped;
|
|
22120
|
+
next.total_shipped = (next.total_shipped ?? 0) + counts.shipped - savedShipped;
|
|
22081
22121
|
next.run = run;
|
|
22082
22122
|
}
|
|
22083
22123
|
if (resumes)
|
|
@@ -22097,7 +22137,7 @@ async function runKnowledgeSync(options = {}) {
|
|
|
22097
22137
|
lock.release();
|
|
22098
22138
|
}
|
|
22099
22139
|
}
|
|
22100
|
-
var SCAN_WINDOW_DAYS = 30, LEDGER_GRACE_DAYS = 7,
|
|
22140
|
+
var SCAN_WINDOW_DAYS = 30, LEDGER_GRACE_DAYS = 7, DAY_MS, ENDED_LOCK_WAIT_MS, LOCK_POLL_MS = 2000, LOG_PREVIEW_LIMIT = 10;
|
|
22101
22141
|
var init_sync = __esm(() => {
|
|
22102
22142
|
init_logger();
|
|
22103
22143
|
init_lineage();
|
|
@@ -22178,7 +22218,7 @@ var init_backlog = __esm(() => {
|
|
|
22178
22218
|
});
|
|
22179
22219
|
|
|
22180
22220
|
// src/telemetry/debug-id.ts
|
|
22181
|
-
var BUNDLE_DEBUG_ID_PLACEHOLDER = "
|
|
22221
|
+
var BUNDLE_DEBUG_ID_PLACEHOLDER = "c8a0f0e8-7cd4-1cd4-6475-6e2164756e21";
|
|
22182
22222
|
|
|
22183
22223
|
// src/telemetry/telemetry.ts
|
|
22184
22224
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
@@ -23147,10 +23187,6 @@ function isDirectory(path) {
|
|
|
23147
23187
|
return false;
|
|
23148
23188
|
}
|
|
23149
23189
|
}
|
|
23150
|
-
function saveScope(repos) {
|
|
23151
|
-
const { repo_filter: _repos, project_filter: _folders, ...rest } = loadSyncState();
|
|
23152
|
-
saveSyncState(repos ? { ...rest, repo_filter: repos } : rest);
|
|
23153
|
-
}
|
|
23154
23190
|
function scopeCommand() {
|
|
23155
23191
|
const cmd = new Command("scope").description("Show or set which repos' sessions ship to Dosu memory (the TUI's sync scope)");
|
|
23156
23192
|
cmd.command("show").description("Show the repos whose sessions ship").option("--json", "Output as JSON").action((opts) => {
|
|
@@ -23180,11 +23216,11 @@ function scopeCommand() {
|
|
|
23180
23216
|
repos.add(repo);
|
|
23181
23217
|
}
|
|
23182
23218
|
const sorted = [...repos].sort();
|
|
23183
|
-
|
|
23219
|
+
saveRepoFilter(sorted);
|
|
23184
23220
|
console.log(`โ Sessions ship only from ${sorted.join(", ")}.`);
|
|
23185
23221
|
});
|
|
23186
23222
|
cmd.command("clear").description("Ship sessions from every repo, and sessions outside any repo").action(() => {
|
|
23187
|
-
|
|
23223
|
+
saveRepoFilter(null);
|
|
23188
23224
|
console.log("โ Sessions from every repo ship, and sessions outside any repo.");
|
|
23189
23225
|
});
|
|
23190
23226
|
return cmd;
|
|
@@ -23598,7 +23634,7 @@ function createShipStep(options) {
|
|
|
23598
23634
|
}
|
|
23599
23635
|
return async (sessions, shippedOf = () => {
|
|
23600
23636
|
return;
|
|
23601
|
-
}, hold = () => null) => {
|
|
23637
|
+
}, hold = () => null, settled = () => {}) => {
|
|
23602
23638
|
const resolver = options.resolveProject && options.resolveBranch ? undefined : createProjectDirResolver();
|
|
23603
23639
|
const resolve = {
|
|
23604
23640
|
resolveProject: options.resolveProject ?? ((session) => resolver?.resolveProject(session) ?? null),
|
|
@@ -23610,6 +23646,7 @@ function createShipStep(options) {
|
|
|
23610
23646
|
if (!result)
|
|
23611
23647
|
break;
|
|
23612
23648
|
results.push(result);
|
|
23649
|
+
settled(result);
|
|
23613
23650
|
if (result.outcome === "failed")
|
|
23614
23651
|
break;
|
|
23615
23652
|
}
|
|
@@ -23999,21 +24036,11 @@ ${results.length - opts.limit} more results not shown.`));
|
|
|
23999
24036
|
...opts.retryRejected ? { retryRejectedBefore: new Date } : {},
|
|
24000
24037
|
deps
|
|
24001
24038
|
};
|
|
24002
|
-
|
|
24003
|
-
let sessionsShipped = outcome.counts?.shipped ?? 0;
|
|
24004
|
-
if ((opts.bootstrap || opts.flush) && deps.ship) {
|
|
24005
|
-
const maxRounds = Math.ceil(outcome.readySessions / SHIP_BATCH_LIMIT) + 2;
|
|
24006
|
-
for (let round = 1;outcome.status === "shipped" && (outcome.settledSessions ?? 0) > 0 && (outcome.settledSessions ?? 0) < outcome.readySessions && round < maxRounds; round++) {
|
|
24007
|
-
if (!opts.quiet && !opts.json)
|
|
24008
|
-
printSyncOutcome(outcome);
|
|
24009
|
-
outcome = await runKnowledgeSync(syncOptions);
|
|
24010
|
-
sessionsShipped += outcome.counts?.shipped ?? 0;
|
|
24011
|
-
}
|
|
24012
|
-
}
|
|
24039
|
+
const outcome = await runKnowledgeSync(syncOptions);
|
|
24013
24040
|
recordCommandFacets({
|
|
24014
24041
|
sync_trigger: trigger,
|
|
24015
24042
|
sync_status: outcome.status,
|
|
24016
|
-
sessions_shipped:
|
|
24043
|
+
sessions_shipped: outcome.counts?.shipped ?? 0
|
|
24017
24044
|
});
|
|
24018
24045
|
if (opts.quiet)
|
|
24019
24046
|
return;
|
|
@@ -24345,9 +24372,9 @@ function printSyncOutcome(outcome) {
|
|
|
24345
24372
|
process.exitCode = 1;
|
|
24346
24373
|
break;
|
|
24347
24374
|
}
|
|
24348
|
-
|
|
24349
|
-
|
|
24350
|
-
console.log(import_picocolors23.default.dim(`${
|
|
24375
|
+
if (outcome.inFlightSessions > 0) {
|
|
24376
|
+
const n = outcome.inFlightSessions;
|
|
24377
|
+
console.log(import_picocolors23.default.dim(`${n} session${plural(n)} still in progress will ship once ${n === 1 ? "it ends" : "they end"}.`));
|
|
24351
24378
|
}
|
|
24352
24379
|
break;
|
|
24353
24380
|
}
|
|
@@ -30566,6 +30593,58 @@ var init_rules_step = __esm(() => {
|
|
|
30566
30593
|
init_styles();
|
|
30567
30594
|
});
|
|
30568
30595
|
|
|
30596
|
+
// src/setup/scope-step.ts
|
|
30597
|
+
function scopeCandidates(sessions, current) {
|
|
30598
|
+
const byRepo = new Map;
|
|
30599
|
+
for (const { repo, updated } of sessions) {
|
|
30600
|
+
if (!repo)
|
|
30601
|
+
continue;
|
|
30602
|
+
const candidate = byRepo.get(repo);
|
|
30603
|
+
if (!candidate)
|
|
30604
|
+
byRepo.set(repo, { repo, sessions: 1, updated });
|
|
30605
|
+
else {
|
|
30606
|
+
candidate.sessions += 1;
|
|
30607
|
+
if (updated > candidate.updated)
|
|
30608
|
+
candidate.updated = updated;
|
|
30609
|
+
}
|
|
30610
|
+
}
|
|
30611
|
+
for (const repo of current ?? []) {
|
|
30612
|
+
if (!byRepo.has(repo))
|
|
30613
|
+
byRepo.set(repo, { repo, sessions: 0, updated: "" });
|
|
30614
|
+
}
|
|
30615
|
+
return [...byRepo.values()].sort((a, b) => b.sessions - a.sessions || b.updated.localeCompare(a.updated) || a.repo.localeCompare(b.repo));
|
|
30616
|
+
}
|
|
30617
|
+
function canPickScope(candidates) {
|
|
30618
|
+
return process.stdin.isTTY === true && candidates.length > 1;
|
|
30619
|
+
}
|
|
30620
|
+
async function stepPickSyncScope(candidates, current) {
|
|
30621
|
+
const repos = candidates.map((candidate) => candidate.repo);
|
|
30622
|
+
const plural = (n) => n === 1 ? "" : "s";
|
|
30623
|
+
const selected = await multiselect2({
|
|
30624
|
+
message: "Ship sessions to Dosu memory from which repos?",
|
|
30625
|
+
options: candidates.map(({ repo, sessions }) => ({
|
|
30626
|
+
value: repo,
|
|
30627
|
+
label: displayRepo(repo),
|
|
30628
|
+
hint: sessions > 0 ? `${sessions} session${plural(sessions)}` : "no recent sessions"
|
|
30629
|
+
})),
|
|
30630
|
+
initialValues: current ?? repos,
|
|
30631
|
+
summary: (picked) => picked.length === repos.length ? "all repos ยท new ones included automatically" : `${picked.length} of ${repos.length} repos`,
|
|
30632
|
+
validate: (picked) => picked.length === 0 ? "Select at least one repo." : undefined
|
|
30633
|
+
});
|
|
30634
|
+
if (isCancel2(selected))
|
|
30635
|
+
return current;
|
|
30636
|
+
const scope = selected.length === repos.length ? null : selected;
|
|
30637
|
+
saveRepoFilter(scope);
|
|
30638
|
+
log2.success(`Sync scope ${dim(`ยท ${scope ? scope.map(displayRepo).join(", ") : "all repos"}`)}`);
|
|
30639
|
+
return scope;
|
|
30640
|
+
}
|
|
30641
|
+
var init_scope_step = __esm(() => {
|
|
30642
|
+
init_repo();
|
|
30643
|
+
init_state();
|
|
30644
|
+
init_prompts();
|
|
30645
|
+
init_styles();
|
|
30646
|
+
});
|
|
30647
|
+
|
|
30569
30648
|
// src/auth/server.ts
|
|
30570
30649
|
function escapeHtml(value) {
|
|
30571
30650
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
@@ -31130,17 +31209,32 @@ async function stepOfferInitialSync(cfg) {
|
|
|
31130
31209
|
logger.info("setup", "Step: offer initial knowledge sync");
|
|
31131
31210
|
const s = spinner2();
|
|
31132
31211
|
s.start("Checking for agent sessions from the last 30 days...");
|
|
31133
|
-
const outcome = await runKnowledgeSync();
|
|
31134
|
-
const
|
|
31212
|
+
const outcome = await runKnowledgeSync({ everyRepo: true });
|
|
31213
|
+
const storedScope = loadSyncState().repo_filter ?? null;
|
|
31214
|
+
const everyBacklog = outcome.status === "backlog" ? unsettledSessions(outcome.sessions, state) : [];
|
|
31135
31215
|
const lineage = sessionLineage(outcome.sessions);
|
|
31136
|
-
const
|
|
31216
|
+
const offered = (sessions) => withoutSubagents(sessions).filter((session) => !isAgentIncognito(state, session, lineage));
|
|
31217
|
+
const inScope = (scope) => filterSessionsByRepo(everyBacklog, scope, (session) => session.repo ?? null);
|
|
31218
|
+
const candidates = scopeCandidates(offered(everyBacklog), storedScope);
|
|
31219
|
+
const pickScope = canPickScope(candidates);
|
|
31220
|
+
let backlog = pickScope ? everyBacklog : inScope(storedScope);
|
|
31221
|
+
let n = offered(backlog).length;
|
|
31137
31222
|
if (n === 0) {
|
|
31138
31223
|
s.stop("No recent agent sessions. Dosu memory learns from new ones as you work.");
|
|
31139
31224
|
recordCommandFacets({ backfill_offer: "not-offered" });
|
|
31140
31225
|
return;
|
|
31141
31226
|
}
|
|
31142
|
-
const them = n === 1 ? "it" : "them";
|
|
31143
31227
|
s.stop(`Found ${n} agent session${n === 1 ? "" : "s"} from the last 30 days on this machine.`);
|
|
31228
|
+
if (pickScope) {
|
|
31229
|
+
backlog = inScope(await stepPickSyncScope(candidates, storedScope));
|
|
31230
|
+
n = offered(backlog).length;
|
|
31231
|
+
if (n === 0) {
|
|
31232
|
+
log2.info("No sessions from those repos are waiting. Dosu memory learns from new ones.");
|
|
31233
|
+
recordCommandFacets({ backfill_offer: "not-offered" });
|
|
31234
|
+
return;
|
|
31235
|
+
}
|
|
31236
|
+
}
|
|
31237
|
+
const them = n === 1 ? "it" : "them";
|
|
31144
31238
|
const incognito = installedIncognitoCommands();
|
|
31145
31239
|
log2.message(wrapLog([
|
|
31146
31240
|
"Dosu memory learns from finished agent sessions: where things live, commands that work, approaches that failed, so the next agent starts from what the last one learned.",
|
|
@@ -31922,6 +32016,7 @@ var init_flow2 = __esm(() => {
|
|
|
31922
32016
|
init_agents_md_step();
|
|
31923
32017
|
init_analytics2();
|
|
31924
32018
|
init_rules_step();
|
|
32019
|
+
init_scope_step();
|
|
31925
32020
|
init_styles();
|
|
31926
32021
|
import_picocolors38 = __toESM(require_picocolors(), 1);
|
|
31927
32022
|
OAUTH_ANALYTICS_REASONS = new Set([
|
|
@@ -33534,43 +33629,27 @@ async function runSettings(cfg) {
|
|
|
33534
33629
|
}
|
|
33535
33630
|
}
|
|
33536
33631
|
function discoverSessionRepos(current) {
|
|
33537
|
-
const
|
|
33632
|
+
const inRepos = [];
|
|
33538
33633
|
try {
|
|
33539
33634
|
const resolver = createProjectDirResolver();
|
|
33540
33635
|
for (const session of scanAgentSessions({})) {
|
|
33541
33636
|
const repo = resolver.resolveRepo(session);
|
|
33542
33637
|
if (repo)
|
|
33543
|
-
|
|
33638
|
+
inRepos.push({ ...session, repo });
|
|
33544
33639
|
}
|
|
33545
33640
|
resolver.flush();
|
|
33546
33641
|
} catch {}
|
|
33547
|
-
|
|
33548
|
-
if (!counts.has(repo))
|
|
33549
|
-
counts.set(repo, 0);
|
|
33550
|
-
return [...counts.entries()].sort((a, b) => b[1] - a[1]).map(([repo]) => repo);
|
|
33642
|
+
return scopeCandidates(inRepos, current);
|
|
33551
33643
|
}
|
|
33552
33644
|
async function runStudyingProjectsSetting() {
|
|
33553
33645
|
const state = loadSyncState();
|
|
33554
33646
|
const current = studyRepoFilter(state, () => scanAgentSessions({}), createProjectDirResolver());
|
|
33555
|
-
const
|
|
33556
|
-
if (
|
|
33647
|
+
const candidates = discoverSessionRepos(current);
|
|
33648
|
+
if (candidates.length === 0) {
|
|
33557
33649
|
log2.info("No agent sessions inside a git repo found yet; nothing to scope.");
|
|
33558
33650
|
return;
|
|
33559
33651
|
}
|
|
33560
|
-
|
|
33561
|
-
message: "Ship sessions to Dosu memory from which repos?",
|
|
33562
|
-
options: repos.map((repo) => ({ label: displayRepo(repo), hint: repo, value: repo })),
|
|
33563
|
-
initialValues: current ?? repos,
|
|
33564
|
-
summary: (picked) => picked.length === repos.length ? "all repos ยท new ones included automatically" : `${picked.length} of ${repos.length} repos`,
|
|
33565
|
-
validate: (picked) => picked.length === 0 ? "Select at least one repo." : undefined
|
|
33566
|
-
});
|
|
33567
|
-
if (isCancel2(selected))
|
|
33568
|
-
return;
|
|
33569
|
-
const { repo_filter: _repos, project_filter: _folders, ...fresh } = loadSyncState();
|
|
33570
|
-
const all = selected.length === repos.length;
|
|
33571
|
-
saveSyncState(all ? fresh : { ...fresh, repo_filter: [...selected] });
|
|
33572
|
-
const scope = all ? "all repos" : selected.map(displayRepo).join(", ");
|
|
33573
|
-
log2.success(`Sync scope ${dim(`ยท ${scope}`)}`);
|
|
33652
|
+
await stepPickSyncScope(candidates, current);
|
|
33574
33653
|
}
|
|
33575
33654
|
async function handleAuthenticate(cfg) {
|
|
33576
33655
|
const shouldLogin = await confirm2({ message: "Open browser to log in?" });
|
|
@@ -33626,6 +33705,7 @@ var init_tui = __esm(() => {
|
|
|
33626
33705
|
init_scan();
|
|
33627
33706
|
init_agents_md_step();
|
|
33628
33707
|
init_flow2();
|
|
33708
|
+
init_scope_step();
|
|
33629
33709
|
init_styles();
|
|
33630
33710
|
init_state();
|
|
33631
33711
|
init_status();
|
|
@@ -34900,4 +34980,4 @@ main().catch((err) => {
|
|
|
34900
34980
|
process.exit(1);
|
|
34901
34981
|
});
|
|
34902
34982
|
|
|
34903
|
-
//# debugId=
|
|
34983
|
+
//# debugId=c8a0f0e8-7cd4-1cd4-6475-6e2164756e21
|