@aigne/afs-cli 1.11.0-beta.1 → 1.11.0-beta.3
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 +63 -9
- package/dist/_virtual/rolldown_runtime.cjs +29 -0
- package/dist/cli.cjs +251 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.mjs +241 -19
- package/dist/cli.mjs.map +1 -0
- package/dist/commands/exec.cjs +46 -0
- package/dist/commands/exec.mjs +45 -0
- package/dist/commands/exec.mjs.map +1 -0
- package/dist/commands/explain.cjs +244 -0
- package/dist/commands/explain.mjs +242 -0
- package/dist/commands/explain.mjs.map +1 -0
- package/dist/commands/index.cjs +8 -0
- package/dist/commands/index.mjs +10 -0
- package/dist/commands/ls.cjs +141 -0
- package/dist/commands/ls.mjs +140 -0
- package/dist/commands/ls.mjs.map +1 -0
- package/dist/commands/mount.cjs +170 -0
- package/dist/commands/mount.mjs +166 -0
- package/dist/commands/mount.mjs.map +1 -0
- package/dist/commands/read.cjs +65 -0
- package/dist/commands/read.mjs +64 -0
- package/dist/commands/read.mjs.map +1 -0
- package/dist/commands/serve.cjs +141 -0
- package/dist/commands/serve.mjs +140 -0
- package/dist/commands/serve.mjs.map +1 -0
- package/dist/commands/stat.cjs +113 -0
- package/dist/commands/stat.mjs +112 -0
- package/dist/commands/stat.mjs.map +1 -0
- package/dist/commands/write.cjs +52 -0
- package/dist/commands/write.mjs +51 -0
- package/dist/commands/write.mjs.map +1 -0
- package/dist/config/env.cjs +46 -0
- package/dist/config/env.mjs +46 -0
- package/dist/config/env.mjs.map +1 -0
- package/dist/config/loader.cjs +171 -0
- package/dist/config/loader.mjs +169 -0
- package/dist/config/loader.mjs.map +1 -0
- package/dist/config/provider-factory.cjs +92 -0
- package/dist/config/provider-factory.mjs +93 -0
- package/dist/config/provider-factory.mjs.map +1 -0
- package/dist/config/schema.cjs +36 -0
- package/dist/config/schema.mjs +36 -0
- package/dist/config/schema.mjs.map +1 -0
- package/dist/config/uri-parser.cjs +92 -0
- package/dist/config/uri-parser.mjs +92 -0
- package/dist/config/uri-parser.mjs.map +1 -0
- package/dist/errors.cjs +29 -0
- package/dist/errors.mjs +28 -0
- package/dist/errors.mjs.map +1 -0
- package/dist/index.cjs +3 -0
- package/dist/index.d.cts +2 -0
- package/dist/index.d.mts +1 -3
- package/dist/index.mjs +1 -1
- package/dist/runtime.cjs +82 -0
- package/dist/runtime.mjs +82 -0
- package/dist/runtime.mjs.map +1 -0
- package/dist/version.cjs +9 -0
- package/dist/version.d.cts +5 -0
- package/dist/version.d.cts.map +1 -0
- package/dist/version.d.mts +5 -0
- package/dist/version.d.mts.map +1 -0
- package/dist/version.mjs +9 -0
- package/dist/version.mjs.map +1 -0
- package/package.json +52 -11
- package/.turbo/turbo-build.log +0 -18
- package/.turbo/turbo-check-types.log +0 -4
- package/dist/version--p6A8sKX.mjs +0 -5
- package/src/cli.test.ts +0 -8
- package/src/cli.ts +0 -29
- package/src/index.ts +0 -7
- package/src/version.ts +0 -1
- package/tsconfig.json +0 -16
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
|
|
2
|
+
//#region src/commands/read.ts
|
|
3
|
+
/**
|
|
4
|
+
* Read file content
|
|
5
|
+
*/
|
|
6
|
+
async function readCommand(runtime, path) {
|
|
7
|
+
const result = await runtime.read(path);
|
|
8
|
+
if (!result.data) return {
|
|
9
|
+
path,
|
|
10
|
+
content: void 0
|
|
11
|
+
};
|
|
12
|
+
const entry = result.data;
|
|
13
|
+
return {
|
|
14
|
+
path: entry.path,
|
|
15
|
+
content: typeof entry.content === "string" ? entry.content : JSON.stringify(entry.content),
|
|
16
|
+
type: entry.metadata?.type === "directory" ? "directory" : "file",
|
|
17
|
+
size: entry.metadata?.size,
|
|
18
|
+
mimeType: entry.metadata?.mimeType
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Format read output for different views
|
|
23
|
+
*/
|
|
24
|
+
function formatReadOutput(read, view) {
|
|
25
|
+
switch (view) {
|
|
26
|
+
case "json": return formatJson(read);
|
|
27
|
+
case "llm": return formatLlm(read);
|
|
28
|
+
case "human": return formatHuman(read);
|
|
29
|
+
default: return formatDefault(read);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Default format: Raw content
|
|
34
|
+
*/
|
|
35
|
+
function formatDefault(read) {
|
|
36
|
+
return read.content ?? "";
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* JSON format
|
|
40
|
+
*/
|
|
41
|
+
function formatJson(read) {
|
|
42
|
+
return JSON.stringify(read, null, 2);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* LLM format: Content with header
|
|
46
|
+
*/
|
|
47
|
+
function formatLlm(read) {
|
|
48
|
+
const lines = [];
|
|
49
|
+
lines.push(`FILE ${read.path}`);
|
|
50
|
+
if (read.mimeType) lines.push(`MIME ${read.mimeType}`);
|
|
51
|
+
if (read.size !== void 0) lines.push(`SIZE ${read.size}`);
|
|
52
|
+
lines.push(`CONTENT`);
|
|
53
|
+
lines.push(read.content ?? "");
|
|
54
|
+
return lines.join("\n");
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Human format: With syntax highlighting placeholder
|
|
58
|
+
*/
|
|
59
|
+
function formatHuman(read) {
|
|
60
|
+
return `${`--- ${read.path} ---`}\n${read.content ?? "(empty)"}`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
//#endregion
|
|
64
|
+
exports.formatReadOutput = formatReadOutput;
|
|
65
|
+
exports.readCommand = readCommand;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
//#region src/commands/read.ts
|
|
2
|
+
/**
|
|
3
|
+
* Read file content
|
|
4
|
+
*/
|
|
5
|
+
async function readCommand(runtime, path) {
|
|
6
|
+
const result = await runtime.read(path);
|
|
7
|
+
if (!result.data) return {
|
|
8
|
+
path,
|
|
9
|
+
content: void 0
|
|
10
|
+
};
|
|
11
|
+
const entry = result.data;
|
|
12
|
+
return {
|
|
13
|
+
path: entry.path,
|
|
14
|
+
content: typeof entry.content === "string" ? entry.content : JSON.stringify(entry.content),
|
|
15
|
+
type: entry.metadata?.type === "directory" ? "directory" : "file",
|
|
16
|
+
size: entry.metadata?.size,
|
|
17
|
+
mimeType: entry.metadata?.mimeType
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Format read output for different views
|
|
22
|
+
*/
|
|
23
|
+
function formatReadOutput(read, view) {
|
|
24
|
+
switch (view) {
|
|
25
|
+
case "json": return formatJson(read);
|
|
26
|
+
case "llm": return formatLlm(read);
|
|
27
|
+
case "human": return formatHuman(read);
|
|
28
|
+
default: return formatDefault(read);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Default format: Raw content
|
|
33
|
+
*/
|
|
34
|
+
function formatDefault(read) {
|
|
35
|
+
return read.content ?? "";
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* JSON format
|
|
39
|
+
*/
|
|
40
|
+
function formatJson(read) {
|
|
41
|
+
return JSON.stringify(read, null, 2);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* LLM format: Content with header
|
|
45
|
+
*/
|
|
46
|
+
function formatLlm(read) {
|
|
47
|
+
const lines = [];
|
|
48
|
+
lines.push(`FILE ${read.path}`);
|
|
49
|
+
if (read.mimeType) lines.push(`MIME ${read.mimeType}`);
|
|
50
|
+
if (read.size !== void 0) lines.push(`SIZE ${read.size}`);
|
|
51
|
+
lines.push(`CONTENT`);
|
|
52
|
+
lines.push(read.content ?? "");
|
|
53
|
+
return lines.join("\n");
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Human format: With syntax highlighting placeholder
|
|
57
|
+
*/
|
|
58
|
+
function formatHuman(read) {
|
|
59
|
+
return `${`--- ${read.path} ---`}\n${read.content ?? "(empty)"}`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
//#endregion
|
|
63
|
+
export { formatReadOutput, readCommand };
|
|
64
|
+
//# sourceMappingURL=read.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read.mjs","names":[],"sources":["../../src/commands/read.ts"],"sourcesContent":["import type { AFSRuntime } from \"../runtime.js\";\nimport type { ViewType } from \"./ls.js\";\n\nexport interface ReadResult {\n path: string;\n content?: string;\n type?: \"file\" | \"directory\";\n size?: number;\n mimeType?: string;\n}\n\n/**\n * Read file content\n */\nexport async function readCommand(runtime: AFSRuntime, path: string): Promise<ReadResult> {\n const result = await runtime.read(path);\n\n if (!result.data) {\n return {\n path,\n content: undefined,\n };\n }\n\n const entry = result.data;\n return {\n path: entry.path,\n content: typeof entry.content === \"string\" ? entry.content : JSON.stringify(entry.content),\n type: entry.metadata?.type === \"directory\" ? \"directory\" : \"file\",\n size: entry.metadata?.size,\n mimeType: entry.metadata?.mimeType,\n };\n}\n\n/**\n * Format read output for different views\n */\nexport function formatReadOutput(read: ReadResult, view: ViewType): string {\n switch (view) {\n case \"json\":\n return formatJson(read);\n case \"llm\":\n return formatLlm(read);\n case \"human\":\n return formatHuman(read);\n default:\n return formatDefault(read);\n }\n}\n\n/**\n * Default format: Raw content\n */\nfunction formatDefault(read: ReadResult): string {\n return read.content ?? \"\";\n}\n\n/**\n * JSON format\n */\nfunction formatJson(read: ReadResult): string {\n return JSON.stringify(read, null, 2);\n}\n\n/**\n * LLM format: Content with header\n */\nfunction formatLlm(read: ReadResult): string {\n const lines: string[] = [];\n\n lines.push(`FILE ${read.path}`);\n\n if (read.mimeType) {\n lines.push(`MIME ${read.mimeType}`);\n }\n\n if (read.size !== undefined) {\n lines.push(`SIZE ${read.size}`);\n }\n\n lines.push(`CONTENT`);\n lines.push(read.content ?? \"\");\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Human format: With syntax highlighting placeholder\n */\nfunction formatHuman(read: ReadResult): string {\n const header = `--- ${read.path} ---`;\n return `${header}\\n${read.content ?? \"(empty)\"}`;\n}\n"],"mappings":";;;;AAcA,eAAsB,YAAY,SAAqB,MAAmC;CACxF,MAAM,SAAS,MAAM,QAAQ,KAAK,KAAK;AAEvC,KAAI,CAAC,OAAO,KACV,QAAO;EACL;EACA,SAAS;EACV;CAGH,MAAM,QAAQ,OAAO;AACrB,QAAO;EACL,MAAM,MAAM;EACZ,SAAS,OAAO,MAAM,YAAY,WAAW,MAAM,UAAU,KAAK,UAAU,MAAM,QAAQ;EAC1F,MAAM,MAAM,UAAU,SAAS,cAAc,cAAc;EAC3D,MAAM,MAAM,UAAU;EACtB,UAAU,MAAM,UAAU;EAC3B;;;;;AAMH,SAAgB,iBAAiB,MAAkB,MAAwB;AACzE,SAAQ,MAAR;EACE,KAAK,OACH,QAAO,WAAW,KAAK;EACzB,KAAK,MACH,QAAO,UAAU,KAAK;EACxB,KAAK,QACH,QAAO,YAAY,KAAK;EAC1B,QACE,QAAO,cAAc,KAAK;;;;;;AAOhC,SAAS,cAAc,MAA0B;AAC/C,QAAO,KAAK,WAAW;;;;;AAMzB,SAAS,WAAW,MAA0B;AAC5C,QAAO,KAAK,UAAU,MAAM,MAAM,EAAE;;;;;AAMtC,SAAS,UAAU,MAA0B;CAC3C,MAAM,QAAkB,EAAE;AAE1B,OAAM,KAAK,QAAQ,KAAK,OAAO;AAE/B,KAAI,KAAK,SACP,OAAM,KAAK,QAAQ,KAAK,WAAW;AAGrC,KAAI,KAAK,SAAS,OAChB,OAAM,KAAK,QAAQ,KAAK,OAAO;AAGjC,OAAM,KAAK,UAAU;AACrB,OAAM,KAAK,KAAK,WAAW,GAAG;AAE9B,QAAO,MAAM,KAAK,KAAK;;;;;AAMzB,SAAS,YAAY,MAA0B;AAE7C,QAAO,GADQ,OAAO,KAAK,KAAK,MACf,IAAI,KAAK,WAAW"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
const require_rolldown_runtime = require('../_virtual/rolldown_runtime.cjs');
|
|
2
|
+
const require_loader = require('../config/loader.cjs');
|
|
3
|
+
const require_runtime = require('../runtime.cjs');
|
|
4
|
+
let node_http = require("node:http");
|
|
5
|
+
let _aigne_afs_http = require("@aigne/afs-http");
|
|
6
|
+
|
|
7
|
+
//#region src/commands/serve.ts
|
|
8
|
+
/**
|
|
9
|
+
* Create an AFSModule wrapper around AFSRuntime
|
|
10
|
+
*/
|
|
11
|
+
function createRuntimeModule(runtime, readonly) {
|
|
12
|
+
return {
|
|
13
|
+
name: "afs-server",
|
|
14
|
+
accessMode: readonly ? "readonly" : "readwrite",
|
|
15
|
+
async list(path, options) {
|
|
16
|
+
return runtime.list(path, options);
|
|
17
|
+
},
|
|
18
|
+
async read(path, options) {
|
|
19
|
+
return runtime.read(path, options);
|
|
20
|
+
},
|
|
21
|
+
async write(path, content, options) {
|
|
22
|
+
if (readonly) throw new Error("Server is in readonly mode");
|
|
23
|
+
return runtime.write(path, content, options);
|
|
24
|
+
},
|
|
25
|
+
async delete(path, options) {
|
|
26
|
+
if (readonly) throw new Error("Server is in readonly mode");
|
|
27
|
+
return runtime.delete(path, options);
|
|
28
|
+
},
|
|
29
|
+
async search(path, query, options) {
|
|
30
|
+
return runtime.search(path, query, options);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Start HTTP server to expose AFS providers
|
|
36
|
+
*/
|
|
37
|
+
async function serveCommand(options = {}) {
|
|
38
|
+
const config = await new require_loader.ConfigLoader().load(process.cwd());
|
|
39
|
+
const serveConfig = config.serve ?? {};
|
|
40
|
+
const host = options.host ?? serveConfig.host ?? "localhost";
|
|
41
|
+
const port = options.port ?? serveConfig.port ?? 3e3;
|
|
42
|
+
const basePath = options.path ?? serveConfig.path ?? "/afs";
|
|
43
|
+
const readonly = options.readonly ?? serveConfig.readonly ?? false;
|
|
44
|
+
const cors = options.cors ?? serveConfig.cors ?? false;
|
|
45
|
+
const maxBodySize = options.maxBodySize ?? serveConfig.max_body_size ?? 10 * 1024 * 1024;
|
|
46
|
+
const runtime = await require_runtime.createRuntime();
|
|
47
|
+
const mounts = config.mounts.map((m) => ({
|
|
48
|
+
path: m.path,
|
|
49
|
+
provider: m.uri
|
|
50
|
+
}));
|
|
51
|
+
const handler = (0, _aigne_afs_http.createAFSHttpHandler)({
|
|
52
|
+
module: createRuntimeModule(runtime, readonly),
|
|
53
|
+
maxBodySize
|
|
54
|
+
});
|
|
55
|
+
const server = (0, node_http.createServer)(async (req, res) => {
|
|
56
|
+
const url = new URL(req.url || "/", `http://${req.headers.host}`);
|
|
57
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
58
|
+
console.error(`${timestamp} ${req.method} ${url.pathname}`);
|
|
59
|
+
if (cors) {
|
|
60
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
61
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
|
|
62
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
|
63
|
+
if (req.method === "OPTIONS") {
|
|
64
|
+
res.writeHead(204);
|
|
65
|
+
res.end();
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (!url.pathname.startsWith(basePath)) {
|
|
70
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
71
|
+
res.end(JSON.stringify({
|
|
72
|
+
code: 1,
|
|
73
|
+
error: "Not found"
|
|
74
|
+
}));
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const headers = new Headers();
|
|
78
|
+
for (const [key, value] of Object.entries(req.headers)) if (value) headers.append(key, Array.isArray(value) ? value.join(", ") : value);
|
|
79
|
+
const chunks = [];
|
|
80
|
+
for await (const chunk of req) chunks.push(chunk);
|
|
81
|
+
const body = Buffer.concat(chunks);
|
|
82
|
+
const request = new Request(`http://${req.headers.host}${req.url}`, {
|
|
83
|
+
method: req.method,
|
|
84
|
+
headers,
|
|
85
|
+
body: body.length > 0 ? body : void 0
|
|
86
|
+
});
|
|
87
|
+
try {
|
|
88
|
+
const response = await handler(request);
|
|
89
|
+
res.writeHead(response.status, { "Content-Type": response.headers.get("Content-Type") || "application/json" });
|
|
90
|
+
const responseBody = await response.text();
|
|
91
|
+
res.end(responseBody);
|
|
92
|
+
console.error(`${timestamp} ${req.method} ${url.pathname} status=${response.status}`);
|
|
93
|
+
} catch (error) {
|
|
94
|
+
console.error(`Error handling request:`, error);
|
|
95
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
96
|
+
res.end(JSON.stringify({
|
|
97
|
+
code: 5,
|
|
98
|
+
error: "Internal server error"
|
|
99
|
+
}));
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
await new Promise((resolve, reject) => {
|
|
103
|
+
server.listen(port, host, () => {
|
|
104
|
+
resolve();
|
|
105
|
+
});
|
|
106
|
+
server.on("error", reject);
|
|
107
|
+
});
|
|
108
|
+
const shutdown = () => {
|
|
109
|
+
console.error("\nShutting down server...");
|
|
110
|
+
server.close(() => {
|
|
111
|
+
process.exit(0);
|
|
112
|
+
});
|
|
113
|
+
};
|
|
114
|
+
process.on("SIGINT", shutdown);
|
|
115
|
+
process.on("SIGTERM", shutdown);
|
|
116
|
+
return {
|
|
117
|
+
success: true,
|
|
118
|
+
host,
|
|
119
|
+
port,
|
|
120
|
+
path: basePath,
|
|
121
|
+
url: `http://${host}:${port}${basePath}`,
|
|
122
|
+
mounts
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Format serve result for output
|
|
127
|
+
*/
|
|
128
|
+
function formatServeOutput(result) {
|
|
129
|
+
const lines = [];
|
|
130
|
+
lines.push("AFS HTTP Server starting...");
|
|
131
|
+
lines.push("Mounted providers:");
|
|
132
|
+
for (const mount of result.mounts) lines.push(` ${mount.path.padEnd(20)} ${mount.provider}`);
|
|
133
|
+
lines.push("");
|
|
134
|
+
lines.push(`Listening on: ${result.url}`);
|
|
135
|
+
lines.push("Press Ctrl+C to stop");
|
|
136
|
+
return lines.join("\n");
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
//#endregion
|
|
140
|
+
exports.formatServeOutput = formatServeOutput;
|
|
141
|
+
exports.serveCommand = serveCommand;
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { ConfigLoader } from "../config/loader.mjs";
|
|
2
|
+
import { createRuntime } from "../runtime.mjs";
|
|
3
|
+
import { createServer } from "node:http";
|
|
4
|
+
import { createAFSHttpHandler } from "@aigne/afs-http";
|
|
5
|
+
|
|
6
|
+
//#region src/commands/serve.ts
|
|
7
|
+
/**
|
|
8
|
+
* Create an AFSModule wrapper around AFSRuntime
|
|
9
|
+
*/
|
|
10
|
+
function createRuntimeModule(runtime, readonly) {
|
|
11
|
+
return {
|
|
12
|
+
name: "afs-server",
|
|
13
|
+
accessMode: readonly ? "readonly" : "readwrite",
|
|
14
|
+
async list(path, options) {
|
|
15
|
+
return runtime.list(path, options);
|
|
16
|
+
},
|
|
17
|
+
async read(path, options) {
|
|
18
|
+
return runtime.read(path, options);
|
|
19
|
+
},
|
|
20
|
+
async write(path, content, options) {
|
|
21
|
+
if (readonly) throw new Error("Server is in readonly mode");
|
|
22
|
+
return runtime.write(path, content, options);
|
|
23
|
+
},
|
|
24
|
+
async delete(path, options) {
|
|
25
|
+
if (readonly) throw new Error("Server is in readonly mode");
|
|
26
|
+
return runtime.delete(path, options);
|
|
27
|
+
},
|
|
28
|
+
async search(path, query, options) {
|
|
29
|
+
return runtime.search(path, query, options);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Start HTTP server to expose AFS providers
|
|
35
|
+
*/
|
|
36
|
+
async function serveCommand(options = {}) {
|
|
37
|
+
const config = await new ConfigLoader().load(process.cwd());
|
|
38
|
+
const serveConfig = config.serve ?? {};
|
|
39
|
+
const host = options.host ?? serveConfig.host ?? "localhost";
|
|
40
|
+
const port = options.port ?? serveConfig.port ?? 3e3;
|
|
41
|
+
const basePath = options.path ?? serveConfig.path ?? "/afs";
|
|
42
|
+
const readonly = options.readonly ?? serveConfig.readonly ?? false;
|
|
43
|
+
const cors = options.cors ?? serveConfig.cors ?? false;
|
|
44
|
+
const maxBodySize = options.maxBodySize ?? serveConfig.max_body_size ?? 10 * 1024 * 1024;
|
|
45
|
+
const runtime = await createRuntime();
|
|
46
|
+
const mounts = config.mounts.map((m) => ({
|
|
47
|
+
path: m.path,
|
|
48
|
+
provider: m.uri
|
|
49
|
+
}));
|
|
50
|
+
const handler = createAFSHttpHandler({
|
|
51
|
+
module: createRuntimeModule(runtime, readonly),
|
|
52
|
+
maxBodySize
|
|
53
|
+
});
|
|
54
|
+
const server = createServer(async (req, res) => {
|
|
55
|
+
const url = new URL(req.url || "/", `http://${req.headers.host}`);
|
|
56
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
57
|
+
console.error(`${timestamp} ${req.method} ${url.pathname}`);
|
|
58
|
+
if (cors) {
|
|
59
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
60
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
|
|
61
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
|
62
|
+
if (req.method === "OPTIONS") {
|
|
63
|
+
res.writeHead(204);
|
|
64
|
+
res.end();
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (!url.pathname.startsWith(basePath)) {
|
|
69
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
70
|
+
res.end(JSON.stringify({
|
|
71
|
+
code: 1,
|
|
72
|
+
error: "Not found"
|
|
73
|
+
}));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const headers = new Headers();
|
|
77
|
+
for (const [key, value] of Object.entries(req.headers)) if (value) headers.append(key, Array.isArray(value) ? value.join(", ") : value);
|
|
78
|
+
const chunks = [];
|
|
79
|
+
for await (const chunk of req) chunks.push(chunk);
|
|
80
|
+
const body = Buffer.concat(chunks);
|
|
81
|
+
const request = new Request(`http://${req.headers.host}${req.url}`, {
|
|
82
|
+
method: req.method,
|
|
83
|
+
headers,
|
|
84
|
+
body: body.length > 0 ? body : void 0
|
|
85
|
+
});
|
|
86
|
+
try {
|
|
87
|
+
const response = await handler(request);
|
|
88
|
+
res.writeHead(response.status, { "Content-Type": response.headers.get("Content-Type") || "application/json" });
|
|
89
|
+
const responseBody = await response.text();
|
|
90
|
+
res.end(responseBody);
|
|
91
|
+
console.error(`${timestamp} ${req.method} ${url.pathname} status=${response.status}`);
|
|
92
|
+
} catch (error) {
|
|
93
|
+
console.error(`Error handling request:`, error);
|
|
94
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
95
|
+
res.end(JSON.stringify({
|
|
96
|
+
code: 5,
|
|
97
|
+
error: "Internal server error"
|
|
98
|
+
}));
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
await new Promise((resolve, reject) => {
|
|
102
|
+
server.listen(port, host, () => {
|
|
103
|
+
resolve();
|
|
104
|
+
});
|
|
105
|
+
server.on("error", reject);
|
|
106
|
+
});
|
|
107
|
+
const shutdown = () => {
|
|
108
|
+
console.error("\nShutting down server...");
|
|
109
|
+
server.close(() => {
|
|
110
|
+
process.exit(0);
|
|
111
|
+
});
|
|
112
|
+
};
|
|
113
|
+
process.on("SIGINT", shutdown);
|
|
114
|
+
process.on("SIGTERM", shutdown);
|
|
115
|
+
return {
|
|
116
|
+
success: true,
|
|
117
|
+
host,
|
|
118
|
+
port,
|
|
119
|
+
path: basePath,
|
|
120
|
+
url: `http://${host}:${port}${basePath}`,
|
|
121
|
+
mounts
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Format serve result for output
|
|
126
|
+
*/
|
|
127
|
+
function formatServeOutput(result) {
|
|
128
|
+
const lines = [];
|
|
129
|
+
lines.push("AFS HTTP Server starting...");
|
|
130
|
+
lines.push("Mounted providers:");
|
|
131
|
+
for (const mount of result.mounts) lines.push(` ${mount.path.padEnd(20)} ${mount.provider}`);
|
|
132
|
+
lines.push("");
|
|
133
|
+
lines.push(`Listening on: ${result.url}`);
|
|
134
|
+
lines.push("Press Ctrl+C to stop");
|
|
135
|
+
return lines.join("\n");
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
//#endregion
|
|
139
|
+
export { formatServeOutput, serveCommand };
|
|
140
|
+
//# sourceMappingURL=serve.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serve.mjs","names":[],"sources":["../../src/commands/serve.ts"],"sourcesContent":["/**\n * AFS Serve Command\n *\n * Starts an HTTP server to expose AFS providers over HTTP transport\n */\n\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { createServer } from \"node:http\";\nimport type { AFSModule } from \"@aigne/afs\";\nimport { createAFSHttpHandler } from \"@aigne/afs-http\";\nimport { ConfigLoader } from \"../config/loader.js\";\nimport type { MountConfig, ServeConfig } from \"../config/schema.js\";\nimport { type AFSRuntime, createRuntime } from \"../runtime.js\";\n\nexport interface ServeOptions {\n host?: string;\n port?: number;\n path?: string;\n readonly?: boolean;\n cors?: boolean;\n maxBodySize?: number;\n}\n\nexport interface ServeResult {\n success: boolean;\n host: string;\n port: number;\n path: string;\n url: string;\n mounts: Array<{ path: string; provider: string }>;\n}\n\n/**\n * Create an AFSModule wrapper around AFSRuntime\n */\nfunction createRuntimeModule(runtime: AFSRuntime, readonly: boolean): AFSModule {\n return {\n name: \"afs-server\",\n accessMode: readonly ? \"readonly\" : \"readwrite\",\n async list(path, options) {\n return runtime.list(path, options);\n },\n async read(path, options) {\n return runtime.read(path, options);\n },\n async write(path, content, options) {\n if (readonly) {\n throw new Error(\"Server is in readonly mode\");\n }\n return runtime.write(path, content, options);\n },\n async delete(path, options) {\n if (readonly) {\n throw new Error(\"Server is in readonly mode\");\n }\n return runtime.delete(path, options);\n },\n async search(path, query, options) {\n return runtime.search(path, query, options);\n },\n };\n}\n\n/**\n * Start HTTP server to expose AFS providers\n */\nexport async function serveCommand(options: ServeOptions = {}): Promise<ServeResult> {\n // Load config to get mount information and serve defaults\n const configLoader = new ConfigLoader();\n const config = await configLoader.load(process.cwd());\n\n // Get serve config from config file (may be partial or undefined)\n const serveConfig: Partial<ServeConfig> = config.serve ?? {};\n\n // Merge: command line > config file > defaults\n const host = options.host ?? serveConfig.host ?? \"localhost\";\n const port = options.port ?? serveConfig.port ?? 3000;\n const basePath = options.path ?? serveConfig.path ?? \"/afs\";\n const readonly = options.readonly ?? serveConfig.readonly ?? false;\n const cors = options.cors ?? serveConfig.cors ?? false;\n const maxBodySize = options.maxBodySize ?? serveConfig.max_body_size ?? 10 * 1024 * 1024;\n\n // Create runtime and load all mounts\n const runtime = await createRuntime();\n const mounts: Array<{ path: string; provider: string }> = config.mounts.map((m: MountConfig) => ({\n path: m.path,\n provider: m.uri,\n }));\n\n // Create AFSModule wrapper\n const module = createRuntimeModule(runtime, readonly);\n\n // Create HTTP handler\n const handler = createAFSHttpHandler({\n module,\n maxBodySize,\n });\n\n // Create HTTP server with path routing\n const server = createServer(async (req: IncomingMessage, res: ServerResponse) => {\n const url = new URL(req.url || \"/\", `http://${req.headers.host}`);\n\n // Log request (default view)\n const timestamp = new Date().toISOString();\n console.error(`${timestamp} ${req.method} ${url.pathname}`);\n\n // CORS support\n if (cors) {\n res.setHeader(\"Access-Control-Allow-Origin\", \"*\");\n res.setHeader(\"Access-Control-Allow-Methods\", \"GET, POST, PUT, DELETE, OPTIONS\");\n res.setHeader(\"Access-Control-Allow-Headers\", \"Content-Type, Authorization\");\n\n if (req.method === \"OPTIONS\") {\n res.writeHead(204);\n res.end();\n return;\n }\n }\n\n // Check if request is for our base path\n if (!url.pathname.startsWith(basePath)) {\n res.writeHead(404, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ code: 1, error: \"Not found\" }));\n return;\n }\n\n // Convert Node.js request to Web Standard Request\n const headers = new Headers();\n for (const [key, value] of Object.entries(req.headers)) {\n if (value) {\n headers.append(key, Array.isArray(value) ? value.join(\", \") : value);\n }\n }\n\n // Collect request body\n const chunks: Buffer[] = [];\n for await (const chunk of req) {\n chunks.push(chunk);\n }\n const body = Buffer.concat(chunks);\n\n const request = new Request(`http://${req.headers.host}${req.url}`, {\n method: req.method,\n headers,\n body: body.length > 0 ? body : undefined,\n });\n\n try {\n // Call handler\n const response = await handler(request);\n\n // Copy response to Node.js response\n res.writeHead(response.status, {\n \"Content-Type\": response.headers.get(\"Content-Type\") || \"application/json\",\n });\n\n const responseBody = await response.text();\n res.end(responseBody);\n\n // Log response\n console.error(`${timestamp} ${req.method} ${url.pathname} status=${response.status}`);\n } catch (error) {\n console.error(`Error handling request:`, error);\n res.writeHead(500, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ code: 5, error: \"Internal server error\" }));\n }\n });\n\n // Start server\n await new Promise<void>((resolve, reject) => {\n server.listen(port, host, () => {\n resolve();\n });\n server.on(\"error\", reject);\n });\n\n // Handle graceful shutdown\n const shutdown = () => {\n console.error(\"\\nShutting down server...\");\n server.close(() => {\n process.exit(0);\n });\n };\n\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n\n const url = `http://${host}:${port}${basePath}`;\n\n return {\n success: true,\n host,\n port,\n path: basePath,\n url,\n mounts,\n };\n}\n\n/**\n * Format serve result for output\n */\nexport function formatServeOutput(result: ServeResult): string {\n const lines: string[] = [];\n\n lines.push(\"AFS HTTP Server starting...\");\n lines.push(\"Mounted providers:\");\n\n for (const mount of result.mounts) {\n lines.push(` ${mount.path.padEnd(20)} ${mount.provider}`);\n }\n\n lines.push(\"\");\n lines.push(`Listening on: ${result.url}`);\n lines.push(\"Press Ctrl+C to stop\");\n\n return lines.join(\"\\n\");\n}\n"],"mappings":";;;;;;;;;AAmCA,SAAS,oBAAoB,SAAqB,UAA8B;AAC9E,QAAO;EACL,MAAM;EACN,YAAY,WAAW,aAAa;EACpC,MAAM,KAAK,MAAM,SAAS;AACxB,UAAO,QAAQ,KAAK,MAAM,QAAQ;;EAEpC,MAAM,KAAK,MAAM,SAAS;AACxB,UAAO,QAAQ,KAAK,MAAM,QAAQ;;EAEpC,MAAM,MAAM,MAAM,SAAS,SAAS;AAClC,OAAI,SACF,OAAM,IAAI,MAAM,6BAA6B;AAE/C,UAAO,QAAQ,MAAM,MAAM,SAAS,QAAQ;;EAE9C,MAAM,OAAO,MAAM,SAAS;AAC1B,OAAI,SACF,OAAM,IAAI,MAAM,6BAA6B;AAE/C,UAAO,QAAQ,OAAO,MAAM,QAAQ;;EAEtC,MAAM,OAAO,MAAM,OAAO,SAAS;AACjC,UAAO,QAAQ,OAAO,MAAM,OAAO,QAAQ;;EAE9C;;;;;AAMH,eAAsB,aAAa,UAAwB,EAAE,EAAwB;CAGnF,MAAM,SAAS,MADM,IAAI,cAAc,CACL,KAAK,QAAQ,KAAK,CAAC;CAGrD,MAAM,cAAoC,OAAO,SAAS,EAAE;CAG5D,MAAM,OAAO,QAAQ,QAAQ,YAAY,QAAQ;CACjD,MAAM,OAAO,QAAQ,QAAQ,YAAY,QAAQ;CACjD,MAAM,WAAW,QAAQ,QAAQ,YAAY,QAAQ;CACrD,MAAM,WAAW,QAAQ,YAAY,YAAY,YAAY;CAC7D,MAAM,OAAO,QAAQ,QAAQ,YAAY,QAAQ;CACjD,MAAM,cAAc,QAAQ,eAAe,YAAY,iBAAiB,KAAK,OAAO;CAGpF,MAAM,UAAU,MAAM,eAAe;CACrC,MAAM,SAAoD,OAAO,OAAO,KAAK,OAAoB;EAC/F,MAAM,EAAE;EACR,UAAU,EAAE;EACb,EAAE;CAMH,MAAM,UAAU,qBAAqB;EACnC,QAJa,oBAAoB,SAAS,SAAS;EAKnD;EACD,CAAC;CAGF,MAAM,SAAS,aAAa,OAAO,KAAsB,QAAwB;EAC/E,MAAM,MAAM,IAAI,IAAI,IAAI,OAAO,KAAK,UAAU,IAAI,QAAQ,OAAO;EAGjE,MAAM,6BAAY,IAAI,MAAM,EAAC,aAAa;AAC1C,UAAQ,MAAM,GAAG,UAAU,GAAG,IAAI,OAAO,GAAG,IAAI,WAAW;AAG3D,MAAI,MAAM;AACR,OAAI,UAAU,+BAA+B,IAAI;AACjD,OAAI,UAAU,gCAAgC,kCAAkC;AAChF,OAAI,UAAU,gCAAgC,8BAA8B;AAE5E,OAAI,IAAI,WAAW,WAAW;AAC5B,QAAI,UAAU,IAAI;AAClB,QAAI,KAAK;AACT;;;AAKJ,MAAI,CAAC,IAAI,SAAS,WAAW,SAAS,EAAE;AACtC,OAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,OAAI,IAAI,KAAK,UAAU;IAAE,MAAM;IAAG,OAAO;IAAa,CAAC,CAAC;AACxD;;EAIF,MAAM,UAAU,IAAI,SAAS;AAC7B,OAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,IAAI,QAAQ,CACpD,KAAI,MACF,SAAQ,OAAO,KAAK,MAAM,QAAQ,MAAM,GAAG,MAAM,KAAK,KAAK,GAAG,MAAM;EAKxE,MAAM,SAAmB,EAAE;AAC3B,aAAW,MAAM,SAAS,IACxB,QAAO,KAAK,MAAM;EAEpB,MAAM,OAAO,OAAO,OAAO,OAAO;EAElC,MAAM,UAAU,IAAI,QAAQ,UAAU,IAAI,QAAQ,OAAO,IAAI,OAAO;GAClE,QAAQ,IAAI;GACZ;GACA,MAAM,KAAK,SAAS,IAAI,OAAO;GAChC,CAAC;AAEF,MAAI;GAEF,MAAM,WAAW,MAAM,QAAQ,QAAQ;AAGvC,OAAI,UAAU,SAAS,QAAQ,EAC7B,gBAAgB,SAAS,QAAQ,IAAI,eAAe,IAAI,oBACzD,CAAC;GAEF,MAAM,eAAe,MAAM,SAAS,MAAM;AAC1C,OAAI,IAAI,aAAa;AAGrB,WAAQ,MAAM,GAAG,UAAU,GAAG,IAAI,OAAO,GAAG,IAAI,SAAS,UAAU,SAAS,SAAS;WAC9E,OAAO;AACd,WAAQ,MAAM,2BAA2B,MAAM;AAC/C,OAAI,UAAU,KAAK,EAAE,gBAAgB,oBAAoB,CAAC;AAC1D,OAAI,IAAI,KAAK,UAAU;IAAE,MAAM;IAAG,OAAO;IAAyB,CAAC,CAAC;;GAEtE;AAGF,OAAM,IAAI,SAAe,SAAS,WAAW;AAC3C,SAAO,OAAO,MAAM,YAAY;AAC9B,YAAS;IACT;AACF,SAAO,GAAG,SAAS,OAAO;GAC1B;CAGF,MAAM,iBAAiB;AACrB,UAAQ,MAAM,4BAA4B;AAC1C,SAAO,YAAY;AACjB,WAAQ,KAAK,EAAE;IACf;;AAGJ,SAAQ,GAAG,UAAU,SAAS;AAC9B,SAAQ,GAAG,WAAW,SAAS;AAI/B,QAAO;EACL,SAAS;EACT;EACA;EACA,MAAM;EACN,KAPU,UAAU,KAAK,GAAG,OAAO;EAQnC;EACD;;;;;AAMH,SAAgB,kBAAkB,QAA6B;CAC7D,MAAM,QAAkB,EAAE;AAE1B,OAAM,KAAK,8BAA8B;AACzC,OAAM,KAAK,qBAAqB;AAEhC,MAAK,MAAM,SAAS,OAAO,OACzB,OAAM,KAAK,KAAK,MAAM,KAAK,OAAO,GAAG,CAAC,GAAG,MAAM,WAAW;AAG5D,OAAM,KAAK,GAAG;AACd,OAAM,KAAK,iBAAiB,OAAO,MAAM;AACzC,OAAM,KAAK,uBAAuB;AAElC,QAAO,MAAM,KAAK,KAAK"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
|
|
2
|
+
//#region src/commands/stat.ts
|
|
3
|
+
/**
|
|
4
|
+
* Get stat information for a path
|
|
5
|
+
*/
|
|
6
|
+
async function statCommand(runtime, path) {
|
|
7
|
+
const readResult = await runtime.read(path);
|
|
8
|
+
if (!readResult.data) {
|
|
9
|
+
const entry$1 = (await runtime.list(path, { maxDepth: 0 })).data.find((e) => e.path === path);
|
|
10
|
+
if (!entry$1) return {
|
|
11
|
+
path,
|
|
12
|
+
type: "file"
|
|
13
|
+
};
|
|
14
|
+
return {
|
|
15
|
+
path: entry$1.path,
|
|
16
|
+
type: mapEntryType(entry$1.metadata?.type),
|
|
17
|
+
size: entry$1.metadata?.size,
|
|
18
|
+
modified: entry$1.updatedAt?.toISOString(),
|
|
19
|
+
created: entry$1.createdAt?.toISOString()
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
const entry = readResult.data;
|
|
23
|
+
return {
|
|
24
|
+
path: entry.path,
|
|
25
|
+
type: mapEntryType(entry.metadata?.type),
|
|
26
|
+
size: entry.metadata?.size,
|
|
27
|
+
modified: entry.updatedAt?.toISOString(),
|
|
28
|
+
created: entry.createdAt?.toISOString()
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function mapEntryType(type) {
|
|
32
|
+
switch (type) {
|
|
33
|
+
case "file": return "file";
|
|
34
|
+
case "directory":
|
|
35
|
+
case "dir": return "directory";
|
|
36
|
+
case "exec": return "exec";
|
|
37
|
+
case "link": return "link";
|
|
38
|
+
default: return "file";
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Format stat output for different views
|
|
43
|
+
*/
|
|
44
|
+
function formatStatOutput(stat, view) {
|
|
45
|
+
switch (view) {
|
|
46
|
+
case "json": return formatJson(stat);
|
|
47
|
+
case "llm": return formatLlm(stat);
|
|
48
|
+
case "human": return formatHuman(stat);
|
|
49
|
+
default: return formatDefault(stat);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Default format: KEY=VALUE pairs
|
|
54
|
+
*/
|
|
55
|
+
function formatDefault(stat) {
|
|
56
|
+
const lines = [];
|
|
57
|
+
lines.push(`PATH=${stat.path}`);
|
|
58
|
+
lines.push(`TYPE=${stat.type}`);
|
|
59
|
+
if (stat.size !== void 0) lines.push(`SIZE=${stat.size}`);
|
|
60
|
+
if (stat.modified) lines.push(`MODIFIED=${stat.modified}`);
|
|
61
|
+
if (stat.created) lines.push(`CREATED=${stat.created}`);
|
|
62
|
+
if (stat.hash) lines.push(`HASH=${stat.hash}`);
|
|
63
|
+
if (stat.provider) lines.push(`PROVIDER=${stat.provider}`);
|
|
64
|
+
return lines.join("\n");
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* JSON format
|
|
68
|
+
*/
|
|
69
|
+
function formatJson(stat) {
|
|
70
|
+
return JSON.stringify(stat, null, 2);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* LLM format: Uppercase keys, semantic
|
|
74
|
+
*/
|
|
75
|
+
function formatLlm(stat) {
|
|
76
|
+
const lines = [];
|
|
77
|
+
const typeLabel = stat.type.toUpperCase();
|
|
78
|
+
lines.push(`${typeLabel} ${stat.path}`);
|
|
79
|
+
lines.push(`TYPE ${typeLabel}`);
|
|
80
|
+
if (stat.size !== void 0) lines.push(`SIZE ${stat.size}`);
|
|
81
|
+
if (stat.hash) lines.push(`HASH ${stat.hash}`);
|
|
82
|
+
if (stat.modified) lines.push(`UPDATED ${stat.modified}`);
|
|
83
|
+
lines.push(`SIDE_EFFECT NONE`);
|
|
84
|
+
return lines.join("\n");
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Human format: Formatted table
|
|
88
|
+
*/
|
|
89
|
+
function formatHuman(stat) {
|
|
90
|
+
const lines = [];
|
|
91
|
+
lines.push(`Path: ${stat.path}`);
|
|
92
|
+
lines.push(`Type: ${stat.type}`);
|
|
93
|
+
if (stat.size !== void 0) lines.push(`Size: ${formatSize(stat.size)}`);
|
|
94
|
+
if (stat.modified) lines.push(`Modified: ${formatDate(stat.modified)}`);
|
|
95
|
+
if (stat.provider) lines.push(`Provider: ${stat.provider}`);
|
|
96
|
+
return lines.join("\n");
|
|
97
|
+
}
|
|
98
|
+
function formatSize(bytes) {
|
|
99
|
+
if (bytes < 1024) return `${bytes} bytes`;
|
|
100
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
101
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
102
|
+
}
|
|
103
|
+
function formatDate(isoString) {
|
|
104
|
+
try {
|
|
105
|
+
return new Date(isoString).toLocaleString();
|
|
106
|
+
} catch {
|
|
107
|
+
return isoString;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
//#endregion
|
|
112
|
+
exports.formatStatOutput = formatStatOutput;
|
|
113
|
+
exports.statCommand = statCommand;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
//#region src/commands/stat.ts
|
|
2
|
+
/**
|
|
3
|
+
* Get stat information for a path
|
|
4
|
+
*/
|
|
5
|
+
async function statCommand(runtime, path) {
|
|
6
|
+
const readResult = await runtime.read(path);
|
|
7
|
+
if (!readResult.data) {
|
|
8
|
+
const entry$1 = (await runtime.list(path, { maxDepth: 0 })).data.find((e) => e.path === path);
|
|
9
|
+
if (!entry$1) return {
|
|
10
|
+
path,
|
|
11
|
+
type: "file"
|
|
12
|
+
};
|
|
13
|
+
return {
|
|
14
|
+
path: entry$1.path,
|
|
15
|
+
type: mapEntryType(entry$1.metadata?.type),
|
|
16
|
+
size: entry$1.metadata?.size,
|
|
17
|
+
modified: entry$1.updatedAt?.toISOString(),
|
|
18
|
+
created: entry$1.createdAt?.toISOString()
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
const entry = readResult.data;
|
|
22
|
+
return {
|
|
23
|
+
path: entry.path,
|
|
24
|
+
type: mapEntryType(entry.metadata?.type),
|
|
25
|
+
size: entry.metadata?.size,
|
|
26
|
+
modified: entry.updatedAt?.toISOString(),
|
|
27
|
+
created: entry.createdAt?.toISOString()
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function mapEntryType(type) {
|
|
31
|
+
switch (type) {
|
|
32
|
+
case "file": return "file";
|
|
33
|
+
case "directory":
|
|
34
|
+
case "dir": return "directory";
|
|
35
|
+
case "exec": return "exec";
|
|
36
|
+
case "link": return "link";
|
|
37
|
+
default: return "file";
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Format stat output for different views
|
|
42
|
+
*/
|
|
43
|
+
function formatStatOutput(stat, view) {
|
|
44
|
+
switch (view) {
|
|
45
|
+
case "json": return formatJson(stat);
|
|
46
|
+
case "llm": return formatLlm(stat);
|
|
47
|
+
case "human": return formatHuman(stat);
|
|
48
|
+
default: return formatDefault(stat);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Default format: KEY=VALUE pairs
|
|
53
|
+
*/
|
|
54
|
+
function formatDefault(stat) {
|
|
55
|
+
const lines = [];
|
|
56
|
+
lines.push(`PATH=${stat.path}`);
|
|
57
|
+
lines.push(`TYPE=${stat.type}`);
|
|
58
|
+
if (stat.size !== void 0) lines.push(`SIZE=${stat.size}`);
|
|
59
|
+
if (stat.modified) lines.push(`MODIFIED=${stat.modified}`);
|
|
60
|
+
if (stat.created) lines.push(`CREATED=${stat.created}`);
|
|
61
|
+
if (stat.hash) lines.push(`HASH=${stat.hash}`);
|
|
62
|
+
if (stat.provider) lines.push(`PROVIDER=${stat.provider}`);
|
|
63
|
+
return lines.join("\n");
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* JSON format
|
|
67
|
+
*/
|
|
68
|
+
function formatJson(stat) {
|
|
69
|
+
return JSON.stringify(stat, null, 2);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* LLM format: Uppercase keys, semantic
|
|
73
|
+
*/
|
|
74
|
+
function formatLlm(stat) {
|
|
75
|
+
const lines = [];
|
|
76
|
+
const typeLabel = stat.type.toUpperCase();
|
|
77
|
+
lines.push(`${typeLabel} ${stat.path}`);
|
|
78
|
+
lines.push(`TYPE ${typeLabel}`);
|
|
79
|
+
if (stat.size !== void 0) lines.push(`SIZE ${stat.size}`);
|
|
80
|
+
if (stat.hash) lines.push(`HASH ${stat.hash}`);
|
|
81
|
+
if (stat.modified) lines.push(`UPDATED ${stat.modified}`);
|
|
82
|
+
lines.push(`SIDE_EFFECT NONE`);
|
|
83
|
+
return lines.join("\n");
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Human format: Formatted table
|
|
87
|
+
*/
|
|
88
|
+
function formatHuman(stat) {
|
|
89
|
+
const lines = [];
|
|
90
|
+
lines.push(`Path: ${stat.path}`);
|
|
91
|
+
lines.push(`Type: ${stat.type}`);
|
|
92
|
+
if (stat.size !== void 0) lines.push(`Size: ${formatSize(stat.size)}`);
|
|
93
|
+
if (stat.modified) lines.push(`Modified: ${formatDate(stat.modified)}`);
|
|
94
|
+
if (stat.provider) lines.push(`Provider: ${stat.provider}`);
|
|
95
|
+
return lines.join("\n");
|
|
96
|
+
}
|
|
97
|
+
function formatSize(bytes) {
|
|
98
|
+
if (bytes < 1024) return `${bytes} bytes`;
|
|
99
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
100
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
101
|
+
}
|
|
102
|
+
function formatDate(isoString) {
|
|
103
|
+
try {
|
|
104
|
+
return new Date(isoString).toLocaleString();
|
|
105
|
+
} catch {
|
|
106
|
+
return isoString;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
//#endregion
|
|
111
|
+
export { formatStatOutput, statCommand };
|
|
112
|
+
//# sourceMappingURL=stat.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stat.mjs","names":["entry"],"sources":["../../src/commands/stat.ts"],"sourcesContent":["import type { AFSEntry } from \"@aigne/afs\";\nimport type { AFSRuntime } from \"../runtime.js\";\nimport type { ViewType } from \"./ls.js\";\n\nexport interface StatResult {\n path: string;\n type: \"file\" | \"directory\" | \"exec\" | \"link\";\n size?: number;\n modified?: string;\n created?: string;\n hash?: string;\n provider?: string;\n permissions?: string[];\n}\n\n/**\n * Get stat information for a path\n */\nexport async function statCommand(runtime: AFSRuntime, path: string): Promise<StatResult> {\n // Use read to get detailed metadata\n const readResult = await runtime.read(path);\n\n if (!readResult.data) {\n // Try list as fallback\n const listResult = await runtime.list(path, { maxDepth: 0 });\n const entry = listResult.data.find((e: AFSEntry) => e.path === path);\n\n if (!entry) {\n return {\n path,\n type: \"file\",\n };\n }\n\n return {\n path: entry.path,\n type: mapEntryType(entry.metadata?.type),\n size: entry.metadata?.size,\n modified: entry.updatedAt?.toISOString(),\n created: entry.createdAt?.toISOString(),\n };\n }\n\n const entry = readResult.data;\n return {\n path: entry.path,\n type: mapEntryType(entry.metadata?.type),\n size: entry.metadata?.size,\n modified: entry.updatedAt?.toISOString(),\n created: entry.createdAt?.toISOString(),\n };\n}\n\nfunction mapEntryType(type?: string): StatResult[\"type\"] {\n switch (type) {\n case \"file\":\n return \"file\";\n case \"directory\":\n case \"dir\":\n return \"directory\";\n case \"exec\":\n return \"exec\";\n case \"link\":\n return \"link\";\n default:\n return \"file\";\n }\n}\n\n/**\n * Format stat output for different views\n */\nexport function formatStatOutput(stat: StatResult, view: ViewType): string {\n switch (view) {\n case \"json\":\n return formatJson(stat);\n case \"llm\":\n return formatLlm(stat);\n case \"human\":\n return formatHuman(stat);\n default:\n return formatDefault(stat);\n }\n}\n\n/**\n * Default format: KEY=VALUE pairs\n */\nfunction formatDefault(stat: StatResult): string {\n const lines: string[] = [];\n\n lines.push(`PATH=${stat.path}`);\n lines.push(`TYPE=${stat.type}`);\n\n if (stat.size !== undefined) {\n lines.push(`SIZE=${stat.size}`);\n }\n\n if (stat.modified) {\n lines.push(`MODIFIED=${stat.modified}`);\n }\n\n if (stat.created) {\n lines.push(`CREATED=${stat.created}`);\n }\n\n if (stat.hash) {\n lines.push(`HASH=${stat.hash}`);\n }\n\n if (stat.provider) {\n lines.push(`PROVIDER=${stat.provider}`);\n }\n\n return lines.join(\"\\n\");\n}\n\n/**\n * JSON format\n */\nfunction formatJson(stat: StatResult): string {\n return JSON.stringify(stat, null, 2);\n}\n\n/**\n * LLM format: Uppercase keys, semantic\n */\nfunction formatLlm(stat: StatResult): string {\n const lines: string[] = [];\n\n const typeLabel = stat.type.toUpperCase();\n lines.push(`${typeLabel} ${stat.path}`);\n lines.push(`TYPE ${typeLabel}`);\n\n if (stat.size !== undefined) {\n lines.push(`SIZE ${stat.size}`);\n }\n\n if (stat.hash) {\n lines.push(`HASH ${stat.hash}`);\n }\n\n if (stat.modified) {\n lines.push(`UPDATED ${stat.modified}`);\n }\n\n lines.push(`SIDE_EFFECT NONE`);\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Human format: Formatted table\n */\nfunction formatHuman(stat: StatResult): string {\n const lines: string[] = [];\n\n lines.push(`Path: ${stat.path}`);\n lines.push(`Type: ${stat.type}`);\n\n if (stat.size !== undefined) {\n lines.push(`Size: ${formatSize(stat.size)}`);\n }\n\n if (stat.modified) {\n lines.push(`Modified: ${formatDate(stat.modified)}`);\n }\n\n if (stat.provider) {\n lines.push(`Provider: ${stat.provider}`);\n }\n\n return lines.join(\"\\n\");\n}\n\nfunction formatSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} bytes`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n\nfunction formatDate(isoString: string): string {\n try {\n const date = new Date(isoString);\n return date.toLocaleString();\n } catch {\n return isoString;\n }\n}\n"],"mappings":"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"}
|