bertrand 0.20.1 → 0.22.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 +155 -48
- package/dist/dashboard/assets/{index-mg96b7q-.js → index-SB5vH7Ec.js} +2 -2
- package/dist/dashboard/index.html +1 -1
- package/dist/migrations/0004_dapper_apocalypse.sql +1 -0
- package/dist/migrations/meta/0004_snapshot.json +784 -0
- package/dist/migrations/meta/_journal.json +7 -0
- package/dist/run-screen.js +180 -67
- package/package.json +1 -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
|
};
|
|
@@ -651,6 +653,7 @@ var init_schema = __esm(() => {
|
|
|
651
653
|
enum: ["active", "waiting", "paused", "archived"]
|
|
652
654
|
}).notNull().default("paused"),
|
|
653
655
|
summary: text("summary"),
|
|
656
|
+
rating: integer("rating"),
|
|
654
657
|
pid: integer("pid"),
|
|
655
658
|
startedAt: text("started_at").notNull().default(sql`(datetime('now'))`),
|
|
656
659
|
endedAt: text("ended_at"),
|
|
@@ -3453,10 +3456,8 @@ function installExitHandlers() {
|
|
|
3453
3456
|
const onSignal = (signal) => {
|
|
3454
3457
|
if (isClaudeRunning())
|
|
3455
3458
|
return;
|
|
3456
|
-
process.exit(signal === "
|
|
3459
|
+
process.exit(signal === "SIGHUP" ? 129 : 143);
|
|
3457
3460
|
};
|
|
3458
|
-
process.on("SIGINT", onSignal);
|
|
3459
|
-
process.on("SIGTERM", onSignal);
|
|
3460
3461
|
process.on("SIGHUP", onSignal);
|
|
3461
3462
|
}
|
|
3462
3463
|
async function launch(opts) {
|
|
@@ -3568,6 +3569,7 @@ function finalizeSession(sessionId, conversationId, exitCode) {
|
|
|
3568
3569
|
emitSessionEnded({ sessionId });
|
|
3569
3570
|
computeAndPersist(sessionId);
|
|
3570
3571
|
stopServerIfIdle();
|
|
3572
|
+
triggerBackgroundPush();
|
|
3571
3573
|
}
|
|
3572
3574
|
var liveSession = null, exitHandlersInstalled = false;
|
|
3573
3575
|
var init_session = __esm(() => {
|
|
@@ -3582,27 +3584,56 @@ var init_session = __esm(() => {
|
|
|
3582
3584
|
init_spawn_context();
|
|
3583
3585
|
init_timing();
|
|
3584
3586
|
init_server_lifecycle();
|
|
3587
|
+
init_trigger();
|
|
3585
3588
|
});
|
|
3586
3589
|
|
|
3587
3590
|
// src/tui/app.tsx
|
|
3588
3591
|
import { spawn as spawn4 } from "child_process";
|
|
3589
3592
|
import { existsSync as existsSync8, readFileSync as readFileSync8, unlinkSync as unlinkSync3 } from "fs";
|
|
3593
|
+
import { tmpdir } from "os";
|
|
3590
3594
|
import { join as join9 } from "path";
|
|
3591
3595
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
3592
3596
|
async function runScreen(screen, ...args) {
|
|
3593
|
-
const tmpFile =
|
|
3597
|
+
const tmpFile = join9(tmpdir(), `bertrand-tui-${randomUUID2()}.json`);
|
|
3598
|
+
if (process.env.BERTRAND_DEBUG_TUI) {
|
|
3599
|
+
try {
|
|
3600
|
+
const { appendFileSync } = await import("fs");
|
|
3601
|
+
appendFileSync(process.env.BERTRAND_DEBUG_TUI, `--- parent runScreen("${screen}") at ${Date.now()} entry=${SCREEN_ENTRY}
|
|
3602
|
+
`);
|
|
3603
|
+
} catch {}
|
|
3604
|
+
}
|
|
3594
3605
|
const child = spawn4("bun", ["run", SCREEN_ENTRY, screen, tmpFile, ...args], {
|
|
3595
|
-
stdio: "inherit"
|
|
3596
|
-
|
|
3597
|
-
const exitCode = await new Promise((resolve) => {
|
|
3598
|
-
child.on("exit", (code) => resolve(code ?? 1));
|
|
3606
|
+
stdio: "inherit",
|
|
3607
|
+
env: process.env
|
|
3599
3608
|
});
|
|
3600
|
-
|
|
3601
|
-
|
|
3609
|
+
const noopSignal = () => {};
|
|
3610
|
+
process.on("SIGINT", noopSignal);
|
|
3611
|
+
process.on("SIGTERM", noopSignal);
|
|
3612
|
+
let spawnError = null;
|
|
3613
|
+
try {
|
|
3614
|
+
const { code, signal } = await new Promise((resolve) => {
|
|
3615
|
+
child.on("error", (err) => {
|
|
3616
|
+
spawnError = err;
|
|
3617
|
+
resolve({ code: 1, signal: null });
|
|
3618
|
+
});
|
|
3619
|
+
child.on("exit", (c, s) => resolve({ code: c, signal: s }));
|
|
3620
|
+
});
|
|
3621
|
+
if (spawnError) {
|
|
3622
|
+
throw new Error(`Failed to launch TUI screen "${screen}": ${spawnError.message}`);
|
|
3623
|
+
}
|
|
3624
|
+
if (!existsSync8(tmpFile)) {
|
|
3625
|
+
const detail = signal ? `killed by ${signal}` : `exited with code ${code ?? "?"} without writing result`;
|
|
3626
|
+
throw new Error(`TUI screen "${screen}" ${detail}`);
|
|
3627
|
+
}
|
|
3628
|
+
return JSON.parse(readFileSync8(tmpFile, "utf-8"));
|
|
3629
|
+
} finally {
|
|
3630
|
+
process.removeListener("SIGINT", noopSignal);
|
|
3631
|
+
process.removeListener("SIGTERM", noopSignal);
|
|
3632
|
+
try {
|
|
3633
|
+
if (existsSync8(tmpFile))
|
|
3634
|
+
unlinkSync3(tmpFile);
|
|
3635
|
+
} catch {}
|
|
3602
3636
|
}
|
|
3603
|
-
const result = JSON.parse(readFileSync8(tmpFile, "utf-8"));
|
|
3604
|
-
unlinkSync3(tmpFile);
|
|
3605
|
-
return result;
|
|
3606
3637
|
}
|
|
3607
3638
|
async function startLaunchTui() {
|
|
3608
3639
|
return runScreen("launch");
|
|
@@ -3615,9 +3646,6 @@ async function startExitTui(sessionId) {
|
|
|
3615
3646
|
}
|
|
3616
3647
|
async function startResumeTui(sessionId) {
|
|
3617
3648
|
const conversations2 = getConversationsBySession(sessionId);
|
|
3618
|
-
if (conversations2.length === 1) {
|
|
3619
|
-
return { type: "conversation", conversationId: conversations2[0].id };
|
|
3620
|
-
}
|
|
3621
3649
|
if (conversations2.length === 0) {
|
|
3622
3650
|
return { type: "new" };
|
|
3623
3651
|
}
|
|
@@ -3739,14 +3767,18 @@ function parseSessionName(input) {
|
|
|
3739
3767
|
throw new Error(`Session name must include at least one category: "category/session" (got "${trimmed}")`);
|
|
3740
3768
|
}
|
|
3741
3769
|
for (const segment of segments) {
|
|
3742
|
-
if (
|
|
3770
|
+
if (!SEGMENT_PATTERN.test(segment)) {
|
|
3743
3771
|
throw new Error(`Invalid segment "${segment}": must start with alphanumeric and contain only letters, digits, dots, underscores, or dashes`);
|
|
3744
3772
|
}
|
|
3745
3773
|
}
|
|
3746
|
-
const
|
|
3747
|
-
const
|
|
3774
|
+
const categoryPath = segments[0];
|
|
3775
|
+
const slug = segments.slice(1).join("/");
|
|
3748
3776
|
return { categoryPath, slug };
|
|
3749
3777
|
}
|
|
3778
|
+
var SEGMENT_PATTERN;
|
|
3779
|
+
var init_parse_session_name = __esm(() => {
|
|
3780
|
+
SEGMENT_PATTERN = /^[a-z0-9][a-z0-9._-]*$/i;
|
|
3781
|
+
});
|
|
3750
3782
|
|
|
3751
3783
|
// src/engine/recovery.ts
|
|
3752
3784
|
function isProcessAlive2(pid) {
|
|
@@ -3782,21 +3814,34 @@ var init_recovery = __esm(() => {
|
|
|
3782
3814
|
|
|
3783
3815
|
// src/cli/commands/launch.ts
|
|
3784
3816
|
var exports_launch = {};
|
|
3817
|
+
function reportFatal(err) {
|
|
3818
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3819
|
+
console.error(`bertrand: ${message}`);
|
|
3820
|
+
if (process.env.BERTRAND_DEBUG && err instanceof Error && err.stack) {
|
|
3821
|
+
console.error(err.stack);
|
|
3822
|
+
}
|
|
3823
|
+
process.exit(1);
|
|
3824
|
+
}
|
|
3785
3825
|
var init_launch = __esm(() => {
|
|
3786
3826
|
init_router();
|
|
3787
3827
|
init_app();
|
|
3828
|
+
init_parse_session_name();
|
|
3788
3829
|
init_session();
|
|
3789
3830
|
init_recovery();
|
|
3790
3831
|
register("launch", async (args) => {
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3832
|
+
try {
|
|
3833
|
+
recoverStaleSessions();
|
|
3834
|
+
const sessionName = args[0];
|
|
3835
|
+
if (sessionName) {
|
|
3836
|
+
const { categoryPath, slug } = parseSessionName(sessionName);
|
|
3837
|
+
const sessionId = await launch({ categoryPath, slug });
|
|
3838
|
+
await runSessionLoop(sessionId);
|
|
3839
|
+
return;
|
|
3840
|
+
}
|
|
3841
|
+
await startTui();
|
|
3842
|
+
} catch (err) {
|
|
3843
|
+
reportFatal(err);
|
|
3798
3844
|
}
|
|
3799
|
-
await startTui();
|
|
3800
3845
|
});
|
|
3801
3846
|
});
|
|
3802
3847
|
|
|
@@ -3804,7 +3849,7 @@ var init_launch = __esm(() => {
|
|
|
3804
3849
|
function quietHelper(bin) {
|
|
3805
3850
|
return `bq() { ${bin} "$@" 2>/dev/null || true; }`;
|
|
3806
3851
|
}
|
|
3807
|
-
function waitingScript(bin) {
|
|
3852
|
+
function waitingScript(bin, runtimeDir) {
|
|
3808
3853
|
return `#!/usr/bin/env bash
|
|
3809
3854
|
# Hook: PreToolUse AskUserQuestion \u2192 enforce multiSelect, mark session as waiting
|
|
3810
3855
|
${quietHelper(bin)}
|
|
@@ -3829,7 +3874,7 @@ question="$(printf '%s' "$input" | grep -o '"question":"[^"]*"' | head -1 | cut
|
|
|
3829
3874
|
[ -z "$question" ] && question="Waiting for input"
|
|
3830
3875
|
|
|
3831
3876
|
# Clear working debounce marker so next resume\u2192working transition fires
|
|
3832
|
-
rm -f "/
|
|
3877
|
+
rm -f "${runtimeDir}/working-$sid"
|
|
3833
3878
|
|
|
3834
3879
|
bq update --session-id "$sid" --event session.waiting --meta "$(jq -n --arg q "$question" --arg cid "$cid" '{question:$q, claude_id:$cid}')"
|
|
3835
3880
|
|
|
@@ -3850,7 +3895,7 @@ bq notify bertrand "$question" &
|
|
|
3850
3895
|
wait
|
|
3851
3896
|
`;
|
|
3852
3897
|
}
|
|
3853
|
-
function answeredScript(bin) {
|
|
3898
|
+
function answeredScript(bin, _runtimeDir) {
|
|
3854
3899
|
return `#!/usr/bin/env bash
|
|
3855
3900
|
# Hook: PostToolUse AskUserQuestion \u2192 mark session as active
|
|
3856
3901
|
#
|
|
@@ -3904,7 +3949,7 @@ fi
|
|
|
3904
3949
|
wait
|
|
3905
3950
|
`;
|
|
3906
3951
|
}
|
|
3907
|
-
function activeScript(bin) {
|
|
3952
|
+
function activeScript(bin, runtimeDir) {
|
|
3908
3953
|
return `#!/usr/bin/env bash
|
|
3909
3954
|
# Hook: PreToolUse (catch-all) \u2192 flip waiting to active
|
|
3910
3955
|
${quietHelper(bin)}
|
|
@@ -3913,7 +3958,7 @@ sid="\${BERTRAND_SESSION:-}"
|
|
|
3913
3958
|
|
|
3914
3959
|
# Debounce: skip if we already sent session.active within the last 5 seconds.
|
|
3915
3960
|
# This avoids spawning bertrand (~31ms) on every tool call during rapid sequences.
|
|
3916
|
-
marker="/
|
|
3961
|
+
marker="${runtimeDir}/working-$sid"
|
|
3917
3962
|
if [ -f "$marker" ]; then
|
|
3918
3963
|
age=$(( $(date +%s) - $(stat -f%m "$marker" 2>/dev/null || echo 0) ))
|
|
3919
3964
|
[ "$age" -lt 5 ] && exit 0
|
|
@@ -3930,7 +3975,7 @@ cid="\${BERTRAND_CLAUDE_ID:-}"
|
|
|
3930
3975
|
bq update --session-id "$sid" --event session.active --meta "$(jq -n --arg cid "$cid" '{claude_id:$cid}')"
|
|
3931
3976
|
`;
|
|
3932
3977
|
}
|
|
3933
|
-
function permissionWaitScript(bin) {
|
|
3978
|
+
function permissionWaitScript(bin, runtimeDir) {
|
|
3934
3979
|
return `#!/usr/bin/env bash
|
|
3935
3980
|
# Hook: PermissionRequest \u2192 mark pending, emit permission.request
|
|
3936
3981
|
${quietHelper(bin)}
|
|
@@ -3942,7 +3987,7 @@ ${EXTRACT_TOOL}
|
|
|
3942
3987
|
[ "$tool" = "AskUserQuestion" ] && exit 0
|
|
3943
3988
|
|
|
3944
3989
|
# Write marker so PostToolUse knows this was a real permission prompt (not auto-approved)
|
|
3945
|
-
touch "/
|
|
3990
|
+
touch "${runtimeDir}/perm-pending-$sid"
|
|
3946
3991
|
|
|
3947
3992
|
# Extract detail from tool_input via grep (avoid jq for simple fields)
|
|
3948
3993
|
detail=""
|
|
@@ -3960,7 +4005,7 @@ bq notify bertrand "Needs permission: $tool" &
|
|
|
3960
4005
|
wait
|
|
3961
4006
|
`;
|
|
3962
4007
|
}
|
|
3963
|
-
function permissionDoneScript(bin) {
|
|
4008
|
+
function permissionDoneScript(bin, runtimeDir) {
|
|
3964
4009
|
return `#!/usr/bin/env bash
|
|
3965
4010
|
# Hook: PostToolUse (catch-all)
|
|
3966
4011
|
#
|
|
@@ -3985,7 +4030,7 @@ ${EXTRACT_TOOL}
|
|
|
3985
4030
|
# Don't double-log: AskUserQuestion has its own waiting/answered events
|
|
3986
4031
|
[ "$tool" = "AskUserQuestion" ] && exit 0
|
|
3987
4032
|
|
|
3988
|
-
marker="/
|
|
4033
|
+
marker="${runtimeDir}/perm-pending-$sid"
|
|
3989
4034
|
had_marker=0
|
|
3990
4035
|
if [ -f "$marker" ]; then
|
|
3991
4036
|
had_marker=1
|
|
@@ -4044,7 +4089,7 @@ fi
|
|
|
4044
4089
|
wait
|
|
4045
4090
|
`;
|
|
4046
4091
|
}
|
|
4047
|
-
function userPromptScript(bin) {
|
|
4092
|
+
function userPromptScript(bin, _runtimeDir) {
|
|
4048
4093
|
return `#!/usr/bin/env bash
|
|
4049
4094
|
# Hook: UserPromptSubmit \u2192 record user free-text prompt
|
|
4050
4095
|
${quietHelper(bin)}
|
|
@@ -4060,7 +4105,7 @@ meta="$(printf '%s' "$input" | jq --arg cid "$cid" '{prompt: (.prompt // ""), cl
|
|
|
4060
4105
|
bq update --session-id "$sid" --event user.prompt --meta "$meta"
|
|
4061
4106
|
`;
|
|
4062
4107
|
}
|
|
4063
|
-
function doneScript(bin) {
|
|
4108
|
+
function doneScript(bin, _runtimeDir) {
|
|
4064
4109
|
return `#!/usr/bin/env bash
|
|
4065
4110
|
# Hook: Stop \u2192 mark session as paused
|
|
4066
4111
|
${quietHelper(bin)}
|
|
@@ -4102,9 +4147,10 @@ import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync5, chmodSync as
|
|
|
4102
4147
|
import { join as join10 } from "path";
|
|
4103
4148
|
function installHookScripts(bin) {
|
|
4104
4149
|
mkdirSync7(paths.hooks, { recursive: true });
|
|
4150
|
+
mkdirSync7(paths.runtime, { recursive: true });
|
|
4105
4151
|
for (const [filename, scriptFn] of Object.entries(HOOK_SCRIPTS)) {
|
|
4106
4152
|
const filePath = join10(paths.hooks, filename);
|
|
4107
|
-
writeFileSync5(filePath, scriptFn(bin));
|
|
4153
|
+
writeFileSync5(filePath, scriptFn(bin, paths.runtime));
|
|
4108
4154
|
chmodSync2(filePath, 493);
|
|
4109
4155
|
}
|
|
4110
4156
|
console.log(`Installed ${Object.keys(HOOK_SCRIPTS).length} hook scripts to ${paths.hooks}`);
|
|
@@ -4293,6 +4339,7 @@ var init_init = __esm(() => {
|
|
|
4293
4339
|
`);
|
|
4294
4340
|
mkdirSync10(paths.root, { recursive: true });
|
|
4295
4341
|
mkdirSync10(paths.hooks, { recursive: true });
|
|
4342
|
+
mkdirSync10(paths.runtime, { recursive: true });
|
|
4296
4343
|
const active = resolveActiveProject();
|
|
4297
4344
|
runMigrations(active.db);
|
|
4298
4345
|
console.log(` Database: ${active.db}`);
|
|
@@ -4323,6 +4370,40 @@ Error: couldn't locate the bertrand binary on PATH.
|
|
|
4323
4370
|
});
|
|
4324
4371
|
});
|
|
4325
4372
|
|
|
4373
|
+
// src/lib/projects/cli-flag.ts
|
|
4374
|
+
function extractProjectFlag2(args) {
|
|
4375
|
+
const rest = [];
|
|
4376
|
+
let project;
|
|
4377
|
+
for (let i = 0;i < args.length; i++) {
|
|
4378
|
+
const a = args[i];
|
|
4379
|
+
if (a === "--project") {
|
|
4380
|
+
project = args[i + 1];
|
|
4381
|
+
i++;
|
|
4382
|
+
continue;
|
|
4383
|
+
}
|
|
4384
|
+
if (a.startsWith("--project=")) {
|
|
4385
|
+
project = a.slice("--project=".length);
|
|
4386
|
+
continue;
|
|
4387
|
+
}
|
|
4388
|
+
rest.push(a);
|
|
4389
|
+
}
|
|
4390
|
+
return { project, rest };
|
|
4391
|
+
}
|
|
4392
|
+
function applyProjectFlag(slug) {
|
|
4393
|
+
if (!slug)
|
|
4394
|
+
return;
|
|
4395
|
+
if (!projectExists(slug)) {
|
|
4396
|
+
console.error(`Unknown project: ${slug}. Run \`bertrand project list\` to see registered projects.`);
|
|
4397
|
+
process.exit(1);
|
|
4398
|
+
}
|
|
4399
|
+
process.env.BERTRAND_PROJECT = slug;
|
|
4400
|
+
_resetActiveProjectCache();
|
|
4401
|
+
}
|
|
4402
|
+
var init_cli_flag = __esm(() => {
|
|
4403
|
+
init_resolve();
|
|
4404
|
+
init_registry();
|
|
4405
|
+
});
|
|
4406
|
+
|
|
4326
4407
|
// src/cli/commands/list.ts
|
|
4327
4408
|
var exports_list = {};
|
|
4328
4409
|
function buildRows(sessions2) {
|
|
@@ -4338,13 +4419,15 @@ function buildRows(sessions2) {
|
|
|
4338
4419
|
});
|
|
4339
4420
|
}
|
|
4340
4421
|
function renderTable(rows) {
|
|
4422
|
+
const dim = "\x1B[2m";
|
|
4423
|
+
const reset = "\x1B[0m";
|
|
4424
|
+
const project = resolveActiveProject();
|
|
4425
|
+
console.log(`${dim}Project: ${project.slug} (${project.name})${reset}`);
|
|
4341
4426
|
if (rows.length === 0) {
|
|
4342
4427
|
console.log("No sessions found.");
|
|
4343
4428
|
return;
|
|
4344
4429
|
}
|
|
4345
4430
|
const maxName = Math.max(...rows.map((r) => r.name.length), 4);
|
|
4346
|
-
const dim = "\x1B[2m";
|
|
4347
|
-
const reset = "\x1B[0m";
|
|
4348
4431
|
console.log(`${dim}${" "} ${"NAME".padEnd(maxName)} ${"STATUS".padEnd(10)} ${"DURATION".padEnd(8)} ${"CONVOS".padEnd(6)} LAST ACTIVE${reset}`);
|
|
4349
4432
|
for (const row of rows) {
|
|
4350
4433
|
const dot = STATUS_DOTS[row.status] ?? "?";
|
|
@@ -4356,12 +4439,14 @@ function renderTable(rows) {
|
|
|
4356
4439
|
}
|
|
4357
4440
|
}
|
|
4358
4441
|
function renderJson(rows) {
|
|
4442
|
+
const project = resolveActiveProject();
|
|
4359
4443
|
const data = rows.map((r) => ({
|
|
4360
4444
|
name: r.name,
|
|
4361
4445
|
status: r.status,
|
|
4362
4446
|
duration: r.duration,
|
|
4363
4447
|
conversations: r.conversations,
|
|
4364
|
-
updatedAt: r.updatedAt
|
|
4448
|
+
updatedAt: r.updatedAt,
|
|
4449
|
+
project: { slug: project.slug, name: project.name }
|
|
4365
4450
|
}));
|
|
4366
4451
|
console.log(JSON.stringify(data, null, 2));
|
|
4367
4452
|
}
|
|
@@ -4372,6 +4457,8 @@ var init_list = __esm(() => {
|
|
|
4372
4457
|
init_categories();
|
|
4373
4458
|
init_stats();
|
|
4374
4459
|
init_format();
|
|
4460
|
+
init_resolve();
|
|
4461
|
+
init_cli_flag();
|
|
4375
4462
|
STATUS_DOTS = {
|
|
4376
4463
|
active: "\x1B[32m\u25CF\x1B[0m",
|
|
4377
4464
|
waiting: "\x1B[33m\u25CF\x1B[0m",
|
|
@@ -4380,10 +4467,12 @@ var init_list = __esm(() => {
|
|
|
4380
4467
|
};
|
|
4381
4468
|
alias("ls", "list");
|
|
4382
4469
|
register("list", async (args) => {
|
|
4383
|
-
const
|
|
4384
|
-
|
|
4385
|
-
const
|
|
4386
|
-
const
|
|
4470
|
+
const { project: projectSlug, rest: argsWithoutProject } = extractProjectFlag2(args);
|
|
4471
|
+
applyProjectFlag(projectSlug);
|
|
4472
|
+
const isJson = argsWithoutProject.includes("--json");
|
|
4473
|
+
const showAll = argsWithoutProject.includes("--all") || argsWithoutProject.includes("-a");
|
|
4474
|
+
const categoryFlag = argsWithoutProject.indexOf("--category");
|
|
4475
|
+
const categoryPath = categoryFlag !== -1 ? argsWithoutProject[categoryFlag + 1] : undefined;
|
|
4387
4476
|
let sessionRows;
|
|
4388
4477
|
if (categoryPath) {
|
|
4389
4478
|
const category = getCategoryByPath(categoryPath);
|
|
@@ -4643,7 +4732,9 @@ function ansi256(code, text2) {
|
|
|
4643
4732
|
return `\x1B[38;5;${code}m${text2}${RESET}`;
|
|
4644
4733
|
}
|
|
4645
4734
|
function showAllSessions() {
|
|
4735
|
+
const project = resolveActiveProject();
|
|
4646
4736
|
const rows = getAllSessions().sort((a, b) => new Date(b.session.updatedAt).getTime() - new Date(a.session.updatedAt).getTime());
|
|
4737
|
+
console.log(`${DIM}Project: ${project.slug} (${project.name})${RESET}`);
|
|
4647
4738
|
if (rows.length === 0) {
|
|
4648
4739
|
console.log("No sessions.");
|
|
4649
4740
|
return;
|
|
@@ -4768,15 +4859,22 @@ function renderTimingFooter(sessionId) {
|
|
|
4768
4859
|
lines.push(`${DIM}Duration: ${formatDuration(durationS * 1000)} \xB7 Claude: ${formatDuration(claudeWorkS * 1000)} (${activePctVal}%) \xB7 Wait: ${formatDuration(userWaitS * 1000)} (${100 - activePctVal}%)${RESET}`);
|
|
4769
4860
|
return lines;
|
|
4770
4861
|
}
|
|
4862
|
+
function renderBreadcrumb(projectSlug, projectName, sessionName) {
|
|
4863
|
+
const segments = sessionName.split("/").filter(Boolean);
|
|
4864
|
+
const trail = segments.join(` ${DIM}\u203A${RESET} `);
|
|
4865
|
+
return `${DIM}${projectSlug} (${projectName})${RESET} ${DIM}\xB7${RESET} ${BOLD}${trail}${RESET}`;
|
|
4866
|
+
}
|
|
4771
4867
|
function showSessionLog(session, sessionName, isJson) {
|
|
4772
4868
|
const sessionId = session.id;
|
|
4773
4869
|
const rawEvents = getEventsBySession(sessionId);
|
|
4774
4870
|
const enriched = enrichAll(rawEvents);
|
|
4775
4871
|
const compacted = compact(enriched);
|
|
4872
|
+
const project = resolveActiveProject();
|
|
4776
4873
|
if (isJson) {
|
|
4777
4874
|
const stats = getSessionStats(sessionId);
|
|
4778
4875
|
const conversations2 = getConversationsBySession(sessionId);
|
|
4779
4876
|
console.log(JSON.stringify({
|
|
4877
|
+
project: { slug: project.slug, name: project.name },
|
|
4780
4878
|
session: { ...session, name: sessionName },
|
|
4781
4879
|
stats,
|
|
4782
4880
|
conversations: conversations2,
|
|
@@ -4794,6 +4892,8 @@ function showSessionLog(session, sessionName, isJson) {
|
|
|
4794
4892
|
}
|
|
4795
4893
|
const segments = segmentByConversation(compacted);
|
|
4796
4894
|
const allLines = [];
|
|
4895
|
+
allLines.push(renderBreadcrumb(project.slug, project.name, sessionName));
|
|
4896
|
+
allLines.push("");
|
|
4797
4897
|
for (let i = 0;i < segments.length; i++) {
|
|
4798
4898
|
const segLines = renderSegment(segments[i], i, segments.length);
|
|
4799
4899
|
allLines.push(...segLines);
|
|
@@ -4814,7 +4914,10 @@ var init_log = __esm(() => {
|
|
|
4814
4914
|
init_catalog();
|
|
4815
4915
|
init_compact();
|
|
4816
4916
|
init_timing();
|
|
4917
|
+
init_parse_session_name();
|
|
4817
4918
|
init_format();
|
|
4919
|
+
init_resolve();
|
|
4920
|
+
init_cli_flag();
|
|
4818
4921
|
STATUS_DOTS2 = {
|
|
4819
4922
|
active: ansi(32, "\u25CF"),
|
|
4820
4923
|
waiting: ansi(33, "\u25CF"),
|
|
@@ -4822,8 +4925,10 @@ var init_log = __esm(() => {
|
|
|
4822
4925
|
archived: `${DIM}\u25CB${RESET}`
|
|
4823
4926
|
};
|
|
4824
4927
|
register("log", async (args) => {
|
|
4825
|
-
const
|
|
4826
|
-
|
|
4928
|
+
const { project: projectSlug, rest: argsWithoutProject } = extractProjectFlag2(args);
|
|
4929
|
+
applyProjectFlag(projectSlug);
|
|
4930
|
+
const isJson = argsWithoutProject.includes("--json");
|
|
4931
|
+
const filteredArgs = argsWithoutProject.filter((a) => !a.startsWith("--"));
|
|
4827
4932
|
const target = filteredArgs[0];
|
|
4828
4933
|
if (!target) {
|
|
4829
4934
|
showAllSessions();
|
|
@@ -4968,6 +5073,7 @@ var init_stats2 = __esm(() => {
|
|
|
4968
5073
|
init_events();
|
|
4969
5074
|
init_timing();
|
|
4970
5075
|
init_format();
|
|
5076
|
+
init_parse_session_name();
|
|
4971
5077
|
ACTIVE_STATUSES2 = ["active", "waiting"];
|
|
4972
5078
|
register("stats", async (args) => {
|
|
4973
5079
|
const isJson = args.includes("--json");
|
|
@@ -5045,6 +5151,7 @@ var init_archive = __esm(() => {
|
|
|
5045
5151
|
init_router();
|
|
5046
5152
|
init_sessions();
|
|
5047
5153
|
init_categories();
|
|
5154
|
+
init_parse_session_name();
|
|
5048
5155
|
init_session_archive();
|
|
5049
5156
|
register("archive", async (args) => {
|
|
5050
5157
|
const isUndo = args.includes("--undo");
|