@staff0rd/assist 0.196.1 → 0.197.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 +1 -0
- package/dist/index.js +341 -140
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -195,6 +195,7 @@ After installation, the `assist` command will be available globally. You can als
|
|
|
195
195
|
- `assist voice logs [-n <count>]` - Show recent voice daemon log entries
|
|
196
196
|
- `assist sessions` - Start the sessions web dashboard (same as `sessions web`)
|
|
197
197
|
- `assist sessions web [-p, --port <number>]` - Start a web dashboard for Claude Code sessions with xterm.js terminals (default port 3100)
|
|
198
|
+
- `assist sessions summarise [-f, --force] [-n, --limit <count>]` - Generate one-line summaries for unsummarised Claude sessions (force re-generates all; limit caps how many to process)
|
|
198
199
|
- `assist next` - Alias for `backlog next -w`
|
|
199
200
|
- `assist draft` (alias: `feat`) - Launch Claude in `/draft` mode, chain into next on `/next` signal
|
|
200
201
|
- `assist bug` - Launch Claude in `/bug` mode, chain into next on `/next` signal
|
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { Command } from "commander";
|
|
|
6
6
|
// package.json
|
|
7
7
|
var package_default = {
|
|
8
8
|
name: "@staff0rd/assist",
|
|
9
|
-
version: "0.
|
|
9
|
+
version: "0.197.0",
|
|
10
10
|
type: "module",
|
|
11
11
|
main: "dist/index.js",
|
|
12
12
|
bin: {
|
|
@@ -12590,11 +12590,319 @@ function screenshot(processName) {
|
|
|
12590
12590
|
}
|
|
12591
12591
|
}
|
|
12592
12592
|
|
|
12593
|
+
// src/commands/sessions/summarise/index.ts
|
|
12594
|
+
import * as fs27 from "fs";
|
|
12595
|
+
import chalk134 from "chalk";
|
|
12596
|
+
|
|
12597
|
+
// src/commands/sessions/web/discoverSessions.ts
|
|
12598
|
+
import * as fs24 from "fs";
|
|
12599
|
+
import * as os from "os";
|
|
12600
|
+
import * as path47 from "path";
|
|
12601
|
+
|
|
12602
|
+
// src/commands/sessions/web/parseSessionFile.ts
|
|
12603
|
+
import * as fs23 from "fs";
|
|
12604
|
+
import * as path46 from "path";
|
|
12605
|
+
|
|
12606
|
+
// src/commands/sessions/web/extractSessionMeta.ts
|
|
12607
|
+
function extractSessionMeta(lines) {
|
|
12608
|
+
let sessionId = "";
|
|
12609
|
+
let cwd = "";
|
|
12610
|
+
let timestamp = "";
|
|
12611
|
+
let name = "";
|
|
12612
|
+
for (const line of lines) {
|
|
12613
|
+
const entry = safeParse(line);
|
|
12614
|
+
if (!entry) continue;
|
|
12615
|
+
sessionId ||= typeof entry.sessionId === "string" ? entry.sessionId : "";
|
|
12616
|
+
timestamp ||= typeof entry.timestamp === "string" ? entry.timestamp : "";
|
|
12617
|
+
cwd ||= typeof entry.cwd === "string" ? entry.cwd : "";
|
|
12618
|
+
if (entry.type === "user" && !entry.isMeta) {
|
|
12619
|
+
name = extractName(entry);
|
|
12620
|
+
break;
|
|
12621
|
+
}
|
|
12622
|
+
}
|
|
12623
|
+
return { sessionId, cwd, timestamp, name };
|
|
12624
|
+
}
|
|
12625
|
+
function safeParse(line) {
|
|
12626
|
+
try {
|
|
12627
|
+
return JSON.parse(line);
|
|
12628
|
+
} catch {
|
|
12629
|
+
return null;
|
|
12630
|
+
}
|
|
12631
|
+
}
|
|
12632
|
+
function extractName(entry) {
|
|
12633
|
+
const msg = entry.message;
|
|
12634
|
+
const content = msg?.content;
|
|
12635
|
+
const text = typeof content === "string" ? content : Array.isArray(content) ? content.find((c) => c.type === "text")?.text ?? "" : "";
|
|
12636
|
+
return text.replace(/<command-[^>]*>[^<]*<\/command-[^>]*>/g, "").trim().slice(0, 80);
|
|
12637
|
+
}
|
|
12638
|
+
|
|
12639
|
+
// src/commands/sessions/web/parseSessionFile.ts
|
|
12640
|
+
async function parseSessionFile(filePath) {
|
|
12641
|
+
let handle;
|
|
12642
|
+
try {
|
|
12643
|
+
handle = await fs23.promises.open(filePath, "r");
|
|
12644
|
+
const buf = Buffer.alloc(16384);
|
|
12645
|
+
const { bytesRead } = await handle.read(buf, 0, buf.length, 0);
|
|
12646
|
+
const lines = buf.toString("utf8", 0, bytesRead).split("\n").filter(Boolean);
|
|
12647
|
+
const meta = extractSessionMeta(lines);
|
|
12648
|
+
if (!meta.sessionId) return null;
|
|
12649
|
+
const timestamp = meta.timestamp || (await fs23.promises.stat(filePath)).mtime.toISOString();
|
|
12650
|
+
const project = meta.cwd ? path46.basename(meta.cwd) : dirNameToProject(filePath);
|
|
12651
|
+
return {
|
|
12652
|
+
sessionId: meta.sessionId,
|
|
12653
|
+
name: meta.name || `Session ${meta.sessionId.slice(0, 8)}`,
|
|
12654
|
+
project,
|
|
12655
|
+
cwd: meta.cwd,
|
|
12656
|
+
timestamp
|
|
12657
|
+
};
|
|
12658
|
+
} catch {
|
|
12659
|
+
return null;
|
|
12660
|
+
} finally {
|
|
12661
|
+
await handle?.close();
|
|
12662
|
+
}
|
|
12663
|
+
}
|
|
12664
|
+
function dirNameToProject(filePath) {
|
|
12665
|
+
const dirName = path46.basename(path46.dirname(filePath));
|
|
12666
|
+
const parts = dirName.split("--");
|
|
12667
|
+
return parts[parts.length - 1].replace(/-/g, "/");
|
|
12668
|
+
}
|
|
12669
|
+
|
|
12670
|
+
// src/commands/sessions/web/discoverSessions.ts
|
|
12671
|
+
async function discoverSessionJsonlPaths() {
|
|
12672
|
+
const projectsDir = path47.join(os.homedir(), ".claude", "projects");
|
|
12673
|
+
let projectDirs;
|
|
12674
|
+
try {
|
|
12675
|
+
projectDirs = await fs24.promises.readdir(projectsDir);
|
|
12676
|
+
} catch {
|
|
12677
|
+
return [];
|
|
12678
|
+
}
|
|
12679
|
+
const paths = [];
|
|
12680
|
+
await Promise.all(
|
|
12681
|
+
projectDirs.map(async (dirName) => {
|
|
12682
|
+
const dirPath = path47.join(projectsDir, dirName);
|
|
12683
|
+
let entries;
|
|
12684
|
+
try {
|
|
12685
|
+
entries = await fs24.promises.readdir(dirPath);
|
|
12686
|
+
} catch {
|
|
12687
|
+
return;
|
|
12688
|
+
}
|
|
12689
|
+
const jsonlFiles = entries.filter((e) => e.endsWith(".jsonl"));
|
|
12690
|
+
for (const file of jsonlFiles) {
|
|
12691
|
+
paths.push(path47.join(dirPath, file));
|
|
12692
|
+
}
|
|
12693
|
+
})
|
|
12694
|
+
);
|
|
12695
|
+
return paths;
|
|
12696
|
+
}
|
|
12697
|
+
async function discoverSessions() {
|
|
12698
|
+
const paths = await discoverSessionJsonlPaths();
|
|
12699
|
+
const sessions = [];
|
|
12700
|
+
await Promise.all(
|
|
12701
|
+
paths.map(async (filePath) => {
|
|
12702
|
+
const session = await parseSessionFile(filePath);
|
|
12703
|
+
if (session) sessions.push(session);
|
|
12704
|
+
})
|
|
12705
|
+
);
|
|
12706
|
+
sessions.sort(
|
|
12707
|
+
(a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
|
|
12708
|
+
);
|
|
12709
|
+
return sessions;
|
|
12710
|
+
}
|
|
12711
|
+
|
|
12712
|
+
// src/commands/sessions/summarise/shared.ts
|
|
12713
|
+
import * as fs25 from "fs";
|
|
12714
|
+
function writeSummary(jsonlPath, summary) {
|
|
12715
|
+
fs25.writeFileSync(summaryPathFor(jsonlPath), `${summary.trim()}
|
|
12716
|
+
`, "utf8");
|
|
12717
|
+
}
|
|
12718
|
+
function hasSummary(jsonlPath) {
|
|
12719
|
+
return fs25.existsSync(summaryPathFor(jsonlPath));
|
|
12720
|
+
}
|
|
12721
|
+
function summaryPathFor(jsonlPath) {
|
|
12722
|
+
return jsonlPath.replace(/\.jsonl$/, ".summary");
|
|
12723
|
+
}
|
|
12724
|
+
|
|
12725
|
+
// src/commands/sessions/summarise/summariseSession.ts
|
|
12726
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
12727
|
+
|
|
12728
|
+
// src/commands/sessions/summarise/iterateUserMessages.ts
|
|
12729
|
+
import * as fs26 from "fs";
|
|
12730
|
+
function* iterateUserMessages(filePath, maxBytes = 65536) {
|
|
12731
|
+
let content;
|
|
12732
|
+
try {
|
|
12733
|
+
const fd = fs26.openSync(filePath, "r");
|
|
12734
|
+
try {
|
|
12735
|
+
const buf = Buffer.alloc(maxBytes);
|
|
12736
|
+
const bytesRead = fs26.readSync(fd, buf, 0, buf.length, 0);
|
|
12737
|
+
content = buf.toString("utf8", 0, bytesRead);
|
|
12738
|
+
} finally {
|
|
12739
|
+
fs26.closeSync(fd);
|
|
12740
|
+
}
|
|
12741
|
+
} catch {
|
|
12742
|
+
return;
|
|
12743
|
+
}
|
|
12744
|
+
for (const line of content.split("\n")) {
|
|
12745
|
+
if (!line) continue;
|
|
12746
|
+
let entry;
|
|
12747
|
+
try {
|
|
12748
|
+
entry = JSON.parse(line);
|
|
12749
|
+
} catch {
|
|
12750
|
+
continue;
|
|
12751
|
+
}
|
|
12752
|
+
if (entry.type !== "user") continue;
|
|
12753
|
+
const msg = entry.message;
|
|
12754
|
+
const c = msg?.content;
|
|
12755
|
+
if (typeof c === "string") {
|
|
12756
|
+
yield c;
|
|
12757
|
+
} else if (Array.isArray(c)) {
|
|
12758
|
+
const text = c.filter((b) => b.type === "text").map((b) => b.text ?? "").join("\n");
|
|
12759
|
+
if (text) yield text;
|
|
12760
|
+
}
|
|
12761
|
+
}
|
|
12762
|
+
}
|
|
12763
|
+
|
|
12764
|
+
// src/commands/sessions/summarise/extractFirstUserMessage.ts
|
|
12765
|
+
function extractFirstUserMessage(filePath) {
|
|
12766
|
+
for (const text of iterateUserMessages(filePath)) {
|
|
12767
|
+
return truncate2(text);
|
|
12768
|
+
}
|
|
12769
|
+
return void 0;
|
|
12770
|
+
}
|
|
12771
|
+
function truncate2(text, maxChars = 500) {
|
|
12772
|
+
const trimmed = text.trim();
|
|
12773
|
+
if (trimmed.length <= maxChars) return trimmed;
|
|
12774
|
+
return `${trimmed.slice(0, maxChars)}\u2026`;
|
|
12775
|
+
}
|
|
12776
|
+
|
|
12777
|
+
// src/commands/sessions/summarise/scanSessionBacklogRefs.ts
|
|
12778
|
+
function scanSessionBacklogRefs(filePath) {
|
|
12779
|
+
const ids = /* @__PURE__ */ new Set();
|
|
12780
|
+
for (const text of iterateUserMessages(filePath, Number.MAX_SAFE_INTEGER)) {
|
|
12781
|
+
for (const id of extractBacklogIds(text)) {
|
|
12782
|
+
ids.add(id);
|
|
12783
|
+
}
|
|
12784
|
+
}
|
|
12785
|
+
return [...ids].sort((a, b) => a - b);
|
|
12786
|
+
}
|
|
12787
|
+
function extractBacklogIds(text) {
|
|
12788
|
+
const ids = [];
|
|
12789
|
+
for (const m of text.matchAll(/backlog\s+run\s+(\d+)/gi)) {
|
|
12790
|
+
ids.push(Number.parseInt(m[1], 10));
|
|
12791
|
+
}
|
|
12792
|
+
for (const m of text.matchAll(/backlog\s+(?:item\s+)?#(\d+)/gi)) {
|
|
12793
|
+
ids.push(Number.parseInt(m[1], 10));
|
|
12794
|
+
}
|
|
12795
|
+
for (const m of text.matchAll(/backlog\s+phase-done\s+(\d+)/gi)) {
|
|
12796
|
+
ids.push(Number.parseInt(m[1], 10));
|
|
12797
|
+
}
|
|
12798
|
+
for (const m of text.matchAll(/backlog\s+comment\s+(\d+)/gi)) {
|
|
12799
|
+
ids.push(Number.parseInt(m[1], 10));
|
|
12800
|
+
}
|
|
12801
|
+
for (const m of text.matchAll(/(?:^|[\s(])#(\d{1,4})(?=[\s).,;:!?]|$)/gm)) {
|
|
12802
|
+
ids.push(Number.parseInt(m[1], 10));
|
|
12803
|
+
}
|
|
12804
|
+
return ids;
|
|
12805
|
+
}
|
|
12806
|
+
|
|
12807
|
+
// src/commands/sessions/summarise/summariseSession.ts
|
|
12808
|
+
function summariseSession(jsonlPath) {
|
|
12809
|
+
const firstMessage = extractFirstUserMessage(jsonlPath);
|
|
12810
|
+
const backlogIds = scanSessionBacklogRefs(jsonlPath);
|
|
12811
|
+
if (!firstMessage && backlogIds.length === 0) {
|
|
12812
|
+
return void 0;
|
|
12813
|
+
}
|
|
12814
|
+
const prompt = buildPrompt2(firstMessage, backlogIds);
|
|
12815
|
+
try {
|
|
12816
|
+
const output = execFileSync2("claude", ["-p", "--model", "haiku", prompt], {
|
|
12817
|
+
encoding: "utf8",
|
|
12818
|
+
timeout: 3e4,
|
|
12819
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
12820
|
+
});
|
|
12821
|
+
const summary = output.trim();
|
|
12822
|
+
if (!summary) return void 0;
|
|
12823
|
+
return summary.split("\n")[0].replace(/^["']|["']$/g, "").trim();
|
|
12824
|
+
} catch {
|
|
12825
|
+
return void 0;
|
|
12826
|
+
}
|
|
12827
|
+
}
|
|
12828
|
+
function buildPrompt2(firstMessage, backlogIds) {
|
|
12829
|
+
const parts = [
|
|
12830
|
+
"Summarise this Claude Code session in ONE short sentence (under 100 chars).",
|
|
12831
|
+
"Return ONLY the summary, no quotes or explanation."
|
|
12832
|
+
];
|
|
12833
|
+
if (backlogIds.length > 0) {
|
|
12834
|
+
const refs = backlogIds.map((id) => `#${id}`).join(", ");
|
|
12835
|
+
parts.push(
|
|
12836
|
+
`The session references backlog item(s) ${refs}. Start the summary with "Backlog ${refs} \u2014" if relevant.`
|
|
12837
|
+
);
|
|
12838
|
+
}
|
|
12839
|
+
if (firstMessage) {
|
|
12840
|
+
parts.push(`First user message:
|
|
12841
|
+
${firstMessage}`);
|
|
12842
|
+
}
|
|
12843
|
+
return parts.join("\n");
|
|
12844
|
+
}
|
|
12845
|
+
|
|
12846
|
+
// src/commands/sessions/summarise/index.ts
|
|
12847
|
+
async function summarise3(options2) {
|
|
12848
|
+
const files = await discoverSessionJsonlPaths();
|
|
12849
|
+
if (files.length === 0) {
|
|
12850
|
+
console.log(chalk134.yellow("No sessions found."));
|
|
12851
|
+
return;
|
|
12852
|
+
}
|
|
12853
|
+
const toProcess = selectCandidates(files, options2);
|
|
12854
|
+
if (toProcess.length === 0) {
|
|
12855
|
+
console.log(chalk134.green("All sessions already summarised."));
|
|
12856
|
+
return;
|
|
12857
|
+
}
|
|
12858
|
+
console.log(
|
|
12859
|
+
chalk134.cyan(
|
|
12860
|
+
`Summarising ${toProcess.length} session(s) (${files.length} total)\u2026`
|
|
12861
|
+
)
|
|
12862
|
+
);
|
|
12863
|
+
const { succeeded, failed } = processSessions(toProcess);
|
|
12864
|
+
console.log(
|
|
12865
|
+
chalk134.green(`Done: ${succeeded} summarised`) + (failed > 0 ? chalk134.yellow(`, ${failed} skipped`) : "")
|
|
12866
|
+
);
|
|
12867
|
+
}
|
|
12868
|
+
function selectCandidates(files, options2) {
|
|
12869
|
+
const candidates = options2.force ? files : files.filter((f) => !hasSummary(f));
|
|
12870
|
+
candidates.sort((a, b) => {
|
|
12871
|
+
try {
|
|
12872
|
+
return fs27.statSync(b).mtimeMs - fs27.statSync(a).mtimeMs;
|
|
12873
|
+
} catch {
|
|
12874
|
+
return 0;
|
|
12875
|
+
}
|
|
12876
|
+
});
|
|
12877
|
+
const limit = options2.limit ? Number.parseInt(options2.limit, 10) : void 0;
|
|
12878
|
+
return limit && limit > 0 ? candidates.slice(0, limit) : candidates;
|
|
12879
|
+
}
|
|
12880
|
+
function processSessions(files) {
|
|
12881
|
+
let succeeded = 0;
|
|
12882
|
+
let failed = 0;
|
|
12883
|
+
for (let i = 0; i < files.length; i++) {
|
|
12884
|
+
const file = files[i];
|
|
12885
|
+
process.stdout.write(chalk134.dim(` [${i + 1}/${files.length}] `));
|
|
12886
|
+
const summary = summariseSession(file);
|
|
12887
|
+
if (summary) {
|
|
12888
|
+
writeSummary(file, summary);
|
|
12889
|
+
succeeded++;
|
|
12890
|
+
process.stdout.write(`${chalk134.green("\u2713")} ${summary}
|
|
12891
|
+
`);
|
|
12892
|
+
} else {
|
|
12893
|
+
failed++;
|
|
12894
|
+
process.stdout.write(` ${chalk134.yellow("skip")}
|
|
12895
|
+
`);
|
|
12896
|
+
}
|
|
12897
|
+
}
|
|
12898
|
+
return { succeeded, failed };
|
|
12899
|
+
}
|
|
12900
|
+
|
|
12593
12901
|
// src/commands/sessions/web/index.ts
|
|
12594
12902
|
import { WebSocketServer } from "ws";
|
|
12595
12903
|
|
|
12596
12904
|
// src/commands/sessions/web/handleRequest.ts
|
|
12597
|
-
import { readFileSync as
|
|
12905
|
+
import { readFileSync as readFileSync35 } from "fs";
|
|
12598
12906
|
import { createRequire as createRequire2 } from "module";
|
|
12599
12907
|
|
|
12600
12908
|
// src/commands/sessions/web/getHtml.ts
|
|
@@ -12629,7 +12937,7 @@ function createCssHandler(packageEntry) {
|
|
|
12629
12937
|
return (_req, res) => {
|
|
12630
12938
|
if (!cache) {
|
|
12631
12939
|
const resolved = require3.resolve(packageEntry);
|
|
12632
|
-
cache =
|
|
12940
|
+
cache = readFileSync35(resolved, "utf-8");
|
|
12633
12941
|
}
|
|
12634
12942
|
res.writeHead(200, { "Content-Type": "text/css" });
|
|
12635
12943
|
res.end(cache);
|
|
@@ -12752,114 +13060,6 @@ function resumeSession(id, sessionId, cwd, name) {
|
|
|
12752
13060
|
};
|
|
12753
13061
|
}
|
|
12754
13062
|
|
|
12755
|
-
// src/commands/sessions/web/discoverSessions.ts
|
|
12756
|
-
import * as fs24 from "fs";
|
|
12757
|
-
import * as os from "os";
|
|
12758
|
-
import * as path47 from "path";
|
|
12759
|
-
|
|
12760
|
-
// src/commands/sessions/web/parseSessionFile.ts
|
|
12761
|
-
import * as fs23 from "fs";
|
|
12762
|
-
import * as path46 from "path";
|
|
12763
|
-
|
|
12764
|
-
// src/commands/sessions/web/extractSessionMeta.ts
|
|
12765
|
-
function extractSessionMeta(lines) {
|
|
12766
|
-
let sessionId = "";
|
|
12767
|
-
let cwd = "";
|
|
12768
|
-
let timestamp = "";
|
|
12769
|
-
let name = "";
|
|
12770
|
-
for (const line of lines) {
|
|
12771
|
-
const entry = safeParse(line);
|
|
12772
|
-
if (!entry) continue;
|
|
12773
|
-
sessionId ||= typeof entry.sessionId === "string" ? entry.sessionId : "";
|
|
12774
|
-
timestamp ||= typeof entry.timestamp === "string" ? entry.timestamp : "";
|
|
12775
|
-
cwd ||= typeof entry.cwd === "string" ? entry.cwd : "";
|
|
12776
|
-
if (entry.type === "user" && !entry.isMeta) {
|
|
12777
|
-
name = extractName(entry);
|
|
12778
|
-
break;
|
|
12779
|
-
}
|
|
12780
|
-
}
|
|
12781
|
-
return { sessionId, cwd, timestamp, name };
|
|
12782
|
-
}
|
|
12783
|
-
function safeParse(line) {
|
|
12784
|
-
try {
|
|
12785
|
-
return JSON.parse(line);
|
|
12786
|
-
} catch {
|
|
12787
|
-
return null;
|
|
12788
|
-
}
|
|
12789
|
-
}
|
|
12790
|
-
function extractName(entry) {
|
|
12791
|
-
const msg = entry.message;
|
|
12792
|
-
const content = msg?.content;
|
|
12793
|
-
const text = typeof content === "string" ? content : Array.isArray(content) ? content.find((c) => c.type === "text")?.text ?? "" : "";
|
|
12794
|
-
return text.replace(/<command-[^>]*>[^<]*<\/command-[^>]*>/g, "").trim().slice(0, 80);
|
|
12795
|
-
}
|
|
12796
|
-
|
|
12797
|
-
// src/commands/sessions/web/parseSessionFile.ts
|
|
12798
|
-
async function parseSessionFile(filePath) {
|
|
12799
|
-
let handle;
|
|
12800
|
-
try {
|
|
12801
|
-
handle = await fs23.promises.open(filePath, "r");
|
|
12802
|
-
const buf = Buffer.alloc(16384);
|
|
12803
|
-
const { bytesRead } = await handle.read(buf, 0, buf.length, 0);
|
|
12804
|
-
const lines = buf.toString("utf8", 0, bytesRead).split("\n").filter(Boolean);
|
|
12805
|
-
const meta = extractSessionMeta(lines);
|
|
12806
|
-
if (!meta.sessionId) return null;
|
|
12807
|
-
const timestamp = meta.timestamp || (await fs23.promises.stat(filePath)).mtime.toISOString();
|
|
12808
|
-
const project = meta.cwd ? path46.basename(meta.cwd) : dirNameToProject(filePath);
|
|
12809
|
-
return {
|
|
12810
|
-
sessionId: meta.sessionId,
|
|
12811
|
-
name: meta.name || `Session ${meta.sessionId.slice(0, 8)}`,
|
|
12812
|
-
project,
|
|
12813
|
-
cwd: meta.cwd,
|
|
12814
|
-
timestamp
|
|
12815
|
-
};
|
|
12816
|
-
} catch {
|
|
12817
|
-
return null;
|
|
12818
|
-
} finally {
|
|
12819
|
-
await handle?.close();
|
|
12820
|
-
}
|
|
12821
|
-
}
|
|
12822
|
-
function dirNameToProject(filePath) {
|
|
12823
|
-
const dirName = path46.basename(path46.dirname(filePath));
|
|
12824
|
-
const parts = dirName.split("--");
|
|
12825
|
-
return parts[parts.length - 1].replace(/-/g, "/");
|
|
12826
|
-
}
|
|
12827
|
-
|
|
12828
|
-
// src/commands/sessions/web/discoverSessions.ts
|
|
12829
|
-
async function discoverSessions() {
|
|
12830
|
-
const projectsDir = path47.join(os.homedir(), ".claude", "projects");
|
|
12831
|
-
let projectDirs;
|
|
12832
|
-
try {
|
|
12833
|
-
projectDirs = await fs24.promises.readdir(projectsDir);
|
|
12834
|
-
} catch {
|
|
12835
|
-
return [];
|
|
12836
|
-
}
|
|
12837
|
-
const sessions = [];
|
|
12838
|
-
await Promise.all(
|
|
12839
|
-
projectDirs.map(async (dirName) => {
|
|
12840
|
-
const dirPath = path47.join(projectsDir, dirName);
|
|
12841
|
-
let entries;
|
|
12842
|
-
try {
|
|
12843
|
-
entries = await fs24.promises.readdir(dirPath);
|
|
12844
|
-
} catch {
|
|
12845
|
-
return;
|
|
12846
|
-
}
|
|
12847
|
-
const jsonlFiles = entries.filter((e) => e.endsWith(".jsonl"));
|
|
12848
|
-
await Promise.all(
|
|
12849
|
-
jsonlFiles.map(async (file) => {
|
|
12850
|
-
const filePath = path47.join(dirPath, file);
|
|
12851
|
-
const session = await parseSessionFile(filePath);
|
|
12852
|
-
if (session) sessions.push(session);
|
|
12853
|
-
})
|
|
12854
|
-
);
|
|
12855
|
-
})
|
|
12856
|
-
);
|
|
12857
|
-
sessions.sort(
|
|
12858
|
-
(a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
|
|
12859
|
-
);
|
|
12860
|
-
return sessions;
|
|
12861
|
-
}
|
|
12862
|
-
|
|
12863
13063
|
// src/commands/sessions/web/scheduleIdle.ts
|
|
12864
13064
|
var IDLE_MS = 3e3;
|
|
12865
13065
|
function scheduleIdle(session, onIdle) {
|
|
@@ -13033,13 +13233,14 @@ async function web3(options2) {
|
|
|
13033
13233
|
function registerSessions(program2) {
|
|
13034
13234
|
const cmd = program2.command("sessions").description("Web dashboard for Claude Code sessions").action(() => web3({ port: "3100" }));
|
|
13035
13235
|
cmd.command("web").description("Start the sessions web dashboard").option("-p, --port <number>", "Port to listen on", "3100").action(web3);
|
|
13236
|
+
cmd.command("summarise").description("Generate one-line summaries for Claude sessions").option("-f, --force", "Re-generate all summaries, even existing ones").option("-n, --limit <count>", "Maximum number of sessions to summarise").action(summarise3);
|
|
13036
13237
|
}
|
|
13037
13238
|
|
|
13038
13239
|
// src/commands/statusLine.ts
|
|
13039
|
-
import
|
|
13240
|
+
import chalk136 from "chalk";
|
|
13040
13241
|
|
|
13041
13242
|
// src/commands/buildLimitsSegment.ts
|
|
13042
|
-
import
|
|
13243
|
+
import chalk135 from "chalk";
|
|
13043
13244
|
var FIVE_HOUR_SECONDS = 5 * 3600;
|
|
13044
13245
|
var SEVEN_DAY_SECONDS = 7 * 86400;
|
|
13045
13246
|
function formatTimeLeft(resetsAt) {
|
|
@@ -13062,10 +13263,10 @@ function projectUsage(pct, resetsAt, windowSeconds) {
|
|
|
13062
13263
|
function colorizeRateLimit(pct, resetsAt, windowSeconds) {
|
|
13063
13264
|
const label2 = `${Math.round(pct)}%`;
|
|
13064
13265
|
const projected = projectUsage(pct, resetsAt, windowSeconds);
|
|
13065
|
-
if (projected == null) return
|
|
13066
|
-
if (projected > 100) return
|
|
13067
|
-
if (projected > 75) return
|
|
13068
|
-
return
|
|
13266
|
+
if (projected == null) return chalk135.green(label2);
|
|
13267
|
+
if (projected > 100) return chalk135.red(label2);
|
|
13268
|
+
if (projected > 75) return chalk135.yellow(label2);
|
|
13269
|
+
return chalk135.green(label2);
|
|
13069
13270
|
}
|
|
13070
13271
|
function formatLimit(pct, resetsAt, windowSeconds, fallbackLabel) {
|
|
13071
13272
|
const timeLabel = resetsAt ? formatTimeLeft(resetsAt) : fallbackLabel;
|
|
@@ -13091,14 +13292,14 @@ function buildLimitsSegment(rateLimits) {
|
|
|
13091
13292
|
}
|
|
13092
13293
|
|
|
13093
13294
|
// src/commands/statusLine.ts
|
|
13094
|
-
|
|
13295
|
+
chalk136.level = 3;
|
|
13095
13296
|
function formatNumber(num) {
|
|
13096
13297
|
return num.toLocaleString("en-US");
|
|
13097
13298
|
}
|
|
13098
13299
|
function colorizePercent(pct) {
|
|
13099
13300
|
const label2 = `${Math.round(pct)}%`;
|
|
13100
|
-
if (pct > 80) return
|
|
13101
|
-
if (pct > 40) return
|
|
13301
|
+
if (pct > 80) return chalk136.red(label2);
|
|
13302
|
+
if (pct > 40) return chalk136.yellow(label2);
|
|
13102
13303
|
return label2;
|
|
13103
13304
|
}
|
|
13104
13305
|
async function statusLine() {
|
|
@@ -13113,29 +13314,29 @@ async function statusLine() {
|
|
|
13113
13314
|
}
|
|
13114
13315
|
|
|
13115
13316
|
// src/commands/sync.ts
|
|
13116
|
-
import * as
|
|
13317
|
+
import * as fs30 from "fs";
|
|
13117
13318
|
import * as os2 from "os";
|
|
13118
13319
|
import * as path50 from "path";
|
|
13119
13320
|
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
13120
13321
|
|
|
13121
13322
|
// src/commands/sync/syncClaudeMd.ts
|
|
13122
|
-
import * as
|
|
13323
|
+
import * as fs28 from "fs";
|
|
13123
13324
|
import * as path48 from "path";
|
|
13124
|
-
import
|
|
13325
|
+
import chalk137 from "chalk";
|
|
13125
13326
|
async function syncClaudeMd(claudeDir, targetBase, options2) {
|
|
13126
13327
|
const source = path48.join(claudeDir, "CLAUDE.md");
|
|
13127
13328
|
const target = path48.join(targetBase, "CLAUDE.md");
|
|
13128
|
-
const sourceContent =
|
|
13129
|
-
if (
|
|
13130
|
-
const targetContent =
|
|
13329
|
+
const sourceContent = fs28.readFileSync(source, "utf-8");
|
|
13330
|
+
if (fs28.existsSync(target)) {
|
|
13331
|
+
const targetContent = fs28.readFileSync(target, "utf-8");
|
|
13131
13332
|
if (sourceContent !== targetContent) {
|
|
13132
13333
|
console.log(
|
|
13133
|
-
|
|
13334
|
+
chalk137.yellow("\n\u26A0\uFE0F Warning: CLAUDE.md differs from existing file")
|
|
13134
13335
|
);
|
|
13135
13336
|
console.log();
|
|
13136
13337
|
printDiff(targetContent, sourceContent);
|
|
13137
13338
|
const confirm = options2?.yes || await promptConfirm(
|
|
13138
|
-
|
|
13339
|
+
chalk137.red("Overwrite existing CLAUDE.md?"),
|
|
13139
13340
|
false
|
|
13140
13341
|
);
|
|
13141
13342
|
if (!confirm) {
|
|
@@ -13144,21 +13345,21 @@ async function syncClaudeMd(claudeDir, targetBase, options2) {
|
|
|
13144
13345
|
}
|
|
13145
13346
|
}
|
|
13146
13347
|
}
|
|
13147
|
-
|
|
13348
|
+
fs28.copyFileSync(source, target);
|
|
13148
13349
|
console.log("Copied CLAUDE.md to ~/.claude/CLAUDE.md");
|
|
13149
13350
|
}
|
|
13150
13351
|
|
|
13151
13352
|
// src/commands/sync/syncSettings.ts
|
|
13152
|
-
import * as
|
|
13353
|
+
import * as fs29 from "fs";
|
|
13153
13354
|
import * as path49 from "path";
|
|
13154
|
-
import
|
|
13355
|
+
import chalk138 from "chalk";
|
|
13155
13356
|
async function syncSettings(claudeDir, targetBase, options2) {
|
|
13156
13357
|
const source = path49.join(claudeDir, "settings.json");
|
|
13157
13358
|
const target = path49.join(targetBase, "settings.json");
|
|
13158
|
-
const sourceContent =
|
|
13359
|
+
const sourceContent = fs29.readFileSync(source, "utf-8");
|
|
13159
13360
|
const mergedContent = JSON.stringify(JSON.parse(sourceContent), null, " ");
|
|
13160
|
-
if (
|
|
13161
|
-
const targetContent =
|
|
13361
|
+
if (fs29.existsSync(target)) {
|
|
13362
|
+
const targetContent = fs29.readFileSync(target, "utf-8");
|
|
13162
13363
|
const normalizedTarget = JSON.stringify(
|
|
13163
13364
|
JSON.parse(targetContent),
|
|
13164
13365
|
null,
|
|
@@ -13167,14 +13368,14 @@ async function syncSettings(claudeDir, targetBase, options2) {
|
|
|
13167
13368
|
if (mergedContent !== normalizedTarget) {
|
|
13168
13369
|
if (!options2?.yes) {
|
|
13169
13370
|
console.log(
|
|
13170
|
-
|
|
13371
|
+
chalk138.yellow(
|
|
13171
13372
|
"\n\u26A0\uFE0F Warning: settings.json differs from existing file"
|
|
13172
13373
|
)
|
|
13173
13374
|
);
|
|
13174
13375
|
console.log();
|
|
13175
13376
|
printDiff(targetContent, mergedContent);
|
|
13176
13377
|
const confirm = await promptConfirm(
|
|
13177
|
-
|
|
13378
|
+
chalk138.red("Overwrite existing settings.json?"),
|
|
13178
13379
|
false
|
|
13179
13380
|
);
|
|
13180
13381
|
if (!confirm) {
|
|
@@ -13184,7 +13385,7 @@ async function syncSettings(claudeDir, targetBase, options2) {
|
|
|
13184
13385
|
}
|
|
13185
13386
|
}
|
|
13186
13387
|
}
|
|
13187
|
-
|
|
13388
|
+
fs29.writeFileSync(target, mergedContent);
|
|
13188
13389
|
console.log("Copied settings.json to ~/.claude/settings.json");
|
|
13189
13390
|
}
|
|
13190
13391
|
|
|
@@ -13203,10 +13404,10 @@ async function sync(options2) {
|
|
|
13203
13404
|
function syncCommands(claudeDir, targetBase) {
|
|
13204
13405
|
const sourceDir = path50.join(claudeDir, "commands");
|
|
13205
13406
|
const targetDir = path50.join(targetBase, "commands");
|
|
13206
|
-
|
|
13207
|
-
const files =
|
|
13407
|
+
fs30.mkdirSync(targetDir, { recursive: true });
|
|
13408
|
+
const files = fs30.readdirSync(sourceDir);
|
|
13208
13409
|
for (const file of files) {
|
|
13209
|
-
|
|
13410
|
+
fs30.copyFileSync(path50.join(sourceDir, file), path50.join(targetDir, file));
|
|
13210
13411
|
console.log(`Copied ${file} to ${targetDir}`);
|
|
13211
13412
|
}
|
|
13212
13413
|
console.log(`Synced ${files.length} command(s) to ~/.claude/commands`);
|