@basestream/cli 0.2.6 → 0.2.7
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/dist/cli.mjs +119 -81
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -252,8 +252,9 @@ var CLAUDE_DIR = path2.join(os2.homedir(), ".claude");
|
|
|
252
252
|
var CLAUDE_SETTINGS_PATH = path2.join(CLAUDE_DIR, "settings.json");
|
|
253
253
|
var CLAUDE_SKILLS_PATH = path2.join(CLAUDE_DIR, "skills", "bs-summarize.md");
|
|
254
254
|
var CLAUDE_MD_PATH = path2.join(CLAUDE_DIR, "CLAUDE.md");
|
|
255
|
-
var HOOK_COMMAND = "npx @basestream/cli _hook-stop";
|
|
255
|
+
var HOOK_COMMAND = "npx --yes @basestream/cli@latest _hook-stop";
|
|
256
256
|
var HOOK_MARKER = "_hook-stop";
|
|
257
|
+
var HOOK_TIMEOUT = 60;
|
|
257
258
|
var CLAUDE_MD_MARKER = "<!-- basestream -->";
|
|
258
259
|
var CLAUDE_MD_RULE = `${CLAUDE_MD_MARKER}
|
|
259
260
|
- Before starting any non-trivial task, if the reason or context behind the work isn't already clear, ask: "What's the context for this?" Skip this for small tasks, typo fixes, or anything self-explanatory.
|
|
@@ -276,6 +277,11 @@ Do not just append \u2014 synthesize everything into a single cohesive summary.
|
|
|
276
277
|
|
|
277
278
|
Write the result to \`~/.basestream/sessions/\${CLAUDE_SESSION_ID}-summary.md\`, overwriting the file.
|
|
278
279
|
`;
|
|
280
|
+
var BASESTREAM_DIR2 = path2.join(os2.homedir(), ".basestream");
|
|
281
|
+
var REQUIRED_PERMISSIONS = [
|
|
282
|
+
`Write(${BASESTREAM_DIR2}/**)`,
|
|
283
|
+
`Read(${BASESTREAM_DIR2}/**)`
|
|
284
|
+
];
|
|
279
285
|
function injectClaudeMdRule() {
|
|
280
286
|
let existing = "";
|
|
281
287
|
if (fs2.existsSync(CLAUDE_MD_PATH)) {
|
|
@@ -313,6 +319,33 @@ function detectClaudeCode() {
|
|
|
313
319
|
return null;
|
|
314
320
|
}
|
|
315
321
|
}
|
|
322
|
+
function injectPermissions() {
|
|
323
|
+
const settingsDir = path2.dirname(CLAUDE_SETTINGS_PATH);
|
|
324
|
+
fs2.mkdirSync(settingsDir, { recursive: true });
|
|
325
|
+
let settings = {};
|
|
326
|
+
if (fs2.existsSync(CLAUDE_SETTINGS_PATH)) {
|
|
327
|
+
try {
|
|
328
|
+
settings = JSON.parse(fs2.readFileSync(CLAUDE_SETTINGS_PATH, "utf-8"));
|
|
329
|
+
} catch {
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
if (!settings.permissions || typeof settings.permissions !== "object") {
|
|
333
|
+
settings.permissions = {};
|
|
334
|
+
}
|
|
335
|
+
const permissions = settings.permissions;
|
|
336
|
+
if (!Array.isArray(permissions.allow)) {
|
|
337
|
+
permissions.allow = [];
|
|
338
|
+
}
|
|
339
|
+
const allow = permissions.allow;
|
|
340
|
+
const missing = REQUIRED_PERMISSIONS.filter((p) => !allow.includes(p));
|
|
341
|
+
if (missing.length === 0) {
|
|
342
|
+
check("Claude Code permissions already configured");
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
allow.push(...missing);
|
|
346
|
+
fs2.writeFileSync(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2));
|
|
347
|
+
check("Added ~/.basestream/** permissions to ~/.claude/settings.json");
|
|
348
|
+
}
|
|
316
349
|
function injectClaudeCodeHook() {
|
|
317
350
|
const settingsDir = path2.dirname(CLAUDE_SETTINGS_PATH);
|
|
318
351
|
fs2.mkdirSync(settingsDir, { recursive: true });
|
|
@@ -329,52 +362,37 @@ function injectClaudeCodeHook() {
|
|
|
329
362
|
const hooks = settings.hooks;
|
|
330
363
|
if (Array.isArray(hooks.Stop)) {
|
|
331
364
|
const existing = hooks.Stop;
|
|
332
|
-
const
|
|
365
|
+
const ourEntryIndex = existing.findIndex(
|
|
333
366
|
(entry) => entry.hooks?.some((h) => h.command?.includes(HOOK_MARKER))
|
|
334
367
|
);
|
|
335
|
-
if (
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
hooks
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
type: "command",
|
|
343
|
-
command: HOOK_COMMAND,
|
|
344
|
-
timeout: 30
|
|
345
|
-
}
|
|
346
|
-
]
|
|
347
|
-
});
|
|
348
|
-
}
|
|
349
|
-
} else {
|
|
350
|
-
hooks.Stop = [
|
|
351
|
-
{
|
|
352
|
-
matcher: "*",
|
|
353
|
-
hooks: [
|
|
354
|
-
{
|
|
355
|
-
type: "command",
|
|
356
|
-
command: HOOK_COMMAND,
|
|
357
|
-
timeout: 30
|
|
358
|
-
}
|
|
359
|
-
]
|
|
368
|
+
if (ourEntryIndex !== -1) {
|
|
369
|
+
const entry = existing[ourEntryIndex];
|
|
370
|
+
const hookIndex = entry.hooks.findIndex((h) => h.command?.includes(HOOK_MARKER));
|
|
371
|
+
const hook = entry.hooks[hookIndex];
|
|
372
|
+
if (hook.command === HOOK_COMMAND && hook.timeout === HOOK_TIMEOUT) {
|
|
373
|
+
check("Claude Code hook already installed");
|
|
374
|
+
return;
|
|
360
375
|
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
if (!Array.isArray(permissions.allow)) {
|
|
368
|
-
permissions.allow = [];
|
|
376
|
+
hook.command = HOOK_COMMAND;
|
|
377
|
+
hook.timeout = HOOK_TIMEOUT;
|
|
378
|
+
fs2.writeFileSync(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2));
|
|
379
|
+
check("Updated Claude Code hook to latest command");
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
369
382
|
}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
`Write(${os2.homedir()}/.basestream/sessions/**)`,
|
|
373
|
-
`Read(${os2.homedir()}/.basestream/sessions/**)`
|
|
374
|
-
];
|
|
375
|
-
for (const rule of SKILL_PERMISSIONS2) {
|
|
376
|
-
if (!allow.includes(rule)) allow.push(rule);
|
|
383
|
+
if (!Array.isArray(hooks.Stop)) {
|
|
384
|
+
hooks.Stop = [];
|
|
377
385
|
}
|
|
386
|
+
hooks.Stop.push({
|
|
387
|
+
matcher: "*",
|
|
388
|
+
hooks: [
|
|
389
|
+
{
|
|
390
|
+
type: "command",
|
|
391
|
+
command: HOOK_COMMAND,
|
|
392
|
+
timeout: HOOK_TIMEOUT
|
|
393
|
+
}
|
|
394
|
+
]
|
|
395
|
+
});
|
|
378
396
|
fs2.writeFileSync(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2));
|
|
379
397
|
check("Injected tracking hook into ~/.claude/settings.json");
|
|
380
398
|
}
|
|
@@ -388,6 +406,7 @@ async function init() {
|
|
|
388
406
|
}
|
|
389
407
|
console.log();
|
|
390
408
|
injectClaudeCodeHook();
|
|
409
|
+
injectPermissions();
|
|
391
410
|
installSkill();
|
|
392
411
|
injectClaudeMdRule();
|
|
393
412
|
ensureDirs();
|
|
@@ -395,9 +414,11 @@ async function init() {
|
|
|
395
414
|
console.log();
|
|
396
415
|
await login();
|
|
397
416
|
console.log();
|
|
398
|
-
console.log(
|
|
399
|
-
|
|
400
|
-
);
|
|
417
|
+
console.log(` ${c.dim("That's it. One last step:")}`);
|
|
418
|
+
console.log();
|
|
419
|
+
console.log(` ${c.bold("Restart Claude Code")} for the hook to take effect.`);
|
|
420
|
+
console.log();
|
|
421
|
+
console.log(` ${c.dim("After that, every session is automatically tracked.")}`);
|
|
401
422
|
console.log();
|
|
402
423
|
}
|
|
403
424
|
|
|
@@ -631,24 +652,34 @@ function categorizeWork(acc) {
|
|
|
631
652
|
const files = Array.from(acc.filesWritten);
|
|
632
653
|
const fileCount = files.length;
|
|
633
654
|
let category = WorkCategory.OTHER;
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
655
|
+
if (fileCount > 0) {
|
|
656
|
+
const testFiles = files.filter(
|
|
657
|
+
(f) => f.includes(".test.") || f.includes(".spec.") || f.includes("__tests__")
|
|
658
|
+
);
|
|
659
|
+
const docFiles = files.filter(
|
|
660
|
+
(f) => f.endsWith(".md") || f.includes("/docs/")
|
|
661
|
+
);
|
|
662
|
+
const configFiles = files.filter(
|
|
663
|
+
(f) => f.includes("Dockerfile") || f.includes(".yml") || f.includes(".yaml") || f.includes("ci")
|
|
664
|
+
);
|
|
665
|
+
if (testFiles.length > fileCount / 2) category = WorkCategory.TESTING;
|
|
666
|
+
else if (docFiles.length > fileCount / 2) category = WorkCategory.DOCS;
|
|
667
|
+
else if (configFiles.length > fileCount / 2) category = WorkCategory.DEVOPS;
|
|
668
|
+
else category = WorkCategory.FEATURE;
|
|
669
|
+
} else {
|
|
670
|
+
const tools = acc.toolCalls.map((t) => t.tool);
|
|
671
|
+
const writeTools = tools.filter(
|
|
672
|
+
(t) => ["Write", "Edit", "NotebookEdit"].includes(t)
|
|
673
|
+
);
|
|
674
|
+
const readTools = tools.filter(
|
|
675
|
+
(t) => ["Read", "Grep", "Glob", "WebFetch", "WebSearch"].includes(t)
|
|
676
|
+
);
|
|
677
|
+
if (writeTools.length > 0) category = WorkCategory.FEATURE;
|
|
678
|
+
else if (readTools.length > tools.length / 2) category = WorkCategory.REFACTOR;
|
|
679
|
+
}
|
|
647
680
|
let complexity = Complexity.LOW;
|
|
648
|
-
if (fileCount
|
|
649
|
-
else if (fileCount
|
|
650
|
-
else if (fileCount <= 6) complexity = Complexity.MEDIUM;
|
|
651
|
-
else complexity = Complexity.HIGH;
|
|
681
|
+
if (fileCount >= 7 || acc.toolCalls.length >= 30) complexity = Complexity.HIGH;
|
|
682
|
+
else if (fileCount >= 3 || acc.toolCalls.length >= 10) complexity = Complexity.MEDIUM;
|
|
652
683
|
return { category, complexity };
|
|
653
684
|
}
|
|
654
685
|
function readSkillSummary(sessionId) {
|
|
@@ -708,24 +739,28 @@ function flushToBuffer(acc) {
|
|
|
708
739
|
function buildSummary(acc, category) {
|
|
709
740
|
const fileCount = acc.filesWritten.size;
|
|
710
741
|
const commitCount = acc.commitShas.size;
|
|
742
|
+
const toolCount = acc.toolCalls.length;
|
|
743
|
+
const project = acc.projectName || "unknown project";
|
|
711
744
|
const parts = [];
|
|
712
745
|
if (category !== WorkCategory.OTHER) {
|
|
713
746
|
parts.push(category.toLowerCase());
|
|
714
747
|
}
|
|
715
748
|
if (fileCount > 0) {
|
|
716
|
-
parts.push(
|
|
717
|
-
`${fileCount} file${fileCount !== 1 ? "s" : ""} modified`
|
|
718
|
-
);
|
|
749
|
+
parts.push(`${fileCount} file${fileCount !== 1 ? "s" : ""} modified`);
|
|
719
750
|
}
|
|
720
751
|
if (commitCount > 0) {
|
|
721
|
-
parts.push(
|
|
722
|
-
`${commitCount} commit${commitCount !== 1 ? "s" : ""}`
|
|
723
|
-
);
|
|
752
|
+
parts.push(`${commitCount} commit${commitCount !== 1 ? "s" : ""}`);
|
|
724
753
|
}
|
|
725
|
-
if (
|
|
726
|
-
|
|
754
|
+
if (fileCount === 0 && commitCount === 0) {
|
|
755
|
+
if (toolCount > 0) {
|
|
756
|
+
const uniqueTools = [...new Set(acc.toolCalls.map((t) => t.tool))];
|
|
757
|
+
parts.push(`${acc.turns} turn${acc.turns !== 1 ? "s" : ""}, ${toolCount} tool call${toolCount !== 1 ? "s" : ""} (${uniqueTools.slice(0, 3).join(", ")})`);
|
|
758
|
+
} else {
|
|
759
|
+
parts.push(`${acc.turns} turn${acc.turns !== 1 ? "s" : ""} Q&A`);
|
|
760
|
+
}
|
|
727
761
|
}
|
|
728
|
-
|
|
762
|
+
parts.push(`in ${project}`);
|
|
763
|
+
return parts.join(", ");
|
|
729
764
|
}
|
|
730
765
|
function makeLogger(sessionId) {
|
|
731
766
|
ensureDirs();
|
|
@@ -786,13 +821,15 @@ async function hookStop() {
|
|
|
786
821
|
};
|
|
787
822
|
log("new session");
|
|
788
823
|
}
|
|
824
|
+
acc.turns = 0;
|
|
825
|
+
acc.toolCalls = [];
|
|
789
826
|
if (transcript_path) {
|
|
790
827
|
acc = analyzeTranscript(transcript_path, acc);
|
|
791
828
|
}
|
|
792
829
|
acc.lastUpdatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
793
830
|
log(`turns=${acc.turns} files=${acc.filesWritten.size} commits=${acc.commitShas.size}`);
|
|
794
831
|
writeSessionAccumulator(acc);
|
|
795
|
-
if (acc.filesWritten.size > 0 || acc.commitShas.size > 0 || acc.turns >=
|
|
832
|
+
if (acc.filesWritten.size > 0 || acc.commitShas.size > 0 || acc.turns >= 1) {
|
|
796
833
|
flushToBuffer(acc);
|
|
797
834
|
const config = readConfig();
|
|
798
835
|
if (config?.apiKey) {
|
|
@@ -822,11 +859,12 @@ var CLAUDE_SETTINGS_PATH2 = path6.join(CLAUDE_DIR2, "settings.json");
|
|
|
822
859
|
var CLAUDE_SKILLS_PATH2 = path6.join(CLAUDE_DIR2, "skills", "bs-summarize.md");
|
|
823
860
|
var CLAUDE_MD_PATH2 = path6.join(CLAUDE_DIR2, "CLAUDE.md");
|
|
824
861
|
var HOOK_MARKER2 = "_hook-stop";
|
|
825
|
-
var
|
|
826
|
-
var
|
|
827
|
-
`Write(${
|
|
828
|
-
`Read(${
|
|
862
|
+
var BASESTREAM_DIR3 = path6.join(os4.homedir(), ".basestream");
|
|
863
|
+
var REQUIRED_PERMISSIONS2 = [
|
|
864
|
+
`Write(${BASESTREAM_DIR3}/**)`,
|
|
865
|
+
`Read(${BASESTREAM_DIR3}/**)`
|
|
829
866
|
];
|
|
867
|
+
var CLAUDE_MD_BLOCK_RE = /\n?<!-- basestream -->[\s\S]*?<!-- \/basestream -->\n?/;
|
|
830
868
|
function removeClaudeCodeHook() {
|
|
831
869
|
if (!fs6.existsSync(CLAUDE_SETTINGS_PATH2)) {
|
|
832
870
|
warn("No Claude Code settings found \u2014 nothing to remove");
|
|
@@ -856,7 +894,7 @@ function removeClaudeCodeHook() {
|
|
|
856
894
|
if (permissions && Array.isArray(permissions.allow)) {
|
|
857
895
|
const before = permissions.allow.length;
|
|
858
896
|
permissions.allow = permissions.allow.filter(
|
|
859
|
-
(
|
|
897
|
+
(p) => !REQUIRED_PERMISSIONS2.includes(p)
|
|
860
898
|
);
|
|
861
899
|
if (permissions.allow.length !== before) {
|
|
862
900
|
if (permissions.allow.length === 0) delete permissions.allow;
|
|
@@ -879,9 +917,9 @@ function removeSkill() {
|
|
|
879
917
|
}
|
|
880
918
|
function removeClaudeMdRule() {
|
|
881
919
|
if (!fs6.existsSync(CLAUDE_MD_PATH2)) return;
|
|
882
|
-
const
|
|
883
|
-
if (!
|
|
884
|
-
const updated =
|
|
920
|
+
const content = fs6.readFileSync(CLAUDE_MD_PATH2, "utf-8");
|
|
921
|
+
if (!CLAUDE_MD_BLOCK_RE.test(content)) return;
|
|
922
|
+
const updated = content.replace(CLAUDE_MD_BLOCK_RE, "\n").trimEnd();
|
|
885
923
|
fs6.writeFileSync(CLAUDE_MD_PATH2, updated ? updated + "\n" : "");
|
|
886
924
|
check("Removed Basestream rules from ~/.claude/CLAUDE.md");
|
|
887
925
|
}
|
|
@@ -925,7 +963,7 @@ async function main() {
|
|
|
925
963
|
process.exit(0);
|
|
926
964
|
}
|
|
927
965
|
if (command === "--version" || command === "-v") {
|
|
928
|
-
console.log(true ? "0.2.
|
|
966
|
+
console.log(true ? "0.2.7" : "dev");
|
|
929
967
|
process.exit(0);
|
|
930
968
|
}
|
|
931
969
|
switch (command || "init") {
|
package/package.json
CHANGED