@basestream/cli 0.2.4 → 0.2.6

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 +133 -42
  2. package/package.json +1 -1
package/dist/cli.mjs CHANGED
@@ -14,7 +14,7 @@ import fs from "node:fs";
14
14
  import path from "node:path";
15
15
  import os from "node:os";
16
16
  function ensureDirs() {
17
- for (const dir of [BASESTREAM_DIR, BUFFER_DIR, SESSION_DIR]) {
17
+ for (const dir of [BASESTREAM_DIR, BUFFER_DIR, SESSION_DIR, LOG_DIR]) {
18
18
  fs.mkdirSync(dir, { recursive: true });
19
19
  }
20
20
  }
@@ -34,7 +34,7 @@ function writeConfig(config) {
34
34
  ensureDirs();
35
35
  fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
36
36
  }
37
- var BASESTREAM_DIR, BUFFER_DIR, CONFIG_FILE, SESSION_DIR;
37
+ var BASESTREAM_DIR, BUFFER_DIR, CONFIG_FILE, SESSION_DIR, LOG_DIR, LOG_RETENTION_DAYS;
38
38
  var init_config = __esm({
39
39
  "src/cli/config.ts"() {
40
40
  "use strict";
@@ -42,6 +42,8 @@ var init_config = __esm({
42
42
  BUFFER_DIR = path.join(BASESTREAM_DIR, "buffer");
43
43
  CONFIG_FILE = path.join(BASESTREAM_DIR, "config.json");
44
44
  SESSION_DIR = path.join(BASESTREAM_DIR, "sessions");
45
+ LOG_DIR = path.join(BASESTREAM_DIR, "logs");
46
+ LOG_RETENTION_DAYS = 7;
45
47
  }
46
48
  });
47
49
 
@@ -332,22 +334,47 @@ function injectClaudeCodeHook() {
332
334
  );
333
335
  if (alreadyInstalled) {
334
336
  check("Claude Code hook already installed");
335
- return;
337
+ } else {
338
+ hooks.Stop.push({
339
+ matcher: "*",
340
+ hooks: [
341
+ {
342
+ type: "command",
343
+ command: HOOK_COMMAND,
344
+ timeout: 30
345
+ }
346
+ ]
347
+ });
336
348
  }
337
- }
338
- if (!Array.isArray(hooks.Stop)) {
339
- hooks.Stop = [];
340
- }
341
- hooks.Stop.push({
342
- matcher: "*",
343
- hooks: [
349
+ } else {
350
+ hooks.Stop = [
344
351
  {
345
- type: "command",
346
- command: HOOK_COMMAND,
347
- timeout: 30
352
+ matcher: "*",
353
+ hooks: [
354
+ {
355
+ type: "command",
356
+ command: HOOK_COMMAND,
357
+ timeout: 30
358
+ }
359
+ ]
348
360
  }
349
- ]
350
- });
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 = [];
369
+ }
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);
377
+ }
351
378
  fs2.writeFileSync(CLAUDE_SETTINGS_PATH, JSON.stringify(settings, null, 2));
352
379
  check("Injected tracking hook into ~/.claude/settings.json");
353
380
  }
@@ -700,6 +727,30 @@ function buildSummary(acc, category) {
700
727
  }
701
728
  return parts.length > 0 ? parts.join(", ") : `Claude Code session in ${acc.projectName || "unknown project"}`;
702
729
  }
730
+ function makeLogger(sessionId) {
731
+ ensureDirs();
732
+ const logFile = path5.join(LOG_DIR, `${sessionId}.log`);
733
+ return (...args) => {
734
+ const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] ${args.join(" ")}
735
+ `;
736
+ try {
737
+ fs5.appendFileSync(logFile, line);
738
+ } catch {
739
+ }
740
+ };
741
+ }
742
+ function pruneOldLogs() {
743
+ try {
744
+ const cutoff = Date.now() - LOG_RETENTION_DAYS * 24 * 60 * 60 * 1e3;
745
+ for (const file of fs5.readdirSync(LOG_DIR)) {
746
+ if (!file.endsWith(".log")) continue;
747
+ const full = path5.join(LOG_DIR, file);
748
+ const stat = fs5.statSync(full);
749
+ if (stat.mtimeMs < cutoff) fs5.unlinkSync(full);
750
+ }
751
+ } catch {
752
+ }
753
+ }
703
754
  async function hookStop() {
704
755
  let payload;
705
756
  try {
@@ -714,6 +765,9 @@ async function hookStop() {
714
765
  const { session_id, transcript_path, cwd } = payload;
715
766
  if (!session_id) process.exit(0);
716
767
  ensureDirs();
768
+ pruneOldLogs();
769
+ const log = makeLogger(session_id);
770
+ log("hook fired", "cwd=" + cwd);
717
771
  let acc = readSessionAccumulator(session_id);
718
772
  if (!acc) {
719
773
  const gitInfo = extractGitInfo(cwd);
@@ -730,11 +784,13 @@ async function hookStop() {
730
784
  gitRepo: gitInfo.repo,
731
785
  projectName: gitInfo.projectName
732
786
  };
787
+ log("new session");
733
788
  }
734
789
  if (transcript_path) {
735
790
  acc = analyzeTranscript(transcript_path, acc);
736
791
  }
737
792
  acc.lastUpdatedAt = (/* @__PURE__ */ new Date()).toISOString();
793
+ log(`turns=${acc.turns} files=${acc.filesWritten.size} commits=${acc.commitShas.size}`);
738
794
  writeSessionAccumulator(acc);
739
795
  if (acc.filesWritten.size > 0 || acc.commitShas.size > 0 || acc.turns >= 3) {
740
796
  flushToBuffer(acc);
@@ -743,9 +799,16 @@ async function hookStop() {
743
799
  try {
744
800
  const { syncEntries: syncEntries2 } = await Promise.resolve().then(() => (init_sync(), sync_exports));
745
801
  await syncEntries2(config);
746
- } catch {
802
+ log("synced");
803
+ process.stdout.write(JSON.stringify({ systemMessage: "basestream synced" }) + "\n");
804
+ } catch (e) {
805
+ log("sync error:", String(e));
747
806
  }
807
+ } else {
808
+ log("no apiKey, skipping sync");
748
809
  }
810
+ } else {
811
+ log("nothing to flush");
749
812
  }
750
813
  }
751
814
 
@@ -754,51 +817,79 @@ init_util();
754
817
  import fs6 from "node:fs";
755
818
  import path6 from "node:path";
756
819
  import os4 from "node:os";
757
- var CLAUDE_SETTINGS_PATH2 = path6.join(
758
- os4.homedir(),
759
- ".claude",
760
- "settings.json"
761
- );
820
+ var CLAUDE_DIR2 = path6.join(os4.homedir(), ".claude");
821
+ var CLAUDE_SETTINGS_PATH2 = path6.join(CLAUDE_DIR2, "settings.json");
822
+ var CLAUDE_SKILLS_PATH2 = path6.join(CLAUDE_DIR2, "skills", "bs-summarize.md");
823
+ var CLAUDE_MD_PATH2 = path6.join(CLAUDE_DIR2, "CLAUDE.md");
762
824
  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/**)`
829
+ ];
763
830
  function removeClaudeCodeHook() {
764
831
  if (!fs6.existsSync(CLAUDE_SETTINGS_PATH2)) {
765
832
  warn("No Claude Code settings found \u2014 nothing to remove");
766
- return false;
833
+ return;
767
834
  }
768
835
  let settings;
769
836
  try {
770
837
  settings = JSON.parse(fs6.readFileSync(CLAUDE_SETTINGS_PATH2, "utf-8"));
771
838
  } catch {
772
839
  warn("Could not parse ~/.claude/settings.json");
773
- return false;
840
+ return;
774
841
  }
842
+ let changed = false;
775
843
  const hooks = settings.hooks;
776
- if (!hooks || !Array.isArray(hooks.Stop)) {
777
- warn("No Basestream hook installed");
778
- return false;
844
+ if (hooks && Array.isArray(hooks.Stop)) {
845
+ const before = hooks.Stop.length;
846
+ hooks.Stop = hooks.Stop.filter(
847
+ (entry) => !entry.hooks?.some((h) => h.command?.includes(HOOK_MARKER2))
848
+ );
849
+ if (hooks.Stop.length !== before) {
850
+ if (hooks.Stop.length === 0) delete hooks.Stop;
851
+ if (Object.keys(hooks).length === 0) delete settings.hooks;
852
+ changed = true;
853
+ }
779
854
  }
780
- const before = hooks.Stop.length;
781
- hooks.Stop = hooks.Stop.filter(
782
- (entry) => !entry.hooks?.some((h) => h.command?.includes(HOOK_MARKER2))
783
- );
784
- const after = hooks.Stop.length;
785
- if (before === after) {
786
- warn("No Basestream hook installed");
787
- return false;
855
+ const permissions = settings.permissions;
856
+ if (permissions && Array.isArray(permissions.allow)) {
857
+ const before = permissions.allow.length;
858
+ permissions.allow = permissions.allow.filter(
859
+ (rule) => !SKILL_PERMISSIONS.includes(rule)
860
+ );
861
+ if (permissions.allow.length !== before) {
862
+ if (permissions.allow.length === 0) delete permissions.allow;
863
+ if (Object.keys(permissions).length === 0) delete settings.permissions;
864
+ changed = true;
865
+ }
788
866
  }
789
- if (hooks.Stop.length === 0) {
790
- delete hooks.Stop;
867
+ if (changed) {
868
+ fs6.writeFileSync(CLAUDE_SETTINGS_PATH2, JSON.stringify(settings, null, 2));
869
+ check("Removed hook and permissions from ~/.claude/settings.json");
870
+ } else {
871
+ warn("No Basestream hook installed");
791
872
  }
792
- if (Object.keys(hooks).length === 0) {
793
- delete settings.hooks;
873
+ }
874
+ function removeSkill() {
875
+ if (fs6.existsSync(CLAUDE_SKILLS_PATH2)) {
876
+ fs6.rmSync(CLAUDE_SKILLS_PATH2);
877
+ check("Removed /bs-summarize skill from ~/.claude/skills/");
794
878
  }
795
- fs6.writeFileSync(CLAUDE_SETTINGS_PATH2, JSON.stringify(settings, null, 2));
796
- check("Removed tracking hook from ~/.claude/settings.json");
797
- return true;
879
+ }
880
+ function removeClaudeMdRule() {
881
+ 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();
885
+ fs6.writeFileSync(CLAUDE_MD_PATH2, updated ? updated + "\n" : "");
886
+ check("Removed Basestream rules from ~/.claude/CLAUDE.md");
798
887
  }
799
888
  async function uninstall() {
800
889
  console.log();
801
890
  removeClaudeCodeHook();
891
+ removeSkill();
892
+ removeClaudeMdRule();
802
893
  console.log();
803
894
  console.log(
804
895
  ` ${c.dim("Basestream will no longer track new Claude Code sessions.")}`
@@ -834,7 +925,7 @@ async function main() {
834
925
  process.exit(0);
835
926
  }
836
927
  if (command === "--version" || command === "-v") {
837
- console.log(true ? "0.2.4" : "dev");
928
+ console.log(true ? "0.2.6" : "dev");
838
929
  process.exit(0);
839
930
  }
840
931
  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.4"
21
+ "version": "0.2.6"
22
22
  }