haansi 0.1.2 → 0.1.4

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/haansi.js +216 -722
  2. package/package.json +1 -1
package/dist/haansi.js CHANGED
@@ -44,10 +44,10 @@ function prompt(question) {
44
44
  input: process.stdin,
45
45
  output: process.stdout
46
46
  });
47
- return new Promise((resolve5) => {
47
+ return new Promise((resolve4) => {
48
48
  rl.question(question, (answer) => {
49
49
  rl.close();
50
- resolve5(answer.trim());
50
+ resolve4(answer.trim());
51
51
  });
52
52
  });
53
53
  }
@@ -380,7 +380,7 @@ var require_main = __commonJS({
380
380
  return { parsed: parsedAll };
381
381
  }
382
382
  }
383
- function config6(options) {
383
+ function config5(options) {
384
384
  if (_dotenvKey(options).length === 0) {
385
385
  return DotenvModule.configDotenv(options);
386
386
  }
@@ -447,7 +447,7 @@ var require_main = __commonJS({
447
447
  configDotenv,
448
448
  _configVault,
449
449
  _parseVault,
450
- config: config6,
450
+ config: config5,
451
451
  decrypt,
452
452
  parse: parse4,
453
453
  populate
@@ -515,506 +515,6 @@ var init_logger = __esm({
515
515
  }
516
516
  });
517
517
 
518
- // ../service-capture/claude-sessions/reader.ts
519
- var import_fs, import_fs2, import_path, import_os, logger, CLAUDE_DIR, PROJECTS_DIR, ClaudeSessionReader;
520
- var init_reader = __esm({
521
- "../service-capture/claude-sessions/reader.ts"() {
522
- "use strict";
523
- import_fs = require("fs");
524
- import_fs2 = require("fs");
525
- import_path = require("path");
526
- import_os = require("os");
527
- init_logger();
528
- logger = createLogger("claude-session-reader");
529
- CLAUDE_DIR = (0, import_path.join)((0, import_os.homedir)(), ".claude");
530
- PROJECTS_DIR = (0, import_path.join)(CLAUDE_DIR, "projects");
531
- ClaudeSessionReader = class {
532
- /**
533
- * Convert a filesystem path to the ~/.claude/projects/ directory name format.
534
- * Claude Code replaces both '/' and '_' with '-'.
535
- * e.g. /Users/foo/git_repos/haansi-platform → -Users-foo-git-repos-haansi-platform
536
- */
537
- static projectDirName(absolutePath) {
538
- return absolutePath.replace(/[/_]/g, "-");
539
- }
540
- /**
541
- * Returns the ~/.claude/projects/[encoded-path]/ directory for a given project.
542
- */
543
- static getProjectSessionsDir(projectAbsolutePath) {
544
- return (0, import_path.join)(PROJECTS_DIR, this.projectDirName(projectAbsolutePath));
545
- }
546
- /**
547
- * List all project directories in ~/.claude/projects/.
548
- */
549
- listProjectDirs() {
550
- if (!(0, import_fs.existsSync)(PROJECTS_DIR)) return [];
551
- return (0, import_fs2.readdirSync)(PROJECTS_DIR).map((name) => (0, import_path.join)(PROJECTS_DIR, name)).filter((p) => (0, import_fs2.statSync)(p).isDirectory());
552
- }
553
- /**
554
- * Read and parse sessions-index.json for a given project sessions directory.
555
- * Returns null if the file doesn't exist or can't be parsed.
556
- */
557
- readSessionsIndex(sessionsDir) {
558
- const indexPath = (0, import_path.join)(sessionsDir, "sessions-index.json");
559
- if (!(0, import_fs.existsSync)(indexPath)) return null;
560
- try {
561
- const raw = (0, import_fs.readFileSync)(indexPath, "utf-8");
562
- return JSON.parse(raw);
563
- } catch (err) {
564
- logger.warn("Failed to parse sessions-index.json", {
565
- sessionsDir,
566
- err: String(err)
567
- });
568
- return null;
569
- }
570
- }
571
- /**
572
- * Parse all JSONL entries from a session file. Returns raw entries in file order.
573
- * Lines that fail to parse are skipped with a warning.
574
- */
575
- parseSessionFile(filePath) {
576
- if (!(0, import_fs.existsSync)(filePath)) {
577
- logger.warn("Session file not found", { filePath });
578
- return [];
579
- }
580
- const entries = [];
581
- const lines = (0, import_fs.readFileSync)(filePath, "utf-8").split("\n");
582
- for (let i = 0; i < lines.length; i++) {
583
- const line = lines[i].trim();
584
- if (!line) continue;
585
- try {
586
- entries.push(JSON.parse(line));
587
- } catch {
588
- }
589
- }
590
- return entries;
591
- }
592
- /**
593
- * Extract plain text from a message content value.
594
- * Handles both legacy string content and structured block arrays.
595
- */
596
- extractText(content) {
597
- if (typeof content === "string") return content;
598
- return content.filter(
599
- (b) => b.type === "text" || b.type === "thinking"
600
- ).map((b) => "text" in b ? b.text : b.thinking ?? "").join("\n").trim();
601
- }
602
- /**
603
- * Extract tool_use blocks from a message content array (assistant turns).
604
- */
605
- extractToolCalls(content) {
606
- if (typeof content === "string") return [];
607
- return content.filter(
608
- (b) => b.type === "tool_use"
609
- ).map((b) => ({
610
- id: b.id,
611
- name: b.name,
612
- input: b.input
613
- }));
614
- }
615
- /**
616
- * Extract tool_result blocks from a message content array (user turns).
617
- */
618
- extractToolResults(content) {
619
- if (typeof content === "string") return [];
620
- return content.filter(
621
- (b) => b.type === "tool_result"
622
- ).map((b) => {
623
- const rawContent = b.content;
624
- let text;
625
- if (typeof rawContent === "string") {
626
- text = rawContent;
627
- } else if (Array.isArray(rawContent)) {
628
- text = rawContent.map((item) => "text" in item ? item.text ?? "" : "").join("\n");
629
- } else {
630
- text = "";
631
- }
632
- return {
633
- toolUseId: b.tool_use_id,
634
- content: text,
635
- isError: b.is_error ?? false
636
- };
637
- });
638
- }
639
- /**
640
- * Convert raw JSONL entries into ordered ClauseTurns.
641
- * Reconstruction order: build a parentUuid → child map, then walk from roots.
642
- * Falls back to file order if the graph has cycles or disconnected nodes.
643
- */
644
- reconstructThread(entries) {
645
- const messageEntries = entries.filter(
646
- (e) => (e.type === "user" || e.type === "assistant") && e.message
647
- );
648
- if (messageEntries.length === 0) return [];
649
- const byUuid = /* @__PURE__ */ new Map();
650
- const childrenOf = /* @__PURE__ */ new Map();
651
- for (const entry of messageEntries) {
652
- byUuid.set(entry.uuid, entry);
653
- const parent = entry.parentUuid ?? "__root__";
654
- if (!childrenOf.has(parent)) childrenOf.set(parent, []);
655
- childrenOf.get(parent).push(entry.uuid);
656
- }
657
- const ordered = [];
658
- const visited = /* @__PURE__ */ new Set();
659
- const queue = childrenOf.get("__root__") ?? [];
660
- while (queue.length > 0) {
661
- const uuid3 = queue.shift();
662
- if (visited.has(uuid3)) continue;
663
- visited.add(uuid3);
664
- const entry = byUuid.get(uuid3);
665
- if (entry) {
666
- ordered.push(entry);
667
- const children = childrenOf.get(uuid3) ?? [];
668
- queue.push(...children);
669
- }
670
- }
671
- for (const entry of messageEntries) {
672
- if (!visited.has(entry.uuid)) {
673
- ordered.push(entry);
674
- }
675
- }
676
- return ordered.map((entry) => {
677
- const content = entry.message.content;
678
- return {
679
- uuid: entry.uuid,
680
- parentUuid: entry.parentUuid,
681
- role: entry.message.role,
682
- text: this.extractText(content),
683
- toolCalls: this.extractToolCalls(content),
684
- toolResults: this.extractToolResults(content),
685
- timestamp: entry.timestamp ?? (/* @__PURE__ */ new Date(0)).toISOString(),
686
- isSidechain: entry.isSidechain ?? false,
687
- agentId: entry.agentId,
688
- gitBranch: entry.gitBranch
689
- };
690
- });
691
- }
692
- /**
693
- * Read a single session JSONL file and return a ClaudeSession.
694
- * Uses index metadata when provided for faster startup (avoids full parse for summary/firstPrompt).
695
- */
696
- readSession(filePath, indexEntry) {
697
- const entries = this.parseSessionFile(filePath);
698
- const turns = this.reconstructThread(entries);
699
- const stat = (0, import_fs2.statSync)(filePath);
700
- const sessionId = indexEntry?.sessionId ?? (0, import_path.basename)(filePath, ".jsonl");
701
- const summaryEntry = entries.find((e) => e.type === "summary");
702
- const summary = indexEntry?.summary ?? summaryEntry?.summary;
703
- const firstUserTurn = turns.find(
704
- (t) => t.role === "user" && !t.isSidechain
705
- );
706
- const firstPrompt = indexEntry?.firstPrompt ?? firstUserTurn?.text?.slice(0, 500) ?? "";
707
- return {
708
- sessionId,
709
- projectPath: indexEntry?.projectPath ?? "",
710
- filePath,
711
- fileMtime: stat.mtimeMs,
712
- gitBranch: indexEntry?.gitBranch ?? turns[0]?.gitBranch,
713
- firstPrompt,
714
- summary,
715
- created: indexEntry?.created ?? turns[0]?.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
716
- modified: indexEntry?.modified ?? turns[turns.length - 1]?.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
717
- turns,
718
- subagentSessions: [],
719
- // populated separately by ingester if needed
720
- messageCount: turns.length
721
- };
722
- }
723
- /**
724
- * Read all sessions for a project directory using the sessions-index for efficiency.
725
- * Only reads JSONL files that are present and parseable.
726
- */
727
- readAllSessions(sessionsDir) {
728
- const index = this.readSessionsIndex(sessionsDir);
729
- const sessions = [];
730
- if (!index) {
731
- logger.warn(
732
- "No sessions-index.json found, falling back to directory scan",
733
- { sessionsDir }
734
- );
735
- return this.scanSessionFiles(sessionsDir);
736
- }
737
- const mainEntries = index.entries.filter((e) => !e.isSidechain);
738
- for (const entry of mainEntries) {
739
- if (!(0, import_fs.existsSync)(entry.fullPath)) continue;
740
- try {
741
- const session = this.readSession(entry.fullPath, entry);
742
- sessions.push(session);
743
- } catch (err) {
744
- logger.warn("Failed to read session", {
745
- sessionId: entry.sessionId,
746
- err: String(err)
747
- });
748
- }
749
- }
750
- logger.info(`Read ${sessions.length} sessions from index`, { sessionsDir });
751
- return sessions;
752
- }
753
- /**
754
- * Fallback: scan a directory for *.jsonl files and parse each one.
755
- * Skips the subagents/ subdirectory (handled separately).
756
- */
757
- scanSessionFiles(sessionsDir) {
758
- if (!(0, import_fs.existsSync)(sessionsDir)) return [];
759
- const sessions = [];
760
- const entries = (0, import_fs2.readdirSync)(sessionsDir);
761
- for (const name of entries) {
762
- if (!name.endsWith(".jsonl")) continue;
763
- const filePath = (0, import_path.join)(sessionsDir, name);
764
- try {
765
- sessions.push(this.readSession(filePath));
766
- } catch (err) {
767
- logger.warn("Failed to parse session file", { name, err: String(err) });
768
- }
769
- }
770
- return sessions;
771
- }
772
- };
773
- }
774
- });
775
-
776
- // ../service-capture/claude-sessions/ingester.ts
777
- async function main() {
778
- const projectPath = process.env.CLAUDE_PROJECT_PATH ?? process.cwd();
779
- const forceAll = process.argv.includes("--force-all");
780
- logger2.info("Claude Session Ingester starting", { projectPath, forceAll });
781
- const ingester = new ClaudeSessionIngester({ projectPath });
782
- const result = await ingester.scan({ forceAll });
783
- console.log("\n=== Scan Results ===");
784
- console.log(`Total sessions scanned : ${result.totalScanned}`);
785
- console.log(`New sessions : ${result.newSessions.length}`);
786
- console.log(`Updated sessions : ${result.updatedSessions.length}`);
787
- console.log(`Unchanged (skipped) : ${result.unchangedCount}`);
788
- const toProcess = [...result.newSessions, ...result.updatedSessions];
789
- if (toProcess.length === 0) {
790
- console.log("\nNo new sessions to process.");
791
- return;
792
- }
793
- console.log("\n=== Sessions to Process ===");
794
- for (const session of toProcess) {
795
- const preview = session.firstPrompt.slice(0, 100).replace(/\n/g, " ");
796
- console.log(
797
- `
798
- [${session.sessionId.slice(0, 8)}] ${session.gitBranch ?? "unknown-branch"}`
799
- );
800
- console.log(` Turns : ${session.messageCount}`);
801
- console.log(` Created : ${session.created.slice(0, 19)}`);
802
- console.log(` Modified : ${session.modified.slice(0, 19)}`);
803
- console.log(` Preview : ${preview}${preview.length === 100 ? "\u2026" : ""}`);
804
- }
805
- console.log(
806
- "\n\u2713 Reader and ingester working. Next step: resolution detection."
807
- );
808
- }
809
- var import_dotenv, import_path2, import_fs3, import_os2, logger2, DEFAULT_STATE_FILE, STATE_VERSION, ClaudeSessionIngester;
810
- var init_ingester = __esm({
811
- "../service-capture/claude-sessions/ingester.ts"() {
812
- "use strict";
813
- import_dotenv = __toESM(require_main());
814
- import_path2 = require("path");
815
- import_fs3 = require("fs");
816
- import_os2 = require("os");
817
- init_reader();
818
- init_logger();
819
- (0, import_dotenv.config)({ path: (0, import_path2.resolve)(__dirname, "../../../.env") });
820
- logger2 = createLogger("claude-session-ingester");
821
- DEFAULT_STATE_FILE = (0, import_path2.join)(
822
- (0, import_os2.homedir)(),
823
- ".claude",
824
- "haansi-mining-state.json"
825
- );
826
- STATE_VERSION = 1;
827
- ClaudeSessionIngester = class {
828
- reader;
829
- stateFile;
830
- projectPath;
831
- constructor(options) {
832
- this.reader = new ClaudeSessionReader();
833
- this.projectPath = options?.projectPath ?? process.env.CLAUDE_PROJECT_PATH ?? process.cwd();
834
- this.stateFile = options?.stateFile ?? process.env.CLAUDE_STATE_FILE ?? DEFAULT_STATE_FILE;
835
- }
836
- // ---------------------------------------------------------------------------
837
- // State management
838
- // ---------------------------------------------------------------------------
839
- loadState() {
840
- if (!(0, import_fs3.existsSync)(this.stateFile)) {
841
- return {
842
- version: STATE_VERSION,
843
- lastRunAt: (/* @__PURE__ */ new Date(0)).toISOString(),
844
- projectPath: this.projectPath,
845
- processed: {}
846
- };
847
- }
848
- try {
849
- return JSON.parse((0, import_fs3.readFileSync)(this.stateFile, "utf-8"));
850
- } catch {
851
- logger2.warn("Could not parse state file, starting fresh", {
852
- stateFile: this.stateFile
853
- });
854
- return {
855
- version: STATE_VERSION,
856
- lastRunAt: (/* @__PURE__ */ new Date(0)).toISOString(),
857
- projectPath: this.projectPath,
858
- processed: {}
859
- };
860
- }
861
- }
862
- saveState(state) {
863
- (0, import_fs3.writeFileSync)(this.stateFile, JSON.stringify(state, null, 2), "utf-8");
864
- logger2.debug("State saved", { stateFile: this.stateFile });
865
- }
866
- // ---------------------------------------------------------------------------
867
- // Core scan logic
868
- // ---------------------------------------------------------------------------
869
- /**
870
- * Scan for new or changed sessions relative to the persisted state.
871
- * Returns only sessions that need processing (new or mtime changed).
872
- */
873
- async scan(options) {
874
- const sessionsDir = ClaudeSessionReader.getProjectSessionsDir(
875
- this.projectPath
876
- );
877
- if (!(0, import_fs3.existsSync)(sessionsDir)) {
878
- logger2.warn("No Claude sessions directory found for project", {
879
- projectPath: this.projectPath,
880
- sessionsDir
881
- });
882
- return {
883
- newSessions: [],
884
- updatedSessions: [],
885
- unchangedCount: 0,
886
- totalScanned: 0
887
- };
888
- }
889
- logger2.info("Scanning for Claude sessions", { sessionsDir });
890
- return this.scanDirs([sessionsDir], options);
891
- }
892
- /**
893
- * Scan ALL project directories under ~/.claude/projects/ regardless of project path.
894
- * Returns combined results across every project.
895
- */
896
- async scanAll(options) {
897
- const dirs = this.reader.listProjectDirs();
898
- logger2.info("Scanning all Claude project directories", {
899
- count: dirs.length
900
- });
901
- return this.scanDirs(dirs, options);
902
- }
903
- async scanDirs(sessionsDirs, options) {
904
- const state = this.loadState();
905
- const newSessions = [];
906
- const updatedSessions = [];
907
- let unchangedCount = 0;
908
- let totalScanned = 0;
909
- for (const sessionsDir of sessionsDirs) {
910
- const allSessions = this.reader.readAllSessions(sessionsDir);
911
- totalScanned += allSessions.length;
912
- for (const session of allSessions) {
913
- if (session.turns.length < 2) {
914
- unchangedCount++;
915
- continue;
916
- }
917
- const record2 = state.processed[session.sessionId];
918
- if (options?.forceAll) {
919
- newSessions.push(session);
920
- continue;
921
- }
922
- if (!record2) {
923
- newSessions.push(session);
924
- } else if (session.fileMtime > record2.fileMtime) {
925
- updatedSessions.push(session);
926
- } else {
927
- unchangedCount++;
928
- }
929
- }
930
- }
931
- logger2.info("Scan complete", {
932
- dirs: sessionsDirs.length,
933
- total: totalScanned,
934
- new: newSessions.length,
935
- updated: updatedSessions.length,
936
- unchanged: unchangedCount
937
- });
938
- return { newSessions, updatedSessions, unchangedCount, totalScanned };
939
- }
940
- /**
941
- * Mark a session as processed and persist state.
942
- */
943
- /**
944
- * Return the number of lines already uploaded for this session (0 if unknown).
945
- */
946
- getUploadedLineCount(sessionId) {
947
- const state = this.loadState();
948
- return state.processed[sessionId]?.uploadedLineCount ?? 0;
949
- }
950
- /**
951
- * Return whether this session was previously finalized.
952
- */
953
- isFinalized(sessionId) {
954
- const state = this.loadState();
955
- return state.processed[sessionId]?.finalized ?? false;
956
- }
957
- /**
958
- * Return all session IDs that are tracked but not yet finalized.
959
- * These are sessions that were previously uploaded as open and may
960
- * need finalization if they no longer appear in the scan results.
961
- */
962
- getOpenSessionIds() {
963
- const state = this.loadState();
964
- return Object.values(state.processed).filter((r) => r.finalized === false).map((r) => r.sessionId);
965
- }
966
- /**
967
- * Mark a session as finalized without re-reading its file.
968
- * Used when a previously-open session is no longer receiving new lines.
969
- */
970
- markFinalized(sessionId) {
971
- const state = this.loadState();
972
- const record2 = state.processed[sessionId];
973
- if (record2) {
974
- record2.finalized = true;
975
- record2.processedAt = (/* @__PURE__ */ new Date()).toISOString();
976
- state.lastRunAt = (/* @__PURE__ */ new Date()).toISOString();
977
- this.saveState(state);
978
- }
979
- }
980
- markProcessed(session, status, extras) {
981
- const state = this.loadState();
982
- state.processed[session.sessionId] = {
983
- sessionId: session.sessionId,
984
- filePath: session.filePath,
985
- fileMtime: session.fileMtime,
986
- processedAt: (/* @__PURE__ */ new Date()).toISOString(),
987
- status,
988
- ...extras
989
- };
990
- state.lastRunAt = (/* @__PURE__ */ new Date()).toISOString();
991
- this.saveState(state);
992
- }
993
- /**
994
- * Return persisted processing records (useful for reporting).
995
- */
996
- getState() {
997
- return this.loadState();
998
- }
999
- /**
1000
- * Reset state for a specific session (forces re-processing on next scan).
1001
- */
1002
- resetSession(sessionId) {
1003
- const state = this.loadState();
1004
- delete state.processed[sessionId];
1005
- this.saveState(state);
1006
- logger2.info("Reset session state", { sessionId });
1007
- }
1008
- };
1009
- if (require.main === module) {
1010
- main().catch((err) => {
1011
- logger2.error("Ingester failed", err);
1012
- process.exit(1);
1013
- });
1014
- }
1015
- }
1016
- });
1017
-
1018
518
  // ../service-capture/claude-sessions/collector.ts
1019
519
  var collector_exports = {};
1020
520
  __export(collector_exports, {
@@ -1029,34 +529,85 @@ function edgeScrub(content) {
1029
529
  }
1030
530
  function resolveToken() {
1031
531
  if (process.env.HAANSI_TOKEN) return process.env.HAANSI_TOKEN;
1032
- if ((0, import_fs4.existsSync)(CREDENTIALS_FILE2)) {
532
+ if ((0, import_fs.existsSync)(CREDENTIALS_FILE2)) {
1033
533
  try {
1034
- const creds = JSON.parse((0, import_fs4.readFileSync)(CREDENTIALS_FILE2, "utf-8"));
534
+ const creds = JSON.parse((0, import_fs.readFileSync)(CREDENTIALS_FILE2, "utf-8"));
1035
535
  if (creds.token) return creds.token;
1036
536
  } catch {
1037
- logger3.warn("Could not parse credentials file", {
537
+ logger.warn("Could not parse credentials file", {
1038
538
  file: CREDENTIALS_FILE2
1039
539
  });
1040
540
  }
1041
541
  }
1042
542
  return null;
1043
543
  }
1044
- async function uploadSession(sessionId, jsonlContent, apiUrl, token, isOpen) {
1045
- const headers = {
1046
- Authorization: `Bearer ${token}`,
1047
- "Content-Type": "application/x-ndjson",
1048
- "x-session-id": sessionId,
1049
- "x-session-open": isOpen ? "true" : "false"
1050
- };
1051
- let body;
1052
- if (jsonlContent.length > 0) {
1053
- body = (0, import_zlib.gzipSync)(Buffer.from(jsonlContent, "utf-8"));
1054
- headers["Content-Encoding"] = "gzip";
544
+ function discoverSessions() {
545
+ if (!(0, import_fs.existsSync)(CLAUDE_PROJECTS_DIR)) return [];
546
+ const sessions = [];
547
+ const projectDirs = (0, import_fs.readdirSync)(CLAUDE_PROJECTS_DIR).map((name) => (0, import_path.join)(CLAUDE_PROJECTS_DIR, name)).filter((p) => {
548
+ try {
549
+ return (0, import_fs.statSync)(p).isDirectory();
550
+ } catch {
551
+ return false;
552
+ }
553
+ });
554
+ for (const dir of projectDirs) {
555
+ const files = (0, import_fs.readdirSync)(dir).filter((f) => f.endsWith(".jsonl"));
556
+ for (const file2 of files) {
557
+ const filePath = (0, import_path.join)(dir, file2);
558
+ const sessionId = (0, import_path.basename)(file2, ".jsonl");
559
+ try {
560
+ const stat = (0, import_fs.statSync)(filePath);
561
+ const content = (0, import_fs.readFileSync)(filePath, "utf-8");
562
+ const lineCount = content.split("\n").filter((l) => l.trim()).length;
563
+ if (lineCount >= MIN_LINES) {
564
+ sessions.push({
565
+ sessionId,
566
+ filePath,
567
+ lineCount,
568
+ sizeBytes: stat.size
569
+ });
570
+ }
571
+ } catch {
572
+ }
573
+ }
574
+ }
575
+ return sessions;
576
+ }
577
+ async function apiGet(path, apiUrl, token) {
578
+ const response = await fetch(`${apiUrl}/api/v1${path}`, {
579
+ headers: { Authorization: `Bearer ${token}` }
580
+ });
581
+ if (!response.ok) {
582
+ const text = await response.text().catch(() => "");
583
+ throw new Error(`API ${response.status}: ${text.slice(0, 200)}`);
1055
584
  }
585
+ return response.json();
586
+ }
587
+ async function getUploadedSessions(apiUrl, token) {
588
+ try {
589
+ const data = await apiGet("/sessions/uploaded", apiUrl, token);
590
+ const map2 = /* @__PURE__ */ new Map();
591
+ for (const s of data.sessions) {
592
+ map2.set(s.sessionId, s.lineCount);
593
+ }
594
+ return map2;
595
+ } catch {
596
+ logger.warn("Could not fetch uploaded sessions list, will upload all");
597
+ return /* @__PURE__ */ new Map();
598
+ }
599
+ }
600
+ async function uploadSession(sessionId, content, apiUrl, token) {
601
+ const body = (0, import_zlib.gzipSync)(Buffer.from(content, "utf-8"));
1056
602
  const response = await fetch(`${apiUrl}/api/v1/sessions/raw`, {
1057
603
  method: "POST",
1058
- headers,
1059
- body: body ? new Uint8Array(body) : void 0
604
+ headers: {
605
+ Authorization: `Bearer ${token}`,
606
+ "Content-Type": "application/x-ndjson",
607
+ "Content-Encoding": "gzip",
608
+ "x-session-id": sessionId
609
+ },
610
+ body: new Uint8Array(body)
1060
611
  });
1061
612
  if (!response.ok) {
1062
613
  const text = await response.text().catch(() => "");
@@ -1073,119 +624,61 @@ async function collect(options) {
1073
624
  "HAANSI_TOKEN is required. Set it as env var or in ~/.haansi/credentials.json"
1074
625
  );
1075
626
  }
1076
- const ingester = new ClaudeSessionIngester({
1077
- projectPath: options.projectPath
1078
- });
1079
- const scanResult = options.projectPath ? await ingester.scan({ forceAll: options.forceAll }) : await ingester.scanAll({ forceAll: options.forceAll });
1080
- const activeSessions = [
1081
- ...scanResult.newSessions,
1082
- ...scanResult.updatedSessions
1083
- ];
1084
- const activeSessionIds = new Set(activeSessions.map((s) => s.sessionId));
1085
- const sessions = options.limit ? activeSessions.slice(0, options.limit) : activeSessions;
627
+ const localSessions = discoverSessions();
628
+ logger.info(`Found ${localSessions.length} session files on disk`);
629
+ if (localSessions.length === 0) {
630
+ return { uploaded: 0, skipped: 0, errors: 0 };
631
+ }
1086
632
  let uploaded = 0;
633
+ let skipped = 0;
1087
634
  let errors = 0;
1088
- if (sessions.length > 0) {
1089
- logger3.info(`Uploading ${sessions.length} active session(s)`, {
1090
- apiUrl,
1091
- dryRun: options.dryRun ?? false
1092
- });
1093
- for (const session of sessions) {
1094
- const shortId = session.sessionId.slice(0, 8);
1095
- try {
1096
- const rawContent = (0, import_fs4.readFileSync)(session.filePath, "utf-8");
1097
- const allLines = rawContent.split("\n").filter((l) => l.trim().length > 0);
1098
- const totalLines = allLines.length;
1099
- let uploadedLineCount = ingester.getUploadedLineCount(
1100
- session.sessionId
1101
- );
1102
- const wasPreviouslyFinalized = ingester.isFinalized(session.sessionId);
1103
- if (wasPreviouslyFinalized && totalLines > uploadedLineCount) {
1104
- uploadedLineCount = 0;
1105
- }
1106
- if (totalLines < uploadedLineCount) {
1107
- uploadedLineCount = 0;
1108
- }
1109
- const deltaLines = allLines.slice(uploadedLineCount);
1110
- if (deltaLines.length === 0) {
1111
- continue;
1112
- }
1113
- const deltaContent = edgeScrub(deltaLines.join("\n") + "\n");
1114
- if (options.dryRun) {
1115
- logger3.info(
1116
- `[dry-run] Would upload delta for session ${shortId} (${deltaLines.length} new lines, open)`
1117
- );
1118
- uploaded++;
1119
- continue;
1120
- }
1121
- await uploadSession(
1122
- session.sessionId,
1123
- deltaContent,
1124
- apiUrl,
1125
- token,
1126
- true
1127
- );
1128
- ingester.markProcessed(session, "pending", {
1129
- uploadedLineCount: totalLines,
1130
- finalized: false
1131
- });
1132
- uploaded++;
1133
- logger3.info(
1134
- `delta uploaded session ${shortId} (${deltaLines.length} new lines, open)`
1135
- );
1136
- } catch (err) {
1137
- errors++;
1138
- logger3.error(`Failed to upload session ${shortId}`, {
1139
- err: err.message
1140
- });
1141
- }
635
+ const remoteMap = options.forceAll ? /* @__PURE__ */ new Map() : await getUploadedSessions(apiUrl, token);
636
+ const toUpload = [];
637
+ for (const session of localSessions) {
638
+ const remoteLine = remoteMap.get(session.sessionId);
639
+ if (remoteLine !== void 0 && remoteLine >= session.lineCount) {
640
+ skipped++;
641
+ } else {
642
+ toUpload.push(session);
1142
643
  }
1143
644
  }
1144
- const openSessionIds = ingester.getOpenSessionIds();
1145
- const toFinalize = openSessionIds.filter((id) => !activeSessionIds.has(id));
1146
- if (toFinalize.length > 0) {
1147
- logger3.info(`Finalizing ${toFinalize.length} idle session(s)`);
1148
- for (const sessionId of toFinalize) {
1149
- const shortId = sessionId.slice(0, 8);
1150
- try {
1151
- if (options.dryRun) {
1152
- logger3.info(`[dry-run] Would finalize session ${shortId}`);
1153
- uploaded++;
1154
- continue;
1155
- }
1156
- await uploadSession(sessionId, "", apiUrl, token, false);
1157
- ingester.markFinalized(sessionId);
645
+ const batch = options.limit ? toUpload.slice(0, options.limit) : toUpload;
646
+ skipped += toUpload.length - batch.length;
647
+ logger.info(
648
+ `Uploading ${batch.length} session(s), skipping ${skipped} unchanged`
649
+ );
650
+ for (const session of batch) {
651
+ const shortId = session.sessionId.slice(0, 8);
652
+ try {
653
+ const rawContent = (0, import_fs.readFileSync)(session.filePath, "utf-8");
654
+ const scrubbed = edgeScrub(rawContent);
655
+ if (options.dryRun) {
656
+ logger.info(
657
+ `[dry-run] Would upload ${shortId} (${session.lineCount} lines)`
658
+ );
1158
659
  uploaded++;
1159
- logger3.info(`finalized session ${shortId}`);
1160
- } catch (err) {
1161
- errors++;
1162
- logger3.error(`Failed to finalize session ${shortId}`, {
1163
- err: err.message
1164
- });
660
+ continue;
1165
661
  }
662
+ await uploadSession(session.sessionId, scrubbed, apiUrl, token);
663
+ uploaded++;
664
+ logger.info(`Uploaded ${shortId} (${session.lineCount} lines)`);
665
+ } catch (err) {
666
+ errors++;
667
+ logger.error(`Failed to upload ${shortId}`, { err: err.message });
1166
668
  }
1167
669
  }
1168
- if (sessions.length === 0 && toFinalize.length === 0) {
1169
- logger3.info("No new sessions to upload");
1170
- }
1171
- return { uploaded, skipped: scanResult.unchangedCount, errors };
670
+ return { uploaded, skipped, errors };
1172
671
  }
1173
- async function main2() {
672
+ async function main() {
1174
673
  const forceAll = process.argv.includes("--force-all");
1175
674
  const dryRun = process.argv.includes("--dry-run");
1176
- const projectPath = process.env.CLAUDE_PROJECT_PATH || void 0;
1177
675
  const limitIdx = process.argv.indexOf("--limit");
1178
676
  const limit = limitIdx !== -1 ? parseInt(process.argv[limitIdx + 1], 10) : void 0;
1179
- logger3.info("Collector starting", {
1180
- forceAll,
1181
- dryRun,
1182
- projectPath: projectPath ?? "all"
1183
- });
677
+ logger.info("Collector starting", { forceAll, dryRun });
1184
678
  const { uploaded, skipped, errors } = await collect({
1185
679
  forceAll,
1186
680
  dryRun,
1187
- limit,
1188
- projectPath
681
+ limit
1189
682
  });
1190
683
  console.log(
1191
684
  `
@@ -1193,21 +686,22 @@ Collector done: ${uploaded} uploaded, ${skipped} unchanged, ${errors} errors`
1193
686
  );
1194
687
  if (errors > 0) process.exit(1);
1195
688
  }
1196
- var import_dotenv2, import_path3, import_fs4, import_os3, import_zlib, logger3, DEFAULT_API_URL2, CREDENTIALS_FILE2, EDGE_SCRUB_PATTERNS;
689
+ var import_dotenv, import_path, import_fs, import_os, import_zlib, logger, DEFAULT_API_URL2, CREDENTIALS_FILE2, CLAUDE_PROJECTS_DIR, MIN_LINES, EDGE_SCRUB_PATTERNS;
1197
690
  var init_collector = __esm({
1198
691
  "../service-capture/claude-sessions/collector.ts"() {
1199
692
  "use strict";
1200
- import_dotenv2 = __toESM(require_main());
1201
- import_path3 = require("path");
1202
- import_fs4 = require("fs");
1203
- import_os3 = require("os");
693
+ import_dotenv = __toESM(require_main());
694
+ import_path = require("path");
695
+ import_fs = require("fs");
696
+ import_os = require("os");
1204
697
  import_zlib = require("zlib");
1205
- init_ingester();
1206
698
  init_logger();
1207
- (0, import_dotenv2.config)({ path: (0, import_path3.resolve)(__dirname, "../../../.env") });
1208
- logger3 = createLogger("claude-collector");
699
+ (0, import_dotenv.config)({ path: (0, import_path.resolve)(__dirname, "../../../.env") });
700
+ logger = createLogger("claude-collector");
1209
701
  DEFAULT_API_URL2 = "https://api.haansi.co";
1210
- CREDENTIALS_FILE2 = (0, import_path3.join)((0, import_os3.homedir)(), ".haansi", "credentials.json");
702
+ CREDENTIALS_FILE2 = (0, import_path.join)((0, import_os.homedir)(), ".haansi", "credentials.json");
703
+ CLAUDE_PROJECTS_DIR = (0, import_path.join)((0, import_os.homedir)(), ".claude", "projects");
704
+ MIN_LINES = 4;
1211
705
  EDGE_SCRUB_PATTERNS = [
1212
706
  {
1213
707
  pattern: /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/g,
@@ -1248,8 +742,8 @@ var init_collector = __esm({
1248
742
  }
1249
743
  ];
1250
744
  if (require.main === module) {
1251
- main2().catch((err) => {
1252
- logger3.error("Collector failed", err);
745
+ main().catch((err) => {
746
+ logger.error("Collector failed", err);
1253
747
  process.exit(1);
1254
748
  });
1255
749
  }
@@ -2170,49 +1664,49 @@ var require_node_cron = __commonJS({
2170
1664
  var collector_daemon_exports = {};
2171
1665
  async function run() {
2172
1666
  const projectPath = process.env.CLAUDE_PROJECT_PATH || void 0;
2173
- logger4.info("Collect run starting", { projectPath: projectPath ?? "all" });
1667
+ logger2.info("Collect run starting", { projectPath: projectPath ?? "all" });
2174
1668
  const start = Date.now();
2175
1669
  try {
2176
1670
  const { uploaded, skipped, errors } = await collect({ projectPath });
2177
1671
  const elapsed = ((Date.now() - start) / 1e3).toFixed(1);
2178
- logger4.info(`Collect run complete in ${elapsed}s`, {
1672
+ logger2.info(`Collect run complete in ${elapsed}s`, {
2179
1673
  uploaded,
2180
1674
  skipped,
2181
1675
  errors
2182
1676
  });
2183
1677
  } catch (err) {
2184
- logger4.error("Collect run failed", { err: err.message });
1678
+ logger2.error("Collect run failed", { err: err.message });
2185
1679
  }
2186
1680
  }
2187
- async function main3() {
2188
- logger4.info("Collector daemon starting", { schedule: SCHEDULE });
1681
+ async function main2() {
1682
+ logger2.info("Collector daemon starting", { schedule: SCHEDULE });
2189
1683
  await run();
2190
1684
  import_node_cron.default.schedule(SCHEDULE, async () => {
2191
- logger4.info("Scheduled collect run triggered");
1685
+ logger2.info("Scheduled collect run triggered");
2192
1686
  await run();
2193
1687
  });
2194
- logger4.info(`Daemon running. Next run scheduled per cron: "${SCHEDULE}"`);
1688
+ logger2.info(`Daemon running. Next run scheduled per cron: "${SCHEDULE}"`);
2195
1689
  const shutdown = async (signal) => {
2196
- logger4.info(`Received ${signal}, shutting down`);
1690
+ logger2.info(`Received ${signal}, shutting down`);
2197
1691
  process.exit(0);
2198
1692
  };
2199
1693
  process.on("SIGTERM", () => shutdown("SIGTERM"));
2200
1694
  process.on("SIGINT", () => shutdown("SIGINT"));
2201
1695
  }
2202
- var import_dotenv3, import_path4, import_node_cron, logger4, SCHEDULE;
1696
+ var import_dotenv2, import_path2, import_node_cron, logger2, SCHEDULE;
2203
1697
  var init_collector_daemon = __esm({
2204
1698
  "../service-capture/claude-sessions/collector-daemon.ts"() {
2205
1699
  "use strict";
2206
- import_dotenv3 = __toESM(require_main());
2207
- import_path4 = require("path");
1700
+ import_dotenv2 = __toESM(require_main());
1701
+ import_path2 = require("path");
2208
1702
  import_node_cron = __toESM(require_node_cron());
2209
1703
  init_collector();
2210
1704
  init_logger();
2211
- (0, import_dotenv3.config)({ path: (0, import_path4.resolve)(__dirname, "../../../.env") });
2212
- logger4 = createLogger("collector-daemon");
1705
+ (0, import_dotenv2.config)({ path: (0, import_path2.resolve)(__dirname, "../../../.env") });
1706
+ logger2 = createLogger("collector-daemon");
2213
1707
  SCHEDULE = process.env.CLAUDE_COLLECT_SCHEDULE ?? "*/30 * * * *";
2214
- main3().catch((err) => {
2215
- logger4.error("Daemon failed to start", err);
1708
+ main2().catch((err) => {
1709
+ logger2.error("Daemon failed to start", err);
2216
1710
  process.exit(1);
2217
1711
  });
2218
1712
  }
@@ -6215,7 +5709,7 @@ function $constructor(name, initializer3, params) {
6215
5709
  Object.defineProperty(_, "name", { value: name });
6216
5710
  return _;
6217
5711
  }
6218
- function config4(newConfig) {
5712
+ function config3(newConfig) {
6219
5713
  if (newConfig)
6220
5714
  Object.assign(globalConfig, newConfig);
6221
5715
  return globalConfig;
@@ -6740,10 +6234,10 @@ function prefixIssues(path, issues) {
6740
6234
  function unwrapMessage(message) {
6741
6235
  return typeof message === "string" ? message : message?.message;
6742
6236
  }
6743
- function finalizeIssue(iss, ctx, config6) {
6237
+ function finalizeIssue(iss, ctx, config5) {
6744
6238
  const full = { ...iss, path: iss.path ?? [] };
6745
6239
  if (!iss.message) {
6746
- const message = unwrapMessage(iss.inst?._zod.def?.error?.(iss)) ?? unwrapMessage(ctx?.error?.(iss)) ?? unwrapMessage(config6.customError?.(iss)) ?? unwrapMessage(config6.localeError?.(iss)) ?? "Invalid input";
6240
+ const message = unwrapMessage(iss.inst?._zod.def?.error?.(iss)) ?? unwrapMessage(ctx?.error?.(iss)) ?? unwrapMessage(config5.customError?.(iss)) ?? unwrapMessage(config5.localeError?.(iss)) ?? "Invalid input";
6747
6241
  full.message = message;
6748
6242
  }
6749
6243
  delete full.inst;
@@ -7013,7 +6507,7 @@ var init_parse2 = __esm({
7013
6507
  throw new $ZodAsyncError();
7014
6508
  }
7015
6509
  if (result.issues.length) {
7016
- const e = new (_params?.Err ?? _Err)(result.issues.map((iss) => finalizeIssue(iss, ctx, config4())));
6510
+ const e = new (_params?.Err ?? _Err)(result.issues.map((iss) => finalizeIssue(iss, ctx, config3())));
7017
6511
  captureStackTrace(e, _params?.callee);
7018
6512
  throw e;
7019
6513
  }
@@ -7026,7 +6520,7 @@ var init_parse2 = __esm({
7026
6520
  if (result instanceof Promise)
7027
6521
  result = await result;
7028
6522
  if (result.issues.length) {
7029
- const e = new (params?.Err ?? _Err)(result.issues.map((iss) => finalizeIssue(iss, ctx, config4())));
6523
+ const e = new (params?.Err ?? _Err)(result.issues.map((iss) => finalizeIssue(iss, ctx, config3())));
7030
6524
  captureStackTrace(e, params?.callee);
7031
6525
  throw e;
7032
6526
  }
@@ -7041,7 +6535,7 @@ var init_parse2 = __esm({
7041
6535
  }
7042
6536
  return result.issues.length ? {
7043
6537
  success: false,
7044
- error: new (_Err ?? $ZodError)(result.issues.map((iss) => finalizeIssue(iss, ctx, config4())))
6538
+ error: new (_Err ?? $ZodError)(result.issues.map((iss) => finalizeIssue(iss, ctx, config3())))
7045
6539
  } : { success: true, data: result.value };
7046
6540
  };
7047
6541
  safeParse = /* @__PURE__ */ _safeParse($ZodRealError);
@@ -7052,7 +6546,7 @@ var init_parse2 = __esm({
7052
6546
  result = await result;
7053
6547
  return result.issues.length ? {
7054
6548
  success: false,
7055
- error: new _Err(result.issues.map((iss) => finalizeIssue(iss, ctx, config4())))
6549
+ error: new _Err(result.issues.map((iss) => finalizeIssue(iss, ctx, config3())))
7056
6550
  } : { success: true, data: result.value };
7057
6551
  };
7058
6552
  safeParseAsync = /* @__PURE__ */ _safeParseAsync($ZodRealError);
@@ -7986,7 +7480,7 @@ function handleUnionResults(results, final, inst, ctx) {
7986
7480
  code: "invalid_union",
7987
7481
  input: final.value,
7988
7482
  inst,
7989
- errors: results.map((result) => result.issues.map((iss) => finalizeIssue(iss, ctx, config4())))
7483
+ errors: results.map((result) => result.issues.map((iss) => finalizeIssue(iss, ctx, config3())))
7990
7484
  });
7991
7485
  return final;
7992
7486
  }
@@ -8001,7 +7495,7 @@ function handleExclusiveUnionResults(results, final, inst, ctx) {
8001
7495
  code: "invalid_union",
8002
7496
  input: final.value,
8003
7497
  inst,
8004
- errors: results.map((result) => result.issues.map((iss) => finalizeIssue(iss, ctx, config4())))
7498
+ errors: results.map((result) => result.issues.map((iss) => finalizeIssue(iss, ctx, config3())))
8005
7499
  });
8006
7500
  } else {
8007
7501
  final.issues.push({
@@ -8113,7 +7607,7 @@ function handleMapResult(keyResult, valueResult, final, key, input, inst, ctx) {
8113
7607
  origin: "map",
8114
7608
  input,
8115
7609
  inst,
8116
- issues: keyResult.issues.map((iss) => finalizeIssue(iss, ctx, config4()))
7610
+ issues: keyResult.issues.map((iss) => finalizeIssue(iss, ctx, config3()))
8117
7611
  });
8118
7612
  }
8119
7613
  }
@@ -8127,7 +7621,7 @@ function handleMapResult(keyResult, valueResult, final, key, input, inst, ctx) {
8127
7621
  input,
8128
7622
  inst,
8129
7623
  key,
8130
- issues: valueResult.issues.map((iss) => finalizeIssue(iss, ctx, config4()))
7624
+ issues: valueResult.issues.map((iss) => finalizeIssue(iss, ctx, config3()))
8131
7625
  });
8132
7626
  }
8133
7627
  }
@@ -9289,7 +8783,7 @@ var init_schemas = __esm({
9289
8783
  payload.issues.push({
9290
8784
  code: "invalid_key",
9291
8785
  origin: "record",
9292
- issues: keyResult.issues.map((iss) => finalizeIssue(iss, ctx, config4())),
8786
+ issues: keyResult.issues.map((iss) => finalizeIssue(iss, ctx, config3())),
9293
8787
  input: key,
9294
8788
  path: [key],
9295
8789
  inst
@@ -9585,7 +9079,7 @@ var init_schemas = __esm({
9585
9079
  payload.value = def.catchValue({
9586
9080
  ...payload,
9587
9081
  error: {
9588
- issues: result2.issues.map((iss) => finalizeIssue(iss, ctx, config4()))
9082
+ issues: result2.issues.map((iss) => finalizeIssue(iss, ctx, config3()))
9589
9083
  },
9590
9084
  input: payload.value
9591
9085
  });
@@ -9599,7 +9093,7 @@ var init_schemas = __esm({
9599
9093
  payload.value = def.catchValue({
9600
9094
  ...payload,
9601
9095
  error: {
9602
- issues: result.issues.map((iss) => finalizeIssue(iss, ctx, config4()))
9096
+ issues: result.issues.map((iss) => finalizeIssue(iss, ctx, config3()))
9603
9097
  },
9604
9098
  input: payload.value
9605
9099
  });
@@ -13897,7 +13391,7 @@ var init_external3 = __esm({
13897
13391
  init_iso2();
13898
13392
  init_iso2();
13899
13393
  init_coerce2();
13900
- config4(en_default2());
13394
+ config3(en_default2());
13901
13395
  }
13902
13396
  });
13903
13397
 
@@ -16924,13 +16418,13 @@ var init_zodToJsonSchema = __esm({
16924
16418
  }, true) ?? parseAnyDef(refs)
16925
16419
  }), {}) : void 0;
16926
16420
  const name = typeof options === "string" ? options : options?.nameStrategy === "title" ? void 0 : options?.name;
16927
- const main5 = parseDef(schema._def, name === void 0 ? refs : {
16421
+ const main4 = parseDef(schema._def, name === void 0 ? refs : {
16928
16422
  ...refs,
16929
16423
  currentPath: [...refs.basePath, refs.definitionPath, name]
16930
16424
  }, false) ?? parseAnyDef(refs);
16931
16425
  const title = typeof options === "object" && options.name !== void 0 && options.nameStrategy === "title" ? options.name : void 0;
16932
16426
  if (title !== void 0) {
16933
- main5.title = title;
16427
+ main4.title = title;
16934
16428
  }
16935
16429
  if (refs.flags.hasReferencedOpenAiAnyType) {
16936
16430
  if (!definitions) {
@@ -16951,9 +16445,9 @@ var init_zodToJsonSchema = __esm({
16951
16445
  }
16952
16446
  }
16953
16447
  const combined = name === void 0 ? definitions ? {
16954
- ...main5,
16448
+ ...main4,
16955
16449
  [refs.definitionPath]: definitions
16956
- } : main5 : {
16450
+ } : main4 : {
16957
16451
  $ref: [
16958
16452
  ...refs.$refStrategy === "relative" ? [] : refs.basePath,
16959
16453
  refs.definitionPath,
@@ -16961,7 +16455,7 @@ var init_zodToJsonSchema = __esm({
16961
16455
  ].join("/"),
16962
16456
  [refs.definitionPath]: {
16963
16457
  ...definitions,
16964
- [name]: main5
16458
+ [name]: main4
16965
16459
  }
16966
16460
  };
16967
16461
  if (refs.target === "jsonSchema7") {
@@ -17591,7 +17085,7 @@ var init_protocol = __esm({
17591
17085
  return;
17592
17086
  }
17593
17087
  const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
17594
- await new Promise((resolve5) => setTimeout(resolve5, pollInterval));
17088
+ await new Promise((resolve4) => setTimeout(resolve4, pollInterval));
17595
17089
  options?.signal?.throwIfAborted();
17596
17090
  }
17597
17091
  } catch (error2) {
@@ -17608,7 +17102,7 @@ var init_protocol = __esm({
17608
17102
  */
17609
17103
  request(request, resultSchema, options) {
17610
17104
  const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
17611
- return new Promise((resolve5, reject) => {
17105
+ return new Promise((resolve4, reject) => {
17612
17106
  const earlyReject = (error2) => {
17613
17107
  reject(error2);
17614
17108
  };
@@ -17686,7 +17180,7 @@ var init_protocol = __esm({
17686
17180
  if (!parseResult.success) {
17687
17181
  reject(parseResult.error);
17688
17182
  } else {
17689
- resolve5(parseResult.data);
17183
+ resolve4(parseResult.data);
17690
17184
  }
17691
17185
  } catch (error2) {
17692
17186
  reject(error2);
@@ -17947,12 +17441,12 @@ var init_protocol = __esm({
17947
17441
  }
17948
17442
  } catch {
17949
17443
  }
17950
- return new Promise((resolve5, reject) => {
17444
+ return new Promise((resolve4, reject) => {
17951
17445
  if (signal.aborted) {
17952
17446
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
17953
17447
  return;
17954
17448
  }
17955
- const timeoutId = setTimeout(resolve5, interval);
17449
+ const timeoutId = setTimeout(resolve4, interval);
17956
17450
  signal.addEventListener("abort", () => {
17957
17451
  clearTimeout(timeoutId);
17958
17452
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
@@ -20979,7 +20473,7 @@ var require_compile = __commonJS({
20979
20473
  const schOrFunc = root.refs[ref];
20980
20474
  if (schOrFunc)
20981
20475
  return schOrFunc;
20982
- let _sch = resolve5.call(this, root, ref);
20476
+ let _sch = resolve4.call(this, root, ref);
20983
20477
  if (_sch === void 0) {
20984
20478
  const schema = (_a2 = root.localRefs) === null || _a2 === void 0 ? void 0 : _a2[ref];
20985
20479
  const { schemaId } = this.opts;
@@ -21006,7 +20500,7 @@ var require_compile = __commonJS({
21006
20500
  function sameSchemaEnv(s1, s2) {
21007
20501
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
21008
20502
  }
21009
- function resolve5(root, ref) {
20503
+ function resolve4(root, ref) {
21010
20504
  let sch;
21011
20505
  while (typeof (sch = this.refs[ref]) == "string")
21012
20506
  ref = sch;
@@ -21581,7 +21075,7 @@ var require_fast_uri = __commonJS({
21581
21075
  }
21582
21076
  return uri;
21583
21077
  }
21584
- function resolve5(baseURI, relativeURI, options) {
21078
+ function resolve4(baseURI, relativeURI, options) {
21585
21079
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
21586
21080
  const resolved = resolveComponent(parse4(baseURI, schemelessOptions), parse4(relativeURI, schemelessOptions), schemelessOptions, true);
21587
21081
  schemelessOptions.skipEscape = true;
@@ -21808,7 +21302,7 @@ var require_fast_uri = __commonJS({
21808
21302
  var fastUri = {
21809
21303
  SCHEMES,
21810
21304
  normalize,
21811
- resolve: resolve5,
21305
+ resolve: resolve4,
21812
21306
  resolveComponent,
21813
21307
  equal,
21814
21308
  serialize,
@@ -22363,13 +21857,13 @@ var require_core = __commonJS({
22363
21857
  }, warn() {
22364
21858
  }, error() {
22365
21859
  } };
22366
- function getLogger(logger5) {
22367
- if (logger5 === false)
21860
+ function getLogger(logger3) {
21861
+ if (logger3 === false)
22368
21862
  return noLogs;
22369
- if (logger5 === void 0)
21863
+ if (logger3 === void 0)
22370
21864
  return console;
22371
- if (logger5.log && logger5.warn && logger5.error)
22372
- return logger5;
21865
+ if (logger3.log && logger3.warn && logger3.error)
21866
+ return logger3;
22373
21867
  throw new Error("logger must implement log, warn and error methods");
22374
21868
  }
22375
21869
  var KEYWORD_NAME = /^[a-z_$][a-z0-9_$:-]*$/i;
@@ -27599,7 +27093,7 @@ var require_compile2 = __commonJS({
27599
27093
  const schOrFunc = root.refs[ref];
27600
27094
  if (schOrFunc)
27601
27095
  return schOrFunc;
27602
- let _sch = resolve5.call(this, root, ref);
27096
+ let _sch = resolve4.call(this, root, ref);
27603
27097
  if (_sch === void 0) {
27604
27098
  const schema = (_a2 = root.localRefs) === null || _a2 === void 0 ? void 0 : _a2[ref];
27605
27099
  const { schemaId } = this.opts;
@@ -27626,7 +27120,7 @@ var require_compile2 = __commonJS({
27626
27120
  function sameSchemaEnv(s1, s2) {
27627
27121
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
27628
27122
  }
27629
- function resolve5(root, ref) {
27123
+ function resolve4(root, ref) {
27630
27124
  let sch;
27631
27125
  while (typeof (sch = this.refs[ref]) == "string")
27632
27126
  ref = sch;
@@ -28261,13 +27755,13 @@ var require_core3 = __commonJS({
28261
27755
  }, warn() {
28262
27756
  }, error() {
28263
27757
  } };
28264
- function getLogger(logger5) {
28265
- if (logger5 === false)
27758
+ function getLogger(logger3) {
27759
+ if (logger3 === false)
28266
27760
  return noLogs;
28267
- if (logger5 === void 0)
27761
+ if (logger3 === void 0)
28268
27762
  return console;
28269
- if (logger5.log && logger5.warn && logger5.error)
28270
- return logger5;
27763
+ if (logger3.log && logger3.warn && logger3.error)
27764
+ return logger3;
28271
27765
  throw new Error("logger must implement log, warn and error methods");
28272
27766
  }
28273
27767
  var KEYWORD_NAME = /^[a-z_$][a-z0-9_$:-]*$/i;
@@ -31158,13 +30652,13 @@ var init_mcp_server = __esm({
31158
30652
  constructor(_mcpServer) {
31159
30653
  this._mcpServer = _mcpServer;
31160
30654
  }
31161
- registerToolTask(name, config6, handler) {
31162
- const execution = { taskSupport: "required", ...config6.execution };
30655
+ registerToolTask(name, config5, handler) {
30656
+ const execution = { taskSupport: "required", ...config5.execution };
31163
30657
  if (execution.taskSupport === "forbidden") {
31164
30658
  throw new Error(`Cannot register task-based tool '${name}' with taskSupport 'forbidden'. Use registerTool() instead.`);
31165
30659
  }
31166
30660
  const mcpServerInternal = this._mcpServer;
31167
- return mcpServerInternal._createRegisteredTool(name, config6.title, config6.description, config6.inputSchema, config6.outputSchema, config6.annotations, execution, config6._meta, handler);
30661
+ return mcpServerInternal._createRegisteredTool(name, config5.title, config5.description, config5.inputSchema, config5.outputSchema, config5.annotations, execution, config5._meta, handler);
31168
30662
  }
31169
30663
  };
31170
30664
  }
@@ -31476,7 +30970,7 @@ var init_mcp = __esm({
31476
30970
  let task = createTaskResult.task;
31477
30971
  const pollInterval = task.pollInterval ?? 5e3;
31478
30972
  while (task.status !== "completed" && task.status !== "failed" && task.status !== "cancelled") {
31479
- await new Promise((resolve5) => setTimeout(resolve5, pollInterval));
30973
+ await new Promise((resolve4) => setTimeout(resolve4, pollInterval));
31480
30974
  const updatedTask = await extra.taskStore.getTask(taskId);
31481
30975
  if (!updatedTask) {
31482
30976
  throw new McpError(ErrorCode.InternalError, `Task ${taskId} not found during polling`);
@@ -31677,12 +31171,12 @@ var init_mcp = __esm({
31677
31171
  return registeredResourceTemplate;
31678
31172
  }
31679
31173
  }
31680
- registerResource(name, uriOrTemplate, config6, readCallback) {
31174
+ registerResource(name, uriOrTemplate, config5, readCallback) {
31681
31175
  if (typeof uriOrTemplate === "string") {
31682
31176
  if (this._registeredResources[uriOrTemplate]) {
31683
31177
  throw new Error(`Resource ${uriOrTemplate} is already registered`);
31684
31178
  }
31685
- const registeredResource = this._createRegisteredResource(name, config6.title, uriOrTemplate, config6, readCallback);
31179
+ const registeredResource = this._createRegisteredResource(name, config5.title, uriOrTemplate, config5, readCallback);
31686
31180
  this.setResourceRequestHandlers();
31687
31181
  this.sendResourceListChanged();
31688
31182
  return registeredResource;
@@ -31690,7 +31184,7 @@ var init_mcp = __esm({
31690
31184
  if (this._registeredResourceTemplates[name]) {
31691
31185
  throw new Error(`Resource template ${name} is already registered`);
31692
31186
  }
31693
- const registeredResourceTemplate = this._createRegisteredResourceTemplate(name, config6.title, uriOrTemplate, config6, readCallback);
31187
+ const registeredResourceTemplate = this._createRegisteredResourceTemplate(name, config5.title, uriOrTemplate, config5, readCallback);
31694
31188
  this.setResourceRequestHandlers();
31695
31189
  this.sendResourceListChanged();
31696
31190
  return registeredResourceTemplate;
@@ -31885,11 +31379,11 @@ var init_mcp = __esm({
31885
31379
  /**
31886
31380
  * Registers a tool with a config object and callback.
31887
31381
  */
31888
- registerTool(name, config6, cb) {
31382
+ registerTool(name, config5, cb) {
31889
31383
  if (this._registeredTools[name]) {
31890
31384
  throw new Error(`Tool ${name} is already registered`);
31891
31385
  }
31892
- const { title, description, inputSchema, outputSchema, annotations, _meta } = config6;
31386
+ const { title, description, inputSchema, outputSchema, annotations, _meta } = config5;
31893
31387
  return this._createRegisteredTool(name, title, description, inputSchema, outputSchema, annotations, { taskSupport: "forbidden" }, _meta, cb);
31894
31388
  }
31895
31389
  prompt(name, ...rest) {
@@ -31913,11 +31407,11 @@ var init_mcp = __esm({
31913
31407
  /**
31914
31408
  * Registers a prompt with a config object and callback.
31915
31409
  */
31916
- registerPrompt(name, config6, cb) {
31410
+ registerPrompt(name, config5, cb) {
31917
31411
  if (this._registeredPrompts[name]) {
31918
31412
  throw new Error(`Prompt ${name} is already registered`);
31919
31413
  }
31920
- const { title, description, argsSchema } = config6;
31414
+ const { title, description, argsSchema } = config5;
31921
31415
  const registeredPrompt = this._createRegisteredPrompt(name, title, description, argsSchema, cb);
31922
31416
  this.setPromptRequestHandlers();
31923
31417
  this.sendPromptListChanged();
@@ -32067,12 +31561,12 @@ var init_stdio2 = __esm({
32067
31561
  this.onclose?.();
32068
31562
  }
32069
31563
  send(message) {
32070
- return new Promise((resolve5) => {
31564
+ return new Promise((resolve4) => {
32071
31565
  const json2 = serializeMessage(message);
32072
31566
  if (this._stdout.write(json2)) {
32073
- resolve5();
31567
+ resolve4();
32074
31568
  } else {
32075
- this._stdout.once("drain", resolve5);
31569
+ this._stdout.once("drain", resolve4);
32076
31570
  }
32077
31571
  });
32078
31572
  }
@@ -32084,9 +31578,9 @@ var init_stdio2 = __esm({
32084
31578
  var mcp_server_exports = {};
32085
31579
  function resolveToken2() {
32086
31580
  if (process.env.HAANSI_TOKEN) return process.env.HAANSI_TOKEN;
32087
- if ((0, import_fs5.existsSync)(CREDENTIALS_FILE3)) {
31581
+ if ((0, import_fs2.existsSync)(CREDENTIALS_FILE3)) {
32088
31582
  try {
32089
- const creds = JSON.parse((0, import_fs5.readFileSync)(CREDENTIALS_FILE3, "utf-8"));
31583
+ const creds = JSON.parse((0, import_fs2.readFileSync)(CREDENTIALS_FILE3, "utf-8"));
32090
31584
  if (creds.token) return creds.token;
32091
31585
  } catch {
32092
31586
  }
@@ -32112,7 +31606,7 @@ function maybeCollect() {
32112
31606
  triggerCollectInBackground();
32113
31607
  }
32114
31608
  }
32115
- async function apiGet(path) {
31609
+ async function apiGet2(path) {
32116
31610
  const response = await fetch(`${API_URL}/api/v1${path}`, {
32117
31611
  headers: { Authorization: `Bearer ${TOKEN}` }
32118
31612
  });
@@ -32122,9 +31616,9 @@ async function apiGet(path) {
32122
31616
  }
32123
31617
  return response.json();
32124
31618
  }
32125
- async function main4() {
31619
+ async function main3() {
32126
31620
  try {
32127
- await apiGet("/sessions/recent?limit=1");
31621
+ await apiGet2("/sessions/recent?limit=1");
32128
31622
  console.error("[mcp-server] API connection ok");
32129
31623
  triggerCollectInBackground();
32130
31624
  } catch (err) {
@@ -32137,21 +31631,21 @@ async function main4() {
32137
31631
  await mcpServer.connect(transport);
32138
31632
  console.error("[mcp-server] haansi-solutions MCP server running on stdio");
32139
31633
  }
32140
- var import_dotenv4, import_path5, import_fs5, import_os4, DEFAULT_API_URL3, CREDENTIALS_FILE3, API_URL, TOKEN, isCollecting, lastCollectTime, COLLECT_COOLDOWN_MS, mcpServer, server;
31634
+ var import_dotenv3, import_path3, import_fs2, import_os2, DEFAULT_API_URL3, CREDENTIALS_FILE3, API_URL, TOKEN, isCollecting, lastCollectTime, COLLECT_COOLDOWN_MS, mcpServer, server;
32141
31635
  var init_mcp_server2 = __esm({
32142
31636
  "../service-capture/claude-sessions/mcp-server.ts"() {
32143
31637
  "use strict";
32144
- import_dotenv4 = __toESM(require_main());
32145
- import_path5 = require("path");
32146
- import_fs5 = require("fs");
32147
- import_os4 = require("os");
31638
+ import_dotenv3 = __toESM(require_main());
31639
+ import_path3 = require("path");
31640
+ import_fs2 = require("fs");
31641
+ import_os2 = require("os");
32148
31642
  init_collector();
32149
31643
  init_mcp();
32150
31644
  init_stdio2();
32151
31645
  init_types2();
32152
- (0, import_dotenv4.config)({ path: (0, import_path5.resolve)(__dirname, "../../../.env") });
31646
+ (0, import_dotenv3.config)({ path: (0, import_path3.resolve)(__dirname, "../../../.env") });
32153
31647
  DEFAULT_API_URL3 = "https://api.haansi.co";
32154
- CREDENTIALS_FILE3 = (0, import_path5.join)((0, import_os4.homedir)(), ".haansi", "credentials.json");
31648
+ CREDENTIALS_FILE3 = (0, import_path3.join)((0, import_os2.homedir)(), ".haansi", "credentials.json");
32155
31649
  API_URL = process.env.HAANSI_API_URL ?? DEFAULT_API_URL3;
32156
31650
  TOKEN = resolveToken2();
32157
31651
  if (!TOKEN) {
@@ -32213,7 +31707,7 @@ var init_mcp_server2 = __esm({
32213
31707
  case "search_solutions": {
32214
31708
  const { query, top_k = 5 } = args;
32215
31709
  const k = Math.min(Math.max(1, top_k), 20);
32216
- const data = await apiGet(
31710
+ const data = await apiGet2(
32217
31711
  `/sessions/search?q=${encodeURIComponent(query)}&top_k=${k}`
32218
31712
  );
32219
31713
  if (!data.results || data.results.length === 0) {
@@ -32233,7 +31727,7 @@ var init_mcp_server2 = __esm({
32233
31727
  case "list_recent_solutions": {
32234
31728
  const { limit = 10 } = args;
32235
31729
  const n = Math.min(Math.max(1, limit), 50);
32236
- const data = await apiGet(`/sessions/recent?limit=${n}`);
31730
+ const data = await apiGet2(`/sessions/recent?limit=${n}`);
32237
31731
  if (!data.results || data.results.length === 0) {
32238
31732
  return {
32239
31733
  content: [
@@ -32259,7 +31753,7 @@ var init_mcp_server2 = __esm({
32259
31753
  };
32260
31754
  }
32261
31755
  });
32262
- main4().catch((err) => {
31756
+ main3().catch((err) => {
32263
31757
  console.error("[mcp-server] Failed to start:", err);
32264
31758
  process.exit(1);
32265
31759
  });
@@ -32272,24 +31766,24 @@ __export(setup_mcp_exports, {
32272
31766
  setupMcp: () => setupMcp
32273
31767
  });
32274
31768
  async function setupMcp() {
32275
- let config6 = {};
31769
+ let config5 = {};
32276
31770
  if ((0, import_node_fs2.existsSync)(CLAUDE_JSON)) {
32277
31771
  try {
32278
- config6 = JSON.parse((0, import_node_fs2.readFileSync)(CLAUDE_JSON, "utf-8"));
31772
+ config5 = JSON.parse((0, import_node_fs2.readFileSync)(CLAUDE_JSON, "utf-8"));
32279
31773
  } catch {
32280
31774
  console.error(
32281
31775
  `Warning: Could not parse ${CLAUDE_JSON} \u2014 will overwrite with new config.`
32282
31776
  );
32283
31777
  }
32284
31778
  }
32285
- if (!config6.mcpServers) {
32286
- config6.mcpServers = {};
31779
+ if (!config5.mcpServers) {
31780
+ config5.mcpServers = {};
32287
31781
  }
32288
- config6.mcpServers["haansi-solutions"] = {
31782
+ config5.mcpServers["haansi-solutions"] = {
32289
31783
  command: "npx",
32290
31784
  args: ["haansi", "mcp-server"]
32291
31785
  };
32292
- (0, import_node_fs2.writeFileSync)(CLAUDE_JSON, JSON.stringify(config6, null, 2) + "\n", "utf-8");
31786
+ (0, import_node_fs2.writeFileSync)(CLAUDE_JSON, JSON.stringify(config5, null, 2) + "\n", "utf-8");
32293
31787
  console.log(`MCP server configured in ${CLAUDE_JSON}`);
32294
31788
  console.log(
32295
31789
  "Restart Claude Code to activate the haansi-solutions MCP server."
@@ -32414,7 +31908,7 @@ function resolveToken3() {
32414
31908
  }
32415
31909
  throw new Error("No token found. Run `haansi init` first.");
32416
31910
  }
32417
- async function apiGet2(path, token, apiUrl) {
31911
+ async function apiGet3(path, token, apiUrl) {
32418
31912
  const response = await fetch(`${apiUrl}/api/v1${path}`, {
32419
31913
  headers: { Authorization: `Bearer ${token}` }
32420
31914
  });
@@ -32429,13 +31923,13 @@ async function list(options) {
32429
31923
  const token = resolveToken3();
32430
31924
  let data;
32431
31925
  if (options.search) {
32432
- data = await apiGet2(
31926
+ data = await apiGet3(
32433
31927
  `/sessions/search?q=${encodeURIComponent(options.search)}&top_k=${options.limit}`,
32434
31928
  token,
32435
31929
  apiUrl
32436
31930
  );
32437
31931
  } else {
32438
- data = await apiGet2(
31932
+ data = await apiGet3(
32439
31933
  `/sessions/recent?limit=${options.limit}`,
32440
31934
  token,
32441
31935
  apiUrl