@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,180 @@
|
|
|
1
|
+
import { deriveKeyFromPassphrase, readVaultSalt } from "./encrypted-file.mjs";
|
|
2
|
+
import { randomBytes } from "node:crypto";
|
|
3
|
+
import { execFile } from "node:child_process";
|
|
4
|
+
import { platform } from "node:os";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
|
|
7
|
+
//#region ../../providers/vault/dist/key-resolver.mjs
|
|
8
|
+
/**
|
|
9
|
+
* Master key resolution chain.
|
|
10
|
+
*
|
|
11
|
+
* Priority (first available wins):
|
|
12
|
+
* 1. AFS_VAULT_KEY environment variable (hex-encoded) — explicit override for CI/automation
|
|
13
|
+
* 2. OS Keychain (macOS Keychain / Linux Secret Service) — desktop default
|
|
14
|
+
* 3. Passphrase prompt (key derived via crypto.scrypt, cached in memory)
|
|
15
|
+
*
|
|
16
|
+
* No native dependencies — uses system CLIs for keychain access.
|
|
17
|
+
*/
|
|
18
|
+
const execFileAsync = promisify(execFile);
|
|
19
|
+
const SERVICE_NAME = "afs-vault";
|
|
20
|
+
const ACCOUNT_NAME = "master-key";
|
|
21
|
+
const KEY_LENGTH = 32;
|
|
22
|
+
/** Cached key for the process lifetime (avoids repeated prompts). */
|
|
23
|
+
let cachedKey = null;
|
|
24
|
+
/**
|
|
25
|
+
* Resolve master key using the priority chain.
|
|
26
|
+
*
|
|
27
|
+
* @param interactive - If true, allows passphrase prompt. Default true.
|
|
28
|
+
* @param vaultPath - Path to vault file (used for per-file salt in passphrase mode).
|
|
29
|
+
* @returns 32-byte master key buffer.
|
|
30
|
+
*/
|
|
31
|
+
async function resolveMasterKey(interactive = true, vaultPath) {
|
|
32
|
+
if (cachedKey) return cachedKey;
|
|
33
|
+
const envKey = process.env.AFS_VAULT_KEY;
|
|
34
|
+
if (envKey) {
|
|
35
|
+
const buf = Buffer.from(envKey, "hex");
|
|
36
|
+
if (buf.length !== KEY_LENGTH) throw new Error("AFS_VAULT_KEY must be a 64-character hex string (32 bytes)");
|
|
37
|
+
cachedKey = buf;
|
|
38
|
+
return buf;
|
|
39
|
+
}
|
|
40
|
+
const keychainKey = await readKeychain();
|
|
41
|
+
if (keychainKey) {
|
|
42
|
+
cachedKey = keychainKey;
|
|
43
|
+
return keychainKey;
|
|
44
|
+
}
|
|
45
|
+
if (!interactive) throw new Error("No vault master key found. Set AFS_VAULT_KEY environment variable (64-char hex),\nor run in interactive mode to enter a passphrase.");
|
|
46
|
+
const key = deriveKeyFromPassphrase(await promptPassphrase(), (vaultPath ? await readVaultSalt(vaultPath) : null) ?? await readPassphraseSalt());
|
|
47
|
+
cachedKey = key;
|
|
48
|
+
return key;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Store a master key in the OS keychain.
|
|
52
|
+
* Returns true on success, false if keychain is unavailable.
|
|
53
|
+
*/
|
|
54
|
+
async function storeKeychain(masterKey) {
|
|
55
|
+
const hexKey = masterKey.toString("hex");
|
|
56
|
+
const os = platform();
|
|
57
|
+
try {
|
|
58
|
+
if (os === "darwin") {
|
|
59
|
+
await execFileAsync("security", [
|
|
60
|
+
"add-generic-password",
|
|
61
|
+
"-s",
|
|
62
|
+
SERVICE_NAME,
|
|
63
|
+
"-a",
|
|
64
|
+
ACCOUNT_NAME,
|
|
65
|
+
"-w",
|
|
66
|
+
hexKey,
|
|
67
|
+
"-U"
|
|
68
|
+
]);
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
if (os === "linux") {
|
|
72
|
+
const { spawn: spawn$1 } = await import("node:child_process");
|
|
73
|
+
return new Promise((resolve) => {
|
|
74
|
+
const proc = spawn$1("secret-tool", [
|
|
75
|
+
"store",
|
|
76
|
+
"--label",
|
|
77
|
+
"AFS Vault Master Key",
|
|
78
|
+
"service",
|
|
79
|
+
SERVICE_NAME,
|
|
80
|
+
"account",
|
|
81
|
+
ACCOUNT_NAME
|
|
82
|
+
]);
|
|
83
|
+
proc.stdin.write(hexKey);
|
|
84
|
+
proc.stdin.end();
|
|
85
|
+
proc.on("close", (code) => resolve(code === 0));
|
|
86
|
+
proc.on("error", () => resolve(false));
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
return false;
|
|
90
|
+
} catch {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Read master key from OS keychain.
|
|
96
|
+
* Returns null if not found or keychain unavailable.
|
|
97
|
+
*/
|
|
98
|
+
async function readKeychain() {
|
|
99
|
+
const os = platform();
|
|
100
|
+
try {
|
|
101
|
+
if (os === "darwin") {
|
|
102
|
+
const { stdout } = await execFileAsync("security", [
|
|
103
|
+
"find-generic-password",
|
|
104
|
+
"-s",
|
|
105
|
+
SERVICE_NAME,
|
|
106
|
+
"-a",
|
|
107
|
+
ACCOUNT_NAME,
|
|
108
|
+
"-w"
|
|
109
|
+
]);
|
|
110
|
+
const hex = stdout.trim();
|
|
111
|
+
if (hex.length === KEY_LENGTH * 2) return Buffer.from(hex, "hex");
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
if (os === "linux") {
|
|
115
|
+
const { stdout } = await execFileAsync("secret-tool", [
|
|
116
|
+
"lookup",
|
|
117
|
+
"service",
|
|
118
|
+
SERVICE_NAME,
|
|
119
|
+
"account",
|
|
120
|
+
ACCOUNT_NAME
|
|
121
|
+
]);
|
|
122
|
+
const hex = stdout.trim();
|
|
123
|
+
if (hex.length === KEY_LENGTH * 2) return Buffer.from(hex, "hex");
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
return null;
|
|
127
|
+
} catch {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Prompt user for passphrase via stdin (no echo).
|
|
133
|
+
*/
|
|
134
|
+
async function promptPassphrase() {
|
|
135
|
+
const { createInterface } = await import("node:readline");
|
|
136
|
+
return new Promise((resolve, reject) => {
|
|
137
|
+
const rl = createInterface({
|
|
138
|
+
input: process.stdin,
|
|
139
|
+
output: process.stderr,
|
|
140
|
+
terminal: true
|
|
141
|
+
});
|
|
142
|
+
if (process.stdin.isTTY) {
|
|
143
|
+
process.stderr.write("Vault passphrase: ");
|
|
144
|
+
rl._writeToOutput = () => {};
|
|
145
|
+
}
|
|
146
|
+
rl.question("", (answer) => {
|
|
147
|
+
rl.close();
|
|
148
|
+
if (process.stdin.isTTY) process.stderr.write("\n");
|
|
149
|
+
if (!answer || answer.trim() === "") {
|
|
150
|
+
reject(/* @__PURE__ */ new Error("Passphrase cannot be empty"));
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
resolve(answer);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Read or create the passphrase salt file.
|
|
159
|
+
*
|
|
160
|
+
* The salt is stored alongside the vault file to enable consistent
|
|
161
|
+
* key derivation from the same passphrase.
|
|
162
|
+
*/
|
|
163
|
+
async function readPassphraseSalt() {
|
|
164
|
+
const { homedir: homedir$1 } = await import("node:os");
|
|
165
|
+
const { join } = await import("node:path");
|
|
166
|
+
const { readFile, writeFile, mkdir } = await import("node:fs/promises");
|
|
167
|
+
const saltPath = join(homedir$1(), ".afs-config", "vault-salt");
|
|
168
|
+
try {
|
|
169
|
+
const data = await readFile(saltPath);
|
|
170
|
+
if (data.length === 32) return data;
|
|
171
|
+
} catch {}
|
|
172
|
+
const salt = randomBytes(32);
|
|
173
|
+
await mkdir(join(homedir$1(), ".afs-config"), { recursive: true });
|
|
174
|
+
await writeFile(saltPath, salt, { mode: 384 });
|
|
175
|
+
return salt;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
//#endregion
|
|
179
|
+
export { resolveMasterKey, storeKeychain };
|
|
180
|
+
//# sourceMappingURL=key-resolver.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"key-resolver.mjs","names":["spawn","homedir"],"sources":["../../../../../../providers/vault/dist/key-resolver.mjs"],"sourcesContent":["import { deriveKeyFromPassphrase, readVaultSalt } from \"./encrypted-file.mjs\";\nimport { randomBytes } from \"node:crypto\";\nimport { execFile } from \"node:child_process\";\nimport { platform } from \"node:os\";\nimport { promisify } from \"node:util\";\n\n//#region src/key-resolver.ts\n/**\n* Master key resolution chain.\n*\n* Priority (first available wins):\n* 1. AFS_VAULT_KEY environment variable (hex-encoded) — explicit override for CI/automation\n* 2. OS Keychain (macOS Keychain / Linux Secret Service) — desktop default\n* 3. Passphrase prompt (key derived via crypto.scrypt, cached in memory)\n*\n* No native dependencies — uses system CLIs for keychain access.\n*/\nconst execFileAsync = promisify(execFile);\nconst SERVICE_NAME = \"afs-vault\";\nconst ACCOUNT_NAME = \"master-key\";\nconst KEY_LENGTH = 32;\n/** Cached key for the process lifetime (avoids repeated prompts). */\nlet cachedKey = null;\n/**\n* Resolve master key using the priority chain.\n*\n* @param interactive - If true, allows passphrase prompt. Default true.\n* @param vaultPath - Path to vault file (used for per-file salt in passphrase mode).\n* @returns 32-byte master key buffer.\n*/\nasync function resolveMasterKey(interactive = true, vaultPath) {\n\tif (cachedKey) return cachedKey;\n\tconst envKey = process.env.AFS_VAULT_KEY;\n\tif (envKey) {\n\t\tconst buf = Buffer.from(envKey, \"hex\");\n\t\tif (buf.length !== KEY_LENGTH) throw new Error(\"AFS_VAULT_KEY must be a 64-character hex string (32 bytes)\");\n\t\tcachedKey = buf;\n\t\treturn buf;\n\t}\n\tconst keychainKey = await readKeychain();\n\tif (keychainKey) {\n\t\tcachedKey = keychainKey;\n\t\treturn keychainKey;\n\t}\n\tif (!interactive) throw new Error(\"No vault master key found. Set AFS_VAULT_KEY environment variable (64-char hex),\\nor run in interactive mode to enter a passphrase.\");\n\tconst key = deriveKeyFromPassphrase(await promptPassphrase(), (vaultPath ? await readVaultSalt(vaultPath) : null) ?? await readPassphraseSalt());\n\tcachedKey = key;\n\treturn key;\n}\n/**\n* Store a master key in the OS keychain.\n* Returns true on success, false if keychain is unavailable.\n*/\nasync function storeKeychain(masterKey) {\n\tconst hexKey = masterKey.toString(\"hex\");\n\tconst os = platform();\n\ttry {\n\t\tif (os === \"darwin\") {\n\t\t\tawait execFileAsync(\"security\", [\n\t\t\t\t\"add-generic-password\",\n\t\t\t\t\"-s\",\n\t\t\t\tSERVICE_NAME,\n\t\t\t\t\"-a\",\n\t\t\t\tACCOUNT_NAME,\n\t\t\t\t\"-w\",\n\t\t\t\thexKey,\n\t\t\t\t\"-U\"\n\t\t\t]);\n\t\t\treturn true;\n\t\t}\n\t\tif (os === \"linux\") {\n\t\t\tconst { spawn } = await import(\"node:child_process\");\n\t\t\treturn new Promise((resolve) => {\n\t\t\t\tconst proc = spawn(\"secret-tool\", [\n\t\t\t\t\t\"store\",\n\t\t\t\t\t\"--label\",\n\t\t\t\t\t\"AFS Vault Master Key\",\n\t\t\t\t\t\"service\",\n\t\t\t\t\tSERVICE_NAME,\n\t\t\t\t\t\"account\",\n\t\t\t\t\tACCOUNT_NAME\n\t\t\t\t]);\n\t\t\t\tproc.stdin.write(hexKey);\n\t\t\t\tproc.stdin.end();\n\t\t\t\tproc.on(\"close\", (code) => resolve(code === 0));\n\t\t\t\tproc.on(\"error\", () => resolve(false));\n\t\t\t});\n\t\t}\n\t\treturn false;\n\t} catch {\n\t\treturn false;\n\t}\n}\n/**\n* Read master key from OS keychain.\n* Returns null if not found or keychain unavailable.\n*/\nasync function readKeychain() {\n\tconst os = platform();\n\ttry {\n\t\tif (os === \"darwin\") {\n\t\t\tconst { stdout } = await execFileAsync(\"security\", [\n\t\t\t\t\"find-generic-password\",\n\t\t\t\t\"-s\",\n\t\t\t\tSERVICE_NAME,\n\t\t\t\t\"-a\",\n\t\t\t\tACCOUNT_NAME,\n\t\t\t\t\"-w\"\n\t\t\t]);\n\t\t\tconst hex = stdout.trim();\n\t\t\tif (hex.length === KEY_LENGTH * 2) return Buffer.from(hex, \"hex\");\n\t\t\treturn null;\n\t\t}\n\t\tif (os === \"linux\") {\n\t\t\tconst { stdout } = await execFileAsync(\"secret-tool\", [\n\t\t\t\t\"lookup\",\n\t\t\t\t\"service\",\n\t\t\t\tSERVICE_NAME,\n\t\t\t\t\"account\",\n\t\t\t\tACCOUNT_NAME\n\t\t\t]);\n\t\t\tconst hex = stdout.trim();\n\t\t\tif (hex.length === KEY_LENGTH * 2) return Buffer.from(hex, \"hex\");\n\t\t\treturn null;\n\t\t}\n\t\treturn null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n/**\n* Delete master key from OS keychain.\n*/\nasync function deleteKeychain() {\n\tconst os = platform();\n\ttry {\n\t\tif (os === \"darwin\") {\n\t\t\tawait execFileAsync(\"security\", [\n\t\t\t\t\"delete-generic-password\",\n\t\t\t\t\"-s\",\n\t\t\t\tSERVICE_NAME,\n\t\t\t\t\"-a\",\n\t\t\t\tACCOUNT_NAME\n\t\t\t]);\n\t\t\treturn true;\n\t\t}\n\t\tif (os === \"linux\") {\n\t\t\tawait execFileAsync(\"secret-tool\", [\n\t\t\t\t\"clear\",\n\t\t\t\t\"service\",\n\t\t\t\tSERVICE_NAME,\n\t\t\t\t\"account\",\n\t\t\t\tACCOUNT_NAME\n\t\t\t]);\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t} catch {\n\t\treturn false;\n\t}\n}\n/**\n* Prompt user for passphrase via stdin (no echo).\n*/\nasync function promptPassphrase() {\n\tconst { createInterface } = await import(\"node:readline\");\n\treturn new Promise((resolve, reject) => {\n\t\tconst rl = createInterface({\n\t\t\tinput: process.stdin,\n\t\t\toutput: process.stderr,\n\t\t\tterminal: true\n\t\t});\n\t\tif (process.stdin.isTTY) {\n\t\t\tprocess.stderr.write(\"Vault passphrase: \");\n\t\t\trl._writeToOutput = () => {};\n\t\t}\n\t\trl.question(\"\", (answer) => {\n\t\t\trl.close();\n\t\t\tif (process.stdin.isTTY) process.stderr.write(\"\\n\");\n\t\t\tif (!answer || answer.trim() === \"\") {\n\t\t\t\treject(/* @__PURE__ */ new Error(\"Passphrase cannot be empty\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tresolve(answer);\n\t\t});\n\t});\n}\n/**\n* Read or create the passphrase salt file.\n*\n* The salt is stored alongside the vault file to enable consistent\n* key derivation from the same passphrase.\n*/\nasync function readPassphraseSalt() {\n\tconst { homedir } = await import(\"node:os\");\n\tconst { join } = await import(\"node:path\");\n\tconst { readFile, writeFile, mkdir } = await import(\"node:fs/promises\");\n\tconst saltPath = join(homedir(), \".afs-config\", \"vault-salt\");\n\ttry {\n\t\tconst data = await readFile(saltPath);\n\t\tif (data.length === 32) return data;\n\t} catch {}\n\tconst salt = randomBytes(32);\n\tawait mkdir(join(homedir(), \".afs-config\"), { recursive: true });\n\tawait writeFile(saltPath, salt, { mode: 384 });\n\treturn salt;\n}\n/**\n* Reset cached key (for testing).\n*/\nfunction _resetCachedKeyForTesting() {\n\tcachedKey = null;\n}\n\n//#endregion\nexport { _resetCachedKeyForTesting, deleteKeychain, resolveMasterKey, storeKeychain };\n//# sourceMappingURL=key-resolver.mjs.map"],"mappings":"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"}
|
package/dist/repl.cjs
CHANGED
|
@@ -30,7 +30,11 @@ const ALL_COMMANDS = [
|
|
|
30
30
|
"stat",
|
|
31
31
|
"exec",
|
|
32
32
|
"explain",
|
|
33
|
+
"search",
|
|
33
34
|
"mount",
|
|
35
|
+
"program",
|
|
36
|
+
"vault",
|
|
37
|
+
"service",
|
|
34
38
|
"explore",
|
|
35
39
|
"cd",
|
|
36
40
|
"pwd",
|
|
@@ -84,7 +88,11 @@ function formatHelp() {
|
|
|
84
88
|
" stat [path] Get file or directory info (default: current node)",
|
|
85
89
|
" exec <action> Execute an action",
|
|
86
90
|
" explain [topic] Explain AFS concepts or paths (default: current path)",
|
|
87
|
-
"
|
|
91
|
+
" search <path> <query> Search content in a path",
|
|
92
|
+
" mount Mount management (add, list, remove)",
|
|
93
|
+
" program Program management (install, list, uninstall)",
|
|
94
|
+
" vault Credential vault management",
|
|
95
|
+
" service Daemon service management",
|
|
88
96
|
" explore [path] Interactive TUI explorer (default: current dir)",
|
|
89
97
|
"",
|
|
90
98
|
" REPL Commands:",
|
|
@@ -227,18 +235,29 @@ function isExploreCommand(cmd) {
|
|
|
227
235
|
const normalized = cmd.replace(/^afs\s+/, "").trim();
|
|
228
236
|
return normalized === "explore" || normalized.startsWith("explore ");
|
|
229
237
|
}
|
|
230
|
-
function
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
238
|
+
function parseExploreArgs(cmd, ctx) {
|
|
239
|
+
const parts = cmd.replace(/^afs\s+/, "").trim().split(/\s+/).slice(1);
|
|
240
|
+
let web = false;
|
|
241
|
+
let port = 0;
|
|
242
|
+
const pathParts = [];
|
|
243
|
+
for (let i = 0; i < parts.length; i++) {
|
|
244
|
+
const part = parts[i];
|
|
245
|
+
if (part === "--web") web = true;
|
|
246
|
+
else if (part === "--port" && i + 1 < parts.length) port = Number(parts[++i]) || 0;
|
|
247
|
+
else if (part.startsWith("--port=")) port = Number(part.slice(7)) || 0;
|
|
248
|
+
else if (!part.startsWith("-")) pathParts.push(part);
|
|
235
249
|
}
|
|
236
|
-
|
|
250
|
+
let path = pathParts[0];
|
|
251
|
+
if (!path) path = ctx.currentNamespace ? `$afs:${ctx.currentNamespace}${ctx.currentPath}` : ctx.currentPath;
|
|
252
|
+
else if (!path.startsWith("/") && !path.startsWith("@") && !path.startsWith("$afs")) {
|
|
237
253
|
const resolved = normalizePath((0, ufo.joinURL)(ctx.currentPath, path));
|
|
238
|
-
|
|
239
|
-
return resolved;
|
|
254
|
+
path = ctx.currentNamespace ? `$afs:${ctx.currentNamespace}${resolved}` : resolved;
|
|
240
255
|
}
|
|
241
|
-
return
|
|
256
|
+
return {
|
|
257
|
+
path,
|
|
258
|
+
web,
|
|
259
|
+
port
|
|
260
|
+
};
|
|
242
261
|
}
|
|
243
262
|
function createCompleter(ctx) {
|
|
244
263
|
return function completer(line, callback) {
|
|
@@ -375,6 +394,41 @@ async function startRepl(options) {
|
|
|
375
394
|
console.warn(`⚠ ${failures.length} ${noun} failed:`);
|
|
376
395
|
for (const f of failures) console.warn(` - ${f.path}: ${f.reason}`);
|
|
377
396
|
}
|
|
397
|
+
let programManager;
|
|
398
|
+
try {
|
|
399
|
+
const { ProgramManager } = await Promise.resolve().then(() => require("./program/program-manager.cjs"));
|
|
400
|
+
const { scanProgramTriggers } = await Promise.resolve().then(() => require("./program/trigger-scanner.cjs"));
|
|
401
|
+
const { listInstalledPrograms, getUserConfigDir } = await Promise.resolve().then(() => require("./config/program-install.cjs"));
|
|
402
|
+
const userConfigDir = getUserConfigDir();
|
|
403
|
+
programManager = new ProgramManager({
|
|
404
|
+
globalAFS: afs,
|
|
405
|
+
createProvider: afs.createProviderFromMount,
|
|
406
|
+
listPrograms: async () => {
|
|
407
|
+
return (await listInstalledPrograms({ userConfigDir })).map((p) => ({
|
|
408
|
+
id: p.id,
|
|
409
|
+
installPath: p.installPath,
|
|
410
|
+
mountPath: p.mountPath
|
|
411
|
+
}));
|
|
412
|
+
},
|
|
413
|
+
scanTriggers: async (programDir) => {
|
|
414
|
+
let compile = null;
|
|
415
|
+
try {
|
|
416
|
+
compile = (await import("@aigne/ash")).compileSource;
|
|
417
|
+
} catch {}
|
|
418
|
+
return scanProgramTriggers(programDir, compile);
|
|
419
|
+
},
|
|
420
|
+
dataDir: (programId) => `/.data/${programId}`,
|
|
421
|
+
readMountOverrides: async (programId) => {
|
|
422
|
+
const { readProgramMountOverrides } = await Promise.resolve().then(() => require("./config/program-install.cjs"));
|
|
423
|
+
return readProgramMountOverrides(programId, { userConfigDir });
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
await programManager.activateAll();
|
|
427
|
+
const activated = programManager.getActivatedPrograms();
|
|
428
|
+
if (activated.length > 0) console.log(`Activated ${activated.length} program(s): ${activated.join(", ")}`);
|
|
429
|
+
} catch (err) {
|
|
430
|
+
console.error(`[PM] Program activation error:`, err instanceof Error ? err.message : err);
|
|
431
|
+
}
|
|
378
432
|
const executor = new require_index.AFSCommandExecutor(afs, {
|
|
379
433
|
cwd,
|
|
380
434
|
tty: true,
|
|
@@ -389,6 +443,7 @@ async function startRepl(options) {
|
|
|
389
443
|
let closed = false;
|
|
390
444
|
let rl;
|
|
391
445
|
let originalDataHandler = null;
|
|
446
|
+
let activeWebExplorer = null;
|
|
392
447
|
function startReplLoop() {
|
|
393
448
|
const stdin = process.stdin;
|
|
394
449
|
if (stdin._readableState) {
|
|
@@ -448,22 +503,30 @@ async function startRepl(options) {
|
|
|
448
503
|
rl.on("close", () => {
|
|
449
504
|
if (!closed) {
|
|
450
505
|
closed = true;
|
|
506
|
+
if (activeWebExplorer) {
|
|
507
|
+
activeWebExplorer.stop();
|
|
508
|
+
activeWebExplorer = null;
|
|
509
|
+
}
|
|
451
510
|
console.log("\nBye!");
|
|
452
511
|
}
|
|
453
512
|
});
|
|
454
513
|
rl.prompt();
|
|
455
514
|
}
|
|
456
515
|
async function handleExplore(trimmed, ctx$1) {
|
|
516
|
+
const args = parseExploreArgs(trimmed, ctx$1);
|
|
517
|
+
if (args.web) {
|
|
518
|
+
await handleExploreWeb(args, ctx$1);
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
457
521
|
rl.removeAllListeners("line");
|
|
458
522
|
rl.removeAllListeners("close");
|
|
459
523
|
rl.close();
|
|
460
524
|
cleanupStdinAfterBlessed();
|
|
461
525
|
try {
|
|
462
526
|
const { createExplorerScreen } = await Promise.resolve().then(() => require("./explorer/screen.cjs"));
|
|
463
|
-
const startPath = parseExplorePath(trimmed, ctx$1);
|
|
464
527
|
await createExplorerScreen({
|
|
465
528
|
afs: ctx$1.afs,
|
|
466
|
-
startPath,
|
|
529
|
+
startPath: args.path,
|
|
467
530
|
version: ctx$1.version,
|
|
468
531
|
onExit: () => {}
|
|
469
532
|
});
|
|
@@ -475,13 +538,45 @@ async function startRepl(options) {
|
|
|
475
538
|
console.log("");
|
|
476
539
|
if (!closed) startReplLoop();
|
|
477
540
|
}
|
|
541
|
+
async function handleExploreWeb(args, ctx$1) {
|
|
542
|
+
if (activeWebExplorer) {
|
|
543
|
+
activeWebExplorer.stop();
|
|
544
|
+
console.log("Stopped previous web explorer.");
|
|
545
|
+
activeWebExplorer = null;
|
|
546
|
+
}
|
|
547
|
+
try {
|
|
548
|
+
const { resolve } = await import("node:path");
|
|
549
|
+
const { startExplorer } = await import("@aigne/afs-explorer");
|
|
550
|
+
let webRoot;
|
|
551
|
+
try {
|
|
552
|
+
const { createRequire } = await import("node:module");
|
|
553
|
+
webRoot = resolve(createRequire(require("url").pathToFileURL(__filename).href).resolve("@aigne/afs-explorer/package.json"), "..", "web");
|
|
554
|
+
} catch {}
|
|
555
|
+
const info = await startExplorer(ctx$1.afs, {
|
|
556
|
+
port: args.port,
|
|
557
|
+
host: "localhost",
|
|
558
|
+
webRoot,
|
|
559
|
+
open: true
|
|
560
|
+
});
|
|
561
|
+
activeWebExplorer = info;
|
|
562
|
+
console.log(`Web explorer running at ${info.url}`);
|
|
563
|
+
console.log("Type \"explore --web\" again to restart, or continue using REPL.");
|
|
564
|
+
} catch (e) {
|
|
565
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
566
|
+
console.error(`Failed to start web explorer: ${msg}`);
|
|
567
|
+
}
|
|
568
|
+
if (!closed) rl.prompt();
|
|
569
|
+
}
|
|
478
570
|
startReplLoop();
|
|
479
571
|
return new Promise((resolve) => {
|
|
480
572
|
const checkClosed = setInterval(() => {
|
|
481
573
|
if (closed) {
|
|
482
574
|
clearInterval(checkClosed);
|
|
483
|
-
|
|
484
|
-
|
|
575
|
+
const cleanup = async () => {
|
|
576
|
+
if (programManager) await programManager.deactivateAll().catch(() => {});
|
|
577
|
+
if (onExit) await onExit().catch(() => {});
|
|
578
|
+
};
|
|
579
|
+
cleanup().then(resolve);
|
|
485
580
|
}
|
|
486
581
|
}, 100);
|
|
487
582
|
});
|
package/dist/repl.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repl.d.cts","names":[],"sources":["../src/repl.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"repl.d.cts","names":[],"sources":["../src/repl.ts"],"mappings":";;;iBA2jBsB,SAAA,CAAU,OAAA;EAC9B,GAAA;EACA,OAAA;EACA,MAAA,SAAe,OAAA;EAEf,cAAA,GAAiB,KAAA;IAAQ,QAAA,EAAU,SAAA;IAAW,SAAA;EAAA;AAAA,IAC5C,OAAA"}
|
package/dist/repl.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repl.d.mts","names":[],"sources":["../src/repl.ts"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"repl.d.mts","names":[],"sources":["../src/repl.ts"],"mappings":";;;;iBA2jBsB,SAAA,CAAU,OAAA;EAC9B,GAAA;EACA,OAAA;EACA,MAAA,SAAe,OAAA;EAEf,cAAA,GAAiB,KAAA;IAAQ,QAAA,EAAU,SAAA;IAAW,SAAA;EAAA;AAAA,IAC5C,OAAA"}
|
package/dist/repl.mjs
CHANGED
|
@@ -29,7 +29,11 @@ const ALL_COMMANDS = [
|
|
|
29
29
|
"stat",
|
|
30
30
|
"exec",
|
|
31
31
|
"explain",
|
|
32
|
+
"search",
|
|
32
33
|
"mount",
|
|
34
|
+
"program",
|
|
35
|
+
"vault",
|
|
36
|
+
"service",
|
|
33
37
|
"explore",
|
|
34
38
|
"cd",
|
|
35
39
|
"pwd",
|
|
@@ -83,7 +87,11 @@ function formatHelp() {
|
|
|
83
87
|
" stat [path] Get file or directory info (default: current node)",
|
|
84
88
|
" exec <action> Execute an action",
|
|
85
89
|
" explain [topic] Explain AFS concepts or paths (default: current path)",
|
|
86
|
-
"
|
|
90
|
+
" search <path> <query> Search content in a path",
|
|
91
|
+
" mount Mount management (add, list, remove)",
|
|
92
|
+
" program Program management (install, list, uninstall)",
|
|
93
|
+
" vault Credential vault management",
|
|
94
|
+
" service Daemon service management",
|
|
87
95
|
" explore [path] Interactive TUI explorer (default: current dir)",
|
|
88
96
|
"",
|
|
89
97
|
" REPL Commands:",
|
|
@@ -226,18 +234,29 @@ function isExploreCommand(cmd) {
|
|
|
226
234
|
const normalized = cmd.replace(/^afs\s+/, "").trim();
|
|
227
235
|
return normalized === "explore" || normalized.startsWith("explore ");
|
|
228
236
|
}
|
|
229
|
-
function
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
237
|
+
function parseExploreArgs(cmd, ctx) {
|
|
238
|
+
const parts = cmd.replace(/^afs\s+/, "").trim().split(/\s+/).slice(1);
|
|
239
|
+
let web = false;
|
|
240
|
+
let port = 0;
|
|
241
|
+
const pathParts = [];
|
|
242
|
+
for (let i = 0; i < parts.length; i++) {
|
|
243
|
+
const part = parts[i];
|
|
244
|
+
if (part === "--web") web = true;
|
|
245
|
+
else if (part === "--port" && i + 1 < parts.length) port = Number(parts[++i]) || 0;
|
|
246
|
+
else if (part.startsWith("--port=")) port = Number(part.slice(7)) || 0;
|
|
247
|
+
else if (!part.startsWith("-")) pathParts.push(part);
|
|
234
248
|
}
|
|
235
|
-
|
|
249
|
+
let path = pathParts[0];
|
|
250
|
+
if (!path) path = ctx.currentNamespace ? `$afs:${ctx.currentNamespace}${ctx.currentPath}` : ctx.currentPath;
|
|
251
|
+
else if (!path.startsWith("/") && !path.startsWith("@") && !path.startsWith("$afs")) {
|
|
236
252
|
const resolved = normalizePath(joinURL(ctx.currentPath, path));
|
|
237
|
-
|
|
238
|
-
return resolved;
|
|
253
|
+
path = ctx.currentNamespace ? `$afs:${ctx.currentNamespace}${resolved}` : resolved;
|
|
239
254
|
}
|
|
240
|
-
return
|
|
255
|
+
return {
|
|
256
|
+
path,
|
|
257
|
+
web,
|
|
258
|
+
port
|
|
259
|
+
};
|
|
241
260
|
}
|
|
242
261
|
function createCompleter(ctx) {
|
|
243
262
|
return function completer(line, callback) {
|
|
@@ -374,6 +393,41 @@ async function startRepl(options) {
|
|
|
374
393
|
console.warn(`⚠ ${failures.length} ${noun} failed:`);
|
|
375
394
|
for (const f of failures) console.warn(` - ${f.path}: ${f.reason}`);
|
|
376
395
|
}
|
|
396
|
+
let programManager;
|
|
397
|
+
try {
|
|
398
|
+
const { ProgramManager } = await import("./program/program-manager.mjs");
|
|
399
|
+
const { scanProgramTriggers } = await import("./program/trigger-scanner.mjs");
|
|
400
|
+
const { listInstalledPrograms, getUserConfigDir } = await import("./config/program-install.mjs");
|
|
401
|
+
const userConfigDir = getUserConfigDir();
|
|
402
|
+
programManager = new ProgramManager({
|
|
403
|
+
globalAFS: afs,
|
|
404
|
+
createProvider: afs.createProviderFromMount,
|
|
405
|
+
listPrograms: async () => {
|
|
406
|
+
return (await listInstalledPrograms({ userConfigDir })).map((p) => ({
|
|
407
|
+
id: p.id,
|
|
408
|
+
installPath: p.installPath,
|
|
409
|
+
mountPath: p.mountPath
|
|
410
|
+
}));
|
|
411
|
+
},
|
|
412
|
+
scanTriggers: async (programDir) => {
|
|
413
|
+
let compile = null;
|
|
414
|
+
try {
|
|
415
|
+
compile = (await import("@aigne/ash")).compileSource;
|
|
416
|
+
} catch {}
|
|
417
|
+
return scanProgramTriggers(programDir, compile);
|
|
418
|
+
},
|
|
419
|
+
dataDir: (programId) => `/.data/${programId}`,
|
|
420
|
+
readMountOverrides: async (programId) => {
|
|
421
|
+
const { readProgramMountOverrides } = await import("./config/program-install.mjs");
|
|
422
|
+
return readProgramMountOverrides(programId, { userConfigDir });
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
await programManager.activateAll();
|
|
426
|
+
const activated = programManager.getActivatedPrograms();
|
|
427
|
+
if (activated.length > 0) console.log(`Activated ${activated.length} program(s): ${activated.join(", ")}`);
|
|
428
|
+
} catch (err) {
|
|
429
|
+
console.error(`[PM] Program activation error:`, err instanceof Error ? err.message : err);
|
|
430
|
+
}
|
|
377
431
|
const executor = new AFSCommandExecutor(afs, {
|
|
378
432
|
cwd,
|
|
379
433
|
tty: true,
|
|
@@ -388,6 +442,7 @@ async function startRepl(options) {
|
|
|
388
442
|
let closed = false;
|
|
389
443
|
let rl;
|
|
390
444
|
let originalDataHandler = null;
|
|
445
|
+
let activeWebExplorer = null;
|
|
391
446
|
function startReplLoop() {
|
|
392
447
|
const stdin = process.stdin;
|
|
393
448
|
if (stdin._readableState) {
|
|
@@ -447,22 +502,30 @@ async function startRepl(options) {
|
|
|
447
502
|
rl.on("close", () => {
|
|
448
503
|
if (!closed) {
|
|
449
504
|
closed = true;
|
|
505
|
+
if (activeWebExplorer) {
|
|
506
|
+
activeWebExplorer.stop();
|
|
507
|
+
activeWebExplorer = null;
|
|
508
|
+
}
|
|
450
509
|
console.log("\nBye!");
|
|
451
510
|
}
|
|
452
511
|
});
|
|
453
512
|
rl.prompt();
|
|
454
513
|
}
|
|
455
514
|
async function handleExplore(trimmed, ctx$1) {
|
|
515
|
+
const args = parseExploreArgs(trimmed, ctx$1);
|
|
516
|
+
if (args.web) {
|
|
517
|
+
await handleExploreWeb(args, ctx$1);
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
456
520
|
rl.removeAllListeners("line");
|
|
457
521
|
rl.removeAllListeners("close");
|
|
458
522
|
rl.close();
|
|
459
523
|
cleanupStdinAfterBlessed();
|
|
460
524
|
try {
|
|
461
525
|
const { createExplorerScreen } = await import("./explorer/screen.mjs");
|
|
462
|
-
const startPath = parseExplorePath(trimmed, ctx$1);
|
|
463
526
|
await createExplorerScreen({
|
|
464
527
|
afs: ctx$1.afs,
|
|
465
|
-
startPath,
|
|
528
|
+
startPath: args.path,
|
|
466
529
|
version: ctx$1.version,
|
|
467
530
|
onExit: () => {}
|
|
468
531
|
});
|
|
@@ -474,13 +537,45 @@ async function startRepl(options) {
|
|
|
474
537
|
console.log("");
|
|
475
538
|
if (!closed) startReplLoop();
|
|
476
539
|
}
|
|
540
|
+
async function handleExploreWeb(args, ctx$1) {
|
|
541
|
+
if (activeWebExplorer) {
|
|
542
|
+
activeWebExplorer.stop();
|
|
543
|
+
console.log("Stopped previous web explorer.");
|
|
544
|
+
activeWebExplorer = null;
|
|
545
|
+
}
|
|
546
|
+
try {
|
|
547
|
+
const { resolve: resolve$1 } = await import("node:path");
|
|
548
|
+
const { startExplorer } = await import("@aigne/afs-explorer");
|
|
549
|
+
let webRoot;
|
|
550
|
+
try {
|
|
551
|
+
const { createRequire } = await import("node:module");
|
|
552
|
+
webRoot = resolve$1(createRequire(import.meta.url).resolve("@aigne/afs-explorer/package.json"), "..", "web");
|
|
553
|
+
} catch {}
|
|
554
|
+
const info = await startExplorer(ctx$1.afs, {
|
|
555
|
+
port: args.port,
|
|
556
|
+
host: "localhost",
|
|
557
|
+
webRoot,
|
|
558
|
+
open: true
|
|
559
|
+
});
|
|
560
|
+
activeWebExplorer = info;
|
|
561
|
+
console.log(`Web explorer running at ${info.url}`);
|
|
562
|
+
console.log("Type \"explore --web\" again to restart, or continue using REPL.");
|
|
563
|
+
} catch (e) {
|
|
564
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
565
|
+
console.error(`Failed to start web explorer: ${msg}`);
|
|
566
|
+
}
|
|
567
|
+
if (!closed) rl.prompt();
|
|
568
|
+
}
|
|
477
569
|
startReplLoop();
|
|
478
570
|
return new Promise((resolve$1) => {
|
|
479
571
|
const checkClosed = setInterval(() => {
|
|
480
572
|
if (closed) {
|
|
481
573
|
clearInterval(checkClosed);
|
|
482
|
-
|
|
483
|
-
|
|
574
|
+
const cleanup = async () => {
|
|
575
|
+
if (programManager) await programManager.deactivateAll().catch(() => {});
|
|
576
|
+
if (onExit) await onExit().catch(() => {});
|
|
577
|
+
};
|
|
578
|
+
cleanup().then(resolve$1);
|
|
484
579
|
}
|
|
485
580
|
}, 100);
|
|
486
581
|
});
|