@aigne/afs-json 1.11.0-beta → 1.11.0-beta.10
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/LICENSE.md +17 -84
- package/README.md +0 -3
- package/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.cjs +11 -0
- package/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.mjs +10 -0
- package/dist/index.cjs +462 -170
- package/dist/index.d.cts +134 -72
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +134 -72
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +460 -170
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -5
package/dist/index.cjs
CHANGED
|
@@ -1,11 +1,23 @@
|
|
|
1
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
2
|
+
const require_decorate = require('./_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.cjs');
|
|
3
|
+
let node_fs = require("node:fs");
|
|
1
4
|
let node_fs_promises = require("node:fs/promises");
|
|
2
5
|
let node_path = require("node:path");
|
|
6
|
+
let _aigne_afs = require("@aigne/afs");
|
|
7
|
+
let _aigne_afs_provider = require("@aigne/afs/provider");
|
|
3
8
|
let _aigne_afs_utils_zod = require("@aigne/afs/utils/zod");
|
|
9
|
+
let ufo = require("ufo");
|
|
4
10
|
let yaml = require("yaml");
|
|
5
11
|
let zod = require("zod");
|
|
6
12
|
|
|
7
13
|
//#region src/index.ts
|
|
8
14
|
const LIST_MAX_LIMIT = 1e3;
|
|
15
|
+
/** Hidden key for storing AFS metadata (mirrors FS provider's .afs directory) */
|
|
16
|
+
const AFS_KEY = ".afs";
|
|
17
|
+
/** Subkey for storing metadata (mirrors FS provider's meta.yaml file) */
|
|
18
|
+
const META_KEY = "meta";
|
|
19
|
+
/** Subkey for storing child node metadata (mirrors FS provider's .nodes directory) */
|
|
20
|
+
const NODES_KEY = ".nodes";
|
|
9
21
|
const afsJSONOptionsSchema = (0, _aigne_afs_utils_zod.camelize)(zod.z.object({
|
|
10
22
|
name: (0, _aigne_afs_utils_zod.optionalize)(zod.z.string()),
|
|
11
23
|
jsonPath: zod.z.string().describe("The path to the JSON/YAML file to mount"),
|
|
@@ -19,26 +31,51 @@ const afsJSONOptionsSchema = (0, _aigne_afs_utils_zod.camelize)(zod.z.object({
|
|
|
19
31
|
* JSON/YAML objects are treated as directories, and properties/array items as files.
|
|
20
32
|
* Supports nested structures and path-based access to data values.
|
|
21
33
|
*/
|
|
22
|
-
var AFSJSON = class AFSJSON {
|
|
34
|
+
var AFSJSON = class AFSJSON extends _aigne_afs_provider.AFSBaseProvider {
|
|
23
35
|
static schema() {
|
|
24
36
|
return afsJSONOptionsSchema;
|
|
25
37
|
}
|
|
26
|
-
static
|
|
38
|
+
static manifest() {
|
|
39
|
+
return {
|
|
40
|
+
name: "json",
|
|
41
|
+
description: "JSON or YAML file — navigate and edit structured data as a tree.\n- Objects and arrays become directories, primitives become leaf nodes\n- Read/write individual values, search across structure\n- Path structure: `/{key}/{nested-key}` (arrays indexed by position)",
|
|
42
|
+
uriTemplate: "json://{localPath+}",
|
|
43
|
+
category: "structured-data",
|
|
44
|
+
schema: zod.z.object({ localPath: zod.z.string() }),
|
|
45
|
+
tags: [
|
|
46
|
+
"json",
|
|
47
|
+
"yaml",
|
|
48
|
+
"structured-data"
|
|
49
|
+
]
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
static async load({ basePath, config } = {}) {
|
|
27
53
|
return new AFSJSON({
|
|
28
|
-
...await AFSJSON.schema().parseAsync(
|
|
29
|
-
cwd:
|
|
54
|
+
...await AFSJSON.schema().parseAsync(config),
|
|
55
|
+
cwd: basePath
|
|
30
56
|
});
|
|
31
57
|
}
|
|
58
|
+
name;
|
|
59
|
+
description;
|
|
60
|
+
accessMode;
|
|
61
|
+
agentSkills;
|
|
32
62
|
jsonData = null;
|
|
33
63
|
fileStats = {};
|
|
34
64
|
fileFormat = "json";
|
|
65
|
+
resolvedJsonPath;
|
|
35
66
|
constructor(options) {
|
|
67
|
+
super();
|
|
36
68
|
this.options = options;
|
|
69
|
+
if (options.localPath && !options.jsonPath) options.jsonPath = options.localPath;
|
|
37
70
|
(0, _aigne_afs_utils_zod.zodParse)(afsJSONOptionsSchema, options);
|
|
38
71
|
let jsonPath;
|
|
39
72
|
jsonPath = options.jsonPath.replaceAll("${CWD}", process.cwd());
|
|
40
73
|
if (jsonPath.startsWith("~/")) jsonPath = (0, node_path.join)(process.env.HOME || "", jsonPath.slice(2));
|
|
41
74
|
if (!(0, node_path.isAbsolute)(jsonPath)) jsonPath = (0, node_path.join)(options.cwd || process.cwd(), jsonPath);
|
|
75
|
+
if (!(0, node_fs.existsSync)(jsonPath)) {
|
|
76
|
+
(0, node_fs.mkdirSync)((0, node_path.dirname)(jsonPath), { recursive: true });
|
|
77
|
+
(0, node_fs.writeFileSync)(jsonPath, "{}", "utf8");
|
|
78
|
+
}
|
|
42
79
|
const ext = (0, node_path.extname)(jsonPath).toLowerCase();
|
|
43
80
|
this.fileFormat = ext === ".yaml" || ext === ".yml" ? "yaml" : "json";
|
|
44
81
|
const extensions = [
|
|
@@ -55,12 +92,400 @@ var AFSJSON = class AFSJSON {
|
|
|
55
92
|
this.description = options.description;
|
|
56
93
|
this.agentSkills = options.agentSkills;
|
|
57
94
|
this.accessMode = options.accessMode ?? (options.agentSkills ? "readonly" : "readwrite");
|
|
58
|
-
this.
|
|
95
|
+
this.resolvedJsonPath = jsonPath;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Read metadata for a JSON node via /.meta or /path/.meta
|
|
99
|
+
* Returns stored metadata merged with computed type information
|
|
100
|
+
* Note: Meta is read-only. To write metadata, use write() with payload.meta.
|
|
101
|
+
*/
|
|
102
|
+
async readMetaHandler(ctx) {
|
|
103
|
+
await this.ensureLoaded();
|
|
104
|
+
const nodePath = (0, ufo.joinURL)("/", ctx.params.path ?? "");
|
|
105
|
+
const segments = this.getPathSegments(nodePath);
|
|
106
|
+
const value = this.getValueAtPath(this.jsonData, segments);
|
|
107
|
+
if (value === void 0) throw new _aigne_afs.AFSNotFoundError(nodePath);
|
|
108
|
+
const isDir = this.isDirectoryValue(value);
|
|
109
|
+
const children = isDir ? this.getChildren(value) : [];
|
|
110
|
+
let type;
|
|
111
|
+
if (Array.isArray(value)) type = "array";
|
|
112
|
+
else if (value === null) type = "null";
|
|
113
|
+
else if (typeof value === "object") type = "object";
|
|
114
|
+
else type = typeof value;
|
|
115
|
+
const storedMeta = this.loadMeta(nodePath) || {};
|
|
116
|
+
const computedMeta = {
|
|
117
|
+
type,
|
|
118
|
+
path: nodePath
|
|
119
|
+
};
|
|
120
|
+
if (isDir) {
|
|
121
|
+
computedMeta.childrenCount = children.length;
|
|
122
|
+
if (Array.isArray(value)) computedMeta.length = value.length;
|
|
123
|
+
else computedMeta.keys = Object.keys(value).filter((k) => !this.isMetaKey(k));
|
|
124
|
+
} else computedMeta.value = value;
|
|
125
|
+
if (this.fileStats.birthtime) computedMeta.created = this.fileStats.birthtime;
|
|
126
|
+
if (this.fileStats.mtime) computedMeta.modified = this.fileStats.mtime;
|
|
127
|
+
return this.buildEntry((0, ufo.joinURL)(nodePath, ".meta"), {
|
|
128
|
+
meta: storedMeta,
|
|
129
|
+
content: computedMeta,
|
|
130
|
+
createdAt: this.fileStats.birthtime,
|
|
131
|
+
updatedAt: this.fileStats.mtime
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
async listHandler(ctx) {
|
|
135
|
+
await this.ensureLoaded();
|
|
136
|
+
const normalizedPath = ctx.params.path ? `/${ctx.params.path}` : "/";
|
|
137
|
+
const options = ctx.options;
|
|
138
|
+
const limit = Math.min(options?.limit || LIST_MAX_LIMIT, LIST_MAX_LIMIT);
|
|
139
|
+
const maxChildren = typeof options?.maxChildren === "number" ? options.maxChildren : Number.MAX_SAFE_INTEGER;
|
|
140
|
+
const maxDepth = options?.maxDepth ?? 1;
|
|
141
|
+
const segments = this.getPathSegments(normalizedPath);
|
|
142
|
+
const value = this.getValueAtPath(this.jsonData, segments);
|
|
143
|
+
if (value === void 0) throw new _aigne_afs.AFSNotFoundError(normalizedPath);
|
|
144
|
+
if (maxDepth === 0) return { data: [] };
|
|
145
|
+
if (!this.isDirectoryValue(value)) return { data: [] };
|
|
146
|
+
const entries = [];
|
|
147
|
+
const rootChildren = this.getChildren(value);
|
|
148
|
+
const queue = (rootChildren.length > maxChildren ? rootChildren.slice(0, maxChildren) : rootChildren).map((child) => ({
|
|
149
|
+
path: normalizedPath === "/" ? `/${child.key}` : `${normalizedPath}/${child.key}`,
|
|
150
|
+
value: child.value,
|
|
151
|
+
depth: 1
|
|
152
|
+
}));
|
|
153
|
+
while (queue.length > 0) {
|
|
154
|
+
const item = queue.shift();
|
|
155
|
+
if (!item) break;
|
|
156
|
+
const { path: itemPath, value: itemValue, depth } = item;
|
|
157
|
+
const entry = this.valueToAFSEntry(itemPath, itemValue);
|
|
158
|
+
entries.push(entry);
|
|
159
|
+
if (entries.length >= limit) break;
|
|
160
|
+
if (this.isDirectoryValue(itemValue) && depth < maxDepth) {
|
|
161
|
+
const children = this.getChildren(itemValue);
|
|
162
|
+
const childrenToProcess = children.length > maxChildren ? children.slice(0, maxChildren) : children;
|
|
163
|
+
for (const child of childrenToProcess) {
|
|
164
|
+
const childPath = itemPath === "/" ? `/${child.key}` : `${itemPath}/${child.key}`;
|
|
165
|
+
queue.push({
|
|
166
|
+
path: childPath,
|
|
167
|
+
value: child.value,
|
|
168
|
+
depth: depth + 1
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return { data: entries };
|
|
174
|
+
}
|
|
175
|
+
async readHandler(ctx) {
|
|
176
|
+
await this.ensureLoaded();
|
|
177
|
+
const normalizedPath = ctx.params.path ? `/${ctx.params.path}` : "/";
|
|
178
|
+
const segments = this.getPathSegments(normalizedPath);
|
|
179
|
+
const value = this.getValueAtPath(this.jsonData, segments);
|
|
180
|
+
if (value === void 0) throw new _aigne_afs.AFSNotFoundError(normalizedPath);
|
|
181
|
+
return this.valueToAFSEntry(normalizedPath, value);
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Write handler - supports writing content and/or metadata
|
|
185
|
+
*
|
|
186
|
+
* | payload | behavior |
|
|
187
|
+
* |---------|----------|
|
|
188
|
+
* | { content } | write content only |
|
|
189
|
+
* | { metadata } | write metadata only (to .afs storage) |
|
|
190
|
+
* | { content, metadata } | write both |
|
|
191
|
+
*/
|
|
192
|
+
async writeHandler(ctx, payload) {
|
|
193
|
+
await this.ensureLoaded();
|
|
194
|
+
const normalizedPath = ctx.params.path ? `/${ctx.params.path}` : "/";
|
|
195
|
+
const segments = this.getPathSegments(normalizedPath);
|
|
196
|
+
if (payload.content !== void 0) this.setValueAtPath(this.jsonData, segments, payload.content);
|
|
197
|
+
if (payload.meta !== void 0 && typeof payload.meta === "object") {
|
|
198
|
+
const finalMeta = {
|
|
199
|
+
...this.loadMeta(normalizedPath) || {},
|
|
200
|
+
...payload.meta
|
|
201
|
+
};
|
|
202
|
+
this.saveMeta(normalizedPath, finalMeta);
|
|
203
|
+
}
|
|
204
|
+
await this.saveToFile();
|
|
205
|
+
const newValue = this.getValueAtPath(this.jsonData, segments);
|
|
206
|
+
const isDir = this.isDirectoryValue(newValue);
|
|
207
|
+
const children = isDir ? this.getChildren(newValue) : [];
|
|
208
|
+
const storedMeta = this.loadMeta(normalizedPath) || {};
|
|
209
|
+
return { data: {
|
|
210
|
+
id: normalizedPath,
|
|
211
|
+
path: normalizedPath,
|
|
212
|
+
content: payload.content !== void 0 ? payload.content : newValue,
|
|
213
|
+
summary: payload.summary,
|
|
214
|
+
createdAt: this.fileStats.birthtime,
|
|
215
|
+
updatedAt: this.fileStats.mtime,
|
|
216
|
+
meta: {
|
|
217
|
+
...storedMeta,
|
|
218
|
+
childrenCount: isDir ? children.length : void 0
|
|
219
|
+
},
|
|
220
|
+
userId: payload.userId,
|
|
221
|
+
sessionId: payload.sessionId,
|
|
222
|
+
linkTo: payload.linkTo
|
|
223
|
+
} };
|
|
224
|
+
}
|
|
225
|
+
async deleteHandler(ctx) {
|
|
226
|
+
await this.ensureLoaded();
|
|
227
|
+
const normalizedPath = ctx.params.path ? `/${ctx.params.path}` : "/";
|
|
228
|
+
const options = ctx.options;
|
|
229
|
+
const segments = this.getPathSegments(normalizedPath);
|
|
230
|
+
const value = this.getValueAtPath(this.jsonData, segments);
|
|
231
|
+
if (value === void 0) throw new _aigne_afs.AFSNotFoundError(normalizedPath);
|
|
232
|
+
if (this.isDirectoryValue(value) && this.getChildren(value).length > 0 && !options?.recursive) throw new Error(`Cannot delete directory '${normalizedPath}' without recursive option. Set recursive: true to delete directories.`);
|
|
233
|
+
this.deleteValueAtPath(this.jsonData, segments);
|
|
234
|
+
await this.saveToFile();
|
|
235
|
+
return { message: `Successfully deleted: ${normalizedPath}` };
|
|
236
|
+
}
|
|
237
|
+
async renameHandler(ctx, newPath) {
|
|
238
|
+
await this.ensureLoaded();
|
|
239
|
+
const normalizedOldPath = ctx.params.path ? `/${ctx.params.path}` : "/";
|
|
240
|
+
const normalizedNewPath = this.normalizePath(newPath);
|
|
241
|
+
const options = ctx.options;
|
|
242
|
+
const oldSegments = this.getPathSegments(normalizedOldPath);
|
|
243
|
+
const newSegments = this.getPathSegments(normalizedNewPath);
|
|
244
|
+
const oldValue = this.getValueAtPath(this.jsonData, oldSegments);
|
|
245
|
+
if (oldValue === void 0) throw new _aigne_afs.AFSNotFoundError(normalizedOldPath);
|
|
246
|
+
if (this.getValueAtPath(this.jsonData, newSegments) !== void 0 && !options?.overwrite) throw new Error(`Destination '${normalizedNewPath}' already exists. Set overwrite: true to replace it.`);
|
|
247
|
+
this.setValueAtPath(this.jsonData, newSegments, oldValue);
|
|
248
|
+
this.deleteValueAtPath(this.jsonData, oldSegments);
|
|
249
|
+
await this.saveToFile();
|
|
250
|
+
return { message: `Successfully renamed '${normalizedOldPath}' to '${normalizedNewPath}'` };
|
|
251
|
+
}
|
|
252
|
+
async searchHandler(ctx, query, options) {
|
|
253
|
+
await this.ensureLoaded();
|
|
254
|
+
const normalizedPath = ctx.params.path ? `/${ctx.params.path}` : "/";
|
|
255
|
+
const limit = Math.min(options?.limit || LIST_MAX_LIMIT, LIST_MAX_LIMIT);
|
|
256
|
+
const caseSensitive = options?.caseSensitive ?? false;
|
|
257
|
+
const segments = this.getPathSegments(normalizedPath);
|
|
258
|
+
const rootValue = this.getValueAtPath(this.jsonData, segments);
|
|
259
|
+
if (rootValue === void 0) throw new _aigne_afs.AFSNotFoundError(normalizedPath);
|
|
260
|
+
const entries = [];
|
|
261
|
+
const searchQuery = caseSensitive ? query : query.toLowerCase();
|
|
262
|
+
const searchInValue = (valuePath, value) => {
|
|
263
|
+
if (entries.length >= limit) return;
|
|
264
|
+
let matched = false;
|
|
265
|
+
if (!this.isDirectoryValue(value)) {
|
|
266
|
+
const valueStr = typeof value === "string" ? value : JSON.stringify(value);
|
|
267
|
+
if ((caseSensitive ? valueStr : valueStr.toLowerCase()).includes(searchQuery)) matched = true;
|
|
268
|
+
}
|
|
269
|
+
if (matched) entries.push(this.valueToAFSEntry(valuePath, value));
|
|
270
|
+
if (this.isDirectoryValue(value)) {
|
|
271
|
+
const children = this.getChildren(value);
|
|
272
|
+
for (const child of children) {
|
|
273
|
+
if (entries.length >= limit) break;
|
|
274
|
+
searchInValue(valuePath === "/" ? `/${child.key}` : `${valuePath}/${child.key}`, child.value);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
searchInValue(normalizedPath, rootValue);
|
|
279
|
+
return {
|
|
280
|
+
data: entries,
|
|
281
|
+
message: entries.length >= limit ? `Results truncated to limit ${limit}` : void 0
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
async statHandler(ctx) {
|
|
285
|
+
await this.ensureLoaded();
|
|
286
|
+
const normalizedPath = ctx.params.path ? `/${ctx.params.path}` : "/";
|
|
287
|
+
const segments = this.getPathSegments(normalizedPath);
|
|
288
|
+
const value = this.getValueAtPath(this.jsonData, segments);
|
|
289
|
+
if (value === void 0) throw new _aigne_afs.AFSNotFoundError(normalizedPath);
|
|
290
|
+
const isDir = this.isDirectoryValue(value);
|
|
291
|
+
const children = isDir ? this.getChildren(value) : [];
|
|
292
|
+
const meta = { ...this.loadMeta(normalizedPath) };
|
|
293
|
+
if (isDir) meta.childrenCount = children.length;
|
|
294
|
+
return { data: {
|
|
295
|
+
id: segments.length > 0 ? segments[segments.length - 1] : "/",
|
|
296
|
+
path: normalizedPath,
|
|
297
|
+
createdAt: this.fileStats.birthtime,
|
|
298
|
+
updatedAt: this.fileStats.mtime,
|
|
299
|
+
meta
|
|
300
|
+
} };
|
|
301
|
+
}
|
|
302
|
+
async readCapabilitiesHandler(_ctx) {
|
|
303
|
+
await this.ensureLoaded();
|
|
304
|
+
const operations = [
|
|
305
|
+
"list",
|
|
306
|
+
"read",
|
|
307
|
+
"stat",
|
|
308
|
+
"explain",
|
|
309
|
+
"search"
|
|
310
|
+
];
|
|
311
|
+
if (this.accessMode === "readwrite") operations.push("write", "delete", "rename");
|
|
312
|
+
return {
|
|
313
|
+
id: "/.meta/.capabilities",
|
|
314
|
+
path: "/.meta/.capabilities",
|
|
315
|
+
content: {
|
|
316
|
+
schemaVersion: 1,
|
|
317
|
+
provider: this.name,
|
|
318
|
+
description: this.description || `JSON/YAML virtual filesystem (${this.fileFormat} format)`,
|
|
319
|
+
tools: [],
|
|
320
|
+
actions: [],
|
|
321
|
+
operations: this.getOperationsDeclaration()
|
|
322
|
+
},
|
|
323
|
+
meta: {
|
|
324
|
+
kind: "afs:capabilities",
|
|
325
|
+
operations
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
async explainHandler(ctx) {
|
|
330
|
+
await this.ensureLoaded();
|
|
331
|
+
const normalizedPath = (0, ufo.joinURL)("/", ctx.params.path ?? "");
|
|
332
|
+
const format = ctx.options?.format || "markdown";
|
|
333
|
+
const segments = this.getPathSegments(normalizedPath);
|
|
334
|
+
const value = this.getValueAtPath(this.jsonData, segments);
|
|
335
|
+
if (value === void 0) throw new _aigne_afs.AFSNotFoundError(normalizedPath);
|
|
336
|
+
const nodeName = segments.length > 0 ? segments[segments.length - 1] : "/";
|
|
337
|
+
const isDir = this.isDirectoryValue(value);
|
|
338
|
+
const storedMeta = this.loadMeta(normalizedPath);
|
|
339
|
+
const lines = [];
|
|
340
|
+
if (format === "markdown") {
|
|
341
|
+
lines.push(`# ${nodeName}`);
|
|
342
|
+
lines.push("");
|
|
343
|
+
lines.push(`**Path:** \`${normalizedPath}\``);
|
|
344
|
+
lines.push(`**Format:** ${this.fileFormat.toUpperCase()}`);
|
|
345
|
+
if (normalizedPath === "/") {
|
|
346
|
+
const topType = Array.isArray(this.jsonData) ? "array" : "object";
|
|
347
|
+
const children = this.getChildren(this.jsonData);
|
|
348
|
+
lines.push(`**Structure:** ${topType}`);
|
|
349
|
+
lines.push(`**Top-level keys:** ${children.length}`);
|
|
350
|
+
if (children.length > 0) {
|
|
351
|
+
lines.push("");
|
|
352
|
+
lines.push("## Keys");
|
|
353
|
+
lines.push("");
|
|
354
|
+
for (const child of children.slice(0, 30)) {
|
|
355
|
+
const childVal = child.value;
|
|
356
|
+
const childType = this.describeType(childVal);
|
|
357
|
+
lines.push(`- \`${child.key}\` — ${childType}`);
|
|
358
|
+
}
|
|
359
|
+
if (children.length > 30) lines.push(`- ... and ${children.length - 30} more`);
|
|
360
|
+
}
|
|
361
|
+
} else if (Array.isArray(value)) {
|
|
362
|
+
lines.push(`**Type:** array`);
|
|
363
|
+
lines.push(`**Elements:** ${value.length}`);
|
|
364
|
+
if (value.length > 0) {
|
|
365
|
+
const elementType = this.describeType(value[0]);
|
|
366
|
+
const isHomogeneous = value.every((v) => this.describeType(v) === elementType);
|
|
367
|
+
lines.push(`**Element type:** ${isHomogeneous ? elementType : "mixed"}`);
|
|
368
|
+
}
|
|
369
|
+
} else if (typeof value === "object" && value !== null) {
|
|
370
|
+
const children = this.getChildren(value);
|
|
371
|
+
lines.push(`**Type:** object`);
|
|
372
|
+
lines.push(`**Keys:** ${children.length}`);
|
|
373
|
+
if (children.length > 0) {
|
|
374
|
+
lines.push("");
|
|
375
|
+
lines.push("## Keys");
|
|
376
|
+
lines.push("");
|
|
377
|
+
for (const child of children.slice(0, 30)) {
|
|
378
|
+
const childType = this.describeType(child.value);
|
|
379
|
+
lines.push(`- \`${child.key}\` — ${childType}`);
|
|
380
|
+
}
|
|
381
|
+
if (children.length > 30) lines.push(`- ... and ${children.length - 30} more`);
|
|
382
|
+
}
|
|
383
|
+
} else {
|
|
384
|
+
const valType = value === null ? "null" : typeof value;
|
|
385
|
+
lines.push(`**Type:** ${valType}`);
|
|
386
|
+
const valStr = String(value);
|
|
387
|
+
if (valStr.length > 200) lines.push(`**Value:** ${valStr.slice(0, 200)}...`);
|
|
388
|
+
else lines.push(`**Value:** ${valStr}`);
|
|
389
|
+
}
|
|
390
|
+
if (storedMeta) {
|
|
391
|
+
lines.push("");
|
|
392
|
+
lines.push("## Metadata");
|
|
393
|
+
for (const [key, val] of Object.entries(storedMeta)) lines.push(`- **${key}:** ${JSON.stringify(val)}`);
|
|
394
|
+
}
|
|
395
|
+
} else {
|
|
396
|
+
lines.push(`${nodeName} (${isDir ? "directory" : "value"})`);
|
|
397
|
+
lines.push(`Path: ${normalizedPath}`);
|
|
398
|
+
lines.push(`Format: ${this.fileFormat}`);
|
|
399
|
+
if (isDir) {
|
|
400
|
+
const children = this.getChildren(value);
|
|
401
|
+
lines.push(`Children: ${children.length}`);
|
|
402
|
+
} else {
|
|
403
|
+
const valStr = String(value);
|
|
404
|
+
lines.push(`Type: ${value === null ? "null" : typeof value}`);
|
|
405
|
+
lines.push(`Value: ${valStr.length > 200 ? `${valStr.slice(0, 200)}...` : valStr}`);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
return {
|
|
409
|
+
content: lines.join("\n"),
|
|
410
|
+
format
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Get a human-readable type description for a JSON value.
|
|
415
|
+
*/
|
|
416
|
+
describeType(value) {
|
|
417
|
+
if (value === null) return "null";
|
|
418
|
+
if (Array.isArray(value)) return `array[${value.length}]`;
|
|
419
|
+
if (typeof value === "object") return `object{${Object.keys(value).filter((k) => !this.isMetaKey(k)).length} keys}`;
|
|
420
|
+
return typeof value;
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Check if a key is a hidden meta key that should be filtered from listings
|
|
424
|
+
*/
|
|
425
|
+
isMetaKey(key) {
|
|
426
|
+
return key === AFS_KEY;
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Load metadata for a node.
|
|
430
|
+
*
|
|
431
|
+
* Storage location depends on node type (mirrors FS provider's .afs structure):
|
|
432
|
+
* - Objects: `.afs.meta` key within the object itself
|
|
433
|
+
* - Primitives: parent's `.afs[".nodes"][key].meta`
|
|
434
|
+
*/
|
|
435
|
+
loadMeta(nodePath) {
|
|
436
|
+
const segments = this.getPathSegments(nodePath);
|
|
437
|
+
const value = this.getValueAtPath(this.jsonData, segments);
|
|
438
|
+
if (value === void 0) return null;
|
|
439
|
+
if (this.isDirectoryValue(value) && !Array.isArray(value)) {
|
|
440
|
+
const afs$1 = value[AFS_KEY];
|
|
441
|
+
if (afs$1 && typeof afs$1 === "object" && !Array.isArray(afs$1)) {
|
|
442
|
+
const meta$1 = afs$1[META_KEY];
|
|
443
|
+
if (meta$1 && typeof meta$1 === "object" && !Array.isArray(meta$1)) return meta$1;
|
|
444
|
+
}
|
|
445
|
+
return null;
|
|
446
|
+
}
|
|
447
|
+
if (segments.length === 0) return null;
|
|
448
|
+
const parentSegments = segments.slice(0, -1);
|
|
449
|
+
const nodeKey = segments[segments.length - 1];
|
|
450
|
+
const parentValue = this.getValueAtPath(this.jsonData, parentSegments);
|
|
451
|
+
if (!parentValue || Array.isArray(parentValue) || typeof parentValue !== "object") return null;
|
|
452
|
+
const afs = parentValue[AFS_KEY];
|
|
453
|
+
if (!afs || typeof afs !== "object" || Array.isArray(afs)) return null;
|
|
454
|
+
const nodes = afs[NODES_KEY];
|
|
455
|
+
if (!nodes || typeof nodes !== "object" || Array.isArray(nodes)) return null;
|
|
456
|
+
const nodeEntry = nodes[nodeKey];
|
|
457
|
+
if (!nodeEntry || typeof nodeEntry !== "object" || Array.isArray(nodeEntry)) return null;
|
|
458
|
+
const meta = nodeEntry[META_KEY];
|
|
459
|
+
if (!meta || typeof meta !== "object" || Array.isArray(meta)) return null;
|
|
460
|
+
return meta;
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Save metadata for a node.
|
|
464
|
+
*
|
|
465
|
+
* Storage location depends on node type (mirrors FS provider's .afs structure):
|
|
466
|
+
* - Objects: `.afs.meta` key within the object itself
|
|
467
|
+
* - Primitives: parent's `.afs[".nodes"][key].meta`
|
|
468
|
+
*/
|
|
469
|
+
saveMeta(nodePath, meta) {
|
|
470
|
+
const segments = this.getPathSegments(nodePath);
|
|
471
|
+
const value = this.getValueAtPath(this.jsonData, segments);
|
|
472
|
+
if (value === void 0) throw new _aigne_afs.AFSNotFoundError(nodePath);
|
|
473
|
+
if (this.isDirectoryValue(value) && !Array.isArray(value)) {
|
|
474
|
+
if (!value[AFS_KEY]) value[AFS_KEY] = {};
|
|
475
|
+
value[AFS_KEY][META_KEY] = meta;
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
if (segments.length === 0) throw new Error("Cannot save meta for root when root is not an object");
|
|
479
|
+
const parentSegments = segments.slice(0, -1);
|
|
480
|
+
const nodeKey = segments[segments.length - 1];
|
|
481
|
+
const parentValue = this.getValueAtPath(this.jsonData, parentSegments);
|
|
482
|
+
if (!parentValue || typeof parentValue !== "object") throw new Error(`Parent path is not an object`);
|
|
483
|
+
if (Array.isArray(parentValue)) throw new Error(`Cannot save meta for array elements`);
|
|
484
|
+
if (!parentValue[AFS_KEY]) parentValue[AFS_KEY] = {};
|
|
485
|
+
if (!parentValue[AFS_KEY][NODES_KEY]) parentValue[AFS_KEY][NODES_KEY] = {};
|
|
486
|
+
if (!parentValue[AFS_KEY][NODES_KEY][nodeKey]) parentValue[AFS_KEY][NODES_KEY][nodeKey] = {};
|
|
487
|
+
parentValue[AFS_KEY][NODES_KEY][nodeKey][META_KEY] = meta;
|
|
59
488
|
}
|
|
60
|
-
name;
|
|
61
|
-
description;
|
|
62
|
-
accessMode;
|
|
63
|
-
agentSkills;
|
|
64
489
|
/**
|
|
65
490
|
* Load JSON/YAML data from file. Called lazily on first access.
|
|
66
491
|
* Uses YAML parser which can handle both JSON and YAML formats.
|
|
@@ -68,12 +493,12 @@ var AFSJSON = class AFSJSON {
|
|
|
68
493
|
async ensureLoaded() {
|
|
69
494
|
if (this.jsonData !== null) return;
|
|
70
495
|
try {
|
|
71
|
-
const stats = await (0, node_fs_promises.stat)(this.
|
|
496
|
+
const stats = await (0, node_fs_promises.stat)(this.resolvedJsonPath);
|
|
72
497
|
this.fileStats = {
|
|
73
498
|
birthtime: stats.birthtime,
|
|
74
499
|
mtime: stats.mtime
|
|
75
500
|
};
|
|
76
|
-
this.jsonData = (0, yaml.parse)(await (0, node_fs_promises.readFile)(this.
|
|
501
|
+
this.jsonData = (0, yaml.parse)(await (0, node_fs_promises.readFile)(this.resolvedJsonPath, "utf8"));
|
|
77
502
|
} catch (error) {
|
|
78
503
|
if (error.code === "ENOENT") this.jsonData = {};
|
|
79
504
|
else throw error;
|
|
@@ -86,22 +511,14 @@ var AFSJSON = class AFSJSON {
|
|
|
86
511
|
let content;
|
|
87
512
|
if (this.fileFormat === "yaml") content = (0, yaml.stringify)(this.jsonData);
|
|
88
513
|
else content = JSON.stringify(this.jsonData, null, 2);
|
|
89
|
-
await (0, node_fs_promises.writeFile)(this.
|
|
90
|
-
const stats = await (0, node_fs_promises.stat)(this.
|
|
514
|
+
await (0, node_fs_promises.writeFile)(this.resolvedJsonPath, content, "utf8");
|
|
515
|
+
const stats = await (0, node_fs_promises.stat)(this.resolvedJsonPath);
|
|
91
516
|
this.fileStats = {
|
|
92
517
|
birthtime: this.fileStats.birthtime || stats.birthtime,
|
|
93
518
|
mtime: stats.mtime
|
|
94
519
|
};
|
|
95
520
|
}
|
|
96
521
|
/**
|
|
97
|
-
* Normalize path to ensure consistent format
|
|
98
|
-
*/
|
|
99
|
-
normalizePath(path) {
|
|
100
|
-
let normalized = path.startsWith("/") ? path : `/${path}`;
|
|
101
|
-
if (normalized.length > 1 && normalized.endsWith("/")) normalized = normalized.slice(0, -1);
|
|
102
|
-
return normalized;
|
|
103
|
-
}
|
|
104
|
-
/**
|
|
105
522
|
* Get path segments from normalized path
|
|
106
523
|
*/
|
|
107
524
|
getPathSegments(path) {
|
|
@@ -187,20 +604,20 @@ var AFSJSON = class AFSJSON {
|
|
|
187
604
|
/**
|
|
188
605
|
* Check if a value is a "directory" (object or array with children)
|
|
189
606
|
*/
|
|
190
|
-
|
|
607
|
+
isDirectoryValue(value) {
|
|
191
608
|
if (Array.isArray(value)) return true;
|
|
192
609
|
if (typeof value === "object" && value !== null) return true;
|
|
193
610
|
return false;
|
|
194
611
|
}
|
|
195
612
|
/**
|
|
196
|
-
* Get children of a directory value
|
|
613
|
+
* Get children of a directory value (filters out .afs meta key)
|
|
197
614
|
*/
|
|
198
615
|
getChildren(value) {
|
|
199
616
|
if (Array.isArray(value)) return value.map((item, index) => ({
|
|
200
617
|
key: String(index),
|
|
201
618
|
value: item
|
|
202
619
|
}));
|
|
203
|
-
if (typeof value === "object" && value !== null) return Object.entries(value).map(([key, val]) => ({
|
|
620
|
+
if (typeof value === "object" && value !== null) return Object.entries(value).filter(([key]) => !this.isMetaKey(key)).map(([key, val]) => ({
|
|
204
621
|
key,
|
|
205
622
|
value: val
|
|
206
623
|
}));
|
|
@@ -210,157 +627,32 @@ var AFSJSON = class AFSJSON {
|
|
|
210
627
|
* Convert a JSON value to an AFSEntry
|
|
211
628
|
*/
|
|
212
629
|
valueToAFSEntry(path, value) {
|
|
213
|
-
const isDir = this.
|
|
630
|
+
const isDir = this.isDirectoryValue(value);
|
|
214
631
|
const children = isDir ? this.getChildren(value) : [];
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
path,
|
|
632
|
+
const kind = Array.isArray(value) ? "json:array" : isDir ? "json:object" : "json:value";
|
|
633
|
+
return this.buildEntry(path, {
|
|
218
634
|
content: isDir ? void 0 : value,
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
metadata: {
|
|
222
|
-
type: isDir ? "directory" : "file",
|
|
223
|
-
childrenCount: isDir ? children.length : void 0
|
|
224
|
-
}
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
async list(path, options) {
|
|
228
|
-
await this.ensureLoaded();
|
|
229
|
-
const normalizedPath = this.normalizePath(path);
|
|
230
|
-
const limit = Math.min(options?.limit || LIST_MAX_LIMIT, LIST_MAX_LIMIT);
|
|
231
|
-
const maxChildren = typeof options?.maxChildren === "number" ? options.maxChildren : Number.MAX_SAFE_INTEGER;
|
|
232
|
-
const maxDepth = options?.maxDepth ?? 1;
|
|
233
|
-
const segments = this.getPathSegments(normalizedPath);
|
|
234
|
-
const value = this.getValueAtPath(this.jsonData, segments);
|
|
235
|
-
if (value === void 0) throw new Error(`Path not found: ${normalizedPath}`);
|
|
236
|
-
const entries = [];
|
|
237
|
-
const queue = [{
|
|
238
|
-
path: normalizedPath,
|
|
239
|
-
value,
|
|
240
|
-
depth: 0
|
|
241
|
-
}];
|
|
242
|
-
while (queue.length > 0) {
|
|
243
|
-
const item = queue.shift();
|
|
244
|
-
if (!item) break;
|
|
245
|
-
const { path: itemPath, value: itemValue, depth } = item;
|
|
246
|
-
const entry = this.valueToAFSEntry(itemPath, itemValue);
|
|
247
|
-
entries.push(entry);
|
|
248
|
-
if (entries.length >= limit) {
|
|
249
|
-
if (entry.metadata) entry.metadata.childrenTruncated = true;
|
|
250
|
-
break;
|
|
251
|
-
}
|
|
252
|
-
if (this.isDirectory(itemValue) && depth < maxDepth) {
|
|
253
|
-
const children = this.getChildren(itemValue);
|
|
254
|
-
const childrenToProcess = children.length > maxChildren ? children.slice(0, maxChildren) : children;
|
|
255
|
-
if (childrenToProcess.length < children.length && entry.metadata) entry.metadata.childrenTruncated = true;
|
|
256
|
-
for (const child of childrenToProcess) {
|
|
257
|
-
const childPath = itemPath === "/" ? `/${child.key}` : `${itemPath}/${child.key}`;
|
|
258
|
-
queue.push({
|
|
259
|
-
path: childPath,
|
|
260
|
-
value: child.value,
|
|
261
|
-
depth: depth + 1
|
|
262
|
-
});
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
return { data: entries };
|
|
267
|
-
}
|
|
268
|
-
async read(path, _options) {
|
|
269
|
-
await this.ensureLoaded();
|
|
270
|
-
const normalizedPath = this.normalizePath(path);
|
|
271
|
-
const segments = this.getPathSegments(normalizedPath);
|
|
272
|
-
const value = this.getValueAtPath(this.jsonData, segments);
|
|
273
|
-
if (value === void 0) return {
|
|
274
|
-
data: void 0,
|
|
275
|
-
message: `Path not found: ${normalizedPath}`
|
|
276
|
-
};
|
|
277
|
-
return { data: this.valueToAFSEntry(normalizedPath, value) };
|
|
278
|
-
}
|
|
279
|
-
async write(path, entry, _options) {
|
|
280
|
-
await this.ensureLoaded();
|
|
281
|
-
const normalizedPath = this.normalizePath(path);
|
|
282
|
-
const segments = this.getPathSegments(normalizedPath);
|
|
283
|
-
this.setValueAtPath(this.jsonData, segments, entry.content);
|
|
284
|
-
await this.saveToFile();
|
|
285
|
-
const newValue = this.getValueAtPath(this.jsonData, segments);
|
|
286
|
-
const isDir = this.isDirectory(newValue);
|
|
287
|
-
const children = isDir ? this.getChildren(newValue) : [];
|
|
288
|
-
return { data: {
|
|
289
|
-
id: normalizedPath,
|
|
290
|
-
path: normalizedPath,
|
|
291
|
-
content: entry.content,
|
|
292
|
-
summary: entry.summary,
|
|
293
|
-
description: entry.description,
|
|
294
|
-
createdAt: this.fileStats.birthtime,
|
|
295
|
-
updatedAt: this.fileStats.mtime,
|
|
296
|
-
metadata: {
|
|
297
|
-
...entry.metadata,
|
|
298
|
-
type: isDir ? "directory" : "file",
|
|
635
|
+
meta: {
|
|
636
|
+
kind,
|
|
299
637
|
childrenCount: isDir ? children.length : void 0
|
|
300
638
|
},
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
} };
|
|
305
|
-
}
|
|
306
|
-
async delete(path, options) {
|
|
307
|
-
await this.ensureLoaded();
|
|
308
|
-
const normalizedPath = this.normalizePath(path);
|
|
309
|
-
const segments = this.getPathSegments(normalizedPath);
|
|
310
|
-
const value = this.getValueAtPath(this.jsonData, segments);
|
|
311
|
-
if (value === void 0) throw new Error(`Path not found: ${normalizedPath}`);
|
|
312
|
-
if (this.isDirectory(value) && this.getChildren(value).length > 0 && !options?.recursive) throw new Error(`Cannot delete directory '${normalizedPath}' without recursive option. Set recursive: true to delete directories.`);
|
|
313
|
-
this.deleteValueAtPath(this.jsonData, segments);
|
|
314
|
-
await this.saveToFile();
|
|
315
|
-
return { message: `Successfully deleted: ${normalizedPath}` };
|
|
316
|
-
}
|
|
317
|
-
async rename(oldPath, newPath, options) {
|
|
318
|
-
await this.ensureLoaded();
|
|
319
|
-
const normalizedOldPath = this.normalizePath(oldPath);
|
|
320
|
-
const normalizedNewPath = this.normalizePath(newPath);
|
|
321
|
-
const oldSegments = this.getPathSegments(normalizedOldPath);
|
|
322
|
-
const newSegments = this.getPathSegments(normalizedNewPath);
|
|
323
|
-
const oldValue = this.getValueAtPath(this.jsonData, oldSegments);
|
|
324
|
-
if (oldValue === void 0) throw new Error(`Source path not found: ${normalizedOldPath}`);
|
|
325
|
-
if (this.getValueAtPath(this.jsonData, newSegments) !== void 0 && !options?.overwrite) throw new Error(`Destination '${normalizedNewPath}' already exists. Set overwrite: true to replace it.`);
|
|
326
|
-
this.setValueAtPath(this.jsonData, newSegments, oldValue);
|
|
327
|
-
this.deleteValueAtPath(this.jsonData, oldSegments);
|
|
328
|
-
await this.saveToFile();
|
|
329
|
-
return { message: `Successfully renamed '${normalizedOldPath}' to '${normalizedNewPath}'` };
|
|
330
|
-
}
|
|
331
|
-
async search(path, query, options) {
|
|
332
|
-
await this.ensureLoaded();
|
|
333
|
-
const normalizedPath = this.normalizePath(path);
|
|
334
|
-
const limit = Math.min(options?.limit || LIST_MAX_LIMIT, LIST_MAX_LIMIT);
|
|
335
|
-
const caseSensitive = options?.caseSensitive ?? false;
|
|
336
|
-
const segments = this.getPathSegments(normalizedPath);
|
|
337
|
-
const rootValue = this.getValueAtPath(this.jsonData, segments);
|
|
338
|
-
if (rootValue === void 0) throw new Error(`Path not found: ${normalizedPath}`);
|
|
339
|
-
const entries = [];
|
|
340
|
-
const searchQuery = caseSensitive ? query : query.toLowerCase();
|
|
341
|
-
const searchInValue = (valuePath, value) => {
|
|
342
|
-
if (entries.length >= limit) return;
|
|
343
|
-
let matched = false;
|
|
344
|
-
if (!this.isDirectory(value)) {
|
|
345
|
-
const valueStr = typeof value === "string" ? value : JSON.stringify(value);
|
|
346
|
-
if ((caseSensitive ? valueStr : valueStr.toLowerCase()).includes(searchQuery)) matched = true;
|
|
347
|
-
}
|
|
348
|
-
if (matched) entries.push(this.valueToAFSEntry(valuePath, value));
|
|
349
|
-
if (this.isDirectory(value)) {
|
|
350
|
-
const children = this.getChildren(value);
|
|
351
|
-
for (const child of children) {
|
|
352
|
-
if (entries.length >= limit) break;
|
|
353
|
-
searchInValue(valuePath === "/" ? `/${child.key}` : `${valuePath}/${child.key}`, child.value);
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
};
|
|
357
|
-
searchInValue(normalizedPath, rootValue);
|
|
358
|
-
return {
|
|
359
|
-
data: entries,
|
|
360
|
-
message: entries.length >= limit ? `Results truncated to limit ${limit}` : void 0
|
|
361
|
-
};
|
|
639
|
+
createdAt: this.fileStats.birthtime,
|
|
640
|
+
updatedAt: this.fileStats.mtime
|
|
641
|
+
});
|
|
362
642
|
}
|
|
363
643
|
};
|
|
644
|
+
require_decorate.__decorate([(0, _aigne_afs_provider.Meta)("/:path*")], AFSJSON.prototype, "readMetaHandler", null);
|
|
645
|
+
require_decorate.__decorate([(0, _aigne_afs_provider.List)("/:path*", { handleDepth: true })], AFSJSON.prototype, "listHandler", null);
|
|
646
|
+
require_decorate.__decorate([(0, _aigne_afs_provider.Read)("/:path*")], AFSJSON.prototype, "readHandler", null);
|
|
647
|
+
require_decorate.__decorate([(0, _aigne_afs_provider.Write)("/:path*")], AFSJSON.prototype, "writeHandler", null);
|
|
648
|
+
require_decorate.__decorate([(0, _aigne_afs_provider.Delete)("/:path*")], AFSJSON.prototype, "deleteHandler", null);
|
|
649
|
+
require_decorate.__decorate([(0, _aigne_afs_provider.Rename)("/:path*")], AFSJSON.prototype, "renameHandler", null);
|
|
650
|
+
require_decorate.__decorate([(0, _aigne_afs_provider.Search)("/:path*")], AFSJSON.prototype, "searchHandler", null);
|
|
651
|
+
require_decorate.__decorate([(0, _aigne_afs_provider.Stat)("/:path*")], AFSJSON.prototype, "statHandler", null);
|
|
652
|
+
require_decorate.__decorate([(0, _aigne_afs_provider.Read)("/.meta/.capabilities")], AFSJSON.prototype, "readCapabilitiesHandler", null);
|
|
653
|
+
require_decorate.__decorate([(0, _aigne_afs_provider.Explain)("/:path*")], AFSJSON.prototype, "explainHandler", null);
|
|
654
|
+
var src_default = AFSJSON;
|
|
364
655
|
|
|
365
656
|
//#endregion
|
|
366
|
-
exports.AFSJSON = AFSJSON;
|
|
657
|
+
exports.AFSJSON = AFSJSON;
|
|
658
|
+
exports.default = src_default;
|