bertrand 0.20.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/dist/bertrand.js +182 -57
- package/dist/dashboard/assets/index-D_l3EPhC.css +1 -0
- package/dist/dashboard/assets/index-mg96b7q-.js +684 -0
- package/dist/dashboard/index.html +3 -3
- package/dist/run-screen.js +430 -501
- package/package.json +1 -1
- package/dist/dashboard/assets/index-DgvcDkSY.js +0 -673
- package/dist/dashboard/assets/index-qVHXGTGL.css +0 -1
- package/dist/dashboard/assets/workbox-window.prod.es5-BBnX5xw4.js +0 -2
- package/dist/dashboard/manifest.webmanifest +0 -1
- package/dist/dashboard/sw.js +0 -1
- package/dist/dashboard/workbox-e4022e15.js +0 -1
package/README.md
CHANGED
|
@@ -79,13 +79,14 @@ Shows a picker: start a fresh Claude conversation, or resume one of the prior co
|
|
|
79
79
|
bertrand list
|
|
80
80
|
```
|
|
81
81
|
|
|
82
|
-
Interactive picker showing all sessions with status badges.
|
|
82
|
+
Interactive picker showing all sessions with status badges. Add `--project <slug>` to list sessions from another project without switching the active one.
|
|
83
83
|
|
|
84
84
|
### Other commands
|
|
85
85
|
|
|
86
86
|
| Command | Purpose |
|
|
87
87
|
|---|---|
|
|
88
|
-
| `bertrand log
|
|
88
|
+
| `bertrand log` | List sessions in the active project. Add `--project <slug>` to scope to a different project. |
|
|
89
|
+
| `bertrand log <session>` | Print the timeline event log for a session. Supports `--json` (includes `project: { slug, name }` for agent consumption) and `--project <slug>` for cross-project reads. |
|
|
89
90
|
| `bertrand stats <session>` | Print materialized stats (duration, work/wait split, lines changed). |
|
|
90
91
|
| `bertrand archive <name>` | Archive or unarchive a session. |
|
|
91
92
|
| `bertrand serve` | Start the dashboard HTTP API on `:5200`. |
|
package/dist/bertrand.js
CHANGED
|
@@ -15,6 +15,7 @@ var __export = (target, all) => {
|
|
|
15
15
|
});
|
|
16
16
|
};
|
|
17
17
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
18
|
+
var __require = import.meta.require;
|
|
18
19
|
|
|
19
20
|
// src/lib/paths.ts
|
|
20
21
|
import { homedir } from "os";
|
|
@@ -25,6 +26,7 @@ var init_paths = __esm(() => {
|
|
|
25
26
|
root: join(homedir(), BERTRAND_DIR),
|
|
26
27
|
hooks: join(homedir(), BERTRAND_DIR, "hooks"),
|
|
27
28
|
sessions: join(homedir(), BERTRAND_DIR, "sessions"),
|
|
29
|
+
runtime: join(homedir(), BERTRAND_DIR, "run"),
|
|
28
30
|
db: join(homedir(), BERTRAND_DIR, "bertrand.db"),
|
|
29
31
|
syncEnv: join(homedir(), BERTRAND_DIR, "sync.env")
|
|
30
32
|
};
|
|
@@ -2117,8 +2119,10 @@ async function handleSwitchProject(req) {
|
|
|
2117
2119
|
return Response.json({ error: `Unknown project: ${slug}` }, { status: 404 });
|
|
2118
2120
|
}
|
|
2119
2121
|
setActiveProjectSlug(slug);
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
+
process.env.BERTRAND_PROJECT = slug;
|
|
2123
|
+
_resetActiveProjectCache();
|
|
2124
|
+
invalidateDbCache();
|
|
2125
|
+
return Response.json({ ok: true, slug });
|
|
2122
2126
|
}
|
|
2123
2127
|
async function handleOpen(req) {
|
|
2124
2128
|
let body;
|
|
@@ -2287,6 +2291,7 @@ var init_server = __esm(() => {
|
|
|
2287
2291
|
init_session_archive();
|
|
2288
2292
|
init_registry();
|
|
2289
2293
|
init_resolve();
|
|
2294
|
+
init_client();
|
|
2290
2295
|
PORT = Number(process.env.BERTRAND_PORT ?? 5200);
|
|
2291
2296
|
routes = [
|
|
2292
2297
|
[/^\/api\/sessions$/, listSessions],
|
|
@@ -3450,10 +3455,8 @@ function installExitHandlers() {
|
|
|
3450
3455
|
const onSignal = (signal) => {
|
|
3451
3456
|
if (isClaudeRunning())
|
|
3452
3457
|
return;
|
|
3453
|
-
process.exit(signal === "
|
|
3458
|
+
process.exit(signal === "SIGHUP" ? 129 : 143);
|
|
3454
3459
|
};
|
|
3455
|
-
process.on("SIGINT", onSignal);
|
|
3456
|
-
process.on("SIGTERM", onSignal);
|
|
3457
3460
|
process.on("SIGHUP", onSignal);
|
|
3458
3461
|
}
|
|
3459
3462
|
async function launch(opts) {
|
|
@@ -3565,6 +3568,7 @@ function finalizeSession(sessionId, conversationId, exitCode) {
|
|
|
3565
3568
|
emitSessionEnded({ sessionId });
|
|
3566
3569
|
computeAndPersist(sessionId);
|
|
3567
3570
|
stopServerIfIdle();
|
|
3571
|
+
triggerBackgroundPush();
|
|
3568
3572
|
}
|
|
3569
3573
|
var liveSession = null, exitHandlersInstalled = false;
|
|
3570
3574
|
var init_session = __esm(() => {
|
|
@@ -3579,39 +3583,68 @@ var init_session = __esm(() => {
|
|
|
3579
3583
|
init_spawn_context();
|
|
3580
3584
|
init_timing();
|
|
3581
3585
|
init_server_lifecycle();
|
|
3586
|
+
init_trigger();
|
|
3582
3587
|
});
|
|
3583
3588
|
|
|
3584
3589
|
// src/tui/app.tsx
|
|
3585
3590
|
import { spawn as spawn4 } from "child_process";
|
|
3586
3591
|
import { existsSync as existsSync8, readFileSync as readFileSync8, unlinkSync as unlinkSync3 } from "fs";
|
|
3592
|
+
import { tmpdir } from "os";
|
|
3587
3593
|
import { join as join9 } from "path";
|
|
3588
3594
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
3589
3595
|
async function runScreen(screen, ...args) {
|
|
3590
|
-
const tmpFile =
|
|
3596
|
+
const tmpFile = join9(tmpdir(), `bertrand-tui-${randomUUID2()}.json`);
|
|
3597
|
+
if (process.env.BERTRAND_DEBUG_TUI) {
|
|
3598
|
+
try {
|
|
3599
|
+
const { appendFileSync } = await import("fs");
|
|
3600
|
+
appendFileSync(process.env.BERTRAND_DEBUG_TUI, `--- parent runScreen("${screen}") at ${Date.now()} entry=${SCREEN_ENTRY}
|
|
3601
|
+
`);
|
|
3602
|
+
} catch {}
|
|
3603
|
+
}
|
|
3591
3604
|
const child = spawn4("bun", ["run", SCREEN_ENTRY, screen, tmpFile, ...args], {
|
|
3592
|
-
stdio: "inherit"
|
|
3593
|
-
|
|
3594
|
-
const exitCode = await new Promise((resolve) => {
|
|
3595
|
-
child.on("exit", (code) => resolve(code ?? 1));
|
|
3605
|
+
stdio: "inherit",
|
|
3606
|
+
env: process.env
|
|
3596
3607
|
});
|
|
3597
|
-
|
|
3598
|
-
|
|
3608
|
+
const noopSignal = () => {};
|
|
3609
|
+
process.on("SIGINT", noopSignal);
|
|
3610
|
+
process.on("SIGTERM", noopSignal);
|
|
3611
|
+
let spawnError = null;
|
|
3612
|
+
try {
|
|
3613
|
+
const { code, signal } = await new Promise((resolve) => {
|
|
3614
|
+
child.on("error", (err) => {
|
|
3615
|
+
spawnError = err;
|
|
3616
|
+
resolve({ code: 1, signal: null });
|
|
3617
|
+
});
|
|
3618
|
+
child.on("exit", (c, s) => resolve({ code: c, signal: s }));
|
|
3619
|
+
});
|
|
3620
|
+
if (spawnError) {
|
|
3621
|
+
throw new Error(`Failed to launch TUI screen "${screen}": ${spawnError.message}`);
|
|
3622
|
+
}
|
|
3623
|
+
if (!existsSync8(tmpFile)) {
|
|
3624
|
+
const detail = signal ? `killed by ${signal}` : `exited with code ${code ?? "?"} without writing result`;
|
|
3625
|
+
throw new Error(`TUI screen "${screen}" ${detail}`);
|
|
3626
|
+
}
|
|
3627
|
+
return JSON.parse(readFileSync8(tmpFile, "utf-8"));
|
|
3628
|
+
} finally {
|
|
3629
|
+
process.removeListener("SIGINT", noopSignal);
|
|
3630
|
+
process.removeListener("SIGTERM", noopSignal);
|
|
3631
|
+
try {
|
|
3632
|
+
if (existsSync8(tmpFile))
|
|
3633
|
+
unlinkSync3(tmpFile);
|
|
3634
|
+
} catch {}
|
|
3599
3635
|
}
|
|
3600
|
-
const result = JSON.parse(readFileSync8(tmpFile, "utf-8"));
|
|
3601
|
-
unlinkSync3(tmpFile);
|
|
3602
|
-
return result;
|
|
3603
3636
|
}
|
|
3604
|
-
async function
|
|
3605
|
-
return runScreen("
|
|
3637
|
+
async function startLaunchTui() {
|
|
3638
|
+
return runScreen("launch");
|
|
3639
|
+
}
|
|
3640
|
+
async function startProjectPickerTui() {
|
|
3641
|
+
return runScreen("project-picker");
|
|
3606
3642
|
}
|
|
3607
3643
|
async function startExitTui(sessionId) {
|
|
3608
3644
|
return runScreen("exit", sessionId);
|
|
3609
3645
|
}
|
|
3610
3646
|
async function startResumeTui(sessionId) {
|
|
3611
3647
|
const conversations2 = getConversationsBySession(sessionId);
|
|
3612
|
-
if (conversations2.length === 1) {
|
|
3613
|
-
return { type: "conversation", conversationId: conversations2[0].id };
|
|
3614
|
-
}
|
|
3615
3648
|
if (conversations2.length === 0) {
|
|
3616
3649
|
return { type: "new" };
|
|
3617
3650
|
}
|
|
@@ -3658,19 +3691,17 @@ function shouldShowProjectPicker() {
|
|
|
3658
3691
|
const projects = listProjects();
|
|
3659
3692
|
return projects.length !== 1;
|
|
3660
3693
|
}
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
const initialProjectSlug = getActiveProjectSlug();
|
|
3664
|
-
const selection = await startStartupTui(skipProjectPicker, initialProjectSlug);
|
|
3694
|
+
function activateProject(slug) {
|
|
3695
|
+
setActiveProjectSlug(slug);
|
|
3665
3696
|
_resetActiveProjectCache();
|
|
3697
|
+
}
|
|
3698
|
+
async function runLaunchCycle() {
|
|
3699
|
+
const selection = await startLaunchTui();
|
|
3666
3700
|
switch (selection.type) {
|
|
3667
3701
|
case "quit":
|
|
3668
3702
|
return;
|
|
3669
3703
|
case "create": {
|
|
3670
|
-
const sessionId = await launch(
|
|
3671
|
-
categoryPath: selection.categoryPath,
|
|
3672
|
-
slug: selection.slug
|
|
3673
|
-
});
|
|
3704
|
+
const sessionId = await launch(selection);
|
|
3674
3705
|
await runSessionLoop(sessionId);
|
|
3675
3706
|
return;
|
|
3676
3707
|
}
|
|
@@ -3687,6 +3718,28 @@ async function startTui() {
|
|
|
3687
3718
|
}
|
|
3688
3719
|
}
|
|
3689
3720
|
}
|
|
3721
|
+
async function startTui() {
|
|
3722
|
+
if (!shouldShowProjectPicker()) {
|
|
3723
|
+
await runLaunchCycle();
|
|
3724
|
+
return;
|
|
3725
|
+
}
|
|
3726
|
+
const projectSelection = await startProjectPickerTui();
|
|
3727
|
+
switch (projectSelection.type) {
|
|
3728
|
+
case "quit":
|
|
3729
|
+
return;
|
|
3730
|
+
case "select": {
|
|
3731
|
+
activateProject(projectSelection.slug);
|
|
3732
|
+
await runLaunchCycle();
|
|
3733
|
+
return;
|
|
3734
|
+
}
|
|
3735
|
+
case "create": {
|
|
3736
|
+
createProject({ slug: projectSelection.slug });
|
|
3737
|
+
activateProject(projectSelection.slug);
|
|
3738
|
+
await runLaunchCycle();
|
|
3739
|
+
return;
|
|
3740
|
+
}
|
|
3741
|
+
}
|
|
3742
|
+
}
|
|
3690
3743
|
var SCREEN_ENTRY;
|
|
3691
3744
|
var init_app = __esm(() => {
|
|
3692
3745
|
init_sessions();
|
|
@@ -3694,6 +3747,7 @@ var init_app = __esm(() => {
|
|
|
3694
3747
|
init_session_archive();
|
|
3695
3748
|
init_session();
|
|
3696
3749
|
init_registry();
|
|
3750
|
+
init_create();
|
|
3697
3751
|
init_resolve();
|
|
3698
3752
|
SCREEN_ENTRY = (() => {
|
|
3699
3753
|
const built = join9(import.meta.dir, "run-screen.js");
|
|
@@ -3755,21 +3809,33 @@ var init_recovery = __esm(() => {
|
|
|
3755
3809
|
|
|
3756
3810
|
// src/cli/commands/launch.ts
|
|
3757
3811
|
var exports_launch = {};
|
|
3812
|
+
function reportFatal(err) {
|
|
3813
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3814
|
+
console.error(`bertrand: ${message}`);
|
|
3815
|
+
if (process.env.BERTRAND_DEBUG && err instanceof Error && err.stack) {
|
|
3816
|
+
console.error(err.stack);
|
|
3817
|
+
}
|
|
3818
|
+
process.exit(1);
|
|
3819
|
+
}
|
|
3758
3820
|
var init_launch = __esm(() => {
|
|
3759
3821
|
init_router();
|
|
3760
3822
|
init_app();
|
|
3761
3823
|
init_session();
|
|
3762
3824
|
init_recovery();
|
|
3763
3825
|
register("launch", async (args) => {
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3826
|
+
try {
|
|
3827
|
+
recoverStaleSessions();
|
|
3828
|
+
const sessionName = args[0];
|
|
3829
|
+
if (sessionName) {
|
|
3830
|
+
const { categoryPath, slug } = parseSessionName(sessionName);
|
|
3831
|
+
const sessionId = await launch({ categoryPath, slug });
|
|
3832
|
+
await runSessionLoop(sessionId);
|
|
3833
|
+
return;
|
|
3834
|
+
}
|
|
3835
|
+
await startTui();
|
|
3836
|
+
} catch (err) {
|
|
3837
|
+
reportFatal(err);
|
|
3771
3838
|
}
|
|
3772
|
-
await startTui();
|
|
3773
3839
|
});
|
|
3774
3840
|
});
|
|
3775
3841
|
|
|
@@ -3777,7 +3843,7 @@ var init_launch = __esm(() => {
|
|
|
3777
3843
|
function quietHelper(bin) {
|
|
3778
3844
|
return `bq() { ${bin} "$@" 2>/dev/null || true; }`;
|
|
3779
3845
|
}
|
|
3780
|
-
function waitingScript(bin) {
|
|
3846
|
+
function waitingScript(bin, runtimeDir) {
|
|
3781
3847
|
return `#!/usr/bin/env bash
|
|
3782
3848
|
# Hook: PreToolUse AskUserQuestion \u2192 enforce multiSelect, mark session as waiting
|
|
3783
3849
|
${quietHelper(bin)}
|
|
@@ -3802,7 +3868,7 @@ question="$(printf '%s' "$input" | grep -o '"question":"[^"]*"' | head -1 | cut
|
|
|
3802
3868
|
[ -z "$question" ] && question="Waiting for input"
|
|
3803
3869
|
|
|
3804
3870
|
# Clear working debounce marker so next resume\u2192working transition fires
|
|
3805
|
-
rm -f "/
|
|
3871
|
+
rm -f "${runtimeDir}/working-$sid"
|
|
3806
3872
|
|
|
3807
3873
|
bq update --session-id "$sid" --event session.waiting --meta "$(jq -n --arg q "$question" --arg cid "$cid" '{question:$q, claude_id:$cid}')"
|
|
3808
3874
|
|
|
@@ -3823,7 +3889,7 @@ bq notify bertrand "$question" &
|
|
|
3823
3889
|
wait
|
|
3824
3890
|
`;
|
|
3825
3891
|
}
|
|
3826
|
-
function answeredScript(bin) {
|
|
3892
|
+
function answeredScript(bin, _runtimeDir) {
|
|
3827
3893
|
return `#!/usr/bin/env bash
|
|
3828
3894
|
# Hook: PostToolUse AskUserQuestion \u2192 mark session as active
|
|
3829
3895
|
#
|
|
@@ -3877,7 +3943,7 @@ fi
|
|
|
3877
3943
|
wait
|
|
3878
3944
|
`;
|
|
3879
3945
|
}
|
|
3880
|
-
function activeScript(bin) {
|
|
3946
|
+
function activeScript(bin, runtimeDir) {
|
|
3881
3947
|
return `#!/usr/bin/env bash
|
|
3882
3948
|
# Hook: PreToolUse (catch-all) \u2192 flip waiting to active
|
|
3883
3949
|
${quietHelper(bin)}
|
|
@@ -3886,7 +3952,7 @@ sid="\${BERTRAND_SESSION:-}"
|
|
|
3886
3952
|
|
|
3887
3953
|
# Debounce: skip if we already sent session.active within the last 5 seconds.
|
|
3888
3954
|
# This avoids spawning bertrand (~31ms) on every tool call during rapid sequences.
|
|
3889
|
-
marker="/
|
|
3955
|
+
marker="${runtimeDir}/working-$sid"
|
|
3890
3956
|
if [ -f "$marker" ]; then
|
|
3891
3957
|
age=$(( $(date +%s) - $(stat -f%m "$marker" 2>/dev/null || echo 0) ))
|
|
3892
3958
|
[ "$age" -lt 5 ] && exit 0
|
|
@@ -3903,7 +3969,7 @@ cid="\${BERTRAND_CLAUDE_ID:-}"
|
|
|
3903
3969
|
bq update --session-id "$sid" --event session.active --meta "$(jq -n --arg cid "$cid" '{claude_id:$cid}')"
|
|
3904
3970
|
`;
|
|
3905
3971
|
}
|
|
3906
|
-
function permissionWaitScript(bin) {
|
|
3972
|
+
function permissionWaitScript(bin, runtimeDir) {
|
|
3907
3973
|
return `#!/usr/bin/env bash
|
|
3908
3974
|
# Hook: PermissionRequest \u2192 mark pending, emit permission.request
|
|
3909
3975
|
${quietHelper(bin)}
|
|
@@ -3915,7 +3981,7 @@ ${EXTRACT_TOOL}
|
|
|
3915
3981
|
[ "$tool" = "AskUserQuestion" ] && exit 0
|
|
3916
3982
|
|
|
3917
3983
|
# Write marker so PostToolUse knows this was a real permission prompt (not auto-approved)
|
|
3918
|
-
touch "/
|
|
3984
|
+
touch "${runtimeDir}/perm-pending-$sid"
|
|
3919
3985
|
|
|
3920
3986
|
# Extract detail from tool_input via grep (avoid jq for simple fields)
|
|
3921
3987
|
detail=""
|
|
@@ -3933,7 +3999,7 @@ bq notify bertrand "Needs permission: $tool" &
|
|
|
3933
3999
|
wait
|
|
3934
4000
|
`;
|
|
3935
4001
|
}
|
|
3936
|
-
function permissionDoneScript(bin) {
|
|
4002
|
+
function permissionDoneScript(bin, runtimeDir) {
|
|
3937
4003
|
return `#!/usr/bin/env bash
|
|
3938
4004
|
# Hook: PostToolUse (catch-all)
|
|
3939
4005
|
#
|
|
@@ -3958,7 +4024,7 @@ ${EXTRACT_TOOL}
|
|
|
3958
4024
|
# Don't double-log: AskUserQuestion has its own waiting/answered events
|
|
3959
4025
|
[ "$tool" = "AskUserQuestion" ] && exit 0
|
|
3960
4026
|
|
|
3961
|
-
marker="/
|
|
4027
|
+
marker="${runtimeDir}/perm-pending-$sid"
|
|
3962
4028
|
had_marker=0
|
|
3963
4029
|
if [ -f "$marker" ]; then
|
|
3964
4030
|
had_marker=1
|
|
@@ -4017,7 +4083,7 @@ fi
|
|
|
4017
4083
|
wait
|
|
4018
4084
|
`;
|
|
4019
4085
|
}
|
|
4020
|
-
function userPromptScript(bin) {
|
|
4086
|
+
function userPromptScript(bin, _runtimeDir) {
|
|
4021
4087
|
return `#!/usr/bin/env bash
|
|
4022
4088
|
# Hook: UserPromptSubmit \u2192 record user free-text prompt
|
|
4023
4089
|
${quietHelper(bin)}
|
|
@@ -4033,7 +4099,7 @@ meta="$(printf '%s' "$input" | jq --arg cid "$cid" '{prompt: (.prompt // ""), cl
|
|
|
4033
4099
|
bq update --session-id "$sid" --event user.prompt --meta "$meta"
|
|
4034
4100
|
`;
|
|
4035
4101
|
}
|
|
4036
|
-
function doneScript(bin) {
|
|
4102
|
+
function doneScript(bin, _runtimeDir) {
|
|
4037
4103
|
return `#!/usr/bin/env bash
|
|
4038
4104
|
# Hook: Stop \u2192 mark session as paused
|
|
4039
4105
|
${quietHelper(bin)}
|
|
@@ -4075,9 +4141,10 @@ import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync5, chmodSync as
|
|
|
4075
4141
|
import { join as join10 } from "path";
|
|
4076
4142
|
function installHookScripts(bin) {
|
|
4077
4143
|
mkdirSync7(paths.hooks, { recursive: true });
|
|
4144
|
+
mkdirSync7(paths.runtime, { recursive: true });
|
|
4078
4145
|
for (const [filename, scriptFn] of Object.entries(HOOK_SCRIPTS)) {
|
|
4079
4146
|
const filePath = join10(paths.hooks, filename);
|
|
4080
|
-
writeFileSync5(filePath, scriptFn(bin));
|
|
4147
|
+
writeFileSync5(filePath, scriptFn(bin, paths.runtime));
|
|
4081
4148
|
chmodSync2(filePath, 493);
|
|
4082
4149
|
}
|
|
4083
4150
|
console.log(`Installed ${Object.keys(HOOK_SCRIPTS).length} hook scripts to ${paths.hooks}`);
|
|
@@ -4266,6 +4333,7 @@ var init_init = __esm(() => {
|
|
|
4266
4333
|
`);
|
|
4267
4334
|
mkdirSync10(paths.root, { recursive: true });
|
|
4268
4335
|
mkdirSync10(paths.hooks, { recursive: true });
|
|
4336
|
+
mkdirSync10(paths.runtime, { recursive: true });
|
|
4269
4337
|
const active = resolveActiveProject();
|
|
4270
4338
|
runMigrations(active.db);
|
|
4271
4339
|
console.log(` Database: ${active.db}`);
|
|
@@ -4296,6 +4364,40 @@ Error: couldn't locate the bertrand binary on PATH.
|
|
|
4296
4364
|
});
|
|
4297
4365
|
});
|
|
4298
4366
|
|
|
4367
|
+
// src/lib/projects/cli-flag.ts
|
|
4368
|
+
function extractProjectFlag2(args) {
|
|
4369
|
+
const rest = [];
|
|
4370
|
+
let project;
|
|
4371
|
+
for (let i = 0;i < args.length; i++) {
|
|
4372
|
+
const a = args[i];
|
|
4373
|
+
if (a === "--project") {
|
|
4374
|
+
project = args[i + 1];
|
|
4375
|
+
i++;
|
|
4376
|
+
continue;
|
|
4377
|
+
}
|
|
4378
|
+
if (a.startsWith("--project=")) {
|
|
4379
|
+
project = a.slice("--project=".length);
|
|
4380
|
+
continue;
|
|
4381
|
+
}
|
|
4382
|
+
rest.push(a);
|
|
4383
|
+
}
|
|
4384
|
+
return { project, rest };
|
|
4385
|
+
}
|
|
4386
|
+
function applyProjectFlag(slug) {
|
|
4387
|
+
if (!slug)
|
|
4388
|
+
return;
|
|
4389
|
+
if (!projectExists(slug)) {
|
|
4390
|
+
console.error(`Unknown project: ${slug}. Run \`bertrand project list\` to see registered projects.`);
|
|
4391
|
+
process.exit(1);
|
|
4392
|
+
}
|
|
4393
|
+
process.env.BERTRAND_PROJECT = slug;
|
|
4394
|
+
_resetActiveProjectCache();
|
|
4395
|
+
}
|
|
4396
|
+
var init_cli_flag = __esm(() => {
|
|
4397
|
+
init_resolve();
|
|
4398
|
+
init_registry();
|
|
4399
|
+
});
|
|
4400
|
+
|
|
4299
4401
|
// src/cli/commands/list.ts
|
|
4300
4402
|
var exports_list = {};
|
|
4301
4403
|
function buildRows(sessions2) {
|
|
@@ -4311,13 +4413,15 @@ function buildRows(sessions2) {
|
|
|
4311
4413
|
});
|
|
4312
4414
|
}
|
|
4313
4415
|
function renderTable(rows) {
|
|
4416
|
+
const dim = "\x1B[2m";
|
|
4417
|
+
const reset = "\x1B[0m";
|
|
4418
|
+
const project = resolveActiveProject();
|
|
4419
|
+
console.log(`${dim}Project: ${project.slug} (${project.name})${reset}`);
|
|
4314
4420
|
if (rows.length === 0) {
|
|
4315
4421
|
console.log("No sessions found.");
|
|
4316
4422
|
return;
|
|
4317
4423
|
}
|
|
4318
4424
|
const maxName = Math.max(...rows.map((r) => r.name.length), 4);
|
|
4319
|
-
const dim = "\x1B[2m";
|
|
4320
|
-
const reset = "\x1B[0m";
|
|
4321
4425
|
console.log(`${dim}${" "} ${"NAME".padEnd(maxName)} ${"STATUS".padEnd(10)} ${"DURATION".padEnd(8)} ${"CONVOS".padEnd(6)} LAST ACTIVE${reset}`);
|
|
4322
4426
|
for (const row of rows) {
|
|
4323
4427
|
const dot = STATUS_DOTS[row.status] ?? "?";
|
|
@@ -4329,12 +4433,14 @@ function renderTable(rows) {
|
|
|
4329
4433
|
}
|
|
4330
4434
|
}
|
|
4331
4435
|
function renderJson(rows) {
|
|
4436
|
+
const project = resolveActiveProject();
|
|
4332
4437
|
const data = rows.map((r) => ({
|
|
4333
4438
|
name: r.name,
|
|
4334
4439
|
status: r.status,
|
|
4335
4440
|
duration: r.duration,
|
|
4336
4441
|
conversations: r.conversations,
|
|
4337
|
-
updatedAt: r.updatedAt
|
|
4442
|
+
updatedAt: r.updatedAt,
|
|
4443
|
+
project: { slug: project.slug, name: project.name }
|
|
4338
4444
|
}));
|
|
4339
4445
|
console.log(JSON.stringify(data, null, 2));
|
|
4340
4446
|
}
|
|
@@ -4345,6 +4451,8 @@ var init_list = __esm(() => {
|
|
|
4345
4451
|
init_categories();
|
|
4346
4452
|
init_stats();
|
|
4347
4453
|
init_format();
|
|
4454
|
+
init_resolve();
|
|
4455
|
+
init_cli_flag();
|
|
4348
4456
|
STATUS_DOTS = {
|
|
4349
4457
|
active: "\x1B[32m\u25CF\x1B[0m",
|
|
4350
4458
|
waiting: "\x1B[33m\u25CF\x1B[0m",
|
|
@@ -4353,10 +4461,12 @@ var init_list = __esm(() => {
|
|
|
4353
4461
|
};
|
|
4354
4462
|
alias("ls", "list");
|
|
4355
4463
|
register("list", async (args) => {
|
|
4356
|
-
const
|
|
4357
|
-
|
|
4358
|
-
const
|
|
4359
|
-
const
|
|
4464
|
+
const { project: projectSlug, rest: argsWithoutProject } = extractProjectFlag2(args);
|
|
4465
|
+
applyProjectFlag(projectSlug);
|
|
4466
|
+
const isJson = argsWithoutProject.includes("--json");
|
|
4467
|
+
const showAll = argsWithoutProject.includes("--all") || argsWithoutProject.includes("-a");
|
|
4468
|
+
const categoryFlag = argsWithoutProject.indexOf("--category");
|
|
4469
|
+
const categoryPath = categoryFlag !== -1 ? argsWithoutProject[categoryFlag + 1] : undefined;
|
|
4360
4470
|
let sessionRows;
|
|
4361
4471
|
if (categoryPath) {
|
|
4362
4472
|
const category = getCategoryByPath(categoryPath);
|
|
@@ -4616,7 +4726,9 @@ function ansi256(code, text2) {
|
|
|
4616
4726
|
return `\x1B[38;5;${code}m${text2}${RESET}`;
|
|
4617
4727
|
}
|
|
4618
4728
|
function showAllSessions() {
|
|
4729
|
+
const project = resolveActiveProject();
|
|
4619
4730
|
const rows = getAllSessions().sort((a, b) => new Date(b.session.updatedAt).getTime() - new Date(a.session.updatedAt).getTime());
|
|
4731
|
+
console.log(`${DIM}Project: ${project.slug} (${project.name})${RESET}`);
|
|
4620
4732
|
if (rows.length === 0) {
|
|
4621
4733
|
console.log("No sessions.");
|
|
4622
4734
|
return;
|
|
@@ -4741,15 +4853,22 @@ function renderTimingFooter(sessionId) {
|
|
|
4741
4853
|
lines.push(`${DIM}Duration: ${formatDuration(durationS * 1000)} \xB7 Claude: ${formatDuration(claudeWorkS * 1000)} (${activePctVal}%) \xB7 Wait: ${formatDuration(userWaitS * 1000)} (${100 - activePctVal}%)${RESET}`);
|
|
4742
4854
|
return lines;
|
|
4743
4855
|
}
|
|
4856
|
+
function renderBreadcrumb(projectSlug, projectName, sessionName) {
|
|
4857
|
+
const segments = sessionName.split("/").filter(Boolean);
|
|
4858
|
+
const trail = segments.join(` ${DIM}\u203A${RESET} `);
|
|
4859
|
+
return `${DIM}${projectSlug} (${projectName})${RESET} ${DIM}\xB7${RESET} ${BOLD}${trail}${RESET}`;
|
|
4860
|
+
}
|
|
4744
4861
|
function showSessionLog(session, sessionName, isJson) {
|
|
4745
4862
|
const sessionId = session.id;
|
|
4746
4863
|
const rawEvents = getEventsBySession(sessionId);
|
|
4747
4864
|
const enriched = enrichAll(rawEvents);
|
|
4748
4865
|
const compacted = compact(enriched);
|
|
4866
|
+
const project = resolveActiveProject();
|
|
4749
4867
|
if (isJson) {
|
|
4750
4868
|
const stats = getSessionStats(sessionId);
|
|
4751
4869
|
const conversations2 = getConversationsBySession(sessionId);
|
|
4752
4870
|
console.log(JSON.stringify({
|
|
4871
|
+
project: { slug: project.slug, name: project.name },
|
|
4753
4872
|
session: { ...session, name: sessionName },
|
|
4754
4873
|
stats,
|
|
4755
4874
|
conversations: conversations2,
|
|
@@ -4767,6 +4886,8 @@ function showSessionLog(session, sessionName, isJson) {
|
|
|
4767
4886
|
}
|
|
4768
4887
|
const segments = segmentByConversation(compacted);
|
|
4769
4888
|
const allLines = [];
|
|
4889
|
+
allLines.push(renderBreadcrumb(project.slug, project.name, sessionName));
|
|
4890
|
+
allLines.push("");
|
|
4770
4891
|
for (let i = 0;i < segments.length; i++) {
|
|
4771
4892
|
const segLines = renderSegment(segments[i], i, segments.length);
|
|
4772
4893
|
allLines.push(...segLines);
|
|
@@ -4788,6 +4909,8 @@ var init_log = __esm(() => {
|
|
|
4788
4909
|
init_compact();
|
|
4789
4910
|
init_timing();
|
|
4790
4911
|
init_format();
|
|
4912
|
+
init_resolve();
|
|
4913
|
+
init_cli_flag();
|
|
4791
4914
|
STATUS_DOTS2 = {
|
|
4792
4915
|
active: ansi(32, "\u25CF"),
|
|
4793
4916
|
waiting: ansi(33, "\u25CF"),
|
|
@@ -4795,8 +4918,10 @@ var init_log = __esm(() => {
|
|
|
4795
4918
|
archived: `${DIM}\u25CB${RESET}`
|
|
4796
4919
|
};
|
|
4797
4920
|
register("log", async (args) => {
|
|
4798
|
-
const
|
|
4799
|
-
|
|
4921
|
+
const { project: projectSlug, rest: argsWithoutProject } = extractProjectFlag2(args);
|
|
4922
|
+
applyProjectFlag(projectSlug);
|
|
4923
|
+
const isJson = argsWithoutProject.includes("--json");
|
|
4924
|
+
const filteredArgs = argsWithoutProject.filter((a) => !a.startsWith("--"));
|
|
4800
4925
|
const target = filteredArgs[0];
|
|
4801
4926
|
if (!target) {
|
|
4802
4927
|
showAllSessions();
|