@nex-ai/nex 0.1.8 → 0.1.9

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 (59) hide show
  1. package/README.md +5 -1
  2. package/dist/commands/setup.d.ts +2 -1
  3. package/dist/commands/setup.js +51 -9
  4. package/dist/commands/setup.js.map +1 -1
  5. package/dist/lib/installers.d.ts +2 -2
  6. package/dist/lib/installers.js +55 -64
  7. package/dist/lib/installers.js.map +1 -1
  8. package/dist/plugin/auto-capture.d.ts +11 -0
  9. package/dist/plugin/auto-capture.js +135 -0
  10. package/dist/plugin/auto-capture.js.map +1 -0
  11. package/dist/plugin/auto-recall.d.ts +11 -0
  12. package/dist/plugin/auto-recall.js +111 -0
  13. package/dist/plugin/auto-recall.js.map +1 -0
  14. package/dist/plugin/auto-register.d.ts +10 -0
  15. package/dist/plugin/auto-register.js +53 -0
  16. package/dist/plugin/auto-register.js.map +1 -0
  17. package/dist/plugin/auto-scan.d.ts +10 -0
  18. package/dist/plugin/auto-scan.js +49 -0
  19. package/dist/plugin/auto-scan.js.map +1 -0
  20. package/dist/plugin/auto-session-start.d.ts +14 -0
  21. package/dist/plugin/auto-session-start.js +146 -0
  22. package/dist/plugin/auto-session-start.js.map +1 -0
  23. package/dist/plugin/capture-filter.d.ts +21 -0
  24. package/dist/plugin/capture-filter.js +32 -0
  25. package/dist/plugin/capture-filter.js.map +1 -0
  26. package/dist/plugin/config.d.ts +54 -0
  27. package/dist/plugin/config.js +166 -0
  28. package/dist/plugin/config.js.map +1 -0
  29. package/dist/plugin/context-files.d.ts +23 -0
  30. package/dist/plugin/context-files.js +103 -0
  31. package/dist/plugin/context-files.js.map +1 -0
  32. package/dist/plugin/context-format.d.ts +18 -0
  33. package/dist/plugin/context-format.js +34 -0
  34. package/dist/plugin/context-format.js.map +1 -0
  35. package/dist/plugin/file-manifest.d.ts +21 -0
  36. package/dist/plugin/file-manifest.js +48 -0
  37. package/dist/plugin/file-manifest.js.map +1 -0
  38. package/dist/plugin/file-scanner.d.ts +19 -0
  39. package/dist/plugin/file-scanner.js +90 -0
  40. package/dist/plugin/file-scanner.js.map +1 -0
  41. package/dist/plugin/nex-client.d.ts +50 -0
  42. package/dist/plugin/nex-client.js +129 -0
  43. package/dist/plugin/nex-client.js.map +1 -0
  44. package/dist/plugin/rate-limiter.d.ts +26 -0
  45. package/dist/plugin/rate-limiter.js +65 -0
  46. package/dist/plugin/rate-limiter.js.map +1 -0
  47. package/dist/plugin/recall-filter.d.ts +23 -0
  48. package/dist/plugin/recall-filter.js +94 -0
  49. package/dist/plugin/recall-filter.js.map +1 -0
  50. package/dist/plugin/session-store.d.ts +23 -0
  51. package/dist/plugin/session-store.js +74 -0
  52. package/dist/plugin/session-store.js.map +1 -0
  53. package/package.json +2 -1
  54. package/plugin-commands/entities.md +18 -0
  55. package/plugin-commands/integrate.md +34 -0
  56. package/plugin-commands/recall.md +5 -0
  57. package/plugin-commands/register.md +28 -0
  58. package/plugin-commands/remember.md +5 -0
  59. package/plugin-commands/scan.md +8 -0
@@ -0,0 +1,166 @@
1
+ /**
2
+ * Plugin configuration — reads from environment variables,
3
+ * with fallback to ~/.nex-mcp.json (shared with MCP server).
4
+ */
5
+ import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
6
+ import { join, dirname } from "node:path";
7
+ import { homedir } from "node:os";
8
+ export class ConfigError extends Error {
9
+ constructor(message) {
10
+ super(message);
11
+ this.name = "ConfigError";
12
+ }
13
+ }
14
+ /** Shared config file with MCP server — stores registration data. */
15
+ const MCP_CONFIG_PATH = join(homedir(), ".nex-mcp.json");
16
+ export { MCP_CONFIG_PATH };
17
+ /** Read ~/.nex-mcp.json (shared with MCP server registration). */
18
+ export function loadMcpConfig() {
19
+ try {
20
+ const raw = readFileSync(MCP_CONFIG_PATH, "utf-8");
21
+ return JSON.parse(raw);
22
+ }
23
+ catch {
24
+ return {};
25
+ }
26
+ }
27
+ /** Write registration data to ~/.nex-mcp.json. */
28
+ export function persistRegistration(data) {
29
+ const existing = loadMcpConfig();
30
+ if (typeof data.api_key === "string")
31
+ existing.api_key = data.api_key;
32
+ if (typeof data.workspace_id === "string" || typeof data.workspace_id === "number") {
33
+ existing.workspace_id = String(data.workspace_id);
34
+ }
35
+ if (typeof data.workspace_slug === "string")
36
+ existing.workspace_slug = data.workspace_slug;
37
+ mkdirSync(dirname(MCP_CONFIG_PATH), { recursive: true });
38
+ writeFileSync(MCP_CONFIG_PATH, JSON.stringify(existing, null, 2) + "\n", "utf-8");
39
+ }
40
+ /**
41
+ * Load config from environment variables, with fallback to ~/.nex-mcp.json.
42
+ *
43
+ * Priority: NEX_API_KEY env > ~/.nex-mcp.json api_key
44
+ * If neither is set, throws ConfigError with registration instructions.
45
+ */
46
+ export function loadConfig() {
47
+ let apiKey = process.env.NEX_API_KEY;
48
+ if (!apiKey) {
49
+ // Fallback to shared MCP config
50
+ const mcpConfig = loadMcpConfig();
51
+ apiKey = mcpConfig.api_key;
52
+ }
53
+ if (!apiKey) {
54
+ throw new ConfigError("No API key found. Set NEX_API_KEY or run /register to create an account.");
55
+ }
56
+ let baseUrl = process.env.NEX_API_BASE_URL ?? "https://app.nex.ai";
57
+ // Strip trailing slash
58
+ baseUrl = baseUrl.replace(/\/+$/, "");
59
+ return { apiKey, baseUrl };
60
+ }
61
+ /**
62
+ * Load base URL without requiring an API key.
63
+ * Used for registration (which doesn't need auth).
64
+ */
65
+ export function loadBaseUrl() {
66
+ let baseUrl = process.env.NEX_API_BASE_URL ?? "https://app.nex.ai";
67
+ return baseUrl.replace(/\/+$/, "");
68
+ }
69
+ function parseTomlValue(raw) {
70
+ if (raw === "true")
71
+ return true;
72
+ if (raw === "false")
73
+ return false;
74
+ if (/^-?\d+(\.\d+)?$/.test(raw))
75
+ return Number(raw);
76
+ if ((raw.startsWith('"') && raw.endsWith('"')) || (raw.startsWith("'") && raw.endsWith("'"))) {
77
+ return raw.slice(1, -1);
78
+ }
79
+ if (raw.startsWith("[") && raw.endsWith("]")) {
80
+ const inner = raw.slice(1, -1).trim();
81
+ if (!inner)
82
+ return [];
83
+ return inner.split(",").map((item) => parseTomlValue(item.trim()));
84
+ }
85
+ return raw;
86
+ }
87
+ function parseToml(content) {
88
+ const obj = {};
89
+ const currentSection = [];
90
+ for (const line of content.split("\n")) {
91
+ const trimmed = line.trim();
92
+ if (!trimmed || trimmed.startsWith("#"))
93
+ continue;
94
+ const sectionMatch = trimmed.match(/^\[([^\]]+)\]$/);
95
+ if (sectionMatch) {
96
+ currentSection.length = 0;
97
+ currentSection.push(...sectionMatch[1].split("."));
98
+ continue;
99
+ }
100
+ const kvMatch = trimmed.match(/^([a-zA-Z_][a-zA-Z0-9_.]*)\s*=\s*(.+)$/);
101
+ if (!kvMatch)
102
+ continue;
103
+ const path = [...currentSection, ...kvMatch[1].trim().split(".")];
104
+ const value = parseTomlValue(kvMatch[2].trim());
105
+ let target = obj;
106
+ for (let i = 0; i < path.length - 1; i++) {
107
+ if (!(path[i] in target) || typeof target[path[i]] !== "object") {
108
+ target[path[i]] = {};
109
+ }
110
+ target = target[path[i]];
111
+ }
112
+ target[path[path.length - 1]] = value;
113
+ }
114
+ return obj;
115
+ }
116
+ /**
117
+ * Check if a specific hook is enabled in .nex.toml.
118
+ * Returns true by default (hooks are opt-out).
119
+ */
120
+ export function isHookEnabled(hookName) {
121
+ try {
122
+ const tomlPath = join(process.cwd(), ".nex.toml");
123
+ const content = readFileSync(tomlPath, "utf-8");
124
+ const config = parseToml(content);
125
+ // Master kill switch
126
+ if (config.hooks?.enabled === false)
127
+ return false;
128
+ // Per-hook setting
129
+ const hookConfig = config.hooks?.[hookName];
130
+ if (hookConfig?.enabled === false)
131
+ return false;
132
+ return true;
133
+ }
134
+ catch {
135
+ return true; // No .nex.toml or read error → hooks enabled by default
136
+ }
137
+ }
138
+ const DEFAULT_SCAN_EXTENSIONS = [".md", ".txt", ".csv", ".json", ".yaml", ".yml"];
139
+ const DEFAULT_IGNORE_DIRS = [
140
+ "node_modules", ".git", "dist", "build", ".next", "__pycache__",
141
+ "vendor", ".venv", ".claude", "coverage", ".turbo", ".cache",
142
+ ];
143
+ /**
144
+ * Load scan config from NEX_SCAN_* environment variables.
145
+ * All fields have sensible defaults; NEX_SCAN_ENABLED=false is the kill switch.
146
+ */
147
+ export function loadScanConfig() {
148
+ const enabled = (process.env.NEX_SCAN_ENABLED ?? "true").toLowerCase() !== "false";
149
+ const extensions = process.env.NEX_SCAN_EXTENSIONS
150
+ ? process.env.NEX_SCAN_EXTENSIONS.split(",").map((e) => e.trim())
151
+ : DEFAULT_SCAN_EXTENSIONS;
152
+ const maxFileSize = process.env.NEX_SCAN_MAX_FILE_SIZE
153
+ ? parseInt(process.env.NEX_SCAN_MAX_FILE_SIZE, 10)
154
+ : 100_000;
155
+ const maxFilesPerScan = process.env.NEX_SCAN_MAX_FILES
156
+ ? parseInt(process.env.NEX_SCAN_MAX_FILES, 10)
157
+ : 5;
158
+ const scanDepth = process.env.NEX_SCAN_DEPTH
159
+ ? parseInt(process.env.NEX_SCAN_DEPTH, 10)
160
+ : 2;
161
+ const ignoreDirs = process.env.NEX_SCAN_IGNORE_DIRS
162
+ ? process.env.NEX_SCAN_IGNORE_DIRS.split(",").map((d) => d.trim())
163
+ : DEFAULT_IGNORE_DIRS;
164
+ return { extensions, maxFileSize, maxFilesPerScan, scanDepth, ignoreDirs, enabled };
165
+ }
166
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/plugin/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Ingests Claude Code context files (CLAUDE.md + memory files) into Nex.
3
+ *
4
+ * Reads from both global and project-level locations:
5
+ * - ~/.claude/CLAUDE.md (global instructions)
6
+ * - {cwd}/CLAUDE.md (project instructions)
7
+ * - ~/.claude/projects/{project-key}/memory/MEMORY.md (auto-memory)
8
+ * - ~/.claude/projects/{project-key}/memory/*.md (topic memory files)
9
+ *
10
+ * Uses the file manifest for change detection — unchanged files are skipped.
11
+ */
12
+ import type { NexClient } from "./nex-client.js";
13
+ import type { RateLimiter } from "./rate-limiter.js";
14
+ export interface ContextFilesResult {
15
+ ingested: number;
16
+ skipped: number;
17
+ errors: number;
18
+ files: string[];
19
+ }
20
+ /**
21
+ * Ingest changed CLAUDE.md and memory files into Nex.
22
+ */
23
+ export declare function ingestContextFiles(client: NexClient, rateLimiter: RateLimiter, cwd: string): Promise<ContextFilesResult>;
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Ingests Claude Code context files (CLAUDE.md + memory files) into Nex.
3
+ *
4
+ * Reads from both global and project-level locations:
5
+ * - ~/.claude/CLAUDE.md (global instructions)
6
+ * - {cwd}/CLAUDE.md (project instructions)
7
+ * - ~/.claude/projects/{project-key}/memory/MEMORY.md (auto-memory)
8
+ * - ~/.claude/projects/{project-key}/memory/*.md (topic memory files)
9
+ *
10
+ * Uses the file manifest for change detection — unchanged files are skipped.
11
+ */
12
+ import { existsSync, readdirSync, statSync, readFileSync } from "node:fs";
13
+ import { join, extname, basename } from "node:path";
14
+ import { homedir } from "node:os";
15
+ import { readManifest, writeManifest, isChanged, markIngested } from "./file-manifest.js";
16
+ const CLAUDE_DIR = join(homedir(), ".claude");
17
+ const INGEST_TIMEOUT_MS = 10_000;
18
+ const MAX_FILE_SIZE = 100_000;
19
+ /**
20
+ * Derive the Claude Code project key from a cwd path.
21
+ * e.g. /Users/foo/bar → -Users-foo-bar
22
+ */
23
+ function projectKey(cwd) {
24
+ return cwd.replace(/\//g, "-");
25
+ }
26
+ /**
27
+ * Collect all context file paths to check.
28
+ */
29
+ function collectContextFiles(cwd) {
30
+ const files = [];
31
+ const key = projectKey(cwd);
32
+ // 1. Global CLAUDE.md
33
+ const globalClaude = join(CLAUDE_DIR, "CLAUDE.md");
34
+ if (existsSync(globalClaude)) {
35
+ files.push({ path: globalClaude, contextTag: "claude-md:global" });
36
+ }
37
+ // 2. Project CLAUDE.md
38
+ const projectClaude = join(cwd, "CLAUDE.md");
39
+ if (existsSync(projectClaude)) {
40
+ files.push({ path: projectClaude, contextTag: "claude-md:project" });
41
+ }
42
+ // 3. Memory files: ~/.claude/projects/{key}/memory/*.md
43
+ const memoryDir = join(CLAUDE_DIR, "projects", key, "memory");
44
+ if (existsSync(memoryDir)) {
45
+ try {
46
+ const entries = readdirSync(memoryDir, { withFileTypes: true });
47
+ for (const entry of entries) {
48
+ if (!entry.isFile())
49
+ continue;
50
+ if (extname(entry.name).toLowerCase() !== ".md")
51
+ continue;
52
+ const fullPath = join(memoryDir, entry.name);
53
+ const name = basename(entry.name, ".md");
54
+ files.push({ path: fullPath, contextTag: `claude-memory:${name}` });
55
+ }
56
+ }
57
+ catch {
58
+ // memoryDir unreadable — skip silently
59
+ }
60
+ }
61
+ return files;
62
+ }
63
+ /**
64
+ * Ingest changed CLAUDE.md and memory files into Nex.
65
+ */
66
+ export async function ingestContextFiles(client, rateLimiter, cwd) {
67
+ const result = { ingested: 0, skipped: 0, errors: 0, files: [] };
68
+ const manifest = readManifest();
69
+ const candidates = collectContextFiles(cwd);
70
+ let dirty = false;
71
+ for (const { path, contextTag } of candidates) {
72
+ try {
73
+ const stat = statSync(path);
74
+ if (!isChanged(path, stat, manifest)) {
75
+ result.skipped++;
76
+ continue;
77
+ }
78
+ if (!rateLimiter.canProceed()) {
79
+ process.stderr.write("[nex-context-files] Rate limited — stopping context file ingest\n");
80
+ result.skipped += candidates.length - result.ingested - result.skipped - result.errors;
81
+ break;
82
+ }
83
+ let content = readFileSync(path, "utf-8");
84
+ if (content.length > MAX_FILE_SIZE) {
85
+ content = content.slice(0, MAX_FILE_SIZE) + "\n[...truncated]";
86
+ }
87
+ await client.ingest(content, contextTag, INGEST_TIMEOUT_MS);
88
+ markIngested(path, stat, contextTag, manifest);
89
+ result.ingested++;
90
+ result.files.push(contextTag);
91
+ dirty = true;
92
+ }
93
+ catch (err) {
94
+ process.stderr.write(`[nex-context-files] Failed to ingest ${contextTag}: ${err instanceof Error ? err.message : String(err)}\n`);
95
+ result.errors++;
96
+ }
97
+ }
98
+ if (dirty) {
99
+ writeManifest(manifest);
100
+ }
101
+ return result;
102
+ }
103
+ //# sourceMappingURL=context-files.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-files.js","sourceRoot":"","sources":["../../src/plugin/context-files.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * XML context formatting and stripping for recall injection.
3
+ * Wraps Nex answers in <nex-context> tags and strips them before capture.
4
+ */
5
+ export interface NexRecallResult {
6
+ answer: string;
7
+ entityCount: number;
8
+ sessionId?: string;
9
+ }
10
+ /**
11
+ * Format a Nex /ask response as an XML block for context injection.
12
+ */
13
+ export declare function formatNexContext(result: NexRecallResult): string;
14
+ /**
15
+ * Strip all <nex-context>...</nex-context> blocks from text.
16
+ * Also handles unclosed tags (strips from open tag to end of text).
17
+ */
18
+ export declare function stripNexContext(text: string): string;
@@ -0,0 +1,34 @@
1
+ /**
2
+ * XML context formatting and stripping for recall injection.
3
+ * Wraps Nex answers in <nex-context> tags and strips them before capture.
4
+ */
5
+ const OPEN_TAG = "<nex-context>";
6
+ const CLOSE_TAG = "</nex-context>";
7
+ /**
8
+ * Format a Nex /ask response as an XML block for context injection.
9
+ */
10
+ export function formatNexContext(result) {
11
+ const parts = [
12
+ OPEN_TAG,
13
+ "The following is relevant context from the user's knowledge base. Use it to inform your response, but do not mention this block directly.",
14
+ ];
15
+ if (result.entityCount > 0) {
16
+ parts.push(`[${result.entityCount} related entities found]`);
17
+ }
18
+ parts.push("");
19
+ parts.push(result.answer);
20
+ parts.push(CLOSE_TAG);
21
+ return parts.join("\n");
22
+ }
23
+ /**
24
+ * Strip all <nex-context>...</nex-context> blocks from text.
25
+ * Also handles unclosed tags (strips from open tag to end of text).
26
+ */
27
+ export function stripNexContext(text) {
28
+ // First: strip complete blocks
29
+ let result = text.replace(/<nex-context>[\s\S]*?<\/nex-context>/g, "");
30
+ // Then: strip unclosed tags (open tag without matching close)
31
+ result = result.replace(/<nex-context>[\s\S]*/g, "");
32
+ return result.trim();
33
+ }
34
+ //# sourceMappingURL=context-format.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-format.js","sourceRoot":"","sources":["../../src/plugin/context-format.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,QAAQ,GAAG,eAAe,CAAC;AACjC,MAAM,SAAS,GAAG,gBAAgB,CAAC;AAQnC;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAuB;IACtD,MAAM,KAAK,GAAa;QACtB,QAAQ;QACR,2IAA2I;KAC5I,CAAC;IAEF,IAAI,MAAM,CAAC,WAAW,GAAG,CAAC,EAAE,CAAC;QAC3B,KAAK,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,WAAW,0BAA0B,CAAC,CAAC;IAC/D,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACf,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC1B,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAEtB,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,+BAA+B;IAC/B,IAAI,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,uCAAuC,EAAE,EAAE,CAAC,CAAC;IACvE,8DAA8D;IAC9D,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,uBAAuB,EAAE,EAAE,CAAC,CAAC;IACrD,OAAO,MAAM,CAAC,IAAI,EAAE,CAAC;AACvB,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Persistent file manifest — tracks which files have been ingested
3
+ * using mtime + size as change detection.
4
+ *
5
+ * Stored at ~/.nex/file-scan-manifest.json. Follows session-store.ts pattern.
6
+ */
7
+ import { type Stats } from "node:fs";
8
+ export interface FileManifestEntry {
9
+ mtime: number;
10
+ size: number;
11
+ ingestedAt: number;
12
+ context: string;
13
+ }
14
+ export interface FileManifest {
15
+ version: 1;
16
+ files: Record<string, FileManifestEntry>;
17
+ }
18
+ export declare function readManifest(): FileManifest;
19
+ export declare function writeManifest(manifest: FileManifest): void;
20
+ export declare function isChanged(path: string, stat: Stats, manifest: FileManifest): boolean;
21
+ export declare function markIngested(path: string, stat: Stats, context: string, manifest: FileManifest): void;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Persistent file manifest — tracks which files have been ingested
3
+ * using mtime + size as change detection.
4
+ *
5
+ * Stored at ~/.nex/file-scan-manifest.json. Follows session-store.ts pattern.
6
+ */
7
+ import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
8
+ import { join } from "node:path";
9
+ import { homedir } from "node:os";
10
+ const DATA_DIR = join(homedir(), ".nex");
11
+ const MANIFEST_PATH = join(DATA_DIR, "file-scan-manifest.json");
12
+ export function readManifest() {
13
+ try {
14
+ const raw = readFileSync(MANIFEST_PATH, "utf-8");
15
+ const data = JSON.parse(raw);
16
+ if (data && data.version === 1 && data.files) {
17
+ return data;
18
+ }
19
+ return { version: 1, files: {} };
20
+ }
21
+ catch {
22
+ return { version: 1, files: {} };
23
+ }
24
+ }
25
+ export function writeManifest(manifest) {
26
+ try {
27
+ mkdirSync(DATA_DIR, { recursive: true });
28
+ writeFileSync(MANIFEST_PATH, JSON.stringify(manifest, null, 2), "utf-8");
29
+ }
30
+ catch {
31
+ // Best-effort — if we can't write, next scan re-ingests
32
+ }
33
+ }
34
+ export function isChanged(path, stat, manifest) {
35
+ const entry = manifest.files[path];
36
+ if (!entry)
37
+ return true;
38
+ return entry.mtime !== stat.mtimeMs || entry.size !== stat.size;
39
+ }
40
+ export function markIngested(path, stat, context, manifest) {
41
+ manifest.files[path] = {
42
+ mtime: stat.mtimeMs,
43
+ size: stat.size,
44
+ ingestedAt: Date.now(),
45
+ context,
46
+ };
47
+ }
48
+ //# sourceMappingURL=file-manifest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-manifest.js","sourceRoot":"","sources":["../../src/plugin/file-manifest.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAc,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAclC,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,MAAM,CAAC,CAAC;AACzC,MAAM,aAAa,GAAG,IAAI,CAAC,QAAQ,EAAE,yBAAyB,CAAC,CAAC;AAEhE,MAAM,UAAU,YAAY;IAC1B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,YAAY,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;QACjD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC7B,IAAI,IAAI,IAAI,IAAI,CAAC,OAAO,KAAK,CAAC,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YAC7C,OAAO,IAAoB,CAAC;QAC9B,CAAC;QACD,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;IACnC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;IACnC,CAAC;AACH,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,QAAsB;IAClD,IAAI,CAAC;QACH,SAAS,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACzC,aAAa,CAAC,aAAa,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;IAC3E,CAAC;IAAC,MAAM,CAAC;QACP,wDAAwD;IAC1D,CAAC;AACH,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,IAAY,EAAE,IAAW,EAAE,QAAsB;IACzE,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACnC,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,OAAO,KAAK,CAAC,KAAK,KAAK,IAAI,CAAC,OAAO,IAAI,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,CAAC;AAClE,CAAC;AAED,MAAM,UAAU,YAAY,CAC1B,IAAY,EACZ,IAAW,EACX,OAAe,EACf,QAAsB;IAEtB,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG;QACrB,KAAK,EAAE,IAAI,CAAC,OAAO;QACnB,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE;QACtB,OAAO;KACR,CAAC;AACJ,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Core file scanner — walks project directories, detects changed files,
3
+ * and ingests them into Nex via the developer API.
4
+ *
5
+ * No new dependencies — uses only Node.js built-ins.
6
+ */
7
+ import type { NexClient } from "./nex-client.js";
8
+ import type { RateLimiter } from "./rate-limiter.js";
9
+ import type { ScanConfig } from "./config.js";
10
+ export interface ScanResult {
11
+ scanned: number;
12
+ ingested: number;
13
+ skipped: number;
14
+ errors: number;
15
+ }
16
+ /**
17
+ * Scan project directory for text files and ingest changed ones into Nex.
18
+ */
19
+ export declare function scanAndIngest(client: NexClient, rateLimiter: RateLimiter, cwd: string, config: ScanConfig): Promise<ScanResult>;
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Core file scanner — walks project directories, detects changed files,
3
+ * and ingests them into Nex via the developer API.
4
+ *
5
+ * No new dependencies — uses only Node.js built-ins.
6
+ */
7
+ import { readdirSync, statSync, readFileSync } from "node:fs";
8
+ import { join, relative, extname } from "node:path";
9
+ import { readManifest, writeManifest, isChanged, markIngested } from "./file-manifest.js";
10
+ /**
11
+ * Recursively collect candidate files up to scanDepth levels.
12
+ */
13
+ function walkDir(dir, cwd, config, depth, results) {
14
+ if (depth > config.scanDepth)
15
+ return;
16
+ let entries;
17
+ try {
18
+ entries = readdirSync(dir, { withFileTypes: true });
19
+ }
20
+ catch {
21
+ return; // Permission denied or missing — skip silently
22
+ }
23
+ for (const entry of entries) {
24
+ const fullPath = join(dir, entry.name);
25
+ if (entry.isDirectory()) {
26
+ if (config.ignoreDirs.includes(entry.name))
27
+ continue;
28
+ walkDir(fullPath, cwd, config, depth + 1, results);
29
+ }
30
+ else if (entry.isFile()) {
31
+ const ext = extname(entry.name).toLowerCase();
32
+ if (!config.extensions.includes(ext))
33
+ continue;
34
+ try {
35
+ const stat = statSync(fullPath);
36
+ results.push({
37
+ absolutePath: fullPath,
38
+ relativePath: relative(cwd, fullPath),
39
+ stat,
40
+ });
41
+ }
42
+ catch {
43
+ // stat failed — skip
44
+ }
45
+ }
46
+ }
47
+ }
48
+ /**
49
+ * Scan project directory for text files and ingest changed ones into Nex.
50
+ */
51
+ export async function scanAndIngest(client, rateLimiter, cwd, config) {
52
+ const result = { scanned: 0, ingested: 0, skipped: 0, errors: 0 };
53
+ if (!config.enabled)
54
+ return result;
55
+ const manifest = readManifest();
56
+ const candidates = [];
57
+ walkDir(cwd, cwd, config, 0, candidates);
58
+ result.scanned = candidates.length;
59
+ // Filter to changed files, sort by mtime descending (newest first)
60
+ const changed = candidates
61
+ .filter((f) => isChanged(f.absolutePath, f.stat, manifest))
62
+ .sort((a, b) => b.stat.mtimeMs - a.stat.mtimeMs)
63
+ .slice(0, config.maxFilesPerScan);
64
+ result.skipped = candidates.length - changed.length;
65
+ for (const file of changed) {
66
+ if (!rateLimiter.canProceed()) {
67
+ process.stderr.write(`[nex-scan] Rate limited — stopping after ${result.ingested} files\n`);
68
+ result.skipped += changed.length - result.ingested - result.errors;
69
+ break;
70
+ }
71
+ try {
72
+ let content = readFileSync(file.absolutePath, "utf-8");
73
+ // Truncate large files
74
+ if (content.length > config.maxFileSize) {
75
+ content = content.slice(0, config.maxFileSize) + "\n[...truncated]";
76
+ }
77
+ const context = `file-scan:${file.relativePath}`;
78
+ await client.ingest(content, context, 10_000);
79
+ markIngested(file.absolutePath, file.stat, context, manifest);
80
+ result.ingested++;
81
+ }
82
+ catch (err) {
83
+ process.stderr.write(`[nex-scan] Failed to ingest ${file.relativePath}: ${err instanceof Error ? err.message : String(err)}\n`);
84
+ result.errors++;
85
+ }
86
+ }
87
+ writeManifest(manifest);
88
+ return result;
89
+ }
90
+ //# sourceMappingURL=file-scanner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-scanner.js","sourceRoot":"","sources":["../../src/plugin/file-scanner.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * HTTP client for the Nex Developer API.
3
+ * Adapted from openclaw-plugin — uses native fetch with timeout.
4
+ */
5
+ export declare class NexAuthError extends Error {
6
+ constructor(message?: string);
7
+ }
8
+ export declare class NexRateLimitError extends Error {
9
+ retryAfterMs: number;
10
+ constructor(retryAfterMs?: number);
11
+ }
12
+ export declare class NexServerError extends Error {
13
+ status: number;
14
+ constructor(status: number, body?: string);
15
+ }
16
+ export interface RegisterResponse {
17
+ api_key: string;
18
+ workspace_id?: string | number;
19
+ workspace_slug?: string;
20
+ [key: string]: unknown;
21
+ }
22
+ export interface IngestResponse {
23
+ artifact_id: string;
24
+ }
25
+ export interface EntityReference {
26
+ id?: number;
27
+ name: string;
28
+ type: string;
29
+ count?: number;
30
+ }
31
+ export interface AskResponse {
32
+ answer: string;
33
+ session_id?: string;
34
+ entity_references?: EntityReference[];
35
+ }
36
+ export declare class NexClient {
37
+ private apiKey;
38
+ private baseUrl;
39
+ constructor(apiKey: string, baseUrl: string);
40
+ private request;
41
+ /**
42
+ * Register a new account and get an API key.
43
+ * Does NOT require an existing API key — uses the public registration endpoint.
44
+ */
45
+ static register(baseUrl: string, email: string, name?: string, companyName?: string): Promise<RegisterResponse>;
46
+ /** Ingest text content into the Nex knowledge graph. */
47
+ ingest(content: string, context?: string, timeoutMs?: number): Promise<IngestResponse>;
48
+ /** Ask a question against the Nex knowledge graph. */
49
+ ask(query: string, sessionId?: string, timeoutMs?: number): Promise<AskResponse>;
50
+ }