@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.
Files changed (2) hide show
  1. package/dist/cli.mjs +135 -88
  2. 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(/<!-- basestream -->[\s\S]*?<!-- \/basestream -->/, CLAUDE_MD_RULE);
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 alreadyInstalled = existing.some(
368
+ const ourEntryIndex = existing.findIndex(
333
369
  (entry) => entry.hooks?.some((h) => h.command?.includes(HOOK_MARKER))
334
370
  );
335
- if (alreadyInstalled) {
336
- check("Claude Code hook already installed");
337
- } else {
338
- hooks.Stop.push({
339
- matcher: "*",
340
- hooks: [
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
- ]
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
- if (!settings.permissions || typeof settings.permissions !== "object") {
364
- settings.permissions = {};
365
- }
366
- const permissions = settings.permissions;
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
- const allow = permissions.allow;
371
- const SKILL_PERMISSIONS2 = [
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("That's it. Work normally \u2014 every session is now tracked.")}`
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
- const testFiles = files.filter(
635
- (f) => f.includes(".test.") || f.includes(".spec.") || f.includes("__tests__")
636
- );
637
- const docFiles = files.filter(
638
- (f) => f.endsWith(".md") || f.includes("/docs/")
639
- );
640
- const configFiles = files.filter(
641
- (f) => f.includes("Dockerfile") || f.includes(".yml") || f.includes(".yaml") || f.includes("ci")
642
- );
643
- if (testFiles.length > fileCount / 2) category = WorkCategory.TESTING;
644
- else if (docFiles.length > fileCount / 2) category = WorkCategory.DOCS;
645
- else if (configFiles.length > fileCount / 2) category = WorkCategory.DEVOPS;
646
- else if (fileCount > 0) category = WorkCategory.FEATURE;
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 === 0) complexity = Complexity.LOW;
649
- else if (fileCount <= 2) complexity = Complexity.LOW;
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 (acc.projectName) {
726
- parts.push(`in ${acc.projectName}`);
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
- return parts.length > 0 ? parts.join(", ") : `Claude Code session in ${acc.projectName || "unknown project"}`;
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 >= 3) {
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 CLAUDE_MD_MARKER2 = "<!-- basestream -->";
826
- var SKILL_PERMISSIONS = [
827
- `Write(${os4.homedir()}/.basestream/sessions/**)`,
828
- `Read(${os4.homedir()}/.basestream/sessions/**)`
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
- (rule) => !SKILL_PERMISSIONS.includes(rule)
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 existing = fs6.readFileSync(CLAUDE_MD_PATH2, "utf-8");
883
- if (!existing.includes(CLAUDE_MD_MARKER2)) return;
884
- const updated = existing.replace(/\n\n<!-- basestream -->[\s\S]*?<!-- \/basestream -->\n?/, "").replace(/<!-- basestream -->[\s\S]*?<!-- \/basestream -->\n?/, "").trimEnd();
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 init Set up everything (hooks, auth, buffer)
955
+ npx @basestream/cli <command>
909
956
 
910
957
  Commands:
911
- basestream init Detect AI tools, inject hooks, authenticate
912
- basestream login Authenticate with your Basestream account
913
- basestream status Show this week's logged sessions
914
- basestream sync Manually sync buffered entries to Basestream
915
- basestream uninstall Remove the Claude Code tracking hook
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 Show this help message
919
- --version, -v Show version
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.6" : "dev");
975
+ console.log(true ? "0.2.8" : "dev");
929
976
  process.exit(0);
930
977
  }
931
978
  switch (command || "init") {
package/package.json CHANGED
@@ -18,5 +18,5 @@
18
18
  "unlink:cli": "npm unlink -g @basestream/cli"
19
19
  },
20
20
  "type": "module",
21
- "version": "0.2.6"
21
+ "version": "0.2.8"
22
22
  }