@arloraccoon/openclaw-vaultwarden-plugin 0.1.2 → 0.1.3
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/README.md +10 -0
- package/dist/index.mjs +66 -19
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -59,6 +59,7 @@ cd "$REPO_DIR"
|
|
|
59
59
|
git pull --ff-only
|
|
60
60
|
pnpm install --frozen-lockfile
|
|
61
61
|
pnpm build
|
|
62
|
+
pnpm test:package
|
|
62
63
|
openclaw plugins install "$REPO_DIR" --link --force --accept-capabilities
|
|
63
64
|
openclaw config validate
|
|
64
65
|
openclaw plugins reload vaultwarden --accept-capabilities
|
|
@@ -324,3 +325,12 @@ SecretRef provider); OpenClaw's `plugins validate` authoring check is for
|
|
|
324
325
|
`defineToolPlugin`-style tool-only packages. Use the installed runtime smoke
|
|
325
326
|
test for this package instead of treating that tool-only check as a generic
|
|
326
327
|
plugin validity gate.
|
|
328
|
+
|
|
329
|
+
## Release synchronization
|
|
330
|
+
|
|
331
|
+
Forgejo is the canonical development repository; the public GitHub mirror
|
|
332
|
+
excludes `.beads/`, `.crew/`, and generated `dist/` files. npm and ClawHub must
|
|
333
|
+
carry the same semver version. Release tags run `publish-npm.yml` through npm
|
|
334
|
+
Trusted Publishing (OIDC); do not add registry tokens to GitHub secrets. Run
|
|
335
|
+
`RELEASE-CHECKLIST.md` before creating a tag and verify both registries after
|
|
336
|
+
publication.
|
package/dist/index.mjs
CHANGED
|
@@ -186,25 +186,51 @@ function sessionRecoverySource(session, sessionEnv, inherited) {
|
|
|
186
186
|
return inherited[sessionEnv] ? "env" : "default";
|
|
187
187
|
}
|
|
188
188
|
function recoveryInstruction(source) {
|
|
189
|
+
const statusCheck = "First verify the host with `bw status --raw`;";
|
|
189
190
|
switch (source) {
|
|
190
191
|
case "file":
|
|
191
|
-
return
|
|
192
|
+
return `${statusCheck} 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
193
|
case "env":
|
|
193
|
-
return
|
|
194
|
+
return `${statusCheck} 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
195
|
case "exec":
|
|
195
196
|
case "store":
|
|
196
|
-
return
|
|
197
|
+
return `${statusCheck} Refresh the configured SecretRef at its provider, then retry. \`openclaw vaultwarden\` writes a local file and does not update that provider.`;
|
|
197
198
|
case "inline":
|
|
198
|
-
return
|
|
199
|
+
return `${statusCheck} 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
200
|
default:
|
|
200
|
-
return
|
|
201
|
+
return `${statusCheck} 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
|
}
|
|
202
203
|
}
|
|
203
204
|
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;
|
|
205
|
+
function classifySessionFailure(error) {
|
|
206
|
+
const text = cliErrorText(error);
|
|
207
|
+
if (!sessionFailurePattern.test(text)) return void 0;
|
|
208
|
+
if (/expired|session.*(?:invalid|not valid)|(?:invalid|not valid).*session/i.test(text)) {
|
|
209
|
+
return "expired";
|
|
210
|
+
}
|
|
211
|
+
if (/vault (?:is )?locked|vault must be unlocked|locked/i.test(text)) return "locked";
|
|
212
|
+
if (/not logged in|unauthenticated|\b401\b|unauthori[sz]ed/i.test(text)) {
|
|
213
|
+
return "unauthenticated";
|
|
214
|
+
}
|
|
215
|
+
return "unavailable";
|
|
216
|
+
}
|
|
217
|
+
function sessionFailurePrefix(kind) {
|
|
218
|
+
switch (kind) {
|
|
219
|
+
case "locked":
|
|
220
|
+
return "Vaultwarden vault is locked";
|
|
221
|
+
case "unauthenticated":
|
|
222
|
+
return "Vaultwarden authentication is unavailable";
|
|
223
|
+
case "unavailable":
|
|
224
|
+
return "Vaultwarden session could not be used";
|
|
225
|
+
default:
|
|
226
|
+
return "Vaultwarden session is expired";
|
|
227
|
+
}
|
|
228
|
+
}
|
|
204
229
|
function redactCliError(error) {
|
|
205
|
-
|
|
230
|
+
const failureKind = classifySessionFailure(error);
|
|
231
|
+
if (failureKind) {
|
|
206
232
|
const source = error && typeof error === "object" && "recoverySource" in error ? error.recoverySource : void 0;
|
|
207
|
-
return
|
|
233
|
+
return `${sessionFailurePrefix(failureKind)}. ${recoveryInstruction(source ?? "default")} The plugin will not prompt for or expose the master password.`;
|
|
208
234
|
}
|
|
209
235
|
return "Vaultwarden CLI command failed";
|
|
210
236
|
}
|
|
@@ -284,12 +310,13 @@ import { Type } from "typebox";
|
|
|
284
310
|
import { textResult } from "openclaw/plugin-sdk/tool-results";
|
|
285
311
|
|
|
286
312
|
// src/audit.ts
|
|
287
|
-
function emitVaultwardenAudit(audit, operation, outcome) {
|
|
313
|
+
function emitVaultwardenAudit(audit, operation, outcome, itemId) {
|
|
288
314
|
if (!audit) {
|
|
289
315
|
return;
|
|
290
316
|
}
|
|
291
317
|
try {
|
|
292
|
-
|
|
318
|
+
const safeItemId = itemId && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(itemId) ? itemId : void 0;
|
|
319
|
+
audit({ event: "vaultwarden.tool", operation, outcome, ...safeItemId ? { itemId: safeItemId } : {} });
|
|
293
320
|
} catch {
|
|
294
321
|
}
|
|
295
322
|
}
|
|
@@ -323,6 +350,22 @@ import { textResult as textResult2 } from "openclaw/plugin-sdk/tool-results";
|
|
|
323
350
|
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
324
351
|
var MAX_ITEM_BYTES = 64 * 1024;
|
|
325
352
|
var MAX_SECRET_BYTES = 24 * 1024;
|
|
353
|
+
var mutationLocks = /* @__PURE__ */ new Map();
|
|
354
|
+
async function withMutationLock(itemId, operation) {
|
|
355
|
+
const previous = mutationLocks.get(itemId) ?? Promise.resolve();
|
|
356
|
+
let release;
|
|
357
|
+
const current = new Promise((resolve2) => {
|
|
358
|
+
release = resolve2;
|
|
359
|
+
});
|
|
360
|
+
mutationLocks.set(itemId, current);
|
|
361
|
+
await previous;
|
|
362
|
+
try {
|
|
363
|
+
return await operation();
|
|
364
|
+
} finally {
|
|
365
|
+
release();
|
|
366
|
+
if (mutationLocks.get(itemId) === current) mutationLocks.delete(itemId);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
326
369
|
function checkedString(value, label, maxBytes = MAX_SECRET_BYTES) {
|
|
327
370
|
if (typeof value !== "string" || !value.trim() || /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/.test(value) || Buffer.byteLength(value, "utf8") > maxBytes) {
|
|
328
371
|
throw new Error(`Invalid ${label}`);
|
|
@@ -444,15 +487,19 @@ async function createLoginVaultItem(params) {
|
|
|
444
487
|
}
|
|
445
488
|
async function updateLoginVaultItem(params) {
|
|
446
489
|
const itemId = checkedId(params.itemId, "item ID");
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
490
|
+
return withMutationLock(itemId, async () => {
|
|
491
|
+
const current = await params.runner(["get", "item", itemId]);
|
|
492
|
+
const item = mergeLoginVaultItem(JSON.parse(current.stdout), params.patch);
|
|
493
|
+
const result = await params.runner(["edit", "item", itemId], encodeVaultItem(item));
|
|
494
|
+
return parseMutationItem(result.stdout);
|
|
495
|
+
});
|
|
451
496
|
}
|
|
452
497
|
async function deleteLoginVaultItem(params) {
|
|
453
498
|
const itemId = checkedId(params.itemId, "item ID");
|
|
454
|
-
|
|
455
|
-
|
|
499
|
+
return withMutationLock(itemId, async () => {
|
|
500
|
+
await params.runner(["delete", "item", itemId]);
|
|
501
|
+
return { id: itemId, deleted: true };
|
|
502
|
+
});
|
|
456
503
|
}
|
|
457
504
|
function stringValue(value) {
|
|
458
505
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
@@ -596,10 +643,10 @@ function createVaultwardenUpdateItemTool(runner, audit) {
|
|
|
596
643
|
try {
|
|
597
644
|
const { itemId, ...patch } = params;
|
|
598
645
|
const result = await updateLoginVaultItem({ runner, itemId, patch });
|
|
599
|
-
emitVaultwardenAudit(audit, "update_item", "success");
|
|
646
|
+
emitVaultwardenAudit(audit, "update_item", "success", itemId);
|
|
600
647
|
return jsonToolResult(result);
|
|
601
648
|
} catch (error) {
|
|
602
|
-
emitVaultwardenAudit(audit, "update_item", "error");
|
|
649
|
+
emitVaultwardenAudit(audit, "update_item", "error", params.itemId);
|
|
603
650
|
return errorToolResult(error);
|
|
604
651
|
}
|
|
605
652
|
}
|
|
@@ -621,10 +668,10 @@ function createVaultwardenDeleteItemTool(runner, audit) {
|
|
|
621
668
|
try {
|
|
622
669
|
if (params.confirmation !== params.itemId) throw new Error("Confirmation mismatch");
|
|
623
670
|
const result = await deleteLoginVaultItem({ runner, itemId: params.itemId });
|
|
624
|
-
emitVaultwardenAudit(audit, "delete_item", "success");
|
|
671
|
+
emitVaultwardenAudit(audit, "delete_item", "success", params.itemId);
|
|
625
672
|
return jsonToolResult(result);
|
|
626
673
|
} catch (error) {
|
|
627
|
-
emitVaultwardenAudit(audit, "delete_item", "error");
|
|
674
|
+
emitVaultwardenAudit(audit, "delete_item", "error", params.itemId);
|
|
628
675
|
return errorToolResult(error);
|
|
629
676
|
}
|
|
630
677
|
}
|
package/package.json
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arloraccoon/openclaw-vaultwarden-plugin",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Metadata-safe Vaultwarden tools for OpenClaw with opt-in item mutations",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"build": "esbuild index.ts --bundle --platform=node --format=esm --external:openclaw '--external:openclaw/*' --external:typebox --outfile=dist/index.mjs && esbuild src/secret-resolver-entry.ts --bundle --platform=node --format=esm --outfile=dist/secret-resolver.mjs",
|
|
7
7
|
"test": "vitest run src/*.test.ts",
|
|
8
|
+
"test:package": "bash tests/package-smoke.sh",
|
|
8
9
|
"typecheck": "tsc --noEmit",
|
|
9
10
|
"lint": "eslint . --max-warnings 0"
|
|
10
11
|
},
|
|
11
12
|
"repository": {
|
|
12
13
|
"type": "git",
|
|
13
|
-
"url": "https://github.com/ArloTheRaccoon/openclaw-vaultwarden-plugin"
|
|
14
|
+
"url": "git+https://github.com/ArloTheRaccoon/openclaw-vaultwarden-plugin.git"
|
|
14
15
|
},
|
|
16
|
+
"packageManager": "pnpm@10.33.2",
|
|
15
17
|
"license": "MIT",
|
|
16
18
|
"type": "module",
|
|
17
19
|
"main": "./dist/index.mjs",
|