@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.
Files changed (154) 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 +64 -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 +59 -310
  9. package/dist/config/afs-loader.mjs.map +1 -1
  10. package/dist/config/credential-helpers.cjs +291 -0
  11. package/dist/config/credential-helpers.d.mts +2 -0
  12. package/dist/config/credential-helpers.mjs +288 -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 +276 -0
  18. package/dist/config/program-install.d.mts +1 -0
  19. package/dist/config/program-install.mjs +273 -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 +207 -0
  26. package/dist/core/commands/daemon.d.mts +2 -0
  27. package/dist/core/commands/daemon.mjs +208 -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 +91 -0
  46. package/dist/core/commands/install.d.mts +2 -0
  47. package/dist/core/commands/install.mjs +92 -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 +21 -0
  96. package/dist/core/formatters/install.d.mts +1 -0
  97. package/dist/core/formatters/install.mjs +19 -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/index.d.mts +2 -1
  103. package/dist/credential/mcp-auth-context.cjs +21 -5
  104. package/dist/credential/mcp-auth-context.mjs +21 -5
  105. package/dist/credential/mcp-auth-context.mjs.map +1 -1
  106. package/dist/credential/resolver.cjs +7 -2
  107. package/dist/credential/resolver.mjs +7 -2
  108. package/dist/credential/resolver.mjs.map +1 -1
  109. package/dist/credential/vault-store.d.mts +1 -0
  110. package/dist/daemon/config-manager.cjs +279 -0
  111. package/dist/daemon/config-manager.mjs +279 -0
  112. package/dist/daemon/config-manager.mjs.map +1 -0
  113. package/dist/daemon/manager.cjs +164 -0
  114. package/dist/daemon/manager.mjs +157 -0
  115. package/dist/daemon/manager.mjs.map +1 -0
  116. package/dist/daemon/server.cjs +220 -0
  117. package/dist/daemon/server.mjs +220 -0
  118. package/dist/daemon/server.mjs.map +1 -0
  119. package/dist/mcp/http-transport.cjs +14 -1
  120. package/dist/mcp/http-transport.mjs +14 -1
  121. package/dist/mcp/http-transport.mjs.map +1 -1
  122. package/dist/mcp/server.cjs +4 -2
  123. package/dist/mcp/server.mjs +4 -2
  124. package/dist/mcp/server.mjs.map +1 -1
  125. package/dist/mcp/tools.cjs +62 -12
  126. package/dist/mcp/tools.mjs +62 -12
  127. package/dist/mcp/tools.mjs.map +1 -1
  128. package/dist/program/daemon-integration.cjs +46 -0
  129. package/dist/program/daemon-integration.mjs +45 -0
  130. package/dist/program/daemon-integration.mjs.map +1 -0
  131. package/dist/program/program-manager.cjs +162 -0
  132. package/dist/program/program-manager.mjs +162 -0
  133. package/dist/program/program-manager.mjs.map +1 -0
  134. package/dist/program/trigger-scanner.cjs +148 -0
  135. package/dist/program/trigger-scanner.mjs +148 -0
  136. package/dist/program/trigger-scanner.mjs.map +1 -0
  137. package/dist/providers/vault/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.cjs +11 -0
  138. package/dist/providers/vault/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.mjs +11 -0
  139. package/dist/providers/vault/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.mjs.map +1 -0
  140. package/dist/providers/vault/dist/encrypted-file.cjs +158 -0
  141. package/dist/providers/vault/dist/encrypted-file.mjs +153 -0
  142. package/dist/providers/vault/dist/encrypted-file.mjs.map +1 -0
  143. package/dist/providers/vault/dist/index.cjs +405 -0
  144. package/dist/providers/vault/dist/index.mjs +400 -0
  145. package/dist/providers/vault/dist/index.mjs.map +1 -0
  146. package/dist/providers/vault/dist/key-resolver.cjs +181 -0
  147. package/dist/providers/vault/dist/key-resolver.mjs +180 -0
  148. package/dist/providers/vault/dist/key-resolver.mjs.map +1 -0
  149. package/dist/repl.cjs +105 -14
  150. package/dist/repl.d.cts.map +1 -1
  151. package/dist/repl.d.mts.map +1 -1
  152. package/dist/repl.mjs +105 -14
  153. package/dist/repl.mjs.map +1 -1
  154. 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
- " 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,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
- if (onExit) onExit().catch(() => {}).then(resolve);
484
- else resolve();
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
  });
@@ -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,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
- if (onExit) onExit().catch(() => {}).then(resolve$1);
483
- else resolve$1();
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
  });