@basestream/cli 0.2.6 → 0.2.8
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 +135 -88
- 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)) {
|
|
@@ -283,7 +289,10 @@ function injectClaudeMdRule() {
|
|
|
283
289
|
}
|
|
284
290
|
let updated;
|
|
285
291
|
if (existing.includes(CLAUDE_MD_MARKER)) {
|
|
286
|
-
updated = existing.replace(
|
|
292
|
+
updated = existing.replace(
|
|
293
|
+
/<!-- basestream -->[\s\S]*?<!-- \/basestream -->/,
|
|
294
|
+
CLAUDE_MD_RULE
|
|
295
|
+
);
|
|
287
296
|
fs2.writeFileSync(CLAUDE_MD_PATH, updated);
|
|
288
297
|
check("Updated Basestream rules in ~/.claude/CLAUDE.md");
|
|
289
298
|
} else {
|
|
@@ -313,6 +322,33 @@ function detectClaudeCode() {
|
|
|
313
322
|
return null;
|
|
314
323
|
}
|
|
315
324
|
}
|
|
325
|
+
function injectPermissions() {
|
|
326
|
+
const settingsDir = path2.dirname(CLAUDE_SETTINGS_PATH);
|
|
327
|
+
fs2.mkdirSync(settingsDir, { recursive: true });
|
|
328
|
+
let settings = {};
|
|
329
|
+
if (fs2.existsSync(CLAUDE_SETTINGS_PATH)) {
|
|
330
|
+
try {
|
|
331
|
+
settings = JSON.parse(fs2.readFileSync(CLAUDE_SETTINGS_PATH, "utf-8"));
|
|
332
|
+
} catch {
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
if (!settings.permissions || typeof settings.permissions !== "object") {
|
|
336
|
+
settings.permissions = {};
|
|
337
|
+
}
|
|
338
|
+
const permissions = settings.permissions;
|
|
339
|
+
if (!Array.isArray(permissions.allow)) {
|
|
340
|
+
permissions.allow = [];
|
|
341
|
+
}
|
|
342
|
+
const allow = permissions.allow;
|
|
343
|
+
const missing = REQUIRED_PERMISSIONS.filter((p) => !allow.includes(p));
|
|
344
|
+
if (missing.length === 0) {
|
|
345
|
+
check("Claude Code permissions already configured");
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
allow.push(...missing);
|
|
349
|
+
fs2.writeFileSync(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2));
|
|
350
|
+
check("Added ~/.basestream/** permissions to ~/.claude/settings.json");
|
|
351
|
+
}
|
|
316
352
|
function injectClaudeCodeHook() {
|
|
317
353
|
const settingsDir = path2.dirname(CLAUDE_SETTINGS_PATH);
|
|
318
354
|
fs2.mkdirSync(settingsDir, { recursive: true });
|
|
@@ -329,52 +365,39 @@ function injectClaudeCodeHook() {
|
|
|
329
365
|
const hooks = settings.hooks;
|
|
330
366
|
if (Array.isArray(hooks.Stop)) {
|
|
331
367
|
const existing = hooks.Stop;
|
|
332
|
-
const
|
|
368
|
+
const ourEntryIndex = existing.findIndex(
|
|
333
369
|
(entry) => entry.hooks?.some((h) => h.command?.includes(HOOK_MARKER))
|
|
334
370
|
);
|
|
335
|
-
if (
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
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
|
-
]
|
|
371
|
+
if (ourEntryIndex !== -1) {
|
|
372
|
+
const entry = existing[ourEntryIndex];
|
|
373
|
+
const hookIndex = entry.hooks.findIndex(
|
|
374
|
+
(h) => h.command?.includes(HOOK_MARKER)
|
|
375
|
+
);
|
|
376
|
+
const hook = entry.hooks[hookIndex];
|
|
377
|
+
if (hook.command === HOOK_COMMAND && hook.timeout === HOOK_TIMEOUT) {
|
|
378
|
+
check("Claude Code hook already installed");
|
|
379
|
+
return;
|
|
360
380
|
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
if (!Array.isArray(permissions.allow)) {
|
|
368
|
-
permissions.allow = [];
|
|
381
|
+
hook.command = HOOK_COMMAND;
|
|
382
|
+
hook.timeout = HOOK_TIMEOUT;
|
|
383
|
+
fs2.writeFileSync(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2));
|
|
384
|
+
check("Updated Claude Code hook to latest command");
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
369
387
|
}
|
|
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);
|
|
388
|
+
if (!Array.isArray(hooks.Stop)) {
|
|
389
|
+
hooks.Stop = [];
|
|
377
390
|
}
|
|
391
|
+
hooks.Stop.push({
|
|
392
|
+
matcher: "*",
|
|
393
|
+
hooks: [
|
|
394
|
+
{
|
|
395
|
+
type: "command",
|
|
396
|
+
command: HOOK_COMMAND,
|
|
397
|
+
timeout: HOOK_TIMEOUT
|
|
398
|
+
}
|
|
399
|
+
]
|
|
400
|
+
});
|
|
378
401
|
fs2.writeFileSync(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2));
|
|
379
402
|
check("Injected tracking hook into ~/.claude/settings.json");
|
|
380
403
|
}
|
|
@@ -388,6 +411,7 @@ async function init() {
|
|
|
388
411
|
}
|
|
389
412
|
console.log();
|
|
390
413
|
injectClaudeCodeHook();
|
|
414
|
+
injectPermissions();
|
|
391
415
|
installSkill();
|
|
392
416
|
injectClaudeMdRule();
|
|
393
417
|
ensureDirs();
|
|
@@ -395,8 +419,14 @@ async function init() {
|
|
|
395
419
|
console.log();
|
|
396
420
|
await login();
|
|
397
421
|
console.log();
|
|
422
|
+
console.log(` ${c.dim("That's it. One last step:")}`);
|
|
423
|
+
console.log();
|
|
424
|
+
console.log(
|
|
425
|
+
` ${c.bold("Restart Claude Code")} for the hook to take effect.`
|
|
426
|
+
);
|
|
427
|
+
console.log();
|
|
398
428
|
console.log(
|
|
399
|
-
` ${c.dim("
|
|
429
|
+
` ${c.dim("After that, every session is automatically tracked.")}`
|
|
400
430
|
);
|
|
401
431
|
console.log();
|
|
402
432
|
}
|
|
@@ -631,24 +661,34 @@ function categorizeWork(acc) {
|
|
|
631
661
|
const files = Array.from(acc.filesWritten);
|
|
632
662
|
const fileCount = files.length;
|
|
633
663
|
let category = WorkCategory.OTHER;
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
664
|
+
if (fileCount > 0) {
|
|
665
|
+
const testFiles = files.filter(
|
|
666
|
+
(f) => f.includes(".test.") || f.includes(".spec.") || f.includes("__tests__")
|
|
667
|
+
);
|
|
668
|
+
const docFiles = files.filter(
|
|
669
|
+
(f) => f.endsWith(".md") || f.includes("/docs/")
|
|
670
|
+
);
|
|
671
|
+
const configFiles = files.filter(
|
|
672
|
+
(f) => f.includes("Dockerfile") || f.includes(".yml") || f.includes(".yaml") || f.includes("ci")
|
|
673
|
+
);
|
|
674
|
+
if (testFiles.length > fileCount / 2) category = WorkCategory.TESTING;
|
|
675
|
+
else if (docFiles.length > fileCount / 2) category = WorkCategory.DOCS;
|
|
676
|
+
else if (configFiles.length > fileCount / 2) category = WorkCategory.DEVOPS;
|
|
677
|
+
else category = WorkCategory.FEATURE;
|
|
678
|
+
} else {
|
|
679
|
+
const tools = acc.toolCalls.map((t) => t.tool);
|
|
680
|
+
const writeTools = tools.filter(
|
|
681
|
+
(t) => ["Write", "Edit", "NotebookEdit"].includes(t)
|
|
682
|
+
);
|
|
683
|
+
const readTools = tools.filter(
|
|
684
|
+
(t) => ["Read", "Grep", "Glob", "WebFetch", "WebSearch"].includes(t)
|
|
685
|
+
);
|
|
686
|
+
if (writeTools.length > 0) category = WorkCategory.FEATURE;
|
|
687
|
+
else if (readTools.length > tools.length / 2) category = WorkCategory.REFACTOR;
|
|
688
|
+
}
|
|
647
689
|
let complexity = Complexity.LOW;
|
|
648
|
-
if (fileCount
|
|
649
|
-
else if (fileCount
|
|
650
|
-
else if (fileCount <= 6) complexity = Complexity.MEDIUM;
|
|
651
|
-
else complexity = Complexity.HIGH;
|
|
690
|
+
if (fileCount >= 7 || acc.toolCalls.length >= 30) complexity = Complexity.HIGH;
|
|
691
|
+
else if (fileCount >= 3 || acc.toolCalls.length >= 10) complexity = Complexity.MEDIUM;
|
|
652
692
|
return { category, complexity };
|
|
653
693
|
}
|
|
654
694
|
function readSkillSummary(sessionId) {
|
|
@@ -708,24 +748,28 @@ function flushToBuffer(acc) {
|
|
|
708
748
|
function buildSummary(acc, category) {
|
|
709
749
|
const fileCount = acc.filesWritten.size;
|
|
710
750
|
const commitCount = acc.commitShas.size;
|
|
751
|
+
const toolCount = acc.toolCalls.length;
|
|
752
|
+
const project = acc.projectName || "unknown project";
|
|
711
753
|
const parts = [];
|
|
712
754
|
if (category !== WorkCategory.OTHER) {
|
|
713
755
|
parts.push(category.toLowerCase());
|
|
714
756
|
}
|
|
715
757
|
if (fileCount > 0) {
|
|
716
|
-
parts.push(
|
|
717
|
-
`${fileCount} file${fileCount !== 1 ? "s" : ""} modified`
|
|
718
|
-
);
|
|
758
|
+
parts.push(`${fileCount} file${fileCount !== 1 ? "s" : ""} modified`);
|
|
719
759
|
}
|
|
720
760
|
if (commitCount > 0) {
|
|
721
|
-
parts.push(
|
|
722
|
-
`${commitCount} commit${commitCount !== 1 ? "s" : ""}`
|
|
723
|
-
);
|
|
761
|
+
parts.push(`${commitCount} commit${commitCount !== 1 ? "s" : ""}`);
|
|
724
762
|
}
|
|
725
|
-
if (
|
|
726
|
-
|
|
763
|
+
if (fileCount === 0 && commitCount === 0) {
|
|
764
|
+
if (toolCount > 0) {
|
|
765
|
+
const uniqueTools = [...new Set(acc.toolCalls.map((t) => t.tool))];
|
|
766
|
+
parts.push(`${acc.turns} turn${acc.turns !== 1 ? "s" : ""}, ${toolCount} tool call${toolCount !== 1 ? "s" : ""} (${uniqueTools.slice(0, 3).join(", ")})`);
|
|
767
|
+
} else {
|
|
768
|
+
parts.push(`${acc.turns} turn${acc.turns !== 1 ? "s" : ""} Q&A`);
|
|
769
|
+
}
|
|
727
770
|
}
|
|
728
|
-
|
|
771
|
+
parts.push(`in ${project}`);
|
|
772
|
+
return parts.join(", ");
|
|
729
773
|
}
|
|
730
774
|
function makeLogger(sessionId) {
|
|
731
775
|
ensureDirs();
|
|
@@ -786,13 +830,15 @@ async function hookStop() {
|
|
|
786
830
|
};
|
|
787
831
|
log("new session");
|
|
788
832
|
}
|
|
833
|
+
acc.turns = 0;
|
|
834
|
+
acc.toolCalls = [];
|
|
789
835
|
if (transcript_path) {
|
|
790
836
|
acc = analyzeTranscript(transcript_path, acc);
|
|
791
837
|
}
|
|
792
838
|
acc.lastUpdatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
793
839
|
log(`turns=${acc.turns} files=${acc.filesWritten.size} commits=${acc.commitShas.size}`);
|
|
794
840
|
writeSessionAccumulator(acc);
|
|
795
|
-
if (acc.filesWritten.size > 0 || acc.commitShas.size > 0 || acc.turns >=
|
|
841
|
+
if (acc.filesWritten.size > 0 || acc.commitShas.size > 0 || acc.turns >= 1) {
|
|
796
842
|
flushToBuffer(acc);
|
|
797
843
|
const config = readConfig();
|
|
798
844
|
if (config?.apiKey) {
|
|
@@ -822,11 +868,12 @@ var CLAUDE_SETTINGS_PATH2 = path6.join(CLAUDE_DIR2, "settings.json");
|
|
|
822
868
|
var CLAUDE_SKILLS_PATH2 = path6.join(CLAUDE_DIR2, "skills", "bs-summarize.md");
|
|
823
869
|
var CLAUDE_MD_PATH2 = path6.join(CLAUDE_DIR2, "CLAUDE.md");
|
|
824
870
|
var HOOK_MARKER2 = "_hook-stop";
|
|
825
|
-
var
|
|
826
|
-
var
|
|
827
|
-
`Write(${
|
|
828
|
-
`Read(${
|
|
871
|
+
var BASESTREAM_DIR3 = path6.join(os4.homedir(), ".basestream");
|
|
872
|
+
var REQUIRED_PERMISSIONS2 = [
|
|
873
|
+
`Write(${BASESTREAM_DIR3}/**)`,
|
|
874
|
+
`Read(${BASESTREAM_DIR3}/**)`
|
|
829
875
|
];
|
|
876
|
+
var CLAUDE_MD_BLOCK_RE = /\n?<!-- basestream -->[\s\S]*?<!-- \/basestream -->\n?/;
|
|
830
877
|
function removeClaudeCodeHook() {
|
|
831
878
|
if (!fs6.existsSync(CLAUDE_SETTINGS_PATH2)) {
|
|
832
879
|
warn("No Claude Code settings found \u2014 nothing to remove");
|
|
@@ -856,7 +903,7 @@ function removeClaudeCodeHook() {
|
|
|
856
903
|
if (permissions && Array.isArray(permissions.allow)) {
|
|
857
904
|
const before = permissions.allow.length;
|
|
858
905
|
permissions.allow = permissions.allow.filter(
|
|
859
|
-
(
|
|
906
|
+
(p) => !REQUIRED_PERMISSIONS2.includes(p)
|
|
860
907
|
);
|
|
861
908
|
if (permissions.allow.length !== before) {
|
|
862
909
|
if (permissions.allow.length === 0) delete permissions.allow;
|
|
@@ -879,9 +926,9 @@ function removeSkill() {
|
|
|
879
926
|
}
|
|
880
927
|
function removeClaudeMdRule() {
|
|
881
928
|
if (!fs6.existsSync(CLAUDE_MD_PATH2)) return;
|
|
882
|
-
const
|
|
883
|
-
if (!
|
|
884
|
-
const updated =
|
|
929
|
+
const content = fs6.readFileSync(CLAUDE_MD_PATH2, "utf-8");
|
|
930
|
+
if (!CLAUDE_MD_BLOCK_RE.test(content)) return;
|
|
931
|
+
const updated = content.replace(CLAUDE_MD_BLOCK_RE, "\n").trimEnd();
|
|
885
932
|
fs6.writeFileSync(CLAUDE_MD_PATH2, updated ? updated + "\n" : "");
|
|
886
933
|
check("Removed Basestream rules from ~/.claude/CLAUDE.md");
|
|
887
934
|
}
|
|
@@ -905,18 +952,18 @@ var HELP = `
|
|
|
905
952
|
basestream \u2014 AI work intelligence for teams
|
|
906
953
|
|
|
907
954
|
Usage:
|
|
908
|
-
npx @basestream/cli
|
|
955
|
+
npx @basestream/cli <command>
|
|
909
956
|
|
|
910
957
|
Commands:
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
958
|
+
init Detect AI tools, inject hooks, authenticate
|
|
959
|
+
login Authenticate with your Basestream account
|
|
960
|
+
status Show this week's logged sessions
|
|
961
|
+
sync Manually sync buffered entries to Basestream
|
|
962
|
+
uninstall Remove the Claude Code tracking hook
|
|
916
963
|
|
|
917
964
|
Options:
|
|
918
|
-
--help, -h
|
|
919
|
-
--version, -v
|
|
965
|
+
--help, -h Show this help message
|
|
966
|
+
--version, -v Show version
|
|
920
967
|
`;
|
|
921
968
|
async function main() {
|
|
922
969
|
const command = process.argv[2];
|
|
@@ -925,7 +972,7 @@ async function main() {
|
|
|
925
972
|
process.exit(0);
|
|
926
973
|
}
|
|
927
974
|
if (command === "--version" || command === "-v") {
|
|
928
|
-
console.log(true ? "0.2.
|
|
975
|
+
console.log(true ? "0.2.8" : "dev");
|
|
929
976
|
process.exit(0);
|
|
930
977
|
}
|
|
931
978
|
switch (command || "init") {
|
package/package.json
CHANGED