@aigne/afs-cli 1.11.0-beta.11 → 1.11.0-beta.13
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/dist/cli.cjs +3 -2
- package/dist/cli.mjs +3 -2
- package/dist/cli.mjs.map +1 -1
- package/dist/config/afs-loader.cjs +36 -315
- package/dist/config/afs-loader.d.cts.map +1 -1
- package/dist/config/afs-loader.d.mts +2 -1
- package/dist/config/afs-loader.d.mts.map +1 -1
- package/dist/config/afs-loader.mjs +28 -307
- package/dist/config/afs-loader.mjs.map +1 -1
- package/dist/config/credential-helpers.cjs +303 -0
- package/dist/config/credential-helpers.d.mts +2 -0
- package/dist/config/credential-helpers.mjs +300 -0
- package/dist/config/credential-helpers.mjs.map +1 -0
- package/dist/config/loader.cjs +3 -1
- package/dist/config/loader.mjs +3 -2
- package/dist/config/loader.mjs.map +1 -1
- package/dist/config/program-install.cjs +450 -0
- package/dist/config/program-install.d.mts +1 -0
- package/dist/config/program-install.mjs +444 -0
- package/dist/config/program-install.mjs.map +1 -0
- package/dist/core/commands/connect.cjs +53 -0
- package/dist/core/commands/connect.d.mts +2 -0
- package/dist/core/commands/connect.mjs +55 -0
- package/dist/core/commands/connect.mjs.map +1 -0
- package/dist/core/commands/daemon.cjs +211 -0
- package/dist/core/commands/daemon.d.mts +2 -0
- package/dist/core/commands/daemon.mjs +212 -0
- package/dist/core/commands/daemon.mjs.map +1 -0
- package/dist/core/commands/explain.cjs +3 -1
- package/dist/core/commands/explain.mjs +3 -1
- package/dist/core/commands/explain.mjs.map +1 -1
- package/dist/core/commands/explore.cjs +47 -12
- package/dist/core/commands/explore.mjs +47 -12
- package/dist/core/commands/explore.mjs.map +1 -1
- package/dist/core/commands/gen-agent-md.cjs +126 -0
- package/dist/core/commands/gen-agent-md.d.mts +2 -0
- package/dist/core/commands/gen-agent-md.mjs +125 -0
- package/dist/core/commands/gen-agent-md.mjs.map +1 -0
- package/dist/core/commands/index.cjs +13 -1
- package/dist/core/commands/index.d.cts.map +1 -1
- package/dist/core/commands/index.d.mts +6 -0
- package/dist/core/commands/index.d.mts.map +1 -1
- package/dist/core/commands/index.mjs +13 -1
- package/dist/core/commands/index.mjs.map +1 -1
- package/dist/core/commands/install.cjs +139 -0
- package/dist/core/commands/install.d.mts +2 -0
- package/dist/core/commands/install.mjs +140 -0
- package/dist/core/commands/install.mjs.map +1 -0
- package/dist/core/commands/ls.cjs +14 -2
- package/dist/core/commands/ls.d.cts +2 -0
- package/dist/core/commands/ls.d.cts.map +1 -1
- package/dist/core/commands/ls.d.mts +2 -0
- package/dist/core/commands/ls.d.mts.map +1 -1
- package/dist/core/commands/ls.mjs +14 -2
- package/dist/core/commands/ls.mjs.map +1 -1
- package/dist/core/commands/mcp-bridge.cjs +201 -0
- package/dist/core/commands/mcp-bridge.d.mts +2 -0
- package/dist/core/commands/mcp-bridge.mjs +201 -0
- package/dist/core/commands/mcp-bridge.mjs.map +1 -0
- package/dist/core/commands/read.cjs +20 -7
- package/dist/core/commands/read.d.cts +2 -0
- package/dist/core/commands/read.d.cts.map +1 -1
- package/dist/core/commands/read.d.mts +2 -0
- package/dist/core/commands/read.d.mts.map +1 -1
- package/dist/core/commands/read.mjs +20 -7
- package/dist/core/commands/read.mjs.map +1 -1
- package/dist/core/commands/search.cjs +5 -1
- package/dist/core/commands/search.mjs +5 -1
- package/dist/core/commands/search.mjs.map +1 -1
- package/dist/core/commands/stat.mjs.map +1 -1
- package/dist/core/commands/types.d.cts +2 -0
- package/dist/core/commands/types.d.cts.map +1 -1
- package/dist/core/commands/types.d.mts +2 -0
- package/dist/core/commands/types.d.mts.map +1 -1
- package/dist/core/commands/types.mjs.map +1 -1
- package/dist/core/commands/vault.cjs +289 -0
- package/dist/core/commands/vault.d.mts +2 -0
- package/dist/core/commands/vault.mjs +289 -0
- package/dist/core/commands/vault.mjs.map +1 -0
- package/dist/core/commands/write.cjs +19 -6
- package/dist/core/commands/write.d.cts +2 -1
- package/dist/core/commands/write.d.cts.map +1 -1
- package/dist/core/commands/write.d.mts +2 -1
- package/dist/core/commands/write.d.mts.map +1 -1
- package/dist/core/commands/write.mjs +19 -6
- package/dist/core/commands/write.mjs.map +1 -1
- package/dist/core/executor/index.cjs +95 -19
- package/dist/core/executor/index.d.cts +4 -0
- package/dist/core/executor/index.d.cts.map +1 -1
- package/dist/core/executor/index.d.mts +4 -0
- package/dist/core/executor/index.d.mts.map +1 -1
- package/dist/core/executor/index.mjs +95 -19
- package/dist/core/executor/index.mjs.map +1 -1
- package/dist/core/formatters/index.d.mts +1 -0
- package/dist/core/formatters/install.cjs +40 -0
- package/dist/core/formatters/install.d.mts +1 -0
- package/dist/core/formatters/install.mjs +36 -0
- package/dist/core/formatters/install.mjs.map +1 -0
- package/dist/core/formatters/vault.cjs +36 -0
- package/dist/core/formatters/vault.mjs +32 -0
- package/dist/core/formatters/vault.mjs.map +1 -0
- package/dist/credential/auth-server.cjs +22 -4
- package/dist/credential/auth-server.mjs +22 -4
- package/dist/credential/auth-server.mjs.map +1 -1
- package/dist/credential/index.d.mts +2 -1
- package/dist/credential/mcp-auth-context.cjs +21 -5
- package/dist/credential/mcp-auth-context.mjs +21 -5
- package/dist/credential/mcp-auth-context.mjs.map +1 -1
- package/dist/credential/resolver.cjs +11 -3
- package/dist/credential/resolver.mjs +11 -3
- package/dist/credential/resolver.mjs.map +1 -1
- package/dist/credential/vault-store.d.mts +1 -0
- package/dist/daemon/config-manager.cjs +279 -0
- package/dist/daemon/config-manager.mjs +279 -0
- package/dist/daemon/config-manager.mjs.map +1 -0
- package/dist/daemon/manager.cjs +164 -0
- package/dist/daemon/manager.mjs +157 -0
- package/dist/daemon/manager.mjs.map +1 -0
- package/dist/daemon/server.cjs +220 -0
- package/dist/daemon/server.mjs +220 -0
- package/dist/daemon/server.mjs.map +1 -0
- package/dist/mcp/http-transport.cjs +14 -1
- package/dist/mcp/http-transport.mjs +14 -1
- package/dist/mcp/http-transport.mjs.map +1 -1
- package/dist/mcp/server.cjs +4 -2
- package/dist/mcp/server.mjs +4 -2
- package/dist/mcp/server.mjs.map +1 -1
- package/dist/mcp/tools.cjs +62 -12
- package/dist/mcp/tools.mjs +62 -12
- package/dist/mcp/tools.mjs.map +1 -1
- package/dist/program/daemon-integration.cjs +46 -0
- package/dist/program/daemon-integration.mjs +45 -0
- package/dist/program/daemon-integration.mjs.map +1 -0
- package/dist/program/program-manager.cjs +166 -0
- package/dist/program/program-manager.mjs +166 -0
- package/dist/program/program-manager.mjs.map +1 -0
- package/dist/program/trigger-scanner.cjs +148 -0
- package/dist/program/trigger-scanner.mjs +148 -0
- package/dist/program/trigger-scanner.mjs.map +1 -0
- package/dist/providers/vault/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.cjs +11 -0
- package/dist/providers/vault/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.mjs +11 -0
- package/dist/providers/vault/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.mjs.map +1 -0
- package/dist/providers/vault/dist/encrypted-file.cjs +158 -0
- package/dist/providers/vault/dist/encrypted-file.mjs +153 -0
- package/dist/providers/vault/dist/encrypted-file.mjs.map +1 -0
- package/dist/providers/vault/dist/index.cjs +405 -0
- package/dist/providers/vault/dist/index.mjs +400 -0
- package/dist/providers/vault/dist/index.mjs.map +1 -0
- package/dist/providers/vault/dist/key-resolver.cjs +181 -0
- package/dist/providers/vault/dist/key-resolver.mjs +180 -0
- package/dist/providers/vault/dist/key-resolver.mjs.map +1 -0
- package/dist/repl.cjs +109 -14
- package/dist/repl.d.cts.map +1 -1
- package/dist/repl.d.mts.map +1 -1
- package/dist/repl.mjs +109 -14
- package/dist/repl.mjs.map +1 -1
- package/package.json +27 -20
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
const require_index = require('../../ui/index.cjs');
|
|
2
|
+
|
|
3
|
+
//#region src/core/commands/daemon.ts
|
|
4
|
+
/** No-op formatter for lifecycle commands that manage their own output. */
|
|
5
|
+
const noopFormat = () => "";
|
|
6
|
+
/** Print endpoint table for a running service. */
|
|
7
|
+
function printEndpoints(url) {
|
|
8
|
+
console.log("");
|
|
9
|
+
console.log(` ${require_index.colors.dim("Endpoints:")}`);
|
|
10
|
+
console.log(` ${require_index.colors.brightCyan(`${url}/`)}${require_index.colors.dim(" Explorer UI")}`);
|
|
11
|
+
console.log(` ${require_index.colors.brightCyan(`${url}/ws`)}${require_index.colors.dim(" WebSocket JSON-RPC")}`);
|
|
12
|
+
console.log(` ${require_index.colors.brightCyan(`${url}/afs/*`)}${require_index.colors.dim(" REST API")}`);
|
|
13
|
+
console.log(` ${require_index.colors.brightCyan(`${url}/mcp`)}${require_index.colors.dim(" MCP Streamable HTTP")}`);
|
|
14
|
+
}
|
|
15
|
+
function createServiceCommand(options) {
|
|
16
|
+
return {
|
|
17
|
+
command: "service <action>",
|
|
18
|
+
describe: "Manage AFS background service",
|
|
19
|
+
builder: (yargs) => yargs.positional("action", {
|
|
20
|
+
type: "string",
|
|
21
|
+
choices: [
|
|
22
|
+
"start",
|
|
23
|
+
"stop",
|
|
24
|
+
"status",
|
|
25
|
+
"restart"
|
|
26
|
+
],
|
|
27
|
+
description: "Service action"
|
|
28
|
+
}).option("port", {
|
|
29
|
+
type: "number",
|
|
30
|
+
default: 4900,
|
|
31
|
+
description: "Port for service"
|
|
32
|
+
}).option("cwd", {
|
|
33
|
+
type: "string",
|
|
34
|
+
hidden: true,
|
|
35
|
+
description: "Working directory (used by _run)"
|
|
36
|
+
}),
|
|
37
|
+
handler: async (argv) => {
|
|
38
|
+
const action = argv.action;
|
|
39
|
+
const { getDaemonStatus, stopDaemon, spawnDaemon, getLogFile } = await Promise.resolve().then(() => require("../../daemon/manager.cjs"));
|
|
40
|
+
switch (action) {
|
|
41
|
+
case "start": {
|
|
42
|
+
const existing = await getDaemonStatus();
|
|
43
|
+
if (existing) {
|
|
44
|
+
console.log(`${require_index.colors.yellow("Service already running")} (PID ${existing.pid}, port ${existing.port})`);
|
|
45
|
+
printEndpoints(existing.url);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
console.log(require_index.colors.dim("Starting AFS service..."));
|
|
49
|
+
try {
|
|
50
|
+
const info = await spawnDaemon(argv.port);
|
|
51
|
+
console.log(require_index.colors.green("AFS Service started"));
|
|
52
|
+
console.log(` ${require_index.colors.dim("PID:")} ${info.pid}`);
|
|
53
|
+
console.log(` ${require_index.colors.dim("Port:")} ${info.port}`);
|
|
54
|
+
console.log(` ${require_index.colors.dim("Log:")} ${getLogFile()}`);
|
|
55
|
+
printEndpoints(info.url);
|
|
56
|
+
} catch (err) {
|
|
57
|
+
console.error(require_index.colors.red(`Failed to start service: ${err.message}`));
|
|
58
|
+
process.exitCode = 1;
|
|
59
|
+
}
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
case "_run": {
|
|
63
|
+
const logTs = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
64
|
+
process.on("uncaughtException", (err) => {
|
|
65
|
+
console.error(`[${logTs()}] FATAL uncaughtException: ${err.stack || err.message}`);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
});
|
|
68
|
+
process.on("unhandledRejection", (reason) => {
|
|
69
|
+
const msg = reason instanceof Error ? reason.stack || reason.message : String(reason);
|
|
70
|
+
console.error(`[${logTs()}] FATAL unhandledRejection: ${msg}`);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
});
|
|
73
|
+
const { homedir } = await import("node:os");
|
|
74
|
+
const cwd = argv.cwd ?? homedir();
|
|
75
|
+
const { createAFS } = await Promise.resolve().then(() => require("../../config/afs-loader.cjs"));
|
|
76
|
+
const { startDaemonServer } = await Promise.resolve().then(() => require("../../daemon/server.cjs"));
|
|
77
|
+
const { DaemonConfigManager } = await Promise.resolve().then(() => require("../../daemon/config-manager.cjs"));
|
|
78
|
+
const { writePidFile, writePortFile, ensureDaemonDir, cleanPidFiles } = await Promise.resolve().then(() => require("../../daemon/manager.cjs"));
|
|
79
|
+
const { createCredentialStore } = await Promise.resolve().then(() => require("../../credential/store.cjs"));
|
|
80
|
+
const { afs, failures, configMountPaths, registry } = await createAFS(cwd, { credentialStore: createCredentialStore() });
|
|
81
|
+
if (failures.length > 0) {
|
|
82
|
+
console.warn(`[${logTs()}] ${failures.length} provider(s) failed to mount:`);
|
|
83
|
+
for (const f of failures) console.warn(` ${f.path}: ${f.reason}`);
|
|
84
|
+
}
|
|
85
|
+
const configManager = new DaemonConfigManager({
|
|
86
|
+
cwd,
|
|
87
|
+
afs,
|
|
88
|
+
registry,
|
|
89
|
+
configMountPaths,
|
|
90
|
+
failures: failures.map((f) => ({
|
|
91
|
+
path: f.path,
|
|
92
|
+
uri: "",
|
|
93
|
+
reason: f.reason
|
|
94
|
+
})),
|
|
95
|
+
onConfigChanged: (added, removed) => {
|
|
96
|
+
if (serverInfo?.server) serverInfo.server.broadcast("configReloaded", {
|
|
97
|
+
added,
|
|
98
|
+
removed
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
configManager.startWatching();
|
|
103
|
+
const { ProgramManager } = await Promise.resolve().then(() => require("../../program/program-manager.cjs"));
|
|
104
|
+
const { scanProgramTriggers } = await Promise.resolve().then(() => require("../../program/trigger-scanner.cjs"));
|
|
105
|
+
const { listInstalledPrograms, getUserConfigDir } = await Promise.resolve().then(() => require("../../config/program-install.cjs"));
|
|
106
|
+
const userConfigDir = getUserConfigDir();
|
|
107
|
+
const programManager = new ProgramManager({
|
|
108
|
+
globalAFS: afs,
|
|
109
|
+
createProvider: afs.createProviderFromMount,
|
|
110
|
+
listPrograms: async () => {
|
|
111
|
+
return (await listInstalledPrograms({ userConfigDir })).map((p) => ({
|
|
112
|
+
id: p.id,
|
|
113
|
+
installPath: p.installPath,
|
|
114
|
+
mountPath: p.mountPath
|
|
115
|
+
}));
|
|
116
|
+
},
|
|
117
|
+
scanTriggers: async (programDir) => {
|
|
118
|
+
let compile = null;
|
|
119
|
+
try {
|
|
120
|
+
compile = (await import("@aigne/ash")).compileSource;
|
|
121
|
+
} catch {}
|
|
122
|
+
return scanProgramTriggers(programDir, compile);
|
|
123
|
+
},
|
|
124
|
+
dataDir: (programId) => `/.data/${programId}`,
|
|
125
|
+
readMountOverrides: async (programId) => {
|
|
126
|
+
const { readProgramMountOverrides } = await Promise.resolve().then(() => require("../../config/program-install.cjs"));
|
|
127
|
+
return readProgramMountOverrides(programId, { userConfigDir });
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
try {
|
|
131
|
+
await programManager.activateAll();
|
|
132
|
+
const activated = programManager.getActivatedPrograms();
|
|
133
|
+
if (activated.length > 0) console.log(`[${logTs()}] Activated ${activated.length} program(s): ${activated.join(", ")}`);
|
|
134
|
+
} catch (err) {
|
|
135
|
+
console.warn(`[${logTs()}] Program activation failed: ${err instanceof Error ? err.message : err}`);
|
|
136
|
+
}
|
|
137
|
+
let serverInfo;
|
|
138
|
+
serverInfo = await startDaemonServer({
|
|
139
|
+
afs,
|
|
140
|
+
port: argv.port,
|
|
141
|
+
configManager,
|
|
142
|
+
programManager
|
|
143
|
+
});
|
|
144
|
+
await ensureDaemonDir();
|
|
145
|
+
await writePidFile(process.pid);
|
|
146
|
+
await writePortFile(serverInfo.port);
|
|
147
|
+
const mounts = afs.getMounts();
|
|
148
|
+
console.log(`[${logTs()}] AFS Service started`);
|
|
149
|
+
console.log(`PID: ${process.pid}, Port: ${serverInfo.port}`);
|
|
150
|
+
console.log(`MCP: ${serverInfo.mcpUrl}`);
|
|
151
|
+
for (const m of mounts) console.log(` ${m.path} → ${m.module.name}`);
|
|
152
|
+
let shuttingDown = false;
|
|
153
|
+
const shutdown = async () => {
|
|
154
|
+
if (shuttingDown) return;
|
|
155
|
+
shuttingDown = true;
|
|
156
|
+
console.log(`[${logTs()}] Shutting down service...`);
|
|
157
|
+
try {
|
|
158
|
+
await programManager.deactivateAll();
|
|
159
|
+
} catch (err) {
|
|
160
|
+
console.warn(`[${logTs()}] Program deactivation error: ${err instanceof Error ? err.message : err}`);
|
|
161
|
+
}
|
|
162
|
+
configManager.stopWatching();
|
|
163
|
+
serverInfo?.stop();
|
|
164
|
+
await cleanPidFiles();
|
|
165
|
+
process.exit(0);
|
|
166
|
+
};
|
|
167
|
+
process.on("SIGINT", shutdown);
|
|
168
|
+
process.on("SIGTERM", shutdown);
|
|
169
|
+
await new Promise(() => {});
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
case "stop":
|
|
173
|
+
if (await stopDaemon()) console.log(require_index.colors.green("Service stopped"));
|
|
174
|
+
else console.log(require_index.colors.yellow("No running service found"));
|
|
175
|
+
break;
|
|
176
|
+
case "status": {
|
|
177
|
+
const info = await getDaemonStatus();
|
|
178
|
+
if (info) {
|
|
179
|
+
console.log(require_index.colors.green("Service is running"));
|
|
180
|
+
console.log(` PID: ${info.pid}`);
|
|
181
|
+
console.log(` Port: ${info.port}`);
|
|
182
|
+
printEndpoints(info.url);
|
|
183
|
+
} else console.log(require_index.colors.dim("Service is not running"));
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
case "restart":
|
|
187
|
+
if (await stopDaemon()) console.log(require_index.colors.dim("Stopped existing service, restarting..."));
|
|
188
|
+
console.log(require_index.colors.dim("Starting AFS service..."));
|
|
189
|
+
try {
|
|
190
|
+
const info = await spawnDaemon(argv.port);
|
|
191
|
+
console.log(require_index.colors.green("AFS Service restarted"));
|
|
192
|
+
console.log(` ${require_index.colors.dim("PID:")} ${info.pid}`);
|
|
193
|
+
console.log(` ${require_index.colors.dim("Port:")} ${info.port}`);
|
|
194
|
+
printEndpoints(info.url);
|
|
195
|
+
} catch (err) {
|
|
196
|
+
console.error(require_index.colors.red(`Failed to restart service: ${err.message}`));
|
|
197
|
+
process.exitCode = 1;
|
|
198
|
+
}
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
options.onResult({
|
|
202
|
+
command: "service",
|
|
203
|
+
result: null,
|
|
204
|
+
format: noopFormat
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
//#endregion
|
|
211
|
+
exports.createServiceCommand = createServiceCommand;
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { colors } from "../../ui/index.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/core/commands/daemon.ts
|
|
4
|
+
/** No-op formatter for lifecycle commands that manage their own output. */
|
|
5
|
+
const noopFormat = () => "";
|
|
6
|
+
/** Print endpoint table for a running service. */
|
|
7
|
+
function printEndpoints(url) {
|
|
8
|
+
console.log("");
|
|
9
|
+
console.log(` ${colors.dim("Endpoints:")}`);
|
|
10
|
+
console.log(` ${colors.brightCyan(`${url}/`)}${colors.dim(" Explorer UI")}`);
|
|
11
|
+
console.log(` ${colors.brightCyan(`${url}/ws`)}${colors.dim(" WebSocket JSON-RPC")}`);
|
|
12
|
+
console.log(` ${colors.brightCyan(`${url}/afs/*`)}${colors.dim(" REST API")}`);
|
|
13
|
+
console.log(` ${colors.brightCyan(`${url}/mcp`)}${colors.dim(" MCP Streamable HTTP")}`);
|
|
14
|
+
}
|
|
15
|
+
function createServiceCommand(options) {
|
|
16
|
+
return {
|
|
17
|
+
command: "service <action>",
|
|
18
|
+
describe: "Manage AFS background service",
|
|
19
|
+
builder: (yargs) => yargs.positional("action", {
|
|
20
|
+
type: "string",
|
|
21
|
+
choices: [
|
|
22
|
+
"start",
|
|
23
|
+
"stop",
|
|
24
|
+
"status",
|
|
25
|
+
"restart"
|
|
26
|
+
],
|
|
27
|
+
description: "Service action"
|
|
28
|
+
}).option("port", {
|
|
29
|
+
type: "number",
|
|
30
|
+
default: 4900,
|
|
31
|
+
description: "Port for service"
|
|
32
|
+
}).option("cwd", {
|
|
33
|
+
type: "string",
|
|
34
|
+
hidden: true,
|
|
35
|
+
description: "Working directory (used by _run)"
|
|
36
|
+
}),
|
|
37
|
+
handler: async (argv) => {
|
|
38
|
+
const action = argv.action;
|
|
39
|
+
const { getDaemonStatus, stopDaemon, spawnDaemon, getLogFile } = await import("../../daemon/manager.mjs");
|
|
40
|
+
switch (action) {
|
|
41
|
+
case "start": {
|
|
42
|
+
const existing = await getDaemonStatus();
|
|
43
|
+
if (existing) {
|
|
44
|
+
console.log(`${colors.yellow("Service already running")} (PID ${existing.pid}, port ${existing.port})`);
|
|
45
|
+
printEndpoints(existing.url);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
console.log(colors.dim("Starting AFS service..."));
|
|
49
|
+
try {
|
|
50
|
+
const info = await spawnDaemon(argv.port);
|
|
51
|
+
console.log(colors.green("AFS Service started"));
|
|
52
|
+
console.log(` ${colors.dim("PID:")} ${info.pid}`);
|
|
53
|
+
console.log(` ${colors.dim("Port:")} ${info.port}`);
|
|
54
|
+
console.log(` ${colors.dim("Log:")} ${getLogFile()}`);
|
|
55
|
+
printEndpoints(info.url);
|
|
56
|
+
} catch (err) {
|
|
57
|
+
console.error(colors.red(`Failed to start service: ${err.message}`));
|
|
58
|
+
process.exitCode = 1;
|
|
59
|
+
}
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
case "_run": {
|
|
63
|
+
const logTs = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
64
|
+
process.on("uncaughtException", (err) => {
|
|
65
|
+
console.error(`[${logTs()}] FATAL uncaughtException: ${err.stack || err.message}`);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
});
|
|
68
|
+
process.on("unhandledRejection", (reason) => {
|
|
69
|
+
const msg = reason instanceof Error ? reason.stack || reason.message : String(reason);
|
|
70
|
+
console.error(`[${logTs()}] FATAL unhandledRejection: ${msg}`);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
});
|
|
73
|
+
const { homedir } = await import("node:os");
|
|
74
|
+
const cwd = argv.cwd ?? homedir();
|
|
75
|
+
const { createAFS } = await import("../../config/afs-loader.mjs");
|
|
76
|
+
const { startDaemonServer } = await import("../../daemon/server.mjs");
|
|
77
|
+
const { DaemonConfigManager } = await import("../../daemon/config-manager.mjs");
|
|
78
|
+
const { writePidFile, writePortFile, ensureDaemonDir, cleanPidFiles } = await import("../../daemon/manager.mjs");
|
|
79
|
+
const { createCredentialStore } = await import("../../credential/store.mjs");
|
|
80
|
+
const { afs, failures, configMountPaths, registry } = await createAFS(cwd, { credentialStore: createCredentialStore() });
|
|
81
|
+
if (failures.length > 0) {
|
|
82
|
+
console.warn(`[${logTs()}] ${failures.length} provider(s) failed to mount:`);
|
|
83
|
+
for (const f of failures) console.warn(` ${f.path}: ${f.reason}`);
|
|
84
|
+
}
|
|
85
|
+
const configManager = new DaemonConfigManager({
|
|
86
|
+
cwd,
|
|
87
|
+
afs,
|
|
88
|
+
registry,
|
|
89
|
+
configMountPaths,
|
|
90
|
+
failures: failures.map((f) => ({
|
|
91
|
+
path: f.path,
|
|
92
|
+
uri: "",
|
|
93
|
+
reason: f.reason
|
|
94
|
+
})),
|
|
95
|
+
onConfigChanged: (added, removed) => {
|
|
96
|
+
if (serverInfo?.server) serverInfo.server.broadcast("configReloaded", {
|
|
97
|
+
added,
|
|
98
|
+
removed
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
configManager.startWatching();
|
|
103
|
+
const { ProgramManager } = await import("../../program/program-manager.mjs");
|
|
104
|
+
const { scanProgramTriggers } = await import("../../program/trigger-scanner.mjs");
|
|
105
|
+
const { listInstalledPrograms, getUserConfigDir } = await import("../../config/program-install.mjs");
|
|
106
|
+
const userConfigDir = getUserConfigDir();
|
|
107
|
+
const programManager = new ProgramManager({
|
|
108
|
+
globalAFS: afs,
|
|
109
|
+
createProvider: afs.createProviderFromMount,
|
|
110
|
+
listPrograms: async () => {
|
|
111
|
+
return (await listInstalledPrograms({ userConfigDir })).map((p) => ({
|
|
112
|
+
id: p.id,
|
|
113
|
+
installPath: p.installPath,
|
|
114
|
+
mountPath: p.mountPath
|
|
115
|
+
}));
|
|
116
|
+
},
|
|
117
|
+
scanTriggers: async (programDir) => {
|
|
118
|
+
let compile = null;
|
|
119
|
+
try {
|
|
120
|
+
compile = (await import("@aigne/ash")).compileSource;
|
|
121
|
+
} catch {}
|
|
122
|
+
return scanProgramTriggers(programDir, compile);
|
|
123
|
+
},
|
|
124
|
+
dataDir: (programId) => `/.data/${programId}`,
|
|
125
|
+
readMountOverrides: async (programId) => {
|
|
126
|
+
const { readProgramMountOverrides } = await import("../../config/program-install.mjs");
|
|
127
|
+
return readProgramMountOverrides(programId, { userConfigDir });
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
try {
|
|
131
|
+
await programManager.activateAll();
|
|
132
|
+
const activated = programManager.getActivatedPrograms();
|
|
133
|
+
if (activated.length > 0) console.log(`[${logTs()}] Activated ${activated.length} program(s): ${activated.join(", ")}`);
|
|
134
|
+
} catch (err) {
|
|
135
|
+
console.warn(`[${logTs()}] Program activation failed: ${err instanceof Error ? err.message : err}`);
|
|
136
|
+
}
|
|
137
|
+
let serverInfo;
|
|
138
|
+
serverInfo = await startDaemonServer({
|
|
139
|
+
afs,
|
|
140
|
+
port: argv.port,
|
|
141
|
+
configManager,
|
|
142
|
+
programManager
|
|
143
|
+
});
|
|
144
|
+
await ensureDaemonDir();
|
|
145
|
+
await writePidFile(process.pid);
|
|
146
|
+
await writePortFile(serverInfo.port);
|
|
147
|
+
const mounts = afs.getMounts();
|
|
148
|
+
console.log(`[${logTs()}] AFS Service started`);
|
|
149
|
+
console.log(`PID: ${process.pid}, Port: ${serverInfo.port}`);
|
|
150
|
+
console.log(`MCP: ${serverInfo.mcpUrl}`);
|
|
151
|
+
for (const m of mounts) console.log(` ${m.path} → ${m.module.name}`);
|
|
152
|
+
let shuttingDown = false;
|
|
153
|
+
const shutdown = async () => {
|
|
154
|
+
if (shuttingDown) return;
|
|
155
|
+
shuttingDown = true;
|
|
156
|
+
console.log(`[${logTs()}] Shutting down service...`);
|
|
157
|
+
try {
|
|
158
|
+
await programManager.deactivateAll();
|
|
159
|
+
} catch (err) {
|
|
160
|
+
console.warn(`[${logTs()}] Program deactivation error: ${err instanceof Error ? err.message : err}`);
|
|
161
|
+
}
|
|
162
|
+
configManager.stopWatching();
|
|
163
|
+
serverInfo?.stop();
|
|
164
|
+
await cleanPidFiles();
|
|
165
|
+
process.exit(0);
|
|
166
|
+
};
|
|
167
|
+
process.on("SIGINT", shutdown);
|
|
168
|
+
process.on("SIGTERM", shutdown);
|
|
169
|
+
await new Promise(() => {});
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
case "stop":
|
|
173
|
+
if (await stopDaemon()) console.log(colors.green("Service stopped"));
|
|
174
|
+
else console.log(colors.yellow("No running service found"));
|
|
175
|
+
break;
|
|
176
|
+
case "status": {
|
|
177
|
+
const info = await getDaemonStatus();
|
|
178
|
+
if (info) {
|
|
179
|
+
console.log(colors.green("Service is running"));
|
|
180
|
+
console.log(` PID: ${info.pid}`);
|
|
181
|
+
console.log(` Port: ${info.port}`);
|
|
182
|
+
printEndpoints(info.url);
|
|
183
|
+
} else console.log(colors.dim("Service is not running"));
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
case "restart":
|
|
187
|
+
if (await stopDaemon()) console.log(colors.dim("Stopped existing service, restarting..."));
|
|
188
|
+
console.log(colors.dim("Starting AFS service..."));
|
|
189
|
+
try {
|
|
190
|
+
const info = await spawnDaemon(argv.port);
|
|
191
|
+
console.log(colors.green("AFS Service restarted"));
|
|
192
|
+
console.log(` ${colors.dim("PID:")} ${info.pid}`);
|
|
193
|
+
console.log(` ${colors.dim("Port:")} ${info.port}`);
|
|
194
|
+
printEndpoints(info.url);
|
|
195
|
+
} catch (err) {
|
|
196
|
+
console.error(colors.red(`Failed to restart service: ${err.message}`));
|
|
197
|
+
process.exitCode = 1;
|
|
198
|
+
}
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
options.onResult({
|
|
202
|
+
command: "service",
|
|
203
|
+
result: null,
|
|
204
|
+
format: noopFormat
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
//#endregion
|
|
211
|
+
export { createServiceCommand };
|
|
212
|
+
//# sourceMappingURL=daemon.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon.mjs","names":[],"sources":["../../../src/core/commands/daemon.ts"],"sourcesContent":["/**\n * AFS Service Command\n *\n * Manages the AFS background service process.\n * Subcommands: start, stop, status, restart, _run (hidden)\n *\n * File is named daemon.ts for historical reasons; the user-facing command is \"service\".\n */\n\nimport type { CommandModule } from \"yargs\";\nimport { colors } from \"../../ui/index.js\";\nimport type { CommandFactoryOptions } from \"./types.js\";\n\nexport interface ServiceArgs {\n port: number;\n cwd?: string;\n}\n\n/** No-op formatter for lifecycle commands that manage their own output. */\nconst noopFormat = () => \"\";\n\n/** Print endpoint table for a running service. */\nfunction printEndpoints(url: string): void {\n console.log(\"\");\n console.log(` ${colors.dim(\"Endpoints:\")}`);\n console.log(` ${colors.brightCyan(`${url}/`)}${colors.dim(\" Explorer UI\")}`);\n console.log(` ${colors.brightCyan(`${url}/ws`)}${colors.dim(\" WebSocket JSON-RPC\")}`);\n console.log(` ${colors.brightCyan(`${url}/afs/*`)}${colors.dim(\" REST API\")}`);\n console.log(` ${colors.brightCyan(`${url}/mcp`)}${colors.dim(\" MCP Streamable HTTP\")}`);\n}\n\nexport function createServiceCommand(\n options: CommandFactoryOptions,\n): CommandModule<unknown, ServiceArgs> {\n return {\n command: \"service <action>\",\n describe: \"Manage AFS background service\",\n builder: (yargs) =>\n yargs\n .positional(\"action\", {\n type: \"string\",\n choices: [\"start\", \"stop\", \"status\", \"restart\"],\n description: \"Service action\",\n })\n .option(\"port\", {\n type: \"number\",\n default: 4900,\n description: \"Port for service\",\n })\n .option(\"cwd\", {\n type: \"string\",\n hidden: true,\n description: \"Working directory (used by _run)\",\n }) as any,\n handler: async (argv) => {\n const action = (argv as any).action as string;\n const { getDaemonStatus, stopDaemon, spawnDaemon, getLogFile } = await import(\n \"../../daemon/manager.js\"\n );\n\n switch (action) {\n case \"start\": {\n const existing = await getDaemonStatus();\n if (existing) {\n console.log(\n `${colors.yellow(\"Service already running\")} (PID ${existing.pid}, port ${existing.port})`,\n );\n printEndpoints(existing.url);\n return;\n }\n\n console.log(colors.dim(\"Starting AFS service...\"));\n\n try {\n const info = await spawnDaemon(argv.port);\n console.log(colors.green(\"AFS Service started\"));\n console.log(` ${colors.dim(\"PID:\")} ${info.pid}`);\n console.log(` ${colors.dim(\"Port:\")} ${info.port}`);\n console.log(` ${colors.dim(\"Log:\")} ${getLogFile()}`);\n printEndpoints(info.url);\n } catch (err) {\n console.error(colors.red(`Failed to start service: ${(err as Error).message}`));\n process.exitCode = 1;\n }\n break;\n }\n\n case \"_run\": {\n // Hidden subcommand — the actual service process (runs in detached child)\n\n // Install crash handlers immediately — before any async work.\n // Without these, uncaught errors kill the process with no trace in the log.\n const logTs = () => new Date().toISOString();\n process.on(\"uncaughtException\", (err) => {\n console.error(`[${logTs()}] FATAL uncaughtException: ${err.stack || err.message}`);\n process.exit(1);\n });\n process.on(\"unhandledRejection\", (reason) => {\n const msg = reason instanceof Error ? reason.stack || reason.message : String(reason);\n console.error(`[${logTs()}] FATAL unhandledRejection: ${msg}`);\n process.exit(1);\n });\n\n // Always use home directory for config discovery — daemon serves a\n // global AFS instance, independent of which directory it was started from.\n const { homedir } = await import(\"node:os\");\n const cwd = argv.cwd ?? homedir();\n const { createAFS } = await import(\"../../config/afs-loader.js\");\n const { startDaemonServer } = await import(\"../../daemon/server.js\");\n const { DaemonConfigManager } = await import(\"../../daemon/config-manager.js\");\n const { writePidFile, writePortFile, ensureDaemonDir, cleanPidFiles } = await import(\n \"../../daemon/manager.js\"\n );\n const { createCredentialStore } = await import(\"../../credential/store.js\");\n\n const { afs, failures, configMountPaths, registry } = await createAFS(cwd, {\n credentialStore: createCredentialStore(),\n // No authContext — daemon is non-interactive, relies on stored credentials\n });\n\n if (failures.length > 0) {\n console.warn(`[${logTs()}] ${failures.length} provider(s) failed to mount:`);\n for (const f of failures) {\n console.warn(` ${f.path}: ${f.reason}`);\n }\n }\n\n const configManager = new DaemonConfigManager({\n cwd,\n afs,\n registry,\n configMountPaths,\n failures: failures.map((f) => ({ path: f.path, uri: \"\", reason: f.reason })),\n onConfigChanged: (added, removed) => {\n if (serverInfo?.server) {\n serverInfo.server.broadcast(\"configReloaded\", { added, removed });\n }\n },\n });\n configManager.startWatching();\n\n // Initialize ProgramManager for program activation\n const { ProgramManager } = await import(\"../../program/program-manager.js\");\n const { scanProgramTriggers } = await import(\"../../program/trigger-scanner.js\");\n const { listInstalledPrograms, getUserConfigDir } = await import(\n \"../../config/program-install.js\"\n );\n\n const userConfigDir = getUserConfigDir();\n const programManager = new ProgramManager({\n globalAFS: afs,\n createProvider: afs.createProviderFromMount,\n listPrograms: async () => {\n const programs = await listInstalledPrograms({ userConfigDir });\n return programs.map((p) => ({\n id: p.id,\n installPath: p.installPath,\n mountPath: p.mountPath,\n }));\n },\n scanTriggers: async (programDir: string) => {\n let compile = null;\n try {\n // Dynamic import — @aigne/ash is an optional peer dependency\n const ashModule = \"@aigne/ash\";\n const mod = await import(/* webpackIgnore: true */ ashModule);\n compile = mod.compileSource;\n } catch {\n // @aigne/ash not available — use regex fallback\n }\n return scanProgramTriggers(programDir, compile);\n },\n dataDir: (programId: string) => `/.data/${programId}`,\n readMountOverrides: async (programId) => {\n const { readProgramMountOverrides } = await import(\"../../config/program-install.js\");\n return readProgramMountOverrides(programId, { userConfigDir });\n },\n });\n\n // Activate all programs (best-effort — failures don't block daemon startup)\n try {\n await programManager.activateAll();\n const activated = programManager.getActivatedPrograms();\n if (activated.length > 0) {\n console.log(\n `[${logTs()}] Activated ${activated.length} program(s): ${activated.join(\", \")}`,\n );\n }\n } catch (err) {\n console.warn(\n `[${logTs()}] Program activation failed: ${err instanceof Error ? err.message : err}`,\n );\n }\n\n let serverInfo: Awaited<ReturnType<typeof startDaemonServer>> | undefined;\n serverInfo = await startDaemonServer({\n afs,\n port: argv.port,\n configManager,\n programManager,\n });\n\n // Write PID and port files so parent (and status/stop) can find us\n await ensureDaemonDir();\n await writePidFile(process.pid);\n await writePortFile(serverInfo.port);\n\n const mounts = afs.getMounts();\n console.log(`[${logTs()}] AFS Service started`);\n console.log(`PID: ${process.pid}, Port: ${serverInfo.port}`);\n console.log(`MCP: ${serverInfo.mcpUrl}`);\n for (const m of mounts) {\n console.log(` ${m.path} → ${m.module.name}`);\n }\n\n // Graceful shutdown\n let shuttingDown = false;\n const shutdown = async () => {\n if (shuttingDown) return;\n shuttingDown = true;\n console.log(`[${logTs()}] Shutting down service...`);\n try {\n await programManager.deactivateAll();\n } catch (err) {\n console.warn(\n `[${logTs()}] Program deactivation error: ${err instanceof Error ? err.message : err}`,\n );\n }\n configManager.stopWatching();\n serverInfo?.stop();\n await cleanPidFiles();\n process.exit(0);\n };\n\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n\n // Keep running\n await new Promise(() => {});\n break;\n }\n\n case \"stop\": {\n const stopped = await stopDaemon();\n if (stopped) {\n console.log(colors.green(\"Service stopped\"));\n } else {\n console.log(colors.yellow(\"No running service found\"));\n }\n break;\n }\n\n case \"status\": {\n const info = await getDaemonStatus();\n if (info) {\n console.log(colors.green(\"Service is running\"));\n console.log(` PID: ${info.pid}`);\n console.log(` Port: ${info.port}`);\n printEndpoints(info.url);\n } else {\n console.log(colors.dim(\"Service is not running\"));\n }\n break;\n }\n\n case \"restart\": {\n const wasRunning = await stopDaemon();\n if (wasRunning) {\n console.log(colors.dim(\"Stopped existing service, restarting...\"));\n }\n\n console.log(colors.dim(\"Starting AFS service...\"));\n try {\n const info = await spawnDaemon(argv.port);\n console.log(colors.green(\"AFS Service restarted\"));\n console.log(` ${colors.dim(\"PID:\")} ${info.pid}`);\n console.log(` ${colors.dim(\"Port:\")} ${info.port}`);\n printEndpoints(info.url);\n } catch (err) {\n console.error(colors.red(`Failed to restart service: ${(err as Error).message}`));\n process.exitCode = 1;\n }\n break;\n }\n }\n\n // Signal executor that command ran (output already printed above)\n options.onResult({ command: \"service\", result: null, format: noopFormat });\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -165,7 +165,9 @@ async function explainMounts(cwd, afs) {
|
|
|
165
165
|
explanation: `Runtime mounts (mounted via API):\n\n${listResult.data.map((entry) => ` ${entry.path} (runtime mount)`).join("\n")}`,
|
|
166
166
|
examples: listResult.data.slice(0, 3).map((entry) => `afs ls ${entry.path}`)
|
|
167
167
|
};
|
|
168
|
-
} catch {
|
|
168
|
+
} catch (err) {
|
|
169
|
+
console.debug("[explain] mount enumeration failed:", err);
|
|
170
|
+
}
|
|
169
171
|
return {
|
|
170
172
|
topic: "Mounts",
|
|
171
173
|
explanation: `No mounts configured.
|
|
@@ -165,7 +165,9 @@ async function explainMounts(cwd, afs) {
|
|
|
165
165
|
explanation: `Runtime mounts (mounted via API):\n\n${listResult.data.map((entry) => ` ${entry.path} (runtime mount)`).join("\n")}`,
|
|
166
166
|
examples: listResult.data.slice(0, 3).map((entry) => `afs ls ${entry.path}`)
|
|
167
167
|
};
|
|
168
|
-
} catch {
|
|
168
|
+
} catch (err) {
|
|
169
|
+
console.debug("[explain] mount enumeration failed:", err);
|
|
170
|
+
}
|
|
169
171
|
return {
|
|
170
172
|
topic: "Mounts",
|
|
171
173
|
explanation: `No mounts configured.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"explain.mjs","names":[],"sources":["../../../src/core/commands/explain.ts"],"sourcesContent":["/**\n * explain Command - Core Implementation\n *\n * Explains AFS paths or concepts.\n * Matches old version output format with topic/explanation/examples structure.\n */\n\nimport type { AFS } from \"@aigne/afs\";\nimport type { CommandModule } from \"yargs\";\nimport { configMountListCommand } from \"../../config/mount-commands.js\";\nimport { formatExplainOutput, formatPathExplainOutput } from \"../formatters/index.js\";\nimport { cliPathToCanonical } from \"../path-utils.js\";\nimport { type CommandFactoryOptions, resolveAFS } from \"./types.js\";\n\n/**\n * Result for concept explanations (mount, paths, uri, overview)\n */\nexport interface ExplainResult {\n topic: string;\n explanation: string;\n examples?: string[];\n}\n\n/**\n * Result for path explanations\n */\nexport interface PathExplainResult {\n path: string;\n type: string;\n description?: string;\n inputs?: string[];\n outputs?: string[];\n errors?: string[];\n sideEffects?: string[];\n meta?: Record<string, string>;\n /** When set, the formatter renders this markdown directly instead of structured fields */\n markdown?: string;\n}\n\n/**\n * Explain command arguments\n */\nexport interface ExplainArgs {\n topic?: string;\n}\n\n/**\n * Create explain command factory\n */\nexport function createExplainCommand(\n options: CommandFactoryOptions,\n): CommandModule<unknown, ExplainArgs> {\n return {\n command: \"explain [topic]\",\n describe: \"Explain AFS concepts or paths\",\n builder: {\n topic: {\n type: \"string\",\n description: \"Topic (mount, path, uri) or AFS path (e.g., /src)\",\n },\n },\n handler: async (argv) => {\n const target = argv.topic;\n\n // No target - explain overview\n if (!target) {\n options.onResult({\n command: \"explain\",\n result: explainOverview(),\n format: formatExplainOutput,\n });\n return;\n }\n\n // Priority: try as path first, then as concept\n const isPath = target.startsWith(\"/\") || target.startsWith(\"@\") || target.startsWith(\"$\");\n\n if (isPath) {\n const afs = await resolveAFS(options);\n const canonicalPath = cliPathToCanonical(target);\n const result = await getPathExplanation(afs, target, canonicalPath);\n options.onResult({ command: \"explain\", result, format: formatPathExplainOutput });\n } else {\n // Not obviously a path — try as path first, fall back to concept\n let afs: AFS | undefined;\n try {\n afs = await resolveAFS(options);\n } catch {}\n\n if (afs) {\n try {\n const canonicalPath = cliPathToCanonical(`/${target}`);\n const explainResult = await afs.explain(canonicalPath);\n if (explainResult.format === \"markdown\" && explainResult.content) {\n options.onResult({\n command: \"explain\",\n result: { path: `/${target}`, type: \"explained\", markdown: explainResult.content },\n format: formatPathExplainOutput,\n });\n return;\n }\n } catch {\n // Not a valid path, fall through to concept\n }\n }\n\n const cwd = options.cwd ?? process.cwd();\n const result = await getConceptExplanation(target.toLowerCase(), cwd, afs);\n options.onResult({\n command: \"explain\",\n result,\n format: formatExplainOutput,\n });\n }\n },\n };\n}\n\n/**\n * Get explanation for a path\n */\nasync function getPathExplanation(\n afs: AFS,\n path: string,\n canonicalPath: string,\n): Promise<PathExplainResult> {\n try {\n // Always try afs.explain() first — transparent passthrough to provider explain\n try {\n const explainResult = await afs.explain(canonicalPath);\n if (explainResult.format === \"markdown\" && explainResult.content) {\n return {\n path,\n type: \"explained\",\n markdown: explainResult.content,\n };\n }\n } catch {\n // explain not available, fall through to stat-based logic\n }\n\n // Fallback: stat-based explanation\n let entry:\n | { path: string; meta?: Record<string, unknown> | null; content?: unknown }\n | undefined;\n\n try {\n const statResult = await afs.stat(canonicalPath);\n entry = statResult.data;\n } catch {}\n\n if (!entry) {\n try {\n const readResult = await afs.read(canonicalPath);\n entry = readResult.data;\n } catch {}\n }\n\n if (!entry) {\n return {\n path,\n type: \"unknown\",\n };\n }\n\n const metadata = entry.meta || {};\n // Determine type from childrenCount: if defined, it has children (directory-like); otherwise file-like\n const entryType =\n metadata.kind === \"afs:executable\"\n ? \"exec\"\n : typeof metadata.childrenCount === \"number\"\n ? \"directory\"\n : \"file\";\n\n const result: PathExplainResult = {\n path: entry.path,\n type: entryType,\n description: metadata.description as string | undefined,\n meta: {},\n };\n\n // Add metadata\n if (metadata.provider) {\n result.meta!.provider = String(metadata.provider);\n }\n if (metadata.permissions) {\n const perms = metadata.permissions;\n result.meta!.permissions = Array.isArray(perms) ? perms.join(\", \") : String(perms);\n }\n\n // For exec type, try to get schema info\n if (entryType === \"exec\") {\n if (metadata.inputs) {\n result.inputs = Array.isArray(metadata.inputs)\n ? (metadata.inputs as string[])\n : [String(metadata.inputs)];\n }\n if (metadata.outputs) {\n result.outputs = Array.isArray(metadata.outputs)\n ? (metadata.outputs as string[])\n : [String(metadata.outputs)];\n }\n if (metadata.errors) {\n result.errors = Array.isArray(metadata.errors)\n ? (metadata.errors as string[])\n : [String(metadata.errors)];\n }\n if (metadata.sideEffects) {\n result.sideEffects = Array.isArray(metadata.sideEffects)\n ? (metadata.sideEffects as string[])\n : [String(metadata.sideEffects)];\n }\n }\n\n // Clean up empty metadata\n if (Object.keys(result.meta!).length === 0) {\n delete result.meta;\n }\n\n return result;\n } catch (err) {\n return {\n path,\n type: \"error\",\n description: err instanceof Error ? err.message : String(err),\n };\n }\n}\n\n/**\n * Get explanation for a concept\n */\nasync function getConceptExplanation(\n topic: string,\n cwd: string,\n afs?: AFS,\n): Promise<ExplainResult> {\n switch (topic) {\n case \"mount\":\n case \"mounts\":\n return explainMounts(cwd, afs);\n case \"path\":\n case \"paths\":\n return explainPaths();\n case \"uri\":\n case \"provider\":\n case \"providers\":\n return explainUri();\n default:\n return {\n topic: \"Unknown Topic\",\n explanation: `Unknown topic: ${topic}\\n\\nAvailable topics: overview, mount, path, uri`,\n };\n }\n}\n\nfunction explainOverview(): ExplainResult {\n return {\n topic: \"AFS Overview\",\n explanation: `AFS (Abstract File System) is a virtual filesystem that unifies different data sources into a single namespace.\n\nCore Concepts:\n- mount: Mount a data source to a virtual path\n- path: Virtual path, e.g., /src, /data\n- uri: Data source address, e.g., fs://, git://, sqlite://\n\nData Flow:\n User Path -> AFS -> /{mount} -> Provider -> Actual Data`,\n examples: [\"afs mount add /src fs:///path/to/source\", \"afs ls /src\", \"afs read /src/file.txt\"],\n };\n}\n\nasync function explainMounts(cwd: string, afs?: AFS): Promise<ExplainResult> {\n const result = await configMountListCommand(cwd);\n\n if (result.mounts.length === 0) {\n // Try runtime mounts if no config mounts\n if (afs) {\n try {\n const listResult = await afs.list(\"$afs:/\");\n if (listResult.data?.length) {\n const runtimeMounts = listResult.data\n .map((entry) => ` ${entry.path} (runtime mount)`)\n .join(\"\\n\");\n\n return {\n topic: \"Mounts\",\n explanation: `Runtime mounts (mounted via API):\\n\\n${runtimeMounts}`,\n examples: listResult.data.slice(0, 3).map((entry) => `afs ls ${entry.path}`),\n };\n }\n } catch {}\n }\n\n return {\n topic: \"Mounts\",\n explanation: `No mounts configured.\n\nUse mount add to add a mount:\n afs mount add <path> <uri>\n\npath: Virtual path for accessing data in AFS\nuri: Data source URI specifying the data origin`,\n examples: [\n \"afs mount add /src fs:///Users/me/project\",\n \"afs mount add /data sqlite:///data.db\",\n ],\n };\n }\n\n const mountList = result.mounts\n .map((m) => ` ${m.path}${m.description ? ` - ${m.description}` : \"\"}`)\n .join(\"\\n\");\n\n return {\n topic: \"Mounts\",\n explanation: `Current mount configuration:\n\n${mountList}\n\nAfter mounting, data is accessed via the mount path:\n path=\"/src\" -> /src`,\n examples: result.mounts.map((m) => `afs ls ${m.path}`),\n };\n}\n\nfunction explainPaths(): ExplainResult {\n return {\n topic: \"Paths\",\n explanation: `AFS Path Structure:\n\n/ Root directory\n/{mount} Mounted data source\n/{mount}/{path} Files/nodes within a mount\n\nPath Mapping:\n config: path=\"/src\" -> access: /src\n config: path=\"/data\" -> access: /data\n\nMounts are accessed directly at their configured path.`,\n };\n}\n\nfunction explainUri(): ExplainResult {\n return {\n topic: \"URI\",\n explanation: `AFS URI Schemes and Objects:\n\nfs:// Local Filesystem Provider\n Format: fs:///absolute/path or fs://./relative/path\n Operations: list, read, write, delete\n\ngit:// Git Repository Provider\n Format: git:///local/repo or git://github.com/user/repo?branch=main\n Operations: list, read, exec (diff, create-branch, commit, merge)\n\nsqlite:// SQLite Database Provider\n Format: sqlite:///path/to/db.sqlite\n Operations: list, read, exec (SQL queries)\n\njson:// JSON Data Provider\n Format: json:///path/to/data.json\n Operations: list, read, write\n\ntoml:// TOML Data Provider\n Format: toml:///path/to/config.toml\n Operations: list, read, write\n\nsandbox:// Sandboxed Script Execution Provider\n Format: sandbox:///path/to/scripts\n Operations: list, read, exec\n\ngithub:// GitHub Issues/PRs Provider\n Format: github://owner/repo\n Operations: list, read, exec (create-issue, close-issue, etc.)\n\nhttp:// HTTP Proxy Provider\n Format: http://host:port/path\n Operations: list, read\n\nmcp:// MCP Server Provider\n Format: mcp:///path/to/server\n Operations: list, read, exec\n\ns3:// AWS S3 Storage Provider\n Format: s3://bucket-name\n Operations: list, read, write, delete\n\ngcs:// Google Cloud Storage Provider\n Format: gcs://bucket-name\n Operations: list, read, write, delete\n\nec2:// AWS EC2 Instances Provider\n Format: ec2://region\n Operations: list, read, exec (run-instances)\n\ngce:// Google Compute Engine Provider\n Format: gce://project/zone\n Operations: list, read, exec\n\ndns:// Cloud DNS Provider\n Format: dns://provider (route53, clouddns)\n Operations: list, read`,\n examples: [\n \"afs mount add /src fs:///Users/me/project\",\n \"afs mount add /repo git://github.com/user/repo\",\n \"afs mount add /db sqlite:///data.sqlite\",\n \"afs mount add /s3 s3://my-bucket\",\n ],\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"explain.mjs","names":[],"sources":["../../../src/core/commands/explain.ts"],"sourcesContent":["/**\n * explain Command - Core Implementation\n *\n * Explains AFS paths or concepts.\n * Matches old version output format with topic/explanation/examples structure.\n */\n\nimport type { AFS } from \"@aigne/afs\";\nimport type { CommandModule } from \"yargs\";\nimport { configMountListCommand } from \"../../config/mount-commands.js\";\nimport { formatExplainOutput, formatPathExplainOutput } from \"../formatters/index.js\";\nimport { cliPathToCanonical } from \"../path-utils.js\";\nimport { type CommandFactoryOptions, resolveAFS } from \"./types.js\";\n\n/**\n * Result for concept explanations (mount, paths, uri, overview)\n */\nexport interface ExplainResult {\n topic: string;\n explanation: string;\n examples?: string[];\n}\n\n/**\n * Result for path explanations\n */\nexport interface PathExplainResult {\n path: string;\n type: string;\n description?: string;\n inputs?: string[];\n outputs?: string[];\n errors?: string[];\n sideEffects?: string[];\n meta?: Record<string, string>;\n /** When set, the formatter renders this markdown directly instead of structured fields */\n markdown?: string;\n}\n\n/**\n * Explain command arguments\n */\nexport interface ExplainArgs {\n topic?: string;\n}\n\n/**\n * Create explain command factory\n */\nexport function createExplainCommand(\n options: CommandFactoryOptions,\n): CommandModule<unknown, ExplainArgs> {\n return {\n command: \"explain [topic]\",\n describe: \"Explain AFS concepts or paths\",\n builder: {\n topic: {\n type: \"string\",\n description: \"Topic (mount, path, uri) or AFS path (e.g., /src)\",\n },\n },\n handler: async (argv) => {\n const target = argv.topic;\n\n // No target - explain overview\n if (!target) {\n options.onResult({\n command: \"explain\",\n result: explainOverview(),\n format: formatExplainOutput,\n });\n return;\n }\n\n // Priority: try as path first, then as concept\n const isPath = target.startsWith(\"/\") || target.startsWith(\"@\") || target.startsWith(\"$\");\n\n if (isPath) {\n const afs = await resolveAFS(options);\n const canonicalPath = cliPathToCanonical(target);\n const result = await getPathExplanation(afs, target, canonicalPath);\n options.onResult({ command: \"explain\", result, format: formatPathExplainOutput });\n } else {\n // Not obviously a path — try as path first, fall back to concept\n let afs: AFS | undefined;\n try {\n afs = await resolveAFS(options);\n } catch {\n // AFS may not be configured — fall through to concept explanation\n }\n\n if (afs) {\n try {\n const canonicalPath = cliPathToCanonical(`/${target}`);\n const explainResult = await afs.explain(canonicalPath);\n if (explainResult.format === \"markdown\" && explainResult.content) {\n options.onResult({\n command: \"explain\",\n result: { path: `/${target}`, type: \"explained\", markdown: explainResult.content },\n format: formatPathExplainOutput,\n });\n return;\n }\n } catch {\n // Not a valid path, fall through to concept\n }\n }\n\n const cwd = options.cwd ?? process.cwd();\n const result = await getConceptExplanation(target.toLowerCase(), cwd, afs);\n options.onResult({\n command: \"explain\",\n result,\n format: formatExplainOutput,\n });\n }\n },\n };\n}\n\n/**\n * Get explanation for a path\n */\nasync function getPathExplanation(\n afs: AFS,\n path: string,\n canonicalPath: string,\n): Promise<PathExplainResult> {\n try {\n // Always try afs.explain() first — transparent passthrough to provider explain\n try {\n const explainResult = await afs.explain(canonicalPath);\n if (explainResult.format === \"markdown\" && explainResult.content) {\n return {\n path,\n type: \"explained\",\n markdown: explainResult.content,\n };\n }\n } catch {\n // explain not available, fall through to stat-based logic\n }\n\n // Fallback: stat-based explanation\n let entry:\n | { path: string; meta?: Record<string, unknown> | null; content?: unknown }\n | undefined;\n\n try {\n const statResult = await afs.stat(canonicalPath);\n entry = statResult.data;\n } catch {\n // stat() optional — fall through to read()\n }\n\n if (!entry) {\n try {\n const readResult = await afs.read(canonicalPath);\n entry = readResult.data;\n } catch {\n // read() also failed — entry stays undefined, returns \"unknown\" type below\n }\n }\n\n if (!entry) {\n return {\n path,\n type: \"unknown\",\n };\n }\n\n const metadata = entry.meta || {};\n // Determine type from childrenCount: if defined, it has children (directory-like); otherwise file-like\n const entryType =\n metadata.kind === \"afs:executable\"\n ? \"exec\"\n : typeof metadata.childrenCount === \"number\"\n ? \"directory\"\n : \"file\";\n\n const result: PathExplainResult = {\n path: entry.path,\n type: entryType,\n description: metadata.description as string | undefined,\n meta: {},\n };\n\n // Add metadata\n if (metadata.provider) {\n result.meta!.provider = String(metadata.provider);\n }\n if (metadata.permissions) {\n const perms = metadata.permissions;\n result.meta!.permissions = Array.isArray(perms) ? perms.join(\", \") : String(perms);\n }\n\n // For exec type, try to get schema info\n if (entryType === \"exec\") {\n if (metadata.inputs) {\n result.inputs = Array.isArray(metadata.inputs)\n ? (metadata.inputs as string[])\n : [String(metadata.inputs)];\n }\n if (metadata.outputs) {\n result.outputs = Array.isArray(metadata.outputs)\n ? (metadata.outputs as string[])\n : [String(metadata.outputs)];\n }\n if (metadata.errors) {\n result.errors = Array.isArray(metadata.errors)\n ? (metadata.errors as string[])\n : [String(metadata.errors)];\n }\n if (metadata.sideEffects) {\n result.sideEffects = Array.isArray(metadata.sideEffects)\n ? (metadata.sideEffects as string[])\n : [String(metadata.sideEffects)];\n }\n }\n\n // Clean up empty metadata\n if (Object.keys(result.meta!).length === 0) {\n delete result.meta;\n }\n\n return result;\n } catch (err) {\n return {\n path,\n type: \"error\",\n description: err instanceof Error ? err.message : String(err),\n };\n }\n}\n\n/**\n * Get explanation for a concept\n */\nasync function getConceptExplanation(\n topic: string,\n cwd: string,\n afs?: AFS,\n): Promise<ExplainResult> {\n switch (topic) {\n case \"mount\":\n case \"mounts\":\n return explainMounts(cwd, afs);\n case \"path\":\n case \"paths\":\n return explainPaths();\n case \"uri\":\n case \"provider\":\n case \"providers\":\n return explainUri();\n default:\n return {\n topic: \"Unknown Topic\",\n explanation: `Unknown topic: ${topic}\\n\\nAvailable topics: overview, mount, path, uri`,\n };\n }\n}\n\nfunction explainOverview(): ExplainResult {\n return {\n topic: \"AFS Overview\",\n explanation: `AFS (Abstract File System) is a virtual filesystem that unifies different data sources into a single namespace.\n\nCore Concepts:\n- mount: Mount a data source to a virtual path\n- path: Virtual path, e.g., /src, /data\n- uri: Data source address, e.g., fs://, git://, sqlite://\n\nData Flow:\n User Path -> AFS -> /{mount} -> Provider -> Actual Data`,\n examples: [\"afs mount add /src fs:///path/to/source\", \"afs ls /src\", \"afs read /src/file.txt\"],\n };\n}\n\nasync function explainMounts(cwd: string, afs?: AFS): Promise<ExplainResult> {\n const result = await configMountListCommand(cwd);\n\n if (result.mounts.length === 0) {\n // Try runtime mounts if no config mounts\n if (afs) {\n try {\n const listResult = await afs.list(\"$afs:/\");\n if (listResult.data?.length) {\n const runtimeMounts = listResult.data\n .map((entry) => ` ${entry.path} (runtime mount)`)\n .join(\"\\n\");\n\n return {\n topic: \"Mounts\",\n explanation: `Runtime mounts (mounted via API):\\n\\n${runtimeMounts}`,\n examples: listResult.data.slice(0, 3).map((entry) => `afs ls ${entry.path}`),\n };\n }\n } catch (err) {\n // Mount enumeration failed — debug log and show \"no mounts\" fallback\n console.debug(\"[explain] mount enumeration failed:\", err);\n }\n }\n\n return {\n topic: \"Mounts\",\n explanation: `No mounts configured.\n\nUse mount add to add a mount:\n afs mount add <path> <uri>\n\npath: Virtual path for accessing data in AFS\nuri: Data source URI specifying the data origin`,\n examples: [\n \"afs mount add /src fs:///Users/me/project\",\n \"afs mount add /data sqlite:///data.db\",\n ],\n };\n }\n\n const mountList = result.mounts\n .map((m) => ` ${m.path}${m.description ? ` - ${m.description}` : \"\"}`)\n .join(\"\\n\");\n\n return {\n topic: \"Mounts\",\n explanation: `Current mount configuration:\n\n${mountList}\n\nAfter mounting, data is accessed via the mount path:\n path=\"/src\" -> /src`,\n examples: result.mounts.map((m) => `afs ls ${m.path}`),\n };\n}\n\nfunction explainPaths(): ExplainResult {\n return {\n topic: \"Paths\",\n explanation: `AFS Path Structure:\n\n/ Root directory\n/{mount} Mounted data source\n/{mount}/{path} Files/nodes within a mount\n\nPath Mapping:\n config: path=\"/src\" -> access: /src\n config: path=\"/data\" -> access: /data\n\nMounts are accessed directly at their configured path.`,\n };\n}\n\nfunction explainUri(): ExplainResult {\n return {\n topic: \"URI\",\n explanation: `AFS URI Schemes and Objects:\n\nfs:// Local Filesystem Provider\n Format: fs:///absolute/path or fs://./relative/path\n Operations: list, read, write, delete\n\ngit:// Git Repository Provider\n Format: git:///local/repo or git://github.com/user/repo?branch=main\n Operations: list, read, exec (diff, create-branch, commit, merge)\n\nsqlite:// SQLite Database Provider\n Format: sqlite:///path/to/db.sqlite\n Operations: list, read, exec (SQL queries)\n\njson:// JSON Data Provider\n Format: json:///path/to/data.json\n Operations: list, read, write\n\ntoml:// TOML Data Provider\n Format: toml:///path/to/config.toml\n Operations: list, read, write\n\nsandbox:// Sandboxed Script Execution Provider\n Format: sandbox:///path/to/scripts\n Operations: list, read, exec\n\ngithub:// GitHub Issues/PRs Provider\n Format: github://owner/repo\n Operations: list, read, exec (create-issue, close-issue, etc.)\n\nhttp:// HTTP Proxy Provider\n Format: http://host:port/path\n Operations: list, read\n\nmcp:// MCP Server Provider\n Format: mcp:///path/to/server\n Operations: list, read, exec\n\ns3:// AWS S3 Storage Provider\n Format: s3://bucket-name\n Operations: list, read, write, delete\n\ngcs:// Google Cloud Storage Provider\n Format: gcs://bucket-name\n Operations: list, read, write, delete\n\nec2:// AWS EC2 Instances Provider\n Format: ec2://region\n Operations: list, read, exec (run-instances)\n\ngce:// Google Compute Engine Provider\n Format: gce://project/zone\n Operations: list, read, exec\n\ndns:// Cloud DNS Provider\n Format: dns://provider (route53, clouddns)\n Operations: list, read`,\n examples: [\n \"afs mount add /src fs:///Users/me/project\",\n \"afs mount add /repo git://github.com/user/repo\",\n \"afs mount add /db sqlite:///data.sqlite\",\n \"afs mount add /s3 s3://my-bucket\",\n ],\n };\n}\n"],"mappings":"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"}
|
|
@@ -8,20 +8,55 @@ const require_version = require('../../version.cjs');
|
|
|
8
8
|
function createExploreCommand(options) {
|
|
9
9
|
return {
|
|
10
10
|
command: "explore [path]",
|
|
11
|
-
describe: "Interactive TUI
|
|
12
|
-
builder: {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
describe: "Interactive explorer (TUI or web)",
|
|
12
|
+
builder: {
|
|
13
|
+
path: {
|
|
14
|
+
type: "string",
|
|
15
|
+
default: "/",
|
|
16
|
+
description: "Starting path"
|
|
17
|
+
},
|
|
18
|
+
web: {
|
|
19
|
+
type: "boolean",
|
|
20
|
+
default: false,
|
|
21
|
+
description: "Launch web-based explorer"
|
|
22
|
+
},
|
|
23
|
+
port: {
|
|
24
|
+
type: "number",
|
|
25
|
+
default: 0,
|
|
26
|
+
description: "Port for web explorer (0 = auto)"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
17
29
|
handler: async (argv) => {
|
|
18
30
|
const afs = await require_types.resolveAFS(options);
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
31
|
+
if (argv.web) {
|
|
32
|
+
const { startExplorer } = await import("@aigne/afs-explorer");
|
|
33
|
+
const { resolve } = await import("node:path");
|
|
34
|
+
let webRoot;
|
|
35
|
+
try {
|
|
36
|
+
const { createRequire } = await import("node:module");
|
|
37
|
+
webRoot = resolve(createRequire(require("url").pathToFileURL(__filename).href).resolve("@aigne/afs-explorer/package.json"), "..", "web");
|
|
38
|
+
} catch {}
|
|
39
|
+
const info = await startExplorer(afs, {
|
|
40
|
+
port: argv.port,
|
|
41
|
+
host: "localhost",
|
|
42
|
+
webRoot,
|
|
43
|
+
open: true
|
|
44
|
+
});
|
|
45
|
+
console.log(`AFS Explorer running at ${info.url}`);
|
|
46
|
+
console.log("Press Ctrl+C to stop");
|
|
47
|
+
process.on("SIGINT", () => {
|
|
48
|
+
info.stop();
|
|
49
|
+
process.exit(0);
|
|
50
|
+
});
|
|
51
|
+
await new Promise(() => {});
|
|
52
|
+
} else {
|
|
53
|
+
const { createExplorerScreen } = await Promise.resolve().then(() => require("../../explorer/screen.cjs"));
|
|
54
|
+
await createExplorerScreen({
|
|
55
|
+
afs,
|
|
56
|
+
startPath: argv.path,
|
|
57
|
+
version: require_version.VERSION
|
|
58
|
+
});
|
|
59
|
+
}
|
|
25
60
|
}
|
|
26
61
|
};
|
|
27
62
|
}
|