@aigne/afs-cli 1.11.0-beta.11 → 1.11.0-beta.12
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 +64 -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 +59 -310
- package/dist/config/afs-loader.mjs.map +1 -1
- package/dist/config/credential-helpers.cjs +291 -0
- package/dist/config/credential-helpers.d.mts +2 -0
- package/dist/config/credential-helpers.mjs +288 -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 +276 -0
- package/dist/config/program-install.d.mts +1 -0
- package/dist/config/program-install.mjs +273 -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 +207 -0
- package/dist/core/commands/daemon.d.mts +2 -0
- package/dist/core/commands/daemon.mjs +208 -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 +91 -0
- package/dist/core/commands/install.d.mts +2 -0
- package/dist/core/commands/install.mjs +92 -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 +21 -0
- package/dist/core/formatters/install.d.mts +1 -0
- package/dist/core/formatters/install.mjs +19 -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/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 +7 -2
- package/dist/credential/resolver.mjs +7 -2
- 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 +162 -0
- package/dist/program/program-manager.mjs +162 -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 +105 -14
- package/dist/repl.d.cts.map +1 -1
- package/dist/repl.d.mts.map +1 -1
- package/dist/repl.mjs +105 -14
- package/dist/repl.mjs.map +1 -1
- package/package.json +29 -22
|
@@ -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,37 @@ 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
|
+
});
|
|
422
|
+
await programManager.activateAll();
|
|
423
|
+
const activated = programManager.getActivatedPrograms();
|
|
424
|
+
if (activated.length > 0) console.log(`Activated ${activated.length} program(s): ${activated.join(", ")}`);
|
|
425
|
+
} catch (err) {
|
|
426
|
+
console.error(`[PM] Program activation error:`, err instanceof Error ? err.message : err);
|
|
427
|
+
}
|
|
378
428
|
const executor = new require_index.AFSCommandExecutor(afs, {
|
|
379
429
|
cwd,
|
|
380
430
|
tty: true,
|
|
@@ -389,6 +439,7 @@ async function startRepl(options) {
|
|
|
389
439
|
let closed = false;
|
|
390
440
|
let rl;
|
|
391
441
|
let originalDataHandler = null;
|
|
442
|
+
let activeWebExplorer = null;
|
|
392
443
|
function startReplLoop() {
|
|
393
444
|
const stdin = process.stdin;
|
|
394
445
|
if (stdin._readableState) {
|
|
@@ -448,22 +499,30 @@ async function startRepl(options) {
|
|
|
448
499
|
rl.on("close", () => {
|
|
449
500
|
if (!closed) {
|
|
450
501
|
closed = true;
|
|
502
|
+
if (activeWebExplorer) {
|
|
503
|
+
activeWebExplorer.stop();
|
|
504
|
+
activeWebExplorer = null;
|
|
505
|
+
}
|
|
451
506
|
console.log("\nBye!");
|
|
452
507
|
}
|
|
453
508
|
});
|
|
454
509
|
rl.prompt();
|
|
455
510
|
}
|
|
456
511
|
async function handleExplore(trimmed, ctx$1) {
|
|
512
|
+
const args = parseExploreArgs(trimmed, ctx$1);
|
|
513
|
+
if (args.web) {
|
|
514
|
+
await handleExploreWeb(args, ctx$1);
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
457
517
|
rl.removeAllListeners("line");
|
|
458
518
|
rl.removeAllListeners("close");
|
|
459
519
|
rl.close();
|
|
460
520
|
cleanupStdinAfterBlessed();
|
|
461
521
|
try {
|
|
462
522
|
const { createExplorerScreen } = await Promise.resolve().then(() => require("./explorer/screen.cjs"));
|
|
463
|
-
const startPath = parseExplorePath(trimmed, ctx$1);
|
|
464
523
|
await createExplorerScreen({
|
|
465
524
|
afs: ctx$1.afs,
|
|
466
|
-
startPath,
|
|
525
|
+
startPath: args.path,
|
|
467
526
|
version: ctx$1.version,
|
|
468
527
|
onExit: () => {}
|
|
469
528
|
});
|
|
@@ -475,13 +534,45 @@ async function startRepl(options) {
|
|
|
475
534
|
console.log("");
|
|
476
535
|
if (!closed) startReplLoop();
|
|
477
536
|
}
|
|
537
|
+
async function handleExploreWeb(args, ctx$1) {
|
|
538
|
+
if (activeWebExplorer) {
|
|
539
|
+
activeWebExplorer.stop();
|
|
540
|
+
console.log("Stopped previous web explorer.");
|
|
541
|
+
activeWebExplorer = null;
|
|
542
|
+
}
|
|
543
|
+
try {
|
|
544
|
+
const { resolve } = await import("node:path");
|
|
545
|
+
const { startExplorer } = await import("@aigne/afs-explorer");
|
|
546
|
+
let webRoot;
|
|
547
|
+
try {
|
|
548
|
+
const { createRequire } = await import("node:module");
|
|
549
|
+
webRoot = resolve(createRequire(require("url").pathToFileURL(__filename).href).resolve("@aigne/afs-explorer/package.json"), "..", "web");
|
|
550
|
+
} catch {}
|
|
551
|
+
const info = await startExplorer(ctx$1.afs, {
|
|
552
|
+
port: args.port,
|
|
553
|
+
host: "localhost",
|
|
554
|
+
webRoot,
|
|
555
|
+
open: true
|
|
556
|
+
});
|
|
557
|
+
activeWebExplorer = info;
|
|
558
|
+
console.log(`Web explorer running at ${info.url}`);
|
|
559
|
+
console.log("Type \"explore --web\" again to restart, or continue using REPL.");
|
|
560
|
+
} catch (e) {
|
|
561
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
562
|
+
console.error(`Failed to start web explorer: ${msg}`);
|
|
563
|
+
}
|
|
564
|
+
if (!closed) rl.prompt();
|
|
565
|
+
}
|
|
478
566
|
startReplLoop();
|
|
479
567
|
return new Promise((resolve) => {
|
|
480
568
|
const checkClosed = setInterval(() => {
|
|
481
569
|
if (closed) {
|
|
482
570
|
clearInterval(checkClosed);
|
|
483
|
-
|
|
484
|
-
|
|
571
|
+
const cleanup = async () => {
|
|
572
|
+
if (programManager) await programManager.deactivateAll().catch(() => {});
|
|
573
|
+
if (onExit) await onExit().catch(() => {});
|
|
574
|
+
};
|
|
575
|
+
cleanup().then(resolve);
|
|
485
576
|
}
|
|
486
577
|
}, 100);
|
|
487
578
|
});
|
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,37 @@ 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
|
+
});
|
|
421
|
+
await programManager.activateAll();
|
|
422
|
+
const activated = programManager.getActivatedPrograms();
|
|
423
|
+
if (activated.length > 0) console.log(`Activated ${activated.length} program(s): ${activated.join(", ")}`);
|
|
424
|
+
} catch (err) {
|
|
425
|
+
console.error(`[PM] Program activation error:`, err instanceof Error ? err.message : err);
|
|
426
|
+
}
|
|
377
427
|
const executor = new AFSCommandExecutor(afs, {
|
|
378
428
|
cwd,
|
|
379
429
|
tty: true,
|
|
@@ -388,6 +438,7 @@ async function startRepl(options) {
|
|
|
388
438
|
let closed = false;
|
|
389
439
|
let rl;
|
|
390
440
|
let originalDataHandler = null;
|
|
441
|
+
let activeWebExplorer = null;
|
|
391
442
|
function startReplLoop() {
|
|
392
443
|
const stdin = process.stdin;
|
|
393
444
|
if (stdin._readableState) {
|
|
@@ -447,22 +498,30 @@ async function startRepl(options) {
|
|
|
447
498
|
rl.on("close", () => {
|
|
448
499
|
if (!closed) {
|
|
449
500
|
closed = true;
|
|
501
|
+
if (activeWebExplorer) {
|
|
502
|
+
activeWebExplorer.stop();
|
|
503
|
+
activeWebExplorer = null;
|
|
504
|
+
}
|
|
450
505
|
console.log("\nBye!");
|
|
451
506
|
}
|
|
452
507
|
});
|
|
453
508
|
rl.prompt();
|
|
454
509
|
}
|
|
455
510
|
async function handleExplore(trimmed, ctx$1) {
|
|
511
|
+
const args = parseExploreArgs(trimmed, ctx$1);
|
|
512
|
+
if (args.web) {
|
|
513
|
+
await handleExploreWeb(args, ctx$1);
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
456
516
|
rl.removeAllListeners("line");
|
|
457
517
|
rl.removeAllListeners("close");
|
|
458
518
|
rl.close();
|
|
459
519
|
cleanupStdinAfterBlessed();
|
|
460
520
|
try {
|
|
461
521
|
const { createExplorerScreen } = await import("./explorer/screen.mjs");
|
|
462
|
-
const startPath = parseExplorePath(trimmed, ctx$1);
|
|
463
522
|
await createExplorerScreen({
|
|
464
523
|
afs: ctx$1.afs,
|
|
465
|
-
startPath,
|
|
524
|
+
startPath: args.path,
|
|
466
525
|
version: ctx$1.version,
|
|
467
526
|
onExit: () => {}
|
|
468
527
|
});
|
|
@@ -474,13 +533,45 @@ async function startRepl(options) {
|
|
|
474
533
|
console.log("");
|
|
475
534
|
if (!closed) startReplLoop();
|
|
476
535
|
}
|
|
536
|
+
async function handleExploreWeb(args, ctx$1) {
|
|
537
|
+
if (activeWebExplorer) {
|
|
538
|
+
activeWebExplorer.stop();
|
|
539
|
+
console.log("Stopped previous web explorer.");
|
|
540
|
+
activeWebExplorer = null;
|
|
541
|
+
}
|
|
542
|
+
try {
|
|
543
|
+
const { resolve: resolve$1 } = await import("node:path");
|
|
544
|
+
const { startExplorer } = await import("@aigne/afs-explorer");
|
|
545
|
+
let webRoot;
|
|
546
|
+
try {
|
|
547
|
+
const { createRequire } = await import("node:module");
|
|
548
|
+
webRoot = resolve$1(createRequire(import.meta.url).resolve("@aigne/afs-explorer/package.json"), "..", "web");
|
|
549
|
+
} catch {}
|
|
550
|
+
const info = await startExplorer(ctx$1.afs, {
|
|
551
|
+
port: args.port,
|
|
552
|
+
host: "localhost",
|
|
553
|
+
webRoot,
|
|
554
|
+
open: true
|
|
555
|
+
});
|
|
556
|
+
activeWebExplorer = info;
|
|
557
|
+
console.log(`Web explorer running at ${info.url}`);
|
|
558
|
+
console.log("Type \"explore --web\" again to restart, or continue using REPL.");
|
|
559
|
+
} catch (e) {
|
|
560
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
561
|
+
console.error(`Failed to start web explorer: ${msg}`);
|
|
562
|
+
}
|
|
563
|
+
if (!closed) rl.prompt();
|
|
564
|
+
}
|
|
477
565
|
startReplLoop();
|
|
478
566
|
return new Promise((resolve$1) => {
|
|
479
567
|
const checkClosed = setInterval(() => {
|
|
480
568
|
if (closed) {
|
|
481
569
|
clearInterval(checkClosed);
|
|
482
|
-
|
|
483
|
-
|
|
570
|
+
const cleanup = async () => {
|
|
571
|
+
if (programManager) await programManager.deactivateAll().catch(() => {});
|
|
572
|
+
if (onExit) await onExit().catch(() => {});
|
|
573
|
+
};
|
|
574
|
+
cleanup().then(resolve$1);
|
|
484
575
|
}
|
|
485
576
|
}, 100);
|
|
486
577
|
});
|