haansi 0.1.1 → 0.1.3
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/haansi.js +261 -618
- 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((
|
|
47
|
+
return new Promise((resolve4) => {
|
|
48
48
|
rl.question(question, (answer) => {
|
|
49
49
|
rl.close();
|
|
50
|
-
|
|
50
|
+
resolve4(answer.trim());
|
|
51
51
|
});
|
|
52
52
|
});
|
|
53
53
|
}
|
|
@@ -65,8 +65,10 @@ async function validateToken(token, apiUrl) {
|
|
|
65
65
|
async function init() {
|
|
66
66
|
const apiUrl = process.env.HAANSI_API_URL ?? DEFAULT_API_URL;
|
|
67
67
|
console.log("Haansi CLI \u2014 Setup\n");
|
|
68
|
-
console.log(
|
|
69
|
-
`
|
|
68
|
+
console.log(
|
|
69
|
+
`You can generate an API token at https://app.haansi.co/settings/tokens
|
|
70
|
+
`
|
|
71
|
+
);
|
|
70
72
|
const token = await prompt("Paste your Haansi API token: ");
|
|
71
73
|
if (!token) {
|
|
72
74
|
console.error("Error: No token provided.");
|
|
@@ -378,7 +380,7 @@ var require_main = __commonJS({
|
|
|
378
380
|
return { parsed: parsedAll };
|
|
379
381
|
}
|
|
380
382
|
}
|
|
381
|
-
function
|
|
383
|
+
function config5(options) {
|
|
382
384
|
if (_dotenvKey(options).length === 0) {
|
|
383
385
|
return DotenvModule.configDotenv(options);
|
|
384
386
|
}
|
|
@@ -445,7 +447,7 @@ var require_main = __commonJS({
|
|
|
445
447
|
configDotenv,
|
|
446
448
|
_configVault,
|
|
447
449
|
_parseVault,
|
|
448
|
-
config:
|
|
450
|
+
config: config5,
|
|
449
451
|
decrypt,
|
|
450
452
|
parse: parse4,
|
|
451
453
|
populate
|
|
@@ -513,469 +515,6 @@ var init_logger = __esm({
|
|
|
513
515
|
}
|
|
514
516
|
});
|
|
515
517
|
|
|
516
|
-
// ../service-capture/claude-sessions/reader.ts
|
|
517
|
-
var import_fs, import_fs2, import_path, import_os, logger, CLAUDE_DIR, PROJECTS_DIR, ClaudeSessionReader;
|
|
518
|
-
var init_reader = __esm({
|
|
519
|
-
"../service-capture/claude-sessions/reader.ts"() {
|
|
520
|
-
"use strict";
|
|
521
|
-
import_fs = require("fs");
|
|
522
|
-
import_fs2 = require("fs");
|
|
523
|
-
import_path = require("path");
|
|
524
|
-
import_os = require("os");
|
|
525
|
-
init_logger();
|
|
526
|
-
logger = createLogger("claude-session-reader");
|
|
527
|
-
CLAUDE_DIR = (0, import_path.join)((0, import_os.homedir)(), ".claude");
|
|
528
|
-
PROJECTS_DIR = (0, import_path.join)(CLAUDE_DIR, "projects");
|
|
529
|
-
ClaudeSessionReader = class {
|
|
530
|
-
/**
|
|
531
|
-
* Convert a filesystem path to the ~/.claude/projects/ directory name format.
|
|
532
|
-
* Claude Code replaces both '/' and '_' with '-'.
|
|
533
|
-
* e.g. /Users/foo/git_repos/haansi-platform → -Users-foo-git-repos-haansi-platform
|
|
534
|
-
*/
|
|
535
|
-
static projectDirName(absolutePath) {
|
|
536
|
-
return absolutePath.replace(/[/_]/g, "-");
|
|
537
|
-
}
|
|
538
|
-
/**
|
|
539
|
-
* Returns the ~/.claude/projects/[encoded-path]/ directory for a given project.
|
|
540
|
-
*/
|
|
541
|
-
static getProjectSessionsDir(projectAbsolutePath) {
|
|
542
|
-
return (0, import_path.join)(PROJECTS_DIR, this.projectDirName(projectAbsolutePath));
|
|
543
|
-
}
|
|
544
|
-
/**
|
|
545
|
-
* List all project directories in ~/.claude/projects/.
|
|
546
|
-
*/
|
|
547
|
-
listProjectDirs() {
|
|
548
|
-
if (!(0, import_fs.existsSync)(PROJECTS_DIR)) return [];
|
|
549
|
-
return (0, import_fs2.readdirSync)(PROJECTS_DIR).map((name) => (0, import_path.join)(PROJECTS_DIR, name)).filter((p) => (0, import_fs2.statSync)(p).isDirectory());
|
|
550
|
-
}
|
|
551
|
-
/**
|
|
552
|
-
* Read and parse sessions-index.json for a given project sessions directory.
|
|
553
|
-
* Returns null if the file doesn't exist or can't be parsed.
|
|
554
|
-
*/
|
|
555
|
-
readSessionsIndex(sessionsDir) {
|
|
556
|
-
const indexPath = (0, import_path.join)(sessionsDir, "sessions-index.json");
|
|
557
|
-
if (!(0, import_fs.existsSync)(indexPath)) return null;
|
|
558
|
-
try {
|
|
559
|
-
const raw = (0, import_fs.readFileSync)(indexPath, "utf-8");
|
|
560
|
-
return JSON.parse(raw);
|
|
561
|
-
} catch (err) {
|
|
562
|
-
logger.warn("Failed to parse sessions-index.json", {
|
|
563
|
-
sessionsDir,
|
|
564
|
-
err: String(err)
|
|
565
|
-
});
|
|
566
|
-
return null;
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
/**
|
|
570
|
-
* Parse all JSONL entries from a session file. Returns raw entries in file order.
|
|
571
|
-
* Lines that fail to parse are skipped with a warning.
|
|
572
|
-
*/
|
|
573
|
-
parseSessionFile(filePath) {
|
|
574
|
-
if (!(0, import_fs.existsSync)(filePath)) {
|
|
575
|
-
logger.warn("Session file not found", { filePath });
|
|
576
|
-
return [];
|
|
577
|
-
}
|
|
578
|
-
const entries = [];
|
|
579
|
-
const lines = (0, import_fs.readFileSync)(filePath, "utf-8").split("\n");
|
|
580
|
-
for (let i = 0; i < lines.length; i++) {
|
|
581
|
-
const line = lines[i].trim();
|
|
582
|
-
if (!line) continue;
|
|
583
|
-
try {
|
|
584
|
-
entries.push(JSON.parse(line));
|
|
585
|
-
} catch {
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
return entries;
|
|
589
|
-
}
|
|
590
|
-
/**
|
|
591
|
-
* Extract plain text from a message content value.
|
|
592
|
-
* Handles both legacy string content and structured block arrays.
|
|
593
|
-
*/
|
|
594
|
-
extractText(content) {
|
|
595
|
-
if (typeof content === "string") return content;
|
|
596
|
-
return content.filter(
|
|
597
|
-
(b) => b.type === "text" || b.type === "thinking"
|
|
598
|
-
).map((b) => "text" in b ? b.text : b.thinking ?? "").join("\n").trim();
|
|
599
|
-
}
|
|
600
|
-
/**
|
|
601
|
-
* Extract tool_use blocks from a message content array (assistant turns).
|
|
602
|
-
*/
|
|
603
|
-
extractToolCalls(content) {
|
|
604
|
-
if (typeof content === "string") return [];
|
|
605
|
-
return content.filter(
|
|
606
|
-
(b) => b.type === "tool_use"
|
|
607
|
-
).map((b) => ({
|
|
608
|
-
id: b.id,
|
|
609
|
-
name: b.name,
|
|
610
|
-
input: b.input
|
|
611
|
-
}));
|
|
612
|
-
}
|
|
613
|
-
/**
|
|
614
|
-
* Extract tool_result blocks from a message content array (user turns).
|
|
615
|
-
*/
|
|
616
|
-
extractToolResults(content) {
|
|
617
|
-
if (typeof content === "string") return [];
|
|
618
|
-
return content.filter(
|
|
619
|
-
(b) => b.type === "tool_result"
|
|
620
|
-
).map((b) => {
|
|
621
|
-
const rawContent = b.content;
|
|
622
|
-
let text;
|
|
623
|
-
if (typeof rawContent === "string") {
|
|
624
|
-
text = rawContent;
|
|
625
|
-
} else if (Array.isArray(rawContent)) {
|
|
626
|
-
text = rawContent.map((item) => "text" in item ? item.text ?? "" : "").join("\n");
|
|
627
|
-
} else {
|
|
628
|
-
text = "";
|
|
629
|
-
}
|
|
630
|
-
return {
|
|
631
|
-
toolUseId: b.tool_use_id,
|
|
632
|
-
content: text,
|
|
633
|
-
isError: b.is_error ?? false
|
|
634
|
-
};
|
|
635
|
-
});
|
|
636
|
-
}
|
|
637
|
-
/**
|
|
638
|
-
* Convert raw JSONL entries into ordered ClauseTurns.
|
|
639
|
-
* Reconstruction order: build a parentUuid → child map, then walk from roots.
|
|
640
|
-
* Falls back to file order if the graph has cycles or disconnected nodes.
|
|
641
|
-
*/
|
|
642
|
-
reconstructThread(entries) {
|
|
643
|
-
const messageEntries = entries.filter(
|
|
644
|
-
(e) => (e.type === "user" || e.type === "assistant") && e.message
|
|
645
|
-
);
|
|
646
|
-
if (messageEntries.length === 0) return [];
|
|
647
|
-
const byUuid = /* @__PURE__ */ new Map();
|
|
648
|
-
const childrenOf = /* @__PURE__ */ new Map();
|
|
649
|
-
for (const entry of messageEntries) {
|
|
650
|
-
byUuid.set(entry.uuid, entry);
|
|
651
|
-
const parent = entry.parentUuid ?? "__root__";
|
|
652
|
-
if (!childrenOf.has(parent)) childrenOf.set(parent, []);
|
|
653
|
-
childrenOf.get(parent).push(entry.uuid);
|
|
654
|
-
}
|
|
655
|
-
const ordered = [];
|
|
656
|
-
const visited = /* @__PURE__ */ new Set();
|
|
657
|
-
const queue = childrenOf.get("__root__") ?? [];
|
|
658
|
-
while (queue.length > 0) {
|
|
659
|
-
const uuid3 = queue.shift();
|
|
660
|
-
if (visited.has(uuid3)) continue;
|
|
661
|
-
visited.add(uuid3);
|
|
662
|
-
const entry = byUuid.get(uuid3);
|
|
663
|
-
if (entry) {
|
|
664
|
-
ordered.push(entry);
|
|
665
|
-
const children = childrenOf.get(uuid3) ?? [];
|
|
666
|
-
queue.push(...children);
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
for (const entry of messageEntries) {
|
|
670
|
-
if (!visited.has(entry.uuid)) {
|
|
671
|
-
ordered.push(entry);
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
return ordered.map((entry) => {
|
|
675
|
-
const content = entry.message.content;
|
|
676
|
-
return {
|
|
677
|
-
uuid: entry.uuid,
|
|
678
|
-
parentUuid: entry.parentUuid,
|
|
679
|
-
role: entry.message.role,
|
|
680
|
-
text: this.extractText(content),
|
|
681
|
-
toolCalls: this.extractToolCalls(content),
|
|
682
|
-
toolResults: this.extractToolResults(content),
|
|
683
|
-
timestamp: entry.timestamp ?? (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
684
|
-
isSidechain: entry.isSidechain ?? false,
|
|
685
|
-
agentId: entry.agentId,
|
|
686
|
-
gitBranch: entry.gitBranch
|
|
687
|
-
};
|
|
688
|
-
});
|
|
689
|
-
}
|
|
690
|
-
/**
|
|
691
|
-
* Read a single session JSONL file and return a ClaudeSession.
|
|
692
|
-
* Uses index metadata when provided for faster startup (avoids full parse for summary/firstPrompt).
|
|
693
|
-
*/
|
|
694
|
-
readSession(filePath, indexEntry) {
|
|
695
|
-
const entries = this.parseSessionFile(filePath);
|
|
696
|
-
const turns = this.reconstructThread(entries);
|
|
697
|
-
const stat = (0, import_fs2.statSync)(filePath);
|
|
698
|
-
const sessionId = indexEntry?.sessionId ?? (0, import_path.basename)(filePath, ".jsonl");
|
|
699
|
-
const summaryEntry = entries.find((e) => e.type === "summary");
|
|
700
|
-
const summary = indexEntry?.summary ?? summaryEntry?.summary;
|
|
701
|
-
const firstUserTurn = turns.find(
|
|
702
|
-
(t) => t.role === "user" && !t.isSidechain
|
|
703
|
-
);
|
|
704
|
-
const firstPrompt = indexEntry?.firstPrompt ?? firstUserTurn?.text?.slice(0, 500) ?? "";
|
|
705
|
-
return {
|
|
706
|
-
sessionId,
|
|
707
|
-
projectPath: indexEntry?.projectPath ?? "",
|
|
708
|
-
filePath,
|
|
709
|
-
fileMtime: stat.mtimeMs,
|
|
710
|
-
gitBranch: indexEntry?.gitBranch ?? turns[0]?.gitBranch,
|
|
711
|
-
firstPrompt,
|
|
712
|
-
summary,
|
|
713
|
-
created: indexEntry?.created ?? turns[0]?.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
714
|
-
modified: indexEntry?.modified ?? turns[turns.length - 1]?.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
715
|
-
turns,
|
|
716
|
-
subagentSessions: [],
|
|
717
|
-
// populated separately by ingester if needed
|
|
718
|
-
messageCount: turns.length
|
|
719
|
-
};
|
|
720
|
-
}
|
|
721
|
-
/**
|
|
722
|
-
* Read all sessions for a project directory using the sessions-index for efficiency.
|
|
723
|
-
* Only reads JSONL files that are present and parseable.
|
|
724
|
-
*/
|
|
725
|
-
readAllSessions(sessionsDir) {
|
|
726
|
-
const index = this.readSessionsIndex(sessionsDir);
|
|
727
|
-
const sessions = [];
|
|
728
|
-
if (!index) {
|
|
729
|
-
logger.warn(
|
|
730
|
-
"No sessions-index.json found, falling back to directory scan",
|
|
731
|
-
{ sessionsDir }
|
|
732
|
-
);
|
|
733
|
-
return this.scanSessionFiles(sessionsDir);
|
|
734
|
-
}
|
|
735
|
-
const mainEntries = index.entries.filter((e) => !e.isSidechain);
|
|
736
|
-
for (const entry of mainEntries) {
|
|
737
|
-
if (!(0, import_fs.existsSync)(entry.fullPath)) continue;
|
|
738
|
-
try {
|
|
739
|
-
const session = this.readSession(entry.fullPath, entry);
|
|
740
|
-
sessions.push(session);
|
|
741
|
-
} catch (err) {
|
|
742
|
-
logger.warn("Failed to read session", {
|
|
743
|
-
sessionId: entry.sessionId,
|
|
744
|
-
err: String(err)
|
|
745
|
-
});
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
logger.info(`Read ${sessions.length} sessions from index`, { sessionsDir });
|
|
749
|
-
return sessions;
|
|
750
|
-
}
|
|
751
|
-
/**
|
|
752
|
-
* Fallback: scan a directory for *.jsonl files and parse each one.
|
|
753
|
-
* Skips the subagents/ subdirectory (handled separately).
|
|
754
|
-
*/
|
|
755
|
-
scanSessionFiles(sessionsDir) {
|
|
756
|
-
if (!(0, import_fs.existsSync)(sessionsDir)) return [];
|
|
757
|
-
const sessions = [];
|
|
758
|
-
const entries = (0, import_fs2.readdirSync)(sessionsDir);
|
|
759
|
-
for (const name of entries) {
|
|
760
|
-
if (!name.endsWith(".jsonl")) continue;
|
|
761
|
-
const filePath = (0, import_path.join)(sessionsDir, name);
|
|
762
|
-
try {
|
|
763
|
-
sessions.push(this.readSession(filePath));
|
|
764
|
-
} catch (err) {
|
|
765
|
-
logger.warn("Failed to parse session file", { name, err: String(err) });
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
return sessions;
|
|
769
|
-
}
|
|
770
|
-
};
|
|
771
|
-
}
|
|
772
|
-
});
|
|
773
|
-
|
|
774
|
-
// ../service-capture/claude-sessions/ingester.ts
|
|
775
|
-
async function main() {
|
|
776
|
-
const projectPath = process.env.CLAUDE_PROJECT_PATH ?? process.cwd();
|
|
777
|
-
const forceAll = process.argv.includes("--force-all");
|
|
778
|
-
logger2.info("Claude Session Ingester starting", { projectPath, forceAll });
|
|
779
|
-
const ingester = new ClaudeSessionIngester({ projectPath });
|
|
780
|
-
const result = await ingester.scan({ forceAll });
|
|
781
|
-
console.log("\n=== Scan Results ===");
|
|
782
|
-
console.log(`Total sessions scanned : ${result.totalScanned}`);
|
|
783
|
-
console.log(`New sessions : ${result.newSessions.length}`);
|
|
784
|
-
console.log(`Updated sessions : ${result.updatedSessions.length}`);
|
|
785
|
-
console.log(`Unchanged (skipped) : ${result.unchangedCount}`);
|
|
786
|
-
const toProcess = [...result.newSessions, ...result.updatedSessions];
|
|
787
|
-
if (toProcess.length === 0) {
|
|
788
|
-
console.log("\nNo new sessions to process.");
|
|
789
|
-
return;
|
|
790
|
-
}
|
|
791
|
-
console.log("\n=== Sessions to Process ===");
|
|
792
|
-
for (const session of toProcess) {
|
|
793
|
-
const preview = session.firstPrompt.slice(0, 100).replace(/\n/g, " ");
|
|
794
|
-
console.log(
|
|
795
|
-
`
|
|
796
|
-
[${session.sessionId.slice(0, 8)}] ${session.gitBranch ?? "unknown-branch"}`
|
|
797
|
-
);
|
|
798
|
-
console.log(` Turns : ${session.messageCount}`);
|
|
799
|
-
console.log(` Created : ${session.created.slice(0, 19)}`);
|
|
800
|
-
console.log(` Modified : ${session.modified.slice(0, 19)}`);
|
|
801
|
-
console.log(` Preview : ${preview}${preview.length === 100 ? "\u2026" : ""}`);
|
|
802
|
-
}
|
|
803
|
-
console.log(
|
|
804
|
-
"\n\u2713 Reader and ingester working. Next step: resolution detection."
|
|
805
|
-
);
|
|
806
|
-
}
|
|
807
|
-
var import_dotenv, import_path2, import_fs3, import_os2, logger2, DEFAULT_STATE_FILE, STATE_VERSION, ClaudeSessionIngester;
|
|
808
|
-
var init_ingester = __esm({
|
|
809
|
-
"../service-capture/claude-sessions/ingester.ts"() {
|
|
810
|
-
"use strict";
|
|
811
|
-
import_dotenv = __toESM(require_main());
|
|
812
|
-
import_path2 = require("path");
|
|
813
|
-
import_fs3 = require("fs");
|
|
814
|
-
import_os2 = require("os");
|
|
815
|
-
init_reader();
|
|
816
|
-
init_logger();
|
|
817
|
-
(0, import_dotenv.config)({ path: (0, import_path2.resolve)(__dirname, "../../../.env") });
|
|
818
|
-
logger2 = createLogger("claude-session-ingester");
|
|
819
|
-
DEFAULT_STATE_FILE = (0, import_path2.join)(
|
|
820
|
-
(0, import_os2.homedir)(),
|
|
821
|
-
".claude",
|
|
822
|
-
"haansi-mining-state.json"
|
|
823
|
-
);
|
|
824
|
-
STATE_VERSION = 1;
|
|
825
|
-
ClaudeSessionIngester = class {
|
|
826
|
-
reader;
|
|
827
|
-
stateFile;
|
|
828
|
-
projectPath;
|
|
829
|
-
constructor(options) {
|
|
830
|
-
this.reader = new ClaudeSessionReader();
|
|
831
|
-
this.projectPath = options?.projectPath ?? process.env.CLAUDE_PROJECT_PATH ?? process.cwd();
|
|
832
|
-
this.stateFile = options?.stateFile ?? process.env.CLAUDE_STATE_FILE ?? DEFAULT_STATE_FILE;
|
|
833
|
-
}
|
|
834
|
-
// ---------------------------------------------------------------------------
|
|
835
|
-
// State management
|
|
836
|
-
// ---------------------------------------------------------------------------
|
|
837
|
-
loadState() {
|
|
838
|
-
if (!(0, import_fs3.existsSync)(this.stateFile)) {
|
|
839
|
-
return {
|
|
840
|
-
version: STATE_VERSION,
|
|
841
|
-
lastRunAt: (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
842
|
-
projectPath: this.projectPath,
|
|
843
|
-
processed: {}
|
|
844
|
-
};
|
|
845
|
-
}
|
|
846
|
-
try {
|
|
847
|
-
return JSON.parse((0, import_fs3.readFileSync)(this.stateFile, "utf-8"));
|
|
848
|
-
} catch {
|
|
849
|
-
logger2.warn("Could not parse state file, starting fresh", {
|
|
850
|
-
stateFile: this.stateFile
|
|
851
|
-
});
|
|
852
|
-
return {
|
|
853
|
-
version: STATE_VERSION,
|
|
854
|
-
lastRunAt: (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
855
|
-
projectPath: this.projectPath,
|
|
856
|
-
processed: {}
|
|
857
|
-
};
|
|
858
|
-
}
|
|
859
|
-
}
|
|
860
|
-
saveState(state) {
|
|
861
|
-
(0, import_fs3.writeFileSync)(this.stateFile, JSON.stringify(state, null, 2), "utf-8");
|
|
862
|
-
logger2.debug("State saved", { stateFile: this.stateFile });
|
|
863
|
-
}
|
|
864
|
-
// ---------------------------------------------------------------------------
|
|
865
|
-
// Core scan logic
|
|
866
|
-
// ---------------------------------------------------------------------------
|
|
867
|
-
/**
|
|
868
|
-
* Scan for new or changed sessions relative to the persisted state.
|
|
869
|
-
* Returns only sessions that need processing (new or mtime changed).
|
|
870
|
-
*/
|
|
871
|
-
async scan(options) {
|
|
872
|
-
const sessionsDir = ClaudeSessionReader.getProjectSessionsDir(
|
|
873
|
-
this.projectPath
|
|
874
|
-
);
|
|
875
|
-
if (!(0, import_fs3.existsSync)(sessionsDir)) {
|
|
876
|
-
logger2.warn("No Claude sessions directory found for project", {
|
|
877
|
-
projectPath: this.projectPath,
|
|
878
|
-
sessionsDir
|
|
879
|
-
});
|
|
880
|
-
return {
|
|
881
|
-
newSessions: [],
|
|
882
|
-
updatedSessions: [],
|
|
883
|
-
unchangedCount: 0,
|
|
884
|
-
totalScanned: 0
|
|
885
|
-
};
|
|
886
|
-
}
|
|
887
|
-
logger2.info("Scanning for Claude sessions", { sessionsDir });
|
|
888
|
-
return this.scanDirs([sessionsDir], options);
|
|
889
|
-
}
|
|
890
|
-
/**
|
|
891
|
-
* Scan ALL project directories under ~/.claude/projects/ regardless of project path.
|
|
892
|
-
* Returns combined results across every project.
|
|
893
|
-
*/
|
|
894
|
-
async scanAll(options) {
|
|
895
|
-
const dirs = this.reader.listProjectDirs();
|
|
896
|
-
logger2.info("Scanning all Claude project directories", {
|
|
897
|
-
count: dirs.length
|
|
898
|
-
});
|
|
899
|
-
return this.scanDirs(dirs, options);
|
|
900
|
-
}
|
|
901
|
-
async scanDirs(sessionsDirs, options) {
|
|
902
|
-
const state = this.loadState();
|
|
903
|
-
const newSessions = [];
|
|
904
|
-
const updatedSessions = [];
|
|
905
|
-
let unchangedCount = 0;
|
|
906
|
-
let totalScanned = 0;
|
|
907
|
-
for (const sessionsDir of sessionsDirs) {
|
|
908
|
-
const allSessions = this.reader.readAllSessions(sessionsDir);
|
|
909
|
-
totalScanned += allSessions.length;
|
|
910
|
-
for (const session of allSessions) {
|
|
911
|
-
if (session.turns.length < 2) {
|
|
912
|
-
unchangedCount++;
|
|
913
|
-
continue;
|
|
914
|
-
}
|
|
915
|
-
const record2 = state.processed[session.sessionId];
|
|
916
|
-
if (options?.forceAll) {
|
|
917
|
-
newSessions.push(session);
|
|
918
|
-
continue;
|
|
919
|
-
}
|
|
920
|
-
if (!record2) {
|
|
921
|
-
newSessions.push(session);
|
|
922
|
-
} else if (session.fileMtime > record2.fileMtime) {
|
|
923
|
-
updatedSessions.push(session);
|
|
924
|
-
} else {
|
|
925
|
-
unchangedCount++;
|
|
926
|
-
}
|
|
927
|
-
}
|
|
928
|
-
}
|
|
929
|
-
logger2.info("Scan complete", {
|
|
930
|
-
dirs: sessionsDirs.length,
|
|
931
|
-
total: totalScanned,
|
|
932
|
-
new: newSessions.length,
|
|
933
|
-
updated: updatedSessions.length,
|
|
934
|
-
unchanged: unchangedCount
|
|
935
|
-
});
|
|
936
|
-
return { newSessions, updatedSessions, unchangedCount, totalScanned };
|
|
937
|
-
}
|
|
938
|
-
/**
|
|
939
|
-
* Mark a session as processed and persist state.
|
|
940
|
-
*/
|
|
941
|
-
markProcessed(session, status, extras) {
|
|
942
|
-
const state = this.loadState();
|
|
943
|
-
state.processed[session.sessionId] = {
|
|
944
|
-
sessionId: session.sessionId,
|
|
945
|
-
filePath: session.filePath,
|
|
946
|
-
fileMtime: session.fileMtime,
|
|
947
|
-
processedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
948
|
-
status,
|
|
949
|
-
...extras
|
|
950
|
-
};
|
|
951
|
-
state.lastRunAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
952
|
-
this.saveState(state);
|
|
953
|
-
}
|
|
954
|
-
/**
|
|
955
|
-
* Return persisted processing records (useful for reporting).
|
|
956
|
-
*/
|
|
957
|
-
getState() {
|
|
958
|
-
return this.loadState();
|
|
959
|
-
}
|
|
960
|
-
/**
|
|
961
|
-
* Reset state for a specific session (forces re-processing on next scan).
|
|
962
|
-
*/
|
|
963
|
-
resetSession(sessionId) {
|
|
964
|
-
const state = this.loadState();
|
|
965
|
-
delete state.processed[sessionId];
|
|
966
|
-
this.saveState(state);
|
|
967
|
-
logger2.info("Reset session state", { sessionId });
|
|
968
|
-
}
|
|
969
|
-
};
|
|
970
|
-
if (require.main === module) {
|
|
971
|
-
main().catch((err) => {
|
|
972
|
-
logger2.error("Ingester failed", err);
|
|
973
|
-
process.exit(1);
|
|
974
|
-
});
|
|
975
|
-
}
|
|
976
|
-
}
|
|
977
|
-
});
|
|
978
|
-
|
|
979
518
|
// ../service-capture/claude-sessions/collector.ts
|
|
980
519
|
var collector_exports = {};
|
|
981
520
|
__export(collector_exports, {
|
|
@@ -990,20 +529,76 @@ function edgeScrub(content) {
|
|
|
990
529
|
}
|
|
991
530
|
function resolveToken() {
|
|
992
531
|
if (process.env.HAANSI_TOKEN) return process.env.HAANSI_TOKEN;
|
|
993
|
-
if ((0,
|
|
532
|
+
if ((0, import_fs.existsSync)(CREDENTIALS_FILE2)) {
|
|
994
533
|
try {
|
|
995
|
-
const creds = JSON.parse((0,
|
|
534
|
+
const creds = JSON.parse((0, import_fs.readFileSync)(CREDENTIALS_FILE2, "utf-8"));
|
|
996
535
|
if (creds.token) return creds.token;
|
|
997
536
|
} catch {
|
|
998
|
-
|
|
537
|
+
logger.warn("Could not parse credentials file", {
|
|
999
538
|
file: CREDENTIALS_FILE2
|
|
1000
539
|
});
|
|
1001
540
|
}
|
|
1002
541
|
}
|
|
1003
542
|
return null;
|
|
1004
543
|
}
|
|
1005
|
-
|
|
1006
|
-
|
|
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)}`);
|
|
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"));
|
|
1007
602
|
const response = await fetch(`${apiUrl}/api/v1/sessions/raw`, {
|
|
1008
603
|
method: "POST",
|
|
1009
604
|
headers: {
|
|
@@ -1012,7 +607,7 @@ async function uploadSession(sessionId, jsonlContent, apiUrl, token) {
|
|
|
1012
607
|
"Content-Encoding": "gzip",
|
|
1013
608
|
"x-session-id": sessionId
|
|
1014
609
|
},
|
|
1015
|
-
body
|
|
610
|
+
body: new Uint8Array(body)
|
|
1016
611
|
});
|
|
1017
612
|
if (!response.ok) {
|
|
1018
613
|
const text = await response.text().catch(() => "");
|
|
@@ -1029,61 +624,61 @@ async function collect(options) {
|
|
|
1029
624
|
"HAANSI_TOKEN is required. Set it as env var or in ~/.haansi/credentials.json"
|
|
1030
625
|
);
|
|
1031
626
|
}
|
|
1032
|
-
const
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
const sessions = options.limit ? toUpload.slice(0, options.limit) : toUpload;
|
|
1038
|
-
if (sessions.length === 0) {
|
|
1039
|
-
logger3.info("No new sessions to upload");
|
|
1040
|
-
return { uploaded: 0, skipped: scanResult.unchangedCount, errors: 0 };
|
|
1041
|
-
}
|
|
1042
|
-
logger3.info(`Uploading ${sessions.length} session(s)`, {
|
|
1043
|
-
apiUrl,
|
|
1044
|
-
dryRun: options.dryRun ?? false
|
|
1045
|
-
});
|
|
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
|
+
}
|
|
1046
632
|
let uploaded = 0;
|
|
633
|
+
let skipped = 0;
|
|
1047
634
|
let errors = 0;
|
|
1048
|
-
|
|
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);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
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) {
|
|
1049
651
|
const shortId = session.sessionId.slice(0, 8);
|
|
1050
652
|
try {
|
|
1051
|
-
const rawContent = (0,
|
|
653
|
+
const rawContent = (0, import_fs.readFileSync)(session.filePath, "utf-8");
|
|
1052
654
|
const scrubbed = edgeScrub(rawContent);
|
|
1053
655
|
if (options.dryRun) {
|
|
1054
|
-
|
|
1055
|
-
`[dry-run] Would upload
|
|
656
|
+
logger.info(
|
|
657
|
+
`[dry-run] Would upload ${shortId} (${session.lineCount} lines)`
|
|
1056
658
|
);
|
|
1057
659
|
uploaded++;
|
|
1058
660
|
continue;
|
|
1059
661
|
}
|
|
1060
662
|
await uploadSession(session.sessionId, scrubbed, apiUrl, token);
|
|
1061
|
-
ingester.markProcessed(session, "pending", {});
|
|
1062
663
|
uploaded++;
|
|
1063
|
-
|
|
664
|
+
logger.info(`Uploaded ${shortId} (${session.lineCount} lines)`);
|
|
1064
665
|
} catch (err) {
|
|
1065
666
|
errors++;
|
|
1066
|
-
|
|
667
|
+
logger.error(`Failed to upload ${shortId}`, { err: err.message });
|
|
1067
668
|
}
|
|
1068
669
|
}
|
|
1069
|
-
return { uploaded, skipped
|
|
670
|
+
return { uploaded, skipped, errors };
|
|
1070
671
|
}
|
|
1071
|
-
async function
|
|
672
|
+
async function main() {
|
|
1072
673
|
const forceAll = process.argv.includes("--force-all");
|
|
1073
674
|
const dryRun = process.argv.includes("--dry-run");
|
|
1074
|
-
const projectPath = process.env.CLAUDE_PROJECT_PATH || void 0;
|
|
1075
675
|
const limitIdx = process.argv.indexOf("--limit");
|
|
1076
676
|
const limit = limitIdx !== -1 ? parseInt(process.argv[limitIdx + 1], 10) : void 0;
|
|
1077
|
-
|
|
1078
|
-
forceAll,
|
|
1079
|
-
dryRun,
|
|
1080
|
-
projectPath: projectPath ?? "all"
|
|
1081
|
-
});
|
|
677
|
+
logger.info("Collector starting", { forceAll, dryRun });
|
|
1082
678
|
const { uploaded, skipped, errors } = await collect({
|
|
1083
679
|
forceAll,
|
|
1084
680
|
dryRun,
|
|
1085
|
-
limit
|
|
1086
|
-
projectPath
|
|
681
|
+
limit
|
|
1087
682
|
});
|
|
1088
683
|
console.log(
|
|
1089
684
|
`
|
|
@@ -1091,21 +686,22 @@ Collector done: ${uploaded} uploaded, ${skipped} unchanged, ${errors} errors`
|
|
|
1091
686
|
);
|
|
1092
687
|
if (errors > 0) process.exit(1);
|
|
1093
688
|
}
|
|
1094
|
-
var
|
|
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;
|
|
1095
690
|
var init_collector = __esm({
|
|
1096
691
|
"../service-capture/claude-sessions/collector.ts"() {
|
|
1097
692
|
"use strict";
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
693
|
+
import_dotenv = __toESM(require_main());
|
|
694
|
+
import_path = require("path");
|
|
695
|
+
import_fs = require("fs");
|
|
696
|
+
import_os = require("os");
|
|
1102
697
|
import_zlib = require("zlib");
|
|
1103
|
-
init_ingester();
|
|
1104
698
|
init_logger();
|
|
1105
|
-
(0,
|
|
1106
|
-
|
|
699
|
+
(0, import_dotenv.config)({ path: (0, import_path.resolve)(__dirname, "../../../.env") });
|
|
700
|
+
logger = createLogger("claude-collector");
|
|
1107
701
|
DEFAULT_API_URL2 = "https://api.haansi.co";
|
|
1108
|
-
CREDENTIALS_FILE2 = (0,
|
|
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;
|
|
1109
705
|
EDGE_SCRUB_PATTERNS = [
|
|
1110
706
|
{
|
|
1111
707
|
pattern: /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/g,
|
|
@@ -1146,8 +742,8 @@ var init_collector = __esm({
|
|
|
1146
742
|
}
|
|
1147
743
|
];
|
|
1148
744
|
if (require.main === module) {
|
|
1149
|
-
|
|
1150
|
-
|
|
745
|
+
main().catch((err) => {
|
|
746
|
+
logger.error("Collector failed", err);
|
|
1151
747
|
process.exit(1);
|
|
1152
748
|
});
|
|
1153
749
|
}
|
|
@@ -2068,49 +1664,49 @@ var require_node_cron = __commonJS({
|
|
|
2068
1664
|
var collector_daemon_exports = {};
|
|
2069
1665
|
async function run() {
|
|
2070
1666
|
const projectPath = process.env.CLAUDE_PROJECT_PATH || void 0;
|
|
2071
|
-
|
|
1667
|
+
logger2.info("Collect run starting", { projectPath: projectPath ?? "all" });
|
|
2072
1668
|
const start = Date.now();
|
|
2073
1669
|
try {
|
|
2074
1670
|
const { uploaded, skipped, errors } = await collect({ projectPath });
|
|
2075
1671
|
const elapsed = ((Date.now() - start) / 1e3).toFixed(1);
|
|
2076
|
-
|
|
1672
|
+
logger2.info(`Collect run complete in ${elapsed}s`, {
|
|
2077
1673
|
uploaded,
|
|
2078
1674
|
skipped,
|
|
2079
1675
|
errors
|
|
2080
1676
|
});
|
|
2081
1677
|
} catch (err) {
|
|
2082
|
-
|
|
1678
|
+
logger2.error("Collect run failed", { err: err.message });
|
|
2083
1679
|
}
|
|
2084
1680
|
}
|
|
2085
|
-
async function
|
|
2086
|
-
|
|
1681
|
+
async function main2() {
|
|
1682
|
+
logger2.info("Collector daemon starting", { schedule: SCHEDULE });
|
|
2087
1683
|
await run();
|
|
2088
1684
|
import_node_cron.default.schedule(SCHEDULE, async () => {
|
|
2089
|
-
|
|
1685
|
+
logger2.info("Scheduled collect run triggered");
|
|
2090
1686
|
await run();
|
|
2091
1687
|
});
|
|
2092
|
-
|
|
1688
|
+
logger2.info(`Daemon running. Next run scheduled per cron: "${SCHEDULE}"`);
|
|
2093
1689
|
const shutdown = async (signal) => {
|
|
2094
|
-
|
|
1690
|
+
logger2.info(`Received ${signal}, shutting down`);
|
|
2095
1691
|
process.exit(0);
|
|
2096
1692
|
};
|
|
2097
1693
|
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
2098
1694
|
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
2099
1695
|
}
|
|
2100
|
-
var
|
|
1696
|
+
var import_dotenv2, import_path2, import_node_cron, logger2, SCHEDULE;
|
|
2101
1697
|
var init_collector_daemon = __esm({
|
|
2102
1698
|
"../service-capture/claude-sessions/collector-daemon.ts"() {
|
|
2103
1699
|
"use strict";
|
|
2104
|
-
|
|
2105
|
-
|
|
1700
|
+
import_dotenv2 = __toESM(require_main());
|
|
1701
|
+
import_path2 = require("path");
|
|
2106
1702
|
import_node_cron = __toESM(require_node_cron());
|
|
2107
1703
|
init_collector();
|
|
2108
1704
|
init_logger();
|
|
2109
|
-
(0,
|
|
2110
|
-
|
|
1705
|
+
(0, import_dotenv2.config)({ path: (0, import_path2.resolve)(__dirname, "../../../.env") });
|
|
1706
|
+
logger2 = createLogger("collector-daemon");
|
|
2111
1707
|
SCHEDULE = process.env.CLAUDE_COLLECT_SCHEDULE ?? "*/30 * * * *";
|
|
2112
|
-
|
|
2113
|
-
|
|
1708
|
+
main2().catch((err) => {
|
|
1709
|
+
logger2.error("Daemon failed to start", err);
|
|
2114
1710
|
process.exit(1);
|
|
2115
1711
|
});
|
|
2116
1712
|
}
|
|
@@ -6113,7 +5709,7 @@ function $constructor(name, initializer3, params) {
|
|
|
6113
5709
|
Object.defineProperty(_, "name", { value: name });
|
|
6114
5710
|
return _;
|
|
6115
5711
|
}
|
|
6116
|
-
function
|
|
5712
|
+
function config3(newConfig) {
|
|
6117
5713
|
if (newConfig)
|
|
6118
5714
|
Object.assign(globalConfig, newConfig);
|
|
6119
5715
|
return globalConfig;
|
|
@@ -6638,10 +6234,10 @@ function prefixIssues(path, issues) {
|
|
|
6638
6234
|
function unwrapMessage(message) {
|
|
6639
6235
|
return typeof message === "string" ? message : message?.message;
|
|
6640
6236
|
}
|
|
6641
|
-
function finalizeIssue(iss, ctx,
|
|
6237
|
+
function finalizeIssue(iss, ctx, config5) {
|
|
6642
6238
|
const full = { ...iss, path: iss.path ?? [] };
|
|
6643
6239
|
if (!iss.message) {
|
|
6644
|
-
const message = unwrapMessage(iss.inst?._zod.def?.error?.(iss)) ?? unwrapMessage(ctx?.error?.(iss)) ?? unwrapMessage(
|
|
6240
|
+
const message = unwrapMessage(iss.inst?._zod.def?.error?.(iss)) ?? unwrapMessage(ctx?.error?.(iss)) ?? unwrapMessage(config5.customError?.(iss)) ?? unwrapMessage(config5.localeError?.(iss)) ?? "Invalid input";
|
|
6645
6241
|
full.message = message;
|
|
6646
6242
|
}
|
|
6647
6243
|
delete full.inst;
|
|
@@ -6911,7 +6507,7 @@ var init_parse2 = __esm({
|
|
|
6911
6507
|
throw new $ZodAsyncError();
|
|
6912
6508
|
}
|
|
6913
6509
|
if (result.issues.length) {
|
|
6914
|
-
const e = new (_params?.Err ?? _Err)(result.issues.map((iss) => finalizeIssue(iss, ctx,
|
|
6510
|
+
const e = new (_params?.Err ?? _Err)(result.issues.map((iss) => finalizeIssue(iss, ctx, config3())));
|
|
6915
6511
|
captureStackTrace(e, _params?.callee);
|
|
6916
6512
|
throw e;
|
|
6917
6513
|
}
|
|
@@ -6924,7 +6520,7 @@ var init_parse2 = __esm({
|
|
|
6924
6520
|
if (result instanceof Promise)
|
|
6925
6521
|
result = await result;
|
|
6926
6522
|
if (result.issues.length) {
|
|
6927
|
-
const e = new (params?.Err ?? _Err)(result.issues.map((iss) => finalizeIssue(iss, ctx,
|
|
6523
|
+
const e = new (params?.Err ?? _Err)(result.issues.map((iss) => finalizeIssue(iss, ctx, config3())));
|
|
6928
6524
|
captureStackTrace(e, params?.callee);
|
|
6929
6525
|
throw e;
|
|
6930
6526
|
}
|
|
@@ -6939,7 +6535,7 @@ var init_parse2 = __esm({
|
|
|
6939
6535
|
}
|
|
6940
6536
|
return result.issues.length ? {
|
|
6941
6537
|
success: false,
|
|
6942
|
-
error: new (_Err ?? $ZodError)(result.issues.map((iss) => finalizeIssue(iss, ctx,
|
|
6538
|
+
error: new (_Err ?? $ZodError)(result.issues.map((iss) => finalizeIssue(iss, ctx, config3())))
|
|
6943
6539
|
} : { success: true, data: result.value };
|
|
6944
6540
|
};
|
|
6945
6541
|
safeParse = /* @__PURE__ */ _safeParse($ZodRealError);
|
|
@@ -6950,7 +6546,7 @@ var init_parse2 = __esm({
|
|
|
6950
6546
|
result = await result;
|
|
6951
6547
|
return result.issues.length ? {
|
|
6952
6548
|
success: false,
|
|
6953
|
-
error: new _Err(result.issues.map((iss) => finalizeIssue(iss, ctx,
|
|
6549
|
+
error: new _Err(result.issues.map((iss) => finalizeIssue(iss, ctx, config3())))
|
|
6954
6550
|
} : { success: true, data: result.value };
|
|
6955
6551
|
};
|
|
6956
6552
|
safeParseAsync = /* @__PURE__ */ _safeParseAsync($ZodRealError);
|
|
@@ -7884,7 +7480,7 @@ function handleUnionResults(results, final, inst, ctx) {
|
|
|
7884
7480
|
code: "invalid_union",
|
|
7885
7481
|
input: final.value,
|
|
7886
7482
|
inst,
|
|
7887
|
-
errors: results.map((result) => result.issues.map((iss) => finalizeIssue(iss, ctx,
|
|
7483
|
+
errors: results.map((result) => result.issues.map((iss) => finalizeIssue(iss, ctx, config3())))
|
|
7888
7484
|
});
|
|
7889
7485
|
return final;
|
|
7890
7486
|
}
|
|
@@ -7899,7 +7495,7 @@ function handleExclusiveUnionResults(results, final, inst, ctx) {
|
|
|
7899
7495
|
code: "invalid_union",
|
|
7900
7496
|
input: final.value,
|
|
7901
7497
|
inst,
|
|
7902
|
-
errors: results.map((result) => result.issues.map((iss) => finalizeIssue(iss, ctx,
|
|
7498
|
+
errors: results.map((result) => result.issues.map((iss) => finalizeIssue(iss, ctx, config3())))
|
|
7903
7499
|
});
|
|
7904
7500
|
} else {
|
|
7905
7501
|
final.issues.push({
|
|
@@ -8011,7 +7607,7 @@ function handleMapResult(keyResult, valueResult, final, key, input, inst, ctx) {
|
|
|
8011
7607
|
origin: "map",
|
|
8012
7608
|
input,
|
|
8013
7609
|
inst,
|
|
8014
|
-
issues: keyResult.issues.map((iss) => finalizeIssue(iss, ctx,
|
|
7610
|
+
issues: keyResult.issues.map((iss) => finalizeIssue(iss, ctx, config3()))
|
|
8015
7611
|
});
|
|
8016
7612
|
}
|
|
8017
7613
|
}
|
|
@@ -8025,7 +7621,7 @@ function handleMapResult(keyResult, valueResult, final, key, input, inst, ctx) {
|
|
|
8025
7621
|
input,
|
|
8026
7622
|
inst,
|
|
8027
7623
|
key,
|
|
8028
|
-
issues: valueResult.issues.map((iss) => finalizeIssue(iss, ctx,
|
|
7624
|
+
issues: valueResult.issues.map((iss) => finalizeIssue(iss, ctx, config3()))
|
|
8029
7625
|
});
|
|
8030
7626
|
}
|
|
8031
7627
|
}
|
|
@@ -9187,7 +8783,7 @@ var init_schemas = __esm({
|
|
|
9187
8783
|
payload.issues.push({
|
|
9188
8784
|
code: "invalid_key",
|
|
9189
8785
|
origin: "record",
|
|
9190
|
-
issues: keyResult.issues.map((iss) => finalizeIssue(iss, ctx,
|
|
8786
|
+
issues: keyResult.issues.map((iss) => finalizeIssue(iss, ctx, config3())),
|
|
9191
8787
|
input: key,
|
|
9192
8788
|
path: [key],
|
|
9193
8789
|
inst
|
|
@@ -9483,7 +9079,7 @@ var init_schemas = __esm({
|
|
|
9483
9079
|
payload.value = def.catchValue({
|
|
9484
9080
|
...payload,
|
|
9485
9081
|
error: {
|
|
9486
|
-
issues: result2.issues.map((iss) => finalizeIssue(iss, ctx,
|
|
9082
|
+
issues: result2.issues.map((iss) => finalizeIssue(iss, ctx, config3()))
|
|
9487
9083
|
},
|
|
9488
9084
|
input: payload.value
|
|
9489
9085
|
});
|
|
@@ -9497,7 +9093,7 @@ var init_schemas = __esm({
|
|
|
9497
9093
|
payload.value = def.catchValue({
|
|
9498
9094
|
...payload,
|
|
9499
9095
|
error: {
|
|
9500
|
-
issues: result.issues.map((iss) => finalizeIssue(iss, ctx,
|
|
9096
|
+
issues: result.issues.map((iss) => finalizeIssue(iss, ctx, config3()))
|
|
9501
9097
|
},
|
|
9502
9098
|
input: payload.value
|
|
9503
9099
|
});
|
|
@@ -13795,7 +13391,7 @@ var init_external3 = __esm({
|
|
|
13795
13391
|
init_iso2();
|
|
13796
13392
|
init_iso2();
|
|
13797
13393
|
init_coerce2();
|
|
13798
|
-
|
|
13394
|
+
config3(en_default2());
|
|
13799
13395
|
}
|
|
13800
13396
|
});
|
|
13801
13397
|
|
|
@@ -16822,13 +16418,13 @@ var init_zodToJsonSchema = __esm({
|
|
|
16822
16418
|
}, true) ?? parseAnyDef(refs)
|
|
16823
16419
|
}), {}) : void 0;
|
|
16824
16420
|
const name = typeof options === "string" ? options : options?.nameStrategy === "title" ? void 0 : options?.name;
|
|
16825
|
-
const
|
|
16421
|
+
const main4 = parseDef(schema._def, name === void 0 ? refs : {
|
|
16826
16422
|
...refs,
|
|
16827
16423
|
currentPath: [...refs.basePath, refs.definitionPath, name]
|
|
16828
16424
|
}, false) ?? parseAnyDef(refs);
|
|
16829
16425
|
const title = typeof options === "object" && options.name !== void 0 && options.nameStrategy === "title" ? options.name : void 0;
|
|
16830
16426
|
if (title !== void 0) {
|
|
16831
|
-
|
|
16427
|
+
main4.title = title;
|
|
16832
16428
|
}
|
|
16833
16429
|
if (refs.flags.hasReferencedOpenAiAnyType) {
|
|
16834
16430
|
if (!definitions) {
|
|
@@ -16849,9 +16445,9 @@ var init_zodToJsonSchema = __esm({
|
|
|
16849
16445
|
}
|
|
16850
16446
|
}
|
|
16851
16447
|
const combined = name === void 0 ? definitions ? {
|
|
16852
|
-
...
|
|
16448
|
+
...main4,
|
|
16853
16449
|
[refs.definitionPath]: definitions
|
|
16854
|
-
} :
|
|
16450
|
+
} : main4 : {
|
|
16855
16451
|
$ref: [
|
|
16856
16452
|
...refs.$refStrategy === "relative" ? [] : refs.basePath,
|
|
16857
16453
|
refs.definitionPath,
|
|
@@ -16859,7 +16455,7 @@ var init_zodToJsonSchema = __esm({
|
|
|
16859
16455
|
].join("/"),
|
|
16860
16456
|
[refs.definitionPath]: {
|
|
16861
16457
|
...definitions,
|
|
16862
|
-
[name]:
|
|
16458
|
+
[name]: main4
|
|
16863
16459
|
}
|
|
16864
16460
|
};
|
|
16865
16461
|
if (refs.target === "jsonSchema7") {
|
|
@@ -17489,7 +17085,7 @@ var init_protocol = __esm({
|
|
|
17489
17085
|
return;
|
|
17490
17086
|
}
|
|
17491
17087
|
const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
|
|
17492
|
-
await new Promise((
|
|
17088
|
+
await new Promise((resolve4) => setTimeout(resolve4, pollInterval));
|
|
17493
17089
|
options?.signal?.throwIfAborted();
|
|
17494
17090
|
}
|
|
17495
17091
|
} catch (error2) {
|
|
@@ -17506,7 +17102,7 @@ var init_protocol = __esm({
|
|
|
17506
17102
|
*/
|
|
17507
17103
|
request(request, resultSchema, options) {
|
|
17508
17104
|
const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
|
|
17509
|
-
return new Promise((
|
|
17105
|
+
return new Promise((resolve4, reject) => {
|
|
17510
17106
|
const earlyReject = (error2) => {
|
|
17511
17107
|
reject(error2);
|
|
17512
17108
|
};
|
|
@@ -17584,7 +17180,7 @@ var init_protocol = __esm({
|
|
|
17584
17180
|
if (!parseResult.success) {
|
|
17585
17181
|
reject(parseResult.error);
|
|
17586
17182
|
} else {
|
|
17587
|
-
|
|
17183
|
+
resolve4(parseResult.data);
|
|
17588
17184
|
}
|
|
17589
17185
|
} catch (error2) {
|
|
17590
17186
|
reject(error2);
|
|
@@ -17845,12 +17441,12 @@ var init_protocol = __esm({
|
|
|
17845
17441
|
}
|
|
17846
17442
|
} catch {
|
|
17847
17443
|
}
|
|
17848
|
-
return new Promise((
|
|
17444
|
+
return new Promise((resolve4, reject) => {
|
|
17849
17445
|
if (signal.aborted) {
|
|
17850
17446
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
17851
17447
|
return;
|
|
17852
17448
|
}
|
|
17853
|
-
const timeoutId = setTimeout(
|
|
17449
|
+
const timeoutId = setTimeout(resolve4, interval);
|
|
17854
17450
|
signal.addEventListener("abort", () => {
|
|
17855
17451
|
clearTimeout(timeoutId);
|
|
17856
17452
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
@@ -20877,7 +20473,7 @@ var require_compile = __commonJS({
|
|
|
20877
20473
|
const schOrFunc = root.refs[ref];
|
|
20878
20474
|
if (schOrFunc)
|
|
20879
20475
|
return schOrFunc;
|
|
20880
|
-
let _sch =
|
|
20476
|
+
let _sch = resolve4.call(this, root, ref);
|
|
20881
20477
|
if (_sch === void 0) {
|
|
20882
20478
|
const schema = (_a2 = root.localRefs) === null || _a2 === void 0 ? void 0 : _a2[ref];
|
|
20883
20479
|
const { schemaId } = this.opts;
|
|
@@ -20904,7 +20500,7 @@ var require_compile = __commonJS({
|
|
|
20904
20500
|
function sameSchemaEnv(s1, s2) {
|
|
20905
20501
|
return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
|
|
20906
20502
|
}
|
|
20907
|
-
function
|
|
20503
|
+
function resolve4(root, ref) {
|
|
20908
20504
|
let sch;
|
|
20909
20505
|
while (typeof (sch = this.refs[ref]) == "string")
|
|
20910
20506
|
ref = sch;
|
|
@@ -21479,7 +21075,7 @@ var require_fast_uri = __commonJS({
|
|
|
21479
21075
|
}
|
|
21480
21076
|
return uri;
|
|
21481
21077
|
}
|
|
21482
|
-
function
|
|
21078
|
+
function resolve4(baseURI, relativeURI, options) {
|
|
21483
21079
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
21484
21080
|
const resolved = resolveComponent(parse4(baseURI, schemelessOptions), parse4(relativeURI, schemelessOptions), schemelessOptions, true);
|
|
21485
21081
|
schemelessOptions.skipEscape = true;
|
|
@@ -21706,7 +21302,7 @@ var require_fast_uri = __commonJS({
|
|
|
21706
21302
|
var fastUri = {
|
|
21707
21303
|
SCHEMES,
|
|
21708
21304
|
normalize,
|
|
21709
|
-
resolve:
|
|
21305
|
+
resolve: resolve4,
|
|
21710
21306
|
resolveComponent,
|
|
21711
21307
|
equal,
|
|
21712
21308
|
serialize,
|
|
@@ -22261,13 +21857,13 @@ var require_core = __commonJS({
|
|
|
22261
21857
|
}, warn() {
|
|
22262
21858
|
}, error() {
|
|
22263
21859
|
} };
|
|
22264
|
-
function getLogger(
|
|
22265
|
-
if (
|
|
21860
|
+
function getLogger(logger3) {
|
|
21861
|
+
if (logger3 === false)
|
|
22266
21862
|
return noLogs;
|
|
22267
|
-
if (
|
|
21863
|
+
if (logger3 === void 0)
|
|
22268
21864
|
return console;
|
|
22269
|
-
if (
|
|
22270
|
-
return
|
|
21865
|
+
if (logger3.log && logger3.warn && logger3.error)
|
|
21866
|
+
return logger3;
|
|
22271
21867
|
throw new Error("logger must implement log, warn and error methods");
|
|
22272
21868
|
}
|
|
22273
21869
|
var KEYWORD_NAME = /^[a-z_$][a-z0-9_$:-]*$/i;
|
|
@@ -27497,7 +27093,7 @@ var require_compile2 = __commonJS({
|
|
|
27497
27093
|
const schOrFunc = root.refs[ref];
|
|
27498
27094
|
if (schOrFunc)
|
|
27499
27095
|
return schOrFunc;
|
|
27500
|
-
let _sch =
|
|
27096
|
+
let _sch = resolve4.call(this, root, ref);
|
|
27501
27097
|
if (_sch === void 0) {
|
|
27502
27098
|
const schema = (_a2 = root.localRefs) === null || _a2 === void 0 ? void 0 : _a2[ref];
|
|
27503
27099
|
const { schemaId } = this.opts;
|
|
@@ -27524,7 +27120,7 @@ var require_compile2 = __commonJS({
|
|
|
27524
27120
|
function sameSchemaEnv(s1, s2) {
|
|
27525
27121
|
return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
|
|
27526
27122
|
}
|
|
27527
|
-
function
|
|
27123
|
+
function resolve4(root, ref) {
|
|
27528
27124
|
let sch;
|
|
27529
27125
|
while (typeof (sch = this.refs[ref]) == "string")
|
|
27530
27126
|
ref = sch;
|
|
@@ -28159,13 +27755,13 @@ var require_core3 = __commonJS({
|
|
|
28159
27755
|
}, warn() {
|
|
28160
27756
|
}, error() {
|
|
28161
27757
|
} };
|
|
28162
|
-
function getLogger(
|
|
28163
|
-
if (
|
|
27758
|
+
function getLogger(logger3) {
|
|
27759
|
+
if (logger3 === false)
|
|
28164
27760
|
return noLogs;
|
|
28165
|
-
if (
|
|
27761
|
+
if (logger3 === void 0)
|
|
28166
27762
|
return console;
|
|
28167
|
-
if (
|
|
28168
|
-
return
|
|
27763
|
+
if (logger3.log && logger3.warn && logger3.error)
|
|
27764
|
+
return logger3;
|
|
28169
27765
|
throw new Error("logger must implement log, warn and error methods");
|
|
28170
27766
|
}
|
|
28171
27767
|
var KEYWORD_NAME = /^[a-z_$][a-z0-9_$:-]*$/i;
|
|
@@ -31056,13 +30652,13 @@ var init_mcp_server = __esm({
|
|
|
31056
30652
|
constructor(_mcpServer) {
|
|
31057
30653
|
this._mcpServer = _mcpServer;
|
|
31058
30654
|
}
|
|
31059
|
-
registerToolTask(name,
|
|
31060
|
-
const execution = { taskSupport: "required", ...
|
|
30655
|
+
registerToolTask(name, config5, handler) {
|
|
30656
|
+
const execution = { taskSupport: "required", ...config5.execution };
|
|
31061
30657
|
if (execution.taskSupport === "forbidden") {
|
|
31062
30658
|
throw new Error(`Cannot register task-based tool '${name}' with taskSupport 'forbidden'. Use registerTool() instead.`);
|
|
31063
30659
|
}
|
|
31064
30660
|
const mcpServerInternal = this._mcpServer;
|
|
31065
|
-
return mcpServerInternal._createRegisteredTool(name,
|
|
30661
|
+
return mcpServerInternal._createRegisteredTool(name, config5.title, config5.description, config5.inputSchema, config5.outputSchema, config5.annotations, execution, config5._meta, handler);
|
|
31066
30662
|
}
|
|
31067
30663
|
};
|
|
31068
30664
|
}
|
|
@@ -31374,7 +30970,7 @@ var init_mcp = __esm({
|
|
|
31374
30970
|
let task = createTaskResult.task;
|
|
31375
30971
|
const pollInterval = task.pollInterval ?? 5e3;
|
|
31376
30972
|
while (task.status !== "completed" && task.status !== "failed" && task.status !== "cancelled") {
|
|
31377
|
-
await new Promise((
|
|
30973
|
+
await new Promise((resolve4) => setTimeout(resolve4, pollInterval));
|
|
31378
30974
|
const updatedTask = await extra.taskStore.getTask(taskId);
|
|
31379
30975
|
if (!updatedTask) {
|
|
31380
30976
|
throw new McpError(ErrorCode.InternalError, `Task ${taskId} not found during polling`);
|
|
@@ -31575,12 +31171,12 @@ var init_mcp = __esm({
|
|
|
31575
31171
|
return registeredResourceTemplate;
|
|
31576
31172
|
}
|
|
31577
31173
|
}
|
|
31578
|
-
registerResource(name, uriOrTemplate,
|
|
31174
|
+
registerResource(name, uriOrTemplate, config5, readCallback) {
|
|
31579
31175
|
if (typeof uriOrTemplate === "string") {
|
|
31580
31176
|
if (this._registeredResources[uriOrTemplate]) {
|
|
31581
31177
|
throw new Error(`Resource ${uriOrTemplate} is already registered`);
|
|
31582
31178
|
}
|
|
31583
|
-
const registeredResource = this._createRegisteredResource(name,
|
|
31179
|
+
const registeredResource = this._createRegisteredResource(name, config5.title, uriOrTemplate, config5, readCallback);
|
|
31584
31180
|
this.setResourceRequestHandlers();
|
|
31585
31181
|
this.sendResourceListChanged();
|
|
31586
31182
|
return registeredResource;
|
|
@@ -31588,7 +31184,7 @@ var init_mcp = __esm({
|
|
|
31588
31184
|
if (this._registeredResourceTemplates[name]) {
|
|
31589
31185
|
throw new Error(`Resource template ${name} is already registered`);
|
|
31590
31186
|
}
|
|
31591
|
-
const registeredResourceTemplate = this._createRegisteredResourceTemplate(name,
|
|
31187
|
+
const registeredResourceTemplate = this._createRegisteredResourceTemplate(name, config5.title, uriOrTemplate, config5, readCallback);
|
|
31592
31188
|
this.setResourceRequestHandlers();
|
|
31593
31189
|
this.sendResourceListChanged();
|
|
31594
31190
|
return registeredResourceTemplate;
|
|
@@ -31783,11 +31379,11 @@ var init_mcp = __esm({
|
|
|
31783
31379
|
/**
|
|
31784
31380
|
* Registers a tool with a config object and callback.
|
|
31785
31381
|
*/
|
|
31786
|
-
registerTool(name,
|
|
31382
|
+
registerTool(name, config5, cb) {
|
|
31787
31383
|
if (this._registeredTools[name]) {
|
|
31788
31384
|
throw new Error(`Tool ${name} is already registered`);
|
|
31789
31385
|
}
|
|
31790
|
-
const { title, description, inputSchema, outputSchema, annotations, _meta } =
|
|
31386
|
+
const { title, description, inputSchema, outputSchema, annotations, _meta } = config5;
|
|
31791
31387
|
return this._createRegisteredTool(name, title, description, inputSchema, outputSchema, annotations, { taskSupport: "forbidden" }, _meta, cb);
|
|
31792
31388
|
}
|
|
31793
31389
|
prompt(name, ...rest) {
|
|
@@ -31811,11 +31407,11 @@ var init_mcp = __esm({
|
|
|
31811
31407
|
/**
|
|
31812
31408
|
* Registers a prompt with a config object and callback.
|
|
31813
31409
|
*/
|
|
31814
|
-
registerPrompt(name,
|
|
31410
|
+
registerPrompt(name, config5, cb) {
|
|
31815
31411
|
if (this._registeredPrompts[name]) {
|
|
31816
31412
|
throw new Error(`Prompt ${name} is already registered`);
|
|
31817
31413
|
}
|
|
31818
|
-
const { title, description, argsSchema } =
|
|
31414
|
+
const { title, description, argsSchema } = config5;
|
|
31819
31415
|
const registeredPrompt = this._createRegisteredPrompt(name, title, description, argsSchema, cb);
|
|
31820
31416
|
this.setPromptRequestHandlers();
|
|
31821
31417
|
this.sendPromptListChanged();
|
|
@@ -31965,12 +31561,12 @@ var init_stdio2 = __esm({
|
|
|
31965
31561
|
this.onclose?.();
|
|
31966
31562
|
}
|
|
31967
31563
|
send(message) {
|
|
31968
|
-
return new Promise((
|
|
31564
|
+
return new Promise((resolve4) => {
|
|
31969
31565
|
const json2 = serializeMessage(message);
|
|
31970
31566
|
if (this._stdout.write(json2)) {
|
|
31971
|
-
|
|
31567
|
+
resolve4();
|
|
31972
31568
|
} else {
|
|
31973
|
-
this._stdout.once("drain",
|
|
31569
|
+
this._stdout.once("drain", resolve4);
|
|
31974
31570
|
}
|
|
31975
31571
|
});
|
|
31976
31572
|
}
|
|
@@ -31982,16 +31578,35 @@ var init_stdio2 = __esm({
|
|
|
31982
31578
|
var mcp_server_exports = {};
|
|
31983
31579
|
function resolveToken2() {
|
|
31984
31580
|
if (process.env.HAANSI_TOKEN) return process.env.HAANSI_TOKEN;
|
|
31985
|
-
if ((0,
|
|
31581
|
+
if ((0, import_fs2.existsSync)(CREDENTIALS_FILE3)) {
|
|
31986
31582
|
try {
|
|
31987
|
-
const creds = JSON.parse((0,
|
|
31583
|
+
const creds = JSON.parse((0, import_fs2.readFileSync)(CREDENTIALS_FILE3, "utf-8"));
|
|
31988
31584
|
if (creds.token) return creds.token;
|
|
31989
31585
|
} catch {
|
|
31990
31586
|
}
|
|
31991
31587
|
}
|
|
31992
31588
|
return null;
|
|
31993
31589
|
}
|
|
31994
|
-
|
|
31590
|
+
function triggerCollectInBackground() {
|
|
31591
|
+
if (isCollecting) return;
|
|
31592
|
+
isCollecting = true;
|
|
31593
|
+
collect({}).then(({ uploaded, skipped, errors }) => {
|
|
31594
|
+
console.error(
|
|
31595
|
+
`[mcp-server] Collection done: ${uploaded} uploaded, ${skipped} unchanged, ${errors} errors`
|
|
31596
|
+
);
|
|
31597
|
+
lastCollectTime = Date.now();
|
|
31598
|
+
}).catch((err) => {
|
|
31599
|
+
console.error("[mcp-server] Collection error:", err.message);
|
|
31600
|
+
}).finally(() => {
|
|
31601
|
+
isCollecting = false;
|
|
31602
|
+
});
|
|
31603
|
+
}
|
|
31604
|
+
function maybeCollect() {
|
|
31605
|
+
if (Date.now() - lastCollectTime >= COLLECT_COOLDOWN_MS) {
|
|
31606
|
+
triggerCollectInBackground();
|
|
31607
|
+
}
|
|
31608
|
+
}
|
|
31609
|
+
async function apiGet2(path) {
|
|
31995
31610
|
const response = await fetch(`${API_URL}/api/v1${path}`, {
|
|
31996
31611
|
headers: { Authorization: `Bearer ${TOKEN}` }
|
|
31997
31612
|
});
|
|
@@ -32001,10 +31616,11 @@ async function apiGet(path) {
|
|
|
32001
31616
|
}
|
|
32002
31617
|
return response.json();
|
|
32003
31618
|
}
|
|
32004
|
-
async function
|
|
31619
|
+
async function main3() {
|
|
32005
31620
|
try {
|
|
32006
|
-
await
|
|
31621
|
+
await apiGet2("/sessions/recent?limit=1");
|
|
32007
31622
|
console.error("[mcp-server] API connection ok");
|
|
31623
|
+
triggerCollectInBackground();
|
|
32008
31624
|
} catch (err) {
|
|
32009
31625
|
console.error(
|
|
32010
31626
|
"[mcp-server] WARNING: API connection check failed:",
|
|
@@ -32015,20 +31631,21 @@ async function main4() {
|
|
|
32015
31631
|
await mcpServer.connect(transport);
|
|
32016
31632
|
console.error("[mcp-server] haansi-solutions MCP server running on stdio");
|
|
32017
31633
|
}
|
|
32018
|
-
var
|
|
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;
|
|
32019
31635
|
var init_mcp_server2 = __esm({
|
|
32020
31636
|
"../service-capture/claude-sessions/mcp-server.ts"() {
|
|
32021
31637
|
"use strict";
|
|
32022
|
-
|
|
32023
|
-
|
|
32024
|
-
|
|
32025
|
-
|
|
31638
|
+
import_dotenv3 = __toESM(require_main());
|
|
31639
|
+
import_path3 = require("path");
|
|
31640
|
+
import_fs2 = require("fs");
|
|
31641
|
+
import_os2 = require("os");
|
|
31642
|
+
init_collector();
|
|
32026
31643
|
init_mcp();
|
|
32027
31644
|
init_stdio2();
|
|
32028
31645
|
init_types2();
|
|
32029
|
-
(0,
|
|
31646
|
+
(0, import_dotenv3.config)({ path: (0, import_path3.resolve)(__dirname, "../../../.env") });
|
|
32030
31647
|
DEFAULT_API_URL3 = "https://api.haansi.co";
|
|
32031
|
-
CREDENTIALS_FILE3 = (0,
|
|
31648
|
+
CREDENTIALS_FILE3 = (0, import_path3.join)((0, import_os2.homedir)(), ".haansi", "credentials.json");
|
|
32032
31649
|
API_URL = process.env.HAANSI_API_URL ?? DEFAULT_API_URL3;
|
|
32033
31650
|
TOKEN = resolveToken2();
|
|
32034
31651
|
if (!TOKEN) {
|
|
@@ -32037,6 +31654,9 @@ var init_mcp_server2 = __esm({
|
|
|
32037
31654
|
);
|
|
32038
31655
|
process.exit(1);
|
|
32039
31656
|
}
|
|
31657
|
+
isCollecting = false;
|
|
31658
|
+
lastCollectTime = 0;
|
|
31659
|
+
COLLECT_COOLDOWN_MS = 5 * 60 * 1e3;
|
|
32040
31660
|
mcpServer = new McpServer(
|
|
32041
31661
|
{ name: "haansi-solutions", version: "0.2.0" },
|
|
32042
31662
|
{ capabilities: { tools: {} } }
|
|
@@ -32079,6 +31699,7 @@ var init_mcp_server2 = __esm({
|
|
|
32079
31699
|
]
|
|
32080
31700
|
}));
|
|
32081
31701
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
31702
|
+
maybeCollect();
|
|
32082
31703
|
const { name, arguments: args } = request.params;
|
|
32083
31704
|
console.error(`[mcp-server] Tool: ${name}`);
|
|
32084
31705
|
try {
|
|
@@ -32086,7 +31707,7 @@ var init_mcp_server2 = __esm({
|
|
|
32086
31707
|
case "search_solutions": {
|
|
32087
31708
|
const { query, top_k = 5 } = args;
|
|
32088
31709
|
const k = Math.min(Math.max(1, top_k), 20);
|
|
32089
|
-
const data = await
|
|
31710
|
+
const data = await apiGet2(
|
|
32090
31711
|
`/sessions/search?q=${encodeURIComponent(query)}&top_k=${k}`
|
|
32091
31712
|
);
|
|
32092
31713
|
if (!data.results || data.results.length === 0) {
|
|
@@ -32106,7 +31727,7 @@ var init_mcp_server2 = __esm({
|
|
|
32106
31727
|
case "list_recent_solutions": {
|
|
32107
31728
|
const { limit = 10 } = args;
|
|
32108
31729
|
const n = Math.min(Math.max(1, limit), 50);
|
|
32109
|
-
const data = await
|
|
31730
|
+
const data = await apiGet2(`/sessions/recent?limit=${n}`);
|
|
32110
31731
|
if (!data.results || data.results.length === 0) {
|
|
32111
31732
|
return {
|
|
32112
31733
|
content: [
|
|
@@ -32132,7 +31753,7 @@ var init_mcp_server2 = __esm({
|
|
|
32132
31753
|
};
|
|
32133
31754
|
}
|
|
32134
31755
|
});
|
|
32135
|
-
|
|
31756
|
+
main3().catch((err) => {
|
|
32136
31757
|
console.error("[mcp-server] Failed to start:", err);
|
|
32137
31758
|
process.exit(1);
|
|
32138
31759
|
});
|
|
@@ -32145,24 +31766,28 @@ __export(setup_mcp_exports, {
|
|
|
32145
31766
|
setupMcp: () => setupMcp
|
|
32146
31767
|
});
|
|
32147
31768
|
async function setupMcp() {
|
|
32148
|
-
let
|
|
31769
|
+
let config5 = {};
|
|
32149
31770
|
if ((0, import_node_fs2.existsSync)(CLAUDE_JSON)) {
|
|
32150
31771
|
try {
|
|
32151
|
-
|
|
31772
|
+
config5 = JSON.parse((0, import_node_fs2.readFileSync)(CLAUDE_JSON, "utf-8"));
|
|
32152
31773
|
} catch {
|
|
32153
|
-
console.error(
|
|
31774
|
+
console.error(
|
|
31775
|
+
`Warning: Could not parse ${CLAUDE_JSON} \u2014 will overwrite with new config.`
|
|
31776
|
+
);
|
|
32154
31777
|
}
|
|
32155
31778
|
}
|
|
32156
|
-
if (!
|
|
32157
|
-
|
|
31779
|
+
if (!config5.mcpServers) {
|
|
31780
|
+
config5.mcpServers = {};
|
|
32158
31781
|
}
|
|
32159
|
-
|
|
31782
|
+
config5.mcpServers["haansi-solutions"] = {
|
|
32160
31783
|
command: "npx",
|
|
32161
31784
|
args: ["haansi", "mcp-server"]
|
|
32162
31785
|
};
|
|
32163
|
-
(0, import_node_fs2.writeFileSync)(CLAUDE_JSON, JSON.stringify(
|
|
31786
|
+
(0, import_node_fs2.writeFileSync)(CLAUDE_JSON, JSON.stringify(config5, null, 2) + "\n", "utf-8");
|
|
32164
31787
|
console.log(`MCP server configured in ${CLAUDE_JSON}`);
|
|
32165
|
-
console.log(
|
|
31788
|
+
console.log(
|
|
31789
|
+
"Restart Claude Code to activate the haansi-solutions MCP server."
|
|
31790
|
+
);
|
|
32166
31791
|
}
|
|
32167
31792
|
var import_node_fs2, import_node_path2, import_node_os2, CLAUDE_JSON;
|
|
32168
31793
|
var init_setup_mcp = __esm({
|
|
@@ -32184,7 +31809,9 @@ function findHaansiBin() {
|
|
|
32184
31809
|
try {
|
|
32185
31810
|
return (0, import_node_child_process.execSync)("which haansi", { encoding: "utf-8" }).trim();
|
|
32186
31811
|
} catch {
|
|
32187
|
-
throw new Error(
|
|
31812
|
+
throw new Error(
|
|
31813
|
+
"Could not find haansi in PATH. Make sure it is installed globally (npm install -g haansi)."
|
|
31814
|
+
);
|
|
32188
31815
|
}
|
|
32189
31816
|
}
|
|
32190
31817
|
function buildPlist(haansiPath) {
|
|
@@ -32255,7 +31882,12 @@ var init_setup_daemon = __esm({
|
|
|
32255
31882
|
import_node_path3 = require("node:path");
|
|
32256
31883
|
import_node_os3 = require("node:os");
|
|
32257
31884
|
PLIST_LABEL = "co.haansi.daemon";
|
|
32258
|
-
PLIST_PATH = (0, import_node_path3.join)(
|
|
31885
|
+
PLIST_PATH = (0, import_node_path3.join)(
|
|
31886
|
+
(0, import_node_os3.homedir)(),
|
|
31887
|
+
"Library",
|
|
31888
|
+
"LaunchAgents",
|
|
31889
|
+
`${PLIST_LABEL}.plist`
|
|
31890
|
+
);
|
|
32259
31891
|
LOG_FILE = (0, import_node_path3.join)((0, import_node_os3.homedir)(), ".haansi", "daemon.log");
|
|
32260
31892
|
}
|
|
32261
31893
|
});
|
|
@@ -32276,7 +31908,7 @@ function resolveToken3() {
|
|
|
32276
31908
|
}
|
|
32277
31909
|
throw new Error("No token found. Run `haansi init` first.");
|
|
32278
31910
|
}
|
|
32279
|
-
async function
|
|
31911
|
+
async function apiGet3(path, token, apiUrl) {
|
|
32280
31912
|
const response = await fetch(`${apiUrl}/api/v1${path}`, {
|
|
32281
31913
|
headers: { Authorization: `Bearer ${token}` }
|
|
32282
31914
|
});
|
|
@@ -32291,13 +31923,17 @@ async function list(options) {
|
|
|
32291
31923
|
const token = resolveToken3();
|
|
32292
31924
|
let data;
|
|
32293
31925
|
if (options.search) {
|
|
32294
|
-
data = await
|
|
31926
|
+
data = await apiGet3(
|
|
32295
31927
|
`/sessions/search?q=${encodeURIComponent(options.search)}&top_k=${options.limit}`,
|
|
32296
31928
|
token,
|
|
32297
31929
|
apiUrl
|
|
32298
31930
|
);
|
|
32299
31931
|
} else {
|
|
32300
|
-
data = await
|
|
31932
|
+
data = await apiGet3(
|
|
31933
|
+
`/sessions/recent?limit=${options.limit}`,
|
|
31934
|
+
token,
|
|
31935
|
+
apiUrl
|
|
31936
|
+
);
|
|
32301
31937
|
}
|
|
32302
31938
|
if (!data.results || data.results.length === 0) {
|
|
32303
31939
|
console.log("No solutions found. Run `haansi collect` to upload sessions.");
|
|
@@ -32336,9 +31972,16 @@ async function run2() {
|
|
|
32336
31972
|
const limitIdx = process.argv.indexOf("--limit");
|
|
32337
31973
|
const limit = limitIdx !== -1 ? parseInt(process.argv[limitIdx + 1], 10) : void 0;
|
|
32338
31974
|
const projectPath = process.env.CLAUDE_PROJECT_PATH || void 0;
|
|
32339
|
-
const { uploaded, skipped, errors } = await collect2({
|
|
32340
|
-
|
|
32341
|
-
|
|
31975
|
+
const { uploaded, skipped, errors } = await collect2({
|
|
31976
|
+
forceAll,
|
|
31977
|
+
dryRun,
|
|
31978
|
+
limit,
|
|
31979
|
+
projectPath
|
|
31980
|
+
});
|
|
31981
|
+
console.log(
|
|
31982
|
+
`
|
|
31983
|
+
Collector done: ${uploaded} uploaded, ${skipped} unchanged, ${errors} errors`
|
|
31984
|
+
);
|
|
32342
31985
|
if (errors > 0) process.exit(1);
|
|
32343
31986
|
break;
|
|
32344
31987
|
}
|