@agent-api/sdk 1.1.2 → 1.1.4
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 +37 -3
- package/dist/client.d.ts +3 -1
- package/dist/client.js +4 -1
- package/dist/index.d.ts +2 -4
- package/dist/index.js +1 -2
- package/dist/node-client.d.ts +6 -0
- package/dist/node-client.js +7 -0
- package/dist/node.d.ts +8 -0
- package/dist/node.js +6 -0
- package/dist/preset-tools.d.ts +53 -0
- package/dist/preset-tools.js +85 -0
- package/dist/resources/responses.js +2 -0
- package/dist/resources/skills-node.d.ts +7 -0
- package/dist/resources/skills-node.js +156 -0
- package/dist/resources/skills.d.ts +1 -3
- package/dist/resources/skills.js +0 -153
- package/dist/tool-validation.d.ts +2 -0
- package/dist/tool-validation.js +15 -0
- package/dist/version.d.ts +2 -2
- package/dist/version.js +1 -1
- package/dist-cjs/client.js +71 -0
- package/dist-cjs/errors.js +146 -0
- package/dist-cjs/index.js +51 -0
- package/dist-cjs/internal/env.js +7 -0
- package/dist-cjs/internal/http.js +108 -0
- package/dist-cjs/internal/output-text.js +15 -0
- package/dist-cjs/internal/query.js +13 -0
- package/dist-cjs/local/context.js +158 -0
- package/dist-cjs/local/core.js +1077 -0
- package/dist-cjs/local/index.js +19 -0
- package/dist-cjs/local/tools.js +380 -0
- package/dist-cjs/local-functions.js +37 -0
- package/dist-cjs/local-skills.js +296 -0
- package/dist-cjs/node-client.js +11 -0
- package/dist-cjs/node.js +32 -0
- package/dist-cjs/package.json +3 -0
- package/dist-cjs/pagination.js +43 -0
- package/dist-cjs/preset-tools.js +91 -0
- package/dist-cjs/resources/auth.js +92 -0
- package/dist-cjs/resources/models.js +13 -0
- package/dist-cjs/resources/presets.js +13 -0
- package/dist-cjs/resources/responses.js +58 -0
- package/dist-cjs/resources/skills-node.js +193 -0
- package/dist-cjs/resources/skills.js +112 -0
- package/dist-cjs/resources/tools.js +13 -0
- package/dist-cjs/resources/volumes.js +114 -0
- package/dist-cjs/streaming.js +42 -0
- package/dist-cjs/tool-validation.js +18 -0
- package/dist-cjs/types/auth.js +2 -0
- package/dist-cjs/types/catalog.js +2 -0
- package/dist-cjs/types/common.js +2 -0
- package/dist-cjs/types/index.js +25 -0
- package/dist-cjs/types/input.js +2 -0
- package/dist-cjs/types/responses.js +2 -0
- package/dist-cjs/types/skills.js +2 -0
- package/dist-cjs/types/streaming.js +2 -0
- package/dist-cjs/types/tools.js +2 -0
- package/dist-cjs/types/volumes.js +2 -0
- package/dist-cjs/version.js +5 -0
- package/package.json +20 -5
|
@@ -0,0 +1,1077 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.LocalSkillStore = exports.LocalWorkspace = exports.LocalWorkspaceManager = exports.LocalConfigStore = exports.LocalFileStore = exports.LocalConfigError = exports.LocalNotTextFileError = exports.LocalFileTooLargeError = exports.LocalIgnoredPathError = exports.LocalPathError = exports.LocalError = void 0;
|
|
7
|
+
exports.createLocalRuntime = createLocalRuntime;
|
|
8
|
+
exports.localAppDirs = localAppDirs;
|
|
9
|
+
exports.classifyLocalPathSensitivity = classifyLocalPathSensitivity;
|
|
10
|
+
const node_crypto_1 = require("node:crypto");
|
|
11
|
+
const node_fs_1 = require("node:fs");
|
|
12
|
+
const promises_1 = require("node:fs/promises");
|
|
13
|
+
const node_os_1 = require("node:os");
|
|
14
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
15
|
+
const local_skills_js_1 = require("../local-skills.js");
|
|
16
|
+
class LocalError extends Error {
|
|
17
|
+
code;
|
|
18
|
+
path;
|
|
19
|
+
constructor(code, message, options = {}) {
|
|
20
|
+
super(message, { cause: options.cause });
|
|
21
|
+
this.name = "LocalError";
|
|
22
|
+
this.code = code;
|
|
23
|
+
this.path = options.path;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
exports.LocalError = LocalError;
|
|
27
|
+
class LocalPathError extends LocalError {
|
|
28
|
+
constructor(message, path) {
|
|
29
|
+
super("local_path_error", message, { path });
|
|
30
|
+
this.name = "LocalPathError";
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
exports.LocalPathError = LocalPathError;
|
|
34
|
+
class LocalIgnoredPathError extends LocalError {
|
|
35
|
+
constructor(path) {
|
|
36
|
+
super("local_ignored_path", `local workspace path is ignored: ${path}`, { path });
|
|
37
|
+
this.name = "LocalIgnoredPathError";
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
exports.LocalIgnoredPathError = LocalIgnoredPathError;
|
|
41
|
+
class LocalFileTooLargeError extends LocalError {
|
|
42
|
+
constructor(path) {
|
|
43
|
+
super("local_file_too_large", `local file is too large: ${path}`, { path });
|
|
44
|
+
this.name = "LocalFileTooLargeError";
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
exports.LocalFileTooLargeError = LocalFileTooLargeError;
|
|
48
|
+
class LocalNotTextFileError extends LocalError {
|
|
49
|
+
constructor(path) {
|
|
50
|
+
super("local_not_text_file", `local file must be text: ${path}`, { path });
|
|
51
|
+
this.name = "LocalNotTextFileError";
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
exports.LocalNotTextFileError = LocalNotTextFileError;
|
|
55
|
+
class LocalConfigError extends LocalError {
|
|
56
|
+
constructor(message, path) {
|
|
57
|
+
super("local_config_error", message, { path });
|
|
58
|
+
this.name = "LocalConfigError";
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
exports.LocalConfigError = LocalConfigError;
|
|
62
|
+
function createLocalRuntime(options) {
|
|
63
|
+
const appName = normalizeAppName(options.appName);
|
|
64
|
+
const dirs = localAppDirs({ ...options, appName });
|
|
65
|
+
const data = new LocalFileStore(dirs.data, { label: "data" });
|
|
66
|
+
const cache = new LocalFileStore(dirs.cache, { label: "cache" });
|
|
67
|
+
const logs = new LocalFileStore(dirs.logs, { label: "logs" });
|
|
68
|
+
const temp = new LocalFileStore(dirs.temp, { label: "temp" });
|
|
69
|
+
const configFiles = new LocalFileStore(dirs.config, { label: "config" });
|
|
70
|
+
return {
|
|
71
|
+
appName,
|
|
72
|
+
dirs,
|
|
73
|
+
files: data,
|
|
74
|
+
data,
|
|
75
|
+
cache,
|
|
76
|
+
logs,
|
|
77
|
+
temp,
|
|
78
|
+
config: new LocalConfigStore(configFiles),
|
|
79
|
+
skills: new LocalSkillStore(data.child("skills")),
|
|
80
|
+
workspaces: new LocalWorkspaceManager(),
|
|
81
|
+
workspace(root, workspaceOptions = {}) {
|
|
82
|
+
return new LocalWorkspace(root, workspaceOptions);
|
|
83
|
+
},
|
|
84
|
+
async ensure() {
|
|
85
|
+
await Promise.all([data.ensure(), cache.ensure(), logs.ensure(), temp.ensure(), configFiles.ensure()]);
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function localAppDirs(options) {
|
|
90
|
+
const appName = normalizeAppName(options.appName);
|
|
91
|
+
const env = options.env ?? process.env;
|
|
92
|
+
const platform = options.platform ?? process.platform;
|
|
93
|
+
const home = node_path_1.default.resolve(env.HOME || env.USERPROFILE || (0, node_os_1.homedir)());
|
|
94
|
+
const baseDir = options.baseDir ? node_path_1.default.resolve(options.baseDir) : "";
|
|
95
|
+
const authorSegment = sanitizePathSegment(options.appAuthor || appName);
|
|
96
|
+
const appSegment = sanitizePathSegment(appName);
|
|
97
|
+
const defaults = defaultDirs(platform, env, home, authorSegment, appSegment, baseDir);
|
|
98
|
+
return {
|
|
99
|
+
home,
|
|
100
|
+
data: node_path_1.default.resolve(options.dirs?.data ?? defaults.data),
|
|
101
|
+
config: node_path_1.default.resolve(options.dirs?.config ?? defaults.config),
|
|
102
|
+
cache: node_path_1.default.resolve(options.dirs?.cache ?? defaults.cache),
|
|
103
|
+
logs: node_path_1.default.resolve(options.dirs?.logs ?? defaults.logs),
|
|
104
|
+
temp: node_path_1.default.resolve(options.dirs?.temp ?? defaults.temp),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
class LocalFileStore {
|
|
108
|
+
root;
|
|
109
|
+
label;
|
|
110
|
+
constructor(root, options = {}) {
|
|
111
|
+
this.root = node_path_1.default.resolve(root);
|
|
112
|
+
this.label = options.label ?? "local";
|
|
113
|
+
}
|
|
114
|
+
child(relativePath, options = {}) {
|
|
115
|
+
return new LocalFileStore(this.resolvePath(relativePath), { label: options.label ?? this.label });
|
|
116
|
+
}
|
|
117
|
+
async ensure() {
|
|
118
|
+
await (0, promises_1.mkdir)(this.root, { recursive: true });
|
|
119
|
+
}
|
|
120
|
+
resolvePath(relativePath = ".") {
|
|
121
|
+
const clean = normalizeRelativePath(relativePath);
|
|
122
|
+
const fullPath = node_path_1.default.resolve(this.root, clean);
|
|
123
|
+
assertInsideRoot(this.root, fullPath);
|
|
124
|
+
return fullPath;
|
|
125
|
+
}
|
|
126
|
+
relativePath(fullPath) {
|
|
127
|
+
const absolute = node_path_1.default.resolve(fullPath);
|
|
128
|
+
assertInsideRoot(this.root, absolute);
|
|
129
|
+
return toPortablePath(node_path_1.default.relative(this.root, absolute));
|
|
130
|
+
}
|
|
131
|
+
async exists(relativePath = ".") {
|
|
132
|
+
try {
|
|
133
|
+
await (0, promises_1.access)(this.resolvePath(relativePath), node_fs_1.constants.F_OK);
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
async stat(relativePath = ".") {
|
|
141
|
+
const fullPath = this.resolvePath(relativePath);
|
|
142
|
+
const info = await (0, promises_1.stat)(fullPath);
|
|
143
|
+
return {
|
|
144
|
+
path: toPortablePath(node_path_1.default.relative(this.root, fullPath)) || ".",
|
|
145
|
+
fullPath,
|
|
146
|
+
type: fileType(info),
|
|
147
|
+
size: info.size,
|
|
148
|
+
modifiedAt: info.mtime,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
async mkdir(relativePath = ".") {
|
|
152
|
+
const fullPath = this.resolvePath(relativePath);
|
|
153
|
+
await (0, promises_1.mkdir)(fullPath, { recursive: true });
|
|
154
|
+
return fullPath;
|
|
155
|
+
}
|
|
156
|
+
async list(relativePath = ".", options = {}) {
|
|
157
|
+
const base = this.resolvePath(relativePath);
|
|
158
|
+
const maxDepth = options.recursive ? options.maxDepth ?? Number.POSITIVE_INFINITY : 1;
|
|
159
|
+
const out = [];
|
|
160
|
+
await this.walk(this.root, base, base, out, maxDepth, options);
|
|
161
|
+
return out.sort((a, b) => a.path.localeCompare(b.path));
|
|
162
|
+
}
|
|
163
|
+
async listEntries(relativePath = ".", options = {}) {
|
|
164
|
+
const stats = await this.list(relativePath, { ...options, includeDirectories: options.includeDirectories ?? true });
|
|
165
|
+
return { object: "list", entries: stats.map(localEntryFromStat) };
|
|
166
|
+
}
|
|
167
|
+
async searchEntries(params) {
|
|
168
|
+
const query = params.query.trim().toLowerCase();
|
|
169
|
+
if (!query) {
|
|
170
|
+
throw new Error("query is required");
|
|
171
|
+
}
|
|
172
|
+
const limit = positiveInt(params.limit, 100);
|
|
173
|
+
const stats = await this.list(params.path ?? ".", { recursive: true, includeDirectories: true });
|
|
174
|
+
const entries = stats
|
|
175
|
+
.filter((item) => item.path.toLowerCase().includes(query))
|
|
176
|
+
.slice(0, limit)
|
|
177
|
+
.map(localEntryFromStat);
|
|
178
|
+
return { object: "list", entries };
|
|
179
|
+
}
|
|
180
|
+
async readText(relativePath) {
|
|
181
|
+
return await (0, promises_1.readFile)(this.resolvePath(relativePath), "utf8");
|
|
182
|
+
}
|
|
183
|
+
async readJSON(relativePath) {
|
|
184
|
+
return JSON.parse(await this.readText(relativePath));
|
|
185
|
+
}
|
|
186
|
+
async readBytes(relativePath) {
|
|
187
|
+
return await (0, promises_1.readFile)(this.resolvePath(relativePath));
|
|
188
|
+
}
|
|
189
|
+
async readFile(relativePath, params = {}) {
|
|
190
|
+
const fullPath = this.resolvePath(relativePath);
|
|
191
|
+
const info = await (0, promises_1.stat)(fullPath);
|
|
192
|
+
if (!info.isFile()) {
|
|
193
|
+
throw new LocalPathError("local path is not a file", relativePath);
|
|
194
|
+
}
|
|
195
|
+
const maxBytes = params.maxBytes ?? Number.POSITIVE_INFINITY;
|
|
196
|
+
const raw = await (0, promises_1.readFile)(fullPath);
|
|
197
|
+
const truncated = Number.isFinite(maxBytes) && raw.byteLength > maxBytes;
|
|
198
|
+
const content = truncated ? raw.subarray(0, maxBytes) : raw;
|
|
199
|
+
const portablePath = toPortablePath(node_path_1.default.relative(this.root, fullPath));
|
|
200
|
+
if ("format" in params && params.format === "raw") {
|
|
201
|
+
return {
|
|
202
|
+
path: portablePath,
|
|
203
|
+
size: info.size,
|
|
204
|
+
truncated,
|
|
205
|
+
content,
|
|
206
|
+
content_type: mimeTypeForPath(portablePath),
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
if (looksBinary(content)) {
|
|
210
|
+
return {
|
|
211
|
+
path: portablePath,
|
|
212
|
+
encoding: "base64",
|
|
213
|
+
mime_type: mimeTypeForPath(portablePath),
|
|
214
|
+
size: info.size,
|
|
215
|
+
truncated,
|
|
216
|
+
content_base64: Buffer.from(content).toString("base64"),
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
return {
|
|
220
|
+
path: portablePath,
|
|
221
|
+
encoding: "text",
|
|
222
|
+
mime_type: mimeTypeForPath(portablePath),
|
|
223
|
+
size: info.size,
|
|
224
|
+
truncated,
|
|
225
|
+
content: content.toString("utf8"),
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
async writeText(relativePath, content, options = {}) {
|
|
229
|
+
const fullPath = this.resolvePath(relativePath);
|
|
230
|
+
await (0, promises_1.mkdir)(node_path_1.default.dirname(fullPath), { recursive: true });
|
|
231
|
+
if (options.atomic === false) {
|
|
232
|
+
await (0, promises_1.writeFile)(fullPath, content, "utf8");
|
|
233
|
+
return fullPath;
|
|
234
|
+
}
|
|
235
|
+
await atomicWrite(fullPath, content);
|
|
236
|
+
return fullPath;
|
|
237
|
+
}
|
|
238
|
+
async writeJSON(relativePath, value, options = {}) {
|
|
239
|
+
const spaces = options.pretty === false ? 0 : 2;
|
|
240
|
+
const text = JSON.stringify(value, null, spaces) + "\n";
|
|
241
|
+
return await this.writeText(relativePath, text, { atomic: options.atomic });
|
|
242
|
+
}
|
|
243
|
+
async writeBytes(relativePath, content, options = {}) {
|
|
244
|
+
const fullPath = this.resolvePath(relativePath);
|
|
245
|
+
await (0, promises_1.mkdir)(node_path_1.default.dirname(fullPath), { recursive: true });
|
|
246
|
+
if (options.atomic === false) {
|
|
247
|
+
await (0, promises_1.writeFile)(fullPath, content);
|
|
248
|
+
return fullPath;
|
|
249
|
+
}
|
|
250
|
+
await atomicWrite(fullPath, content);
|
|
251
|
+
return fullPath;
|
|
252
|
+
}
|
|
253
|
+
async writeFile(relativePath, content, options = {}) {
|
|
254
|
+
const fullPath = typeof content === "string"
|
|
255
|
+
? await this.writeText(relativePath, content, options)
|
|
256
|
+
: await this.writeBytes(relativePath, content, options);
|
|
257
|
+
const info = await (0, promises_1.stat)(fullPath);
|
|
258
|
+
return { path: toPortablePath(node_path_1.default.relative(this.root, fullPath)), size: info.size };
|
|
259
|
+
}
|
|
260
|
+
async remove(relativePath) {
|
|
261
|
+
await (0, promises_1.rm)(this.resolvePath(relativePath), { recursive: true, force: true });
|
|
262
|
+
}
|
|
263
|
+
async deletePath(relativePath) {
|
|
264
|
+
await this.remove(relativePath);
|
|
265
|
+
return { path: normalizeRelativePath(relativePath), recursive: true };
|
|
266
|
+
}
|
|
267
|
+
async createDirectory(relativePath = ".") {
|
|
268
|
+
await this.mkdir(relativePath);
|
|
269
|
+
return { path: normalizeRelativePath(relativePath) };
|
|
270
|
+
}
|
|
271
|
+
async copy(fromRelativePath, toRelativePath) {
|
|
272
|
+
const from = this.resolvePath(fromRelativePath);
|
|
273
|
+
const to = this.resolvePath(toRelativePath);
|
|
274
|
+
await (0, promises_1.mkdir)(node_path_1.default.dirname(to), { recursive: true });
|
|
275
|
+
await (0, promises_1.copyFile)(from, to);
|
|
276
|
+
return to;
|
|
277
|
+
}
|
|
278
|
+
async readLines(relativePath, params) {
|
|
279
|
+
const startLine = Math.trunc(params.startLine);
|
|
280
|
+
const endLine = params.endLine == null ? undefined : Math.trunc(params.endLine);
|
|
281
|
+
const file = await this.readFile(relativePath, { maxBytes: params.maxBytes, format: "raw" });
|
|
282
|
+
if (looksBinary(file.content)) {
|
|
283
|
+
throw new LocalNotTextFileError(relativePath);
|
|
284
|
+
}
|
|
285
|
+
const text = Buffer.from(file.content).toString("utf8");
|
|
286
|
+
const all = splitLines(text);
|
|
287
|
+
const selected = selectLineRange(all, startLine, endLine);
|
|
288
|
+
return {
|
|
289
|
+
path: file.path,
|
|
290
|
+
start_line: startLine,
|
|
291
|
+
end_line: selected.endLine,
|
|
292
|
+
total_lines: all.length,
|
|
293
|
+
lines: selected.lines,
|
|
294
|
+
file_truncated: file.truncated,
|
|
295
|
+
size: file.size,
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
async patchLines(relativePath, params) {
|
|
299
|
+
const startLine = Math.trunc(params.startLine);
|
|
300
|
+
const endLine = params.endLine == null ? undefined : Math.trunc(params.endLine);
|
|
301
|
+
const file = await this.readFile(relativePath, { maxBytes: params.maxBytes, format: "raw" });
|
|
302
|
+
if (file.truncated) {
|
|
303
|
+
throw new LocalFileTooLargeError(relativePath);
|
|
304
|
+
}
|
|
305
|
+
if (looksBinary(file.content)) {
|
|
306
|
+
throw new LocalNotTextFileError(relativePath);
|
|
307
|
+
}
|
|
308
|
+
const original = Buffer.from(file.content).toString("utf8");
|
|
309
|
+
const patched = patchLineRange(original, startLine, endLine, params.replacement ?? "");
|
|
310
|
+
const written = await this.writeFile(relativePath, patched.content);
|
|
311
|
+
return {
|
|
312
|
+
path: file.path,
|
|
313
|
+
start_line: startLine,
|
|
314
|
+
end_line: patched.endLine,
|
|
315
|
+
total_lines: patched.totalLines,
|
|
316
|
+
size: written.size,
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
async grep(params) {
|
|
320
|
+
const pattern = params.pattern.trim();
|
|
321
|
+
if (!pattern) {
|
|
322
|
+
throw new Error("pattern is required");
|
|
323
|
+
}
|
|
324
|
+
const limit = positiveInt(params.limit, 200);
|
|
325
|
+
const maxFiles = positiveInt(params.maxFiles, 500);
|
|
326
|
+
const maxBytesPerFile = positiveInt(params.maxBytesPerFile, 512 * 1024);
|
|
327
|
+
const maxLineLength = positiveInt(params.maxLineLength, 500);
|
|
328
|
+
const stats = await this.list(params.path ?? ".", { recursive: true, ignore: params.ignore });
|
|
329
|
+
const matches = [];
|
|
330
|
+
let filesScanned = 0;
|
|
331
|
+
let scanTruncated = false;
|
|
332
|
+
for (const item of stats) {
|
|
333
|
+
if (matches.length >= limit || filesScanned >= maxFiles) {
|
|
334
|
+
scanTruncated = true;
|
|
335
|
+
break;
|
|
336
|
+
}
|
|
337
|
+
if (item.type !== "file" || item.size > maxBytesPerFile || !isLikelyTextFile(item.path)) {
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
const raw = await (0, promises_1.readFile)(item.fullPath);
|
|
341
|
+
if (looksBinary(raw)) {
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
filesScanned++;
|
|
345
|
+
const lines = splitLines(raw.toString("utf8"));
|
|
346
|
+
for (let i = 0; i < lines.length; i++) {
|
|
347
|
+
if (!lines[i].includes(pattern)) {
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
matches.push({
|
|
351
|
+
path: item.path,
|
|
352
|
+
line_number: i + 1,
|
|
353
|
+
line: lines[i].length > maxLineLength ? lines[i].slice(0, maxLineLength) : lines[i],
|
|
354
|
+
});
|
|
355
|
+
if (matches.length >= limit) {
|
|
356
|
+
scanTruncated = true;
|
|
357
|
+
break;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return { object: "list", matches, files_scanned: filesScanned, scan_truncated: scanTruncated };
|
|
362
|
+
}
|
|
363
|
+
async summarize(params = {}) {
|
|
364
|
+
const maxFiles = positiveInt(params.maxFiles, 2000);
|
|
365
|
+
const maxPreviews = positiveInt(params.maxPreviews, 20);
|
|
366
|
+
const previewBytes = positiveInt(params.previewBytes, 4096);
|
|
367
|
+
const topPaths = positiveInt(params.topPaths, 20);
|
|
368
|
+
const stats = await this.list(params.path ?? ".", { recursive: true, ignore: params.ignore });
|
|
369
|
+
const files = stats.filter((item) => item.type === "file").slice(0, maxFiles);
|
|
370
|
+
const scanTruncated = stats.filter((item) => item.type === "file").length > files.length;
|
|
371
|
+
const totalBytes = files.reduce((sum, item) => sum + item.size, 0);
|
|
372
|
+
const bySize = [...files].sort((a, b) => (b.size !== a.size ? b.size - a.size : a.path.localeCompare(b.path)));
|
|
373
|
+
const previews = [];
|
|
374
|
+
for (const item of bySize) {
|
|
375
|
+
if (previews.length >= maxPreviews) {
|
|
376
|
+
break;
|
|
377
|
+
}
|
|
378
|
+
if (!isLikelyTextFile(item.path) || item.size > previewBytes * 4) {
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
const raw = await (0, promises_1.readFile)(item.fullPath);
|
|
382
|
+
if (looksBinary(raw)) {
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
const truncated = raw.byteLength > previewBytes;
|
|
386
|
+
previews.push({
|
|
387
|
+
path: item.path,
|
|
388
|
+
size: item.size,
|
|
389
|
+
preview: raw.subarray(0, previewBytes).toString("utf8"),
|
|
390
|
+
preview_truncated: truncated || undefined,
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
return {
|
|
394
|
+
summary_path: "",
|
|
395
|
+
file_count: files.length,
|
|
396
|
+
total_bytes: totalBytes,
|
|
397
|
+
top_paths_by_size: bySize.slice(0, topPaths).map((item) => `${item.path} (${item.size} bytes)`),
|
|
398
|
+
text_previews: previews,
|
|
399
|
+
generated_at_unix: Math.floor(Date.now() / 1000),
|
|
400
|
+
scan_truncated: scanTruncated,
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
async walk(storeRoot, scanRoot, dir, out, maxDepth, options) {
|
|
404
|
+
const entries = await (0, promises_1.readdir)(dir, { withFileTypes: true });
|
|
405
|
+
for (const entry of entries) {
|
|
406
|
+
const fullPath = node_path_1.default.join(dir, entry.name);
|
|
407
|
+
const relativePath = toPortablePath(node_path_1.default.relative(storeRoot, fullPath));
|
|
408
|
+
if (ignored(relativePath, options.ignore)) {
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
const info = await (0, promises_1.stat)(fullPath);
|
|
412
|
+
const item = {
|
|
413
|
+
path: relativePath,
|
|
414
|
+
fullPath,
|
|
415
|
+
type: fileType(info),
|
|
416
|
+
size: info.size,
|
|
417
|
+
modifiedAt: info.mtime,
|
|
418
|
+
};
|
|
419
|
+
if (entry.isDirectory()) {
|
|
420
|
+
if (options.includeDirectories) {
|
|
421
|
+
out.push(item);
|
|
422
|
+
}
|
|
423
|
+
const depth = toPortablePath(node_path_1.default.relative(scanRoot, fullPath)).split("/").filter(Boolean).length;
|
|
424
|
+
if (depth < maxDepth) {
|
|
425
|
+
await this.walk(storeRoot, scanRoot, fullPath, out, maxDepth, options);
|
|
426
|
+
}
|
|
427
|
+
continue;
|
|
428
|
+
}
|
|
429
|
+
out.push(item);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
exports.LocalFileStore = LocalFileStore;
|
|
434
|
+
class LocalConfigStore {
|
|
435
|
+
files;
|
|
436
|
+
constructor(files) {
|
|
437
|
+
this.files = files;
|
|
438
|
+
}
|
|
439
|
+
async read(name = "settings.json", fallback) {
|
|
440
|
+
try {
|
|
441
|
+
return await this.files.readJSON(name);
|
|
442
|
+
}
|
|
443
|
+
catch (error) {
|
|
444
|
+
if (error?.code === "ENOENT" && arguments.length >= 2) {
|
|
445
|
+
return fallback;
|
|
446
|
+
}
|
|
447
|
+
throw error;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
async write(name, value) {
|
|
451
|
+
return await this.files.writeJSON(name, value);
|
|
452
|
+
}
|
|
453
|
+
async get(name, key, fallback) {
|
|
454
|
+
const config = await this.readRecord(name);
|
|
455
|
+
return (key in config ? config[key] : fallback);
|
|
456
|
+
}
|
|
457
|
+
async set(name, key, value) {
|
|
458
|
+
const config = await this.readRecord(name);
|
|
459
|
+
config[key] = value;
|
|
460
|
+
return await this.write(name, config);
|
|
461
|
+
}
|
|
462
|
+
async delete(name, key) {
|
|
463
|
+
const config = await this.readRecord(name);
|
|
464
|
+
delete config[key];
|
|
465
|
+
return await this.write(name, config);
|
|
466
|
+
}
|
|
467
|
+
async readRecord(name) {
|
|
468
|
+
const value = await this.read(name, {});
|
|
469
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
470
|
+
throw new LocalConfigError(`local config ${name} must contain a JSON object`, name);
|
|
471
|
+
}
|
|
472
|
+
return value;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
exports.LocalConfigStore = LocalConfigStore;
|
|
476
|
+
class LocalWorkspaceManager {
|
|
477
|
+
open(root, options = {}) {
|
|
478
|
+
return new LocalWorkspace(root, options);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
exports.LocalWorkspaceManager = LocalWorkspaceManager;
|
|
482
|
+
class LocalWorkspace {
|
|
483
|
+
root;
|
|
484
|
+
name;
|
|
485
|
+
metadata;
|
|
486
|
+
trusted;
|
|
487
|
+
files;
|
|
488
|
+
ignore;
|
|
489
|
+
gitignore;
|
|
490
|
+
maxFileBytes;
|
|
491
|
+
constructor(root, options = {}) {
|
|
492
|
+
this.root = node_path_1.default.resolve(root);
|
|
493
|
+
this.name = options.name?.trim() || node_path_1.default.basename(this.root) || "workspace";
|
|
494
|
+
this.metadata = { ...(options.metadata ?? {}) };
|
|
495
|
+
this.trusted = options.trusted ?? false;
|
|
496
|
+
this.files = new LocalFileStore(this.root, { label: "workspace" });
|
|
497
|
+
this.ignore = [...defaultWorkspaceIgnoreRules(), ...(options.ignore ?? [])];
|
|
498
|
+
this.gitignore = options.gitignore ?? true;
|
|
499
|
+
this.maxFileBytes = positiveInt(options.maxFileBytes, 10 * 1024 * 1024);
|
|
500
|
+
}
|
|
501
|
+
async ensure() {
|
|
502
|
+
await this.files.ensure();
|
|
503
|
+
if (this.gitignore) {
|
|
504
|
+
await this.loadIgnoreFiles();
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
async loadIgnoreFiles(files = [".gitignore"]) {
|
|
508
|
+
const loaded = [];
|
|
509
|
+
for (const file of files) {
|
|
510
|
+
try {
|
|
511
|
+
const text = await this.files.readText(file);
|
|
512
|
+
loaded.push(...parseIgnoreFile(text));
|
|
513
|
+
}
|
|
514
|
+
catch (error) {
|
|
515
|
+
if (error?.code !== "ENOENT") {
|
|
516
|
+
throw error;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
this.ignore.push(...loaded);
|
|
521
|
+
return loaded;
|
|
522
|
+
}
|
|
523
|
+
child(relativePath, options = {}) {
|
|
524
|
+
return new LocalWorkspace(this.files.resolvePath(relativePath), {
|
|
525
|
+
name: options.name,
|
|
526
|
+
metadata: options.metadata ?? this.metadata,
|
|
527
|
+
trusted: options.trusted ?? this.trusted,
|
|
528
|
+
ignore: options.ignore ?? this.ignore,
|
|
529
|
+
gitignore: options.gitignore ?? this.gitignore,
|
|
530
|
+
maxFileBytes: options.maxFileBytes ?? this.maxFileBytes,
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
resolvePath(relativePath = ".") {
|
|
534
|
+
this.assertAllowed(relativePath);
|
|
535
|
+
return this.files.resolvePath(relativePath);
|
|
536
|
+
}
|
|
537
|
+
async list(relativePath = ".", options = {}) {
|
|
538
|
+
return await this.files.list(relativePath, this.scopedListOptions(options));
|
|
539
|
+
}
|
|
540
|
+
async listEntries(relativePath = ".", options = {}) {
|
|
541
|
+
return await this.files.listEntries(relativePath, this.scopedListOptions(options));
|
|
542
|
+
}
|
|
543
|
+
async searchEntries(params) {
|
|
544
|
+
const query = params.query.trim().toLowerCase();
|
|
545
|
+
if (!query) {
|
|
546
|
+
throw new Error("query is required");
|
|
547
|
+
}
|
|
548
|
+
const limit = positiveInt(params.limit, 100);
|
|
549
|
+
const stats = await this.list(params.path ?? ".", { recursive: true, includeDirectories: true });
|
|
550
|
+
return {
|
|
551
|
+
object: "list",
|
|
552
|
+
entries: stats
|
|
553
|
+
.filter((item) => item.path.toLowerCase().includes(query))
|
|
554
|
+
.slice(0, limit)
|
|
555
|
+
.map(localEntryFromStat),
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
async readFile(relativePath, params = {}) {
|
|
559
|
+
this.assertAllowed(relativePath);
|
|
560
|
+
return await this.files.readFile(relativePath, { maxBytes: params.maxBytes ?? this.maxFileBytes, ...params });
|
|
561
|
+
}
|
|
562
|
+
async writeFile(relativePath, content, options = {}) {
|
|
563
|
+
this.assertAllowed(relativePath);
|
|
564
|
+
return await this.files.writeFile(relativePath, content, options);
|
|
565
|
+
}
|
|
566
|
+
async readText(relativePath) {
|
|
567
|
+
this.assertAllowed(relativePath);
|
|
568
|
+
return await this.files.readText(relativePath);
|
|
569
|
+
}
|
|
570
|
+
async writeText(relativePath, content, options = {}) {
|
|
571
|
+
this.assertAllowed(relativePath);
|
|
572
|
+
return await this.files.writeText(relativePath, content, options);
|
|
573
|
+
}
|
|
574
|
+
async deletePath(relativePath) {
|
|
575
|
+
this.assertAllowed(relativePath);
|
|
576
|
+
return await this.files.deletePath(relativePath);
|
|
577
|
+
}
|
|
578
|
+
async createDirectory(relativePath = ".") {
|
|
579
|
+
this.assertAllowed(relativePath);
|
|
580
|
+
return await this.files.createDirectory(relativePath);
|
|
581
|
+
}
|
|
582
|
+
async readLines(relativePath, params) {
|
|
583
|
+
this.assertAllowed(relativePath);
|
|
584
|
+
return await this.files.readLines(relativePath, { maxBytes: params.maxBytes ?? this.maxFileBytes, ...params });
|
|
585
|
+
}
|
|
586
|
+
async previewPatchLines(relativePath, params) {
|
|
587
|
+
this.assertAllowed(relativePath);
|
|
588
|
+
const lines = await this.readLines(relativePath, {
|
|
589
|
+
startLine: params.startLine,
|
|
590
|
+
endLine: params.endLine,
|
|
591
|
+
maxBytes: params.maxBytes ?? this.maxFileBytes,
|
|
592
|
+
});
|
|
593
|
+
const file = await this.files.readFile(relativePath, { maxBytes: params.maxBytes ?? this.maxFileBytes, format: "raw" });
|
|
594
|
+
if (file.truncated) {
|
|
595
|
+
throw new Error("local file is too large to patch");
|
|
596
|
+
}
|
|
597
|
+
if (looksBinary(file.content)) {
|
|
598
|
+
throw new Error("local file must be text");
|
|
599
|
+
}
|
|
600
|
+
const patched = patchLineRange(Buffer.from(file.content).toString("utf8"), params.startLine, params.endLine, params.replacement ?? "");
|
|
601
|
+
return {
|
|
602
|
+
path: lines.path,
|
|
603
|
+
start_line: lines.start_line,
|
|
604
|
+
end_line: lines.end_line,
|
|
605
|
+
total_lines: patched.totalLines,
|
|
606
|
+
before: lines.lines,
|
|
607
|
+
after: params.replacement === "" ? [] : splitLines(params.replacement ?? ""),
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
async patchLines(relativePath, params) {
|
|
611
|
+
this.assertAllowed(relativePath);
|
|
612
|
+
return await this.files.patchLines(relativePath, { maxBytes: params.maxBytes ?? this.maxFileBytes, ...params });
|
|
613
|
+
}
|
|
614
|
+
async previewEdits(edits) {
|
|
615
|
+
const previews = [];
|
|
616
|
+
for (const edit of edits) {
|
|
617
|
+
await this.assertExpectedHash(edit.path, edit.expectedSha256);
|
|
618
|
+
previews.push(await this.previewPatchLines(edit.path, edit));
|
|
619
|
+
}
|
|
620
|
+
return { edits: edits.map((edit) => ({ ...edit })), previews };
|
|
621
|
+
}
|
|
622
|
+
async applyEdits(edits) {
|
|
623
|
+
const backups = [];
|
|
624
|
+
const applied = [];
|
|
625
|
+
try {
|
|
626
|
+
for (const edit of edits) {
|
|
627
|
+
await this.assertExpectedHash(edit.path, edit.expectedSha256);
|
|
628
|
+
backups.push({ path: edit.path, content: await this.readText(edit.path) });
|
|
629
|
+
applied.push(await this.patchLines(edit.path, edit));
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
catch (error) {
|
|
633
|
+
for (const backup of backups.reverse()) {
|
|
634
|
+
await this.writeText(backup.path, backup.content);
|
|
635
|
+
}
|
|
636
|
+
throw error;
|
|
637
|
+
}
|
|
638
|
+
return { applied, backups };
|
|
639
|
+
}
|
|
640
|
+
classifyPath(relativePath) {
|
|
641
|
+
return classifyLocalPathSensitivity(relativePath);
|
|
642
|
+
}
|
|
643
|
+
async grep(params) {
|
|
644
|
+
return await this.files.grep({ ...params, ignore: this.mergeIgnore(params.ignore) });
|
|
645
|
+
}
|
|
646
|
+
async summarize(params = {}) {
|
|
647
|
+
return await this.files.summarize({ ...params, ignore: this.mergeIgnore(params.ignore) });
|
|
648
|
+
}
|
|
649
|
+
async snapshot(params = {}) {
|
|
650
|
+
const maxBytes = positiveInt(params.maxBytesPerFile, this.maxFileBytes);
|
|
651
|
+
const shouldHash = params.hash ?? true;
|
|
652
|
+
const stats = await this.list(params.path ?? ".", { recursive: true });
|
|
653
|
+
const files = [];
|
|
654
|
+
for (const item of stats) {
|
|
655
|
+
if (item.type !== "file") {
|
|
656
|
+
continue;
|
|
657
|
+
}
|
|
658
|
+
const snap = {
|
|
659
|
+
path: item.path,
|
|
660
|
+
size: item.size,
|
|
661
|
+
modified_at_unix: Math.floor(item.modifiedAt.getTime() / 1000),
|
|
662
|
+
};
|
|
663
|
+
if (shouldHash && item.size <= maxBytes) {
|
|
664
|
+
snap.sha256 = (0, node_crypto_1.createHash)("sha256").update(await (0, promises_1.readFile)(item.fullPath)).digest("hex");
|
|
665
|
+
}
|
|
666
|
+
files.push(snap);
|
|
667
|
+
}
|
|
668
|
+
return {
|
|
669
|
+
root: this.root,
|
|
670
|
+
name: this.name,
|
|
671
|
+
generated_at_unix: Math.floor(Date.now() / 1000),
|
|
672
|
+
files,
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
diff(before, after) {
|
|
676
|
+
const beforeByPath = new Map(before.files.map((file) => [file.path, file]));
|
|
677
|
+
const afterByPath = new Map(after.files.map((file) => [file.path, file]));
|
|
678
|
+
const added = [];
|
|
679
|
+
const modified = [];
|
|
680
|
+
const deleted = [];
|
|
681
|
+
const unchanged = [];
|
|
682
|
+
for (const afterFile of after.files) {
|
|
683
|
+
const beforeFile = beforeByPath.get(afterFile.path);
|
|
684
|
+
if (!beforeFile) {
|
|
685
|
+
added.push(afterFile);
|
|
686
|
+
}
|
|
687
|
+
else if (snapshotFileChanged(beforeFile, afterFile)) {
|
|
688
|
+
modified.push({ before: beforeFile, after: afterFile });
|
|
689
|
+
}
|
|
690
|
+
else {
|
|
691
|
+
unchanged.push(afterFile);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
for (const beforeFile of before.files) {
|
|
695
|
+
if (!afterByPath.has(beforeFile.path)) {
|
|
696
|
+
deleted.push(beforeFile);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
return { added, modified, deleted, unchanged };
|
|
700
|
+
}
|
|
701
|
+
watch(onEvent, options = {}) {
|
|
702
|
+
const watcher = (0, node_fs_1.watch)(this.root, { recursive: options.recursive ?? process.platform !== "linux" }, (eventType, filename) => {
|
|
703
|
+
const rel = filename ? toPortablePath(String(filename)) : ".";
|
|
704
|
+
if (ignored(rel, this.ignore)) {
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
onEvent({ type: eventType === "rename" ? "rename" : "change", path: rel });
|
|
708
|
+
});
|
|
709
|
+
return { close: () => watcher.close() };
|
|
710
|
+
}
|
|
711
|
+
scopedListOptions(options) {
|
|
712
|
+
return { ...options, ignore: this.mergeIgnore(options.ignore) };
|
|
713
|
+
}
|
|
714
|
+
mergeIgnore(ignore) {
|
|
715
|
+
return [...this.ignore, ...(ignore ?? [])];
|
|
716
|
+
}
|
|
717
|
+
assertAllowed(relativePath) {
|
|
718
|
+
const rel = normalizeRelativePath(relativePath);
|
|
719
|
+
if (rel !== "." && ignored(rel, this.ignore)) {
|
|
720
|
+
throw new LocalIgnoredPathError(rel);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
async assertExpectedHash(relativePath, expectedSha256) {
|
|
724
|
+
if (!expectedSha256) {
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
const raw = await (0, promises_1.readFile)(this.files.resolvePath(relativePath));
|
|
728
|
+
const actual = (0, node_crypto_1.createHash)("sha256").update(raw).digest("hex");
|
|
729
|
+
if (actual !== expectedSha256) {
|
|
730
|
+
throw new LocalError("local_edit_conflict", `local file changed before edit: ${relativePath}`, { path: relativePath });
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
exports.LocalWorkspace = LocalWorkspace;
|
|
735
|
+
class LocalSkillStore {
|
|
736
|
+
files;
|
|
737
|
+
constructor(files) {
|
|
738
|
+
this.files = files;
|
|
739
|
+
}
|
|
740
|
+
async fromDirectory(rootDir, options = {}) {
|
|
741
|
+
return await (0, local_skills_js_1.localSkillFromDirectory)(rootDir, options);
|
|
742
|
+
}
|
|
743
|
+
async discover(options = {}) {
|
|
744
|
+
const roots = options.roots?.length ? options.roots : [this.files.root];
|
|
745
|
+
const skillDirs = [];
|
|
746
|
+
for (const root of roots) {
|
|
747
|
+
skillDirs.push(...await discoverSkillDirectories(node_path_1.default.resolve(root), options));
|
|
748
|
+
}
|
|
749
|
+
const unique = [...new Set(skillDirs)].sort((a, b) => a.localeCompare(b));
|
|
750
|
+
return await Promise.all(unique.map((dir) => (0, local_skills_js_1.localSkillFromDirectory)(dir, options)));
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
exports.LocalSkillStore = LocalSkillStore;
|
|
754
|
+
function defaultDirs(platform, env, home, authorSegment, appSegment, baseDir) {
|
|
755
|
+
if (baseDir) {
|
|
756
|
+
return {
|
|
757
|
+
data: node_path_1.default.join(baseDir, "data"),
|
|
758
|
+
config: node_path_1.default.join(baseDir, "config"),
|
|
759
|
+
cache: node_path_1.default.join(baseDir, "cache"),
|
|
760
|
+
logs: node_path_1.default.join(baseDir, "logs"),
|
|
761
|
+
temp: node_path_1.default.join(baseDir, "tmp"),
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
if (platform === "darwin") {
|
|
765
|
+
return {
|
|
766
|
+
data: node_path_1.default.join(home, "Library", "Application Support", appSegment),
|
|
767
|
+
config: node_path_1.default.join(home, "Library", "Application Support", appSegment),
|
|
768
|
+
cache: node_path_1.default.join(home, "Library", "Caches", appSegment),
|
|
769
|
+
logs: node_path_1.default.join(home, "Library", "Logs", appSegment),
|
|
770
|
+
temp: node_path_1.default.join((0, node_os_1.tmpdir)(), appSegment),
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
if (platform === "win32") {
|
|
774
|
+
const roaming = env.APPDATA || node_path_1.default.join(home, "AppData", "Roaming");
|
|
775
|
+
const local = env.LOCALAPPDATA || node_path_1.default.join(home, "AppData", "Local");
|
|
776
|
+
return {
|
|
777
|
+
data: node_path_1.default.join(roaming, authorSegment, appSegment),
|
|
778
|
+
config: node_path_1.default.join(roaming, authorSegment, appSegment),
|
|
779
|
+
cache: node_path_1.default.join(local, authorSegment, appSegment, "Cache"),
|
|
780
|
+
logs: node_path_1.default.join(local, authorSegment, appSegment, "Logs"),
|
|
781
|
+
temp: node_path_1.default.join((0, node_os_1.tmpdir)(), appSegment),
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
return {
|
|
785
|
+
data: node_path_1.default.join(env.XDG_DATA_HOME || node_path_1.default.join(home, ".local", "share"), appSegment),
|
|
786
|
+
config: node_path_1.default.join(env.XDG_CONFIG_HOME || node_path_1.default.join(home, ".config"), appSegment),
|
|
787
|
+
cache: node_path_1.default.join(env.XDG_CACHE_HOME || node_path_1.default.join(home, ".cache"), appSegment),
|
|
788
|
+
logs: node_path_1.default.join(env.XDG_STATE_HOME || node_path_1.default.join(home, ".local", "state"), appSegment, "logs"),
|
|
789
|
+
temp: node_path_1.default.join((0, node_os_1.tmpdir)(), appSegment),
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
async function discoverSkillDirectories(root, options) {
|
|
793
|
+
try {
|
|
794
|
+
const info = await (0, promises_1.stat)(root);
|
|
795
|
+
if (!info.isDirectory()) {
|
|
796
|
+
return [];
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
catch (error) {
|
|
800
|
+
if (error?.code === "ENOENT") {
|
|
801
|
+
return [];
|
|
802
|
+
}
|
|
803
|
+
throw error;
|
|
804
|
+
}
|
|
805
|
+
const out = [];
|
|
806
|
+
const maxDepth = options.recursive ? options.maxDepth ?? Number.POSITIVE_INFINITY : 1;
|
|
807
|
+
await walkSkillDirectories(root, root, out, maxDepth);
|
|
808
|
+
return out;
|
|
809
|
+
}
|
|
810
|
+
async function walkSkillDirectories(root, dir, out, maxDepth) {
|
|
811
|
+
if (await fileExists(node_path_1.default.join(dir, "SKILL.md"))) {
|
|
812
|
+
out.push(dir);
|
|
813
|
+
}
|
|
814
|
+
const relative = node_path_1.default.relative(root, dir);
|
|
815
|
+
const depth = relative ? relative.split(node_path_1.default.sep).length : 0;
|
|
816
|
+
if (depth >= maxDepth) {
|
|
817
|
+
return;
|
|
818
|
+
}
|
|
819
|
+
const entries = await (0, promises_1.readdir)(dir, { withFileTypes: true });
|
|
820
|
+
for (const entry of entries) {
|
|
821
|
+
if (!entry.isDirectory() || ignoredDirectoryName(entry.name)) {
|
|
822
|
+
continue;
|
|
823
|
+
}
|
|
824
|
+
await walkSkillDirectories(root, node_path_1.default.join(dir, entry.name), out, maxDepth);
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
async function fileExists(fullPath) {
|
|
828
|
+
try {
|
|
829
|
+
const info = await (0, promises_1.stat)(fullPath);
|
|
830
|
+
return info.isFile();
|
|
831
|
+
}
|
|
832
|
+
catch {
|
|
833
|
+
return false;
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
function normalizeAppName(appName) {
|
|
837
|
+
const trimmed = appName.trim();
|
|
838
|
+
if (!trimmed) {
|
|
839
|
+
throw new LocalConfigError("appName is required");
|
|
840
|
+
}
|
|
841
|
+
return trimmed;
|
|
842
|
+
}
|
|
843
|
+
function sanitizePathSegment(value) {
|
|
844
|
+
return value
|
|
845
|
+
.trim()
|
|
846
|
+
.toLowerCase()
|
|
847
|
+
.replace(/[^a-z0-9._-]+/g, "-")
|
|
848
|
+
.replace(/^-+|-+$/g, "") || "agent-api";
|
|
849
|
+
}
|
|
850
|
+
function normalizeRelativePath(value) {
|
|
851
|
+
const trimmed = value.trim();
|
|
852
|
+
if (!trimmed || trimmed === ".") {
|
|
853
|
+
return ".";
|
|
854
|
+
}
|
|
855
|
+
if (node_path_1.default.isAbsolute(trimmed)) {
|
|
856
|
+
throw new LocalPathError("local path must be relative", value);
|
|
857
|
+
}
|
|
858
|
+
return trimmed;
|
|
859
|
+
}
|
|
860
|
+
function assertInsideRoot(root, fullPath) {
|
|
861
|
+
const relative = node_path_1.default.relative(root, fullPath);
|
|
862
|
+
if (relative.startsWith("..") || node_path_1.default.isAbsolute(relative)) {
|
|
863
|
+
throw new LocalPathError("local path must stay inside the store root", fullPath);
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
function toPortablePath(value) {
|
|
867
|
+
return value.split(node_path_1.default.sep).join("/");
|
|
868
|
+
}
|
|
869
|
+
function fileType(info) {
|
|
870
|
+
if (info.isFile())
|
|
871
|
+
return "file";
|
|
872
|
+
if (info.isDirectory())
|
|
873
|
+
return "directory";
|
|
874
|
+
if (info.isSymbolicLink())
|
|
875
|
+
return "symlink";
|
|
876
|
+
return "other";
|
|
877
|
+
}
|
|
878
|
+
function localEntryFromStat(item) {
|
|
879
|
+
return {
|
|
880
|
+
path: item.path,
|
|
881
|
+
is_dir: item.type === "directory",
|
|
882
|
+
size: item.type === "directory" ? 0 : item.size,
|
|
883
|
+
modified_at_unix: Math.floor(item.modifiedAt.getTime() / 1000),
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
async function atomicWrite(fullPath, content) {
|
|
887
|
+
const tmpPath = node_path_1.default.join(node_path_1.default.dirname(fullPath), `.${node_path_1.default.basename(fullPath)}.${process.pid}.${(0, node_crypto_1.randomUUID)()}.tmp`);
|
|
888
|
+
await (0, promises_1.writeFile)(tmpPath, content);
|
|
889
|
+
await (0, promises_1.rename)(tmpPath, fullPath);
|
|
890
|
+
}
|
|
891
|
+
function ignored(relativePath, ignore) {
|
|
892
|
+
if (!ignore?.length) {
|
|
893
|
+
return false;
|
|
894
|
+
}
|
|
895
|
+
return ignore.some((rule) => {
|
|
896
|
+
if (typeof rule === "string") {
|
|
897
|
+
return relativePath === rule || relativePath.startsWith(`${rule}/`) || relativePath.endsWith(`/${rule}`) || relativePath.includes(`/${rule}/`);
|
|
898
|
+
}
|
|
899
|
+
if (rule instanceof RegExp)
|
|
900
|
+
return rule.test(relativePath);
|
|
901
|
+
return rule(relativePath);
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
function ignoredDirectoryName(name) {
|
|
905
|
+
return name === ".git" || name === "node_modules" || name === "__pycache__";
|
|
906
|
+
}
|
|
907
|
+
function defaultWorkspaceIgnoreRules() {
|
|
908
|
+
return [
|
|
909
|
+
".git",
|
|
910
|
+
"node_modules",
|
|
911
|
+
"__pycache__",
|
|
912
|
+
".DS_Store",
|
|
913
|
+
"dist",
|
|
914
|
+
"build",
|
|
915
|
+
"coverage",
|
|
916
|
+
".next",
|
|
917
|
+
".turbo",
|
|
918
|
+
".cache",
|
|
919
|
+
/\.pyc$/,
|
|
920
|
+
/\.pyo$/,
|
|
921
|
+
/\.class$/,
|
|
922
|
+
/\.log$/,
|
|
923
|
+
];
|
|
924
|
+
}
|
|
925
|
+
function parseIgnoreFile(text) {
|
|
926
|
+
const rules = [];
|
|
927
|
+
for (const rawLine of text.split(/\r?\n/)) {
|
|
928
|
+
let line = rawLine.trim();
|
|
929
|
+
if (!line || line.startsWith("#")) {
|
|
930
|
+
continue;
|
|
931
|
+
}
|
|
932
|
+
if (line.startsWith("!")) {
|
|
933
|
+
continue;
|
|
934
|
+
}
|
|
935
|
+
line = line.replace(/\\/g, "/");
|
|
936
|
+
if (line.startsWith("/")) {
|
|
937
|
+
line = line.slice(1);
|
|
938
|
+
}
|
|
939
|
+
if (line.endsWith("/")) {
|
|
940
|
+
line = line.slice(0, -1);
|
|
941
|
+
}
|
|
942
|
+
if (!line || line.includes("*")) {
|
|
943
|
+
rules.push(globIgnoreRule(line));
|
|
944
|
+
continue;
|
|
945
|
+
}
|
|
946
|
+
rules.push(line);
|
|
947
|
+
}
|
|
948
|
+
return rules;
|
|
949
|
+
}
|
|
950
|
+
function globIgnoreRule(pattern) {
|
|
951
|
+
const escaped = pattern
|
|
952
|
+
.split("*")
|
|
953
|
+
.map((part) => part.replace(/[.+?^${}()|[\]\\]/g, "\\$&"))
|
|
954
|
+
.join(".*");
|
|
955
|
+
const regex = new RegExp(`(^|/)${escaped}($|/)`);
|
|
956
|
+
return (relativePath) => regex.test(relativePath);
|
|
957
|
+
}
|
|
958
|
+
function snapshotFileChanged(before, after) {
|
|
959
|
+
if (before.sha256 || after.sha256) {
|
|
960
|
+
return before.sha256 !== after.sha256;
|
|
961
|
+
}
|
|
962
|
+
return before.size !== after.size || before.modified_at_unix !== after.modified_at_unix;
|
|
963
|
+
}
|
|
964
|
+
function classifyLocalPathSensitivity(relativePath) {
|
|
965
|
+
const rel = normalizeRelativePath(relativePath);
|
|
966
|
+
const lower = rel.toLowerCase();
|
|
967
|
+
const base = node_path_1.default.basename(lower);
|
|
968
|
+
if (base === ".env" ||
|
|
969
|
+
base.startsWith(".env.") ||
|
|
970
|
+
lower.includes("id_rsa") ||
|
|
971
|
+
lower.includes("id_ed25519") ||
|
|
972
|
+
lower.endsWith(".pem") ||
|
|
973
|
+
lower.endsWith(".key") ||
|
|
974
|
+
lower.endsWith(".p12") ||
|
|
975
|
+
lower.endsWith(".pfx")) {
|
|
976
|
+
return { path: rel, sensitivity: "secret", reason: "path commonly contains credentials or private keys" };
|
|
977
|
+
}
|
|
978
|
+
if (lower.includes("secret") ||
|
|
979
|
+
lower.includes("token") ||
|
|
980
|
+
lower.includes("credential") ||
|
|
981
|
+
lower.includes("password") ||
|
|
982
|
+
lower.endsWith(".crt") ||
|
|
983
|
+
lower.endsWith(".cert")) {
|
|
984
|
+
return { path: rel, sensitivity: "sensitive", reason: "path name suggests sensitive material" };
|
|
985
|
+
}
|
|
986
|
+
return { path: rel, sensitivity: "normal" };
|
|
987
|
+
}
|
|
988
|
+
function positiveInt(value, fallback) {
|
|
989
|
+
const parsed = Number(value);
|
|
990
|
+
return Number.isFinite(parsed) && parsed > 0 ? Math.trunc(parsed) : fallback;
|
|
991
|
+
}
|
|
992
|
+
function looksBinary(content) {
|
|
993
|
+
return content.includes(0);
|
|
994
|
+
}
|
|
995
|
+
const textExtensions = new Set([
|
|
996
|
+
".txt", ".md", ".markdown", ".json", ".yaml", ".yml", ".toml", ".xml", ".csv",
|
|
997
|
+
".py", ".go", ".js", ".mjs", ".cjs", ".ts", ".tsx", ".jsx", ".html", ".htm",
|
|
998
|
+
".css", ".scss", ".sh", ".bash", ".zsh", ".sql", ".env", ".ini", ".cfg",
|
|
999
|
+
".conf", ".log", ".rst", ".adoc", ".gradle", ".properties", ".mod", ".sum",
|
|
1000
|
+
".dockerfile",
|
|
1001
|
+
]);
|
|
1002
|
+
function isLikelyTextFile(relativePath) {
|
|
1003
|
+
const lower = relativePath.toLowerCase();
|
|
1004
|
+
const ext = node_path_1.default.extname(lower);
|
|
1005
|
+
return !ext || textExtensions.has(ext) || lower.endsWith("dockerfile");
|
|
1006
|
+
}
|
|
1007
|
+
function mimeTypeForPath(relativePath) {
|
|
1008
|
+
const ext = node_path_1.default.extname(relativePath).toLowerCase();
|
|
1009
|
+
switch (ext) {
|
|
1010
|
+
case ".json":
|
|
1011
|
+
return "application/json";
|
|
1012
|
+
case ".md":
|
|
1013
|
+
case ".markdown":
|
|
1014
|
+
return "text/markdown";
|
|
1015
|
+
case ".html":
|
|
1016
|
+
case ".htm":
|
|
1017
|
+
return "text/html";
|
|
1018
|
+
case ".css":
|
|
1019
|
+
return "text/css";
|
|
1020
|
+
case ".csv":
|
|
1021
|
+
return "text/csv";
|
|
1022
|
+
case ".xml":
|
|
1023
|
+
return "application/xml";
|
|
1024
|
+
case ".png":
|
|
1025
|
+
return "image/png";
|
|
1026
|
+
case ".jpg":
|
|
1027
|
+
case ".jpeg":
|
|
1028
|
+
return "image/jpeg";
|
|
1029
|
+
case ".gif":
|
|
1030
|
+
return "image/gif";
|
|
1031
|
+
case ".svg":
|
|
1032
|
+
return "image/svg+xml";
|
|
1033
|
+
case ".pdf":
|
|
1034
|
+
return "application/pdf";
|
|
1035
|
+
default:
|
|
1036
|
+
return isLikelyTextFile(relativePath) ? "text/plain" : "application/octet-stream";
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
function splitLines(content) {
|
|
1040
|
+
if (content.length === 0) {
|
|
1041
|
+
return [""];
|
|
1042
|
+
}
|
|
1043
|
+
let text = content;
|
|
1044
|
+
if (text.endsWith("\n")) {
|
|
1045
|
+
text = text.slice(0, -1);
|
|
1046
|
+
if (text === "") {
|
|
1047
|
+
return [""];
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
return text.split("\n");
|
|
1051
|
+
}
|
|
1052
|
+
function selectLineRange(lines, startLine, endLine) {
|
|
1053
|
+
if (startLine < 1) {
|
|
1054
|
+
throw new Error("startLine must be >= 1");
|
|
1055
|
+
}
|
|
1056
|
+
const total = lines.length || 1;
|
|
1057
|
+
const end = endLine == null || endLine <= 0 ? total : Math.min(endLine, total);
|
|
1058
|
+
if (startLine > total || end < startLine) {
|
|
1059
|
+
throw new Error("invalid line range");
|
|
1060
|
+
}
|
|
1061
|
+
return { lines: lines.slice(startLine - 1, end), endLine: end };
|
|
1062
|
+
}
|
|
1063
|
+
function patchLineRange(content, startLine, endLine, replacement) {
|
|
1064
|
+
const lines = splitLines(content);
|
|
1065
|
+
const selected = selectLineRange(lines, startLine, endLine);
|
|
1066
|
+
const replacementLines = replacement === "" ? [] : splitLines(replacement);
|
|
1067
|
+
const patched = [
|
|
1068
|
+
...lines.slice(0, startLine - 1),
|
|
1069
|
+
...replacementLines,
|
|
1070
|
+
...lines.slice(selected.endLine),
|
|
1071
|
+
];
|
|
1072
|
+
let out = patched.join("\n");
|
|
1073
|
+
if (content.endsWith("\n")) {
|
|
1074
|
+
out += "\n";
|
|
1075
|
+
}
|
|
1076
|
+
return { content: out, endLine: selected.endLine, totalLines: patched.length };
|
|
1077
|
+
}
|