@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.
Files changed (157) hide show
  1. package/dist/cli.cjs +3 -2
  2. package/dist/cli.mjs +3 -2
  3. package/dist/cli.mjs.map +1 -1
  4. package/dist/config/afs-loader.cjs +36 -315
  5. package/dist/config/afs-loader.d.cts.map +1 -1
  6. package/dist/config/afs-loader.d.mts +2 -1
  7. package/dist/config/afs-loader.d.mts.map +1 -1
  8. package/dist/config/afs-loader.mjs +28 -307
  9. package/dist/config/afs-loader.mjs.map +1 -1
  10. package/dist/config/credential-helpers.cjs +303 -0
  11. package/dist/config/credential-helpers.d.mts +2 -0
  12. package/dist/config/credential-helpers.mjs +300 -0
  13. package/dist/config/credential-helpers.mjs.map +1 -0
  14. package/dist/config/loader.cjs +3 -1
  15. package/dist/config/loader.mjs +3 -2
  16. package/dist/config/loader.mjs.map +1 -1
  17. package/dist/config/program-install.cjs +450 -0
  18. package/dist/config/program-install.d.mts +1 -0
  19. package/dist/config/program-install.mjs +444 -0
  20. package/dist/config/program-install.mjs.map +1 -0
  21. package/dist/core/commands/connect.cjs +53 -0
  22. package/dist/core/commands/connect.d.mts +2 -0
  23. package/dist/core/commands/connect.mjs +55 -0
  24. package/dist/core/commands/connect.mjs.map +1 -0
  25. package/dist/core/commands/daemon.cjs +211 -0
  26. package/dist/core/commands/daemon.d.mts +2 -0
  27. package/dist/core/commands/daemon.mjs +212 -0
  28. package/dist/core/commands/daemon.mjs.map +1 -0
  29. package/dist/core/commands/explain.cjs +3 -1
  30. package/dist/core/commands/explain.mjs +3 -1
  31. package/dist/core/commands/explain.mjs.map +1 -1
  32. package/dist/core/commands/explore.cjs +47 -12
  33. package/dist/core/commands/explore.mjs +47 -12
  34. package/dist/core/commands/explore.mjs.map +1 -1
  35. package/dist/core/commands/gen-agent-md.cjs +126 -0
  36. package/dist/core/commands/gen-agent-md.d.mts +2 -0
  37. package/dist/core/commands/gen-agent-md.mjs +125 -0
  38. package/dist/core/commands/gen-agent-md.mjs.map +1 -0
  39. package/dist/core/commands/index.cjs +13 -1
  40. package/dist/core/commands/index.d.cts.map +1 -1
  41. package/dist/core/commands/index.d.mts +6 -0
  42. package/dist/core/commands/index.d.mts.map +1 -1
  43. package/dist/core/commands/index.mjs +13 -1
  44. package/dist/core/commands/index.mjs.map +1 -1
  45. package/dist/core/commands/install.cjs +139 -0
  46. package/dist/core/commands/install.d.mts +2 -0
  47. package/dist/core/commands/install.mjs +140 -0
  48. package/dist/core/commands/install.mjs.map +1 -0
  49. package/dist/core/commands/ls.cjs +14 -2
  50. package/dist/core/commands/ls.d.cts +2 -0
  51. package/dist/core/commands/ls.d.cts.map +1 -1
  52. package/dist/core/commands/ls.d.mts +2 -0
  53. package/dist/core/commands/ls.d.mts.map +1 -1
  54. package/dist/core/commands/ls.mjs +14 -2
  55. package/dist/core/commands/ls.mjs.map +1 -1
  56. package/dist/core/commands/mcp-bridge.cjs +201 -0
  57. package/dist/core/commands/mcp-bridge.d.mts +2 -0
  58. package/dist/core/commands/mcp-bridge.mjs +201 -0
  59. package/dist/core/commands/mcp-bridge.mjs.map +1 -0
  60. package/dist/core/commands/read.cjs +20 -7
  61. package/dist/core/commands/read.d.cts +2 -0
  62. package/dist/core/commands/read.d.cts.map +1 -1
  63. package/dist/core/commands/read.d.mts +2 -0
  64. package/dist/core/commands/read.d.mts.map +1 -1
  65. package/dist/core/commands/read.mjs +20 -7
  66. package/dist/core/commands/read.mjs.map +1 -1
  67. package/dist/core/commands/search.cjs +5 -1
  68. package/dist/core/commands/search.mjs +5 -1
  69. package/dist/core/commands/search.mjs.map +1 -1
  70. package/dist/core/commands/stat.mjs.map +1 -1
  71. package/dist/core/commands/types.d.cts +2 -0
  72. package/dist/core/commands/types.d.cts.map +1 -1
  73. package/dist/core/commands/types.d.mts +2 -0
  74. package/dist/core/commands/types.d.mts.map +1 -1
  75. package/dist/core/commands/types.mjs.map +1 -1
  76. package/dist/core/commands/vault.cjs +289 -0
  77. package/dist/core/commands/vault.d.mts +2 -0
  78. package/dist/core/commands/vault.mjs +289 -0
  79. package/dist/core/commands/vault.mjs.map +1 -0
  80. package/dist/core/commands/write.cjs +19 -6
  81. package/dist/core/commands/write.d.cts +2 -1
  82. package/dist/core/commands/write.d.cts.map +1 -1
  83. package/dist/core/commands/write.d.mts +2 -1
  84. package/dist/core/commands/write.d.mts.map +1 -1
  85. package/dist/core/commands/write.mjs +19 -6
  86. package/dist/core/commands/write.mjs.map +1 -1
  87. package/dist/core/executor/index.cjs +95 -19
  88. package/dist/core/executor/index.d.cts +4 -0
  89. package/dist/core/executor/index.d.cts.map +1 -1
  90. package/dist/core/executor/index.d.mts +4 -0
  91. package/dist/core/executor/index.d.mts.map +1 -1
  92. package/dist/core/executor/index.mjs +95 -19
  93. package/dist/core/executor/index.mjs.map +1 -1
  94. package/dist/core/formatters/index.d.mts +1 -0
  95. package/dist/core/formatters/install.cjs +40 -0
  96. package/dist/core/formatters/install.d.mts +1 -0
  97. package/dist/core/formatters/install.mjs +36 -0
  98. package/dist/core/formatters/install.mjs.map +1 -0
  99. package/dist/core/formatters/vault.cjs +36 -0
  100. package/dist/core/formatters/vault.mjs +32 -0
  101. package/dist/core/formatters/vault.mjs.map +1 -0
  102. package/dist/credential/auth-server.cjs +22 -4
  103. package/dist/credential/auth-server.mjs +22 -4
  104. package/dist/credential/auth-server.mjs.map +1 -1
  105. package/dist/credential/index.d.mts +2 -1
  106. package/dist/credential/mcp-auth-context.cjs +21 -5
  107. package/dist/credential/mcp-auth-context.mjs +21 -5
  108. package/dist/credential/mcp-auth-context.mjs.map +1 -1
  109. package/dist/credential/resolver.cjs +11 -3
  110. package/dist/credential/resolver.mjs +11 -3
  111. package/dist/credential/resolver.mjs.map +1 -1
  112. package/dist/credential/vault-store.d.mts +1 -0
  113. package/dist/daemon/config-manager.cjs +279 -0
  114. package/dist/daemon/config-manager.mjs +279 -0
  115. package/dist/daemon/config-manager.mjs.map +1 -0
  116. package/dist/daemon/manager.cjs +164 -0
  117. package/dist/daemon/manager.mjs +157 -0
  118. package/dist/daemon/manager.mjs.map +1 -0
  119. package/dist/daemon/server.cjs +220 -0
  120. package/dist/daemon/server.mjs +220 -0
  121. package/dist/daemon/server.mjs.map +1 -0
  122. package/dist/mcp/http-transport.cjs +14 -1
  123. package/dist/mcp/http-transport.mjs +14 -1
  124. package/dist/mcp/http-transport.mjs.map +1 -1
  125. package/dist/mcp/server.cjs +4 -2
  126. package/dist/mcp/server.mjs +4 -2
  127. package/dist/mcp/server.mjs.map +1 -1
  128. package/dist/mcp/tools.cjs +62 -12
  129. package/dist/mcp/tools.mjs +62 -12
  130. package/dist/mcp/tools.mjs.map +1 -1
  131. package/dist/program/daemon-integration.cjs +46 -0
  132. package/dist/program/daemon-integration.mjs +45 -0
  133. package/dist/program/daemon-integration.mjs.map +1 -0
  134. package/dist/program/program-manager.cjs +166 -0
  135. package/dist/program/program-manager.mjs +166 -0
  136. package/dist/program/program-manager.mjs.map +1 -0
  137. package/dist/program/trigger-scanner.cjs +148 -0
  138. package/dist/program/trigger-scanner.mjs +148 -0
  139. package/dist/program/trigger-scanner.mjs.map +1 -0
  140. package/dist/providers/vault/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.cjs +11 -0
  141. package/dist/providers/vault/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.mjs +11 -0
  142. package/dist/providers/vault/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.mjs.map +1 -0
  143. package/dist/providers/vault/dist/encrypted-file.cjs +158 -0
  144. package/dist/providers/vault/dist/encrypted-file.mjs +153 -0
  145. package/dist/providers/vault/dist/encrypted-file.mjs.map +1 -0
  146. package/dist/providers/vault/dist/index.cjs +405 -0
  147. package/dist/providers/vault/dist/index.mjs +400 -0
  148. package/dist/providers/vault/dist/index.mjs.map +1 -0
  149. package/dist/providers/vault/dist/key-resolver.cjs +181 -0
  150. package/dist/providers/vault/dist/key-resolver.mjs +180 -0
  151. package/dist/providers/vault/dist/key-resolver.mjs.map +1 -0
  152. package/dist/repl.cjs +109 -14
  153. package/dist/repl.d.cts.map +1 -1
  154. package/dist/repl.d.mts.map +1 -1
  155. package/dist/repl.mjs +109 -14
  156. package/dist/repl.mjs.map +1 -1
  157. 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
- " mount Mount management",
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 parseExplorePath(cmd, ctx) {
231
- const path = cmd.replace(/^afs\s+/, "").trim().split(/\s+/)[1];
232
- if (!path) {
233
- if (ctx.currentNamespace) return `$afs:${ctx.currentNamespace}${ctx.currentPath}`;
234
- return ctx.currentPath;
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
- if (!path.startsWith("/") && !path.startsWith("@") && !path.startsWith("$afs")) {
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
- if (ctx.currentNamespace) return `$afs:${ctx.currentNamespace}${resolved}`;
239
- return resolved;
254
+ path = ctx.currentNamespace ? `$afs:${ctx.currentNamespace}${resolved}` : resolved;
240
255
  }
241
- return path;
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
- if (onExit) onExit().catch(() => {}).then(resolve);
484
- else resolve();
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
  });
@@ -1 +1 @@
1
- {"version":3,"file":"repl.d.cts","names":[],"sources":["../src/repl.ts"],"mappings":";;;iBAiiBsB,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"}
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"}
@@ -1 +1 @@
1
- {"version":3,"file":"repl.d.mts","names":[],"sources":["../src/repl.ts"],"mappings":";;;;iBAiiBsB,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"}
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
- " mount Mount management",
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 parseExplorePath(cmd, ctx) {
230
- const path = cmd.replace(/^afs\s+/, "").trim().split(/\s+/)[1];
231
- if (!path) {
232
- if (ctx.currentNamespace) return `$afs:${ctx.currentNamespace}${ctx.currentPath}`;
233
- return ctx.currentPath;
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
- if (!path.startsWith("/") && !path.startsWith("@") && !path.startsWith("$afs")) {
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
- if (ctx.currentNamespace) return `$afs:${ctx.currentNamespace}${resolved}`;
238
- return resolved;
253
+ path = ctx.currentNamespace ? `$afs:${ctx.currentNamespace}${resolved}` : resolved;
239
254
  }
240
- return path;
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
- if (onExit) onExit().catch(() => {}).then(resolve$1);
483
- else resolve$1();
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
  });