@nex-ai/nex 0.1.17 → 0.1.19
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 +42 -23
- package/dist/commands/integrate.js +11 -2
- package/dist/commands/integrate.js.map +1 -1
- package/dist/commands/setup.d.ts +5 -4
- package/dist/commands/setup.js +141 -43
- package/dist/commands/setup.js.map +1 -1
- package/dist/lib/client.js +12 -1
- package/dist/lib/client.js.map +1 -1
- package/dist/lib/file-scanner.d.ts +1 -1
- package/dist/lib/file-scanner.js +4 -2
- package/dist/lib/file-scanner.js.map +1 -1
- package/dist/lib/installers.d.ts +62 -5
- package/dist/lib/installers.js +373 -34
- package/dist/lib/installers.js.map +1 -1
- package/dist/lib/platform-detect.d.ts +7 -0
- package/dist/lib/platform-detect.js +150 -8
- package/dist/lib/platform-detect.js.map +1 -1
- package/dist/lib/project-config.js +1 -1
- package/dist/plugin/adapters/cline-capture.d.ts +7 -0
- package/dist/plugin/adapters/cline-capture.js +25 -0
- package/dist/plugin/adapters/cline-capture.js.map +1 -0
- package/dist/plugin/adapters/cline-recall.d.ts +7 -0
- package/dist/plugin/adapters/cline-recall.js +30 -0
- package/dist/plugin/adapters/cline-recall.js.map +1 -0
- package/dist/plugin/adapters/cline-task-start.d.ts +7 -0
- package/dist/plugin/adapters/cline-task-start.js +30 -0
- package/dist/plugin/adapters/cline-task-start.js.map +1 -0
- package/dist/plugin/adapters/cursor-recall.d.ts +7 -0
- package/dist/plugin/adapters/cursor-recall.js +31 -0
- package/dist/plugin/adapters/cursor-recall.js.map +1 -0
- package/dist/plugin/adapters/cursor-session-start.d.ts +7 -0
- package/dist/plugin/adapters/cursor-session-start.js +30 -0
- package/dist/plugin/adapters/cursor-session-start.js.map +1 -0
- package/dist/plugin/adapters/cursor-stop.d.ts +7 -0
- package/dist/plugin/adapters/cursor-stop.js +25 -0
- package/dist/plugin/adapters/cursor-stop.js.map +1 -0
- package/dist/plugin/adapters/windsurf-capture.d.ts +7 -0
- package/dist/plugin/adapters/windsurf-capture.js +25 -0
- package/dist/plugin/adapters/windsurf-capture.js.map +1 -0
- package/dist/plugin/adapters/windsurf-recall.d.ts +7 -0
- package/dist/plugin/adapters/windsurf-recall.js +31 -0
- package/dist/plugin/adapters/windsurf-recall.js.map +1 -0
- package/dist/plugin/auto-capture.js +5 -106
- package/dist/plugin/auto-capture.js.map +1 -1
- package/dist/plugin/auto-recall.js +5 -79
- package/dist/plugin/auto-recall.js.map +1 -1
- package/dist/plugin/auto-session-start.d.ts +0 -3
- package/dist/plugin/auto-session-start.js +8 -114
- package/dist/plugin/auto-session-start.js.map +1 -1
- package/dist/plugin/shared.d.ts +38 -0
- package/dist/plugin/shared.js +241 -0
- package/dist/plugin/shared.js.map +1 -0
- package/package.json +3 -1
- package/platform-plugins/continue-provider.ts +62 -0
- package/platform-plugins/kilocode-modes.yaml +17 -0
- package/platform-plugins/opencode-plugin.ts +103 -0
- package/platform-plugins/vscode-agent.md +34 -0
- package/platform-plugins/windsurf-workflows/nex-ask.md +10 -0
- package/platform-plugins/windsurf-workflows/nex-remember.md +10 -0
- package/platform-plugins/windsurf-workflows/nex-search.md +10 -0
- package/platform-rules/aider-conventions.md +38 -0
- package/platform-rules/cline-rules.md +34 -0
- package/platform-rules/continue-rules.md +34 -0
- package/platform-rules/cursor-rules.md +34 -0
- package/platform-rules/kilocode-rules.md +34 -0
- package/platform-rules/opencode-agents.md +38 -0
- package/platform-rules/vscode-instructions.md +38 -0
- package/platform-rules/windsurf-rules.md +34 -0
- package/platform-rules/zed-rules.md +38 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Windsurf post_cascade_response hook — auto-capture to Nex.
|
|
4
|
+
* Input: { tool_info: { response: string } }
|
|
5
|
+
* Output: {}
|
|
6
|
+
*/
|
|
7
|
+
import { doCapture, readStdin } from "../shared.js";
|
|
8
|
+
async function main() {
|
|
9
|
+
try {
|
|
10
|
+
const raw = await readStdin();
|
|
11
|
+
let input = {};
|
|
12
|
+
try {
|
|
13
|
+
input = JSON.parse(raw);
|
|
14
|
+
}
|
|
15
|
+
catch { /* defaults */ }
|
|
16
|
+
await doCapture({ message: input.tool_info?.response ?? "" });
|
|
17
|
+
process.stdout.write("{}");
|
|
18
|
+
}
|
|
19
|
+
catch (err) {
|
|
20
|
+
process.stderr.write(`[nex-windsurf] Capture error: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
21
|
+
process.stdout.write("{}");
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
main().then(() => process.exit(0)).catch(() => process.exit(0));
|
|
25
|
+
//# sourceMappingURL=windsurf-capture.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"windsurf-capture.js","sourceRoot":"","sources":["../../../src/plugin/adapters/windsurf-capture.ts"],"names":[],"mappings":";AACA;;;;GAIG;AAEH,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAMpD,KAAK,UAAU,IAAI;IACjB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,SAAS,EAAE,CAAC;QAC9B,IAAI,KAAK,GAAkB,EAAE,CAAC;QAC9B,IAAI,CAAC;YAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC,CAAC,cAAc,CAAC,CAAC;QAEzD,MAAM,SAAS,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,SAAS,EAAE,QAAQ,IAAI,EAAE,EAAE,CAAC,CAAC;QAC9D,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,iCAAiC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC5G,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Windsurf pre_user_prompt hook — auto-recall from Nex.
|
|
4
|
+
* Input: { tool_info: { user_prompt: string } }
|
|
5
|
+
* Output: context string on stdout (displayed via show_output) or {}
|
|
6
|
+
*/
|
|
7
|
+
import { doRecall, readStdin } from "../shared.js";
|
|
8
|
+
async function main() {
|
|
9
|
+
try {
|
|
10
|
+
const raw = await readStdin();
|
|
11
|
+
let input = {};
|
|
12
|
+
try {
|
|
13
|
+
input = JSON.parse(raw);
|
|
14
|
+
}
|
|
15
|
+
catch { /* defaults */ }
|
|
16
|
+
const prompt = input.tool_info?.user_prompt ?? "";
|
|
17
|
+
const result = await doRecall(prompt, input.session_id);
|
|
18
|
+
if (!result) {
|
|
19
|
+
process.stdout.write("{}");
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
// Windsurf displays stdout content via show_output
|
|
23
|
+
process.stdout.write(JSON.stringify({ additional_context: result.context }));
|
|
24
|
+
}
|
|
25
|
+
catch (err) {
|
|
26
|
+
process.stderr.write(`[nex-windsurf] Recall error: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
27
|
+
process.stdout.write("{}");
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
main().then(() => process.exit(0)).catch(() => process.exit(0));
|
|
31
|
+
//# sourceMappingURL=windsurf-recall.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"windsurf-recall.js","sourceRoot":"","sources":["../../../src/plugin/adapters/windsurf-recall.ts"],"names":[],"mappings":";AACA;;;;GAIG;AAEH,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAOnD,KAAK,UAAU,IAAI;IACjB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,SAAS,EAAE,CAAC;QAC9B,IAAI,KAAK,GAAkB,EAAE,CAAC;QAC9B,IAAI,CAAC;YAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC,CAAC,cAAc,CAAC,CAAC;QAEzD,MAAM,MAAM,GAAG,KAAK,CAAC,SAAS,EAAE,WAAW,IAAI,EAAE,CAAC;QAClD,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;QAExD,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAC3B,OAAO;QACT,CAAC;QAED,mDAAmD;QACnD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,kBAAkB,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IAC/E,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,gCAAgC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC3G,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC"}
|
|
@@ -8,80 +8,10 @@
|
|
|
8
8
|
*
|
|
9
9
|
* On ANY error: outputs {} and exits 0 (graceful degradation).
|
|
10
10
|
*/
|
|
11
|
-
import {
|
|
12
|
-
import { join, extname } from "node:path";
|
|
13
|
-
import { loadConfig, loadScanConfig, isHookEnabled } from "./config.js";
|
|
14
|
-
import { NexClient } from "./nex-client.js";
|
|
15
|
-
import { captureFilter } from "./capture-filter.js";
|
|
16
|
-
import { RateLimiter } from "./rate-limiter.js";
|
|
17
|
-
import { readManifest, writeManifest, isChanged, markIngested } from "./file-manifest.js";
|
|
18
|
-
/** Ingest timeout — 3s leaves buffer within hook timeout */
|
|
19
|
-
const INGEST_TIMEOUT_MS = 3_000;
|
|
20
|
-
const MAX_PLAN_FILES = 2;
|
|
21
|
-
const rateLimiter = new RateLimiter();
|
|
22
|
-
/**
|
|
23
|
-
* Scan .claude/plans/ for changed .md files and ingest up to MAX_PLAN_FILES.
|
|
24
|
-
*/
|
|
25
|
-
async function ingestPlanFiles(client) {
|
|
26
|
-
const scanConfig = loadScanConfig();
|
|
27
|
-
if (!scanConfig.enabled)
|
|
28
|
-
return;
|
|
29
|
-
const plansDir = join(process.cwd(), ".claude", "plans");
|
|
30
|
-
let entries;
|
|
31
|
-
try {
|
|
32
|
-
entries = readdirSync(plansDir, { withFileTypes: true });
|
|
33
|
-
}
|
|
34
|
-
catch {
|
|
35
|
-
return; // No .claude/plans/ dir — normal, skip silently
|
|
36
|
-
}
|
|
37
|
-
const manifest = readManifest();
|
|
38
|
-
let ingested = 0;
|
|
39
|
-
for (const entry of entries) {
|
|
40
|
-
if (ingested >= MAX_PLAN_FILES)
|
|
41
|
-
break;
|
|
42
|
-
if (!entry.isFile())
|
|
43
|
-
continue;
|
|
44
|
-
if (extname(entry.name).toLowerCase() !== ".md")
|
|
45
|
-
continue;
|
|
46
|
-
const fullPath = join(plansDir, entry.name);
|
|
47
|
-
try {
|
|
48
|
-
const stat = statSync(fullPath);
|
|
49
|
-
if (!isChanged(fullPath, stat, manifest))
|
|
50
|
-
continue;
|
|
51
|
-
if (!rateLimiter.canProceed()) {
|
|
52
|
-
process.stderr.write("[nex-capture] Rate limited — skipping plan file ingest\n");
|
|
53
|
-
break;
|
|
54
|
-
}
|
|
55
|
-
let content = readFileSync(fullPath, "utf-8");
|
|
56
|
-
if (content.length > 100_000) {
|
|
57
|
-
content = content.slice(0, 100_000) + "\n[...truncated]";
|
|
58
|
-
}
|
|
59
|
-
const context = `claude-code-plan:${entry.name}`;
|
|
60
|
-
await client.ingest(content, context, INGEST_TIMEOUT_MS);
|
|
61
|
-
markIngested(fullPath, stat, context, manifest);
|
|
62
|
-
ingested++;
|
|
63
|
-
}
|
|
64
|
-
catch (err) {
|
|
65
|
-
process.stderr.write(`[nex-capture] Plan file ingest failed (${entry.name}): ${err instanceof Error ? err.message : String(err)}\n`);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
if (ingested > 0) {
|
|
69
|
-
writeManifest(manifest);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
11
|
+
import { doCapture, readStdin } from "./shared.js";
|
|
72
12
|
async function main() {
|
|
73
13
|
try {
|
|
74
|
-
|
|
75
|
-
const chunks = [];
|
|
76
|
-
for await (const chunk of process.stdin) {
|
|
77
|
-
chunks.push(chunk);
|
|
78
|
-
}
|
|
79
|
-
const raw = Buffer.concat(chunks).toString("utf-8");
|
|
80
|
-
// Check .nex.toml kill switch
|
|
81
|
-
if (!isHookEnabled("capture")) {
|
|
82
|
-
process.stdout.write("{}");
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
14
|
+
const raw = await readStdin();
|
|
85
15
|
let input;
|
|
86
16
|
try {
|
|
87
17
|
input = JSON.parse(raw);
|
|
@@ -90,40 +20,9 @@ async function main() {
|
|
|
90
20
|
process.stdout.write("{}");
|
|
91
21
|
return;
|
|
92
22
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
catch {
|
|
98
|
-
process.stdout.write("{}");
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
const client = new NexClient(cfg.apiKey, cfg.baseUrl);
|
|
102
|
-
// --- Conversation capture (existing behavior) ---
|
|
103
|
-
const message = input.last_assistant_message?.trim();
|
|
104
|
-
if (message) {
|
|
105
|
-
const filterResult = captureFilter(message);
|
|
106
|
-
if (!filterResult.skipped) {
|
|
107
|
-
if (rateLimiter.canProceed()) {
|
|
108
|
-
try {
|
|
109
|
-
await client.ingest(filterResult.text, "claude-code-conversation", INGEST_TIMEOUT_MS);
|
|
110
|
-
}
|
|
111
|
-
catch (err) {
|
|
112
|
-
process.stderr.write(`[nex-capture] Ingest failed: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
else {
|
|
116
|
-
process.stderr.write("[nex-capture] Rate limited — skipping conversation ingest\n");
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
// --- Plan file ingestion ---
|
|
121
|
-
try {
|
|
122
|
-
await ingestPlanFiles(client);
|
|
123
|
-
}
|
|
124
|
-
catch (err) {
|
|
125
|
-
process.stderr.write(`[nex-capture] Plan file scan error: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
126
|
-
}
|
|
23
|
+
await doCapture({
|
|
24
|
+
message: input.last_assistant_message ?? "",
|
|
25
|
+
});
|
|
127
26
|
process.stdout.write("{}");
|
|
128
27
|
}
|
|
129
28
|
catch (err) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auto-capture.js","sourceRoot":"","sources":["../../src/plugin/auto-capture.ts"],"names":[],"mappings":";AACA;;;;;;;;GAQG;AAEH,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"auto-capture.js","sourceRoot":"","sources":["../../src/plugin/auto-capture.ts"],"names":[],"mappings":";AACA;;;;;;;;GAQG;AAEH,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAOnD,KAAK,UAAU,IAAI;IACjB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,SAAS,EAAE,CAAC;QAE9B,IAAI,KAAgB,CAAC;QACrB,IAAI,CAAC;YACH,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAc,CAAC;QACvC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAC3B,OAAO;QACT,CAAC;QAED,MAAM,SAAS,CAAC;YACd,OAAO,EAAE,KAAK,CAAC,sBAAsB,IAAI,EAAE;SAC5C,CAAC,CAAC;QAEH,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,mCAAmC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CACxF,CAAC;QACF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC"}
|
|
@@ -8,31 +8,10 @@
|
|
|
8
8
|
*
|
|
9
9
|
* On ANY error: outputs {} and exits 0 (graceful degradation).
|
|
10
10
|
*/
|
|
11
|
-
import {
|
|
12
|
-
import { NexClient } from "./nex-client.js";
|
|
13
|
-
import { formatNexContext } from "./context-format.js";
|
|
14
|
-
import { SessionStore } from "./session-store.js";
|
|
15
|
-
import { shouldRecall, recordRecall } from "./recall-filter.js";
|
|
16
|
-
const sessions = new SessionStore();
|
|
17
|
-
/**
|
|
18
|
-
* Check if this is the first prompt for this session.
|
|
19
|
-
* A session with no stored Nex session ID is considered "first prompt"
|
|
20
|
-
* (SessionStart may have already set one, but that's fine — it means
|
|
21
|
-
* baseline context was loaded, and first user prompt still gets recall).
|
|
22
|
-
*/
|
|
23
|
-
function isFirstPrompt(sessionKey) {
|
|
24
|
-
if (!sessionKey)
|
|
25
|
-
return true;
|
|
26
|
-
return !sessions.get(sessionKey);
|
|
27
|
-
}
|
|
11
|
+
import { doRecall, readStdin, claudeCodeOutput } from "./shared.js";
|
|
28
12
|
async function main() {
|
|
29
13
|
try {
|
|
30
|
-
|
|
31
|
-
const chunks = [];
|
|
32
|
-
for await (const chunk of process.stdin) {
|
|
33
|
-
chunks.push(chunk);
|
|
34
|
-
}
|
|
35
|
-
const raw = Buffer.concat(chunks).toString("utf-8");
|
|
14
|
+
const raw = await readStdin();
|
|
36
15
|
let input;
|
|
37
16
|
try {
|
|
38
17
|
input = JSON.parse(raw);
|
|
@@ -42,65 +21,12 @@ async function main() {
|
|
|
42
21
|
process.stdout.write("{}");
|
|
43
22
|
return;
|
|
44
23
|
}
|
|
45
|
-
|
|
46
|
-
if (!
|
|
47
|
-
process.stdout.write("{}");
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
const prompt = input.prompt?.trim();
|
|
51
|
-
if (!prompt || prompt.length < 5) {
|
|
52
|
-
process.stdout.write("{}");
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
// Skip slash commands
|
|
56
|
-
if (prompt.startsWith("/")) {
|
|
57
|
-
process.stdout.write("{}");
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
// --- Recall filter: decide if this prompt needs memory recall ---
|
|
61
|
-
const decision = shouldRecall(prompt, isFirstPrompt(input.session_id));
|
|
62
|
-
if (!decision.shouldRecall) {
|
|
24
|
+
const result = await doRecall(input.prompt ?? "", input.session_id);
|
|
25
|
+
if (!result) {
|
|
63
26
|
process.stdout.write("{}");
|
|
64
27
|
return;
|
|
65
28
|
}
|
|
66
|
-
|
|
67
|
-
try {
|
|
68
|
-
cfg = loadConfig();
|
|
69
|
-
}
|
|
70
|
-
catch (err) {
|
|
71
|
-
process.stderr.write(`[nex-recall] Config error: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
72
|
-
process.stdout.write("{}");
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
const client = new NexClient(cfg.apiKey, cfg.baseUrl);
|
|
76
|
-
// Resolve session ID for multi-turn continuity
|
|
77
|
-
const sessionKey = input.session_id;
|
|
78
|
-
const nexSessionId = sessionKey ? sessions.get(sessionKey) : undefined;
|
|
79
|
-
const result = await client.ask(prompt, nexSessionId, 10_000);
|
|
80
|
-
if (!result.answer) {
|
|
81
|
-
process.stdout.write("{}");
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
// Store session ID for future turns
|
|
85
|
-
if (result.session_id && sessionKey) {
|
|
86
|
-
sessions.set(sessionKey, result.session_id);
|
|
87
|
-
}
|
|
88
|
-
// Record successful recall for debounce
|
|
89
|
-
recordRecall(result.session_id);
|
|
90
|
-
const entityCount = result.entity_references?.length ?? 0;
|
|
91
|
-
const context = formatNexContext({
|
|
92
|
-
answer: result.answer,
|
|
93
|
-
entityCount,
|
|
94
|
-
sessionId: result.session_id,
|
|
95
|
-
});
|
|
96
|
-
// Use hookSpecificOutput for discrete context injection (not shown in transcript)
|
|
97
|
-
const output = JSON.stringify({
|
|
98
|
-
hookSpecificOutput: {
|
|
99
|
-
hookEventName: "UserPromptSubmit",
|
|
100
|
-
additionalContext: context,
|
|
101
|
-
},
|
|
102
|
-
});
|
|
103
|
-
process.stdout.write(output);
|
|
29
|
+
process.stdout.write(claudeCodeOutput("UserPromptSubmit", result.context));
|
|
104
30
|
}
|
|
105
31
|
catch (err) {
|
|
106
32
|
process.stderr.write(`[nex-recall] Unexpected error: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auto-recall.js","sourceRoot":"","sources":["../../src/plugin/auto-recall.ts"],"names":[],"mappings":";AACA;;;;;;;;GAQG;AAEH,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"auto-recall.js","sourceRoot":"","sources":["../../src/plugin/auto-recall.ts"],"names":[],"mappings":";AACA;;;;;;;;GAQG;AAEH,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAOpE,KAAK,UAAU,IAAI;IACjB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,SAAS,EAAE,CAAC;QAE9B,IAAI,KAAgB,CAAC;QACrB,IAAI,CAAC;YACH,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAc,CAAC;QACvC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,2CAA2C,CAAC,CAAC;YAClE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAC3B,OAAO;QACT,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,KAAK,CAAC,MAAM,IAAI,EAAE,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;QAEpE,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAC3B,OAAO;QACT,CAAC;QAED,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;IAC7E,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,kCAAkC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CACvF,CAAC;QACF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC"}
|
|
@@ -6,9 +6,6 @@
|
|
|
6
6
|
* a baseline context summary and injects it so the agent "already knows"
|
|
7
7
|
* relevant business context from the first message.
|
|
8
8
|
*
|
|
9
|
-
* On startup/clear: also scans project files and ingests changed ones.
|
|
10
|
-
* On compact/resume: skips file scan (files already ingested, just re-query summary).
|
|
11
|
-
*
|
|
12
9
|
* On ANY error: outputs {} and exits 0 (graceful degradation).
|
|
13
10
|
*/
|
|
14
11
|
export {};
|
|
@@ -6,36 +6,12 @@
|
|
|
6
6
|
* a baseline context summary and injects it so the agent "already knows"
|
|
7
7
|
* relevant business context from the first message.
|
|
8
8
|
*
|
|
9
|
-
* On startup/clear: also scans project files and ingests changed ones.
|
|
10
|
-
* On compact/resume: skips file scan (files already ingested, just re-query summary).
|
|
11
|
-
*
|
|
12
9
|
* On ANY error: outputs {} and exits 0 (graceful degradation).
|
|
13
10
|
*/
|
|
14
|
-
import {
|
|
15
|
-
import { NexClient } from "./nex-client.js";
|
|
16
|
-
import { RateLimiter } from "./rate-limiter.js";
|
|
17
|
-
import { formatNexContext } from "./context-format.js";
|
|
18
|
-
import { SessionStore } from "./session-store.js";
|
|
19
|
-
import { recordRecall } from "./recall-filter.js";
|
|
20
|
-
import { scanAndIngest } from "./file-scanner.js";
|
|
21
|
-
import { ingestContextFiles } from "./context-files.js";
|
|
22
|
-
import { fileURLToPath } from "node:url";
|
|
23
|
-
import { dirname, join } from "node:path";
|
|
24
|
-
const sessions = new SessionStore();
|
|
25
|
-
const SESSION_START_QUERY = "Summarize the key active context, recent interactions, and important updates for this user.";
|
|
11
|
+
import { doSessionStart, readStdin, claudeCodeOutput } from "./shared.js";
|
|
26
12
|
async function main() {
|
|
27
13
|
try {
|
|
28
|
-
|
|
29
|
-
const chunks = [];
|
|
30
|
-
for await (const chunk of process.stdin) {
|
|
31
|
-
chunks.push(chunk);
|
|
32
|
-
}
|
|
33
|
-
const raw = Buffer.concat(chunks).toString("utf-8");
|
|
34
|
-
// Check .nex.toml kill switch
|
|
35
|
-
if (!isHookEnabled("session_start")) {
|
|
36
|
-
process.stdout.write("{}");
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
14
|
+
const raw = await readStdin();
|
|
39
15
|
let input = {};
|
|
40
16
|
try {
|
|
41
17
|
input = JSON.parse(raw);
|
|
@@ -43,99 +19,17 @@ async function main() {
|
|
|
43
19
|
catch {
|
|
44
20
|
process.stderr.write("[nex-session-start] Failed to parse stdin JSON, continuing with defaults\n");
|
|
45
21
|
}
|
|
46
|
-
let cfg;
|
|
47
|
-
try {
|
|
48
|
-
cfg = loadConfig();
|
|
49
|
-
}
|
|
50
|
-
catch (err) {
|
|
51
|
-
if (err instanceof ConfigError) {
|
|
52
|
-
// No API key — inject registration prompt so Claude handles it
|
|
53
|
-
const pluginDir = dirname(fileURLToPath(import.meta.url));
|
|
54
|
-
const registerScript = join(pluginDir, "auto-register.js");
|
|
55
|
-
const registrationPrompt = [
|
|
56
|
-
"[Nex Memory Plugin] No API key configured. Registration is required to enable persistent memory.",
|
|
57
|
-
"",
|
|
58
|
-
"IMPORTANT: Before proceeding with the user's request, you must set up Nex memory:",
|
|
59
|
-
"1. Ask the user for their email address to register for a Nex account",
|
|
60
|
-
"2. Run the registration command: node " + registerScript + " <email> [name] [company]",
|
|
61
|
-
"3. Once registration succeeds, the API key is saved automatically — all memory features activate immediately",
|
|
62
|
-
"",
|
|
63
|
-
"Keep it brief — just ask for the email and register. No restart needed after registration.",
|
|
64
|
-
].join("\n");
|
|
65
|
-
const output = JSON.stringify({
|
|
66
|
-
hookSpecificOutput: {
|
|
67
|
-
hookEventName: "SessionStart",
|
|
68
|
-
additionalContext: registrationPrompt,
|
|
69
|
-
},
|
|
70
|
-
});
|
|
71
|
-
process.stdout.write(output);
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
process.stderr.write(`[nex-session-start] Config error: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
75
|
-
process.stdout.write("{}");
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
const client = new NexClient(cfg.apiKey, cfg.baseUrl);
|
|
79
|
-
const contextParts = [];
|
|
80
|
-
// --- File scan on startup or clear ---
|
|
81
22
|
const source = input.source ?? "startup";
|
|
82
|
-
const
|
|
83
|
-
if (
|
|
84
|
-
const rateLimiter = new RateLimiter();
|
|
85
|
-
const cwd = process.cwd();
|
|
86
|
-
// --- Ingest CLAUDE.md + memory files (highest priority) ---
|
|
87
|
-
try {
|
|
88
|
-
const ctxResult = await ingestContextFiles(client, rateLimiter, cwd);
|
|
89
|
-
if (ctxResult.ingested > 0) {
|
|
90
|
-
contextParts.push(`[Context files: ${ctxResult.ingested} ingested (${ctxResult.files.join(", ")})]`);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
catch (err) {
|
|
94
|
-
process.stderr.write(`[nex-session-start] Context files error: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
95
|
-
}
|
|
96
|
-
// --- Project file scan ---
|
|
97
|
-
try {
|
|
98
|
-
const scanConfig = loadScanConfig();
|
|
99
|
-
if (scanConfig.enabled) {
|
|
100
|
-
const scanResult = await scanAndIngest(client, rateLimiter, cwd, scanConfig);
|
|
101
|
-
if (scanResult.ingested > 0) {
|
|
102
|
-
contextParts.push(`[File scan: ${scanResult.ingested} file${scanResult.ingested === 1 ? "" : "s"} ingested, ${scanResult.scanned} scanned]`);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
catch (err) {
|
|
107
|
-
process.stderr.write(`[nex-session-start] File scan error: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
// --- Nex context query ---
|
|
111
|
-
const result = await client.ask(SESSION_START_QUERY, undefined, 10_000);
|
|
112
|
-
if (!result.answer && contextParts.length === 0) {
|
|
23
|
+
const result = await doSessionStart(source, input.session_id);
|
|
24
|
+
if (!result) {
|
|
113
25
|
process.stdout.write("{}");
|
|
114
26
|
return;
|
|
115
27
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
28
|
+
if (result.registrationPrompt) {
|
|
29
|
+
process.stdout.write(claudeCodeOutput("SessionStart", result.registrationPrompt));
|
|
30
|
+
return;
|
|
119
31
|
}
|
|
120
|
-
|
|
121
|
-
recordRecall(result.session_id);
|
|
122
|
-
const entityCount = result.entity_references?.length ?? 0;
|
|
123
|
-
const context = formatNexContext({
|
|
124
|
-
answer: result.answer,
|
|
125
|
-
entityCount,
|
|
126
|
-
sessionId: result.session_id,
|
|
127
|
-
});
|
|
128
|
-
// Append scan summary if any
|
|
129
|
-
const fullContext = contextParts.length > 0
|
|
130
|
-
? `${context}\n${contextParts.join("\n")}`
|
|
131
|
-
: context;
|
|
132
|
-
const output = JSON.stringify({
|
|
133
|
-
hookSpecificOutput: {
|
|
134
|
-
hookEventName: "SessionStart",
|
|
135
|
-
additionalContext: fullContext,
|
|
136
|
-
},
|
|
137
|
-
});
|
|
138
|
-
process.stdout.write(output);
|
|
32
|
+
process.stdout.write(claudeCodeOutput("SessionStart", result.context));
|
|
139
33
|
}
|
|
140
34
|
catch (err) {
|
|
141
35
|
process.stderr.write(`[nex-session-start] Unexpected error: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auto-session-start.js","sourceRoot":"","sources":["../../src/plugin/auto-session-start.ts"],"names":[],"mappings":";AACA
|
|
1
|
+
{"version":3,"file":"auto-session-start.js","sourceRoot":"","sources":["../../src/plugin/auto-session-start.ts"],"names":[],"mappings":";AACA;;;;;;;;GAQG;AAEH,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAO1E,KAAK,UAAU,IAAI;IACjB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,SAAS,EAAE,CAAC;QAE9B,IAAI,KAAK,GAAc,EAAE,CAAC;QAC1B,IAAI,CAAC;YACH,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAc,CAAC;QACvC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,4EAA4E,CAAC,CAAC;QACrG,CAAC;QAED,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,IAAI,SAAS,CAAC;QACzC,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,MAAM,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;QAE9D,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAC3B,OAAO;QACT,CAAC;QAED,IAAI,MAAM,CAAC,kBAAkB,EAAE,CAAC;YAC9B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,gBAAgB,CAAC,cAAc,EAAE,MAAM,CAAC,kBAAkB,CAAC,CAAC,CAAC;YAClF,OAAO;QACT,CAAC;QAED,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,gBAAgB,CAAC,cAAc,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;IACzE,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,yCAAyC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAC9F,CAAC;QACF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared hook logic — platform-agnostic recall, capture, and session-start.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from auto-recall.ts, auto-capture.ts, auto-session-start.ts
|
|
5
|
+
* so adapters for Cursor, Windsurf, Cline, etc. can reuse the same logic
|
|
6
|
+
* without duplicating filter/client/format code.
|
|
7
|
+
*/
|
|
8
|
+
export interface RecallResult {
|
|
9
|
+
context: string;
|
|
10
|
+
nexSessionId?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface CaptureInput {
|
|
13
|
+
message: string;
|
|
14
|
+
planDir?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface SessionStartResult {
|
|
17
|
+
context: string;
|
|
18
|
+
nexSessionId?: string;
|
|
19
|
+
registrationPrompt?: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Run recall logic: filter prompt → query Nex /ask → return formatted context.
|
|
23
|
+
* Returns null if recall is skipped or fails.
|
|
24
|
+
*/
|
|
25
|
+
export declare function doRecall(prompt: string, sessionKey?: string): Promise<RecallResult | null>;
|
|
26
|
+
/**
|
|
27
|
+
* Run capture logic: filter message → ingest to Nex + scan plan files.
|
|
28
|
+
*/
|
|
29
|
+
export declare function doCapture(input: CaptureInput): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Run session-start logic: scan files → query Nex baseline → return context.
|
|
32
|
+
* Returns null if disabled. Returns registrationPrompt if no API key.
|
|
33
|
+
*/
|
|
34
|
+
export declare function doSessionStart(source: string, sessionKey?: string): Promise<SessionStartResult | null>;
|
|
35
|
+
/** Read stdin as a string (shared across all hook scripts). */
|
|
36
|
+
export declare function readStdin(): Promise<string>;
|
|
37
|
+
/** Wrap output in Claude Code hookSpecificOutput format. */
|
|
38
|
+
export declare function claudeCodeOutput(hookEventName: string, additionalContext: string): string;
|