haansi 0.1.13 → 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 +24 -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 = {
|
|
@@ -56942,7 +56913,6 @@ var init_mcp_server2 = __esm({
|
|
|
56942
56913
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
56943
56914
|
maybeCollect();
|
|
56944
56915
|
const { name, arguments: args } = request.params;
|
|
56945
|
-
console.error(`[mcp-server] Tool: ${name}`);
|
|
56946
56916
|
try {
|
|
56947
56917
|
switch (name) {
|
|
56948
56918
|
case "search_solutions": {
|
|
@@ -57067,15 +57037,13 @@ var init_mcp_server2 = __esm({
|
|
|
57067
57037
|
throw new Error(`Unknown tool: ${name}`);
|
|
57068
57038
|
}
|
|
57069
57039
|
} catch (err) {
|
|
57070
|
-
console.error(`[mcp-server] Tool ${name} error:`, err.message);
|
|
57071
57040
|
return {
|
|
57072
57041
|
content: [{ type: "text", text: `Error: ${err.message}` }],
|
|
57073
57042
|
isError: true
|
|
57074
57043
|
};
|
|
57075
57044
|
}
|
|
57076
57045
|
});
|
|
57077
|
-
main3().catch((
|
|
57078
|
-
console.error("[mcp-server] Failed to start:", err);
|
|
57046
|
+
main3().catch(() => {
|
|
57079
57047
|
process.exit(1);
|
|
57080
57048
|
});
|
|
57081
57049
|
}
|
|
@@ -57183,8 +57151,9 @@ async function setupDaemon(uninstall) {
|
|
|
57183
57151
|
return;
|
|
57184
57152
|
}
|
|
57185
57153
|
try {
|
|
57186
|
-
(0, import_node_child_process2.
|
|
57187
|
-
} 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);
|
|
57188
57157
|
}
|
|
57189
57158
|
(0, import_node_fs3.unlinkSync)(PLIST_PATH);
|
|
57190
57159
|
console.log("Daemon stopped and removed.");
|
|
@@ -57194,10 +57163,11 @@ async function setupDaemon(uninstall) {
|
|
|
57194
57163
|
(0, import_node_fs3.mkdirSync)((0, import_node_path3.join)((0, import_node_os3.homedir)(), ".haansi"), { recursive: true });
|
|
57195
57164
|
(0, import_node_fs3.writeFileSync)(PLIST_PATH, buildPlist(haansiPath), "utf-8");
|
|
57196
57165
|
try {
|
|
57197
|
-
(0, import_node_child_process2.
|
|
57198
|
-
} 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);
|
|
57199
57169
|
}
|
|
57200
|
-
(0, import_node_child_process2.
|
|
57170
|
+
(0, import_node_child_process2.spawnSync)("launchctl", ["load", PLIST_PATH], { stdio: "inherit" });
|
|
57201
57171
|
console.log("Daemon installed and started.");
|
|
57202
57172
|
console.log(`Plist : ${PLIST_PATH}`);
|
|
57203
57173
|
console.log(`Logs : ${LOG_FILE}`);
|
|
@@ -57235,7 +57205,8 @@ function resolveToken3() {
|
|
|
57235
57205
|
try {
|
|
57236
57206
|
const creds = JSON.parse((0, import_node_fs4.readFileSync)(CREDENTIALS_FILE4, "utf-8"));
|
|
57237
57207
|
if (creds.token) return creds.token;
|
|
57238
|
-
} catch {
|
|
57208
|
+
} catch (err) {
|
|
57209
|
+
console.error("Failed to read credentials file:", err);
|
|
57239
57210
|
}
|
|
57240
57211
|
}
|
|
57241
57212
|
return null;
|
|
@@ -57350,7 +57321,8 @@ function resolveToken4() {
|
|
|
57350
57321
|
try {
|
|
57351
57322
|
const creds = JSON.parse((0, import_node_fs5.readFileSync)(CREDENTIALS_FILE5, "utf-8"));
|
|
57352
57323
|
if (creds.token) return creds.token;
|
|
57353
|
-
} catch {
|
|
57324
|
+
} catch (err) {
|
|
57325
|
+
console.error("Failed to read credentials file:", err);
|
|
57354
57326
|
}
|
|
57355
57327
|
}
|
|
57356
57328
|
throw new Error("No token found. Run `haansi init` first.");
|