fast-context-skill 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.
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Windsurf API Key extraction from local installation.
3
+ *
4
+ * Cross-platform: macOS / Windows / Linux.
5
+ * Uses sql.js (pure JS/WASM) to read state.vscdb — no native compilation needed.
6
+ */
7
+
8
+ import { existsSync, readFileSync } from "node:fs";
9
+ import { join } from "node:path";
10
+ import { homedir, platform } from "node:os";
11
+ import initSqlJs from "sql.js";
12
+
13
+ /**
14
+ * Get the platform-specific path to Windsurf's state.vscdb.
15
+ * @returns {string}
16
+ */
17
+ export function getDbPath() {
18
+ const plat = platform();
19
+ const home = homedir();
20
+
21
+ if (plat === "darwin") {
22
+ return join(home, "Library", "Application Support", "Windsurf", "User", "globalStorage", "state.vscdb");
23
+ } else if (plat === "win32") {
24
+ const appdata = process.env.APPDATA || "";
25
+ if (!appdata) throw new Error("Cannot determine APPDATA path");
26
+ return join(appdata, "Windsurf", "User", "globalStorage", "state.vscdb");
27
+ } else {
28
+ // Linux
29
+ const config = process.env.XDG_CONFIG_HOME || join(home, ".config");
30
+ return join(config, "Windsurf", "User", "globalStorage", "state.vscdb");
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Extract API Key from Windsurf state.vscdb.
36
+ * @param {string} [dbPath]
37
+ * @returns {Promise<{ api_key?: string, db_path: string, error?: string, hint?: string }>}
38
+ */
39
+ export async function extractKey(dbPath) {
40
+ if (!dbPath) {
41
+ dbPath = getDbPath();
42
+ }
43
+
44
+ if (!existsSync(dbPath)) {
45
+ return {
46
+ error: `Windsurf database not found: ${dbPath}`,
47
+ hint: "Ensure Windsurf is installed and logged in.",
48
+ db_path: dbPath,
49
+ };
50
+ }
51
+
52
+ let db;
53
+ try {
54
+ const SQL = await initSqlJs();
55
+ const buf = readFileSync(dbPath);
56
+ db = new SQL.Database(buf);
57
+ } catch (e) {
58
+ return { error: `Failed to open database: ${e.message}`, db_path: dbPath };
59
+ }
60
+
61
+ try {
62
+ const stmt = db.prepare("SELECT value FROM ItemTable WHERE key = 'windsurfAuthStatus'");
63
+ if (!stmt.step()) {
64
+ stmt.free();
65
+ return {
66
+ error: "windsurfAuthStatus record not found",
67
+ hint: "Ensure Windsurf is logged in.",
68
+ db_path: dbPath,
69
+ };
70
+ }
71
+
72
+ const row = stmt.getAsObject();
73
+ stmt.free();
74
+
75
+ let data;
76
+ try {
77
+ data = JSON.parse(row.value);
78
+ } catch {
79
+ return { error: "windsurfAuthStatus data parse failed", db_path: dbPath };
80
+ }
81
+
82
+ const apiKey = data.apiKey || "";
83
+ if (!apiKey) {
84
+ return { error: "apiKey field is empty", db_path: dbPath };
85
+ }
86
+
87
+ return { api_key: apiKey, db_path: dbPath };
88
+ } catch (e) {
89
+ return { error: `Extraction failed: ${e.message}`, db_path: dbPath };
90
+ } finally {
91
+ db.close();
92
+ }
93
+ }
@@ -0,0 +1,47 @@
1
+ import { statSync } from "node:fs";
2
+ import { isAbsolute } from "node:path";
3
+ import { z } from "zod";
4
+
5
+ export const PROJECT_PATH_REQUIRED_MESSAGE =
6
+ "project_path is required. Pass the absolute path to the project root directory.";
7
+
8
+ export const projectPathSchema = z
9
+ .string()
10
+ .trim()
11
+ .min(1, PROJECT_PATH_REQUIRED_MESSAGE);
12
+
13
+ /**
14
+ * Validate the project root path provided to fast_context_search.
15
+ * Returns null when valid, otherwise an MCP-friendly error string.
16
+ *
17
+ * @param {string} projectPath
18
+ * @param {(path: string) => import("node:fs").Stats} [statFn]
19
+ * @returns {string|null}
20
+ */
21
+ export function validateProjectPath(projectPath, statFn = statSync) {
22
+ if (!projectPath) {
23
+ return `Error: ${PROJECT_PATH_REQUIRED_MESSAGE}`;
24
+ }
25
+
26
+ if (!isAbsolute(projectPath)) {
27
+ return `Error: project_path must be an absolute path, got: ${projectPath}`;
28
+ }
29
+
30
+ try {
31
+ const st = statFn(projectPath);
32
+ if (!st.isDirectory()) {
33
+ return `Error: project_path is not a directory: ${projectPath}`;
34
+ }
35
+ } catch (error) {
36
+ if (error?.code === "ENOENT") {
37
+ return `Error: project_path does not exist: ${projectPath}`;
38
+ }
39
+ if (error?.code === "EACCES" || error?.code === "EPERM") {
40
+ return `Error: cannot access project_path (${error.code}): ${projectPath}`;
41
+ }
42
+ const reason = error?.message ? `${error.code || "UNKNOWN"}: ${error.message}` : String(error);
43
+ return `Error: failed to validate project_path: ${reason}`;
44
+ }
45
+
46
+ return null;
47
+ }
@@ -0,0 +1,235 @@
1
+ /**
2
+ * Hand-written Protobuf encoder/decoder + Connect-RPC frame handling.
3
+ *
4
+ * Matches the Windsurf wire format exactly.
5
+ * Python bytearray → Node.js Buffer
6
+ * struct.pack(">I", len) → buf.writeUInt32BE
7
+ * gzip.compress/decompress → zlib.gzipSync/gunzipSync
8
+ */
9
+
10
+ import { gzipSync, gunzipSync } from "node:zlib";
11
+
12
+ // ─── Protobuf Encoder ──────────────────────────────────────
13
+
14
+ export class ProtobufEncoder {
15
+ constructor() {
16
+ /** @type {Buffer[]} */
17
+ this._chunks = [];
18
+ }
19
+
20
+ /**
21
+ * Encode an unsigned varint into a Buffer.
22
+ * @param {number} value
23
+ * @returns {Buffer}
24
+ */
25
+ _varint(value) {
26
+ const bytes = [];
27
+ while (value > 0x7f) {
28
+ bytes.push((value & 0x7f) | 0x80);
29
+ value >>>= 7;
30
+ }
31
+ bytes.push(value & 0x7f);
32
+ return Buffer.from(bytes);
33
+ }
34
+
35
+ /**
36
+ * Encode a field tag.
37
+ * @param {number} field
38
+ * @param {number} wire
39
+ * @returns {Buffer}
40
+ */
41
+ _tag(field, wire) {
42
+ return this._varint((field << 3) | wire);
43
+ }
44
+
45
+ /**
46
+ * Write a varint field.
47
+ * @param {number} field
48
+ * @param {number} value
49
+ * @returns {ProtobufEncoder}
50
+ */
51
+ writeVarint(field, value) {
52
+ this._chunks.push(this._tag(field, 0), this._varint(value));
53
+ return this;
54
+ }
55
+
56
+ /**
57
+ * Write a length-delimited string field.
58
+ * @param {number} field
59
+ * @param {string} value
60
+ * @returns {ProtobufEncoder}
61
+ */
62
+ writeString(field, value) {
63
+ const data = Buffer.from(value, "utf-8");
64
+ this._chunks.push(this._tag(field, 2), this._varint(data.length), data);
65
+ return this;
66
+ }
67
+
68
+ /**
69
+ * Write a length-delimited bytes field.
70
+ * @param {number} field
71
+ * @param {Buffer|Uint8Array} value
72
+ * @returns {ProtobufEncoder}
73
+ */
74
+ writeBytes(field, value) {
75
+ const buf = Buffer.isBuffer(value) ? value : Buffer.from(value);
76
+ this._chunks.push(this._tag(field, 2), this._varint(buf.length), buf);
77
+ return this;
78
+ }
79
+
80
+ /**
81
+ * Write a nested message field.
82
+ * @param {number} field
83
+ * @param {ProtobufEncoder} sub
84
+ * @returns {ProtobufEncoder}
85
+ */
86
+ writeMessage(field, sub) {
87
+ const data = sub.toBuffer();
88
+ this._chunks.push(this._tag(field, 2), this._varint(data.length), data);
89
+ return this;
90
+ }
91
+
92
+ /**
93
+ * Return the encoded bytes as a Buffer.
94
+ * @returns {Buffer}
95
+ */
96
+ toBuffer() {
97
+ return Buffer.concat(this._chunks);
98
+ }
99
+ }
100
+
101
+ // ─── Varint Decode ─────────────────────────────────────────
102
+
103
+ /**
104
+ * Decode a varint from a buffer at the given offset.
105
+ * @param {Buffer} buf
106
+ * @param {number} offset
107
+ * @returns {[number, number]} [value, newOffset]
108
+ */
109
+ export function decodeVarint(buf, offset) {
110
+ let value = 0;
111
+ let shift = 0;
112
+ while (offset < buf.length) {
113
+ const b = buf[offset++];
114
+ value |= (b & 0x7f) << shift;
115
+ shift += 7;
116
+ if (!(b & 0x80)) break;
117
+ }
118
+ return [value, offset];
119
+ }
120
+
121
+ // ─── Protobuf String Extraction ────────────────────────────
122
+
123
+ /**
124
+ * Extract all UTF-8 strings (length > 5) from raw protobuf data
125
+ * by parsing wire types. Matches Python proto_extract_strings().
126
+ * @param {Buffer} data
127
+ * @returns {string[]}
128
+ */
129
+ export function extractStrings(data) {
130
+ const strings = [];
131
+ let i = 0;
132
+ while (i < data.length) {
133
+ // Read tag varint
134
+ let tag = 0;
135
+ let shift = 0;
136
+ while (i < data.length) {
137
+ const b = data[i++];
138
+ tag |= (b & 0x7f) << shift;
139
+ shift += 7;
140
+ if (!(b & 0x80)) break;
141
+ }
142
+ const wire = tag & 0x7;
143
+ if (wire === 0) {
144
+ // Varint — skip
145
+ while (i < data.length) {
146
+ const b = data[i++];
147
+ if (!(b & 0x80)) break;
148
+ }
149
+ } else if (wire === 1) {
150
+ // 64-bit fixed
151
+ i += 8;
152
+ } else if (wire === 2) {
153
+ // Length-delimited
154
+ let length = 0;
155
+ shift = 0;
156
+ while (i < data.length) {
157
+ const b = data[i++];
158
+ length |= (b & 0x7f) << shift;
159
+ shift += 7;
160
+ if (!(b & 0x80)) break;
161
+ }
162
+ if (i + length <= data.length) {
163
+ const raw = data.subarray(i, i + length);
164
+ try {
165
+ const text = raw.toString("utf-8");
166
+ if (text.length > 5) {
167
+ strings.push(text);
168
+ }
169
+ } catch {
170
+ // Not valid UTF-8, skip
171
+ }
172
+ }
173
+ i += length;
174
+ } else if (wire === 5) {
175
+ // 32-bit fixed
176
+ i += 4;
177
+ } else {
178
+ // Unknown wire type — stop
179
+ break;
180
+ }
181
+ }
182
+ return strings;
183
+ }
184
+
185
+ // ─── Connect-RPC Frame Encode/Decode ───────────────────────
186
+
187
+ /**
188
+ * Encode protobuf bytes into a gzip-compressed Connect-RPC frame.
189
+ * Frame format: 1-byte flags + 4-byte big-endian length + payload
190
+ * @param {Buffer} protoBytes
191
+ * @param {boolean} [compress=true]
192
+ * @returns {Buffer}
193
+ */
194
+ export function connectFrameEncode(protoBytes, compress = true) {
195
+ let payload;
196
+ let flags;
197
+ if (compress) {
198
+ payload = gzipSync(protoBytes);
199
+ flags = 1; // gzip compressed
200
+ } else {
201
+ payload = protoBytes;
202
+ flags = 0;
203
+ }
204
+ const header = Buffer.alloc(5);
205
+ header[0] = flags;
206
+ header.writeUInt32BE(payload.length, 1);
207
+ return Buffer.concat([header, payload]);
208
+ }
209
+
210
+ /**
211
+ * Decode Connect-RPC frames from raw response data.
212
+ * Handles gzip-compressed frames (flags 1 or 3).
213
+ * @param {Buffer} data
214
+ * @returns {Buffer[]}
215
+ */
216
+ export function connectFrameDecode(data) {
217
+ const frames = [];
218
+ let i = 0;
219
+ while (i + 5 <= data.length) {
220
+ const flags = data[i];
221
+ const length = data.readUInt32BE(i + 1);
222
+ i += 5;
223
+ let payload = data.subarray(i, i + length);
224
+ i += length;
225
+ if (flags === 1 || flags === 3) {
226
+ try {
227
+ payload = gunzipSync(payload);
228
+ } catch {
229
+ // Decompression failed — use raw payload
230
+ }
231
+ }
232
+ frames.push(Buffer.from(payload));
233
+ }
234
+ return frames;
235
+ }