@geminixiang/mama 0.1.10 → 0.2.0-beta.1

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 (151) hide show
  1. package/README.md +80 -23
  2. package/dist/adapter.d.ts +11 -9
  3. package/dist/adapter.d.ts.map +1 -1
  4. package/dist/adapter.js.map +1 -1
  5. package/dist/adapters/discord/bot.d.ts +2 -2
  6. package/dist/adapters/discord/bot.d.ts.map +1 -1
  7. package/dist/adapters/discord/bot.js +33 -21
  8. package/dist/adapters/discord/bot.js.map +1 -1
  9. package/dist/adapters/discord/context.d.ts.map +1 -1
  10. package/dist/adapters/discord/context.js +20 -13
  11. package/dist/adapters/discord/context.js.map +1 -1
  12. package/dist/adapters/slack/bot.d.ts +13 -4
  13. package/dist/adapters/slack/bot.d.ts.map +1 -1
  14. package/dist/adapters/slack/bot.js +98 -43
  15. package/dist/adapters/slack/bot.js.map +1 -1
  16. package/dist/adapters/slack/context.d.ts.map +1 -1
  17. package/dist/adapters/slack/context.js +25 -20
  18. package/dist/adapters/slack/context.js.map +1 -1
  19. package/dist/adapters/telegram/bot.d.ts +4 -2
  20. package/dist/adapters/telegram/bot.d.ts.map +1 -1
  21. package/dist/adapters/telegram/bot.js +143 -58
  22. package/dist/adapters/telegram/bot.js.map +1 -1
  23. package/dist/adapters/telegram/context.d.ts +1 -1
  24. package/dist/adapters/telegram/context.d.ts.map +1 -1
  25. package/dist/adapters/telegram/context.js +124 -29
  26. package/dist/adapters/telegram/context.js.map +1 -1
  27. package/dist/agent.d.ts +7 -4
  28. package/dist/agent.d.ts.map +1 -1
  29. package/dist/agent.js +303 -89
  30. package/dist/agent.js.map +1 -1
  31. package/dist/bindings.d.ts +63 -0
  32. package/dist/bindings.d.ts.map +1 -0
  33. package/dist/bindings.js +94 -0
  34. package/dist/bindings.js.map +1 -0
  35. package/dist/config.d.ts +34 -4
  36. package/dist/config.d.ts.map +1 -1
  37. package/dist/config.js +98 -38
  38. package/dist/config.js.map +1 -1
  39. package/dist/context.d.ts +8 -6
  40. package/dist/context.d.ts.map +1 -1
  41. package/dist/context.js +23 -14
  42. package/dist/context.js.map +1 -1
  43. package/dist/events.d.ts +4 -0
  44. package/dist/events.d.ts.map +1 -1
  45. package/dist/events.js +20 -5
  46. package/dist/events.js.map +1 -1
  47. package/dist/execution-resolver.d.ts +20 -0
  48. package/dist/execution-resolver.d.ts.map +1 -0
  49. package/dist/execution-resolver.js +51 -0
  50. package/dist/execution-resolver.js.map +1 -0
  51. package/dist/instrument.d.ts +2 -0
  52. package/dist/instrument.d.ts.map +1 -0
  53. package/dist/instrument.js +14 -0
  54. package/dist/instrument.js.map +1 -0
  55. package/dist/link-server.d.ts +16 -0
  56. package/dist/link-server.d.ts.map +1 -0
  57. package/dist/link-server.js +839 -0
  58. package/dist/link-server.js.map +1 -0
  59. package/dist/link-token.d.ts +32 -0
  60. package/dist/link-token.d.ts.map +1 -0
  61. package/dist/link-token.js +68 -0
  62. package/dist/link-token.js.map +1 -0
  63. package/dist/log.d.ts +3 -2
  64. package/dist/log.d.ts.map +1 -1
  65. package/dist/log.js +10 -9
  66. package/dist/log.js.map +1 -1
  67. package/dist/login.d.ts +29 -0
  68. package/dist/login.d.ts.map +1 -0
  69. package/dist/login.js +164 -0
  70. package/dist/login.js.map +1 -0
  71. package/dist/main.d.ts +1 -1
  72. package/dist/main.d.ts.map +1 -1
  73. package/dist/main.js +322 -82
  74. package/dist/main.js.map +1 -1
  75. package/dist/provisioner.d.ts +93 -0
  76. package/dist/provisioner.d.ts.map +1 -0
  77. package/dist/provisioner.js +336 -0
  78. package/dist/provisioner.js.map +1 -0
  79. package/dist/sandbox/container.d.ts +15 -0
  80. package/dist/sandbox/container.d.ts.map +1 -0
  81. package/dist/sandbox/container.js +122 -0
  82. package/dist/sandbox/container.js.map +1 -0
  83. package/dist/sandbox/errors.d.ts +6 -0
  84. package/dist/sandbox/errors.d.ts.map +1 -0
  85. package/dist/sandbox/errors.js +11 -0
  86. package/dist/sandbox/errors.js.map +1 -0
  87. package/dist/sandbox/firecracker.d.ts +16 -0
  88. package/dist/sandbox/firecracker.d.ts.map +1 -0
  89. package/dist/sandbox/firecracker.js +206 -0
  90. package/dist/sandbox/firecracker.js.map +1 -0
  91. package/dist/sandbox/host.d.ts +12 -0
  92. package/dist/sandbox/host.d.ts.map +1 -0
  93. package/dist/sandbox/host.js +89 -0
  94. package/dist/sandbox/host.js.map +1 -0
  95. package/dist/sandbox/image.d.ts +5 -0
  96. package/dist/sandbox/image.d.ts.map +1 -0
  97. package/dist/sandbox/image.js +30 -0
  98. package/dist/sandbox/image.js.map +1 -0
  99. package/dist/sandbox/index.d.ts +20 -0
  100. package/dist/sandbox/index.d.ts.map +1 -0
  101. package/dist/sandbox/index.js +51 -0
  102. package/dist/sandbox/index.js.map +1 -0
  103. package/dist/sandbox/types.d.ts +51 -0
  104. package/dist/sandbox/types.d.ts.map +1 -0
  105. package/dist/sandbox/types.js +2 -0
  106. package/dist/sandbox/types.js.map +1 -0
  107. package/dist/sandbox/utils.d.ts +4 -0
  108. package/dist/sandbox/utils.d.ts.map +1 -0
  109. package/dist/sandbox/utils.js +51 -0
  110. package/dist/sandbox/utils.js.map +1 -0
  111. package/dist/sandbox.d.ts +1 -39
  112. package/dist/sandbox.d.ts.map +1 -1
  113. package/dist/sandbox.js +1 -286
  114. package/dist/sandbox.js.map +1 -1
  115. package/dist/sentry.d.ts +31 -0
  116. package/dist/sentry.d.ts.map +1 -0
  117. package/dist/sentry.js +205 -0
  118. package/dist/sentry.js.map +1 -0
  119. package/dist/session-store.d.ts +72 -0
  120. package/dist/session-store.d.ts.map +1 -0
  121. package/dist/session-store.js +186 -0
  122. package/dist/session-store.js.map +1 -0
  123. package/dist/store.d.ts +1 -1
  124. package/dist/store.d.ts.map +1 -1
  125. package/dist/store.js +8 -8
  126. package/dist/store.js.map +1 -1
  127. package/dist/tools/event.d.ts +21 -0
  128. package/dist/tools/event.d.ts.map +1 -0
  129. package/dist/tools/event.js +103 -0
  130. package/dist/tools/event.js.map +1 -0
  131. package/dist/tools/index.d.ts +6 -1
  132. package/dist/tools/index.d.ts.map +1 -1
  133. package/dist/tools/index.js +5 -1
  134. package/dist/tools/index.js.map +1 -1
  135. package/dist/ui-copy.d.ts +11 -0
  136. package/dist/ui-copy.d.ts.map +1 -0
  137. package/dist/ui-copy.js +33 -0
  138. package/dist/ui-copy.js.map +1 -0
  139. package/dist/vault-routing.d.ts +10 -0
  140. package/dist/vault-routing.d.ts.map +1 -0
  141. package/dist/vault-routing.js +58 -0
  142. package/dist/vault-routing.js.map +1 -0
  143. package/dist/vault.d.ts +106 -0
  144. package/dist/vault.d.ts.map +1 -0
  145. package/dist/vault.js +389 -0
  146. package/dist/vault.js.map +1 -0
  147. package/dist/vault.test.d.ts +2 -0
  148. package/dist/vault.test.d.ts.map +1 -0
  149. package/dist/vault.test.js +67 -0
  150. package/dist/vault.test.js.map +1 -0
  151. package/package.json +13 -11
package/dist/vault.js ADDED
@@ -0,0 +1,389 @@
1
+ import { chmodSync, closeSync, constants as fsConstants, existsSync, mkdirSync, openSync, readFileSync, renameSync, unlinkSync, writeSync, } from "fs";
2
+ import { randomBytes } from "crypto";
3
+ import { basename, dirname, isAbsolute, join, normalize, sep } from "path";
4
+ const PRIVATE_DIR_MODE = 0o700;
5
+ const PRIVATE_FILE_MODE = 0o600;
6
+ // ── parseEnvFile ───────────────────────────────────────────────────────────────
7
+ /**
8
+ * Parse a KEY=VALUE env file. Supports:
9
+ * - Lines starting with # are comments
10
+ * - Empty lines are skipped
11
+ * - Values can be quoted with single or double quotes (quotes are stripped)
12
+ * - No variable expansion
13
+ * - The value is everything after the first `=` to end of line (no inline comments)
14
+ */
15
+ export function parseEnvFile(content) {
16
+ const env = {};
17
+ const lines = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
18
+ for (const line of lines) {
19
+ const trimmed = line.trim();
20
+ if (!trimmed || trimmed.startsWith("#"))
21
+ continue;
22
+ const eqIndex = trimmed.indexOf("=");
23
+ if (eqIndex === -1)
24
+ continue;
25
+ const key = trimmed.slice(0, eqIndex).trim();
26
+ if (!key)
27
+ continue;
28
+ let value = trimmed.slice(eqIndex + 1);
29
+ // Strip matching quotes
30
+ if ((value.startsWith('"') && value.endsWith('"')) ||
31
+ (value.startsWith("'") && value.endsWith("'"))) {
32
+ value = value.slice(1, -1);
33
+ }
34
+ env[key] = value;
35
+ }
36
+ return env;
37
+ }
38
+ // ── FileVaultManager ───────────────────────────────────────────────────────────
39
+ export class FileVaultManager {
40
+ constructor(stateDir) {
41
+ this.config = null;
42
+ this.vaultsDir = join(stateDir, "vaults");
43
+ this.configPath = join(this.vaultsDir, "vault.json");
44
+ this.reload();
45
+ }
46
+ reload() {
47
+ if (!existsSync(this.configPath)) {
48
+ this.config = null;
49
+ return;
50
+ }
51
+ try {
52
+ const raw = readFileSync(this.configPath, "utf-8");
53
+ const parsed = JSON.parse(raw);
54
+ if (!parsed ||
55
+ typeof parsed !== "object" ||
56
+ !parsed.vaults ||
57
+ typeof parsed.vaults !== "object") {
58
+ console.error(`vault: malformed vault.json — expected { vaults: { ... } }`);
59
+ this.config = null;
60
+ return;
61
+ }
62
+ this.config = parsed;
63
+ this.warnUnsupportedSandboxTypes();
64
+ }
65
+ catch (err) {
66
+ console.error(`vault: failed to read ${this.configPath}:`, err);
67
+ this.config = null;
68
+ }
69
+ }
70
+ /** Warn for legacy or insecure vault sandbox overrides that are no longer allowed. */
71
+ warnUnsupportedSandboxTypes() {
72
+ if (!this.config)
73
+ return;
74
+ for (const [key, entry] of Object.entries(this.config.vaults)) {
75
+ if (entry.sandbox?.type === "host") {
76
+ console.error(`vault: "${key}" uses sandbox.type=host, which is blocked for credential isolation. ` +
77
+ "Use sandbox.type=image or sandbox.type=firecracker.");
78
+ }
79
+ if (entry.sandbox?.type === "container" || entry.sandbox?.type === "docker") {
80
+ console.error(`vault: "${key}" uses sandbox.type=${entry.sandbox.type}, which is blocked for credential isolation. ` +
81
+ "Use sandbox.type=image for per-user containers or sandbox.type=firecracker.");
82
+ }
83
+ }
84
+ }
85
+ isEnabled() {
86
+ return this.config !== null;
87
+ }
88
+ hasEntry(key) {
89
+ return !!this.config?.vaults[key];
90
+ }
91
+ resolve(userId) {
92
+ const entry = this.config?.vaults[userId];
93
+ if (!entry)
94
+ return undefined;
95
+ return this.buildResolved(userId, entry);
96
+ }
97
+ getSandboxConfig(userId, baseConfig) {
98
+ const vault = this.resolve(userId);
99
+ if (!vault?.sandboxOverride)
100
+ return baseConfig;
101
+ const override = vault.sandboxOverride;
102
+ if (override.type === "image") {
103
+ if (baseConfig.type !== "image") {
104
+ throw new Error(`vault "${userId}" sets sandbox.type=image, but base sandbox is "${baseConfig.type}". ` +
105
+ "Use --sandbox=image:<image> to enable per-user managed containers.");
106
+ }
107
+ const container = override.container || `mama-sandbox-${userId}`;
108
+ return { type: "container", container };
109
+ }
110
+ if (override.type === "firecracker") {
111
+ if (!override.vmId)
112
+ return baseConfig;
113
+ if (baseConfig.type !== "firecracker") {
114
+ throw new Error(`vault "${userId}" sets sandbox.type=firecracker, but base sandbox is "${baseConfig.type}". ` +
115
+ "Use --sandbox=firecracker:<vm-id>:<host-path> so /workspace stays mapped to the real workspace.");
116
+ }
117
+ return {
118
+ type: "firecracker",
119
+ vmId: override.vmId,
120
+ hostPath: baseConfig.hostPath,
121
+ sshUser: override.sshUser,
122
+ sshPort: override.sshPort,
123
+ };
124
+ }
125
+ if (override.type === "host") {
126
+ throw new Error(`vault "${userId}" uses sandbox.type=host, which is blocked for credential isolation. ` +
127
+ "Use sandbox.type=image or sandbox.type=firecracker.");
128
+ }
129
+ if (override.type === "container" || override.type === "docker") {
130
+ throw new Error(`vault "${userId}" uses sandbox.type=${override.type}, which is blocked for credential isolation. ` +
131
+ "Use sandbox.type=image for per-user containers or sandbox.type=firecracker.");
132
+ }
133
+ // No type override — return base config unchanged
134
+ return baseConfig;
135
+ }
136
+ list() {
137
+ if (!this.config)
138
+ return [];
139
+ const results = [];
140
+ for (const [key, entry] of Object.entries(this.config.vaults)) {
141
+ results.push(this.buildResolved(key, entry));
142
+ }
143
+ return results;
144
+ }
145
+ addEntry(key, entry) {
146
+ if (!this.config) {
147
+ this.config = { vaults: {} };
148
+ }
149
+ // Idempotent: skip if already exists
150
+ if (this.config.vaults[key])
151
+ return;
152
+ this.config.vaults[key] = entry;
153
+ this.persistConfig();
154
+ }
155
+ ensureImageSandboxEntry(key, entry) {
156
+ if (entry.sandbox?.type !== "image") {
157
+ throw new Error(`vault: ensureImageSandboxEntry requires sandbox.type=image for "${key}"`);
158
+ }
159
+ if (!this.config) {
160
+ this.config = { vaults: {} };
161
+ }
162
+ const existing = this.config.vaults[key];
163
+ if (!existing) {
164
+ this.config.vaults[key] = entry;
165
+ this.persistConfig();
166
+ return;
167
+ }
168
+ let nextEntry = existing;
169
+ let changed = false;
170
+ if (!existing.platform && entry.platform) {
171
+ nextEntry = { ...nextEntry, platform: entry.platform };
172
+ changed = true;
173
+ }
174
+ const existingSandbox = existing.sandbox;
175
+ if (!existingSandbox?.type) {
176
+ nextEntry = { ...nextEntry, sandbox: entry.sandbox };
177
+ changed = true;
178
+ }
179
+ else if (existingSandbox.type === "image" &&
180
+ !existingSandbox.container &&
181
+ entry.sandbox.container) {
182
+ nextEntry = {
183
+ ...nextEntry,
184
+ sandbox: { ...existingSandbox, container: entry.sandbox.container },
185
+ };
186
+ changed = true;
187
+ }
188
+ if (!changed) {
189
+ return;
190
+ }
191
+ this.config.vaults[key] = nextEntry;
192
+ this.persistConfig();
193
+ }
194
+ upsertEnv(key, env) {
195
+ const dir = join(this.vaultsDir, key);
196
+ const envPath = join(dir, "env");
197
+ ensurePrivateDir(this.vaultsDir);
198
+ ensurePrivateDir(dir);
199
+ const existing = existsSync(envPath)
200
+ ? parseEnvFile(readFileSync(envPath, "utf-8"))
201
+ : {};
202
+ const merged = { ...existing, ...env };
203
+ const content = Object.entries(merged)
204
+ .sort(([left], [right]) => left.localeCompare(right))
205
+ .map(([envKey, value]) => `${envKey}=${value}`)
206
+ .join("\n") + "\n";
207
+ atomicWritePrivateFile(envPath, content);
208
+ }
209
+ upsertFile(key, relativePath, content, targetPath) {
210
+ const normalizedPath = normalizeVaultRelativePath(relativePath);
211
+ const normalizedTarget = normalizeVaultTargetPath(targetPath);
212
+ if (!normalizedPath || (targetPath !== undefined && !normalizedTarget)) {
213
+ throw new Error(`vault: invalid relative secret file path for "${key}": ${relativePath}`);
214
+ }
215
+ const dir = join(this.vaultsDir, key);
216
+ const filePath = join(dir, normalizedPath);
217
+ ensurePrivateDir(this.vaultsDir);
218
+ ensurePrivateDir(dir);
219
+ const parentDir = dirname(filePath);
220
+ if (parentDir !== dir)
221
+ ensurePrivateDir(parentDir);
222
+ atomicWritePrivateFile(filePath, content);
223
+ this.ensureMountEntry(key, normalizedPath, normalizedTarget);
224
+ }
225
+ // ── private ────────────────────────────────────────────────────────────────
226
+ persistConfig() {
227
+ ensurePrivateDir(this.vaultsDir);
228
+ // Preserve concurrent external edits: pull in any entries that appear on
229
+ // disk but not in our in-memory view, so a background edit (e.g. another
230
+ // admin adding a user) is not silently dropped by the next upsert here.
231
+ // Individual field edits still follow last-writer-wins per key.
232
+ const onDisk = this.readConfigFromDisk();
233
+ if (onDisk && this.config) {
234
+ for (const [key, entry] of Object.entries(onDisk.vaults)) {
235
+ if (!(key in this.config.vaults)) {
236
+ this.config.vaults[key] = entry;
237
+ }
238
+ }
239
+ }
240
+ atomicWritePrivateFile(this.configPath, JSON.stringify(this.config, null, 2) + "\n");
241
+ }
242
+ readConfigFromDisk() {
243
+ if (!existsSync(this.configPath))
244
+ return null;
245
+ try {
246
+ const parsed = JSON.parse(readFileSync(this.configPath, "utf-8"));
247
+ if (!parsed ||
248
+ typeof parsed !== "object" ||
249
+ !parsed.vaults ||
250
+ typeof parsed.vaults !== "object") {
251
+ return null;
252
+ }
253
+ return parsed;
254
+ }
255
+ catch {
256
+ return null;
257
+ }
258
+ }
259
+ ensureMountEntry(key, relativePath, targetPath) {
260
+ if (!this.config?.vaults[key]) {
261
+ throw new Error(`vault: cannot add mount "${relativePath}" for missing entry "${key}"`);
262
+ }
263
+ const existing = this.config.vaults[key];
264
+ const mounts = existing.mounts ?? [];
265
+ if (mounts.some((mount) => typeof mount === "string"
266
+ ? mount === relativePath && !targetPath
267
+ : mount.source === relativePath && mount.target === targetPath)) {
268
+ return;
269
+ }
270
+ this.config.vaults[key] = {
271
+ ...existing,
272
+ mounts: [...mounts, targetPath ? { source: relativePath, target: targetPath } : relativePath],
273
+ };
274
+ this.persistConfig();
275
+ }
276
+ buildResolved(key, entry) {
277
+ const dir = join(this.vaultsDir, key);
278
+ const mounts = (entry.mounts ?? [])
279
+ .map((mount) => this.resolveMountEntry(dir, mount))
280
+ .filter((mount) => mount !== undefined);
281
+ let env = {};
282
+ const envPath = join(dir, "env");
283
+ if (entry.envFile !== false && existsSync(envPath)) {
284
+ try {
285
+ env = parseEnvFile(readFileSync(envPath, "utf-8"));
286
+ }
287
+ catch (err) {
288
+ console.error(`vault: failed to parse env file for "${key}":`, err);
289
+ }
290
+ }
291
+ return {
292
+ userId: key,
293
+ displayName: entry.displayName,
294
+ dir,
295
+ mounts,
296
+ env,
297
+ sandboxOverride: entry.sandbox,
298
+ };
299
+ }
300
+ resolveMountEntry(dir, mount) {
301
+ if (typeof mount === "string") {
302
+ const normalizedSource = normalizeVaultRelativePath(mount);
303
+ if (!normalizedSource)
304
+ return undefined;
305
+ return {
306
+ source: join(dir, normalizedSource),
307
+ target: defaultVaultTargetPath(normalizedSource),
308
+ };
309
+ }
310
+ if (!mount || typeof mount !== "object")
311
+ return undefined;
312
+ const normalizedSource = normalizeVaultRelativePath(mount.source);
313
+ if (!normalizedSource)
314
+ return undefined;
315
+ const normalizedTarget = normalizeVaultTargetPath(mount.target);
316
+ return {
317
+ source: join(dir, normalizedSource),
318
+ target: normalizedTarget ?? defaultVaultTargetPath(normalizedSource),
319
+ };
320
+ }
321
+ }
322
+ function ensurePrivateDir(path) {
323
+ mkdirSync(path, { recursive: true, mode: PRIVATE_DIR_MODE });
324
+ chmodSync(path, PRIVATE_DIR_MODE);
325
+ }
326
+ /**
327
+ * Write `content` to `targetPath` with mode 0600, even when `targetPath`
328
+ * already exists. Uses O_CREAT|O_EXCL on a temp sibling (so the kernel
329
+ * guarantees permissions at creation, not after a racy chmod) and then
330
+ * rename(2) into place for atomicity. Readers never see a torn write.
331
+ */
332
+ function atomicWritePrivateFile(targetPath, content) {
333
+ const dir = dirname(targetPath);
334
+ const tmpPath = join(dir, `.${basename(targetPath)}.${process.pid}.${randomBytes(8).toString("hex")}.tmp`);
335
+ const fd = openSync(tmpPath, fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL, PRIVATE_FILE_MODE);
336
+ try {
337
+ writeSync(fd, content);
338
+ }
339
+ catch (err) {
340
+ try {
341
+ unlinkSync(tmpPath);
342
+ }
343
+ catch {
344
+ // ignore — original error is more informative
345
+ }
346
+ throw err;
347
+ }
348
+ finally {
349
+ closeSync(fd);
350
+ }
351
+ try {
352
+ renameSync(tmpPath, targetPath);
353
+ }
354
+ catch (err) {
355
+ try {
356
+ unlinkSync(tmpPath);
357
+ }
358
+ catch {
359
+ // ignore
360
+ }
361
+ throw err;
362
+ }
363
+ }
364
+ function normalizeVaultRelativePath(relativePath) {
365
+ const trimmed = relativePath.trim();
366
+ if (!trimmed || isAbsolute(trimmed))
367
+ return undefined;
368
+ const normalized = normalize(trimmed).split(sep).join("/");
369
+ if (!normalized || normalized === "." || normalized === ".." || normalized.startsWith("../")) {
370
+ return undefined;
371
+ }
372
+ return normalized;
373
+ }
374
+ function normalizeVaultTargetPath(targetPath) {
375
+ if (targetPath === undefined) {
376
+ return undefined;
377
+ }
378
+ const trimmed = targetPath.trim();
379
+ if (!trimmed || !trimmed.startsWith("/")) {
380
+ return undefined;
381
+ }
382
+ const normalized = normalize(trimmed).split(sep).join("/");
383
+ return normalized.startsWith("/") ? normalized : undefined;
384
+ }
385
+ export function defaultVaultTargetPath(relativePath) {
386
+ const normalized = normalizeVaultRelativePath(relativePath) ?? relativePath.replace(/^\/+/, "");
387
+ return `/root/${normalized}`;
388
+ }
389
+ //# sourceMappingURL=vault.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vault.js","sourceRoot":"","sources":["../src/vault.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EACT,SAAS,EACT,SAAS,IAAI,WAAW,EACxB,UAAU,EACV,SAAS,EACT,QAAQ,EACR,YAAY,EACZ,UAAU,EACV,UAAU,EACV,SAAS,GACV,MAAM,IAAI,CAAC;AACZ,OAAO,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,MAAM,CAAC;AAG3E,MAAM,gBAAgB,GAAG,KAAK,CAAC;AAC/B,MAAM,iBAAiB,GAAG,KAAK,CAAC;AAiFhC,kFAAkF;AAElF;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAC,OAAe;IAC1C,MAAM,GAAG,GAA2B,EAAE,CAAC;IACvC,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAE9E,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAS;QAElD,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACrC,IAAI,OAAO,KAAK,CAAC,CAAC;YAAE,SAAS;QAE7B,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;QAC7C,IAAI,CAAC,GAAG;YAAE,SAAS;QAEnB,IAAI,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC;QAEvC,wBAAwB;QACxB,IACE,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;YAC9C,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,EAC9C,CAAC;YACD,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAC7B,CAAC;QAED,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;IACnB,CAAC;IAED,OAAO,GAAG,CAAC;AACb,CAAC;AAED,kFAAkF;AAElF,MAAM,OAAO,gBAAgB;IAK3B,YAAY,QAAgB;QAJpB,WAAM,GAAuB,IAAI,CAAC;QAKxC,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QAC1C,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;QACrD,IAAI,CAAC,MAAM,EAAE,CAAC;IAChB,CAAC;IAED,MAAM;QACJ,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;YACjC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;YACnB,OAAO;QACT,CAAC;QAED,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;YACnD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YAE/B,IACE,CAAC,MAAM;gBACP,OAAO,MAAM,KAAK,QAAQ;gBAC1B,CAAC,MAAM,CAAC,MAAM;gBACd,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ,EACjC,CAAC;gBACD,OAAO,CAAC,KAAK,CAAC,4DAA4D,CAAC,CAAC;gBAC5E,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;gBACnB,OAAO;YACT,CAAC;YAED,IAAI,CAAC,MAAM,GAAG,MAAqB,CAAC;YACpC,IAAI,CAAC,2BAA2B,EAAE,CAAC;QACrC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CAAC,yBAAyB,IAAI,CAAC,UAAU,GAAG,EAAE,GAAG,CAAC,CAAC;YAChE,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QACrB,CAAC;IACH,CAAC;IAED,sFAAsF;IAC9E,2BAA2B;QACjC,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,OAAO;QACzB,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;YAC9D,IAAI,KAAK,CAAC,OAAO,EAAE,IAAI,KAAK,MAAM,EAAE,CAAC;gBACnC,OAAO,CAAC,KAAK,CACX,WAAW,GAAG,uEAAuE;oBACnF,qDAAqD,CACxD,CAAC;YACJ,CAAC;YACD,IAAI,KAAK,CAAC,OAAO,EAAE,IAAI,KAAK,WAAW,IAAI,KAAK,CAAC,OAAO,EAAE,IAAI,KAAK,QAAQ,EAAE,CAAC;gBAC5E,OAAO,CAAC,KAAK,CACX,WAAW,GAAG,uBAAuB,KAAK,CAAC,OAAO,CAAC,IAAI,+CAA+C;oBACpG,6EAA6E,CAChF,CAAC;YACJ,CAAC;QACH,CAAC;IACH,CAAC;IAED,SAAS;QACP,OAAO,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC;IAC9B,CAAC;IAED,QAAQ,CAAC,GAAW;QAClB,OAAO,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;IACpC,CAAC;IAED,OAAO,CAAC,MAAc;QACpB,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;QAC1C,IAAI,CAAC,KAAK;YAAE,OAAO,SAAS,CAAC;QAC7B,OAAO,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAC3C,CAAC;IAED,gBAAgB,CAAC,MAAc,EAAE,UAAyB;QACxD,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACnC,IAAI,CAAC,KAAK,EAAE,eAAe;YAAE,OAAO,UAAU,CAAC;QAE/C,MAAM,QAAQ,GAAG,KAAK,CAAC,eAAe,CAAC;QAEvC,IAAI,QAAQ,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YAC9B,IAAI,UAAU,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;gBAChC,MAAM,IAAI,KAAK,CACb,UAAU,MAAM,mDAAmD,UAAU,CAAC,IAAI,KAAK;oBACrF,oEAAoE,CACvE,CAAC;YACJ,CAAC;YACD,MAAM,SAAS,GAAG,QAAQ,CAAC,SAAS,IAAI,gBAAgB,MAAM,EAAE,CAAC;YACjE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,SAAS,EAAE,CAAC;QAC1C,CAAC;QAED,IAAI,QAAQ,CAAC,IAAI,KAAK,aAAa,EAAE,CAAC;YACpC,IAAI,CAAC,QAAQ,CAAC,IAAI;gBAAE,OAAO,UAAU,CAAC;YACtC,IAAI,UAAU,CAAC,IAAI,KAAK,aAAa,EAAE,CAAC;gBACtC,MAAM,IAAI,KAAK,CACb,UAAU,MAAM,yDAAyD,UAAU,CAAC,IAAI,KAAK;oBAC3F,iGAAiG,CACpG,CAAC;YACJ,CAAC;YACD,OAAO;gBACL,IAAI,EAAE,aAAa;gBACnB,IAAI,EAAE,QAAQ,CAAC,IAAI;gBACnB,QAAQ,EAAE,UAAU,CAAC,QAAQ;gBAC7B,OAAO,EAAE,QAAQ,CAAC,OAAO;gBACzB,OAAO,EAAE,QAAQ,CAAC,OAAO;aAC1B,CAAC;QACJ,CAAC;QAED,IAAI,QAAQ,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YAC7B,MAAM,IAAI,KAAK,CACb,UAAU,MAAM,uEAAuE;gBACrF,qDAAqD,CACxD,CAAC;QACJ,CAAC;QAED,IAAI,QAAQ,CAAC,IAAI,KAAK,WAAW,IAAI,QAAQ,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAChE,MAAM,IAAI,KAAK,CACb,UAAU,MAAM,uBAAuB,QAAQ,CAAC,IAAI,+CAA+C;gBACjG,6EAA6E,CAChF,CAAC;QACJ,CAAC;QAED,kDAAkD;QAClD,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,IAAI;QACF,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,OAAO,EAAE,CAAC;QAE5B,MAAM,OAAO,GAAoB,EAAE,CAAC;QACpC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;YAC9D,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC;QAC/C,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,QAAQ,CAAC,GAAW,EAAE,KAAiB;QACrC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YACjB,IAAI,CAAC,MAAM,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;QAC/B,CAAC;QACD,qCAAqC;QACrC,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC;YAAE,OAAO;QACpC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QAChC,IAAI,CAAC,aAAa,EAAE,CAAC;IACvB,CAAC;IAED,uBAAuB,CAAC,GAAW,EAAE,KAAiB;QACpD,IAAI,KAAK,CAAC,OAAO,EAAE,IAAI,KAAK,OAAO,EAAE,CAAC;YACpC,MAAM,IAAI,KAAK,CAAC,mEAAmE,GAAG,GAAG,CAAC,CAAC;QAC7F,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YACjB,IAAI,CAAC,MAAM,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;QAC/B,CAAC;QAED,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACzC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;YAChC,IAAI,CAAC,aAAa,EAAE,CAAC;YACrB,OAAO;QACT,CAAC;QAED,IAAI,SAAS,GAAG,QAAQ,CAAC;QACzB,IAAI,OAAO,GAAG,KAAK,CAAC;QAEpB,IAAI,CAAC,QAAQ,CAAC,QAAQ,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;YACzC,SAAS,GAAG,EAAE,GAAG,SAAS,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC;YACvD,OAAO,GAAG,IAAI,CAAC;QACjB,CAAC;QAED,MAAM,eAAe,GAAG,QAAQ,CAAC,OAAO,CAAC;QACzC,IAAI,CAAC,eAAe,EAAE,IAAI,EAAE,CAAC;YAC3B,SAAS,GAAG,EAAE,GAAG,SAAS,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC;YACrD,OAAO,GAAG,IAAI,CAAC;QACjB,CAAC;aAAM,IACL,eAAe,CAAC,IAAI,KAAK,OAAO;YAChC,CAAC,eAAe,CAAC,SAAS;YAC1B,KAAK,CAAC,OAAO,CAAC,SAAS,EACvB,CAAC;YACD,SAAS,GAAG;gBACV,GAAG,SAAS;gBACZ,OAAO,EAAE,EAAE,GAAG,eAAe,EAAE,SAAS,EAAE,KAAK,CAAC,OAAO,CAAC,SAAS,EAAE;aACpE,CAAC;YACF,OAAO,GAAG,IAAI,CAAC;QACjB,CAAC;QAED,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO;QACT,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC;QACpC,IAAI,CAAC,aAAa,EAAE,CAAC;IACvB,CAAC;IAED,SAAS,CAAC,GAAW,EAAE,GAA2B;QAChD,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;QACtC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QACjC,gBAAgB,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACjC,gBAAgB,CAAC,GAAG,CAAC,CAAC;QACtB,MAAM,QAAQ,GAAG,UAAU,CAAC,OAAO,CAAC;YAClC,CAAC,CAAC,YAAY,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC9C,CAAC,CAAE,EAA6B,CAAC;QACnC,MAAM,MAAM,GAAG,EAAE,GAAG,QAAQ,EAAE,GAAG,GAAG,EAAE,CAAC;QACvC,MAAM,OAAO,GACX,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC;aACnB,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;aACpD,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,GAAG,MAAM,IAAI,KAAK,EAAE,CAAC;aAC9C,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;QACvB,sBAAsB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAC3C,CAAC;IAED,UAAU,CAAC,GAAW,EAAE,YAAoB,EAAE,OAAe,EAAE,UAAmB;QAChF,MAAM,cAAc,GAAG,0BAA0B,CAAC,YAAY,CAAC,CAAC;QAChE,MAAM,gBAAgB,GAAG,wBAAwB,CAAC,UAAU,CAAC,CAAC;QAC9D,IAAI,CAAC,cAAc,IAAI,CAAC,UAAU,KAAK,SAAS,IAAI,CAAC,gBAAgB,CAAC,EAAE,CAAC;YACvE,MAAM,IAAI,KAAK,CAAC,iDAAiD,GAAG,MAAM,YAAY,EAAE,CAAC,CAAC;QAC5F,CAAC;QAED,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;QACtC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC;QAE3C,gBAAgB,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACjC,gBAAgB,CAAC,GAAG,CAAC,CAAC;QACtB,MAAM,SAAS,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;QACpC,IAAI,SAAS,KAAK,GAAG;YAAE,gBAAgB,CAAC,SAAS,CAAC,CAAC;QACnD,sBAAsB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAC1C,IAAI,CAAC,gBAAgB,CAAC,GAAG,EAAE,cAAc,EAAE,gBAAgB,CAAC,CAAC;IAC/D,CAAC;IAED,8EAA8E;IAEtE,aAAa;QACnB,gBAAgB,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAEjC,yEAAyE;QACzE,yEAAyE;QACzE,wEAAwE;QACxE,gEAAgE;QAChE,MAAM,MAAM,GAAG,IAAI,CAAC,kBAAkB,EAAE,CAAC;QACzC,IAAI,MAAM,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAC1B,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;gBACzD,IAAI,CAAC,CAAC,GAAG,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;oBACjC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;gBAClC,CAAC;YACH,CAAC;QACH,CAAC;QAED,sBAAsB,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IACvF,CAAC;IAEO,kBAAkB;QACxB,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC;YAAE,OAAO,IAAI,CAAC;QAC9C,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC;YAClE,IACE,CAAC,MAAM;gBACP,OAAO,MAAM,KAAK,QAAQ;gBAC1B,CAAC,MAAM,CAAC,MAAM;gBACd,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ,EACjC,CAAC;gBACD,OAAO,IAAI,CAAC;YACd,CAAC;YACD,OAAO,MAAqB,CAAC;QAC/B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAEO,gBAAgB,CAAC,GAAW,EAAE,YAAoB,EAAE,UAAmB;QAC7E,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;YAC9B,MAAM,IAAI,KAAK,CAAC,4BAA4B,YAAY,wBAAwB,GAAG,GAAG,CAAC,CAAC;QAC1F,CAAC;QAED,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACzC,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,IAAI,EAAE,CAAC;QACrC,IACE,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CACpB,OAAO,KAAK,KAAK,QAAQ;YACvB,CAAC,CAAC,KAAK,KAAK,YAAY,IAAI,CAAC,UAAU;YACvC,CAAC,CAAC,KAAK,CAAC,MAAM,KAAK,YAAY,IAAI,KAAK,CAAC,MAAM,KAAK,UAAU,CACjE,EACD,CAAC;YACD,OAAO;QACT,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG;YACxB,GAAG,QAAQ;YACX,MAAM,EAAE,CAAC,GAAG,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC;SAC9F,CAAC;QACF,IAAI,CAAC,aAAa,EAAE,CAAC;IACvB,CAAC;IAEO,aAAa,CAAC,GAAW,EAAE,KAAiB;QAClD,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;QAEtC,MAAM,MAAM,GAAG,CAAC,KAAK,CAAC,MAAM,IAAI,EAAE,CAAC;aAChC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,iBAAiB,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;aAClD,MAAM,CAAC,CAAC,KAAK,EAA+B,EAAE,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC;QAEvE,IAAI,GAAG,GAA2B,EAAE,CAAC;QACrC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QACjC,IAAI,KAAK,CAAC,OAAO,KAAK,KAAK,IAAI,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;YACnD,IAAI,CAAC;gBACH,GAAG,GAAG,YAAY,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;YACrD,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO,CAAC,KAAK,CAAC,wCAAwC,GAAG,IAAI,EAAE,GAAG,CAAC,CAAC;YACtE,CAAC;QACH,CAAC;QAED,OAAO;YACL,MAAM,EAAE,GAAG;YACX,WAAW,EAAE,KAAK,CAAC,WAAW;YAC9B,GAAG;YACH,MAAM;YACN,GAAG;YACH,eAAe,EAAE,KAAK,CAAC,OAAO;SAC/B,CAAC;IACJ,CAAC;IAEO,iBAAiB,CACvB,GAAW,EACX,KAA+B;QAE/B,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC9B,MAAM,gBAAgB,GAAG,0BAA0B,CAAC,KAAK,CAAC,CAAC;YAC3D,IAAI,CAAC,gBAAgB;gBAAE,OAAO,SAAS,CAAC;YACxC,OAAO;gBACL,MAAM,EAAE,IAAI,CAAC,GAAG,EAAE,gBAAgB,CAAC;gBACnC,MAAM,EAAE,sBAAsB,CAAC,gBAAgB,CAAC;aACjD,CAAC;QACJ,CAAC;QAED,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;YAAE,OAAO,SAAS,CAAC;QAC1D,MAAM,gBAAgB,GAAG,0BAA0B,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAClE,IAAI,CAAC,gBAAgB;YAAE,OAAO,SAAS,CAAC;QACxC,MAAM,gBAAgB,GAAG,wBAAwB,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAChE,OAAO;YACL,MAAM,EAAE,IAAI,CAAC,GAAG,EAAE,gBAAgB,CAAC;YACnC,MAAM,EAAE,gBAAgB,IAAI,sBAAsB,CAAC,gBAAgB,CAAC;SACrE,CAAC;IACJ,CAAC;CACF;AAED,SAAS,gBAAgB,CAAC,IAAY;IACpC,SAAS,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,gBAAgB,EAAE,CAAC,CAAC;IAC7D,SAAS,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC;AACpC,CAAC;AAED;;;;;GAKG;AACH,SAAS,sBAAsB,CAAC,UAAkB,EAAE,OAAe;IACjE,MAAM,GAAG,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAChC,MAAM,OAAO,GAAG,IAAI,CAClB,GAAG,EACH,IAAI,QAAQ,CAAC,UAAU,CAAC,IAAI,OAAO,CAAC,GAAG,IAAI,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAChF,CAAC;IACF,MAAM,EAAE,GAAG,QAAQ,CACjB,OAAO,EACP,WAAW,CAAC,QAAQ,GAAG,WAAW,CAAC,OAAO,GAAG,WAAW,CAAC,MAAM,EAC/D,iBAAiB,CAClB,CAAC;IACF,IAAI,CAAC;QACH,SAAS,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;IACzB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,CAAC;YACH,UAAU,CAAC,OAAO,CAAC,CAAC;QACtB,CAAC;QAAC,MAAM,CAAC;YACP,8CAA8C;QAChD,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;YAAS,CAAC;QACT,SAAS,CAAC,EAAE,CAAC,CAAC;IAChB,CAAC;IACD,IAAI,CAAC;QACH,UAAU,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;IAClC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,CAAC;YACH,UAAU,CAAC,OAAO,CAAC,CAAC;QACtB,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC;AAED,SAAS,0BAA0B,CAAC,YAAoB;IACtD,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,EAAE,CAAC;IACpC,IAAI,CAAC,OAAO,IAAI,UAAU,CAAC,OAAO,CAAC;QAAE,OAAO,SAAS,CAAC;IAEtD,MAAM,UAAU,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC3D,IAAI,CAAC,UAAU,IAAI,UAAU,KAAK,GAAG,IAAI,UAAU,KAAK,IAAI,IAAI,UAAU,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;QAC7F,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,SAAS,wBAAwB,CAAC,UAAmB;IACnD,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;QAC7B,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,MAAM,OAAO,GAAG,UAAU,CAAC,IAAI,EAAE,CAAC;IAClC,IAAI,CAAC,OAAO,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACzC,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,MAAM,UAAU,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC3D,OAAO,UAAU,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC;AAC7D,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,YAAoB;IACzD,MAAM,UAAU,GAAG,0BAA0B,CAAC,YAAY,CAAC,IAAI,YAAY,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAChG,OAAO,SAAS,UAAU,EAAE,CAAC;AAC/B,CAAC","sourcesContent":["import {\n chmodSync,\n closeSync,\n constants as fsConstants,\n existsSync,\n mkdirSync,\n openSync,\n readFileSync,\n renameSync,\n unlinkSync,\n writeSync,\n} from \"fs\";\nimport { randomBytes } from \"crypto\";\nimport { basename, dirname, isAbsolute, join, normalize, sep } from \"path\";\nimport type { SandboxConfig } from \"./sandbox.js\";\n\nconst PRIVATE_DIR_MODE = 0o700;\nconst PRIVATE_FILE_MODE = 0o600;\n\n// ── Types ──────────────────────────────────────────────────────────────────────\n\n/** Shape of workspace/vaults/vault.json */\nexport interface VaultConfig {\n vaults: Record<string, VaultEntry>;\n}\n\n/** Per-user vault mount entry in vault.json */\nexport interface VaultMountEntry {\n source: string;\n target?: string;\n}\n\n/** Per-user vault entry in vault.json */\nexport interface VaultEntry {\n displayName: string;\n platform?: \"slack\" | \"discord\" | \"telegram\";\n /** Subdirs/files in vault dir to mount into sandbox (e.g. [\".gcloud\", \".ssh\", \".kube\"]) */\n mounts?: Array<string | VaultMountEntry>;\n /** Whether to load env file as environment variables (default: true if env file exists) */\n envFile?: boolean;\n /** Per-user sandbox config override */\n sandbox?: {\n type?: \"image\" | \"firecracker\" | \"host\" | \"container\" | \"docker\";\n container?: string;\n image?: string;\n vmId?: string;\n sshUser?: string;\n sshPort?: number;\n };\n}\n\nexport interface ResolvedVaultMount {\n source: string;\n target: string;\n}\n\n/** Resolved vault ready for use at runtime */\nexport interface ResolvedVault {\n userId: string;\n displayName: string;\n /** Absolute path to vault directory */\n dir: string;\n /** Absolute mount specs */\n mounts: ResolvedVaultMount[];\n /** Parsed from env file */\n env: Record<string, string>;\n sandboxOverride?: VaultEntry[\"sandbox\"];\n}\n\nexport interface VaultManager {\n /** Return true when vault.json contains this exact key. */\n hasEntry(key: string): boolean;\n /** Resolve vault for a user; returns undefined when no entry exists. */\n resolve(userId: string): ResolvedVault | undefined;\n /** Get sandbox config with credential injection for a user */\n getSandboxConfig(userId: string, baseConfig: SandboxConfig): SandboxConfig;\n /** List all configured vaults */\n list(): ResolvedVault[];\n /** Re-read vault.json without restart */\n reload(): void;\n /** Check if vault system is enabled (vault.json exists) */\n isEnabled(): boolean;\n /**\n * Add a vault entry and persist to disk.\n * No-op if the key already exists (idempotent).\n */\n addEntry(key: string, entry: VaultEntry): void;\n /**\n * Ensure a vault entry has image sandbox metadata.\n * Creates the entry when missing and upgrades existing entries that lack sandbox.type.\n */\n ensureImageSandboxEntry(key: string, entry: VaultEntry): void;\n /** Merge environment variables into vaults/<key>/env and persist them to disk. */\n upsertEnv(key: string, env: Record<string, string>): void;\n /** Write a private file into vaults/<key>/ and ensure it is mounted into the sandbox. */\n upsertFile(key: string, relativePath: string, content: string, targetPath?: string): void;\n}\n\n// ── parseEnvFile ───────────────────────────────────────────────────────────────\n\n/**\n * Parse a KEY=VALUE env file. Supports:\n * - Lines starting with # are comments\n * - Empty lines are skipped\n * - Values can be quoted with single or double quotes (quotes are stripped)\n * - No variable expansion\n * - The value is everything after the first `=` to end of line (no inline comments)\n */\nexport function parseEnvFile(content: string): Record<string, string> {\n const env: Record<string, string> = {};\n const lines = content.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\").split(\"\\n\");\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith(\"#\")) continue;\n\n const eqIndex = trimmed.indexOf(\"=\");\n if (eqIndex === -1) continue;\n\n const key = trimmed.slice(0, eqIndex).trim();\n if (!key) continue;\n\n let value = trimmed.slice(eqIndex + 1);\n\n // Strip matching quotes\n if (\n (value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))\n ) {\n value = value.slice(1, -1);\n }\n\n env[key] = value;\n }\n\n return env;\n}\n\n// ── FileVaultManager ───────────────────────────────────────────────────────────\n\nexport class FileVaultManager implements VaultManager {\n private config: VaultConfig | null = null;\n private readonly vaultsDir: string;\n private readonly configPath: string;\n\n constructor(stateDir: string) {\n this.vaultsDir = join(stateDir, \"vaults\");\n this.configPath = join(this.vaultsDir, \"vault.json\");\n this.reload();\n }\n\n reload(): void {\n if (!existsSync(this.configPath)) {\n this.config = null;\n return;\n }\n\n try {\n const raw = readFileSync(this.configPath, \"utf-8\");\n const parsed = JSON.parse(raw);\n\n if (\n !parsed ||\n typeof parsed !== \"object\" ||\n !parsed.vaults ||\n typeof parsed.vaults !== \"object\"\n ) {\n console.error(`vault: malformed vault.json — expected { vaults: { ... } }`);\n this.config = null;\n return;\n }\n\n this.config = parsed as VaultConfig;\n this.warnUnsupportedSandboxTypes();\n } catch (err) {\n console.error(`vault: failed to read ${this.configPath}:`, err);\n this.config = null;\n }\n }\n\n /** Warn for legacy or insecure vault sandbox overrides that are no longer allowed. */\n private warnUnsupportedSandboxTypes(): void {\n if (!this.config) return;\n for (const [key, entry] of Object.entries(this.config.vaults)) {\n if (entry.sandbox?.type === \"host\") {\n console.error(\n `vault: \"${key}\" uses sandbox.type=host, which is blocked for credential isolation. ` +\n \"Use sandbox.type=image or sandbox.type=firecracker.\",\n );\n }\n if (entry.sandbox?.type === \"container\" || entry.sandbox?.type === \"docker\") {\n console.error(\n `vault: \"${key}\" uses sandbox.type=${entry.sandbox.type}, which is blocked for credential isolation. ` +\n \"Use sandbox.type=image for per-user containers or sandbox.type=firecracker.\",\n );\n }\n }\n }\n\n isEnabled(): boolean {\n return this.config !== null;\n }\n\n hasEntry(key: string): boolean {\n return !!this.config?.vaults[key];\n }\n\n resolve(userId: string): ResolvedVault | undefined {\n const entry = this.config?.vaults[userId];\n if (!entry) return undefined;\n return this.buildResolved(userId, entry);\n }\n\n getSandboxConfig(userId: string, baseConfig: SandboxConfig): SandboxConfig {\n const vault = this.resolve(userId);\n if (!vault?.sandboxOverride) return baseConfig;\n\n const override = vault.sandboxOverride;\n\n if (override.type === \"image\") {\n if (baseConfig.type !== \"image\") {\n throw new Error(\n `vault \"${userId}\" sets sandbox.type=image, but base sandbox is \"${baseConfig.type}\". ` +\n \"Use --sandbox=image:<image> to enable per-user managed containers.\",\n );\n }\n const container = override.container || `mama-sandbox-${userId}`;\n return { type: \"container\", container };\n }\n\n if (override.type === \"firecracker\") {\n if (!override.vmId) return baseConfig;\n if (baseConfig.type !== \"firecracker\") {\n throw new Error(\n `vault \"${userId}\" sets sandbox.type=firecracker, but base sandbox is \"${baseConfig.type}\". ` +\n \"Use --sandbox=firecracker:<vm-id>:<host-path> so /workspace stays mapped to the real workspace.\",\n );\n }\n return {\n type: \"firecracker\",\n vmId: override.vmId,\n hostPath: baseConfig.hostPath,\n sshUser: override.sshUser,\n sshPort: override.sshPort,\n };\n }\n\n if (override.type === \"host\") {\n throw new Error(\n `vault \"${userId}\" uses sandbox.type=host, which is blocked for credential isolation. ` +\n \"Use sandbox.type=image or sandbox.type=firecracker.\",\n );\n }\n\n if (override.type === \"container\" || override.type === \"docker\") {\n throw new Error(\n `vault \"${userId}\" uses sandbox.type=${override.type}, which is blocked for credential isolation. ` +\n \"Use sandbox.type=image for per-user containers or sandbox.type=firecracker.\",\n );\n }\n\n // No type override — return base config unchanged\n return baseConfig;\n }\n\n list(): ResolvedVault[] {\n if (!this.config) return [];\n\n const results: ResolvedVault[] = [];\n for (const [key, entry] of Object.entries(this.config.vaults)) {\n results.push(this.buildResolved(key, entry));\n }\n return results;\n }\n\n addEntry(key: string, entry: VaultEntry): void {\n if (!this.config) {\n this.config = { vaults: {} };\n }\n // Idempotent: skip if already exists\n if (this.config.vaults[key]) return;\n this.config.vaults[key] = entry;\n this.persistConfig();\n }\n\n ensureImageSandboxEntry(key: string, entry: VaultEntry): void {\n if (entry.sandbox?.type !== \"image\") {\n throw new Error(`vault: ensureImageSandboxEntry requires sandbox.type=image for \"${key}\"`);\n }\n\n if (!this.config) {\n this.config = { vaults: {} };\n }\n\n const existing = this.config.vaults[key];\n if (!existing) {\n this.config.vaults[key] = entry;\n this.persistConfig();\n return;\n }\n\n let nextEntry = existing;\n let changed = false;\n\n if (!existing.platform && entry.platform) {\n nextEntry = { ...nextEntry, platform: entry.platform };\n changed = true;\n }\n\n const existingSandbox = existing.sandbox;\n if (!existingSandbox?.type) {\n nextEntry = { ...nextEntry, sandbox: entry.sandbox };\n changed = true;\n } else if (\n existingSandbox.type === \"image\" &&\n !existingSandbox.container &&\n entry.sandbox.container\n ) {\n nextEntry = {\n ...nextEntry,\n sandbox: { ...existingSandbox, container: entry.sandbox.container },\n };\n changed = true;\n }\n\n if (!changed) {\n return;\n }\n\n this.config.vaults[key] = nextEntry;\n this.persistConfig();\n }\n\n upsertEnv(key: string, env: Record<string, string>): void {\n const dir = join(this.vaultsDir, key);\n const envPath = join(dir, \"env\");\n ensurePrivateDir(this.vaultsDir);\n ensurePrivateDir(dir);\n const existing = existsSync(envPath)\n ? parseEnvFile(readFileSync(envPath, \"utf-8\"))\n : ({} as Record<string, string>);\n const merged = { ...existing, ...env };\n const content =\n Object.entries(merged)\n .sort(([left], [right]) => left.localeCompare(right))\n .map(([envKey, value]) => `${envKey}=${value}`)\n .join(\"\\n\") + \"\\n\";\n atomicWritePrivateFile(envPath, content);\n }\n\n upsertFile(key: string, relativePath: string, content: string, targetPath?: string): void {\n const normalizedPath = normalizeVaultRelativePath(relativePath);\n const normalizedTarget = normalizeVaultTargetPath(targetPath);\n if (!normalizedPath || (targetPath !== undefined && !normalizedTarget)) {\n throw new Error(`vault: invalid relative secret file path for \"${key}\": ${relativePath}`);\n }\n\n const dir = join(this.vaultsDir, key);\n const filePath = join(dir, normalizedPath);\n\n ensurePrivateDir(this.vaultsDir);\n ensurePrivateDir(dir);\n const parentDir = dirname(filePath);\n if (parentDir !== dir) ensurePrivateDir(parentDir);\n atomicWritePrivateFile(filePath, content);\n this.ensureMountEntry(key, normalizedPath, normalizedTarget);\n }\n\n // ── private ────────────────────────────────────────────────────────────────\n\n private persistConfig(): void {\n ensurePrivateDir(this.vaultsDir);\n\n // Preserve concurrent external edits: pull in any entries that appear on\n // disk but not in our in-memory view, so a background edit (e.g. another\n // admin adding a user) is not silently dropped by the next upsert here.\n // Individual field edits still follow last-writer-wins per key.\n const onDisk = this.readConfigFromDisk();\n if (onDisk && this.config) {\n for (const [key, entry] of Object.entries(onDisk.vaults)) {\n if (!(key in this.config.vaults)) {\n this.config.vaults[key] = entry;\n }\n }\n }\n\n atomicWritePrivateFile(this.configPath, JSON.stringify(this.config, null, 2) + \"\\n\");\n }\n\n private readConfigFromDisk(): VaultConfig | null {\n if (!existsSync(this.configPath)) return null;\n try {\n const parsed = JSON.parse(readFileSync(this.configPath, \"utf-8\"));\n if (\n !parsed ||\n typeof parsed !== \"object\" ||\n !parsed.vaults ||\n typeof parsed.vaults !== \"object\"\n ) {\n return null;\n }\n return parsed as VaultConfig;\n } catch {\n return null;\n }\n }\n\n private ensureMountEntry(key: string, relativePath: string, targetPath?: string): void {\n if (!this.config?.vaults[key]) {\n throw new Error(`vault: cannot add mount \"${relativePath}\" for missing entry \"${key}\"`);\n }\n\n const existing = this.config.vaults[key];\n const mounts = existing.mounts ?? [];\n if (\n mounts.some((mount) =>\n typeof mount === \"string\"\n ? mount === relativePath && !targetPath\n : mount.source === relativePath && mount.target === targetPath,\n )\n ) {\n return;\n }\n\n this.config.vaults[key] = {\n ...existing,\n mounts: [...mounts, targetPath ? { source: relativePath, target: targetPath } : relativePath],\n };\n this.persistConfig();\n }\n\n private buildResolved(key: string, entry: VaultEntry): ResolvedVault {\n const dir = join(this.vaultsDir, key);\n\n const mounts = (entry.mounts ?? [])\n .map((mount) => this.resolveMountEntry(dir, mount))\n .filter((mount): mount is ResolvedVaultMount => mount !== undefined);\n\n let env: Record<string, string> = {};\n const envPath = join(dir, \"env\");\n if (entry.envFile !== false && existsSync(envPath)) {\n try {\n env = parseEnvFile(readFileSync(envPath, \"utf-8\"));\n } catch (err) {\n console.error(`vault: failed to parse env file for \"${key}\":`, err);\n }\n }\n\n return {\n userId: key,\n displayName: entry.displayName,\n dir,\n mounts,\n env,\n sandboxOverride: entry.sandbox,\n };\n }\n\n private resolveMountEntry(\n dir: string,\n mount: string | VaultMountEntry,\n ): ResolvedVaultMount | undefined {\n if (typeof mount === \"string\") {\n const normalizedSource = normalizeVaultRelativePath(mount);\n if (!normalizedSource) return undefined;\n return {\n source: join(dir, normalizedSource),\n target: defaultVaultTargetPath(normalizedSource),\n };\n }\n\n if (!mount || typeof mount !== \"object\") return undefined;\n const normalizedSource = normalizeVaultRelativePath(mount.source);\n if (!normalizedSource) return undefined;\n const normalizedTarget = normalizeVaultTargetPath(mount.target);\n return {\n source: join(dir, normalizedSource),\n target: normalizedTarget ?? defaultVaultTargetPath(normalizedSource),\n };\n }\n}\n\nfunction ensurePrivateDir(path: string): void {\n mkdirSync(path, { recursive: true, mode: PRIVATE_DIR_MODE });\n chmodSync(path, PRIVATE_DIR_MODE);\n}\n\n/**\n * Write `content` to `targetPath` with mode 0600, even when `targetPath`\n * already exists. Uses O_CREAT|O_EXCL on a temp sibling (so the kernel\n * guarantees permissions at creation, not after a racy chmod) and then\n * rename(2) into place for atomicity. Readers never see a torn write.\n */\nfunction atomicWritePrivateFile(targetPath: string, content: string): void {\n const dir = dirname(targetPath);\n const tmpPath = join(\n dir,\n `.${basename(targetPath)}.${process.pid}.${randomBytes(8).toString(\"hex\")}.tmp`,\n );\n const fd = openSync(\n tmpPath,\n fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL,\n PRIVATE_FILE_MODE,\n );\n try {\n writeSync(fd, content);\n } catch (err) {\n try {\n unlinkSync(tmpPath);\n } catch {\n // ignore — original error is more informative\n }\n throw err;\n } finally {\n closeSync(fd);\n }\n try {\n renameSync(tmpPath, targetPath);\n } catch (err) {\n try {\n unlinkSync(tmpPath);\n } catch {\n // ignore\n }\n throw err;\n }\n}\n\nfunction normalizeVaultRelativePath(relativePath: string): string | undefined {\n const trimmed = relativePath.trim();\n if (!trimmed || isAbsolute(trimmed)) return undefined;\n\n const normalized = normalize(trimmed).split(sep).join(\"/\");\n if (!normalized || normalized === \".\" || normalized === \"..\" || normalized.startsWith(\"../\")) {\n return undefined;\n }\n return normalized;\n}\n\nfunction normalizeVaultTargetPath(targetPath?: string): string | undefined {\n if (targetPath === undefined) {\n return undefined;\n }\n\n const trimmed = targetPath.trim();\n if (!trimmed || !trimmed.startsWith(\"/\")) {\n return undefined;\n }\n\n const normalized = normalize(trimmed).split(sep).join(\"/\");\n return normalized.startsWith(\"/\") ? normalized : undefined;\n}\n\nexport function defaultVaultTargetPath(relativePath: string): string {\n const normalized = normalizeVaultRelativePath(relativePath) ?? relativePath.replace(/^\\/+/, \"\");\n return `/root/${normalized}`;\n}\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=vault.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vault.test.d.ts","sourceRoot":"","sources":["../src/vault.test.ts"],"names":[],"mappings":"","sourcesContent":["import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from \"fs\";\nimport { tmpdir } from \"os\";\nimport { join } from \"path\";\nimport { afterEach, describe, expect, it } from \"vitest\";\nimport { FileVaultManager } from \"./vault.js\";\n\ndescribe(\"FileVaultManager\", () => {\n const tempDirs: string[] = [];\n\n afterEach(() => {\n while (tempDirs.length > 0) {\n rmSync(tempDirs.pop()!, { recursive: true, force: true });\n }\n });\n\n function createStateDir(): string {\n const dir = mkdtempSync(join(tmpdir(), \"mama-vault-test-\"));\n tempDirs.push(dir);\n return dir;\n }\n\n it(\"reuses the base firecracker workspace hostPath for user overrides\", () => {\n const stateDir = createStateDir();\n const vaultsDir = join(stateDir, \"vaults\");\n mkdirSync(vaultsDir, { recursive: true, mode: 0o700 });\n writeFileSync(\n join(vaultsDir, \"vault.json\"),\n JSON.stringify(\n {\n vaults: {\n alice: {\n displayName: \"Alice\",\n sandbox: { type: \"firecracker\", vmId: \"alice-vm\", sshUser: \"root\", sshPort: 2222 },\n },\n },\n },\n null,\n 2,\n ) + \"\\n\",\n { encoding: \"utf-8\", mode: 0o600 },\n );\n\n const manager = new FileVaultManager(stateDir);\n expect(\n manager.getSandboxConfig(\"alice\", {\n type: \"firecracker\",\n vmId: \"shared-vm\",\n hostPath: \"/real/workspace\",\n sshUser: \"root\",\n sshPort: 22,\n }),\n ).toEqual({\n type: \"firecracker\",\n vmId: \"alice-vm\",\n hostPath: \"/real/workspace\",\n sshUser: \"root\",\n sshPort: 2222,\n });\n });\n\n it(\"rejects firecracker overrides when the base sandbox is not firecracker\", () => {\n const stateDir = createStateDir();\n const vaultsDir = join(stateDir, \"vaults\");\n mkdirSync(vaultsDir, { recursive: true, mode: 0o700 });\n writeFileSync(\n join(vaultsDir, \"vault.json\"),\n JSON.stringify(\n {\n vaults: {\n alice: {\n displayName: \"Alice\",\n sandbox: { type: \"firecracker\", vmId: \"alice-vm\" },\n },\n },\n },\n null,\n 2,\n ) + \"\\n\",\n { encoding: \"utf-8\", mode: 0o600 },\n );\n\n const manager = new FileVaultManager(stateDir);\n expect(() =>\n manager.getSandboxConfig(\"alice\", { type: \"image\", image: \"alpine:3.20\" }),\n ).toThrow(/base sandbox is \"image\"/);\n });\n\n it(\"throws on invalid secret file paths instead of silently succeeding\", () => {\n const stateDir = createStateDir();\n const manager = new FileVaultManager(stateDir);\n manager.addEntry(\"alice\", { displayName: \"Alice\" });\n\n expect(() => manager.upsertFile(\"alice\", \"../credentials.txt\", \"secret\")).toThrow(\n /invalid relative secret file path/,\n );\n });\n});\n"]}
@@ -0,0 +1,67 @@
1
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "fs";
2
+ import { tmpdir } from "os";
3
+ import { join } from "path";
4
+ import { afterEach, describe, expect, it } from "vitest";
5
+ import { FileVaultManager } from "./vault.js";
6
+ describe("FileVaultManager", () => {
7
+ const tempDirs = [];
8
+ afterEach(() => {
9
+ while (tempDirs.length > 0) {
10
+ rmSync(tempDirs.pop(), { recursive: true, force: true });
11
+ }
12
+ });
13
+ function createStateDir() {
14
+ const dir = mkdtempSync(join(tmpdir(), "mama-vault-test-"));
15
+ tempDirs.push(dir);
16
+ return dir;
17
+ }
18
+ it("reuses the base firecracker workspace hostPath for user overrides", () => {
19
+ const stateDir = createStateDir();
20
+ const vaultsDir = join(stateDir, "vaults");
21
+ mkdirSync(vaultsDir, { recursive: true, mode: 0o700 });
22
+ writeFileSync(join(vaultsDir, "vault.json"), JSON.stringify({
23
+ vaults: {
24
+ alice: {
25
+ displayName: "Alice",
26
+ sandbox: { type: "firecracker", vmId: "alice-vm", sshUser: "root", sshPort: 2222 },
27
+ },
28
+ },
29
+ }, null, 2) + "\n", { encoding: "utf-8", mode: 0o600 });
30
+ const manager = new FileVaultManager(stateDir);
31
+ expect(manager.getSandboxConfig("alice", {
32
+ type: "firecracker",
33
+ vmId: "shared-vm",
34
+ hostPath: "/real/workspace",
35
+ sshUser: "root",
36
+ sshPort: 22,
37
+ })).toEqual({
38
+ type: "firecracker",
39
+ vmId: "alice-vm",
40
+ hostPath: "/real/workspace",
41
+ sshUser: "root",
42
+ sshPort: 2222,
43
+ });
44
+ });
45
+ it("rejects firecracker overrides when the base sandbox is not firecracker", () => {
46
+ const stateDir = createStateDir();
47
+ const vaultsDir = join(stateDir, "vaults");
48
+ mkdirSync(vaultsDir, { recursive: true, mode: 0o700 });
49
+ writeFileSync(join(vaultsDir, "vault.json"), JSON.stringify({
50
+ vaults: {
51
+ alice: {
52
+ displayName: "Alice",
53
+ sandbox: { type: "firecracker", vmId: "alice-vm" },
54
+ },
55
+ },
56
+ }, null, 2) + "\n", { encoding: "utf-8", mode: 0o600 });
57
+ const manager = new FileVaultManager(stateDir);
58
+ expect(() => manager.getSandboxConfig("alice", { type: "image", image: "alpine:3.20" })).toThrow(/base sandbox is "image"/);
59
+ });
60
+ it("throws on invalid secret file paths instead of silently succeeding", () => {
61
+ const stateDir = createStateDir();
62
+ const manager = new FileVaultManager(stateDir);
63
+ manager.addEntry("alice", { displayName: "Alice" });
64
+ expect(() => manager.upsertFile("alice", "../credentials.txt", "secret")).toThrow(/invalid relative secret file path/);
65
+ });
66
+ });
67
+ //# sourceMappingURL=vault.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vault.test.js","sourceRoot":"","sources":["../src/vault.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from \"fs\";\nimport { tmpdir } from \"os\";\nimport { join } from \"path\";\nimport { afterEach, describe, expect, it } from \"vitest\";\nimport { FileVaultManager } from \"./vault.js\";\n\ndescribe(\"FileVaultManager\", () => {\n const tempDirs: string[] = [];\n\n afterEach(() => {\n while (tempDirs.length > 0) {\n rmSync(tempDirs.pop()!, { recursive: true, force: true });\n }\n });\n\n function createStateDir(): string {\n const dir = mkdtempSync(join(tmpdir(), \"mama-vault-test-\"));\n tempDirs.push(dir);\n return dir;\n }\n\n it(\"reuses the base firecracker workspace hostPath for user overrides\", () => {\n const stateDir = createStateDir();\n const vaultsDir = join(stateDir, \"vaults\");\n mkdirSync(vaultsDir, { recursive: true, mode: 0o700 });\n writeFileSync(\n join(vaultsDir, \"vault.json\"),\n JSON.stringify(\n {\n vaults: {\n alice: {\n displayName: \"Alice\",\n sandbox: { type: \"firecracker\", vmId: \"alice-vm\", sshUser: \"root\", sshPort: 2222 },\n },\n },\n },\n null,\n 2,\n ) + \"\\n\",\n { encoding: \"utf-8\", mode: 0o600 },\n );\n\n const manager = new FileVaultManager(stateDir);\n expect(\n manager.getSandboxConfig(\"alice\", {\n type: \"firecracker\",\n vmId: \"shared-vm\",\n hostPath: \"/real/workspace\",\n sshUser: \"root\",\n sshPort: 22,\n }),\n ).toEqual({\n type: \"firecracker\",\n vmId: \"alice-vm\",\n hostPath: \"/real/workspace\",\n sshUser: \"root\",\n sshPort: 2222,\n });\n });\n\n it(\"rejects firecracker overrides when the base sandbox is not firecracker\", () => {\n const stateDir = createStateDir();\n const vaultsDir = join(stateDir, \"vaults\");\n mkdirSync(vaultsDir, { recursive: true, mode: 0o700 });\n writeFileSync(\n join(vaultsDir, \"vault.json\"),\n JSON.stringify(\n {\n vaults: {\n alice: {\n displayName: \"Alice\",\n sandbox: { type: \"firecracker\", vmId: \"alice-vm\" },\n },\n },\n },\n null,\n 2,\n ) + \"\\n\",\n { encoding: \"utf-8\", mode: 0o600 },\n );\n\n const manager = new FileVaultManager(stateDir);\n expect(() =>\n manager.getSandboxConfig(\"alice\", { type: \"image\", image: \"alpine:3.20\" }),\n ).toThrow(/base sandbox is \"image\"/);\n });\n\n it(\"throws on invalid secret file paths instead of silently succeeding\", () => {\n const stateDir = createStateDir();\n const manager = new FileVaultManager(stateDir);\n manager.addEntry(\"alice\", { displayName: \"Alice\" });\n\n expect(() => manager.upsertFile(\"alice\", \"../credentials.txt\", \"secret\")).toThrow(\n /invalid relative secret file path/,\n );\n });\n});\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geminixiang/mama",
3
- "version": "0.1.10",
3
+ "version": "0.2.0-beta.1",
4
4
  "description": "Slack bot that delegates messages to the pi coding agent",
5
5
  "keywords": [
6
6
  "agent",
@@ -39,20 +39,22 @@
39
39
  "prepare": "husky"
40
40
  },
41
41
  "dependencies": {
42
- "@anthropic-ai/sandbox-runtime": "^0.0.43",
42
+ "@anthropic-ai/sandbox-runtime": "^0.0.49",
43
43
  "@google-cloud/logging": "^11.2.1",
44
- "@mariozechner/pi-agent-core": "^0.63.1",
45
- "@mariozechner/pi-ai": "^0.63.1",
46
- "@mariozechner/pi-coding-agent": "^0.63.1",
44
+ "@mariozechner/pi-agent-core": "^0.69.0",
45
+ "@mariozechner/pi-ai": "^0.69.0",
46
+ "@mariozechner/pi-coding-agent": "^0.69.0",
47
+ "@sentry/node": "^10.47.0",
47
48
  "@sinclair/typebox": "^0.34.49",
48
49
  "@slack/socket-mode": "^2.0.6",
49
50
  "@slack/web-api": "^7.15.0",
50
51
  "chalk": "^5.6.2",
51
52
  "croner": "^10.0.1",
52
53
  "diff": "^8.0.4",
53
- "discord.js": "^14.25.1",
54
- "grammy": "^1.41.1",
55
- "pino": "^10.3.1"
54
+ "discord.js": "^14.26.2",
55
+ "grammy": "^1.42.0",
56
+ "pino": "^10.3.1",
57
+ "playwright": "^1.59.1"
56
58
  },
57
59
  "devDependencies": {
58
60
  "@types/diff": "^8.0.0",
@@ -60,11 +62,11 @@
60
62
  "@typescript/native-preview": "7.0.0-dev.20260328.1",
61
63
  "husky": "^9.1.7",
62
64
  "lint-staged": "^16.4.0",
63
- "oxfmt": "^0.42.0",
64
- "oxlint": "^1.57.0",
65
+ "oxfmt": "^0.44.0",
66
+ "oxlint": "^1.59.0",
65
67
  "shx": "^0.4.0",
66
68
  "typescript": "^6.0.2",
67
- "vitest": "^4.1.2"
69
+ "vitest": "^4.1.3"
68
70
  },
69
71
  "lint-staged": {
70
72
  "*.ts": [