clawdentity 0.0.4 → 0.0.5

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.
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawdentity",
3
- "version": "0.0.4",
3
+ "version": "0.0.5",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -21,6 +21,17 @@
21
21
  "postinstall.mjs",
22
22
  "skill-bundle"
23
23
  ],
24
+ "scripts": {
25
+ "build": "pnpm -F @clawdentity/openclaw-skill build && pnpm run sync:skill-bundle && pnpm run verify:skill-bundle && tsup",
26
+ "format": "biome format .",
27
+ "lint": "biome lint .",
28
+ "prepack": "pnpm run build",
29
+ "postinstall": "node ./postinstall.mjs",
30
+ "sync:skill-bundle": "node ./scripts/sync-skill-bundle.mjs",
31
+ "verify:skill-bundle": "node ./scripts/verify-skill-bundle.mjs",
32
+ "test": "vitest run",
33
+ "typecheck": "tsc --noEmit"
34
+ },
24
35
  "dependencies": {
25
36
  "commander": "^13.1.0",
26
37
  "jsqr": "^1.4.0",
@@ -29,21 +40,11 @@
29
40
  "ws": "^8.19.0"
30
41
  },
31
42
  "devDependencies": {
43
+ "@clawdentity/connector": "workspace:*",
44
+ "@clawdentity/protocol": "workspace:*",
45
+ "@clawdentity/sdk": "workspace:*",
32
46
  "@types/node": "^22.18.11",
33
47
  "@types/pngjs": "^6.0.5",
34
- "@types/qrcode": "^1.5.6",
35
- "@clawdentity/connector": "0.0.0",
36
- "@clawdentity/protocol": "0.0.0",
37
- "@clawdentity/sdk": "0.0.0"
38
- },
39
- "scripts": {
40
- "build": "pnpm -F @clawdentity/openclaw-skill build && pnpm run sync:skill-bundle && pnpm run verify:skill-bundle && tsup",
41
- "format": "biome format .",
42
- "lint": "biome lint .",
43
- "postinstall": "node ./postinstall.mjs",
44
- "sync:skill-bundle": "node ./scripts/sync-skill-bundle.mjs",
45
- "verify:skill-bundle": "node ./scripts/verify-skill-bundle.mjs",
46
- "test": "vitest run",
47
- "typecheck": "tsc --noEmit"
48
+ "@types/qrcode": "^1.5.6"
48
49
  }
49
- }
50
+ }
@@ -1,3 +1,8 @@
1
+ // src/transforms/relay-to-peer.ts
2
+ import { readFile as readFile2 } from "fs/promises";
3
+ import { dirname as dirname2, join as join2 } from "path";
4
+ import { fileURLToPath } from "url";
5
+
1
6
  // src/transforms/peers-config.ts
2
7
  import { chmod, mkdir, readFile, writeFile } from "fs/promises";
3
8
  import { homedir } from "os";
@@ -131,9 +136,17 @@ async function loadPeersConfig(options = {}) {
131
136
  // src/transforms/relay-to-peer.ts
132
137
  var DEFAULT_CONNECTOR_BASE_URL = "http://127.0.0.1:19400";
133
138
  var DEFAULT_CONNECTOR_OUTBOUND_PATH = "/v1/outbound";
139
+ var RELAY_RUNTIME_FILE_NAME = "clawdentity-relay.json";
140
+ var RELAY_PEERS_FILE_NAME = "clawdentity-peers.json";
134
141
  function isRecord2(value) {
135
142
  return typeof value === "object" && value !== null;
136
143
  }
144
+ function getErrorCode2(error) {
145
+ if (!isRecord2(error)) {
146
+ return void 0;
147
+ }
148
+ return typeof error.code === "string" ? error.code : void 0;
149
+ }
137
150
  function parseRequiredString(value) {
138
151
  if (typeof value !== "string") {
139
152
  throw new Error("Input value must be a string");
@@ -182,12 +195,114 @@ function normalizeConnectorPath(value) {
182
195
  }
183
196
  return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
184
197
  }
185
- function resolveConnectorEndpoint(options) {
186
- const baseUrlInput = options.connectorBaseUrl ?? process.env.CLAWDENTITY_CONNECTOR_BASE_URL ?? DEFAULT_CONNECTOR_BASE_URL;
187
- const pathInput = options.connectorPath ?? process.env.CLAWDENTITY_CONNECTOR_OUTBOUND_PATH ?? DEFAULT_CONNECTOR_OUTBOUND_PATH;
188
- const baseUrl = parseConnectorBaseUrl(baseUrlInput.trim());
198
+ function resolveTransformsDir() {
199
+ return dirname2(fileURLToPath(import.meta.url));
200
+ }
201
+ async function readJson(filePath) {
202
+ let raw;
203
+ try {
204
+ raw = await readFile2(filePath, "utf8");
205
+ } catch (error) {
206
+ if (getErrorCode2(error) === "ENOENT") {
207
+ return void 0;
208
+ }
209
+ throw error;
210
+ }
211
+ try {
212
+ return JSON.parse(raw);
213
+ } catch {
214
+ throw new Error(`Relay runtime config at ${filePath} is not valid JSON`);
215
+ }
216
+ }
217
+ function parseRelayRuntimeConfig(value) {
218
+ if (!isRecord2(value)) {
219
+ throw new Error("Relay runtime config must be an object");
220
+ }
221
+ const connectorBaseUrl = typeof value.connectorBaseUrl === "string" && value.connectorBaseUrl.trim().length > 0 ? parseConnectorBaseUrl(value.connectorBaseUrl.trim()) : void 0;
222
+ const connectorPath = typeof value.connectorPath === "string" && value.connectorPath.trim().length > 0 ? normalizeConnectorPath(value.connectorPath) : void 0;
223
+ const peersConfigPath = typeof value.peersConfigPath === "string" && value.peersConfigPath.trim().length > 0 ? value.peersConfigPath.trim() : void 0;
224
+ const connectorBaseUrls = Array.isArray(value.connectorBaseUrls) ? value.connectorBaseUrls.filter((item) => typeof item === "string").map((item) => item.trim()).filter((item) => item.length > 0).map(parseConnectorBaseUrl) : void 0;
225
+ return {
226
+ connectorBaseUrl,
227
+ connectorBaseUrls,
228
+ connectorPath,
229
+ peersConfigPath
230
+ };
231
+ }
232
+ async function loadRelayRuntimeConfig() {
233
+ const runtimePath = join2(resolveTransformsDir(), RELAY_RUNTIME_FILE_NAME);
234
+ const parsed = await readJson(runtimePath);
235
+ if (parsed === void 0) {
236
+ return {};
237
+ }
238
+ return parseRelayRuntimeConfig(parsed);
239
+ }
240
+ function parseGatewayHexToIpv4(value) {
241
+ if (!/^[0-9A-Fa-f]{8}$/.test(value)) {
242
+ return void 0;
243
+ }
244
+ const octets = [0, 2, 4, 6].map(
245
+ (index) => Number.parseInt(value.slice(index, index + 2), 16)
246
+ );
247
+ return `${octets[3]}.${octets[2]}.${octets[1]}.${octets[0]}`;
248
+ }
249
+ async function resolveLinuxDockerGatewayHost() {
250
+ let raw;
251
+ try {
252
+ raw = await readFile2("/proc/net/route", "utf8");
253
+ } catch {
254
+ return void 0;
255
+ }
256
+ const lines = raw.split("\n");
257
+ for (const line of lines.slice(1)) {
258
+ const parts = line.trim().split(/\s+/);
259
+ if (parts.length < 4) {
260
+ continue;
261
+ }
262
+ const destination = parts[1];
263
+ const gateway = parts[2];
264
+ const flags = Number.parseInt(parts[3], 16);
265
+ if (destination === "00000000" && Number.isFinite(flags) && (flags & 2) === 2) {
266
+ return parseGatewayHexToIpv4(gateway);
267
+ }
268
+ }
269
+ return void 0;
270
+ }
271
+ async function resolveConnectorEndpoints(options) {
272
+ const runtimeConfig = await loadRelayRuntimeConfig();
273
+ const pathInput = options.connectorPath ?? runtimeConfig.connectorPath ?? process.env.CLAWDENTITY_CONNECTOR_OUTBOUND_PATH ?? DEFAULT_CONNECTOR_OUTBOUND_PATH;
189
274
  const path = normalizeConnectorPath(pathInput.trim());
190
- return new URL(path, baseUrl).toString();
275
+ const candidates = [];
276
+ if (options.connectorBaseUrl) {
277
+ candidates.push(parseConnectorBaseUrl(options.connectorBaseUrl.trim()));
278
+ }
279
+ if (runtimeConfig.connectorBaseUrls) {
280
+ candidates.push(...runtimeConfig.connectorBaseUrls);
281
+ }
282
+ if (runtimeConfig.connectorBaseUrl) {
283
+ candidates.push(runtimeConfig.connectorBaseUrl);
284
+ }
285
+ if (typeof process.env.CLAWDENTITY_CONNECTOR_BASE_URL === "string" && process.env.CLAWDENTITY_CONNECTOR_BASE_URL.trim().length > 0) {
286
+ candidates.push(
287
+ parseConnectorBaseUrl(process.env.CLAWDENTITY_CONNECTOR_BASE_URL.trim())
288
+ );
289
+ }
290
+ candidates.push(DEFAULT_CONNECTOR_BASE_URL);
291
+ const linuxGatewayHost = await resolveLinuxDockerGatewayHost();
292
+ if (linuxGatewayHost) {
293
+ for (const candidate of [...candidates]) {
294
+ try {
295
+ const parsed = new URL(candidate);
296
+ if (parsed.hostname === "host.docker.internal" || parsed.hostname === "gateway.docker.internal" || parsed.hostname === "172.17.0.1") {
297
+ parsed.hostname = linuxGatewayHost;
298
+ candidates.push(parsed.toString());
299
+ }
300
+ } catch {
301
+ }
302
+ }
303
+ }
304
+ const deduped = Array.from(new Set(candidates.map((candidate) => candidate)));
305
+ return deduped.map((baseUrl) => new URL(path, baseUrl).toString());
191
306
  }
192
307
  function mapConnectorFailure(status) {
193
308
  if (status === 404) {
@@ -218,6 +333,26 @@ async function postToConnector(endpoint, payload, fetchImpl) {
218
333
  throw mapConnectorFailure(response.status);
219
334
  }
220
335
  }
336
+ function shouldTryNextConnectorEndpoint(error) {
337
+ if (!(error instanceof Error)) {
338
+ return false;
339
+ }
340
+ return error.message === "Local connector outbound relay request failed" || error.message === "Local connector outbound endpoint is unavailable";
341
+ }
342
+ async function resolvePeersConfigPathOptions(options) {
343
+ if (options.configPath !== void 0 || options.configDir !== void 0 || options.homeDir !== void 0) {
344
+ return options;
345
+ }
346
+ const runtimeConfig = await loadRelayRuntimeConfig();
347
+ if (runtimeConfig.peersConfigPath) {
348
+ return {
349
+ configPath: join2(resolveTransformsDir(), runtimeConfig.peersConfigPath)
350
+ };
351
+ }
352
+ return {
353
+ configPath: join2(resolveTransformsDir(), RELAY_PEERS_FILE_NAME)
354
+ };
355
+ }
221
356
  async function relayPayloadToPeer(payload, options = {}) {
222
357
  if (!isRecord2(payload)) {
223
358
  return payload;
@@ -227,25 +362,37 @@ async function relayPayloadToPeer(payload, options = {}) {
227
362
  return payload;
228
363
  }
229
364
  const peerAlias = parseRequiredString(peerAliasValue);
230
- const peersConfig = await loadPeersConfig(options);
365
+ const peersConfigPathOptions = await resolvePeersConfigPathOptions(options);
366
+ const peersConfig = await loadPeersConfig(peersConfigPathOptions);
231
367
  const peerEntry = peersConfig.peers[peerAlias];
232
368
  if (!peerEntry) {
233
369
  throw new Error("Peer alias is not configured");
234
370
  }
235
- const connectorEndpoint = resolveConnectorEndpoint(options);
371
+ const connectorEndpoints = await resolveConnectorEndpoints(options);
236
372
  const fetchImpl = resolveRelayFetch(options.fetchImpl);
237
373
  const outboundPayload = removePeerField(payload);
238
- await postToConnector(
239
- connectorEndpoint,
240
- {
241
- peer: peerAlias,
242
- peerDid: peerEntry.did,
243
- peerProxyUrl: peerEntry.proxyUrl,
244
- payload: outboundPayload
245
- },
246
- fetchImpl
247
- );
248
- return null;
374
+ const relayPayload = {
375
+ peer: peerAlias,
376
+ peerDid: peerEntry.did,
377
+ peerProxyUrl: peerEntry.proxyUrl,
378
+ payload: outboundPayload
379
+ };
380
+ let lastError;
381
+ for (const endpoint of connectorEndpoints) {
382
+ try {
383
+ await postToConnector(endpoint, relayPayload, fetchImpl);
384
+ return null;
385
+ } catch (error) {
386
+ lastError = error;
387
+ if (!shouldTryNextConnectorEndpoint(error)) {
388
+ throw error;
389
+ }
390
+ }
391
+ }
392
+ if (lastError instanceof Error) {
393
+ throw lastError;
394
+ }
395
+ throw new Error("Local connector outbound relay request failed");
249
396
  }
250
397
  async function relayToPeer(ctx) {
251
398
  return relayPayloadToPeer(ctx?.payload);
@@ -19,9 +19,14 @@ Use this skill when any of the following are requested:
19
19
  ## Filesystem Truth (must be used exactly)
20
20
 
21
21
  ### OpenClaw state files
22
- - OpenClaw state root (default): `~/.openclaw`
23
- - OpenClaw config: `~/.openclaw/openclaw.json`
22
+ - OpenClaw state root (default): `~/.openclaw` (legacy fallback dirs may exist: `~/.clawdbot`, `~/.moldbot`, `~/.moltbot`)
23
+ - OpenClaw config: `<resolved-openclaw-state>/openclaw.json` (legacy names may exist: `clawdbot.json`, `moldbot.json`, `moltbot.json`)
24
+ - OpenClaw config env overrides: `OPENCLAW_CONFIG_PATH`, legacy `CLAWDBOT_CONFIG_PATH`
25
+ - OpenClaw state env overrides: `OPENCLAW_STATE_DIR`, legacy `CLAWDBOT_STATE_DIR`
26
+ - OpenClaw home override (used when config/state overrides are unset): `OPENCLAW_HOME`
24
27
  - Transform target path: `~/.openclaw/hooks/transforms/relay-to-peer.mjs`
28
+ - Transform runtime snapshot: `~/.openclaw/hooks/transforms/clawdentity-relay.json`
29
+ - Transform peers snapshot: `~/.openclaw/hooks/transforms/clawdentity-peers.json`
25
30
  - Workspace skill location: `~/.openclaw/workspace/skills/clawdentity-openclaw-relay/SKILL.md`
26
31
  - Default transform source expected by CLI setup:
27
32
  `~/.openclaw/workspace/skills/clawdentity-openclaw-relay/relay-to-peer.mjs`
@@ -35,6 +40,7 @@ Use this skill when any of the following are requested:
35
40
  - Peer map: `~/.clawdentity/peers.json`
36
41
  - Local selected agent marker: `~/.clawdentity/openclaw-agent-name`
37
42
  - Relay runtime config: `~/.clawdentity/openclaw-relay.json`
43
+ - Connector assignment map: `~/.clawdentity/openclaw-connectors.json`
38
44
 
39
45
  ## Invite Input Assumption
40
46
 
@@ -133,11 +139,10 @@ Successful confirm establishes mutual trust for the two agent DIDs. After confir
133
139
  - Use `--openclaw-dir <path>` when state directory is non-default.
134
140
  - Use `--openclaw-base-url <url>` when local OpenClaw HTTP endpoint is non-default.
135
141
  - Use `--peer-alias <alias>` only when alias override is required.
136
- - Optional relay echo tuning:
137
- - `--echo-enabled <true|false>`
138
- - `--echo-channel <channel>`
139
- - `--echo-to <target>`
140
- - `--echo-max-length <number>`
142
+ - Setup now provisions OpenClaw hook auth automatically:
143
+ - ensures `hooks.enabled=true`
144
+ - preserves existing `hooks.token` or generates one when missing
145
+ - mirrors token into `~/.clawdentity/openclaw-relay.json` as `openclawHookToken`
141
146
 
142
147
  7. Verify setup outputs.
143
148
  - Confirm setup reports:
@@ -146,12 +151,13 @@ Successful confirm establishes mutual trust for the two agent DIDs. After confir
146
151
  - updated OpenClaw config path
147
152
  - installed transform path
148
153
  - OpenClaw base URL
149
- - relay echo config (enabled/channel/target/max length)
150
154
  - relay runtime config path
151
155
  - Confirm `~/.clawdentity/openclaw-agent-name` is set to the local agent name.
152
156
 
153
157
  8. Start connector runtime for local relay handoff.
154
158
  - Run `clawdentity connector start <agent-name>`.
159
+ - Connector startup auto-loads `openclawHookToken` from `~/.clawdentity/openclaw-relay.json` when `--openclaw-hook-token` is not provided.
160
+ - Connector startup auto-resolves outbound bind URL from `~/.clawdentity/openclaw-connectors.json` when `CLAWDENTITY_CONNECTOR_BASE_URL` is not set.
155
161
  - Optional: run `clawdentity connector service install <agent-name>` for persistent autostart.
156
162
 
157
163
  9. Complete trust pairing bootstrap.
@@ -186,6 +192,7 @@ If setup or relay fails:
186
192
  - Report precise missing file/path/value.
187
193
  - Fix only the failing config/input.
188
194
  - Ensure connector runtime is active (`clawdentity connector start <agent-name>`).
195
+ - If relay failures show `405 Method Not Allowed`, rerun setup and restart OpenClaw so hook config is reloaded.
189
196
  - Re-run `clawdentity openclaw doctor`.
190
197
  - Re-run `clawdentity openclaw relay test --peer <alias>`.
191
198
  - Re-run the same user-style flow from step 6 onward only after health checks pass.
@@ -7,9 +7,15 @@ Define the exact runtime contract used by `relay-to-peer.mjs`.
7
7
  ## Filesystem Paths
8
8
 
9
9
  ### OpenClaw files
10
- - `~/.openclaw/openclaw.json`
11
- - `~/.openclaw/hooks/transforms/relay-to-peer.mjs`
12
- - `~/.openclaw/workspace/skills/clawdentity-openclaw-relay/SKILL.md`
10
+ - `<resolved-openclaw-state>/openclaw.json` (legacy filenames may exist: `clawdbot.json`, `moldbot.json`, `moltbot.json`)
11
+ - `<resolved-openclaw-state>/hooks/transforms/relay-to-peer.mjs`
12
+ - `<resolved-openclaw-state>/hooks/transforms/clawdentity-relay.json`
13
+ - `<resolved-openclaw-state>/hooks/transforms/clawdentity-peers.json`
14
+ - `<resolved-openclaw-state>/workspace/skills/clawdentity-openclaw-relay/SKILL.md`
15
+ - env overrides:
16
+ - `OPENCLAW_CONFIG_PATH`, `CLAWDBOT_CONFIG_PATH`
17
+ - `OPENCLAW_STATE_DIR`, `CLAWDBOT_STATE_DIR`
18
+ - `OPENCLAW_HOME` (used when explicit config/state overrides are unset)
13
19
 
14
20
  ### Clawdentity files
15
21
  - `~/.clawdentity/config.json`
@@ -18,6 +24,7 @@ Define the exact runtime contract used by `relay-to-peer.mjs`.
18
24
  - `~/.clawdentity/peers.json`
19
25
  - `~/.clawdentity/openclaw-agent-name`
20
26
  - `~/.clawdentity/openclaw-relay.json`
27
+ - `~/.clawdentity/openclaw-connectors.json`
21
28
 
22
29
  ## Invite Code Contract
23
30
 
@@ -124,36 +131,31 @@ Relay resolves local agent name in this order:
124
131
 
125
132
  ## Local OpenClaw Base URL Contract
126
133
 
127
- `~/.clawdentity/openclaw-relay.json` stores OpenClaw runtime defaults for relay components:
134
+ `~/.clawdentity/openclaw-relay.json` stores the OpenClaw upstream base URL used by local proxy runtime fallback:
128
135
 
129
136
  ```json
130
137
  {
131
138
  "openclawBaseUrl": "http://127.0.0.1:18789",
132
- "echo": {
133
- "enabled": true,
134
- "channel": "last",
135
- "maxLength": 500
136
- },
139
+ "openclawHookToken": "<auto-provisioned-token>",
137
140
  "updatedAt": "2026-02-15T20:00:00.000Z"
138
141
  }
139
142
  ```
140
143
 
141
144
  Rules:
142
145
  - `openclawBaseUrl` must be absolute `http` or `https`.
143
- - `echo.enabled` defaults to `true`.
144
- - `echo.channel` defaults to `last`.
145
- - `echo.to` is optional; when omitted OpenClaw uses last-route delivery target.
146
- - `echo.maxLength` must be an integer from `50` to `2000` (default `500`).
146
+ - `openclawHookToken` is optional in schema but should be present after `clawdentity openclaw setup`; connector runtime uses it for `/hooks/*` auth when no explicit hook token option/env is provided.
147
147
  - `updatedAt` is ISO-8601 UTC timestamp.
148
- - OpenClaw base URL precedence for local runtimes is: `OPENCLAW_BASE_URL` env first, then `openclaw-relay.json`, then built-in default.
148
+ - Proxy runtime precedence is: `OPENCLAW_BASE_URL` env first, then `openclaw-relay.json`, then built-in default.
149
149
 
150
150
  ## Connector Handoff Contract
151
151
 
152
152
  The transform does not send directly to the peer proxy. It posts to the local connector runtime:
153
- - Default endpoint: `http://127.0.0.1:19400/v1/outbound`
154
- - Optional overrides:
153
+ - Endpoint candidates are loaded from OpenClaw-local `hooks/transforms/clawdentity-relay.json` (generated by `openclaw setup`) and attempted in order.
154
+ - Default fallback endpoint remains `http://127.0.0.1:19400/v1/outbound`.
155
+ - Runtime may also use:
155
156
  - `CLAWDENTITY_CONNECTOR_BASE_URL`
156
157
  - `CLAWDENTITY_CONNECTOR_OUTBOUND_PATH`
158
+ - `connector start <agentName>` resolves bind URL from `~/.clawdentity/openclaw-connectors.json` when explicit env override is absent.
157
159
 
158
160
  Outbound JSON body sent by transform:
159
161