@arloraccoon/openclaw-vaultwarden-plugin 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,752 @@
1
+ // index.ts
2
+ import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
3
+
4
+ // src/auth-cli.ts
5
+ import { chmodSync, closeSync, mkdirSync, openSync, renameSync, writeFileSync } from "node:fs";
6
+ import { dirname, resolve } from "node:path";
7
+ import { spawnSync } from "node:child_process";
8
+ var defaultSessionFile = "~/.openclaw/secrets/vaultwarden-session";
9
+ function expandHome(path) {
10
+ return path.startsWith("~/") ? resolve(process.env.HOME ?? ".", path.slice(2)) : resolve(path);
11
+ }
12
+ function storeSessionToken(token, file) {
13
+ const trimmed = token.trim();
14
+ if (!trimmed) {
15
+ throw new Error("Bitwarden CLI returned an empty session");
16
+ }
17
+ const target = expandHome(file);
18
+ mkdirSync(dirname(target), { recursive: true, mode: 448 });
19
+ const temp = `${target}.${process.pid}.tmp`;
20
+ const fd = openSync(temp, "wx", 384);
21
+ try {
22
+ chmodSync(temp, 384);
23
+ writeFileSync(fd, `${trimmed}
24
+ `, { encoding: "utf8" });
25
+ } finally {
26
+ closeSync(fd);
27
+ }
28
+ renameSync(temp, target);
29
+ return target;
30
+ }
31
+ function unlockWithBitwardenCli() {
32
+ const result = spawnSync("bw", ["unlock", "--raw"], {
33
+ stdio: ["inherit", "pipe", "inherit"],
34
+ encoding: "utf8",
35
+ maxBuffer: 64 * 1024
36
+ });
37
+ if (result.error || result.status !== 0) {
38
+ throw new Error("Bitwarden CLI unlock failed");
39
+ }
40
+ return result.stdout ?? "";
41
+ }
42
+ function loginWithBitwardenCli() {
43
+ const result = spawnSync("bw", ["login"], { stdio: "inherit" });
44
+ if (result.error || result.status !== 0) {
45
+ throw new Error("Bitwarden CLI login failed");
46
+ }
47
+ }
48
+ function isBitwardenLoggedIn() {
49
+ const result = spawnSync("bw", ["status", "--raw"], {
50
+ stdio: ["ignore", "pipe", "ignore"],
51
+ encoding: "utf8",
52
+ maxBuffer: 64 * 1024
53
+ });
54
+ if (result.error || result.status !== 0) {
55
+ return false;
56
+ }
57
+ try {
58
+ const status = JSON.parse(result.stdout ?? "");
59
+ return status.status !== "unauthenticated";
60
+ } catch {
61
+ return false;
62
+ }
63
+ }
64
+ function registerVaultwardenAuthCli(program, options = {}) {
65
+ program.command("vaultwarden").description("Configure a local Vaultwarden CLI session for OpenClaw").option("--session-file <path>", "0600 file for the session token", defaultSessionFile).action(async ({ sessionFile = defaultSessionFile }) => {
66
+ try {
67
+ if (!isBitwardenLoggedIn()) {
68
+ (options.login ?? loginWithBitwardenCli)();
69
+ }
70
+ const target = storeSessionToken(
71
+ (options.unlock ?? unlockWithBitwardenCli)(),
72
+ sessionFile
73
+ );
74
+ console.log(`Vaultwarden session stored at ${target}`);
75
+ console.log(
76
+ `Configure a file SecretRef: provider source=file, path=${target}, mode=singleValue, id=value`
77
+ );
78
+ console.log(
79
+ 'Then set plugins.entries.vaultwarden.config.session to {"source":"file","provider":"vaultwarden_session","id":"value"}.'
80
+ );
81
+ } catch {
82
+ console.error("Vaultwarden session setup failed");
83
+ process.exitCode = 1;
84
+ }
85
+ });
86
+ }
87
+
88
+ // src/cli.ts
89
+ import { execFile, spawn } from "node:child_process";
90
+ import { promisify } from "node:util";
91
+ import { resolveConfiguredSecretInputString } from "openclaw/plugin-sdk/secret-input-runtime";
92
+ var execFileAsync = promisify(execFile);
93
+ function execWithInput(file, args, options) {
94
+ return new Promise((resolve2, reject) => {
95
+ const child = spawn(file, args, {
96
+ env: options.env,
97
+ stdio: ["pipe", "pipe", "pipe"],
98
+ windowsHide: true
99
+ });
100
+ const stdout = [];
101
+ const stderr = [];
102
+ let outputBytes = 0;
103
+ let failed = false;
104
+ let timedOut = false;
105
+ let overflow = false;
106
+ const timeout = setTimeout(() => {
107
+ timedOut = true;
108
+ child.kill("SIGTERM");
109
+ }, options.timeout);
110
+ const forceKill = setTimeout(() => child.kill("SIGKILL"), options.timeout + 1e3);
111
+ forceKill.unref();
112
+ const fail = (error) => {
113
+ if (failed) return;
114
+ failed = true;
115
+ clearTimeout(timeout);
116
+ clearTimeout(forceKill);
117
+ reject(error);
118
+ };
119
+ child.once("error", (error) => fail(error));
120
+ child.stdout.on("data", (chunk) => {
121
+ outputBytes += chunk.length;
122
+ if (outputBytes > options.maxBuffer) {
123
+ overflow = true;
124
+ child.kill("SIGTERM");
125
+ return;
126
+ }
127
+ stdout.push(chunk);
128
+ });
129
+ child.stderr.on("data", (chunk) => {
130
+ outputBytes += chunk.length;
131
+ if (outputBytes > options.maxBuffer) {
132
+ overflow = true;
133
+ child.kill("SIGTERM");
134
+ return;
135
+ }
136
+ stderr.push(chunk);
137
+ });
138
+ child.once("close", (code, signal) => {
139
+ clearTimeout(timeout);
140
+ clearTimeout(forceKill);
141
+ if (failed) return;
142
+ const result = {
143
+ stdout: Buffer.concat(stdout).toString("utf8"),
144
+ stderr: Buffer.concat(stderr).toString("utf8")
145
+ };
146
+ if (overflow) {
147
+ fail(Object.assign(new Error("Vaultwarden CLI output exceeded its limit"), result));
148
+ } else if (timedOut) {
149
+ fail(Object.assign(new Error("Vaultwarden CLI command timed out"), result));
150
+ } else if (code !== 0) {
151
+ fail(Object.assign(new Error(`Vaultwarden CLI exited with ${signal ?? code ?? "unknown status"}`), result));
152
+ } else {
153
+ resolve2(result);
154
+ }
155
+ });
156
+ child.stdin.on("error", () => {
157
+ });
158
+ child.stdin.end(options.input ?? "", "utf8");
159
+ });
160
+ }
161
+ function parseBwStatus(stdout) {
162
+ const parsed = JSON.parse(stdout);
163
+ return {
164
+ serverUrl: typeof parsed.serverUrl === "string" ? parsed.serverUrl : void 0,
165
+ status: typeof parsed.status === "string" ? parsed.status : void 0
166
+ };
167
+ }
168
+ function cliErrorText(error) {
169
+ if (typeof error === "string") {
170
+ return error;
171
+ }
172
+ if (!error || typeof error !== "object") {
173
+ return "";
174
+ }
175
+ const details = error;
176
+ return [details.message, details.stderr].filter((value) => typeof value === "string").join("\n");
177
+ }
178
+ function sessionRecoverySource(session, sessionEnv, inherited) {
179
+ if (typeof session === "string") return "inline";
180
+ if (session && typeof session === "object" && "source" in session) {
181
+ const source = session.source;
182
+ if (source === "file" || source === "env" || source === "exec" || source === "store") {
183
+ return source;
184
+ }
185
+ }
186
+ return inherited[sessionEnv] ? "env" : "default";
187
+ }
188
+ function recoveryInstruction(source) {
189
+ switch (source) {
190
+ case "file":
191
+ return "Refresh the file configured for the Vaultwarden SecretRef, then retry. For a single-value file provider, run `openclaw vaultwarden --session-file <configured-path>` in a trusted terminal; do not use that command on a JSON provider file.";
192
+ case "env":
193
+ return "Refresh the configured environment SecretRef in the Gateway's environment, then retry. `openclaw vaultwarden` writes a local file and does not replace an environment SecretRef.";
194
+ case "exec":
195
+ case "store":
196
+ return "Refresh the configured SecretRef at its provider, then retry. `openclaw vaultwarden` writes a local file and does not update that provider.";
197
+ case "inline":
198
+ return "Replace the configured session value with a freshly unlocked session using a secure local configuration path; do not paste it into chat. `openclaw vaultwarden` writes a local file but does not change inline configuration.";
199
+ default:
200
+ return "On the OpenClaw host, run `openclaw vaultwarden` in a trusted terminal to create the default local session file, then configure the plugin session as a file SecretRef to that file and retry. The CLI does not read the file automatically.";
201
+ }
202
+ }
203
+ var sessionFailurePattern = /(?:session|token|authentication|credential).{0,48}(?:expired|invalid|unauthori[sz]ed|rejected|not valid)|(?:expired|invalid|unauthori[sz]ed|rejected).{0,48}(?:session|token|credential)|not logged in|you are not logged in|vault is locked|vault must be unlocked|\b401\b/i;
204
+ function redactCliError(error) {
205
+ if (sessionFailurePattern.test(cliErrorText(error))) {
206
+ const source = error && typeof error === "object" && "recoverySource" in error ? error.recoverySource : void 0;
207
+ return `Vaultwarden session is unavailable or expired. ${recoveryInstruction(source ?? "default")} The plugin will not prompt for or expose the master password.`;
208
+ }
209
+ return "Vaultwarden CLI command failed";
210
+ }
211
+ function createBitwardenCliRunner(params) {
212
+ const sessionEnv = params?.sessionEnv ?? "BW_SESSION";
213
+ const timeout = (params?.timeoutSeconds ?? 15) * 1e3;
214
+ const inherited = params?.env ?? process.env;
215
+ const executor = params?.executor ?? execFileAsync;
216
+ const env = {
217
+ PATH: inherited.PATH
218
+ };
219
+ for (const key of [
220
+ "HOME",
221
+ "USERPROFILE",
222
+ "XDG_CONFIG_HOME",
223
+ "XDG_DATA_HOME",
224
+ sessionEnv,
225
+ "BITWARDENCLI_APPDATA_DIR",
226
+ "NODE_EXTRA_CA_CERTS"
227
+ ]) {
228
+ if (inherited[key]) {
229
+ env[key] = inherited[key];
230
+ }
231
+ }
232
+ return async (args, stdin) => {
233
+ const commandEnv = { ...env };
234
+ const config = typeof params?.config === "function" ? params.config() : params?.config;
235
+ const session = typeof params?.session === "function" ? params.session() : params?.session;
236
+ if (session !== void 0 && config) {
237
+ const resolved = await resolveConfiguredSecretInputString({
238
+ config,
239
+ env: inherited,
240
+ value: session,
241
+ path: "plugins.entries.vaultwarden.config.session"
242
+ });
243
+ if (resolved.value) {
244
+ commandEnv[sessionEnv] = resolved.value;
245
+ }
246
+ params?.onSessionResolution?.({
247
+ configured: true,
248
+ resolved: Boolean(resolved.value),
249
+ reason: resolved.unresolvedRefReason
250
+ });
251
+ } else {
252
+ params?.onSessionResolution?.({ configured: session !== void 0, resolved: false });
253
+ }
254
+ let result;
255
+ try {
256
+ const executionOptions = {
257
+ env: commandEnv,
258
+ timeout,
259
+ maxBuffer: 2 * 1024 * 1024,
260
+ ...stdin === void 0 ? {} : { input: stdin }
261
+ };
262
+ if (stdin === void 0) {
263
+ result = await executor("bw", args, executionOptions);
264
+ } else if (executor === execFileAsync) {
265
+ result = await execWithInput("bw", args, executionOptions);
266
+ } else {
267
+ result = await executor("bw", args, executionOptions);
268
+ }
269
+ } catch (error) {
270
+ const details = error && typeof error === "object" ? error : {};
271
+ const contextual = new Error(
272
+ typeof details.message === "string" ? details.message : "Vaultwarden CLI command failed"
273
+ );
274
+ if (typeof details.stderr === "string") contextual.stderr = details.stderr;
275
+ contextual.recoverySource = sessionRecoverySource(session, sessionEnv, inherited);
276
+ throw contextual;
277
+ }
278
+ return { stdout: result.stdout, stderr: result.stderr };
279
+ };
280
+ }
281
+
282
+ // src/status-tool.ts
283
+ import { Type } from "typebox";
284
+ import { textResult } from "openclaw/plugin-sdk/tool-results";
285
+
286
+ // src/audit.ts
287
+ function emitVaultwardenAudit(audit, operation, outcome) {
288
+ if (!audit) {
289
+ return;
290
+ }
291
+ try {
292
+ audit({ event: "vaultwarden.tool", operation, outcome });
293
+ } catch {
294
+ }
295
+ }
296
+
297
+ // src/status-tool.ts
298
+ function createVaultwardenStatusTool(runner, audit) {
299
+ return {
300
+ name: "vaultwarden_status",
301
+ label: "Vaultwarden Status",
302
+ description: "Check Vaultwarden connectivity, authentication, and lock state without returning vault contents or credentials.",
303
+ parameters: Type.Object({}, { additionalProperties: false }),
304
+ execute: async () => {
305
+ try {
306
+ const result = await runner(["status"]);
307
+ const status = parseBwStatus(result.stdout);
308
+ emitVaultwardenAudit(audit, "status", "success");
309
+ return textResult(JSON.stringify(status), status);
310
+ } catch (error) {
311
+ emitVaultwardenAudit(audit, "status", "error");
312
+ return Object.assign(textResult(redactCliError(error), { error: true }), { isError: true });
313
+ }
314
+ }
315
+ };
316
+ }
317
+
318
+ // src/vault-tools.ts
319
+ import { Type as Type2 } from "typebox";
320
+ import { textResult as textResult2 } from "openclaw/plugin-sdk/tool-results";
321
+
322
+ // src/vault.ts
323
+ var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
324
+ var MAX_ITEM_BYTES = 64 * 1024;
325
+ var MAX_SECRET_BYTES = 24 * 1024;
326
+ function checkedString(value, label, maxBytes = MAX_SECRET_BYTES) {
327
+ if (typeof value !== "string" || !value.trim() || /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/.test(value) || Buffer.byteLength(value, "utf8") > maxBytes) {
328
+ throw new Error(`Invalid ${label}`);
329
+ }
330
+ return value;
331
+ }
332
+ function checkedNote(value) {
333
+ if (typeof value !== "string" || !value.trim() || /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/.test(value) || Buffer.byteLength(value, "utf8") > MAX_SECRET_BYTES) {
334
+ throw new Error("Invalid notes");
335
+ }
336
+ return value;
337
+ }
338
+ function checkedId(value, label) {
339
+ if (typeof value !== "string" || !UUID.test(value)) {
340
+ throw new Error(`Invalid ${label}`);
341
+ }
342
+ return value;
343
+ }
344
+ function checkedUriList(uris) {
345
+ if (!Array.isArray(uris) || uris.length > 100) throw new Error("Invalid URIs");
346
+ return uris.map((uri) => ({ uri: checkedString(uri, "URI", 4096) }));
347
+ }
348
+ function checkedFields(fields) {
349
+ if (!Array.isArray(fields) || fields.length > 100) throw new Error("Invalid fields");
350
+ const names = /* @__PURE__ */ new Set();
351
+ return fields.map((value) => {
352
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Invalid field");
353
+ const field = value;
354
+ const name = checkedString(field.name, "field name", 256);
355
+ if (names.has(name)) throw new Error("Duplicate field name");
356
+ names.add(name);
357
+ if (field.type !== void 0 && field.type !== "text" && field.type !== "hidden") {
358
+ throw new Error("Invalid field type");
359
+ }
360
+ return {
361
+ name,
362
+ value: checkedString(field.value, "field value"),
363
+ type: field.type === "text" ? 0 : 1
364
+ };
365
+ });
366
+ }
367
+ function checkedCollectionIds(value) {
368
+ if (!Array.isArray(value) || value.length > 100) throw new Error("Invalid collections");
369
+ return value.map((id) => checkedId(id, "collection ID"));
370
+ }
371
+ function buildLoginVaultItem(input) {
372
+ if (!input || typeof input !== "object" || Array.isArray(input)) throw new Error("Invalid item");
373
+ const item = {
374
+ type: 1,
375
+ name: checkedString(input.name, "item name", 256)
376
+ };
377
+ const login = {};
378
+ if (input.username !== void 0) login.username = checkedString(input.username, "username");
379
+ if (input.password !== void 0) login.password = checkedString(input.password, "password");
380
+ if (input.uris !== void 0) login.uris = checkedUriList(input.uris);
381
+ if (Object.keys(login).length > 0) item.login = login;
382
+ if (input.notes !== void 0) item.notes = checkedNote(input.notes);
383
+ if (input.folderId !== void 0) {
384
+ item.folderId = input.folderId === null ? null : checkedId(input.folderId, "folder ID");
385
+ }
386
+ if (input.collectionIds !== void 0) item.collectionIds = checkedCollectionIds(input.collectionIds);
387
+ if (input.fields !== void 0) item.fields = checkedFields(input.fields);
388
+ if (input.favorite !== void 0) {
389
+ if (typeof input.favorite !== "boolean") throw new Error("Invalid favorite flag");
390
+ item.favorite = input.favorite;
391
+ }
392
+ return item;
393
+ }
394
+ function mergeLoginVaultItem(raw, patch) {
395
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) throw new Error("Invalid existing item");
396
+ const existing = raw;
397
+ const itemId = checkedId(existing.id, "item ID");
398
+ if (existing.type !== 1 || !existing.login || typeof existing.login !== "object") {
399
+ throw new Error("Only login items can be updated");
400
+ }
401
+ if (!patch || typeof patch !== "object" || Array.isArray(patch)) throw new Error("Invalid update");
402
+ const item = structuredClone(existing);
403
+ item.id = itemId;
404
+ if (patch.name !== void 0) item.name = checkedString(patch.name, "item name", 256);
405
+ if (patch.folderId !== void 0) {
406
+ item.folderId = patch.folderId === null ? null : checkedId(patch.folderId, "folder ID");
407
+ }
408
+ if (patch.collectionIds !== void 0) item.collectionIds = checkedCollectionIds(patch.collectionIds);
409
+ if (patch.fields !== void 0) item.fields = checkedFields(patch.fields);
410
+ if (patch.favorite !== void 0) {
411
+ if (typeof patch.favorite !== "boolean") throw new Error("Invalid favorite flag");
412
+ item.favorite = patch.favorite;
413
+ }
414
+ if (patch.notes !== void 0) item.notes = patch.notes === null ? null : checkedNote(patch.notes);
415
+ const login = { ...item.login ?? {} };
416
+ if (patch.username !== void 0) {
417
+ login.username = patch.username === null ? null : checkedString(patch.username, "username");
418
+ }
419
+ if (patch.password !== void 0) {
420
+ login.password = patch.password === null ? null : checkedString(patch.password, "password");
421
+ }
422
+ if (patch.uris !== void 0) login.uris = checkedUriList(patch.uris);
423
+ item.login = login;
424
+ const changed = Object.keys(patch).some((key) => key !== "itemId");
425
+ if (!changed) throw new Error("No item fields were supplied");
426
+ if (Buffer.byteLength(JSON.stringify(item), "utf8") > MAX_ITEM_BYTES) throw new Error("Item is too large");
427
+ return item;
428
+ }
429
+ function encodeVaultItem(item) {
430
+ const serialized = JSON.stringify(item);
431
+ if (Buffer.byteLength(serialized, "utf8") > MAX_ITEM_BYTES) throw new Error("Item is too large");
432
+ return Buffer.from(serialized, "utf8").toString("base64");
433
+ }
434
+ function parseMutationItem(stdout) {
435
+ const raw = JSON.parse(stdout);
436
+ const safe = redactVaultItem(raw);
437
+ if (!UUID.test(safe.id) || safe.name === "Unnamed item") throw new Error("Invalid item response");
438
+ return safe;
439
+ }
440
+ async function createLoginVaultItem(params) {
441
+ const item = buildLoginVaultItem(params.item);
442
+ const result = await params.runner(["create", "item"], encodeVaultItem(item));
443
+ return parseMutationItem(result.stdout);
444
+ }
445
+ async function updateLoginVaultItem(params) {
446
+ const itemId = checkedId(params.itemId, "item ID");
447
+ const current = await params.runner(["get", "item", itemId]);
448
+ const item = mergeLoginVaultItem(JSON.parse(current.stdout), params.patch);
449
+ const result = await params.runner(["edit", "item", itemId], encodeVaultItem(item));
450
+ return parseMutationItem(result.stdout);
451
+ }
452
+ async function deleteLoginVaultItem(params) {
453
+ const itemId = checkedId(params.itemId, "item ID");
454
+ await params.runner(["delete", "item", itemId]);
455
+ return { id: itemId, deleted: true };
456
+ }
457
+ function stringValue(value) {
458
+ return typeof value === "string" && value.length > 0 ? value : void 0;
459
+ }
460
+ function redactVaultItem(raw) {
461
+ const item = {
462
+ id: stringValue(raw.id) ?? "unknown",
463
+ name: stringValue(raw.name) ?? "Unnamed item"
464
+ };
465
+ if (typeof raw.type === "number") {
466
+ item.type = raw.type;
467
+ }
468
+ if (raw.folderId === null || typeof raw.folderId === "string") {
469
+ item.folderId = raw.folderId;
470
+ }
471
+ if (Array.isArray(raw.collectionIds)) {
472
+ item.collectionIds = raw.collectionIds.filter(
473
+ (value) => typeof value === "string"
474
+ );
475
+ }
476
+ if (raw.login && Array.isArray(raw.login.uris)) {
477
+ const uris = raw.login.uris.map((entry) => stringValue(entry.uri)).filter((value) => Boolean(value));
478
+ if (uris.length > 0) {
479
+ item.login = { uris };
480
+ }
481
+ }
482
+ return item;
483
+ }
484
+ function parseItemList(stdout, maxResults) {
485
+ const parsed = JSON.parse(stdout);
486
+ if (!Array.isArray(parsed)) {
487
+ throw new Error("Vaultwarden returned an invalid item list");
488
+ }
489
+ return parsed.slice(0, maxResults).map((item) => redactVaultItem(item));
490
+ }
491
+ async function searchVaultItems(params) {
492
+ const result = await params.runner(["list", "items", "--search", params.query]);
493
+ return parseItemList(result.stdout, params.maxResults);
494
+ }
495
+ async function getVaultItem(params) {
496
+ const result = await params.runner(["get", "item", params.itemId]);
497
+ return redactVaultItem(JSON.parse(result.stdout));
498
+ }
499
+ function parseVaultObjects(stdout, maxResults) {
500
+ const parsed = JSON.parse(stdout);
501
+ if (!Array.isArray(parsed)) {
502
+ throw new Error("Vaultwarden returned an invalid object list");
503
+ }
504
+ return parsed.slice(0, maxResults).map((value) => {
505
+ const raw = value;
506
+ const object = {
507
+ id: stringValue(raw.id) ?? "unknown",
508
+ name: stringValue(raw.name) ?? "Unnamed object"
509
+ };
510
+ const organizationId = stringValue(raw.organizationId);
511
+ if (organizationId) {
512
+ object.organizationId = organizationId;
513
+ }
514
+ return object;
515
+ });
516
+ }
517
+ async function listVaultObjects(params) {
518
+ const result = await params.runner(["list", params.object]);
519
+ return parseVaultObjects(result.stdout, params.maxResults);
520
+ }
521
+
522
+ // src/vault-tools.ts
523
+ function jsonToolResult(value) {
524
+ return textResult2(JSON.stringify(value), value);
525
+ }
526
+ function errorToolResult(error) {
527
+ return Object.assign(textResult2(redactCliError(error), { error: true }), { isError: true });
528
+ }
529
+ var UUID_PATTERN = "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$";
530
+ var uuidSchema = Type2.String({ pattern: UUID_PATTERN });
531
+ var itemIdSchema = Type2.String({ pattern: UUID_PATTERN, description: "Exact Vaultwarden item UUID." });
532
+ var optionalText = (description) => Type2.Optional(Type2.String({ maxLength: 24576, description }));
533
+ var optionalNullableText = (description) => Type2.Optional(Type2.Union([Type2.String({ maxLength: 24576, description }), Type2.Null()]));
534
+ var uriArray = Type2.Array(Type2.String({ minLength: 1, maxLength: 4096 }), { maxItems: 100 });
535
+ var fieldArray = Type2.Array(
536
+ Type2.Object(
537
+ {
538
+ name: Type2.String({ minLength: 1, maxLength: 256 }),
539
+ value: Type2.String({ minLength: 1, maxLength: 24576, description: "Stored in the vault; never returned by this tool." }),
540
+ type: Type2.Optional(Type2.Union([Type2.Literal("hidden"), Type2.Literal("text")]))
541
+ },
542
+ { additionalProperties: false }
543
+ ),
544
+ { maxItems: 100 }
545
+ );
546
+ var loginItemFields = {
547
+ name: Type2.String({ minLength: 1, maxLength: 256, description: "Vault item name." }),
548
+ username: optionalText("Stored in the vault; not returned by plugin read tools."),
549
+ password: optionalText("Stored in the vault; never returned by this tool."),
550
+ uris: Type2.Optional(uriArray),
551
+ notes: optionalText("Stored in the vault; never returned by this tool."),
552
+ folderId: Type2.Optional(Type2.Union([Type2.String({ pattern: UUID_PATTERN }), Type2.Null()])),
553
+ collectionIds: Type2.Optional(Type2.Array(uuidSchema, { maxItems: 100 })),
554
+ fields: Type2.Optional(fieldArray),
555
+ favorite: Type2.Optional(Type2.Boolean())
556
+ };
557
+ function createVaultwardenCreateItemTool(runner, audit) {
558
+ return {
559
+ name: "vaultwarden_create_item",
560
+ label: "Vaultwarden Create Login Item",
561
+ description: "Create one login item when mutations are enabled. Secret fields are sent to the local Bitwarden CLI over stdin and are never returned or audited. Use only when explicitly requested.",
562
+ parameters: Type2.Object(loginItemFields, { additionalProperties: false }),
563
+ execute: async (_id, params) => {
564
+ try {
565
+ const result = await createLoginVaultItem({ runner, item: params });
566
+ emitVaultwardenAudit(audit, "create_item", "success");
567
+ return jsonToolResult(result);
568
+ } catch (error) {
569
+ emitVaultwardenAudit(audit, "create_item", "error");
570
+ return errorToolResult(error);
571
+ }
572
+ }
573
+ };
574
+ }
575
+ function createVaultwardenUpdateItemTool(runner, audit) {
576
+ const patchFields = {
577
+ name: Type2.Optional(Type2.String({ minLength: 1, maxLength: 256 })),
578
+ username: optionalNullableText("Replace username; null clears it, omitted fields remain unchanged."),
579
+ password: optionalNullableText("Replace password; null clears it, and it is never returned by this tool."),
580
+ uris: Type2.Optional(uriArray),
581
+ notes: optionalNullableText("Replace notes; null clears them, and they are never returned by this tool."),
582
+ folderId: Type2.Optional(Type2.Union([Type2.String({ pattern: UUID_PATTERN }), Type2.Null()])),
583
+ collectionIds: Type2.Optional(Type2.Array(uuidSchema, { maxItems: 100 })),
584
+ fields: Type2.Optional(fieldArray),
585
+ favorite: Type2.Optional(Type2.Boolean())
586
+ };
587
+ return {
588
+ name: "vaultwarden_update_item",
589
+ label: "Vaultwarden Update Login Item",
590
+ description: "Update supplied fields on one exact login item. Unspecified fields are preserved. Secret fields are sent to the local Bitwarden CLI over stdin and are never returned or audited. Use only when explicitly requested.",
591
+ parameters: Type2.Object(
592
+ { itemId: itemIdSchema, ...patchFields },
593
+ { additionalProperties: false }
594
+ ),
595
+ execute: async (_id, params) => {
596
+ try {
597
+ const { itemId, ...patch } = params;
598
+ const result = await updateLoginVaultItem({ runner, itemId, patch });
599
+ emitVaultwardenAudit(audit, "update_item", "success");
600
+ return jsonToolResult(result);
601
+ } catch (error) {
602
+ emitVaultwardenAudit(audit, "update_item", "error");
603
+ return errorToolResult(error);
604
+ }
605
+ }
606
+ };
607
+ }
608
+ function createVaultwardenDeleteItemTool(runner, audit) {
609
+ return {
610
+ name: "vaultwarden_delete_item",
611
+ label: "Vaultwarden Delete Login Item",
612
+ description: "Soft-delete one login item. Requires confirmation equal to the exact item UUID; hard/permanent deletion is not supported. Use only when explicitly requested.",
613
+ parameters: Type2.Object(
614
+ {
615
+ itemId: itemIdSchema,
616
+ confirmation: Type2.String({ minLength: 1, description: "Must exactly match itemId." })
617
+ },
618
+ { additionalProperties: false }
619
+ ),
620
+ execute: async (_id, params) => {
621
+ try {
622
+ if (params.confirmation !== params.itemId) throw new Error("Confirmation mismatch");
623
+ const result = await deleteLoginVaultItem({ runner, itemId: params.itemId });
624
+ emitVaultwardenAudit(audit, "delete_item", "success");
625
+ return jsonToolResult(result);
626
+ } catch (error) {
627
+ emitVaultwardenAudit(audit, "delete_item", "error");
628
+ return errorToolResult(error);
629
+ }
630
+ }
631
+ };
632
+ }
633
+ function createVaultwardenMutationTools(runner, enabled, audit) {
634
+ if (enabled !== true) return [];
635
+ return [
636
+ createVaultwardenCreateItemTool(runner, audit),
637
+ createVaultwardenUpdateItemTool(runner, audit),
638
+ createVaultwardenDeleteItemTool(runner, audit)
639
+ ];
640
+ }
641
+ function createVaultwardenSearchTool(runner, maxResults, audit) {
642
+ return {
643
+ name: "vaultwarden_search",
644
+ label: "Vaultwarden Search",
645
+ description: "Search Vaultwarden item metadata without returning passwords, TOTP seeds, or secure notes.",
646
+ parameters: Type2.Object(
647
+ { query: Type2.String({ minLength: 1, description: "Item name or metadata to search for." }) },
648
+ { additionalProperties: false }
649
+ ),
650
+ execute: async (_id, params) => {
651
+ try {
652
+ const result = await searchVaultItems({ runner, query: params.query, maxResults });
653
+ emitVaultwardenAudit(audit, "search", "success");
654
+ return jsonToolResult(result);
655
+ } catch (error) {
656
+ emitVaultwardenAudit(audit, "search", "error");
657
+ return errorToolResult(error);
658
+ }
659
+ }
660
+ };
661
+ }
662
+ function createVaultwardenGetItemTool(runner, audit) {
663
+ return {
664
+ name: "vaultwarden_get_item",
665
+ label: "Vaultwarden Get Item",
666
+ description: "Retrieve one Vaultwarden item by ID, returning safe metadata only.",
667
+ parameters: Type2.Object(
668
+ { itemId: Type2.String({ minLength: 1, description: "Stable Vaultwarden item ID." }) },
669
+ { additionalProperties: false }
670
+ ),
671
+ execute: async (_id, params) => {
672
+ try {
673
+ const result = await getVaultItem({ runner, itemId: params.itemId });
674
+ emitVaultwardenAudit(audit, "get_item", "success");
675
+ return jsonToolResult(result);
676
+ } catch (error) {
677
+ emitVaultwardenAudit(audit, "get_item", "error");
678
+ return errorToolResult(error);
679
+ }
680
+ }
681
+ };
682
+ }
683
+ function createListTool(runner, object, maxResults, audit) {
684
+ const label = object === "folders" ? "Folders" : "Collections";
685
+ return {
686
+ name: `vaultwarden_list_${object}`,
687
+ label: `Vaultwarden ${label}`,
688
+ description: `List Vaultwarden ${object} metadata without returning vault item contents.`,
689
+ parameters: Type2.Object({}, { additionalProperties: false }),
690
+ execute: async () => {
691
+ try {
692
+ const result = await listVaultObjects({ runner, object, maxResults });
693
+ emitVaultwardenAudit(audit, `list_${object}`, "success");
694
+ return jsonToolResult(result);
695
+ } catch (error) {
696
+ emitVaultwardenAudit(audit, `list_${object}`, "error");
697
+ return errorToolResult(error);
698
+ }
699
+ }
700
+ };
701
+ }
702
+ function createVaultwardenListFoldersTool(runner, maxResults, audit) {
703
+ return createListTool(runner, "folders", maxResults, audit);
704
+ }
705
+ function createVaultwardenListCollectionsTool(runner, maxResults, audit) {
706
+ return createListTool(runner, "collections", maxResults, audit);
707
+ }
708
+
709
+ // index.ts
710
+ var index_default = definePluginEntry({
711
+ id: "vaultwarden",
712
+ name: "Vaultwarden",
713
+ description: "Bitwarden-compatible Vaultwarden tools for OpenClaw",
714
+ register(api) {
715
+ api.registerCli?.(({ program }) => registerVaultwardenAuthCli(program), {
716
+ commands: ["vaultwarden"]
717
+ });
718
+ const config = api.pluginConfig;
719
+ const configuredSession = api.config.plugins?.entries?.vaultwarden?.config?.session;
720
+ const runner = createBitwardenCliRunner({
721
+ ...config,
722
+ session: configuredSession ?? config.session,
723
+ config: api.config,
724
+ onSessionResolution: (result) => api.logger.info(
725
+ JSON.stringify({
726
+ event: "vaultwarden_session_resolution",
727
+ ...result
728
+ })
729
+ )
730
+ });
731
+ const audit = (event) => {
732
+ api.logger.info(JSON.stringify(event));
733
+ };
734
+ api.registerTool(createVaultwardenStatusTool(runner, audit), { name: "vaultwarden_status" });
735
+ api.registerTool(createVaultwardenSearchTool(runner, config.maxResults ?? 20, audit), {
736
+ name: "vaultwarden_search"
737
+ });
738
+ api.registerTool(createVaultwardenGetItemTool(runner, audit), { name: "vaultwarden_get_item" });
739
+ api.registerTool(createVaultwardenListFoldersTool(runner, config.maxResults ?? 20, audit), {
740
+ name: "vaultwarden_list_folders"
741
+ });
742
+ api.registerTool(createVaultwardenListCollectionsTool(runner, config.maxResults ?? 20, audit), {
743
+ name: "vaultwarden_list_collections"
744
+ });
745
+ for (const tool of createVaultwardenMutationTools(runner, config.allowMutations === true, audit)) {
746
+ api.registerTool(tool, { name: tool.name });
747
+ }
748
+ }
749
+ });
750
+ export {
751
+ index_default as default
752
+ };