@contextstream/mcp-server 0.4.81 → 1.0.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/NOTICE +25 -0
- package/README.md +77 -328
- package/npm/bin/contextstream-hook.mjs +9 -0
- package/npm/bin/contextstream-mcp.mjs +9 -0
- package/npm/bin/mcp-server.mjs +9 -0
- package/npm/launcher.mjs +390 -0
- package/package.json +28 -72
- package/server.json +43 -0
- package/dist/hooks/auto-rules.js +0 -3384
- package/dist/hooks/media-aware.js +0 -103
- package/dist/hooks/notification.js +0 -162
- package/dist/hooks/on-bash.js +0 -190
- package/dist/hooks/on-read.js +0 -321
- package/dist/hooks/on-save-intent.js +0 -132
- package/dist/hooks/on-task.js +0 -139
- package/dist/hooks/on-web.js +0 -155
- package/dist/hooks/permission-request.js +0 -174
- package/dist/hooks/post-compact.js +0 -172
- package/dist/hooks/post-tool-use-failure.js +0 -213
- package/dist/hooks/post-write.js +0 -348
- package/dist/hooks/pre-compact.js +0 -317
- package/dist/hooks/pre-tool-use.js +0 -553
- package/dist/hooks/runner.js +0 -5102
- package/dist/hooks/session-end.js +0 -285
- package/dist/hooks/session-init.js +0 -1846
- package/dist/hooks/stop.js +0 -171
- package/dist/hooks/subagent-start.js +0 -195
- package/dist/hooks/subagent-stop.js +0 -301
- package/dist/hooks/task-completed.js +0 -260
- package/dist/hooks/teammate-idle.js +0 -200
- package/dist/hooks/user-prompt-submit.js +0 -859
- package/dist/index.js +0 -41375
- package/dist/test-server.js +0 -4363
- package/scripts/postinstall.js +0 -56
- package/scripts/registration-smoke.mts +0 -45
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// src/hooks/post-tool-use-failure.ts
|
|
4
|
-
import * as fs2 from "node:fs";
|
|
5
|
-
import * as path2 from "node:path";
|
|
6
|
-
import { homedir as homedir2 } from "node:os";
|
|
7
|
-
|
|
8
|
-
// src/hooks/common.ts
|
|
9
|
-
import * as fs from "node:fs";
|
|
10
|
-
import * as path from "node:path";
|
|
11
|
-
import { homedir } from "node:os";
|
|
12
|
-
var DEFAULT_API_URL = "https://api.contextstream.io";
|
|
13
|
-
var HOOK_SPECIFIC_OUTPUT_EVENTS = /* @__PURE__ */ new Set(["PreToolUse", "UserPromptSubmit", "PostToolUse"]);
|
|
14
|
-
function readHookInput() {
|
|
15
|
-
try {
|
|
16
|
-
return JSON.parse(fs.readFileSync(0, "utf8"));
|
|
17
|
-
} catch {
|
|
18
|
-
return {};
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
function writeHookOutput(output) {
|
|
22
|
-
const eventName = output?.hookEventName || (typeof process.env.HOOK_EVENT_NAME === "string" ? process.env.HOOK_EVENT_NAME.trim() : "");
|
|
23
|
-
const canUseHookSpecificOutput = HOOK_SPECIFIC_OUTPUT_EVENTS.has(eventName);
|
|
24
|
-
const payload = output && (output.additionalContext || output.blocked || output.reason) ? {
|
|
25
|
-
hookSpecificOutput: output.additionalContext && canUseHookSpecificOutput ? {
|
|
26
|
-
hookEventName: eventName,
|
|
27
|
-
additionalContext: output.additionalContext
|
|
28
|
-
} : void 0,
|
|
29
|
-
additionalContext: output.additionalContext,
|
|
30
|
-
blocked: output.blocked,
|
|
31
|
-
reason: output.reason
|
|
32
|
-
} : {};
|
|
33
|
-
console.log(JSON.stringify(payload));
|
|
34
|
-
}
|
|
35
|
-
function extractCwd(input) {
|
|
36
|
-
const cwd = typeof input.cwd === "string" && input.cwd.trim() ? input.cwd.trim() : process.cwd();
|
|
37
|
-
return cwd;
|
|
38
|
-
}
|
|
39
|
-
function loadHookConfig(cwd) {
|
|
40
|
-
let apiUrl = process.env.CONTEXTSTREAM_API_URL || DEFAULT_API_URL;
|
|
41
|
-
let apiKey = process.env.CONTEXTSTREAM_API_KEY || "";
|
|
42
|
-
let jwt = process.env.CONTEXTSTREAM_JWT || "";
|
|
43
|
-
let workspaceId = process.env.CONTEXTSTREAM_WORKSPACE_ID || null;
|
|
44
|
-
let projectId = process.env.CONTEXTSTREAM_PROJECT_ID || null;
|
|
45
|
-
let searchDir = path.resolve(cwd);
|
|
46
|
-
for (let i = 0; i < 6; i++) {
|
|
47
|
-
if (!apiKey && !jwt) {
|
|
48
|
-
const mcpPath = path.join(searchDir, ".mcp.json");
|
|
49
|
-
if (fs.existsSync(mcpPath)) {
|
|
50
|
-
try {
|
|
51
|
-
const config = JSON.parse(fs.readFileSync(mcpPath, "utf8"));
|
|
52
|
-
const env = config.mcpServers?.contextstream?.env;
|
|
53
|
-
if (env?.CONTEXTSTREAM_API_KEY) apiKey = env.CONTEXTSTREAM_API_KEY;
|
|
54
|
-
if (env?.CONTEXTSTREAM_JWT) jwt = env.CONTEXTSTREAM_JWT;
|
|
55
|
-
if (env?.CONTEXTSTREAM_API_URL) apiUrl = env.CONTEXTSTREAM_API_URL;
|
|
56
|
-
if (env?.CONTEXTSTREAM_WORKSPACE_ID && !workspaceId) workspaceId = env.CONTEXTSTREAM_WORKSPACE_ID;
|
|
57
|
-
if (env?.CONTEXTSTREAM_PROJECT_ID && !projectId) projectId = env.CONTEXTSTREAM_PROJECT_ID;
|
|
58
|
-
} catch {
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
if (!workspaceId || !projectId) {
|
|
63
|
-
const localConfigPath = path.join(searchDir, ".contextstream", "config.json");
|
|
64
|
-
if (fs.existsSync(localConfigPath)) {
|
|
65
|
-
try {
|
|
66
|
-
const localConfig = JSON.parse(fs.readFileSync(localConfigPath, "utf8"));
|
|
67
|
-
if (localConfig.workspace_id && !workspaceId) workspaceId = localConfig.workspace_id;
|
|
68
|
-
if (localConfig.project_id && !projectId) projectId = localConfig.project_id;
|
|
69
|
-
} catch {
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
const parentDir = path.dirname(searchDir);
|
|
74
|
-
if (parentDir === searchDir) break;
|
|
75
|
-
searchDir = parentDir;
|
|
76
|
-
}
|
|
77
|
-
if (!apiKey && !jwt) {
|
|
78
|
-
const homeMcpPath = path.join(homedir(), ".mcp.json");
|
|
79
|
-
if (fs.existsSync(homeMcpPath)) {
|
|
80
|
-
try {
|
|
81
|
-
const config = JSON.parse(fs.readFileSync(homeMcpPath, "utf8"));
|
|
82
|
-
const env = config.mcpServers?.contextstream?.env;
|
|
83
|
-
if (env?.CONTEXTSTREAM_API_KEY) apiKey = env.CONTEXTSTREAM_API_KEY;
|
|
84
|
-
if (env?.CONTEXTSTREAM_JWT) jwt = env.CONTEXTSTREAM_JWT;
|
|
85
|
-
if (env?.CONTEXTSTREAM_API_URL) apiUrl = env.CONTEXTSTREAM_API_URL;
|
|
86
|
-
} catch {
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
return { apiUrl, apiKey, jwt, workspaceId, projectId };
|
|
91
|
-
}
|
|
92
|
-
function isConfigured(config) {
|
|
93
|
-
return Boolean(config.apiKey || config.jwt);
|
|
94
|
-
}
|
|
95
|
-
function authHeaders(config) {
|
|
96
|
-
if (config.apiKey) {
|
|
97
|
-
return { "X-API-Key": config.apiKey };
|
|
98
|
-
}
|
|
99
|
-
if (config.jwt) {
|
|
100
|
-
return { Authorization: `Bearer ${config.jwt}` };
|
|
101
|
-
}
|
|
102
|
-
return {};
|
|
103
|
-
}
|
|
104
|
-
async function apiRequest(config, apiPath, init = {}) {
|
|
105
|
-
const response = await fetch(`${config.apiUrl}${apiPath}`, {
|
|
106
|
-
method: init.method || "GET",
|
|
107
|
-
headers: {
|
|
108
|
-
"Content-Type": "application/json",
|
|
109
|
-
...authHeaders(config)
|
|
110
|
-
},
|
|
111
|
-
body: init.body !== void 0 ? JSON.stringify(init.body) : void 0
|
|
112
|
-
});
|
|
113
|
-
if (!response.ok) {
|
|
114
|
-
throw new Error(`${response.status} ${response.statusText}`);
|
|
115
|
-
}
|
|
116
|
-
const json = await response.json();
|
|
117
|
-
if (json && typeof json === "object" && "data" in json) {
|
|
118
|
-
return json.data;
|
|
119
|
-
}
|
|
120
|
-
return json;
|
|
121
|
-
}
|
|
122
|
-
async function postMemoryEvent(config, title, content, tags, eventType = "operation") {
|
|
123
|
-
if (!isConfigured(config) || !config.workspaceId) return;
|
|
124
|
-
await apiRequest(config, "/memory/events", {
|
|
125
|
-
method: "POST",
|
|
126
|
-
body: {
|
|
127
|
-
workspace_id: config.workspaceId,
|
|
128
|
-
project_id: config.projectId || void 0,
|
|
129
|
-
event_type: eventType,
|
|
130
|
-
title,
|
|
131
|
-
content: typeof content === "string" ? content : JSON.stringify(content),
|
|
132
|
-
metadata: {
|
|
133
|
-
tags,
|
|
134
|
-
source: "mcp_hook",
|
|
135
|
-
captured_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// src/hooks/post-tool-use-failure.ts
|
|
142
|
-
var FAILURE_COUNTERS_FILE = path2.join(homedir2(), ".contextstream", "hook-failure-counts.json");
|
|
143
|
-
function extractErrorText(input) {
|
|
144
|
-
return typeof input.error === "string" && input.error || typeof input.tool_error === "string" && input.tool_error || typeof input.stderr === "string" && input.stderr || "Tool execution failed";
|
|
145
|
-
}
|
|
146
|
-
function failureFingerprint(toolName, errorText) {
|
|
147
|
-
const compact = errorText.split(/\s+/).slice(0, 18).join(" ").toLowerCase();
|
|
148
|
-
return `${toolName.toLowerCase()}:${compact}`;
|
|
149
|
-
}
|
|
150
|
-
function incrementFailureCounter(fingerprint) {
|
|
151
|
-
let counters = {};
|
|
152
|
-
try {
|
|
153
|
-
counters = JSON.parse(fs2.readFileSync(FAILURE_COUNTERS_FILE, "utf8"));
|
|
154
|
-
} catch {
|
|
155
|
-
counters = {};
|
|
156
|
-
}
|
|
157
|
-
counters[fingerprint] = (counters[fingerprint] || 0) + 1;
|
|
158
|
-
fs2.mkdirSync(path2.dirname(FAILURE_COUNTERS_FILE), { recursive: true });
|
|
159
|
-
fs2.writeFileSync(FAILURE_COUNTERS_FILE, JSON.stringify(counters, null, 2), "utf8");
|
|
160
|
-
return counters[fingerprint];
|
|
161
|
-
}
|
|
162
|
-
async function runPostToolUseFailureHook() {
|
|
163
|
-
const input = readHookInput();
|
|
164
|
-
const cwd = extractCwd(input);
|
|
165
|
-
const config = loadHookConfig(cwd);
|
|
166
|
-
const toolName = typeof input.tool_name === "string" && input.tool_name || typeof input.toolName === "string" && input.toolName || "unknown";
|
|
167
|
-
const errorText = extractErrorText(input);
|
|
168
|
-
const toolUseId = typeof input.tool_use_id === "string" && input.tool_use_id || typeof input.toolUseId === "string" && input.toolUseId || "";
|
|
169
|
-
const fingerprint = failureFingerprint(toolName, errorText);
|
|
170
|
-
const count = incrementFailureCounter(fingerprint);
|
|
171
|
-
if (isConfigured(config)) {
|
|
172
|
-
await postMemoryEvent(
|
|
173
|
-
config,
|
|
174
|
-
`Tool failure: ${toolName}`,
|
|
175
|
-
{
|
|
176
|
-
tool_name: toolName,
|
|
177
|
-
tool_use_id: toolUseId || null,
|
|
178
|
-
error: errorText,
|
|
179
|
-
fingerprint,
|
|
180
|
-
occurrence_count: count,
|
|
181
|
-
tool_input: input.tool_input || {},
|
|
182
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
183
|
-
},
|
|
184
|
-
["hook", "post_tool_use_failure", "tool_error"]
|
|
185
|
-
).catch(() => {
|
|
186
|
-
});
|
|
187
|
-
const autoLessonEnabled = process.env.CONTEXTSTREAM_FAILURE_AUTO_LESSON !== "false";
|
|
188
|
-
if (autoLessonEnabled && count >= 3) {
|
|
189
|
-
await postMemoryEvent(
|
|
190
|
-
config,
|
|
191
|
-
`Recurring failure lesson: ${toolName}`,
|
|
192
|
-
{
|
|
193
|
-
title: `Recurring failure in ${toolName}`,
|
|
194
|
-
trigger: errorText,
|
|
195
|
-
prevention: "Add guardrails or alternate fallback path for this failure mode.",
|
|
196
|
-
occurrences: count,
|
|
197
|
-
fingerprint
|
|
198
|
-
},
|
|
199
|
-
["hook", "lesson", "recurring_failure"],
|
|
200
|
-
"lesson"
|
|
201
|
-
).catch(() => {
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
writeHookOutput();
|
|
206
|
-
}
|
|
207
|
-
var isDirectRun = process.argv[1]?.includes("post-tool-use-failure") || process.argv[2] === "post-tool-use-failure";
|
|
208
|
-
if (isDirectRun) {
|
|
209
|
-
runPostToolUseFailureHook().catch(() => process.exit(0));
|
|
210
|
-
}
|
|
211
|
-
export {
|
|
212
|
-
runPostToolUseFailureHook
|
|
213
|
-
};
|
package/dist/hooks/post-write.js
DELETED
|
@@ -1,348 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// src/hooks/post-write.ts
|
|
4
|
-
import * as fs from "node:fs";
|
|
5
|
-
import * as path from "node:path";
|
|
6
|
-
import { homedir } from "node:os";
|
|
7
|
-
var API_URL = process.env.CONTEXTSTREAM_API_URL || "https://api.contextstream.io";
|
|
8
|
-
var API_KEY = process.env.CONTEXTSTREAM_API_KEY || "";
|
|
9
|
-
var ENABLED = process.env.CONTEXTSTREAM_POSTWRITE_ENABLED !== "false";
|
|
10
|
-
var INDEXABLE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
11
|
-
".ts",
|
|
12
|
-
".tsx",
|
|
13
|
-
".js",
|
|
14
|
-
".jsx",
|
|
15
|
-
".mjs",
|
|
16
|
-
".cjs",
|
|
17
|
-
".py",
|
|
18
|
-
".pyw",
|
|
19
|
-
".rs",
|
|
20
|
-
".go",
|
|
21
|
-
".java",
|
|
22
|
-
".kt",
|
|
23
|
-
".scala",
|
|
24
|
-
".c",
|
|
25
|
-
".cpp",
|
|
26
|
-
".cc",
|
|
27
|
-
".cxx",
|
|
28
|
-
".h",
|
|
29
|
-
".hpp",
|
|
30
|
-
".cs",
|
|
31
|
-
".fs",
|
|
32
|
-
".vb",
|
|
33
|
-
".rb",
|
|
34
|
-
".php",
|
|
35
|
-
".pl",
|
|
36
|
-
".pm",
|
|
37
|
-
".swift",
|
|
38
|
-
".m",
|
|
39
|
-
".mm",
|
|
40
|
-
".lua",
|
|
41
|
-
".r",
|
|
42
|
-
".jl",
|
|
43
|
-
".sh",
|
|
44
|
-
".bash",
|
|
45
|
-
".zsh",
|
|
46
|
-
".fish",
|
|
47
|
-
".sql",
|
|
48
|
-
".graphql",
|
|
49
|
-
".gql",
|
|
50
|
-
".html",
|
|
51
|
-
".htm",
|
|
52
|
-
".css",
|
|
53
|
-
".scss",
|
|
54
|
-
".sass",
|
|
55
|
-
".less",
|
|
56
|
-
".json",
|
|
57
|
-
".yaml",
|
|
58
|
-
".yml",
|
|
59
|
-
".toml",
|
|
60
|
-
".xml",
|
|
61
|
-
".ini",
|
|
62
|
-
".cfg",
|
|
63
|
-
".md",
|
|
64
|
-
".mdx",
|
|
65
|
-
".txt",
|
|
66
|
-
".rst",
|
|
67
|
-
".vue",
|
|
68
|
-
".svelte",
|
|
69
|
-
".astro",
|
|
70
|
-
".tf",
|
|
71
|
-
".hcl",
|
|
72
|
-
".dockerfile",
|
|
73
|
-
".containerfile",
|
|
74
|
-
".prisma",
|
|
75
|
-
".proto"
|
|
76
|
-
]);
|
|
77
|
-
var MAX_FILE_SIZE = 5 * 1024 * 1024;
|
|
78
|
-
function extractFilePath(input) {
|
|
79
|
-
if (input.tool_input) {
|
|
80
|
-
const filePath = input.tool_input.file_path || input.tool_input.notebook_path || input.tool_input.path;
|
|
81
|
-
if (filePath) return filePath;
|
|
82
|
-
}
|
|
83
|
-
if (input.parameters) {
|
|
84
|
-
const filePath = input.parameters.path || input.parameters.file_path;
|
|
85
|
-
if (filePath) return filePath;
|
|
86
|
-
}
|
|
87
|
-
if (input.toolParameters?.path) {
|
|
88
|
-
return input.toolParameters.path;
|
|
89
|
-
}
|
|
90
|
-
if (input.file_path) {
|
|
91
|
-
return input.file_path;
|
|
92
|
-
}
|
|
93
|
-
return null;
|
|
94
|
-
}
|
|
95
|
-
function extractCwd(input) {
|
|
96
|
-
if (input.cwd) return input.cwd;
|
|
97
|
-
if (input.workspace_roots?.length) return input.workspace_roots[0];
|
|
98
|
-
if (input.workspaceRoots?.length) return input.workspaceRoots[0];
|
|
99
|
-
return process.cwd();
|
|
100
|
-
}
|
|
101
|
-
function findLocalConfig(startDir) {
|
|
102
|
-
let currentDir = path.resolve(startDir);
|
|
103
|
-
for (let i = 0; i < 10; i++) {
|
|
104
|
-
const configPath = path.join(currentDir, ".contextstream", "config.json");
|
|
105
|
-
if (fs.existsSync(configPath)) {
|
|
106
|
-
try {
|
|
107
|
-
const content = fs.readFileSync(configPath, "utf-8");
|
|
108
|
-
return JSON.parse(content);
|
|
109
|
-
} catch {
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
const parentDir = path.dirname(currentDir);
|
|
113
|
-
if (parentDir === currentDir) break;
|
|
114
|
-
currentDir = parentDir;
|
|
115
|
-
}
|
|
116
|
-
return null;
|
|
117
|
-
}
|
|
118
|
-
function loadApiConfig(startDir) {
|
|
119
|
-
let apiUrl = API_URL;
|
|
120
|
-
let apiKey = API_KEY;
|
|
121
|
-
if (apiKey) {
|
|
122
|
-
return { apiUrl, apiKey };
|
|
123
|
-
}
|
|
124
|
-
let currentDir = path.resolve(startDir);
|
|
125
|
-
for (let i = 0; i < 10; i++) {
|
|
126
|
-
const mcpPath = path.join(currentDir, ".mcp.json");
|
|
127
|
-
if (fs.existsSync(mcpPath)) {
|
|
128
|
-
try {
|
|
129
|
-
const content = fs.readFileSync(mcpPath, "utf-8");
|
|
130
|
-
const config = JSON.parse(content);
|
|
131
|
-
const csEnv = config.mcpServers?.contextstream?.env;
|
|
132
|
-
if (csEnv?.CONTEXTSTREAM_API_KEY) {
|
|
133
|
-
apiKey = csEnv.CONTEXTSTREAM_API_KEY;
|
|
134
|
-
}
|
|
135
|
-
if (csEnv?.CONTEXTSTREAM_API_URL) {
|
|
136
|
-
apiUrl = csEnv.CONTEXTSTREAM_API_URL;
|
|
137
|
-
}
|
|
138
|
-
if (apiKey) break;
|
|
139
|
-
} catch {
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
const parentDir = path.dirname(currentDir);
|
|
143
|
-
if (parentDir === currentDir) break;
|
|
144
|
-
currentDir = parentDir;
|
|
145
|
-
}
|
|
146
|
-
if (!apiKey) {
|
|
147
|
-
const homeMcpPath = path.join(homedir(), ".mcp.json");
|
|
148
|
-
if (fs.existsSync(homeMcpPath)) {
|
|
149
|
-
try {
|
|
150
|
-
const content = fs.readFileSync(homeMcpPath, "utf-8");
|
|
151
|
-
const config = JSON.parse(content);
|
|
152
|
-
const csEnv = config.mcpServers?.contextstream?.env;
|
|
153
|
-
if (csEnv?.CONTEXTSTREAM_API_KEY) {
|
|
154
|
-
apiKey = csEnv.CONTEXTSTREAM_API_KEY;
|
|
155
|
-
}
|
|
156
|
-
if (csEnv?.CONTEXTSTREAM_API_URL) {
|
|
157
|
-
apiUrl = csEnv.CONTEXTSTREAM_API_URL;
|
|
158
|
-
}
|
|
159
|
-
} catch {
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
return { apiUrl, apiKey };
|
|
164
|
-
}
|
|
165
|
-
function shouldIndexFile(filePath) {
|
|
166
|
-
const ext = path.extname(filePath).toLowerCase();
|
|
167
|
-
if (!INDEXABLE_EXTENSIONS.has(ext)) {
|
|
168
|
-
const basename2 = path.basename(filePath).toLowerCase();
|
|
169
|
-
if (!["dockerfile", "makefile", "rakefile", "gemfile", "procfile"].includes(basename2)) {
|
|
170
|
-
return false;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
try {
|
|
174
|
-
const stats = fs.statSync(filePath);
|
|
175
|
-
if (stats.size > MAX_FILE_SIZE) {
|
|
176
|
-
return false;
|
|
177
|
-
}
|
|
178
|
-
} catch {
|
|
179
|
-
return false;
|
|
180
|
-
}
|
|
181
|
-
return true;
|
|
182
|
-
}
|
|
183
|
-
function detectLanguage(filePath) {
|
|
184
|
-
const ext = path.extname(filePath).toLowerCase();
|
|
185
|
-
const langMap = {
|
|
186
|
-
".ts": "typescript",
|
|
187
|
-
".tsx": "typescript",
|
|
188
|
-
".js": "javascript",
|
|
189
|
-
".jsx": "javascript",
|
|
190
|
-
".mjs": "javascript",
|
|
191
|
-
".cjs": "javascript",
|
|
192
|
-
".py": "python",
|
|
193
|
-
".pyw": "python",
|
|
194
|
-
".rs": "rust",
|
|
195
|
-
".go": "go",
|
|
196
|
-
".java": "java",
|
|
197
|
-
".kt": "kotlin",
|
|
198
|
-
".scala": "scala",
|
|
199
|
-
".c": "c",
|
|
200
|
-
".cpp": "cpp",
|
|
201
|
-
".cc": "cpp",
|
|
202
|
-
".cxx": "cpp",
|
|
203
|
-
".h": "c",
|
|
204
|
-
".hpp": "cpp",
|
|
205
|
-
".cs": "csharp",
|
|
206
|
-
".fs": "fsharp",
|
|
207
|
-
".vb": "vb",
|
|
208
|
-
".rb": "ruby",
|
|
209
|
-
".php": "php",
|
|
210
|
-
".pl": "perl",
|
|
211
|
-
".pm": "perl",
|
|
212
|
-
".swift": "swift",
|
|
213
|
-
".m": "objective-c",
|
|
214
|
-
".mm": "objective-cpp",
|
|
215
|
-
".lua": "lua",
|
|
216
|
-
".r": "r",
|
|
217
|
-
".jl": "julia",
|
|
218
|
-
".sh": "shell",
|
|
219
|
-
".bash": "shell",
|
|
220
|
-
".zsh": "shell",
|
|
221
|
-
".fish": "shell",
|
|
222
|
-
".sql": "sql",
|
|
223
|
-
".graphql": "graphql",
|
|
224
|
-
".gql": "graphql",
|
|
225
|
-
".html": "html",
|
|
226
|
-
".htm": "html",
|
|
227
|
-
".css": "css",
|
|
228
|
-
".scss": "scss",
|
|
229
|
-
".sass": "sass",
|
|
230
|
-
".less": "less",
|
|
231
|
-
".json": "json",
|
|
232
|
-
".yaml": "yaml",
|
|
233
|
-
".yml": "yaml",
|
|
234
|
-
".toml": "toml",
|
|
235
|
-
".xml": "xml",
|
|
236
|
-
".ini": "ini",
|
|
237
|
-
".cfg": "ini",
|
|
238
|
-
".md": "markdown",
|
|
239
|
-
".mdx": "mdx",
|
|
240
|
-
".txt": "text",
|
|
241
|
-
".rst": "rst",
|
|
242
|
-
".vue": "vue",
|
|
243
|
-
".svelte": "svelte",
|
|
244
|
-
".astro": "astro",
|
|
245
|
-
".tf": "terraform",
|
|
246
|
-
".hcl": "hcl",
|
|
247
|
-
".prisma": "prisma",
|
|
248
|
-
".proto": "protobuf"
|
|
249
|
-
};
|
|
250
|
-
return langMap[ext] || "text";
|
|
251
|
-
}
|
|
252
|
-
async function indexFile(filePath, projectId, apiUrl, apiKey, projectRoot) {
|
|
253
|
-
const content = fs.readFileSync(filePath, "utf-8");
|
|
254
|
-
const relativePath = path.relative(projectRoot, filePath);
|
|
255
|
-
const payload = {
|
|
256
|
-
files: [
|
|
257
|
-
{
|
|
258
|
-
path: relativePath,
|
|
259
|
-
content,
|
|
260
|
-
language: detectLanguage(filePath)
|
|
261
|
-
}
|
|
262
|
-
]
|
|
263
|
-
};
|
|
264
|
-
const response = await fetch(`${apiUrl}/api/v1/projects/${projectId}/files/ingest`, {
|
|
265
|
-
method: "POST",
|
|
266
|
-
headers: {
|
|
267
|
-
"Content-Type": "application/json",
|
|
268
|
-
"X-API-Key": apiKey
|
|
269
|
-
},
|
|
270
|
-
body: JSON.stringify(payload),
|
|
271
|
-
signal: AbortSignal.timeout(1e4)
|
|
272
|
-
// 10 second timeout
|
|
273
|
-
});
|
|
274
|
-
if (!response.ok) {
|
|
275
|
-
throw new Error(`API error: ${response.status} ${response.statusText}`);
|
|
276
|
-
}
|
|
277
|
-
try {
|
|
278
|
-
const body = await response.json();
|
|
279
|
-
if (body?.data?.status === "cooldown" || body?.data?.status === "daily_limit_exceeded") {
|
|
280
|
-
return;
|
|
281
|
-
}
|
|
282
|
-
} catch {
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
function findProjectRoot(filePath) {
|
|
286
|
-
let currentDir = path.dirname(path.resolve(filePath));
|
|
287
|
-
for (let i = 0; i < 10; i++) {
|
|
288
|
-
const configPath = path.join(currentDir, ".contextstream", "config.json");
|
|
289
|
-
if (fs.existsSync(configPath)) {
|
|
290
|
-
return currentDir;
|
|
291
|
-
}
|
|
292
|
-
const parentDir = path.dirname(currentDir);
|
|
293
|
-
if (parentDir === currentDir) break;
|
|
294
|
-
currentDir = parentDir;
|
|
295
|
-
}
|
|
296
|
-
return null;
|
|
297
|
-
}
|
|
298
|
-
async function runPostWriteHook() {
|
|
299
|
-
if (!ENABLED) {
|
|
300
|
-
process.exit(0);
|
|
301
|
-
}
|
|
302
|
-
let inputData = "";
|
|
303
|
-
for await (const chunk of process.stdin) {
|
|
304
|
-
inputData += chunk;
|
|
305
|
-
}
|
|
306
|
-
if (!inputData.trim()) {
|
|
307
|
-
process.exit(0);
|
|
308
|
-
}
|
|
309
|
-
let input;
|
|
310
|
-
try {
|
|
311
|
-
input = JSON.parse(inputData);
|
|
312
|
-
} catch {
|
|
313
|
-
process.exit(0);
|
|
314
|
-
}
|
|
315
|
-
const filePath = extractFilePath(input);
|
|
316
|
-
if (!filePath) {
|
|
317
|
-
process.exit(0);
|
|
318
|
-
}
|
|
319
|
-
const cwd = extractCwd(input);
|
|
320
|
-
const absolutePath = path.isAbsolute(filePath) ? filePath : path.resolve(cwd, filePath);
|
|
321
|
-
if (!fs.existsSync(absolutePath) || !shouldIndexFile(absolutePath)) {
|
|
322
|
-
process.exit(0);
|
|
323
|
-
}
|
|
324
|
-
const projectRoot = findProjectRoot(absolutePath);
|
|
325
|
-
if (!projectRoot) {
|
|
326
|
-
process.exit(0);
|
|
327
|
-
}
|
|
328
|
-
const localConfig = findLocalConfig(projectRoot);
|
|
329
|
-
if (!localConfig?.project_id) {
|
|
330
|
-
process.exit(0);
|
|
331
|
-
}
|
|
332
|
-
const { apiUrl, apiKey } = loadApiConfig(projectRoot);
|
|
333
|
-
if (!apiKey) {
|
|
334
|
-
process.exit(0);
|
|
335
|
-
}
|
|
336
|
-
try {
|
|
337
|
-
await indexFile(absolutePath, localConfig.project_id, apiUrl, apiKey, projectRoot);
|
|
338
|
-
} catch {
|
|
339
|
-
}
|
|
340
|
-
process.exit(0);
|
|
341
|
-
}
|
|
342
|
-
var isDirectRun = process.argv[1]?.includes("post-write") || process.argv[2] === "post-write";
|
|
343
|
-
if (isDirectRun) {
|
|
344
|
-
runPostWriteHook().catch(() => process.exit(0));
|
|
345
|
-
}
|
|
346
|
-
export {
|
|
347
|
-
runPostWriteHook
|
|
348
|
-
};
|