integral-mind 0.1.0
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 +52 -0
- package/dist/cli.js +945 -0
- package/dist/cli.js.map +1 -0
- package/dist/client.js +82 -0
- package/dist/client.js.map +1 -0
- package/dist/config.js +75 -0
- package/dist/config.js.map +1 -0
- package/dist/docs.js +188 -0
- package/dist/docs.js.map +1 -0
- package/dist/exit.js +10 -0
- package/dist/exit.js.map +1 -0
- package/dist/hooks.js +261 -0
- package/dist/hooks.js.map +1 -0
- package/dist/redact.js +77 -0
- package/dist/redact.js.map +1 -0
- package/dist/sync/cli.js +431 -0
- package/dist/sync/cli.js.map +1 -0
- package/dist/sync/mempalace-source.js +136 -0
- package/dist/sync/mempalace-source.js.map +1 -0
- package/dist/sync/mempalace.js +215 -0
- package/dist/sync/mempalace.js.map +1 -0
- package/dist/sync/obsidian-source.js +30 -0
- package/dist/sync/obsidian-source.js.map +1 -0
- package/dist/sync/obsidian.js +52 -0
- package/dist/sync/obsidian.js.map +1 -0
- package/dist/sync/repo-source.js +43 -0
- package/dist/sync/repo-source.js.map +1 -0
- package/dist/sync/repo.js +51 -0
- package/dist/sync/repo.js.map +1 -0
- package/dist/sync/state.js +87 -0
- package/dist/sync/state.js.map +1 -0
- package/dist/sync/tree-sync.js +56 -0
- package/dist/sync/tree-sync.js.map +1 -0
- package/package.json +49 -0
package/dist/hooks.js
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { appendFile, mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { basename, dirname, join } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { resolveConfig } from "./config.js";
|
|
6
|
+
import { redactSecrets } from "./redact.js";
|
|
7
|
+
import { ImClient } from "./client.js";
|
|
8
|
+
/**
|
|
9
|
+
* Claude Code session-end hook: `im hooks install claude` wires a
|
|
10
|
+
* SessionEnd entry into ~/.claude/settings.json; at session end Claude
|
|
11
|
+
* Code invokes `... hooks claude-session-end` with a JSON payload on
|
|
12
|
+
* stdin ({ session_id, transcript_path, cwd, reason }), and this module
|
|
13
|
+
* reshapes the transcript into the session envelope, redacts it, and
|
|
14
|
+
* pushes it.
|
|
15
|
+
*
|
|
16
|
+
* Contract: the hook process ALWAYS exits 0. A failing push must never
|
|
17
|
+
* break or block the Claude session — failures are appended to
|
|
18
|
+
* ~/.local/state/integral-mind/hooks.log instead.
|
|
19
|
+
*/
|
|
20
|
+
const HOOK_MARKER = "hooks claude-session-end";
|
|
21
|
+
export function claudeSettingsPath(env = process.env) {
|
|
22
|
+
return env.INTEGRAL_MIND_CLAUDE_SETTINGS ?? join(homedir(), ".claude", "settings.json");
|
|
23
|
+
}
|
|
24
|
+
export function hookLogPath(env = process.env) {
|
|
25
|
+
return (env.INTEGRAL_MIND_HOOK_LOG ?? join(homedir(), ".local", "state", "integral-mind", "hooks.log"));
|
|
26
|
+
}
|
|
27
|
+
/** The command Claude Code will run: absolute node + absolute cli.js. */
|
|
28
|
+
export function hookCommand() {
|
|
29
|
+
const cliPath = fileURLToPath(new URL("./cli.js", import.meta.url));
|
|
30
|
+
return `${JSON.stringify(process.execPath)} ${JSON.stringify(cliPath)} ${HOOK_MARKER}`;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* A missing file is an empty config; anything else that prevents reading
|
|
34
|
+
* or parsing ABORTS the operation — installing over unreadable settings
|
|
35
|
+
* would silently erase whatever Claude Code had configured.
|
|
36
|
+
*/
|
|
37
|
+
async function readSettings(path) {
|
|
38
|
+
let raw;
|
|
39
|
+
try {
|
|
40
|
+
raw = await readFile(path, "utf8");
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
if (err.code === "ENOENT")
|
|
44
|
+
return {};
|
|
45
|
+
throw new Error(`Cannot read ${path}: ${err instanceof Error ? err.message : String(err)}`);
|
|
46
|
+
}
|
|
47
|
+
let parsed;
|
|
48
|
+
try {
|
|
49
|
+
parsed = JSON.parse(raw);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
throw new Error(`${path} is not valid JSON — fix or remove it before installing the hook (refusing to overwrite it)`);
|
|
53
|
+
}
|
|
54
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
55
|
+
throw new Error(`${path} does not contain a JSON object — refusing to overwrite it`);
|
|
56
|
+
}
|
|
57
|
+
return parsed;
|
|
58
|
+
}
|
|
59
|
+
/** Atomic replace: write a sibling temp file, then rename over the target. */
|
|
60
|
+
async function writeSettings(path, serialized) {
|
|
61
|
+
await mkdir(dirname(path), { recursive: true });
|
|
62
|
+
const tmp = `${path}.integral-mind-${process.pid}.tmp`;
|
|
63
|
+
await writeFile(tmp, serialized);
|
|
64
|
+
await rename(tmp, path);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Read-modify-write that never silently discards a concurrent update.
|
|
68
|
+
* Cooperative locking is impossible here — Claude Code writes
|
|
69
|
+
* settings.json without honoring any lock we could take — so instead of
|
|
70
|
+
* pretending to prevent the race, we detect losing it: after the atomic
|
|
71
|
+
* rename, re-read the file; if it no longer matches what we wrote,
|
|
72
|
+
* another writer won mid-cycle, and the loop re-applies our modification
|
|
73
|
+
* on top of THEIR fresh state. Either our write is the visible state or
|
|
74
|
+
* it is re-merged — nothing is dropped without a retry.
|
|
75
|
+
* Returns false when `modify` decides there is nothing to change.
|
|
76
|
+
*/
|
|
77
|
+
async function updateSettings(path, modify) {
|
|
78
|
+
for (let attempt = 0; attempt < 5; attempt++) {
|
|
79
|
+
const settings = await readSettings(path);
|
|
80
|
+
const next = modify(settings);
|
|
81
|
+
if (next === null)
|
|
82
|
+
return false;
|
|
83
|
+
const serialized = `${JSON.stringify(next, null, 2)}\n`;
|
|
84
|
+
await writeSettings(path, serialized);
|
|
85
|
+
const after = await readFile(path, "utf8").catch(() => null);
|
|
86
|
+
if (after === serialized)
|
|
87
|
+
return true;
|
|
88
|
+
// A concurrent writer overlapped; loop to re-apply on their state.
|
|
89
|
+
}
|
|
90
|
+
throw new Error(`${path} keeps changing while updating it — is a session ending? Retry shortly.`);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Ours = the command *ends with* our subcommand AND references this CLI
|
|
94
|
+
* (cli.js, the npm package name, or a bare `im`). A foreign command that
|
|
95
|
+
* merely mentions the marker somewhere in its arguments is not claimed.
|
|
96
|
+
*/
|
|
97
|
+
function isOurs(entry) {
|
|
98
|
+
return (entry.hooks ?? []).some((h) => {
|
|
99
|
+
const command = h.command?.trim() ?? "";
|
|
100
|
+
if (!command.endsWith(HOOK_MARKER))
|
|
101
|
+
return false;
|
|
102
|
+
if (command === hookCommand())
|
|
103
|
+
return true; // exact current install
|
|
104
|
+
// Package identity, not just "some cli.js": the path must contain this
|
|
105
|
+
// package's name (npm: node_modules/integral-mind/...; repo checkout:
|
|
106
|
+
// apps/im-cli/...), or be the bare bin / npx invocation.
|
|
107
|
+
return (/(^|[\/"'\s])(integral-mind|im-cli)[\/"']/.test(command) ||
|
|
108
|
+
command === `im ${HOOK_MARKER}` ||
|
|
109
|
+
command === `npx integral-mind ${HOOK_MARKER}`);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
/** Idempotent: a second install is a no-op. Returns what happened. */
|
|
113
|
+
export async function installClaudeHook(env = process.env) {
|
|
114
|
+
const wrote = await updateSettings(claudeSettingsPath(env), (settings) => {
|
|
115
|
+
const sessionEnd = settings.hooks?.SessionEnd ?? [];
|
|
116
|
+
if (sessionEnd.some(isOurs))
|
|
117
|
+
return null;
|
|
118
|
+
return {
|
|
119
|
+
...settings,
|
|
120
|
+
hooks: {
|
|
121
|
+
...settings.hooks,
|
|
122
|
+
SessionEnd: [...sessionEnd, { hooks: [{ type: "command", command: hookCommand() }] }],
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
});
|
|
126
|
+
return wrote ? "installed" : "already-installed";
|
|
127
|
+
}
|
|
128
|
+
/** Removes only our entries; other hooks survive untouched. */
|
|
129
|
+
export async function uninstallClaudeHook(env = process.env) {
|
|
130
|
+
const wrote = await updateSettings(claudeSettingsPath(env), (settings) => {
|
|
131
|
+
const sessionEnd = settings.hooks?.SessionEnd ?? [];
|
|
132
|
+
const kept = sessionEnd.filter((entry) => !isOurs(entry));
|
|
133
|
+
if (kept.length === sessionEnd.length)
|
|
134
|
+
return null;
|
|
135
|
+
const hooks = { ...settings.hooks };
|
|
136
|
+
if (kept.length > 0)
|
|
137
|
+
hooks.SessionEnd = kept;
|
|
138
|
+
else
|
|
139
|
+
delete hooks.SessionEnd;
|
|
140
|
+
const next = { ...settings };
|
|
141
|
+
if (Object.keys(hooks).length > 0)
|
|
142
|
+
next.hooks = hooks;
|
|
143
|
+
else
|
|
144
|
+
delete next.hooks;
|
|
145
|
+
return next;
|
|
146
|
+
});
|
|
147
|
+
return wrote ? "uninstalled" : "not-installed";
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Lenient Claude Code transcript (.jsonl) reader: keeps user/assistant
|
|
151
|
+
* text, renders tool activity as short placeholders, skips everything it
|
|
152
|
+
* doesn't recognize. Transcript format drift must degrade, not throw.
|
|
153
|
+
*/
|
|
154
|
+
export function parseTranscript(jsonl) {
|
|
155
|
+
const messages = [];
|
|
156
|
+
for (const line of jsonl.split("\n")) {
|
|
157
|
+
if (!line.trim())
|
|
158
|
+
continue;
|
|
159
|
+
let obj;
|
|
160
|
+
try {
|
|
161
|
+
obj = JSON.parse(line);
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
const type = obj.type;
|
|
167
|
+
if (type !== "user" && type !== "assistant")
|
|
168
|
+
continue;
|
|
169
|
+
const message = obj.message;
|
|
170
|
+
if (!message)
|
|
171
|
+
continue;
|
|
172
|
+
const role = message.role === "assistant" ? "assistant" : "user";
|
|
173
|
+
const content = renderContent(message.content);
|
|
174
|
+
if (!content.trim())
|
|
175
|
+
continue;
|
|
176
|
+
const ts = typeof obj.timestamp === "string" ? obj.timestamp : undefined;
|
|
177
|
+
messages.push({ role, content, ...(ts ? { ts } : {}) });
|
|
178
|
+
}
|
|
179
|
+
return messages;
|
|
180
|
+
}
|
|
181
|
+
function renderContent(content) {
|
|
182
|
+
if (typeof content === "string")
|
|
183
|
+
return content;
|
|
184
|
+
if (!Array.isArray(content))
|
|
185
|
+
return "";
|
|
186
|
+
const parts = [];
|
|
187
|
+
for (const block of content) {
|
|
188
|
+
if (typeof block !== "object" || block === null)
|
|
189
|
+
continue;
|
|
190
|
+
const b = block;
|
|
191
|
+
if (b.type === "text" && typeof b.text === "string")
|
|
192
|
+
parts.push(b.text);
|
|
193
|
+
else if (b.type === "tool_use" && typeof b.name === "string")
|
|
194
|
+
parts.push(`[tool_use: ${b.name}]`);
|
|
195
|
+
else if (b.type === "tool_result")
|
|
196
|
+
parts.push("[tool_result]");
|
|
197
|
+
}
|
|
198
|
+
return parts.join("\n");
|
|
199
|
+
}
|
|
200
|
+
export function defaultSessionEndDeps(env = process.env) {
|
|
201
|
+
return {
|
|
202
|
+
pushSession: async (envelope) => {
|
|
203
|
+
const resolved = await resolveConfig(env);
|
|
204
|
+
if ("error" in resolved)
|
|
205
|
+
throw new Error(resolved.error);
|
|
206
|
+
return new ImClient(resolved).pushSession(envelope);
|
|
207
|
+
},
|
|
208
|
+
log: async (line) => {
|
|
209
|
+
const path = hookLogPath(env);
|
|
210
|
+
await mkdir(dirname(path), { recursive: true });
|
|
211
|
+
await appendFile(path, `${new Date().toISOString()} ${line}\n`);
|
|
212
|
+
},
|
|
213
|
+
readTranscript: (path) => readFile(path, "utf8"),
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* The hook entrypoint. Never throws; always resolves 0 so Claude Code is
|
|
218
|
+
* never blocked by a failing push.
|
|
219
|
+
*/
|
|
220
|
+
export async function runClaudeSessionEnd(stdinJson, deps) {
|
|
221
|
+
try {
|
|
222
|
+
const payload = JSON.parse(stdinJson);
|
|
223
|
+
if (!payload.transcript_path) {
|
|
224
|
+
await deps.log("skip: no transcript_path in hook payload");
|
|
225
|
+
return 0;
|
|
226
|
+
}
|
|
227
|
+
const messages = parseTranscript(await deps.readTranscript(payload.transcript_path));
|
|
228
|
+
if (messages.length < 2) {
|
|
229
|
+
await deps.log(`skip: transcript has ${messages.length} usable message(s)`);
|
|
230
|
+
return 0;
|
|
231
|
+
}
|
|
232
|
+
const project = payload.cwd ? basename(payload.cwd) : "unknown-project";
|
|
233
|
+
const envelope = {
|
|
234
|
+
agent: "claude-code",
|
|
235
|
+
title: `Claude Code — ${project}${payload.session_id ? ` (${payload.session_id.slice(0, 8)})` : ""}`,
|
|
236
|
+
...(messages[0]?.ts ? { started_at: messages[0].ts } : {}),
|
|
237
|
+
messages,
|
|
238
|
+
};
|
|
239
|
+
let redactedCount = 0;
|
|
240
|
+
for (const message of messages) {
|
|
241
|
+
const { text, findings } = redactSecrets(message.content);
|
|
242
|
+
message.content = text;
|
|
243
|
+
redactedCount += findings.reduce((sum, f) => sum + f.count, 0);
|
|
244
|
+
}
|
|
245
|
+
// The title embeds the cwd basename — hook metadata gets the same
|
|
246
|
+
// redaction contract as message content.
|
|
247
|
+
const titleScrub = redactSecrets(String(envelope.title));
|
|
248
|
+
envelope.title = titleScrub.text;
|
|
249
|
+
redactedCount += titleScrub.findings.reduce((sum, f) => sum + f.count, 0);
|
|
250
|
+
const result = await deps.pushSession(envelope);
|
|
251
|
+
await deps.log(`pushed session ${String(result.source_id)} (${messages.length} messages, ${redactedCount} redaction(s), deduplicated=${String(result.deduplicated)})`);
|
|
252
|
+
}
|
|
253
|
+
catch (err) {
|
|
254
|
+
// Swallow everything: the log line is the only side effect of failure.
|
|
255
|
+
await deps
|
|
256
|
+
.log(`push failed: ${err instanceof Error ? err.message : String(err)}`)
|
|
257
|
+
.catch(() => { });
|
|
258
|
+
}
|
|
259
|
+
return 0;
|
|
260
|
+
}
|
|
261
|
+
//# sourceMappingURL=hooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.js","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"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"}
|
package/dist/redact.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// Order matters: structured/specific shapes run before the generic
|
|
2
|
+
// high-entropy catch-all so findings carry the most precise type.
|
|
3
|
+
export const REDACTION_RULES = [
|
|
4
|
+
{
|
|
5
|
+
type: "pem-block",
|
|
6
|
+
pattern: /-----BEGIN [A-Z0-9 ]{0,48}PRIVATE KEY-----[\s\S]*?-----END [A-Z0-9 ]{0,48}PRIVATE KEY-----/g,
|
|
7
|
+
},
|
|
8
|
+
{ type: "anthropic-api-key", pattern: /\bsk-ant-[A-Za-z0-9_-]{16,}\b/g },
|
|
9
|
+
{ type: "openai-api-key", pattern: /\bsk-(?:proj-|svcacct-)?[A-Za-z0-9_-]{20,}\b/g },
|
|
10
|
+
{ type: "integral-mind-token", pattern: /\bimk_[A-Za-z0-9_-]{16,}\b/g },
|
|
11
|
+
{ type: "aws-access-key", pattern: /\b(?:AKIA|ASIA|ABIA|ACCA)[A-Z0-9]{16}\b/g },
|
|
12
|
+
{ type: "github-token", pattern: /\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{20,}\b/g },
|
|
13
|
+
{ type: "github-fine-grained-token", pattern: /\bgithub_pat_[A-Za-z0-9_]{20,}\b/g },
|
|
14
|
+
{ type: "google-api-key", pattern: /\bAIza[A-Za-z0-9_-]{30,}\b/g },
|
|
15
|
+
{ type: "slack-token", pattern: /\bxox[abprs]-[A-Za-z0-9-]{10,}\b/g },
|
|
16
|
+
{ type: "stripe-key", pattern: /\b[sr]k_(?:live|test)_[A-Za-z0-9]{16,}\b/g },
|
|
17
|
+
{ type: "jwt", pattern: /\beyJ[A-Za-z0-9_-]{8,}\.eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g },
|
|
18
|
+
{
|
|
19
|
+
// Square brackets are excluded so an earlier rule's [REDACTED:…]
|
|
20
|
+
// placeholder (which contains a colon) can never re-match as user:pass.
|
|
21
|
+
type: "connection-string",
|
|
22
|
+
pattern: /\b[a-z][a-z0-9+.-]{1,24}:\/\/[^\s:@/[\]]{1,64}:[^\s@[\]]{1,256}@[^\s"'`]+/gi,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
type: "assignment-secret",
|
|
26
|
+
pattern: /\b((?:api[_-]?key|secret|token|passwd|password|credential)[a-z0-9_-]*)(\s*[:=]\s*)["']?(?!\[REDACTED)[^\s"']{8,}["']?/gi,
|
|
27
|
+
},
|
|
28
|
+
];
|
|
29
|
+
const HIGH_ENTROPY_CANDIDATE = /\b[A-Za-z0-9+/=_-]{32,}\b/g;
|
|
30
|
+
const ENTROPY_THRESHOLD = 4.2;
|
|
31
|
+
export function shannonEntropy(value) {
|
|
32
|
+
const counts = new Map();
|
|
33
|
+
for (const ch of value)
|
|
34
|
+
counts.set(ch, (counts.get(ch) ?? 0) + 1);
|
|
35
|
+
let entropy = 0;
|
|
36
|
+
for (const count of counts.values()) {
|
|
37
|
+
const p = count / value.length;
|
|
38
|
+
entropy -= p * Math.log2(p);
|
|
39
|
+
}
|
|
40
|
+
return entropy;
|
|
41
|
+
}
|
|
42
|
+
export function redactSecrets(text) {
|
|
43
|
+
const counts = new Map();
|
|
44
|
+
let out = text;
|
|
45
|
+
for (const rule of REDACTION_RULES) {
|
|
46
|
+
out = out.replace(rule.pattern, (match, ...rest) => {
|
|
47
|
+
counts.set(rule.type, (counts.get(rule.type) ?? 0) + 1);
|
|
48
|
+
if (rule.type === "assignment-secret") {
|
|
49
|
+
// Keep the key name; replace only the value.
|
|
50
|
+
const [name, sep] = rest;
|
|
51
|
+
return `${name}${sep}[REDACTED:${rule.type}]`;
|
|
52
|
+
}
|
|
53
|
+
return `[REDACTED:${rule.type}]`;
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
out = out.replace(HIGH_ENTROPY_CANDIDATE, (match) => {
|
|
57
|
+
if (match.startsWith("[REDACTED"))
|
|
58
|
+
return match;
|
|
59
|
+
// Long hex (git SHAs) and uuid-ish strings are common in transcripts;
|
|
60
|
+
// only redact when entropy says "random secret", not "identifier".
|
|
61
|
+
if (shannonEntropy(match) < ENTROPY_THRESHOLD)
|
|
62
|
+
return match;
|
|
63
|
+
counts.set("high-entropy-string", (counts.get("high-entropy-string") ?? 0) + 1);
|
|
64
|
+
return "[REDACTED:high-entropy-string]";
|
|
65
|
+
});
|
|
66
|
+
const findings = [...counts.entries()]
|
|
67
|
+
.map(([type, count]) => ({ type, count }))
|
|
68
|
+
.sort((a, b) => a.type.localeCompare(b.type));
|
|
69
|
+
return { text: out, findings };
|
|
70
|
+
}
|
|
71
|
+
export function summarizeFindings(findings) {
|
|
72
|
+
if (findings.length === 0)
|
|
73
|
+
return "redaction: nothing to scrub";
|
|
74
|
+
const parts = findings.map((f) => `${f.type}×${f.count}`);
|
|
75
|
+
return `redaction: scrubbed ${parts.join(", ")}`;
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=redact.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redact.js","sourceRoot":"","sources":["../src/redact.ts"],"names":[],"mappings":"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"}
|