haansi 0.1.24 → 0.1.26

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 (3) hide show
  1. package/README.md +33 -19
  2. package/dist/haansi.js +738 -239
  3. package/package.json +2 -2
package/dist/haansi.js CHANGED
@@ -39,8 +39,8 @@ var require_package = __commonJS({
39
39
  "package.json"(exports2, module2) {
40
40
  module2.exports = {
41
41
  name: "haansi",
42
- version: "0.1.24",
43
- description: "Haansi CLI - Session collector and MCP server for Claude Code",
42
+ version: "0.1.26",
43
+ description: "Haansi CLI - Session collector and MCP server for Claude Code & OpenAI Codex",
44
44
  bin: {
45
45
  haansi: "./dist/haansi.js"
46
46
  },
@@ -82,9 +82,9 @@ function sleep(ms) {
82
82
  return new Promise((resolve4) => setTimeout(resolve4, ms));
83
83
  }
84
84
  async function authLogin() {
85
- const apiUrl = process.env.HAANSI_API_URL ?? DEFAULT_API_URL;
85
+ const apiUrl2 = process.env.HAANSI_API_URL ?? DEFAULT_API_URL;
86
86
  console.log("Haansi CLI \u2014 Browser Login\n");
87
- const initiateRes = await fetch(`${apiUrl}/api/v1/auth/extension/initiate`, {
87
+ const initiateRes = await fetch(`${apiUrl2}/api/v1/auth/extension/initiate`, {
88
88
  method: "POST",
89
89
  headers: { "Content-Type": "application/json" },
90
90
  body: JSON.stringify({ client_type: "cli" })
@@ -115,7 +115,7 @@ If the browser didn't open, visit this URL:
115
115
  while (Date.now() < deadline) {
116
116
  await sleep(POLL_INTERVAL_MS);
117
117
  const pollRes = await fetch(
118
- `${apiUrl}/api/v1/auth/extension/token?session_id=${encodeURIComponent(session_id)}`
118
+ `${apiUrl2}/api/v1/auth/extension/token?session_id=${encodeURIComponent(session_id)}`
119
119
  );
120
120
  if (pollRes.status === 410) {
121
121
  throw new Error("Session expired or was already used. Please try again.");
@@ -141,7 +141,7 @@ If the browser didn't open, visit this URL:
141
141
  );
142
142
  }
143
143
  console.log("Creating CLI token...");
144
- const tokenRes = await fetch(`${apiUrl}/api/v1/access/cli-tokens`, {
144
+ const tokenRes = await fetch(`${apiUrl2}/api/v1/access/cli-tokens`, {
145
145
  method: "POST",
146
146
  headers: {
147
147
  Authorization: `Bearer ${jwt2}`,
@@ -205,9 +205,9 @@ function prompt(question) {
205
205
  });
206
206
  });
207
207
  }
208
- async function validateToken(token, apiUrl) {
208
+ async function validateToken(token, apiUrl2) {
209
209
  const response = await fetch(
210
- `${apiUrl}/api/v1/memory/solutions/recent?limit=1`,
210
+ `${apiUrl2}/api/v1/memory/solutions/recent?limit=1`,
211
211
  {
212
212
  headers: { Authorization: `Bearer ${token}` }
213
213
  }
@@ -220,7 +220,7 @@ async function validateToken(token, apiUrl) {
220
220
  }
221
221
  }
222
222
  async function init() {
223
- const apiUrl = process.env.HAANSI_API_URL ?? DEFAULT_API_URL2;
223
+ const apiUrl2 = process.env.HAANSI_API_URL ?? DEFAULT_API_URL2;
224
224
  console.log("Haansi CLI \u2014 Setup\n");
225
225
  console.log(
226
226
  "Tip: For interactive login, run `haansi auth login` to authenticate via browser.\n"
@@ -239,7 +239,7 @@ async function init() {
239
239
  process.exit(1);
240
240
  }
241
241
  console.log("\nValidating token...");
242
- await validateToken(token, apiUrl);
242
+ await validateToken(token, apiUrl2);
243
243
  (0, import_node_fs2.mkdirSync)(HAANSI_DIR2, { recursive: true });
244
244
  (0, import_node_fs2.writeFileSync)(CREDENTIALS_FILE2, JSON.stringify({ token }, null, 2), {
245
245
  encoding: "utf-8",
@@ -248,7 +248,7 @@ async function init() {
248
248
  console.log(`
249
249
  Token saved to ${CREDENTIALS_FILE2}`);
250
250
  console.log(
251
- "Run `haansi daemon` to start collecting or `haansi setup-mcp` to configure Claude Code."
251
+ "Run `haansi daemon` to start collecting or `haansi setup-mcp` to configure your coding agent(s) (Claude Code / Codex)."
252
252
  );
253
253
  }
254
254
  var readline, import_node_fs2, import_node_path2, import_node_os2, DEFAULT_API_URL2, HAANSI_DIR2, CREDENTIALS_FILE2;
@@ -675,6 +675,253 @@ var init_logger = __esm({
675
675
  }
676
676
  });
677
677
 
678
+ // ../service-capture/common/agents/types.ts
679
+ var AGENT_IDS;
680
+ var init_types = __esm({
681
+ "../service-capture/common/agents/types.ts"() {
682
+ "use strict";
683
+ AGENT_IDS = ["claude-code", "codex"];
684
+ }
685
+ });
686
+
687
+ // ../service-capture/common/agents/claude-code.ts
688
+ function projectsDir() {
689
+ return (0, import_path.join)((0, import_os.homedir)(), ".claude", "projects");
690
+ }
691
+ function extractMeta(filePath) {
692
+ const result = {
693
+ cwd: null,
694
+ timestamp: null,
695
+ gitBranch: null
696
+ };
697
+ try {
698
+ const lines = (0, import_fs.readFileSync)(filePath, "utf-8").split("\n").filter((l) => l.trim()).slice(0, 50);
699
+ for (const line of lines) {
700
+ try {
701
+ const entry = JSON.parse(line);
702
+ if (!result.cwd && typeof entry.cwd === "string")
703
+ result.cwd = entry.cwd;
704
+ if (!result.timestamp && typeof entry.timestamp === "string")
705
+ result.timestamp = entry.timestamp;
706
+ if (!result.gitBranch && typeof entry.gitBranch === "string")
707
+ result.gitBranch = entry.gitBranch;
708
+ if (result.cwd && result.timestamp && result.gitBranch) break;
709
+ } catch {
710
+ }
711
+ }
712
+ } catch {
713
+ }
714
+ return result;
715
+ }
716
+ var import_fs, import_path, import_os, claudeCodeAgent;
717
+ var init_claude_code = __esm({
718
+ "../service-capture/common/agents/claude-code.ts"() {
719
+ "use strict";
720
+ import_fs = require("fs");
721
+ import_path = require("path");
722
+ import_os = require("os");
723
+ claudeCodeAgent = {
724
+ id: "claude-code",
725
+ displayName: "Claude Code",
726
+ sessionsRoot() {
727
+ return projectsDir();
728
+ },
729
+ isInstalled() {
730
+ return (0, import_fs.existsSync)(projectsDir());
731
+ },
732
+ discoverSessions() {
733
+ const root = projectsDir();
734
+ if (!(0, import_fs.existsSync)(root)) return [];
735
+ const sessions = [];
736
+ const projectDirs = (0, import_fs.readdirSync)(root).map((name) => (0, import_path.join)(root, name)).filter((p) => {
737
+ try {
738
+ return (0, import_fs.statSync)(p).isDirectory();
739
+ } catch {
740
+ return false;
741
+ }
742
+ });
743
+ for (const dir of projectDirs) {
744
+ let files;
745
+ try {
746
+ files = (0, import_fs.readdirSync)(dir).filter((f) => f.endsWith(".jsonl"));
747
+ } catch {
748
+ continue;
749
+ }
750
+ for (const file2 of files) {
751
+ const filePath = (0, import_path.join)(dir, file2);
752
+ const sessionId = (0, import_path.basename)(file2, ".jsonl");
753
+ try {
754
+ const stat = (0, import_fs.statSync)(filePath);
755
+ const content = (0, import_fs.readFileSync)(filePath, "utf-8");
756
+ const lineCount = content.split("\n").filter((l) => l.trim()).length;
757
+ const meta3 = extractMeta(filePath);
758
+ sessions.push({
759
+ sessionId,
760
+ filePath,
761
+ lineCount,
762
+ sizeBytes: stat.size,
763
+ ...meta3
764
+ });
765
+ } catch {
766
+ }
767
+ }
768
+ }
769
+ return sessions;
770
+ }
771
+ };
772
+ }
773
+ });
774
+
775
+ // ../service-capture/common/agents/codex.ts
776
+ function codexHome() {
777
+ return process.env.CODEX_HOME || (0, import_path2.join)((0, import_os2.homedir)(), ".codex");
778
+ }
779
+ function sessionsDir() {
780
+ return (0, import_path2.join)(codexHome(), "sessions");
781
+ }
782
+ function walkRollouts(dir, out) {
783
+ let entries;
784
+ try {
785
+ entries = (0, import_fs2.readdirSync)(dir);
786
+ } catch {
787
+ return;
788
+ }
789
+ for (const name of entries) {
790
+ const full = (0, import_path2.join)(dir, name);
791
+ let isDir = false;
792
+ try {
793
+ isDir = (0, import_fs2.statSync)(full).isDirectory();
794
+ } catch {
795
+ continue;
796
+ }
797
+ if (isDir) {
798
+ walkRollouts(full, out);
799
+ } else if (name.startsWith("rollout-") && name.endsWith(".jsonl")) {
800
+ out.push(full);
801
+ }
802
+ }
803
+ }
804
+ function readRolloutMeta(filePath, content) {
805
+ const fileName = (0, import_path2.basename)(filePath, ".jsonl");
806
+ const fromName = UUID_RE.exec(fileName)?.[1];
807
+ let cwd = null;
808
+ let timestamp = null;
809
+ let gitBranch = null;
810
+ let metaId = null;
811
+ const lines = content.split("\n").filter((l) => l.trim()).slice(0, 50);
812
+ for (const line of lines) {
813
+ try {
814
+ const entry = JSON.parse(line);
815
+ if (!timestamp && typeof entry.timestamp === "string") {
816
+ timestamp = entry.timestamp;
817
+ }
818
+ const p = entry.payload ?? {};
819
+ if (entry.type === "session_meta") {
820
+ if (typeof p.cwd === "string") cwd = p.cwd;
821
+ if (typeof p.id === "string") metaId = p.id;
822
+ const branch = p.git?.branch ?? p.git_branch;
823
+ if (typeof branch === "string") gitBranch = branch;
824
+ }
825
+ if (cwd && gitBranch && timestamp && metaId) break;
826
+ } catch {
827
+ }
828
+ }
829
+ return {
830
+ sessionId: fromName ?? metaId ?? fileName,
831
+ cwd,
832
+ timestamp,
833
+ gitBranch
834
+ };
835
+ }
836
+ var import_fs2, import_path2, import_os2, UUID_RE, codexAgent;
837
+ var init_codex = __esm({
838
+ "../service-capture/common/agents/codex.ts"() {
839
+ "use strict";
840
+ import_fs2 = require("fs");
841
+ import_path2 = require("path");
842
+ import_os2 = require("os");
843
+ UUID_RE = /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/i;
844
+ codexAgent = {
845
+ id: "codex",
846
+ displayName: "OpenAI Codex",
847
+ sessionsRoot() {
848
+ return sessionsDir();
849
+ },
850
+ isInstalled() {
851
+ return (0, import_fs2.existsSync)(codexHome());
852
+ },
853
+ discoverSessions() {
854
+ const root = sessionsDir();
855
+ if (!(0, import_fs2.existsSync)(root)) return [];
856
+ const files = [];
857
+ walkRollouts(root, files);
858
+ const sessions = [];
859
+ for (const filePath of files) {
860
+ try {
861
+ const stat = (0, import_fs2.statSync)(filePath);
862
+ const content = (0, import_fs2.readFileSync)(filePath, "utf-8");
863
+ const lineCount = content.split("\n").filter((l) => l.trim()).length;
864
+ const meta3 = readRolloutMeta(filePath, content);
865
+ sessions.push({
866
+ sessionId: meta3.sessionId,
867
+ filePath,
868
+ lineCount,
869
+ sizeBytes: stat.size,
870
+ cwd: meta3.cwd,
871
+ timestamp: meta3.timestamp,
872
+ gitBranch: meta3.gitBranch
873
+ });
874
+ } catch {
875
+ }
876
+ }
877
+ return sessions;
878
+ }
879
+ };
880
+ }
881
+ });
882
+
883
+ // ../service-capture/common/agents/registry.ts
884
+ function getAgents() {
885
+ return AGENTS;
886
+ }
887
+ function getAgent(id) {
888
+ const agent = AGENTS.find((a) => a.id === id);
889
+ if (!agent) throw new Error(`Unknown agent: ${id}`);
890
+ return agent;
891
+ }
892
+ function detectInstalled() {
893
+ return AGENTS.filter((a) => a.isInstalled());
894
+ }
895
+ var AGENTS;
896
+ var init_registry = __esm({
897
+ "../service-capture/common/agents/registry.ts"() {
898
+ "use strict";
899
+ init_claude_code();
900
+ init_codex();
901
+ AGENTS = [claudeCodeAgent, codexAgent];
902
+ }
903
+ });
904
+
905
+ // ../service-capture/common/agents/index.ts
906
+ var agents_exports = {};
907
+ __export(agents_exports, {
908
+ AGENT_IDS: () => AGENT_IDS,
909
+ claudeCodeAgent: () => claudeCodeAgent,
910
+ codexAgent: () => codexAgent,
911
+ detectInstalled: () => detectInstalled,
912
+ getAgent: () => getAgent,
913
+ getAgents: () => getAgents
914
+ });
915
+ var init_agents = __esm({
916
+ "../service-capture/common/agents/index.ts"() {
917
+ "use strict";
918
+ init_types();
919
+ init_claude_code();
920
+ init_codex();
921
+ init_registry();
922
+ }
923
+ });
924
+
678
925
  // ../service-capture/claude-sessions/collector.ts
679
926
  var collector_exports = {};
680
927
  __export(collector_exports, {
@@ -735,40 +982,11 @@ function extractGitInfo(projectDir, sessionTimestamp, sessionBranch) {
735
982
  }
736
983
  return result;
737
984
  }
738
- function extractSessionMeta(filePath) {
739
- const result = {
740
- cwd: null,
741
- timestamp: null,
742
- gitBranch: null
743
- };
744
- try {
745
- const content = (0, import_fs.readFileSync)(filePath, "utf-8");
746
- const lines = content.split("\n").filter((l) => l.trim()).slice(0, 50);
747
- for (const line of lines) {
748
- try {
749
- const entry = JSON.parse(line);
750
- if (!result.cwd && entry.cwd && typeof entry.cwd === "string") {
751
- result.cwd = entry.cwd;
752
- }
753
- if (!result.timestamp && entry.timestamp && typeof entry.timestamp === "string") {
754
- result.timestamp = entry.timestamp;
755
- }
756
- if (!result.gitBranch && entry.gitBranch && typeof entry.gitBranch === "string") {
757
- result.gitBranch = entry.gitBranch;
758
- }
759
- if (result.cwd && result.timestamp && result.gitBranch) break;
760
- } catch {
761
- }
762
- }
763
- } catch {
764
- }
765
- return result;
766
- }
767
985
  function resolveToken() {
768
986
  if (process.env.HAANSI_TOKEN) return process.env.HAANSI_TOKEN;
769
- if ((0, import_fs.existsSync)(CREDENTIALS_FILE3)) {
987
+ if ((0, import_fs3.existsSync)(CREDENTIALS_FILE3)) {
770
988
  try {
771
- const creds = JSON.parse((0, import_fs.readFileSync)(CREDENTIALS_FILE3, "utf-8"));
989
+ const creds = JSON.parse((0, import_fs3.readFileSync)(CREDENTIALS_FILE3, "utf-8"));
772
990
  if (creds.token) return creds.token;
773
991
  } catch {
774
992
  logger.warn("Could not parse credentials file", {
@@ -778,41 +996,32 @@ function resolveToken() {
778
996
  }
779
997
  return null;
780
998
  }
781
- function discoverSessions() {
782
- if (!(0, import_fs.existsSync)(CLAUDE_PROJECTS_DIR)) return [];
999
+ function resolveAgentIds(agents) {
1000
+ if (agents && agents.length > 0) return agents;
1001
+ const installed = detectInstalled().map((a) => a.id);
1002
+ return installed.length > 0 ? installed : ["claude-code"];
1003
+ }
1004
+ function discoverSessions(agentIds) {
783
1005
  const sessions = [];
784
- const projectDirs = (0, import_fs.readdirSync)(CLAUDE_PROJECTS_DIR).map((name) => (0, import_path.join)(CLAUDE_PROJECTS_DIR, name)).filter((p) => {
1006
+ for (const id of agentIds) {
1007
+ const agent = getAgent(id);
1008
+ let discovered = [];
785
1009
  try {
786
- return (0, import_fs.statSync)(p).isDirectory();
787
- } catch {
788
- return false;
1010
+ discovered = agent.discoverSessions();
1011
+ } catch (err) {
1012
+ logger.warn(`Discovery failed for ${agent.displayName}`, {
1013
+ err: err?.message
1014
+ });
1015
+ continue;
789
1016
  }
790
- });
791
- for (const dir of projectDirs) {
792
- const files = (0, import_fs.readdirSync)(dir).filter((f) => f.endsWith(".jsonl"));
793
- for (const file2 of files) {
794
- const filePath = (0, import_path.join)(dir, file2);
795
- const sessionId = (0, import_path.basename)(file2, ".jsonl");
796
- try {
797
- const stat = (0, import_fs.statSync)(filePath);
798
- const content = (0, import_fs.readFileSync)(filePath, "utf-8");
799
- const lineCount = content.split("\n").filter((l) => l.trim()).length;
800
- if (lineCount >= MIN_LINES) {
801
- sessions.push({
802
- sessionId,
803
- filePath,
804
- lineCount,
805
- sizeBytes: stat.size
806
- });
807
- }
808
- } catch {
809
- }
1017
+ for (const s of discovered) {
1018
+ if (s.lineCount >= MIN_LINES) sessions.push({ ...s, agentId: id });
810
1019
  }
811
1020
  }
812
1021
  return sessions;
813
1022
  }
814
- async function apiGet(path, apiUrl, token) {
815
- const response = await fetch(`${apiUrl}/api/v1${path}`, {
1023
+ async function apiGet(path, apiUrl2, token) {
1024
+ const response = await fetch(`${apiUrl2}/api/v1${path}`, {
816
1025
  headers: { Authorization: `Bearer ${token}` }
817
1026
  });
818
1027
  if (!response.ok) {
@@ -821,11 +1030,11 @@ async function apiGet(path, apiUrl, token) {
821
1030
  }
822
1031
  return response.json();
823
1032
  }
824
- async function getUploadedSessions(apiUrl, token) {
1033
+ async function getUploadedSessions(apiUrl2, token) {
825
1034
  try {
826
1035
  const data = await apiGet(
827
1036
  "/capture/sessions/uploaded",
828
- apiUrl,
1037
+ apiUrl2,
829
1038
  token
830
1039
  );
831
1040
  const map2 = /* @__PURE__ */ new Map();
@@ -841,18 +1050,19 @@ async function getUploadedSessions(apiUrl, token) {
841
1050
  return /* @__PURE__ */ new Map();
842
1051
  }
843
1052
  }
844
- async function uploadSession(sessionId, content, apiUrl, token, gitInfo) {
1053
+ async function uploadSession(sessionId, content, apiUrl2, token, agentSource, gitInfo) {
845
1054
  const body = (0, import_zlib.gzipSync)(Buffer.from(content, "utf-8"));
846
1055
  const headers = {
847
1056
  Authorization: `Bearer ${token}`,
848
1057
  "Content-Type": "application/x-ndjson",
849
1058
  "Content-Encoding": "gzip",
850
- "x-session-id": sessionId
1059
+ "x-session-id": sessionId,
1060
+ "x-haansi-agent-source": agentSource
851
1061
  };
852
1062
  if (gitInfo?.gitRemoteUrl) headers["x-git-remote-url"] = gitInfo.gitRemoteUrl;
853
1063
  if (gitInfo?.gitCommit) headers["x-git-commit"] = gitInfo.gitCommit;
854
1064
  if (gitInfo?.gitBranch) headers["x-git-branch"] = gitInfo.gitBranch;
855
- const response = await fetch(`${apiUrl}/api/v1/capture/sessions/raw`, {
1065
+ const response = await fetch(`${apiUrl2}/api/v1/capture/sessions/raw`, {
856
1066
  method: "POST",
857
1067
  headers,
858
1068
  body: new Uint8Array(body)
@@ -865,22 +1075,25 @@ async function uploadSession(sessionId, content, apiUrl, token, gitInfo) {
865
1075
  }
866
1076
  }
867
1077
  async function collect(options) {
868
- const apiUrl = process.env.HAANSI_API_URL ?? DEFAULT_API_URL3;
1078
+ const apiUrl2 = process.env.HAANSI_API_URL ?? DEFAULT_API_URL3;
869
1079
  const token = resolveToken();
870
1080
  if (!token && !options.dryRun) {
871
1081
  throw new Error(
872
1082
  "HAANSI_TOKEN is required. Set it as env var or in ~/.haansi/credentials.json"
873
1083
  );
874
1084
  }
875
- const localSessions = discoverSessions();
876
- logger.info(`Found ${localSessions.length} session files on disk`);
1085
+ const agentIds = resolveAgentIds(options.agents);
1086
+ const localSessions = discoverSessions(agentIds);
1087
+ logger.info(`Found ${localSessions.length} session files on disk`, {
1088
+ agents: agentIds.join(",")
1089
+ });
877
1090
  if (localSessions.length === 0) {
878
1091
  return { uploaded: 0, skipped: 0, errors: 0 };
879
1092
  }
880
1093
  let uploaded = 0;
881
1094
  let skipped = 0;
882
1095
  let errors = 0;
883
- const remoteMap = options.forceAll ? /* @__PURE__ */ new Map() : await getUploadedSessions(apiUrl, token);
1096
+ const remoteMap = options.forceAll ? /* @__PURE__ */ new Map() : await getUploadedSessions(apiUrl2, token);
884
1097
  const toUpload = [];
885
1098
  for (const session of localSessions) {
886
1099
  const remote = remoteMap.get(session.sessionId);
@@ -898,22 +1111,22 @@ async function collect(options) {
898
1111
  for (const session of batch) {
899
1112
  const shortId = session.sessionId.slice(0, 8);
900
1113
  try {
901
- const rawContent = (0, import_fs.readFileSync)(session.filePath, "utf-8");
1114
+ const rawContent = (0, import_fs3.readFileSync)(session.filePath, "utf-8");
902
1115
  const scrubbed = edgeScrub(rawContent);
903
- const sessionMeta = extractSessionMeta(session.filePath);
904
- const gitInfo = sessionMeta.cwd ? extractGitInfo(
905
- sessionMeta.cwd,
906
- sessionMeta.timestamp ?? void 0,
907
- sessionMeta.gitBranch ?? void 0
1116
+ const gitInfo = session.cwd ? extractGitInfo(
1117
+ session.cwd,
1118
+ session.timestamp ?? void 0,
1119
+ session.gitBranch ?? void 0
908
1120
  ) : {
909
1121
  gitRemoteUrl: null,
910
1122
  gitCommit: null,
911
- gitBranch: sessionMeta.gitBranch
1123
+ gitBranch: session.gitBranch
912
1124
  };
913
1125
  if (options.dryRun) {
914
1126
  logger.info(
915
- `[dry-run] Would upload ${shortId} (${session.lineCount} lines)`,
1127
+ `[dry-run] Would upload ${shortId} (${session.agentId}, ${session.lineCount} lines)`,
916
1128
  {
1129
+ agentSource: session.agentId,
917
1130
  gitRemoteUrl: gitInfo.gitRemoteUrl,
918
1131
  gitCommit: gitInfo.gitCommit?.slice(0, 8)
919
1132
  }
@@ -921,12 +1134,23 @@ async function collect(options) {
921
1134
  uploaded++;
922
1135
  continue;
923
1136
  }
924
- await uploadSession(session.sessionId, scrubbed, apiUrl, token, gitInfo);
1137
+ await uploadSession(
1138
+ session.sessionId,
1139
+ scrubbed,
1140
+ apiUrl2,
1141
+ token,
1142
+ session.agentId,
1143
+ gitInfo
1144
+ );
925
1145
  uploaded++;
926
- logger.info(`Uploaded ${shortId} (${session.lineCount} lines)`, {
927
- gitRemoteUrl: gitInfo.gitRemoteUrl,
928
- gitCommit: gitInfo.gitCommit?.slice(0, 8)
929
- });
1146
+ logger.info(
1147
+ `Uploaded ${shortId} (${session.agentId}, ${session.lineCount} lines)`,
1148
+ {
1149
+ agentSource: session.agentId,
1150
+ gitRemoteUrl: gitInfo.gitRemoteUrl,
1151
+ gitCommit: gitInfo.gitCommit?.slice(0, 8)
1152
+ }
1153
+ );
930
1154
  } catch (err) {
931
1155
  errors++;
932
1156
  logger.error(`Failed to upload ${shortId}`, { err: err.message });
@@ -947,22 +1171,22 @@ async function main() {
947
1171
  });
948
1172
  if (errors > 0) process.exit(1);
949
1173
  }
950
- var import_dotenv, import_path, import_fs, import_os, import_zlib, import_child_process, logger, DEFAULT_API_URL3, CREDENTIALS_FILE3, CLAUDE_PROJECTS_DIR, MIN_LINES, EDGE_SCRUB_PATTERNS;
1174
+ var import_dotenv, import_path3, import_fs3, import_os3, import_zlib, import_child_process, logger, DEFAULT_API_URL3, CREDENTIALS_FILE3, MIN_LINES, EDGE_SCRUB_PATTERNS;
951
1175
  var init_collector = __esm({
952
1176
  "../service-capture/claude-sessions/collector.ts"() {
953
1177
  "use strict";
954
1178
  import_dotenv = __toESM(require_main());
955
- import_path = require("path");
956
- import_fs = require("fs");
957
- import_os = require("os");
1179
+ import_path3 = require("path");
1180
+ import_fs3 = require("fs");
1181
+ import_os3 = require("os");
958
1182
  import_zlib = require("zlib");
959
1183
  import_child_process = require("child_process");
960
1184
  init_logger();
961
- (0, import_dotenv.config)({ path: (0, import_path.resolve)(__dirname, "../../../.env") });
962
- logger = createLogger("claude-collector");
1185
+ init_agents();
1186
+ (0, import_dotenv.config)({ path: (0, import_path3.resolve)(__dirname, "../../../.env") });
1187
+ logger = createLogger("session-collector");
963
1188
  DEFAULT_API_URL3 = "https://api.haansi.co";
964
- CREDENTIALS_FILE3 = (0, import_path.join)((0, import_os.homedir)(), ".haansi", "credentials.json");
965
- CLAUDE_PROJECTS_DIR = (0, import_path.join)((0, import_os.homedir)(), ".claude", "projects");
1189
+ CREDENTIALS_FILE3 = (0, import_path3.join)((0, import_os3.homedir)(), ".haansi", "credentials.json");
966
1190
  MIN_LINES = 4;
967
1191
  EDGE_SCRUB_PATTERNS = [
968
1192
  {
@@ -1012,6 +1236,151 @@ var init_collector = __esm({
1012
1236
  }
1013
1237
  });
1014
1238
 
1239
+ // src/lib/paths.ts
1240
+ function apiUrl() {
1241
+ return process.env.HAANSI_API_URL ?? DEFAULT_API_URL4;
1242
+ }
1243
+ function resolveToken2() {
1244
+ if (process.env.HAANSI_TOKEN) return process.env.HAANSI_TOKEN;
1245
+ if ((0, import_node_fs3.existsSync)(CREDENTIALS_FILE4)) {
1246
+ try {
1247
+ const creds = JSON.parse((0, import_node_fs3.readFileSync)(CREDENTIALS_FILE4, "utf-8"));
1248
+ if (typeof creds.token === "string" && creds.token) return creds.token;
1249
+ } catch {
1250
+ }
1251
+ }
1252
+ return null;
1253
+ }
1254
+ function resolveHaansiBin() {
1255
+ try {
1256
+ const binPath = (0, import_node_child_process2.execSync)("which haansi", { encoding: "utf-8" }).trim();
1257
+ if (binPath) return binPath;
1258
+ } catch {
1259
+ }
1260
+ return "haansi";
1261
+ }
1262
+ var import_node_fs3, import_node_path3, import_node_os3, import_node_child_process2, DEFAULT_API_URL4, HAANSI_DIR3, CREDENTIALS_FILE4, CONFIG_FILE;
1263
+ var init_paths = __esm({
1264
+ "src/lib/paths.ts"() {
1265
+ "use strict";
1266
+ import_node_fs3 = require("node:fs");
1267
+ import_node_path3 = require("node:path");
1268
+ import_node_os3 = require("node:os");
1269
+ import_node_child_process2 = require("node:child_process");
1270
+ DEFAULT_API_URL4 = "https://api.haansi.co";
1271
+ HAANSI_DIR3 = (0, import_node_path3.join)((0, import_node_os3.homedir)(), ".haansi");
1272
+ CREDENTIALS_FILE4 = (0, import_node_path3.join)(HAANSI_DIR3, "credentials.json");
1273
+ CONFIG_FILE = (0, import_node_path3.join)(HAANSI_DIR3, "config.json");
1274
+ }
1275
+ });
1276
+
1277
+ // src/lib/agent-selection.ts
1278
+ var agent_selection_exports = {};
1279
+ __export(agent_selection_exports, {
1280
+ parseAgentFlag: () => parseAgentFlag,
1281
+ promptForAgents: () => promptForAgents,
1282
+ readSavedAgents: () => readSavedAgents,
1283
+ resolveAgentSelection: () => resolveAgentSelection,
1284
+ saveAgents: () => saveAgents
1285
+ });
1286
+ function parseAgentFlag(value) {
1287
+ if (!value) return null;
1288
+ const normalized = value.trim().toLowerCase();
1289
+ if (normalized === "both" || normalized === "all") return [...VALID];
1290
+ const out = [];
1291
+ for (const part of normalized.split(",").map((s) => s.trim())) {
1292
+ if (part === "claude" || part === "claude-code") {
1293
+ if (!out.includes("claude-code")) out.push("claude-code");
1294
+ } else if (part === "codex") {
1295
+ if (!out.includes("codex")) out.push("codex");
1296
+ }
1297
+ }
1298
+ return out.length > 0 ? out : null;
1299
+ }
1300
+ function readSavedAgents() {
1301
+ if (!(0, import_node_fs4.existsSync)(CONFIG_FILE)) return null;
1302
+ try {
1303
+ const cfg = JSON.parse((0, import_node_fs4.readFileSync)(CONFIG_FILE, "utf-8"));
1304
+ if (Array.isArray(cfg.agents)) {
1305
+ const agents = cfg.agents.filter(
1306
+ (a) => VALID.includes(a)
1307
+ );
1308
+ return agents.length > 0 ? agents : null;
1309
+ }
1310
+ } catch {
1311
+ }
1312
+ return null;
1313
+ }
1314
+ function saveAgents(agents) {
1315
+ let cfg = {};
1316
+ if ((0, import_node_fs4.existsSync)(CONFIG_FILE)) {
1317
+ try {
1318
+ cfg = JSON.parse((0, import_node_fs4.readFileSync)(CONFIG_FILE, "utf-8"));
1319
+ } catch {
1320
+ cfg = {};
1321
+ }
1322
+ }
1323
+ cfg.agents = agents;
1324
+ (0, import_node_fs4.mkdirSync)(HAANSI_DIR3, { recursive: true });
1325
+ (0, import_node_fs4.writeFileSync)(CONFIG_FILE, JSON.stringify(cfg, null, 2) + "\n", {
1326
+ encoding: "utf-8",
1327
+ mode: 384
1328
+ });
1329
+ }
1330
+ function resolveAgentSelection(flagValue) {
1331
+ const fromFlag = parseAgentFlag(flagValue);
1332
+ if (fromFlag) return { agents: fromFlag, source: "flag" };
1333
+ const saved = readSavedAgents();
1334
+ if (saved) return { agents: saved, source: "config" };
1335
+ const detected = detectInstalled().map((a) => a.id);
1336
+ if (detected.length > 0) return { agents: detected, source: "detected" };
1337
+ return { agents: ["claude-code"], source: "default" };
1338
+ }
1339
+ async function promptForAgents() {
1340
+ const installed = new Set(detectInstalled().map((a) => a.id));
1341
+ const agents = getAgents();
1342
+ console.log("\nWhich coding agents should Haansi connect?\n");
1343
+ agents.forEach((a, i) => {
1344
+ const mark = installed.has(a.id) ? "installed" : "not detected";
1345
+ console.log(` ${i + 1}) ${a.displayName} (${mark})`);
1346
+ });
1347
+ const rl = readline2.createInterface({
1348
+ input: process.stdin,
1349
+ output: process.stdout
1350
+ });
1351
+ const answer = await new Promise((resolve4) => {
1352
+ rl.question(
1353
+ "\nEnter numbers (comma-separated), or press Enter for all installed: ",
1354
+ (a) => {
1355
+ rl.close();
1356
+ resolve4(a.trim());
1357
+ }
1358
+ );
1359
+ });
1360
+ if (!answer) {
1361
+ const detected = agents.filter((a) => installed.has(a.id)).map((a) => a.id);
1362
+ return detected.length > 0 ? detected : [...VALID];
1363
+ }
1364
+ const picked = [];
1365
+ for (const token of answer.split(",").map((s) => s.trim())) {
1366
+ const idx = parseInt(token, 10) - 1;
1367
+ const agent = agents[idx];
1368
+ if (agent && !picked.includes(agent.id)) picked.push(agent.id);
1369
+ }
1370
+ return picked.length > 0 ? picked : [...VALID];
1371
+ }
1372
+ var import_node_fs4, readline2, VALID;
1373
+ var init_agent_selection = __esm({
1374
+ "src/lib/agent-selection.ts"() {
1375
+ "use strict";
1376
+ import_node_fs4 = require("node:fs");
1377
+ readline2 = __toESM(require("node:readline"));
1378
+ init_agents();
1379
+ init_paths();
1380
+ VALID = ["claude-code", "codex"];
1381
+ }
1382
+ });
1383
+
1015
1384
  // ../../node_modules/node-cron/src/task.js
1016
1385
  var require_task = __commonJS({
1017
1386
  "../../node_modules/node-cron/src/task.js"(exports2, module2) {
@@ -1924,23 +2293,30 @@ var require_node_cron = __commonJS({
1924
2293
 
1925
2294
  // ../service-capture/claude-sessions/collector-daemon.ts
1926
2295
  var collector_daemon_exports = {};
2296
+ function resolveAgentsEnv() {
2297
+ const raw = process.env.HAANSI_AGENTS;
2298
+ if (!raw) return void 0;
2299
+ const valid = ["claude-code", "codex"];
2300
+ const parsed = raw.split(",").map((s) => s.trim().toLowerCase()).filter((s) => valid.includes(s));
2301
+ return parsed.length > 0 ? parsed : void 0;
2302
+ }
1927
2303
  function getOnDiskVersion() {
1928
2304
  try {
1929
- const realEntry = (0, import_fs2.realpathSync)(process.argv[1]);
1930
- const pkgPath = (0, import_path2.resolve)((0, import_path2.dirname)(realEntry), "..", "package.json");
1931
- if (!(0, import_fs2.existsSync)(pkgPath)) return null;
1932
- const pkg = JSON.parse((0, import_fs2.readFileSync)(pkgPath, "utf-8"));
2305
+ const realEntry = (0, import_fs4.realpathSync)(process.argv[1]);
2306
+ const pkgPath = (0, import_path4.resolve)((0, import_path4.dirname)(realEntry), "..", "package.json");
2307
+ if (!(0, import_fs4.existsSync)(pkgPath)) return null;
2308
+ const pkg = JSON.parse((0, import_fs4.readFileSync)(pkgPath, "utf-8"));
1933
2309
  return pkg.version ?? null;
1934
2310
  } catch {
1935
2311
  return null;
1936
2312
  }
1937
2313
  }
1938
2314
  async function run() {
1939
- const projectPath = process.env.CLAUDE_PROJECT_PATH || void 0;
1940
- logger2.info("Collect run starting", { projectPath: projectPath ?? "all" });
2315
+ const agents = resolveAgentsEnv();
2316
+ logger2.info("Collect run starting", { agents: agents?.join(",") ?? "all" });
1941
2317
  const start = Date.now();
1942
2318
  try {
1943
- const { uploaded, skipped, errors } = await collect({ projectPath });
2319
+ const { uploaded, skipped, errors } = await collect({ agents });
1944
2320
  const elapsed = ((Date.now() - start) / 1e3).toFixed(1);
1945
2321
  logger2.info(`Collect run complete in ${elapsed}s`, {
1946
2322
  uploaded,
@@ -1977,20 +2353,20 @@ async function main2() {
1977
2353
  process.on("SIGTERM", () => shutdown("SIGTERM"));
1978
2354
  process.on("SIGINT", () => shutdown("SIGINT"));
1979
2355
  }
1980
- var import_dotenv2, import_fs2, import_path2, import_node_cron, logger2, IN_MEMORY_VERSION, SCHEDULE;
2356
+ var import_dotenv2, import_fs4, import_path4, import_node_cron, logger2, IN_MEMORY_VERSION, SCHEDULE;
1981
2357
  var init_collector_daemon = __esm({
1982
2358
  "../service-capture/claude-sessions/collector-daemon.ts"() {
1983
2359
  "use strict";
1984
2360
  import_dotenv2 = __toESM(require_main());
1985
- import_fs2 = require("fs");
1986
- import_path2 = require("path");
2361
+ import_fs4 = require("fs");
2362
+ import_path4 = require("path");
1987
2363
  import_node_cron = __toESM(require_node_cron());
1988
2364
  init_collector();
1989
2365
  init_logger();
1990
- (0, import_dotenv2.config)({ path: (0, import_path2.resolve)(__dirname, "../../../.env") });
2366
+ (0, import_dotenv2.config)({ path: (0, import_path4.resolve)(__dirname, "../../../.env") });
1991
2367
  logger2 = createLogger("collector-daemon");
1992
2368
  IN_MEMORY_VERSION = require_package().version;
1993
- SCHEDULE = process.env.CLAUDE_COLLECT_SCHEDULE ?? "*/30 * * * *";
2369
+ SCHEDULE = process.env.HAANSI_COLLECT_SCHEDULE ?? process.env.CLAUDE_COLLECT_SCHEDULE ?? "*/30 * * * *";
1994
2370
  main2().catch((err) => {
1995
2371
  logger2.error("Daemon failed to start", err);
1996
2372
  process.exit(1);
@@ -2907,7 +3283,7 @@ function createZodEnum(values, params) {
2907
3283
  });
2908
3284
  }
2909
3285
  var ParseInputLazyPath, handleResult, ZodType, cuidRegex, cuid2Regex, ulidRegex, uuidRegex, nanoidRegex, jwtRegex, durationRegex, emailRegex, _emojiRegex, emojiRegex, ipv4Regex, ipv4CidrRegex, ipv6Regex, ipv6CidrRegex, base64Regex, base64urlRegex, dateRegexSource, dateRegex, ZodString, ZodNumber, ZodBigInt, ZodBoolean, ZodDate, ZodSymbol, ZodUndefined, ZodNull, ZodAny, ZodUnknown, ZodNever, ZodVoid, ZodArray, ZodObject, ZodUnion, getDiscriminator, ZodDiscriminatedUnion, ZodIntersection, ZodTuple, ZodRecord, ZodMap, ZodSet, ZodFunction, ZodLazy, ZodLiteral, ZodEnum, ZodNativeEnum, ZodPromise, ZodEffects, ZodOptional, ZodNullable, ZodDefault, ZodCatch, ZodNaN, BRAND, ZodBranded, ZodPipeline, ZodReadonly, late, ZodFirstPartyTypeKind, stringType, numberType, nanType, bigIntType, booleanType, dateType, symbolType, undefinedType, nullType, anyType, unknownType, neverType, voidType, arrayType, objectType, strictObjectType, unionType, discriminatedUnionType, intersectionType, tupleType, recordType, mapType, setType, functionType, lazyType, literalType, enumType, nativeEnumType, promiseType, effectsType, optionalType, nullableType, preprocessType, pipelineType;
2910
- var init_types = __esm({
3286
+ var init_types2 = __esm({
2911
3287
  "../../node_modules/zod/v3/types.js"() {
2912
3288
  init_ZodError();
2913
3289
  init_errors();
@@ -6160,7 +6536,7 @@ var init_external = __esm({
6160
6536
  init_parseUtil();
6161
6537
  init_typeAliases();
6162
6538
  init_util();
6163
- init_types();
6539
+ init_types2();
6164
6540
  init_ZodError();
6165
6541
  }
6166
6542
  });
@@ -13942,7 +14318,7 @@ function assertCompleteRequestResourceTemplate(request) {
13942
14318
  void request;
13943
14319
  }
13944
14320
  var LATEST_PROTOCOL_VERSION, DEFAULT_NEGOTIATED_PROTOCOL_VERSION, SUPPORTED_PROTOCOL_VERSIONS, RELATED_TASK_META_KEY, JSONRPC_VERSION, AssertObjectSchema, ProgressTokenSchema, CursorSchema, TaskCreationParamsSchema, TaskMetadataSchema, RelatedTaskMetadataSchema, RequestMetaSchema, BaseRequestParamsSchema, TaskAugmentedRequestParamsSchema, isTaskAugmentedRequestParams, RequestSchema, NotificationsParamsSchema, NotificationSchema, ResultSchema, RequestIdSchema, JSONRPCRequestSchema, isJSONRPCRequest, JSONRPCNotificationSchema, isJSONRPCNotification, JSONRPCResultResponseSchema, isJSONRPCResultResponse, ErrorCode, JSONRPCErrorResponseSchema, isJSONRPCErrorResponse, JSONRPCMessageSchema, JSONRPCResponseSchema, EmptyResultSchema, CancelledNotificationParamsSchema, CancelledNotificationSchema, IconSchema, IconsSchema, BaseMetadataSchema, ImplementationSchema, FormElicitationCapabilitySchema, ElicitationCapabilitySchema, ClientTasksCapabilitySchema, ServerTasksCapabilitySchema, ClientCapabilitiesSchema, InitializeRequestParamsSchema, InitializeRequestSchema, isInitializeRequest, ServerCapabilitiesSchema, InitializeResultSchema, InitializedNotificationSchema, PingRequestSchema, ProgressSchema, ProgressNotificationParamsSchema, ProgressNotificationSchema, PaginatedRequestParamsSchema, PaginatedRequestSchema, PaginatedResultSchema, TaskStatusSchema, TaskSchema, CreateTaskResultSchema, TaskStatusNotificationParamsSchema, TaskStatusNotificationSchema, GetTaskRequestSchema, GetTaskResultSchema, GetTaskPayloadRequestSchema, GetTaskPayloadResultSchema, ListTasksRequestSchema, ListTasksResultSchema, CancelTaskRequestSchema, CancelTaskResultSchema, ResourceContentsSchema, TextResourceContentsSchema, Base64Schema, BlobResourceContentsSchema, RoleSchema, AnnotationsSchema, ResourceSchema, ResourceTemplateSchema, ListResourcesRequestSchema, ListResourcesResultSchema, ListResourceTemplatesRequestSchema, ListResourceTemplatesResultSchema, ResourceRequestParamsSchema, ReadResourceRequestParamsSchema, ReadResourceRequestSchema, ReadResourceResultSchema, ResourceListChangedNotificationSchema, SubscribeRequestParamsSchema, SubscribeRequestSchema, UnsubscribeRequestParamsSchema, UnsubscribeRequestSchema, ResourceUpdatedNotificationParamsSchema, ResourceUpdatedNotificationSchema, PromptArgumentSchema, PromptSchema, ListPromptsRequestSchema, ListPromptsResultSchema, GetPromptRequestParamsSchema, GetPromptRequestSchema, TextContentSchema, ImageContentSchema, AudioContentSchema, ToolUseContentSchema, EmbeddedResourceSchema, ResourceLinkSchema, ContentBlockSchema, PromptMessageSchema, GetPromptResultSchema, PromptListChangedNotificationSchema, ToolAnnotationsSchema, ToolExecutionSchema, ToolSchema, ListToolsRequestSchema, ListToolsResultSchema, CallToolResultSchema, CompatibilityCallToolResultSchema, CallToolRequestParamsSchema, CallToolRequestSchema, ToolListChangedNotificationSchema, ListChangedOptionsBaseSchema, LoggingLevelSchema, SetLevelRequestParamsSchema, SetLevelRequestSchema, LoggingMessageNotificationParamsSchema, LoggingMessageNotificationSchema, ModelHintSchema, ModelPreferencesSchema, ToolChoiceSchema, ToolResultContentSchema, SamplingContentSchema, SamplingMessageContentBlockSchema, SamplingMessageSchema, CreateMessageRequestParamsSchema, CreateMessageRequestSchema, CreateMessageResultSchema, CreateMessageResultWithToolsSchema, BooleanSchemaSchema, StringSchemaSchema, NumberSchemaSchema, UntitledSingleSelectEnumSchemaSchema, TitledSingleSelectEnumSchemaSchema, LegacyTitledEnumSchemaSchema, SingleSelectEnumSchemaSchema, UntitledMultiSelectEnumSchemaSchema, TitledMultiSelectEnumSchemaSchema, MultiSelectEnumSchemaSchema, EnumSchemaSchema, PrimitiveSchemaDefinitionSchema, ElicitRequestFormParamsSchema, ElicitRequestURLParamsSchema, ElicitRequestParamsSchema, ElicitRequestSchema, ElicitationCompleteNotificationParamsSchema, ElicitationCompleteNotificationSchema, ElicitResultSchema, ResourceTemplateReferenceSchema, PromptReferenceSchema, CompleteRequestParamsSchema, CompleteRequestSchema, CompleteResultSchema, RootSchema, ListRootsRequestSchema, ListRootsResultSchema, RootsListChangedNotificationSchema, ClientRequestSchema, ClientNotificationSchema, ClientResultSchema, ServerRequestSchema, ServerNotificationSchema, ServerResultSchema, McpError, UrlElicitationRequiredError;
13945
- var init_types2 = __esm({
14321
+ var init_types3 = __esm({
13946
14322
  "../service-capture/node_modules/@modelcontextprotocol/sdk/dist/esm/types.js"() {
13947
14323
  init_v42();
13948
14324
  LATEST_PROTOCOL_VERSION = "2025-11-25";
@@ -17108,7 +17484,7 @@ var DEFAULT_REQUEST_TIMEOUT_MSEC, Protocol;
17108
17484
  var init_protocol = __esm({
17109
17485
  "../service-capture/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.js"() {
17110
17486
  init_zod_compat();
17111
- init_types2();
17487
+ init_types3();
17112
17488
  init_interfaces();
17113
17489
  init_zod_json_schema_compat();
17114
17490
  DEFAULT_REQUEST_TIMEOUT_MSEC = 6e4;
@@ -30690,7 +31066,7 @@ var Server;
30690
31066
  var init_server2 = __esm({
30691
31067
  "../service-capture/node_modules/@modelcontextprotocol/sdk/dist/esm/server/index.js"() {
30692
31068
  init_protocol();
30693
- init_types2();
31069
+ init_types3();
30694
31070
  init_ajv_provider();
30695
31071
  init_zod_compat();
30696
31072
  init_server();
@@ -31260,7 +31636,7 @@ var init_mcp = __esm({
31260
31636
  init_server2();
31261
31637
  init_zod_compat();
31262
31638
  init_zod_json_schema_compat();
31263
- init_types2();
31639
+ init_types3();
31264
31640
  init_completable();
31265
31641
  init_uriTemplate();
31266
31642
  init_toolNameValidation();
@@ -32001,7 +32377,7 @@ function serializeMessage(message) {
32001
32377
  var ReadBuffer;
32002
32378
  var init_stdio = __esm({
32003
32379
  "../service-capture/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/stdio.js"() {
32004
- init_types2();
32380
+ init_types3();
32005
32381
  ReadBuffer = class {
32006
32382
  append(chunk) {
32007
32383
  this._buffer = this._buffer ? Buffer.concat([this._buffer, chunk]) : chunk;
@@ -50810,9 +51186,9 @@ var require_view = __commonJS({
50810
51186
  var path = require("node:path");
50811
51187
  var fs = require("node:fs");
50812
51188
  var dirname2 = path.dirname;
50813
- var basename2 = path.basename;
51189
+ var basename3 = path.basename;
50814
51190
  var extname = path.extname;
50815
- var join9 = path.join;
51191
+ var join12 = path.join;
50816
51192
  var resolve4 = path.resolve;
50817
51193
  module2.exports = View;
50818
51194
  function View(name, options) {
@@ -50849,7 +51225,7 @@ var require_view = __commonJS({
50849
51225
  var root = roots[i];
50850
51226
  var loc = resolve4(root, name);
50851
51227
  var dir = dirname2(loc);
50852
- var file2 = basename2(loc);
51228
+ var file2 = basename3(loc);
50853
51229
  path2 = this.resolve(dir, file2);
50854
51230
  }
50855
51231
  return path2;
@@ -50874,12 +51250,12 @@ var require_view = __commonJS({
50874
51250
  };
50875
51251
  View.prototype.resolve = function resolve5(dir, file2) {
50876
51252
  var ext = this.ext;
50877
- var path2 = join9(dir, file2);
51253
+ var path2 = join12(dir, file2);
50878
51254
  var stat = tryStat(path2);
50879
51255
  if (stat && stat.isFile()) {
50880
51256
  return path2;
50881
51257
  }
50882
- path2 = join9(dir, basename2(file2, ext), "index" + ext);
51258
+ path2 = join12(dir, basename3(file2, ext), "index" + ext);
50883
51259
  stat = tryStat(path2);
50884
51260
  if (stat && stat.isFile()) {
50885
51261
  return path2;
@@ -54163,7 +54539,7 @@ var require_content_disposition = __commonJS({
54163
54539
  "use strict";
54164
54540
  module2.exports = contentDisposition;
54165
54541
  module2.exports.parse = parse4;
54166
- var basename2 = require("path").basename;
54542
+ var basename3 = require("path").basename;
54167
54543
  var ENCODE_URL_ATTR_CHAR_REGEXP = /[\x00-\x20"'()*,/:;<=>?@[\\\]{}\x7f]/g;
54168
54544
  var HEX_ESCAPE_REGEXP = /%[0-9A-Fa-f]{2}/;
54169
54545
  var HEX_ESCAPE_REPLACE_REGEXP = /%([0-9A-Fa-f]{2})/g;
@@ -54198,9 +54574,9 @@ var require_content_disposition = __commonJS({
54198
54574
  if (typeof fallback === "string" && NON_LATIN1_REGEXP.test(fallback)) {
54199
54575
  throw new TypeError("fallback must be ISO-8859-1 string");
54200
54576
  }
54201
- var name = basename2(filename);
54577
+ var name = basename3(filename);
54202
54578
  var isQuotedString = TEXT_REGEXP.test(name);
54203
- var fallbackName = typeof fallback !== "string" ? fallback && getlatin1(name) : basename2(fallback);
54579
+ var fallbackName = typeof fallback !== "string" ? fallback && getlatin1(name) : basename3(fallback);
54204
54580
  var hasFallback = typeof fallbackName === "string" && fallbackName !== name;
54205
54581
  if (hasFallback || !isQuotedString || HEX_ESCAPE_REGEXP.test(name)) {
54206
54582
  params["filename*"] = name;
@@ -54524,7 +54900,7 @@ var require_send = __commonJS({
54524
54900
  var Stream = require("stream");
54525
54901
  var util2 = require("util");
54526
54902
  var extname = path.extname;
54527
- var join9 = path.join;
54903
+ var join12 = path.join;
54528
54904
  var normalize = path.normalize;
54529
54905
  var resolve4 = path.resolve;
54530
54906
  var sep = path.sep;
@@ -54696,7 +55072,7 @@ var require_send = __commonJS({
54696
55072
  return res;
54697
55073
  }
54698
55074
  parts = path2.split(sep);
54699
- path2 = normalize(join9(root, path2));
55075
+ path2 = normalize(join12(root, path2));
54700
55076
  } else {
54701
55077
  if (UP_PATH_REGEXP.test(path2)) {
54702
55078
  debug('malicious path "%s"', path2);
@@ -54829,7 +55205,7 @@ var require_send = __commonJS({
54829
55205
  if (err) return self.onStatError(err);
54830
55206
  return self.error(404);
54831
55207
  }
54832
- var p = join9(path2, self._index[i]);
55208
+ var p = join12(path2, self._index[i]);
54833
55209
  debug('stat "%s"', p);
54834
55210
  fs.stat(p, function(err2, stat) {
54835
55211
  if (err2) return next(err2);
@@ -56223,7 +56599,7 @@ var init_dist = __esm({
56223
56599
  var WebStandardStreamableHTTPServerTransport;
56224
56600
  var init_webStandardStreamableHttp = __esm({
56225
56601
  "../service-capture/node_modules/@modelcontextprotocol/sdk/dist/esm/server/webStandardStreamableHttp.js"() {
56226
- init_types2();
56602
+ init_types3();
56227
56603
  WebStandardStreamableHTTPServerTransport = class {
56228
56604
  constructor(options = {}) {
56229
56605
  this._started = false;
@@ -56924,11 +57300,11 @@ var init_streamableHttp = __esm({
56924
57300
 
56925
57301
  // ../service-capture/claude-sessions/mcp-server.ts
56926
57302
  var mcp_server_exports = {};
56927
- function resolveToken2() {
57303
+ function resolveToken3() {
56928
57304
  if (process.env.HAANSI_TOKEN) return process.env.HAANSI_TOKEN;
56929
- if ((0, import_fs3.existsSync)(CREDENTIALS_FILE4)) {
57305
+ if ((0, import_fs5.existsSync)(CREDENTIALS_FILE5)) {
56930
57306
  try {
56931
- const creds = JSON.parse((0, import_fs3.readFileSync)(CREDENTIALS_FILE4, "utf-8"));
57307
+ const creds = JSON.parse((0, import_fs5.readFileSync)(CREDENTIALS_FILE5, "utf-8"));
56932
57308
  if (creds.token) return creds.token;
56933
57309
  } catch {
56934
57310
  }
@@ -57013,25 +57389,25 @@ async function main3() {
57013
57389
  }
57014
57390
  warmupInBackground();
57015
57391
  }
57016
- var import_dotenv3, import_path3, import_fs3, import_os2, DEFAULT_API_URL4, CREDENTIALS_FILE4, API_URL, TOKEN, contextSchema, isCollecting, lastCollectTime, COLLECT_COOLDOWN_MS, mcpServer, server;
57392
+ var import_dotenv3, import_path5, import_fs5, import_os4, DEFAULT_API_URL5, CREDENTIALS_FILE5, API_URL, TOKEN, contextSchema, isCollecting, lastCollectTime, COLLECT_COOLDOWN_MS, mcpServer, server;
57017
57393
  var init_mcp_server2 = __esm({
57018
57394
  "../service-capture/claude-sessions/mcp-server.ts"() {
57019
57395
  "use strict";
57020
57396
  import_dotenv3 = __toESM(require_main());
57021
- import_path3 = require("path");
57022
- import_fs3 = require("fs");
57023
- import_os2 = require("os");
57397
+ import_path5 = require("path");
57398
+ import_fs5 = require("fs");
57399
+ import_os4 = require("os");
57024
57400
  init_collector();
57025
57401
  init_http_timeout();
57026
57402
  init_src();
57027
57403
  init_mcp();
57028
57404
  init_stdio2();
57029
- init_types2();
57030
- (0, import_dotenv3.config)({ path: (0, import_path3.resolve)(__dirname, "../../../.env") });
57031
- DEFAULT_API_URL4 = "https://api.haansi.co";
57032
- CREDENTIALS_FILE4 = (0, import_path3.join)((0, import_os2.homedir)(), ".haansi", "credentials.json");
57033
- API_URL = process.env.HAANSI_API_URL ?? DEFAULT_API_URL4;
57034
- TOKEN = resolveToken2();
57405
+ init_types3();
57406
+ (0, import_dotenv3.config)({ path: (0, import_path5.resolve)(__dirname, "../../../.env") });
57407
+ DEFAULT_API_URL5 = "https://api.haansi.co";
57408
+ CREDENTIALS_FILE5 = (0, import_path5.join)((0, import_os4.homedir)(), ".haansi", "credentials.json");
57409
+ API_URL = process.env.HAANSI_API_URL ?? DEFAULT_API_URL5;
57410
+ TOKEN = resolveToken3();
57035
57411
  if (!TOKEN) {
57036
57412
  process.exit(1);
57037
57413
  }
@@ -57323,30 +57699,59 @@ var init_mcp_server2 = __esm({
57323
57699
  }
57324
57700
  });
57325
57701
 
57702
+ // src/lib/codex-config.ts
57703
+ function tomlString(value) {
57704
+ const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n");
57705
+ return `"${escaped}"`;
57706
+ }
57707
+ function tomlStringArray(values) {
57708
+ return `[${values.map(tomlString).join(", ")}]`;
57709
+ }
57710
+ function tomlInlineTable(entries) {
57711
+ const parts = Object.entries(entries).map(
57712
+ ([k, v]) => `${k} = ${tomlString(v)}`
57713
+ );
57714
+ return `{ ${parts.join(", ")} }`;
57715
+ }
57716
+ function upsertTomlTable(content, tablePath, entries) {
57717
+ const bodyLines = Object.entries(entries).map(([k, v]) => `${k} = ${v}`);
57718
+ const block = [`[${tablePath}]`, ...bodyLines].join("\n");
57719
+ const lines = content.split("\n");
57720
+ const headerRe = new RegExp(
57721
+ `^\\s*\\[\\s*${tablePath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*\\]\\s*$`
57722
+ );
57723
+ const startIdx = lines.findIndex((l) => headerRe.test(l));
57724
+ if (startIdx === -1) {
57725
+ const trimmed = content.replace(/\s*$/, "");
57726
+ return trimmed.length > 0 ? `${trimmed}
57727
+
57728
+ ${block}
57729
+ ` : `${block}
57730
+ `;
57731
+ }
57732
+ let endIdx = lines.length;
57733
+ for (let i = startIdx + 1; i < lines.length; i++) {
57734
+ if (/^\s*\[/.test(lines[i])) {
57735
+ endIdx = i;
57736
+ break;
57737
+ }
57738
+ }
57739
+ const before = lines.slice(0, startIdx);
57740
+ const after = lines.slice(endIdx);
57741
+ const rebuilt = [...before, ...block.split("\n"), ...after].join("\n");
57742
+ return rebuilt.replace(/\n{3,}/g, "\n\n");
57743
+ }
57744
+ var init_codex_config = __esm({
57745
+ "src/lib/codex-config.ts"() {
57746
+ "use strict";
57747
+ }
57748
+ });
57749
+
57326
57750
  // src/commands/setup-mcp.ts
57327
57751
  var setup_mcp_exports = {};
57328
57752
  __export(setup_mcp_exports, {
57329
57753
  setupMcp: () => setupMcp
57330
57754
  });
57331
- function resolveHaansiBin() {
57332
- try {
57333
- const binPath = (0, import_node_child_process2.execSync)("which haansi", { encoding: "utf-8" }).trim();
57334
- if (binPath) return binPath;
57335
- } catch {
57336
- }
57337
- return "haansi";
57338
- }
57339
- function resolveToken3() {
57340
- if (process.env.HAANSI_TOKEN) return process.env.HAANSI_TOKEN;
57341
- if ((0, import_node_fs3.existsSync)(CREDENTIALS_FILE5)) {
57342
- try {
57343
- const creds = JSON.parse((0, import_node_fs3.readFileSync)(CREDENTIALS_FILE5, "utf-8"));
57344
- if (typeof creds.token === "string" && creds.token) return creds.token;
57345
- } catch {
57346
- }
57347
- }
57348
- return null;
57349
- }
57350
57755
  function parseTarget(arg) {
57351
57756
  if (!arg || arg === "all") return "all";
57352
57757
  if (arg === "solutions" || arg === "exchange") return arg;
@@ -57354,12 +57759,14 @@ function parseTarget(arg) {
57354
57759
  `Unknown target: ${arg}. Use one of: solutions, exchange, all (default).`
57355
57760
  );
57356
57761
  }
57357
- async function setupMcp(targetArg) {
57358
- const target = parseTarget(targetArg);
57762
+ function exchangeUrl() {
57763
+ return `${apiUrl().replace(/\/+$/, "")}/api/v1/access/exchange/mcp`;
57764
+ }
57765
+ function setupClaudeMcp(target) {
57359
57766
  let config6 = {};
57360
- if ((0, import_node_fs3.existsSync)(CLAUDE_JSON)) {
57767
+ if ((0, import_node_fs5.existsSync)(CLAUDE_JSON)) {
57361
57768
  try {
57362
- config6 = JSON.parse((0, import_node_fs3.readFileSync)(CLAUDE_JSON, "utf-8"));
57769
+ config6 = JSON.parse((0, import_node_fs5.readFileSync)(CLAUDE_JSON, "utf-8"));
57363
57770
  } catch {
57364
57771
  console.error(
57365
57772
  `Warning: Could not parse ${CLAUDE_JSON} \u2014 will overwrite with new config.`
@@ -57378,42 +57785,100 @@ async function setupMcp(targetArg) {
57378
57785
  installed.push(`haansi-solutions (stdio \u2192 ${haansiBin} mcp-server)`);
57379
57786
  }
57380
57787
  if (target === "all" || target === "exchange") {
57381
- const token = resolveToken3();
57788
+ const token = resolveToken2();
57382
57789
  if (!token) {
57383
- const msg = "haansi-exchange not registered: no token found. Run `haansi auth login` first, or set HAANSI_TOKEN.";
57384
- if (target === "exchange") {
57385
- console.error(msg);
57386
- process.exit(1);
57387
- }
57388
- warnings.push(msg);
57790
+ warnings.push(
57791
+ "haansi-exchange not registered: no token found. Run `haansi auth login` first, or set HAANSI_TOKEN."
57792
+ );
57389
57793
  } else {
57390
- const apiUrl = process.env.HAANSI_API_URL ?? DEFAULT_API_URL5;
57391
- const exchangeUrl = `${apiUrl.replace(/\/+$/, "")}/api/v1/access/exchange/mcp`;
57794
+ const url2 = exchangeUrl();
57392
57795
  config6.mcpServers["haansi-exchange"] = {
57393
57796
  type: "http",
57394
- url: exchangeUrl,
57797
+ url: url2,
57395
57798
  headers: { Authorization: `Bearer ${token}` }
57396
57799
  };
57397
- installed.push(`haansi-exchange (http \u2192 ${exchangeUrl})`);
57800
+ installed.push(`haansi-exchange (http \u2192 ${url2})`);
57801
+ }
57802
+ }
57803
+ (0, import_node_fs5.writeFileSync)(CLAUDE_JSON, JSON.stringify(config6, null, 2) + "\n", "utf-8");
57804
+ console.log(`
57805
+ Claude Code \u2014 configured in ${CLAUDE_JSON}`);
57806
+ for (const entry of installed) console.log(` \u2022 ${entry}`);
57807
+ for (const w of warnings) console.log(` ! ${w}`);
57808
+ return { installed, warnings };
57809
+ }
57810
+ function setupCodexMcp(target) {
57811
+ let content = "";
57812
+ if ((0, import_node_fs5.existsSync)(CODEX_CONFIG)) {
57813
+ try {
57814
+ content = (0, import_node_fs5.readFileSync)(CODEX_CONFIG, "utf-8");
57815
+ } catch {
57816
+ console.error(
57817
+ `Warning: Could not read ${CODEX_CONFIG} \u2014 will create a new file.`
57818
+ );
57819
+ }
57820
+ }
57821
+ const installed = [];
57822
+ const warnings = [];
57823
+ if (target === "all" || target === "solutions") {
57824
+ const haansiBin = resolveHaansiBin();
57825
+ content = upsertTomlTable(content, "mcp_servers.haansi-solutions", {
57826
+ command: tomlString(haansiBin),
57827
+ args: tomlStringArray(["mcp-server"])
57828
+ });
57829
+ installed.push(`haansi-solutions (stdio \u2192 ${haansiBin} mcp-server)`);
57830
+ }
57831
+ if (target === "all" || target === "exchange") {
57832
+ const token = resolveToken2();
57833
+ if (!token) {
57834
+ warnings.push(
57835
+ "haansi-exchange not registered: no token found. Run `haansi auth login` first, or set HAANSI_TOKEN."
57836
+ );
57837
+ } else {
57838
+ const url2 = exchangeUrl();
57839
+ content = upsertTomlTable(content, "mcp_servers.haansi-exchange", {
57840
+ url: tomlString(url2),
57841
+ http_headers: tomlInlineTable({ Authorization: `Bearer ${token}` })
57842
+ });
57843
+ installed.push(`haansi-exchange (http \u2192 ${url2})`);
57398
57844
  }
57399
57845
  }
57400
- (0, import_node_fs3.writeFileSync)(CLAUDE_JSON, JSON.stringify(config6, null, 2) + "\n", "utf-8");
57401
- console.log(`MCP servers configured in ${CLAUDE_JSON}`);
57846
+ (0, import_node_fs5.mkdirSync)(CODEX_HOME, { recursive: true });
57847
+ (0, import_node_fs5.writeFileSync)(CODEX_CONFIG, content, "utf-8");
57848
+ console.log(`
57849
+ OpenAI Codex \u2014 configured in ${CODEX_CONFIG}`);
57402
57850
  for (const entry of installed) console.log(` \u2022 ${entry}`);
57403
57851
  for (const w of warnings) console.log(` ! ${w}`);
57404
- console.log("\nRestart Claude Code to activate the changes.");
57852
+ return { installed, warnings };
57853
+ }
57854
+ async function setupMcp(targetArg, agentFlag) {
57855
+ const target = parseTarget(targetArg);
57856
+ const selection = resolveAgentSelection(agentFlag);
57857
+ for (const agentId of selection.agents) {
57858
+ WRITERS[agentId](target);
57859
+ }
57860
+ saveAgents(selection.agents);
57861
+ const restart = selection.agents.map((a) => a === "codex" ? "Codex" : "Claude Code").join(" / ");
57862
+ console.log(`
57863
+ Restart ${restart} to activate the changes.`);
57405
57864
  }
57406
- var import_node_fs3, import_node_path3, import_node_os3, import_node_child_process2, CLAUDE_JSON, CREDENTIALS_FILE5, DEFAULT_API_URL5;
57865
+ var import_node_fs5, import_node_path4, import_node_os4, CLAUDE_JSON, CODEX_HOME, CODEX_CONFIG, WRITERS;
57407
57866
  var init_setup_mcp = __esm({
57408
57867
  "src/commands/setup-mcp.ts"() {
57409
57868
  "use strict";
57410
- import_node_fs3 = require("node:fs");
57411
- import_node_path3 = require("node:path");
57412
- import_node_os3 = require("node:os");
57413
- import_node_child_process2 = require("node:child_process");
57414
- CLAUDE_JSON = (0, import_node_path3.join)((0, import_node_os3.homedir)(), ".claude.json");
57415
- CREDENTIALS_FILE5 = (0, import_node_path3.join)((0, import_node_os3.homedir)(), ".haansi", "credentials.json");
57416
- DEFAULT_API_URL5 = "https://api.haansi.co";
57869
+ import_node_fs5 = require("node:fs");
57870
+ import_node_path4 = require("node:path");
57871
+ import_node_os4 = require("node:os");
57872
+ init_paths();
57873
+ init_agent_selection();
57874
+ init_codex_config();
57875
+ CLAUDE_JSON = (0, import_node_path4.join)((0, import_node_os4.homedir)(), ".claude.json");
57876
+ CODEX_HOME = process.env.CODEX_HOME || (0, import_node_path4.join)((0, import_node_os4.homedir)(), ".codex");
57877
+ CODEX_CONFIG = (0, import_node_path4.join)(CODEX_HOME, "config.toml");
57878
+ WRITERS = {
57879
+ "claude-code": setupClaudeMcp,
57880
+ codex: setupCodexMcp
57881
+ };
57417
57882
  }
57418
57883
  });
57419
57884
 
@@ -57463,7 +57928,7 @@ function buildPlist(haansiPath) {
57463
57928
  }
57464
57929
  async function setupDaemon(uninstall) {
57465
57930
  if (uninstall) {
57466
- if (!(0, import_node_fs4.existsSync)(PLIST_PATH)) {
57931
+ if (!(0, import_node_fs6.existsSync)(PLIST_PATH)) {
57467
57932
  console.log("Daemon is not installed.");
57468
57933
  return;
57469
57934
  }
@@ -57472,13 +57937,13 @@ async function setupDaemon(uninstall) {
57472
57937
  } catch (err) {
57473
57938
  console.error("Failed to unload daemon:", err);
57474
57939
  }
57475
- (0, import_node_fs4.unlinkSync)(PLIST_PATH);
57940
+ (0, import_node_fs6.unlinkSync)(PLIST_PATH);
57476
57941
  console.log("Daemon stopped and removed.");
57477
57942
  return;
57478
57943
  }
57479
57944
  const haansiPath = findHaansiBin();
57480
- (0, import_node_fs4.mkdirSync)((0, import_node_path4.join)((0, import_node_os4.homedir)(), ".haansi"), { recursive: true });
57481
- (0, import_node_fs4.writeFileSync)(PLIST_PATH, buildPlist(haansiPath), "utf-8");
57945
+ (0, import_node_fs6.mkdirSync)((0, import_node_path5.join)((0, import_node_os5.homedir)(), ".haansi"), { recursive: true });
57946
+ (0, import_node_fs6.writeFileSync)(PLIST_PATH, buildPlist(haansiPath), "utf-8");
57482
57947
  try {
57483
57948
  (0, import_node_child_process3.spawnSync)("launchctl", ["unload", PLIST_PATH], { stdio: "pipe" });
57484
57949
  } catch (err) {
@@ -57492,22 +57957,22 @@ async function setupDaemon(uninstall) {
57492
57957
  The daemon will start automatically on every login.`);
57493
57958
  console.log(`To uninstall: haansi setup-daemon --uninstall`);
57494
57959
  }
57495
- var import_node_child_process3, import_node_fs4, import_node_path4, import_node_os4, PLIST_LABEL, PLIST_PATH, LOG_FILE;
57960
+ var import_node_child_process3, import_node_fs6, import_node_path5, import_node_os5, PLIST_LABEL, PLIST_PATH, LOG_FILE;
57496
57961
  var init_setup_daemon = __esm({
57497
57962
  "src/commands/setup-daemon.ts"() {
57498
57963
  "use strict";
57499
57964
  import_node_child_process3 = require("node:child_process");
57500
- import_node_fs4 = require("node:fs");
57501
- import_node_path4 = require("node:path");
57502
- import_node_os4 = require("node:os");
57965
+ import_node_fs6 = require("node:fs");
57966
+ import_node_path5 = require("node:path");
57967
+ import_node_os5 = require("node:os");
57503
57968
  PLIST_LABEL = "co.haansi.daemon";
57504
- PLIST_PATH = (0, import_node_path4.join)(
57505
- (0, import_node_os4.homedir)(),
57969
+ PLIST_PATH = (0, import_node_path5.join)(
57970
+ (0, import_node_os5.homedir)(),
57506
57971
  "Library",
57507
57972
  "LaunchAgents",
57508
57973
  `${PLIST_LABEL}.plist`
57509
57974
  );
57510
- LOG_FILE = (0, import_node_path4.join)((0, import_node_os4.homedir)(), ".haansi", "daemon.log");
57975
+ LOG_FILE = (0, import_node_path5.join)((0, import_node_os5.homedir)(), ".haansi", "daemon.log");
57511
57976
  }
57512
57977
  });
57513
57978
 
@@ -57518,9 +57983,9 @@ __export(config_exports, {
57518
57983
  });
57519
57984
  function resolveToken4() {
57520
57985
  if (process.env.HAANSI_TOKEN) return process.env.HAANSI_TOKEN;
57521
- if ((0, import_node_fs5.existsSync)(CREDENTIALS_FILE6)) {
57986
+ if ((0, import_node_fs7.existsSync)(CREDENTIALS_FILE6)) {
57522
57987
  try {
57523
- const creds = JSON.parse((0, import_node_fs5.readFileSync)(CREDENTIALS_FILE6, "utf-8"));
57988
+ const creds = JSON.parse((0, import_node_fs7.readFileSync)(CREDENTIALS_FILE6, "utf-8"));
57524
57989
  if (creds.token) return creds.token;
57525
57990
  } catch (err) {
57526
57991
  console.error("Failed to read credentials file:", err);
@@ -57537,12 +58002,12 @@ async function config5(args) {
57537
58002
  process.exit(1);
57538
58003
  return;
57539
58004
  }
57540
- const apiUrl = process.env.HAANSI_API_URL ?? DEFAULT_API_URL6;
58005
+ const apiUrl2 = process.env.HAANSI_API_URL ?? DEFAULT_API_URL6;
57541
58006
  const subcommand = args[0];
57542
58007
  if (subcommand === "get") {
57543
58008
  const key = args[1];
57544
58009
  const response = await fetch(
57545
- `${apiUrl}/api/v1/workspace/users/me/preferences`,
58010
+ `${apiUrl2}/api/v1/workspace/users/me/preferences`,
57546
58011
  {
57547
58012
  headers: { Authorization: `Bearer ${token}` }
57548
58013
  }
@@ -57589,7 +58054,7 @@ async function config5(args) {
57589
58054
  return;
57590
58055
  }
57591
58056
  const response = await fetch(
57592
- `${apiUrl}/api/v1/workspace/users/me/preferences`,
58057
+ `${apiUrl2}/api/v1/workspace/users/me/preferences`,
57593
58058
  {
57594
58059
  method: "PATCH",
57595
58060
  headers: {
@@ -57618,15 +58083,15 @@ Supported keys:
57618
58083
  if (subcommand !== void 0) process.exit(1);
57619
58084
  }
57620
58085
  }
57621
- var import_node_fs5, import_node_path5, import_node_os5, DEFAULT_API_URL6, CREDENTIALS_FILE6, VALID_SESSION_MODES;
58086
+ var import_node_fs7, import_node_path6, import_node_os6, DEFAULT_API_URL6, CREDENTIALS_FILE6, VALID_SESSION_MODES;
57622
58087
  var init_config = __esm({
57623
58088
  "src/commands/config.ts"() {
57624
58089
  "use strict";
57625
- import_node_fs5 = require("node:fs");
57626
- import_node_path5 = require("node:path");
57627
- import_node_os5 = require("node:os");
58090
+ import_node_fs7 = require("node:fs");
58091
+ import_node_path6 = require("node:path");
58092
+ import_node_os6 = require("node:os");
57628
58093
  DEFAULT_API_URL6 = "https://api.haansi.co";
57629
- CREDENTIALS_FILE6 = (0, import_node_path5.join)((0, import_node_os5.homedir)(), ".haansi", "credentials.json");
58094
+ CREDENTIALS_FILE6 = (0, import_node_path6.join)((0, import_node_os6.homedir)(), ".haansi", "credentials.json");
57630
58095
  VALID_SESSION_MODES = ["private", "org"];
57631
58096
  }
57632
58097
  });
@@ -57640,9 +58105,9 @@ __export(list_exports, {
57640
58105
  });
57641
58106
  function resolveToken5() {
57642
58107
  if (process.env.HAANSI_TOKEN) return process.env.HAANSI_TOKEN;
57643
- if ((0, import_node_fs6.existsSync)(CREDENTIALS_FILE7)) {
58108
+ if ((0, import_node_fs8.existsSync)(CREDENTIALS_FILE7)) {
57644
58109
  try {
57645
- const creds = JSON.parse((0, import_node_fs6.readFileSync)(CREDENTIALS_FILE7, "utf-8"));
58110
+ const creds = JSON.parse((0, import_node_fs8.readFileSync)(CREDENTIALS_FILE7, "utf-8"));
57646
58111
  if (creds.token) return creds.token;
57647
58112
  } catch (err) {
57648
58113
  console.error("Failed to read credentials file:", err);
@@ -57650,8 +58115,8 @@ function resolveToken5() {
57650
58115
  }
57651
58116
  throw new Error("No token found. Run `haansi init` first.");
57652
58117
  }
57653
- async function apiGet3(path, token, apiUrl) {
57654
- const response = await fetch(`${apiUrl}/api/v1${path}`, {
58118
+ async function apiGet3(path, token, apiUrl2) {
58119
+ const response = await fetch(`${apiUrl2}/api/v1${path}`, {
57655
58120
  headers: { Authorization: `Bearer ${token}` }
57656
58121
  });
57657
58122
  if (!response.ok) {
@@ -57660,8 +58125,8 @@ async function apiGet3(path, token, apiUrl) {
57660
58125
  }
57661
58126
  return response.json();
57662
58127
  }
57663
- async function apiPost2(path, body, token, apiUrl) {
57664
- const response = await fetch(`${apiUrl}/api/v1${path}`, {
58128
+ async function apiPost2(path, body, token, apiUrl2) {
58129
+ const response = await fetch(`${apiUrl2}/api/v1${path}`, {
57665
58130
  method: "POST",
57666
58131
  headers: {
57667
58132
  Authorization: `Bearer ${token}`,
@@ -57676,20 +58141,20 @@ async function apiPost2(path, body, token, apiUrl) {
57676
58141
  return response.json();
57677
58142
  }
57678
58143
  async function list(options) {
57679
- const apiUrl = process.env.HAANSI_API_URL ?? DEFAULT_API_URL7;
58144
+ const apiUrl2 = process.env.HAANSI_API_URL ?? DEFAULT_API_URL7;
57680
58145
  const token = resolveToken5();
57681
58146
  let data;
57682
58147
  if (options.search) {
57683
58148
  data = await apiGet3(
57684
58149
  `/memory/solutions/search?q=${encodeURIComponent(options.search)}&top_k=${options.limit}`,
57685
58150
  token,
57686
- apiUrl
58151
+ apiUrl2
57687
58152
  );
57688
58153
  } else {
57689
58154
  data = await apiGet3(
57690
58155
  `/memory/solutions/recent?limit=${options.limit}`,
57691
58156
  token,
57692
- apiUrl
58157
+ apiUrl2
57693
58158
  );
57694
58159
  }
57695
58160
  if (!data.results || data.results.length === 0) {
@@ -57702,13 +58167,13 @@ async function list(options) {
57702
58167
  ${data.results.length} result(s)`);
57703
58168
  }
57704
58169
  async function searchKnowledge(options) {
57705
- const apiUrl = process.env.HAANSI_API_URL ?? DEFAULT_API_URL7;
58170
+ const apiUrl2 = process.env.HAANSI_API_URL ?? DEFAULT_API_URL7;
57706
58171
  const token = resolveToken5();
57707
58172
  let url2 = `/memory/solutions/knowledge/search?q=${encodeURIComponent(options.query)}&limit=${options.limit}`;
57708
58173
  if (options.artifactType) {
57709
58174
  url2 += `&artifact_type=${encodeURIComponent(options.artifactType)}`;
57710
58175
  }
57711
- const data = await apiGet3(url2, token, apiUrl);
58176
+ const data = await apiGet3(url2, token, apiUrl2);
57712
58177
  if (!data.results || data.results.length === 0) {
57713
58178
  console.log("No knowledge artifacts found matching your query.");
57714
58179
  return;
@@ -57719,7 +58184,7 @@ async function searchKnowledge(options) {
57719
58184
  ${data.results.length} result(s)`);
57720
58185
  }
57721
58186
  async function saveKnowledge(options) {
57722
- const apiUrl = process.env.HAANSI_API_URL ?? DEFAULT_API_URL7;
58187
+ const apiUrl2 = process.env.HAANSI_API_URL ?? DEFAULT_API_URL7;
57723
58188
  const token = resolveToken5();
57724
58189
  const body = {
57725
58190
  problem_description: options.problem,
@@ -57733,28 +58198,32 @@ async function saveKnowledge(options) {
57733
58198
  "/memory/solutions/mined",
57734
58199
  body,
57735
58200
  token,
57736
- apiUrl
58201
+ apiUrl2
57737
58202
  );
57738
58203
  console.log("Knowledge saved successfully.");
57739
58204
  console.log(` Problem: ${data.problem_description}`);
57740
58205
  console.log(` Solution: ${data.solution_summary}`);
57741
58206
  console.log("\nThis is now searchable via `haansi search-solutions`.");
57742
58207
  }
57743
- var import_node_fs6, import_node_path6, import_node_os6, DEFAULT_API_URL7, CREDENTIALS_FILE7;
58208
+ var import_node_fs8, import_node_path7, import_node_os7, DEFAULT_API_URL7, CREDENTIALS_FILE7;
57744
58209
  var init_list = __esm({
57745
58210
  "src/commands/list.ts"() {
57746
58211
  "use strict";
57747
- import_node_fs6 = require("node:fs");
57748
- import_node_path6 = require("node:path");
57749
- import_node_os6 = require("node:os");
58212
+ import_node_fs8 = require("node:fs");
58213
+ import_node_path7 = require("node:path");
58214
+ import_node_os7 = require("node:os");
57750
58215
  DEFAULT_API_URL7 = "https://api.haansi.co";
57751
- CREDENTIALS_FILE7 = (0, import_node_path6.join)((0, import_node_os6.homedir)(), ".haansi", "credentials.json");
58216
+ CREDENTIALS_FILE7 = (0, import_node_path7.join)((0, import_node_os7.homedir)(), ".haansi", "credentials.json");
57752
58217
  }
57753
58218
  });
57754
58219
 
57755
58220
  // src/index.ts
57756
58221
  var { version: VERSION } = require_package();
57757
58222
  var command = process.argv[2];
58223
+ function getFlag(flag) {
58224
+ const idx = process.argv.indexOf(flag);
58225
+ return idx !== -1 ? process.argv[idx + 1] : void 0;
58226
+ }
57758
58227
  async function run2() {
57759
58228
  if (command === "--version" || command === "-v") {
57760
58229
  console.log(VERSION);
@@ -57782,24 +58251,47 @@ Usage: haansi auth login`
57782
58251
  }
57783
58252
  case "collect": {
57784
58253
  const { collect: collect2 } = await Promise.resolve().then(() => (init_collector(), collector_exports));
58254
+ const { resolveAgentSelection: resolveAgentSelection2 } = await Promise.resolve().then(() => (init_agent_selection(), agent_selection_exports));
57785
58255
  const forceAll = process.argv.includes("--force-all");
57786
58256
  const dryRun = process.argv.includes("--dry-run");
57787
58257
  const limitIdx = process.argv.indexOf("--limit");
57788
58258
  const limit = limitIdx !== -1 ? parseInt(process.argv[limitIdx + 1], 10) : void 0;
57789
- const projectPath = process.env.CLAUDE_PROJECT_PATH || void 0;
58259
+ const agents = resolveAgentSelection2(getFlag("--agent")).agents;
57790
58260
  const { uploaded, skipped, errors } = await collect2({
57791
58261
  forceAll,
57792
58262
  dryRun,
57793
58263
  limit,
57794
- projectPath
58264
+ agents
57795
58265
  });
57796
58266
  console.log(
57797
58267
  `
57798
- Collector done: ${uploaded} uploaded, ${skipped} unchanged, ${errors} errors`
58268
+ Collector done (${agents.join(", ")}): ${uploaded} uploaded, ${skipped} unchanged, ${errors} errors`
57799
58269
  );
57800
58270
  if (errors > 0) process.exit(1);
57801
58271
  break;
57802
58272
  }
58273
+ case "agents": {
58274
+ const { detectInstalled: detectInstalled2, getAgents: getAgents2 } = await Promise.resolve().then(() => (init_agents(), agents_exports));
58275
+ const { resolveAgentSelection: resolveAgentSelection2 } = await Promise.resolve().then(() => (init_agent_selection(), agent_selection_exports));
58276
+ const installed = new Set(detectInstalled2().map((a) => a.id));
58277
+ const selection = resolveAgentSelection2();
58278
+ console.log("Supported coding agents:\n");
58279
+ for (const a of getAgents2()) {
58280
+ const flags = [
58281
+ installed.has(a.id) ? "installed" : "not detected",
58282
+ selection.agents.includes(a.id) ? "selected" : null
58283
+ ].filter(Boolean);
58284
+ console.log(` \u2022 ${a.displayName} [${a.id}] \u2014 ${flags.join(", ")}`);
58285
+ }
58286
+ console.log(
58287
+ `
58288
+ Active selection (${selection.source}): ${selection.agents.join(", ")}`
58289
+ );
58290
+ console.log(
58291
+ "Change with `haansi setup-mcp --agent <claude|codex|both>` or `--agent` on collect."
58292
+ );
58293
+ break;
58294
+ }
57803
58295
  case "daemon": {
57804
58296
  await Promise.resolve().then(() => (init_collector_daemon(), collector_daemon_exports));
57805
58297
  break;
@@ -57810,8 +58302,8 @@ Collector done: ${uploaded} uploaded, ${skipped} unchanged, ${errors} errors`
57810
58302
  }
57811
58303
  case "setup-mcp": {
57812
58304
  const { setupMcp: setupMcp2 } = await Promise.resolve().then(() => (init_setup_mcp(), setup_mcp_exports));
57813
- const targetArg = process.argv[3];
57814
- await setupMcp2(targetArg);
58305
+ const targetArg = process.argv[3] && !process.argv[3].startsWith("--") ? process.argv[3] : void 0;
58306
+ await setupMcp2(targetArg, getFlag("--agent"));
57815
58307
  break;
57816
58308
  }
57817
58309
  case "setup-daemon": {
@@ -57896,7 +58388,7 @@ Collector done: ${uploaded} uploaded, ${skipped} unchanged, ${errors} errors`
57896
58388
  }
57897
58389
  function printUsage() {
57898
58390
  console.log(`
57899
- Haansi CLI v${VERSION} \u2014 Session collector and MCP server for Claude Code
58391
+ Haansi CLI v${VERSION} \u2014 Session collector and MCP server for Claude Code & OpenAI Codex
57900
58392
 
57901
58393
  Usage:
57902
58394
  haansi <command> [options]
@@ -57904,7 +58396,8 @@ Usage:
57904
58396
  Commands:
57905
58397
  auth login Log in via browser (recommended)
57906
58398
  init Authenticate with a token (for CI / headless)
57907
- collect Upload new/changed Claude sessions once
58399
+ collect [--agent <sel>] Upload new/changed sessions once
58400
+ agents List supported / installed / selected agents
57908
58401
  config get [key] Show your preferences (e.g. session_mode)
57909
58402
  config set <key> <val> Update a preference
57910
58403
 
@@ -57914,8 +58407,11 @@ Commands:
57914
58407
  save-knowledge Save a problem/solution for future retrieval
57915
58408
 
57916
58409
  daemon Run the collector on a schedule (every 30 min)
57917
- mcp-server Start the Haansi MCP server (used by Claude Code)
57918
- setup-mcp [target] Register MCP servers in ~/.claude.json
58410
+ mcp-server Start the Haansi MCP server (used by the agent)
58411
+ setup-mcp [target] [--agent <sel>]
58412
+ Register MCP servers with a coding agent.
58413
+ Claude Code \u2192 ~/.claude.json; Codex \u2192 ~/.codex/config.toml
58414
+ agent: claude | codex | both (default: installed)
57919
58415
  target: solutions | exchange | all (default: all)
57920
58416
  - solutions: stdio, per-org search/save tools
57921
58417
  - exchange: http, cross-org marketplace (read-only)
@@ -57924,6 +58420,7 @@ Commands:
57924
58420
 
57925
58421
  Options:
57926
58422
  --version, -v Show installed version
58423
+ --agent <sel> claude | codex | both (collect, setup-mcp)
57927
58424
  --limit <n> Number of results (default: 5 for search, 10 for list)
57928
58425
 
57929
58426
  Options for search-knowledge:
@@ -57937,6 +58434,7 @@ Options for save-knowledge:
57937
58434
  --tags tag1,tag2 Comma-separated tags
57938
58435
 
57939
58436
  Options for collect:
58437
+ --agent <sel> claude | codex | both (default: installed agents)
57940
58438
  --force-all Re-upload all sessions, not just new ones
57941
58439
  --dry-run Scan without uploading
57942
58440
  --limit <n> Upload at most n sessions
@@ -57944,8 +58442,9 @@ Options for collect:
57944
58442
  Environment variables:
57945
58443
  HAANSI_TOKEN API token (or use \`haansi init\` to save it)
57946
58444
  HAANSI_API_URL API base URL (default: https://api.haansi.co)
57947
- CLAUDE_PROJECT_PATH Collect from a specific project path only
57948
- CLAUDE_COLLECT_SCHEDULE Cron expression for daemon (default: */30 * * * *)
58445
+ CODEX_HOME Codex home dir (default: ~/.codex)
58446
+ HAANSI_AGENTS Daemon: comma-separated agents (claude-code,codex)
58447
+ HAANSI_COLLECT_SCHEDULE Cron expression for daemon (default: */30 * * * *)
57949
58448
  `);
57950
58449
  }
57951
58450
  run2().catch((err) => {