haansi 0.1.12 → 0.1.14
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/README.md +83 -0
- package/dist/haansi.js +30 -52
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Haansi CLI
|
|
2
|
+
|
|
3
|
+
Session collector and MCP server for Claude Code. Uploads Claude Code session data to the Haansi platform for mining, search, and knowledge retrieval.
|
|
4
|
+
|
|
5
|
+
Published to npm as `haansi`.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g haansi
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Requires Node.js >= 18.0.0.
|
|
14
|
+
|
|
15
|
+
## Authentication
|
|
16
|
+
|
|
17
|
+
The CLI uses `hsk_` tokens for authentication. Generate a token at https://app.haansi.co/settings/tokens.
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
haansi init
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
This prompts for your token, validates it against the API, and saves it to `~/.haansi/credentials.json` (mode 0600). Alternatively, set the `HAANSI_TOKEN` environment variable.
|
|
24
|
+
|
|
25
|
+
## Commands
|
|
26
|
+
|
|
27
|
+
| Command | Description |
|
|
28
|
+
| --------------------------------- | -------------------------------------------------------- |
|
|
29
|
+
| `haansi init` | Authenticate and save your API token |
|
|
30
|
+
| `haansi collect` | Upload new/changed Claude sessions (once) |
|
|
31
|
+
| `haansi daemon` | Run the collector on a schedule (every 30 min) |
|
|
32
|
+
| `haansi setup-daemon` | Install macOS launchd service for auto-start on login |
|
|
33
|
+
| `haansi setup-mcp` | Add haansi-solutions MCP entry to `~/.claude.json` |
|
|
34
|
+
| `haansi mcp-server` | Start the Haansi MCP server (stdio, used by Claude Code) |
|
|
35
|
+
| `haansi config get [key]` | Show preferences (e.g. `session_mode`) |
|
|
36
|
+
| `haansi config set <key> <val>` | Update a preference |
|
|
37
|
+
| `haansi search-solutions <query>` | Semantic search over mined solutions |
|
|
38
|
+
| `haansi list-solutions` | List recent mined solutions |
|
|
39
|
+
| `haansi search-knowledge <query>` | Search knowledge artifacts |
|
|
40
|
+
| `haansi save-knowledge` | Save a problem/solution for future retrieval |
|
|
41
|
+
|
|
42
|
+
### Collect options
|
|
43
|
+
|
|
44
|
+
- `--force-all` -- Re-upload all sessions, not just new ones
|
|
45
|
+
- `--dry-run` -- Scan without uploading
|
|
46
|
+
- `--limit <n>` -- Upload at most n sessions
|
|
47
|
+
|
|
48
|
+
## Configuration
|
|
49
|
+
|
|
50
|
+
| Environment Variable | Description | Default |
|
|
51
|
+
| ------------------------- | ----------------------------------------- | ----------------------- |
|
|
52
|
+
| `HAANSI_TOKEN` | API token (alternative to `haansi init`) | -- |
|
|
53
|
+
| `HAANSI_API_URL` | API base URL | `https://api.haansi.co` |
|
|
54
|
+
| `CLAUDE_PROJECT_PATH` | Collect from a specific project path only | -- |
|
|
55
|
+
| `CLAUDE_COLLECT_SCHEDULE` | Cron expression for daemon | `*/30 * * * *` |
|
|
56
|
+
|
|
57
|
+
### User preferences
|
|
58
|
+
|
|
59
|
+
Set via `haansi config set <key> <value>`:
|
|
60
|
+
|
|
61
|
+
- `session_mode` -- `private` or `org` (default: `org`)
|
|
62
|
+
|
|
63
|
+
## Daemon (macOS)
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
haansi setup-daemon # install and start
|
|
67
|
+
haansi setup-daemon --uninstall # stop and remove
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Installs a launchd plist at `~/Library/LaunchAgents/co.haansi.daemon.plist`. Logs are written to `~/.haansi/daemon.log`.
|
|
71
|
+
|
|
72
|
+
## MCP Server
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
haansi setup-mcp
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Registers `haansi-solutions` in `~/.claude.json` so Claude Code auto-starts the MCP server, providing search and knowledge tools directly in the AI assistant.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
**Package:** `haansi`
|
|
83
|
+
**Location:** `apps/cli/`
|
package/dist/haansi.js
CHANGED
|
@@ -39,7 +39,7 @@ var require_package = __commonJS({
|
|
|
39
39
|
"package.json"(exports2, module2) {
|
|
40
40
|
module2.exports = {
|
|
41
41
|
name: "haansi",
|
|
42
|
-
version: "0.1.
|
|
42
|
+
version: "0.1.14",
|
|
43
43
|
description: "Haansi CLI - Session collector and MCP server for Claude Code",
|
|
44
44
|
bin: {
|
|
45
45
|
haansi: "./dist/haansi.js"
|
|
@@ -567,7 +567,6 @@ function extractGitInfo(projectDir, sessionTimestamp, sessionBranch) {
|
|
|
567
567
|
try {
|
|
568
568
|
(0, import_child_process.execSync)("git rev-parse --is-inside-work-tree", { ...opts, stdio: "pipe" });
|
|
569
569
|
} catch {
|
|
570
|
-
console.warn(`[collector] Not a git repo: ${projectDir}`);
|
|
571
570
|
return empty;
|
|
572
571
|
}
|
|
573
572
|
const result = { ...empty };
|
|
@@ -577,7 +576,6 @@ function extractGitInfo(projectDir, sessionTimestamp, sessionBranch) {
|
|
|
577
576
|
stdio: "pipe"
|
|
578
577
|
}).trim() || null;
|
|
579
578
|
} catch {
|
|
580
|
-
console.warn(`[collector] No git remote for: ${projectDir}`);
|
|
581
579
|
}
|
|
582
580
|
if (sessionTimestamp) {
|
|
583
581
|
const branch = sessionBranch ?? "HEAD";
|
|
@@ -593,7 +591,6 @@ function extractGitInfo(projectDir, sessionTimestamp, sessionBranch) {
|
|
|
593
591
|
try {
|
|
594
592
|
result.gitCommit = (0, import_child_process.execSync)("git rev-parse HEAD", { ...opts, stdio: "pipe" }).trim() || null;
|
|
595
593
|
} catch {
|
|
596
|
-
console.warn(`[collector] No git commits in: ${projectDir}`);
|
|
597
594
|
}
|
|
598
595
|
}
|
|
599
596
|
try {
|
|
@@ -699,7 +696,10 @@ async function getUploadedSessions(apiUrl, token) {
|
|
|
699
696
|
const data = await apiGet("/sessions/uploaded", apiUrl, token);
|
|
700
697
|
const map2 = /* @__PURE__ */ new Map();
|
|
701
698
|
for (const s of data.sessions) {
|
|
702
|
-
map2.set(s.sessionId,
|
|
699
|
+
map2.set(s.sessionId, {
|
|
700
|
+
lineCount: s.lineCount,
|
|
701
|
+
hasGitMeta: s.hasGitMeta ?? false
|
|
702
|
+
});
|
|
703
703
|
}
|
|
704
704
|
return map2;
|
|
705
705
|
} catch {
|
|
@@ -749,8 +749,8 @@ async function collect(options) {
|
|
|
749
749
|
const remoteMap = options.forceAll ? /* @__PURE__ */ new Map() : await getUploadedSessions(apiUrl, token);
|
|
750
750
|
const toUpload = [];
|
|
751
751
|
for (const session of localSessions) {
|
|
752
|
-
const
|
|
753
|
-
if (
|
|
752
|
+
const remote = remoteMap.get(session.sessionId);
|
|
753
|
+
if (remote && remote.lineCount >= session.lineCount && remote.hasGitMeta) {
|
|
754
754
|
skipped++;
|
|
755
755
|
} else {
|
|
756
756
|
toUpload.push(session);
|
|
@@ -811,10 +811,6 @@ async function main() {
|
|
|
811
811
|
dryRun,
|
|
812
812
|
limit
|
|
813
813
|
});
|
|
814
|
-
console.log(
|
|
815
|
-
`
|
|
816
|
-
Collector done: ${uploaded} uploaded, ${skipped} unchanged, ${errors} errors`
|
|
817
|
-
);
|
|
818
814
|
if (errors > 0) process.exit(1);
|
|
819
815
|
}
|
|
820
816
|
var import_dotenv, import_path, import_fs, import_os, import_zlib, import_child_process, logger, DEFAULT_API_URL2, CREDENTIALS_FILE2, CLAUDE_PROJECTS_DIR, MIN_LINES, EDGE_SCRUB_PATTERNS;
|
|
@@ -56675,13 +56671,9 @@ function resolveToken2() {
|
|
|
56675
56671
|
function triggerCollectInBackground() {
|
|
56676
56672
|
if (isCollecting) return;
|
|
56677
56673
|
isCollecting = true;
|
|
56678
|
-
collect({}).then((
|
|
56679
|
-
console.error(
|
|
56680
|
-
`[mcp-server] Collection done: ${uploaded} uploaded, ${skipped} unchanged, ${errors} errors`
|
|
56681
|
-
);
|
|
56674
|
+
collect({}).then(() => {
|
|
56682
56675
|
lastCollectTime = Date.now();
|
|
56683
|
-
}).catch((
|
|
56684
|
-
console.error("[mcp-server] Collection error:", err.message);
|
|
56676
|
+
}).catch(() => {
|
|
56685
56677
|
}).finally(() => {
|
|
56686
56678
|
isCollecting = false;
|
|
56687
56679
|
});
|
|
@@ -56723,23 +56715,10 @@ async function apiPost(path, body, options) {
|
|
|
56723
56715
|
async function main3() {
|
|
56724
56716
|
try {
|
|
56725
56717
|
await apiGet2("/sessions/recent?limit=1");
|
|
56726
|
-
|
|
56727
|
-
} catch (err) {
|
|
56728
|
-
console.error(
|
|
56729
|
-
"[mcp-server] WARNING: API connection check failed:",
|
|
56730
|
-
err.message
|
|
56731
|
-
);
|
|
56718
|
+
} catch {
|
|
56732
56719
|
}
|
|
56733
56720
|
try {
|
|
56734
|
-
|
|
56735
|
-
const mode = data?.preferences?.session_mode;
|
|
56736
|
-
if (!mode) {
|
|
56737
|
-
console.error(
|
|
56738
|
-
'[mcp-server] Session mode not configured. Defaulting to "org" (team-wide). To change: haansi config set session_mode private'
|
|
56739
|
-
);
|
|
56740
|
-
} else {
|
|
56741
|
-
console.error(`[mcp-server] Session mode: ${mode}`);
|
|
56742
|
-
}
|
|
56721
|
+
await apiGet2("/users/me/preferences");
|
|
56743
56722
|
} catch {
|
|
56744
56723
|
}
|
|
56745
56724
|
triggerCollectInBackground();
|
|
@@ -56760,15 +56739,10 @@ async function main3() {
|
|
|
56760
56739
|
app.get("/health", (_req, res) => {
|
|
56761
56740
|
res.json({ status: "ok", service: "haansi-solutions-mcp" });
|
|
56762
56741
|
});
|
|
56763
|
-
app.listen(PORT
|
|
56764
|
-
console.error(
|
|
56765
|
-
`[mcp-server] Haansi Solutions MCP server running on HTTP port ${PORT}`
|
|
56766
|
-
);
|
|
56767
|
-
});
|
|
56742
|
+
app.listen(PORT);
|
|
56768
56743
|
} else {
|
|
56769
56744
|
const transport = new StdioServerTransport();
|
|
56770
56745
|
await mcpServer.connect(transport);
|
|
56771
|
-
console.error("[mcp-server] haansi-solutions MCP server running on stdio");
|
|
56772
56746
|
}
|
|
56773
56747
|
}
|
|
56774
56748
|
var import_dotenv3, import_path3, import_fs2, import_os2, DEFAULT_API_URL3, CREDENTIALS_FILE3, API_URL, TOKEN, contextSchema, isCollecting, lastCollectTime, COLLECT_COOLDOWN_MS, mcpServer, server;
|
|
@@ -56790,9 +56764,6 @@ var init_mcp_server2 = __esm({
|
|
|
56790
56764
|
API_URL = process.env.HAANSI_API_URL ?? DEFAULT_API_URL3;
|
|
56791
56765
|
TOKEN = resolveToken2();
|
|
56792
56766
|
if (!TOKEN) {
|
|
56793
|
-
console.error(
|
|
56794
|
-
"[mcp-server] ERROR: HAANSI_TOKEN is required (or set in ~/.haansi/credentials.json)"
|
|
56795
|
-
);
|
|
56796
56767
|
process.exit(1);
|
|
56797
56768
|
}
|
|
56798
56769
|
contextSchema = {
|
|
@@ -56915,6 +56886,10 @@ var init_mcp_server2 = __esm({
|
|
|
56915
56886
|
type: "string",
|
|
56916
56887
|
description: "Git remote origin URL (e.g. github.com/org/repo)"
|
|
56917
56888
|
},
|
|
56889
|
+
git_commit: {
|
|
56890
|
+
type: "string",
|
|
56891
|
+
description: "Git commit hash for the code changes"
|
|
56892
|
+
},
|
|
56918
56893
|
tags: {
|
|
56919
56894
|
type: "array",
|
|
56920
56895
|
items: { type: "string" },
|
|
@@ -56938,7 +56913,6 @@ var init_mcp_server2 = __esm({
|
|
|
56938
56913
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
56939
56914
|
maybeCollect();
|
|
56940
56915
|
const { name, arguments: args } = request.params;
|
|
56941
|
-
console.error(`[mcp-server] Tool: ${name}`);
|
|
56942
56916
|
try {
|
|
56943
56917
|
switch (name) {
|
|
56944
56918
|
case "search_solutions": {
|
|
@@ -57012,6 +56986,7 @@ var init_mcp_server2 = __esm({
|
|
|
57012
56986
|
project_path,
|
|
57013
56987
|
git_branch,
|
|
57014
56988
|
git_remote_url,
|
|
56989
|
+
git_commit,
|
|
57015
56990
|
tags,
|
|
57016
56991
|
model,
|
|
57017
56992
|
source_tool,
|
|
@@ -57037,6 +57012,7 @@ var init_mcp_server2 = __esm({
|
|
|
57037
57012
|
project_path,
|
|
57038
57013
|
git_branch,
|
|
57039
57014
|
git_remote_url,
|
|
57015
|
+
git_commit,
|
|
57040
57016
|
tags,
|
|
57041
57017
|
model,
|
|
57042
57018
|
source_tool
|
|
@@ -57061,15 +57037,13 @@ var init_mcp_server2 = __esm({
|
|
|
57061
57037
|
throw new Error(`Unknown tool: ${name}`);
|
|
57062
57038
|
}
|
|
57063
57039
|
} catch (err) {
|
|
57064
|
-
console.error(`[mcp-server] Tool ${name} error:`, err.message);
|
|
57065
57040
|
return {
|
|
57066
57041
|
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
57067
57042
|
isError: true
|
|
57068
57043
|
};
|
|
57069
57044
|
}
|
|
57070
57045
|
});
|
|
57071
|
-
main3().catch((
|
|
57072
|
-
console.error("[mcp-server] Failed to start:", err);
|
|
57046
|
+
main3().catch(() => {
|
|
57073
57047
|
process.exit(1);
|
|
57074
57048
|
});
|
|
57075
57049
|
}
|
|
@@ -57177,8 +57151,9 @@ async function setupDaemon(uninstall) {
|
|
|
57177
57151
|
return;
|
|
57178
57152
|
}
|
|
57179
57153
|
try {
|
|
57180
|
-
(0, import_node_child_process2.
|
|
57181
|
-
} catch {
|
|
57154
|
+
(0, import_node_child_process2.spawnSync)("launchctl", ["unload", PLIST_PATH], { stdio: "pipe" });
|
|
57155
|
+
} catch (err) {
|
|
57156
|
+
console.error("Failed to unload daemon:", err);
|
|
57182
57157
|
}
|
|
57183
57158
|
(0, import_node_fs3.unlinkSync)(PLIST_PATH);
|
|
57184
57159
|
console.log("Daemon stopped and removed.");
|
|
@@ -57188,10 +57163,11 @@ async function setupDaemon(uninstall) {
|
|
|
57188
57163
|
(0, import_node_fs3.mkdirSync)((0, import_node_path3.join)((0, import_node_os3.homedir)(), ".haansi"), { recursive: true });
|
|
57189
57164
|
(0, import_node_fs3.writeFileSync)(PLIST_PATH, buildPlist(haansiPath), "utf-8");
|
|
57190
57165
|
try {
|
|
57191
|
-
(0, import_node_child_process2.
|
|
57192
|
-
} catch {
|
|
57166
|
+
(0, import_node_child_process2.spawnSync)("launchctl", ["unload", PLIST_PATH], { stdio: "pipe" });
|
|
57167
|
+
} catch (err) {
|
|
57168
|
+
console.error("Failed to unload previous daemon:", err);
|
|
57193
57169
|
}
|
|
57194
|
-
(0, import_node_child_process2.
|
|
57170
|
+
(0, import_node_child_process2.spawnSync)("launchctl", ["load", PLIST_PATH], { stdio: "inherit" });
|
|
57195
57171
|
console.log("Daemon installed and started.");
|
|
57196
57172
|
console.log(`Plist : ${PLIST_PATH}`);
|
|
57197
57173
|
console.log(`Logs : ${LOG_FILE}`);
|
|
@@ -57229,7 +57205,8 @@ function resolveToken3() {
|
|
|
57229
57205
|
try {
|
|
57230
57206
|
const creds = JSON.parse((0, import_node_fs4.readFileSync)(CREDENTIALS_FILE4, "utf-8"));
|
|
57231
57207
|
if (creds.token) return creds.token;
|
|
57232
|
-
} catch {
|
|
57208
|
+
} catch (err) {
|
|
57209
|
+
console.error("Failed to read credentials file:", err);
|
|
57233
57210
|
}
|
|
57234
57211
|
}
|
|
57235
57212
|
return null;
|
|
@@ -57344,7 +57321,8 @@ function resolveToken4() {
|
|
|
57344
57321
|
try {
|
|
57345
57322
|
const creds = JSON.parse((0, import_node_fs5.readFileSync)(CREDENTIALS_FILE5, "utf-8"));
|
|
57346
57323
|
if (creds.token) return creds.token;
|
|
57347
|
-
} catch {
|
|
57324
|
+
} catch (err) {
|
|
57325
|
+
console.error("Failed to read credentials file:", err);
|
|
57348
57326
|
}
|
|
57349
57327
|
}
|
|
57350
57328
|
throw new Error("No token found. Run `haansi init` first.");
|