@membox-cloud/membox 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/README.md +169 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +159 -0
- package/dist/src/api/account.d.ts +3 -0
- package/dist/src/api/account.js +3 -0
- package/dist/src/api/client.d.ts +21 -0
- package/dist/src/api/client.js +107 -0
- package/dist/src/api/device-flow.d.ts +9 -0
- package/dist/src/api/device-flow.js +55 -0
- package/dist/src/api/devices.d.ts +10 -0
- package/dist/src/api/devices.js +24 -0
- package/dist/src/api/recovery.d.ts +5 -0
- package/dist/src/api/recovery.js +9 -0
- package/dist/src/api/sync.d.ts +9 -0
- package/dist/src/api/sync.js +22 -0
- package/dist/src/cli/bootstrap.d.ts +37 -0
- package/dist/src/cli/bootstrap.js +326 -0
- package/dist/src/cli/grants.d.ts +8 -0
- package/dist/src/cli/grants.js +76 -0
- package/dist/src/cli/helpers.d.ts +6 -0
- package/dist/src/cli/helpers.js +13 -0
- package/dist/src/cli/passphrase-input.d.ts +11 -0
- package/dist/src/cli/passphrase-input.js +94 -0
- package/dist/src/cli/pause.d.ts +2 -0
- package/dist/src/cli/pause.js +29 -0
- package/dist/src/cli/provisioning.d.ts +3 -0
- package/dist/src/cli/provisioning.js +30 -0
- package/dist/src/cli/pull.d.ts +31 -0
- package/dist/src/cli/pull.js +142 -0
- package/dist/src/cli/restore.d.ts +12 -0
- package/dist/src/cli/restore.js +90 -0
- package/dist/src/cli/setup.d.ts +17 -0
- package/dist/src/cli/setup.js +209 -0
- package/dist/src/cli/status.d.ts +1 -0
- package/dist/src/cli/status.js +31 -0
- package/dist/src/cli/sync.d.ts +4 -0
- package/dist/src/cli/sync.js +124 -0
- package/dist/src/cli/unlock.d.ts +19 -0
- package/dist/src/cli/unlock.js +153 -0
- package/dist/src/config.d.ts +4 -0
- package/dist/src/config.js +12 -0
- package/dist/src/constants.d.ts +23 -0
- package/dist/src/constants.js +27 -0
- package/dist/src/contract-types.d.ts +301 -0
- package/dist/src/contract-types.js +52 -0
- package/dist/src/crypto/aes-gcm.d.ts +29 -0
- package/dist/src/crypto/aes-gcm.js +44 -0
- package/dist/src/crypto/device-keys.d.ts +18 -0
- package/dist/src/crypto/device-keys.js +25 -0
- package/dist/src/crypto/grant.d.ts +29 -0
- package/dist/src/crypto/grant.js +87 -0
- package/dist/src/crypto/kdf.d.ts +16 -0
- package/dist/src/crypto/kdf.js +24 -0
- package/dist/src/crypto/keys.d.ts +14 -0
- package/dist/src/crypto/keys.js +35 -0
- package/dist/src/crypto/manifest.d.ts +25 -0
- package/dist/src/crypto/manifest.js +41 -0
- package/dist/src/crypto/recovery.d.ts +16 -0
- package/dist/src/crypto/recovery.js +94 -0
- package/dist/src/crypto/types.d.ts +34 -0
- package/dist/src/crypto/types.js +1 -0
- package/dist/src/debug-logger.d.ts +32 -0
- package/dist/src/debug-logger.js +108 -0
- package/dist/src/hooks/gateway-lifecycle.d.ts +6 -0
- package/dist/src/hooks/gateway-lifecycle.js +40 -0
- package/dist/src/hooks/prompt-inject.d.ts +7 -0
- package/dist/src/hooks/prompt-inject.js +18 -0
- package/dist/src/store/keychain.d.ts +26 -0
- package/dist/src/store/keychain.js +151 -0
- package/dist/src/store/local-state.d.ts +27 -0
- package/dist/src/store/local-state.js +47 -0
- package/dist/src/store/managed-unlock.d.ts +8 -0
- package/dist/src/store/managed-unlock.js +46 -0
- package/dist/src/store/pending-setup.d.ts +23 -0
- package/dist/src/store/pending-setup.js +32 -0
- package/dist/src/store/session.d.ts +13 -0
- package/dist/src/store/session.js +28 -0
- package/dist/src/sync/auto-sync.d.ts +12 -0
- package/dist/src/sync/auto-sync.js +82 -0
- package/dist/src/sync/conflict.d.ts +24 -0
- package/dist/src/sync/conflict.js +92 -0
- package/dist/src/sync/diff.d.ts +25 -0
- package/dist/src/sync/diff.js +75 -0
- package/dist/src/sync/downloader.d.ts +16 -0
- package/dist/src/sync/downloader.js +73 -0
- package/dist/src/sync/scanner.d.ts +12 -0
- package/dist/src/sync/scanner.js +52 -0
- package/dist/src/sync/state.d.ts +4 -0
- package/dist/src/sync/state.js +22 -0
- package/dist/src/sync/uploader.d.ts +20 -0
- package/dist/src/sync/uploader.js +86 -0
- package/dist/src/tools/grants-approve-pending.d.ts +17 -0
- package/dist/src/tools/grants-approve-pending.js +50 -0
- package/dist/src/tools/pull.d.ts +31 -0
- package/dist/src/tools/pull.js +71 -0
- package/dist/src/tools/restore.d.ts +31 -0
- package/dist/src/tools/restore.js +96 -0
- package/dist/src/tools/result.d.ts +7 -0
- package/dist/src/tools/result.js +6 -0
- package/dist/src/tools/secret-file.d.ts +10 -0
- package/dist/src/tools/secret-file.js +37 -0
- package/dist/src/tools/setup-finish.d.ts +36 -0
- package/dist/src/tools/setup-finish.js +108 -0
- package/dist/src/tools/setup-poll.d.ts +27 -0
- package/dist/src/tools/setup-poll.js +83 -0
- package/dist/src/tools/setup-start.d.ts +18 -0
- package/dist/src/tools/setup-start.js +49 -0
- package/dist/src/tools/status.d.ts +17 -0
- package/dist/src/tools/status.js +40 -0
- package/dist/src/tools/sync.d.ts +17 -0
- package/dist/src/tools/sync.js +49 -0
- package/dist/src/tools/unlock-secret.d.ts +42 -0
- package/dist/src/tools/unlock-secret.js +87 -0
- package/dist/src/tools/unlock.d.ts +25 -0
- package/dist/src/tools/unlock.js +72 -0
- package/openclaw.plugin.json +16 -0
- package/package.json +35 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare function readSecretFile(pathValue: string, options: {
|
|
2
|
+
label: string;
|
|
3
|
+
trimWhitespace?: boolean;
|
|
4
|
+
}): Promise<{
|
|
5
|
+
value: string;
|
|
6
|
+
resolvedPath: string;
|
|
7
|
+
}>;
|
|
8
|
+
export declare function writeSecretFile(pathValue: string, value: string): Promise<{
|
|
9
|
+
resolvedPath: string;
|
|
10
|
+
}>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { mkdir, readFile, rename, stat, writeFile, chmod } from "node:fs/promises";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
function ensurePrivatePermissions(mode, label, path) {
|
|
4
|
+
if (process.platform === "win32") {
|
|
5
|
+
return;
|
|
6
|
+
}
|
|
7
|
+
if ((mode & 0o077) !== 0) {
|
|
8
|
+
throw new Error(`${label} file must not be readable by group or others: ${path}. Run \`chmod 600 ${path}\` first.`);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export async function readSecretFile(pathValue, options) {
|
|
12
|
+
const resolvedPath = resolve(process.cwd(), pathValue);
|
|
13
|
+
const info = await stat(resolvedPath);
|
|
14
|
+
if (!info.isFile()) {
|
|
15
|
+
throw new Error(`${options.label} path is not a file: ${resolvedPath}`);
|
|
16
|
+
}
|
|
17
|
+
ensurePrivatePermissions(info.mode, options.label, resolvedPath);
|
|
18
|
+
const raw = await readFile(resolvedPath, "utf8");
|
|
19
|
+
const value = options.trimWhitespace
|
|
20
|
+
? raw.trim()
|
|
21
|
+
: raw.replace(/[\r\n]+$/, "");
|
|
22
|
+
if (!value) {
|
|
23
|
+
throw new Error(`${options.label} file is empty: ${resolvedPath}`);
|
|
24
|
+
}
|
|
25
|
+
return { value, resolvedPath };
|
|
26
|
+
}
|
|
27
|
+
export async function writeSecretFile(pathValue, value) {
|
|
28
|
+
const resolvedPath = resolve(process.cwd(), pathValue);
|
|
29
|
+
const tmp = `${resolvedPath}.tmp.${Date.now()}`;
|
|
30
|
+
await mkdir(dirname(resolvedPath), { recursive: true });
|
|
31
|
+
await writeFile(tmp, value.endsWith("\n") ? value : `${value}\n`, {
|
|
32
|
+
mode: 0o600,
|
|
33
|
+
});
|
|
34
|
+
await rename(tmp, resolvedPath);
|
|
35
|
+
await chmod(resolvedPath, 0o600).catch(() => { });
|
|
36
|
+
return { resolvedPath };
|
|
37
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { memboxConfig } from "../config.js";
|
|
2
|
+
export declare function createSetupFinishTool(cfg: memboxConfig): {
|
|
3
|
+
name: string;
|
|
4
|
+
label: string;
|
|
5
|
+
description: string;
|
|
6
|
+
parameters: {
|
|
7
|
+
type: "object";
|
|
8
|
+
properties: {
|
|
9
|
+
passphrase_file: {
|
|
10
|
+
type: "string";
|
|
11
|
+
description: string;
|
|
12
|
+
};
|
|
13
|
+
recovery_code_output_file: {
|
|
14
|
+
type: "string";
|
|
15
|
+
description: string;
|
|
16
|
+
};
|
|
17
|
+
enable_managed_unlock: {
|
|
18
|
+
type: "boolean";
|
|
19
|
+
description: string;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
required: string[];
|
|
23
|
+
additionalProperties: boolean;
|
|
24
|
+
};
|
|
25
|
+
execute(_toolCallId?: string, params?: {
|
|
26
|
+
passphrase_file?: string;
|
|
27
|
+
recovery_code_output_file?: string;
|
|
28
|
+
enable_managed_unlock?: boolean;
|
|
29
|
+
}): Promise<{
|
|
30
|
+
content: {
|
|
31
|
+
type: "text";
|
|
32
|
+
text: string;
|
|
33
|
+
}[];
|
|
34
|
+
details: unknown;
|
|
35
|
+
}>;
|
|
36
|
+
};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { readState } from "../store/local-state.js";
|
|
2
|
+
import { authorizeDevice, pollPendingDeviceAuthorizationOnce, } from "../cli/bootstrap.js";
|
|
3
|
+
import { finishAuthorizedSetup } from "../cli/setup.js";
|
|
4
|
+
import { runWithProvisioningRollback } from "../cli/provisioning.js";
|
|
5
|
+
import { jsonResult } from "./result.js";
|
|
6
|
+
import { readSecretFile, writeSecretFile } from "./secret-file.js";
|
|
7
|
+
export function createSetupFinishTool(cfg) {
|
|
8
|
+
return {
|
|
9
|
+
name: "membox_setup_finish",
|
|
10
|
+
label: "Membox Vault Setup Finish",
|
|
11
|
+
description: "Finish the authorized Membox Vault setup on this machine using a local passphrase file. Optionally writes the generated recovery code to a local file and can explicitly opt in to managed auto-unlock.",
|
|
12
|
+
parameters: {
|
|
13
|
+
type: "object",
|
|
14
|
+
properties: {
|
|
15
|
+
passphrase_file: {
|
|
16
|
+
type: "string",
|
|
17
|
+
description: "Path to a local file containing the new vault passphrase. On Unix, the file must not be readable by group or others.",
|
|
18
|
+
},
|
|
19
|
+
recovery_code_output_file: {
|
|
20
|
+
type: "string",
|
|
21
|
+
description: "Optional local output file for the recovery code. Required for first-device setup so the recovery code stays local to the machine.",
|
|
22
|
+
},
|
|
23
|
+
enable_managed_unlock: {
|
|
24
|
+
type: "boolean",
|
|
25
|
+
description: "When true, explicitly opt this machine into storing a managed local unlock secret so future sync/pull/grant commands can auto-unlock without another passphrase prompt.",
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
required: ["passphrase_file"],
|
|
29
|
+
additionalProperties: false,
|
|
30
|
+
},
|
|
31
|
+
async execute(_toolCallId, params) {
|
|
32
|
+
const state = await readState();
|
|
33
|
+
if (state?.setup_complete) {
|
|
34
|
+
return jsonResult({
|
|
35
|
+
already_setup: true,
|
|
36
|
+
message: "Vault is already set up on this machine. Use unlock/sync/pull/status instead of setup_finish.",
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
if (!params?.passphrase_file) {
|
|
40
|
+
return jsonResult({
|
|
41
|
+
error: true,
|
|
42
|
+
message: "Missing required parameter: `passphrase_file`.",
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
const authStatus = await pollPendingDeviceAuthorizationOnce(cfg);
|
|
46
|
+
if (authStatus.status === "not_started") {
|
|
47
|
+
return jsonResult({
|
|
48
|
+
status: "not_started",
|
|
49
|
+
error: true,
|
|
50
|
+
message: "No pending setup exists. Call `membox_setup_start`, send the verification link to the user, then poll with `membox_setup_poll`.",
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
if (authStatus.status === "authorization_pending" ||
|
|
54
|
+
authStatus.status === "slow_down") {
|
|
55
|
+
return jsonResult({
|
|
56
|
+
status: authStatus.status,
|
|
57
|
+
user_code: authStatus.pendingSetup?.user_code,
|
|
58
|
+
verification_uri_complete: authStatus.pendingSetup?.verification_uri_complete,
|
|
59
|
+
retry_after_seconds: authStatus.retryAfterSeconds,
|
|
60
|
+
message: authStatus.message ??
|
|
61
|
+
"Still waiting for browser authorization before setup can finish.",
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
if (authStatus.status === "denied" || authStatus.status === "expired") {
|
|
65
|
+
return jsonResult({
|
|
66
|
+
status: authStatus.status,
|
|
67
|
+
error: true,
|
|
68
|
+
message: authStatus.message,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
const { value: passphrase, resolvedPath: passphrasePath } = await readSecretFile(params.passphrase_file, {
|
|
73
|
+
label: "Passphrase",
|
|
74
|
+
trimWhitespace: false,
|
|
75
|
+
});
|
|
76
|
+
const auth = await authorizeDevice(cfg);
|
|
77
|
+
const result = await runWithProvisioningRollback(auth, async (markProvisioned) => {
|
|
78
|
+
const completed = await finishAuthorizedSetup(cfg, auth, {
|
|
79
|
+
passphrase,
|
|
80
|
+
enableManagedUnlock: params.enable_managed_unlock === true,
|
|
81
|
+
onRecoveryCode: params.recovery_code_output_file
|
|
82
|
+
? async (recoveryCode) => {
|
|
83
|
+
await writeSecretFile(params.recovery_code_output_file, recoveryCode);
|
|
84
|
+
}
|
|
85
|
+
: undefined,
|
|
86
|
+
});
|
|
87
|
+
markProvisioned();
|
|
88
|
+
return completed;
|
|
89
|
+
});
|
|
90
|
+
return jsonResult({
|
|
91
|
+
ok: true,
|
|
92
|
+
...result,
|
|
93
|
+
passphrase_file: passphrasePath,
|
|
94
|
+
recovery_code_output_file: params.recovery_code_output_file,
|
|
95
|
+
message: result.initialMode === "pull"
|
|
96
|
+
? "Setup finished. Existing encrypted memory was pulled to this machine."
|
|
97
|
+
: "Setup finished. New vault initialized and initial sync completed.",
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
return jsonResult({
|
|
102
|
+
error: true,
|
|
103
|
+
message: `Setup finish failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { memboxConfig } from "../config.js";
|
|
2
|
+
export declare function createSetupPollTool(cfg: memboxConfig): {
|
|
3
|
+
name: string;
|
|
4
|
+
label: string;
|
|
5
|
+
description: string;
|
|
6
|
+
parameters: {
|
|
7
|
+
type: "object";
|
|
8
|
+
properties: {
|
|
9
|
+
wait_seconds: {
|
|
10
|
+
type: "integer";
|
|
11
|
+
minimum: number;
|
|
12
|
+
maximum: number;
|
|
13
|
+
description: string;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
additionalProperties: boolean;
|
|
17
|
+
};
|
|
18
|
+
execute(_toolCallId?: string, params?: {
|
|
19
|
+
wait_seconds?: number;
|
|
20
|
+
}): Promise<{
|
|
21
|
+
content: {
|
|
22
|
+
type: "text";
|
|
23
|
+
text: string;
|
|
24
|
+
}[];
|
|
25
|
+
details: unknown;
|
|
26
|
+
}>;
|
|
27
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { pollPendingDeviceAuthorizationOnce } from "../cli/bootstrap.js";
|
|
2
|
+
import { readState } from "../store/local-state.js";
|
|
3
|
+
function jsonResult(payload) {
|
|
4
|
+
return {
|
|
5
|
+
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
|
6
|
+
details: payload,
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
function sleep(ms) {
|
|
10
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
11
|
+
}
|
|
12
|
+
export function createSetupPollTool(cfg) {
|
|
13
|
+
return {
|
|
14
|
+
name: "membox_setup_poll",
|
|
15
|
+
label: "Membox Vault Setup Poll",
|
|
16
|
+
description: "Poll the pending Membox Vault browser authorization until it completes or times out. Does not collect passphrases.",
|
|
17
|
+
parameters: {
|
|
18
|
+
type: "object",
|
|
19
|
+
properties: {
|
|
20
|
+
wait_seconds: {
|
|
21
|
+
type: "integer",
|
|
22
|
+
minimum: 0,
|
|
23
|
+
maximum: 30,
|
|
24
|
+
description: "Optional bounded wait window. The tool will keep polling within this window before returning the latest status.",
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
additionalProperties: false,
|
|
28
|
+
},
|
|
29
|
+
async execute(_toolCallId, params) {
|
|
30
|
+
const state = await readState();
|
|
31
|
+
if (state?.setup_complete) {
|
|
32
|
+
return jsonResult({
|
|
33
|
+
already_setup: true,
|
|
34
|
+
message: "Membox Vault is already set up.",
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
const waitSeconds = Math.max(0, Math.min(30, params?.wait_seconds ?? 0));
|
|
38
|
+
const deadline = Date.now() + waitSeconds * 1000;
|
|
39
|
+
let latest = await pollPendingDeviceAuthorizationOnce(cfg);
|
|
40
|
+
while (Date.now() < deadline &&
|
|
41
|
+
(latest.status === "authorization_pending" || latest.status === "slow_down")) {
|
|
42
|
+
const waitMs = Math.min(Math.max(1, latest.retryAfterSeconds ?? 1) * 1000, deadline - Date.now());
|
|
43
|
+
if (waitMs <= 0) {
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
await sleep(waitMs);
|
|
47
|
+
latest = await pollPendingDeviceAuthorizationOnce(cfg);
|
|
48
|
+
}
|
|
49
|
+
if (latest.status === "authorized") {
|
|
50
|
+
return jsonResult({
|
|
51
|
+
status: "authorized",
|
|
52
|
+
user_code: latest.pendingSetup?.user_code,
|
|
53
|
+
verification_uri_complete: latest.pendingSetup?.verification_uri_complete,
|
|
54
|
+
device_name: latest.pendingSetup?.device_name,
|
|
55
|
+
message: latest.message ??
|
|
56
|
+
"Browser authorization completed. This machine can now finish secure setup with `membox_setup_finish`.",
|
|
57
|
+
next_action: "Call `membox_setup_finish` with a local `passphrase_file` to complete passphrase setup, recovery generation, and initial sync.",
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
if (latest.status === "not_started") {
|
|
61
|
+
return jsonResult({
|
|
62
|
+
status: "not_started",
|
|
63
|
+
message: "No pending setup authorization exists. Call `membox_setup_start` first.",
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
if (latest.status === "denied" || latest.status === "expired") {
|
|
67
|
+
return jsonResult({
|
|
68
|
+
status: latest.status,
|
|
69
|
+
message: latest.message,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
return jsonResult({
|
|
73
|
+
status: latest.status,
|
|
74
|
+
user_code: latest.pendingSetup?.user_code,
|
|
75
|
+
verification_uri_complete: latest.pendingSetup?.verification_uri_complete,
|
|
76
|
+
expires_at: latest.pendingSetup?.expires_at,
|
|
77
|
+
retry_after_seconds: latest.retryAfterSeconds,
|
|
78
|
+
message: latest.message ??
|
|
79
|
+
"Still waiting for browser authorization. Keep polling until it completes.",
|
|
80
|
+
});
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { memboxConfig } from "../config.js";
|
|
2
|
+
export declare function createSetupStartTool(cfg: memboxConfig): {
|
|
3
|
+
name: string;
|
|
4
|
+
label: string;
|
|
5
|
+
description: string;
|
|
6
|
+
parameters: {
|
|
7
|
+
type: "object";
|
|
8
|
+
properties: {};
|
|
9
|
+
additionalProperties: boolean;
|
|
10
|
+
};
|
|
11
|
+
execute(_toolCallId?: string): Promise<{
|
|
12
|
+
content: {
|
|
13
|
+
type: "text";
|
|
14
|
+
text: string;
|
|
15
|
+
}[];
|
|
16
|
+
details: unknown;
|
|
17
|
+
}>;
|
|
18
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { ensurePendingDeviceAuthorization } from "../cli/bootstrap.js";
|
|
2
|
+
import { readState } from "../store/local-state.js";
|
|
3
|
+
function jsonResult(payload) {
|
|
4
|
+
return {
|
|
5
|
+
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
|
6
|
+
details: payload,
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
export function createSetupStartTool(cfg) {
|
|
10
|
+
return {
|
|
11
|
+
name: "membox_setup_start",
|
|
12
|
+
label: "Membox Vault Setup",
|
|
13
|
+
description: "Start Membox Vault browser authorization and return a clickable verification link. Does not collect the local vault passphrase.",
|
|
14
|
+
parameters: {
|
|
15
|
+
type: "object",
|
|
16
|
+
properties: {},
|
|
17
|
+
additionalProperties: false,
|
|
18
|
+
},
|
|
19
|
+
async execute(_toolCallId) {
|
|
20
|
+
const state = await readState();
|
|
21
|
+
if (state?.setup_complete) {
|
|
22
|
+
return jsonResult({
|
|
23
|
+
already_setup: true,
|
|
24
|
+
message: "Membox Vault is already set up.",
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
const { pendingSetup, created } = await ensurePendingDeviceAuthorization(cfg);
|
|
28
|
+
if (pendingSetup.status === "authorized") {
|
|
29
|
+
return jsonResult({
|
|
30
|
+
status: "authorized",
|
|
31
|
+
user_code: pendingSetup.user_code,
|
|
32
|
+
verification_uri_complete: pendingSetup.verification_uri_complete,
|
|
33
|
+
message: "Browser authorization is already complete. Call `membox_setup_finish` with a local `passphrase_file` to finish secure setup on this machine.",
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return jsonResult({
|
|
37
|
+
status: "authorization_pending",
|
|
38
|
+
created,
|
|
39
|
+
user_code: pendingSetup.user_code,
|
|
40
|
+
verification_uri_complete: pendingSetup.verification_uri_complete,
|
|
41
|
+
expires_at: pendingSetup.expires_at,
|
|
42
|
+
interval_seconds: pendingSetup.interval_seconds,
|
|
43
|
+
clickable_link_markdown: `[Authorize Membox Vault](${pendingSetup.verification_uri_complete})`,
|
|
44
|
+
message: "Send this link to the user. After they finish login and device approval in a browser, call `membox_setup_poll` until it returns `authorized`.",
|
|
45
|
+
next_action: "When authorization completes, call `membox_setup_finish` with a local `passphrase_file` to finish secure setup on this machine.",
|
|
46
|
+
});
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export declare function createStatusTool(): {
|
|
2
|
+
name: string;
|
|
3
|
+
label: string;
|
|
4
|
+
description: string;
|
|
5
|
+
parameters: {
|
|
6
|
+
type: "object";
|
|
7
|
+
properties: {};
|
|
8
|
+
additionalProperties: boolean;
|
|
9
|
+
};
|
|
10
|
+
execute(): Promise<{
|
|
11
|
+
content: {
|
|
12
|
+
type: "text";
|
|
13
|
+
text: string;
|
|
14
|
+
}[];
|
|
15
|
+
details: unknown;
|
|
16
|
+
}>;
|
|
17
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { readState } from "../store/local-state.js";
|
|
2
|
+
import { vaultSession } from "../store/session.js";
|
|
3
|
+
import { getManagedUnlockStatus } from "../store/managed-unlock.js";
|
|
4
|
+
function jsonResult(payload) {
|
|
5
|
+
return {
|
|
6
|
+
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
|
7
|
+
details: payload,
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export function createStatusTool() {
|
|
11
|
+
return {
|
|
12
|
+
name: "membox_status",
|
|
13
|
+
label: "Membox Vault Status",
|
|
14
|
+
description: "Check Membox Vault sync status. Returns setup state, lock state, sync cursor, and file count.",
|
|
15
|
+
parameters: {
|
|
16
|
+
type: "object",
|
|
17
|
+
properties: {},
|
|
18
|
+
additionalProperties: false,
|
|
19
|
+
},
|
|
20
|
+
async execute() {
|
|
21
|
+
const state = await readState();
|
|
22
|
+
if (!state?.setup_complete) {
|
|
23
|
+
return jsonResult({
|
|
24
|
+
status: "not_setup",
|
|
25
|
+
message: "Membox Vault is not set up. Ask the user to run `openclaw membox setup` in their terminal.",
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
const managedUnlock = await getManagedUnlockStatus();
|
|
29
|
+
return jsonResult({
|
|
30
|
+
status: "ok",
|
|
31
|
+
unlocked: vaultSession.isUnlocked(),
|
|
32
|
+
managed_unlock: managedUnlock,
|
|
33
|
+
server: state.server_url,
|
|
34
|
+
device: state.device_name,
|
|
35
|
+
cursor: state.sync_cursor,
|
|
36
|
+
files: Object.keys(state.file_versions).length,
|
|
37
|
+
});
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export declare function createSyncTool(): {
|
|
2
|
+
name: string;
|
|
3
|
+
label: string;
|
|
4
|
+
description: string;
|
|
5
|
+
parameters: {
|
|
6
|
+
type: "object";
|
|
7
|
+
properties: {};
|
|
8
|
+
additionalProperties: boolean;
|
|
9
|
+
};
|
|
10
|
+
execute(): Promise<{
|
|
11
|
+
content: {
|
|
12
|
+
type: "text";
|
|
13
|
+
text: string;
|
|
14
|
+
}[];
|
|
15
|
+
details: unknown;
|
|
16
|
+
}>;
|
|
17
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { readState } from "../store/local-state.js";
|
|
2
|
+
import { ensureVaultUnlocked } from "../cli/unlock.js";
|
|
3
|
+
function jsonResult(payload) {
|
|
4
|
+
return {
|
|
5
|
+
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
|
6
|
+
details: payload,
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
export function createSyncTool() {
|
|
10
|
+
return {
|
|
11
|
+
name: "membox_sync",
|
|
12
|
+
label: "Membox Vault Sync",
|
|
13
|
+
description: "Sync local memory changes to Membox cloud. Uses the current unlocked session or an explicitly enabled managed local unlock secret.",
|
|
14
|
+
parameters: {
|
|
15
|
+
type: "object",
|
|
16
|
+
properties: {},
|
|
17
|
+
additionalProperties: false,
|
|
18
|
+
},
|
|
19
|
+
async execute() {
|
|
20
|
+
const state = await readState();
|
|
21
|
+
if (!state?.setup_complete) {
|
|
22
|
+
return jsonResult({
|
|
23
|
+
error: true,
|
|
24
|
+
message: "Vault not set up. Ask the user to run `openclaw membox setup` in their terminal.",
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
if (!(await ensureVaultUnlocked({
|
|
28
|
+
allowPrompt: false,
|
|
29
|
+
announceSuccess: true,
|
|
30
|
+
}))) {
|
|
31
|
+
return jsonResult({
|
|
32
|
+
error: true,
|
|
33
|
+
message: "Vault is locked. Call `membox_unlock` with a local `passphrase_file`, enable `membox_unlock_secret_enable`, or ask the user to run `openclaw membox unlock` in their terminal.",
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
const { syncAction } = await import("../cli/sync.js");
|
|
38
|
+
await syncAction();
|
|
39
|
+
return jsonResult({ ok: true, message: "Sync complete." });
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
return jsonResult({
|
|
43
|
+
error: true,
|
|
44
|
+
message: `Sync failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export declare function createUnlockSecretEnableTool(): {
|
|
2
|
+
name: string;
|
|
3
|
+
label: string;
|
|
4
|
+
description: string;
|
|
5
|
+
parameters: {
|
|
6
|
+
type: "object";
|
|
7
|
+
properties: {
|
|
8
|
+
passphrase_file: {
|
|
9
|
+
type: "string";
|
|
10
|
+
description: string;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
required: string[];
|
|
14
|
+
additionalProperties: boolean;
|
|
15
|
+
};
|
|
16
|
+
execute(_toolCallId?: string, params?: {
|
|
17
|
+
passphrase_file?: string;
|
|
18
|
+
}): Promise<{
|
|
19
|
+
content: {
|
|
20
|
+
type: "text";
|
|
21
|
+
text: string;
|
|
22
|
+
}[];
|
|
23
|
+
details: unknown;
|
|
24
|
+
}>;
|
|
25
|
+
};
|
|
26
|
+
export declare function createUnlockSecretDisableTool(): {
|
|
27
|
+
name: string;
|
|
28
|
+
label: string;
|
|
29
|
+
description: string;
|
|
30
|
+
parameters: {
|
|
31
|
+
type: "object";
|
|
32
|
+
properties: {};
|
|
33
|
+
additionalProperties: boolean;
|
|
34
|
+
};
|
|
35
|
+
execute(): Promise<{
|
|
36
|
+
content: {
|
|
37
|
+
type: "text";
|
|
38
|
+
text: string;
|
|
39
|
+
}[];
|
|
40
|
+
details: unknown;
|
|
41
|
+
}>;
|
|
42
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { readState } from "../store/local-state.js";
|
|
2
|
+
import { jsonResult } from "./result.js";
|
|
3
|
+
import { readSecretFile } from "./secret-file.js";
|
|
4
|
+
import { disableManagedUnlockAction, enableManagedUnlockAction, } from "../cli/unlock.js";
|
|
5
|
+
import { getManagedUnlockStatus } from "../store/managed-unlock.js";
|
|
6
|
+
export function createUnlockSecretEnableTool() {
|
|
7
|
+
return {
|
|
8
|
+
name: "membox_unlock_secret_enable",
|
|
9
|
+
label: "Membox Managed Unlock Enable",
|
|
10
|
+
description: "Explicitly opt this machine into a managed local unlock secret by validating and storing the vault passphrase in the local keychain.",
|
|
11
|
+
parameters: {
|
|
12
|
+
type: "object",
|
|
13
|
+
properties: {
|
|
14
|
+
passphrase_file: {
|
|
15
|
+
type: "string",
|
|
16
|
+
description: "Path to a local file containing the current vault passphrase. On Unix, the file must not be readable by group or others.",
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
required: ["passphrase_file"],
|
|
20
|
+
additionalProperties: false,
|
|
21
|
+
},
|
|
22
|
+
async execute(_toolCallId, params) {
|
|
23
|
+
const state = await readState();
|
|
24
|
+
if (!state?.setup_complete) {
|
|
25
|
+
return jsonResult({
|
|
26
|
+
error: true,
|
|
27
|
+
message: "Vault not set up. Complete setup before enabling managed unlock.",
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
if (!params?.passphrase_file) {
|
|
31
|
+
return jsonResult({
|
|
32
|
+
error: true,
|
|
33
|
+
message: "Missing required parameter: `passphrase_file`.",
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
const { value: passphrase, resolvedPath } = await readSecretFile(params.passphrase_file, {
|
|
38
|
+
label: "Passphrase",
|
|
39
|
+
trimWhitespace: false,
|
|
40
|
+
});
|
|
41
|
+
await enableManagedUnlockAction(passphrase);
|
|
42
|
+
const status = await getManagedUnlockStatus();
|
|
43
|
+
return jsonResult({
|
|
44
|
+
ok: true,
|
|
45
|
+
passphrase_file: resolvedPath,
|
|
46
|
+
managed_unlock: status,
|
|
47
|
+
message: "Managed unlock secret enabled on this machine.",
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
return jsonResult({
|
|
52
|
+
error: true,
|
|
53
|
+
message: `Managed unlock enable failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
export function createUnlockSecretDisableTool() {
|
|
60
|
+
return {
|
|
61
|
+
name: "membox_unlock_secret_disable",
|
|
62
|
+
label: "Membox Managed Unlock Disable",
|
|
63
|
+
description: "Disable and delete the managed local unlock secret on this machine.",
|
|
64
|
+
parameters: {
|
|
65
|
+
type: "object",
|
|
66
|
+
properties: {},
|
|
67
|
+
additionalProperties: false,
|
|
68
|
+
},
|
|
69
|
+
async execute() {
|
|
70
|
+
try {
|
|
71
|
+
await disableManagedUnlockAction();
|
|
72
|
+
const status = await getManagedUnlockStatus();
|
|
73
|
+
return jsonResult({
|
|
74
|
+
ok: true,
|
|
75
|
+
managed_unlock: status,
|
|
76
|
+
message: "Managed unlock secret disabled on this machine.",
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
return jsonResult({
|
|
81
|
+
error: true,
|
|
82
|
+
message: `Managed unlock disable failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export declare function createUnlockTool(): {
|
|
2
|
+
name: string;
|
|
3
|
+
label: string;
|
|
4
|
+
description: string;
|
|
5
|
+
parameters: {
|
|
6
|
+
type: "object";
|
|
7
|
+
properties: {
|
|
8
|
+
passphrase_file: {
|
|
9
|
+
type: "string";
|
|
10
|
+
description: string;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
required: string[];
|
|
14
|
+
additionalProperties: boolean;
|
|
15
|
+
};
|
|
16
|
+
execute(_toolCallId?: string, params?: {
|
|
17
|
+
passphrase_file?: string;
|
|
18
|
+
}): Promise<{
|
|
19
|
+
content: {
|
|
20
|
+
type: "text";
|
|
21
|
+
text: string;
|
|
22
|
+
}[];
|
|
23
|
+
details: unknown;
|
|
24
|
+
}>;
|
|
25
|
+
};
|