clawdentity 0.0.2 → 0.0.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/dist/bin.js
CHANGED
|
@@ -17062,7 +17062,7 @@ var DEFAULT_NONCE_TTL_MS = 5 * 60 * 1e3;
|
|
|
17062
17062
|
|
|
17063
17063
|
// src/index.ts
|
|
17064
17064
|
import { createRequire } from "module";
|
|
17065
|
-
import { Command as
|
|
17065
|
+
import { Command as Command10 } from "commander";
|
|
17066
17066
|
|
|
17067
17067
|
// src/commands/admin.ts
|
|
17068
17068
|
import { Command } from "commander";
|
|
@@ -19133,8 +19133,8 @@ var ConnectorClient = class {
|
|
|
19133
19133
|
}
|
|
19134
19134
|
}
|
|
19135
19135
|
async wait(delayMs) {
|
|
19136
|
-
await new Promise((
|
|
19137
|
-
setTimeout(
|
|
19136
|
+
await new Promise((resolve2) => {
|
|
19137
|
+
setTimeout(resolve2, delayMs);
|
|
19138
19138
|
});
|
|
19139
19139
|
}
|
|
19140
19140
|
makeFrameId() {
|
|
@@ -19378,7 +19378,7 @@ async function buildUpgradeHeaders(input) {
|
|
|
19378
19378
|
};
|
|
19379
19379
|
}
|
|
19380
19380
|
async function startConnectorRuntime(input) {
|
|
19381
|
-
const
|
|
19381
|
+
const logger12 = input.logger ?? createLogger({ service: "connector", module: "runtime" });
|
|
19382
19382
|
const fetchImpl = input.fetchImpl ?? fetch;
|
|
19383
19383
|
const secretKey = decodeBase64url(
|
|
19384
19384
|
parseRequiredString(input.credentials.secretKey, "secretKey")
|
|
@@ -19413,7 +19413,7 @@ async function startConnectorRuntime(input) {
|
|
|
19413
19413
|
openclawHookPath: resolveOpenclawHookPath(input.openclawHookPath),
|
|
19414
19414
|
openclawHookToken: resolveOpenclawHookToken(input.openclawHookToken),
|
|
19415
19415
|
fetchImpl,
|
|
19416
|
-
logger:
|
|
19416
|
+
logger: logger12,
|
|
19417
19417
|
webSocketFactory: createWebSocketFactory()
|
|
19418
19418
|
});
|
|
19419
19419
|
const outboundBaseUrl = normalizeOutboundBaseUrl(input.outboundBaseUrl);
|
|
@@ -19502,7 +19502,7 @@ async function startConnectorRuntime(input) {
|
|
|
19502
19502
|
writeJson(res, 202, { accepted: true, peer: relayRequest.peer });
|
|
19503
19503
|
} catch (error48) {
|
|
19504
19504
|
if (error48 instanceof AppError) {
|
|
19505
|
-
|
|
19505
|
+
logger12.warn("connector.outbound.rejected", {
|
|
19506
19506
|
code: error48.code,
|
|
19507
19507
|
status: error48.status,
|
|
19508
19508
|
message: error48.message
|
|
@@ -19515,7 +19515,7 @@ async function startConnectorRuntime(input) {
|
|
|
19515
19515
|
});
|
|
19516
19516
|
return;
|
|
19517
19517
|
}
|
|
19518
|
-
|
|
19518
|
+
logger12.error("connector.outbound.failed", {
|
|
19519
19519
|
errorName: error48 instanceof Error ? error48.name : "unknown"
|
|
19520
19520
|
});
|
|
19521
19521
|
writeJson(res, 500, {
|
|
@@ -19527,35 +19527,35 @@ async function startConnectorRuntime(input) {
|
|
|
19527
19527
|
}
|
|
19528
19528
|
});
|
|
19529
19529
|
let stoppedResolve;
|
|
19530
|
-
const stoppedPromise = new Promise((
|
|
19531
|
-
stoppedResolve =
|
|
19530
|
+
const stoppedPromise = new Promise((resolve2) => {
|
|
19531
|
+
stoppedResolve = resolve2;
|
|
19532
19532
|
});
|
|
19533
19533
|
const stop = async () => {
|
|
19534
19534
|
connectorClient.disconnect();
|
|
19535
|
-
await new Promise((
|
|
19535
|
+
await new Promise((resolve2, reject) => {
|
|
19536
19536
|
server.close((error48) => {
|
|
19537
19537
|
if (error48) {
|
|
19538
19538
|
reject(error48);
|
|
19539
19539
|
return;
|
|
19540
19540
|
}
|
|
19541
|
-
|
|
19541
|
+
resolve2();
|
|
19542
19542
|
});
|
|
19543
19543
|
});
|
|
19544
19544
|
stoppedResolve?.();
|
|
19545
19545
|
};
|
|
19546
|
-
await new Promise((
|
|
19546
|
+
await new Promise((resolve2, reject) => {
|
|
19547
19547
|
server.once("error", reject);
|
|
19548
19548
|
server.listen(
|
|
19549
19549
|
Number(outboundBaseUrl.port || "80"),
|
|
19550
19550
|
outboundBaseUrl.hostname,
|
|
19551
19551
|
() => {
|
|
19552
19552
|
server.off("error", reject);
|
|
19553
|
-
|
|
19553
|
+
resolve2();
|
|
19554
19554
|
}
|
|
19555
19555
|
);
|
|
19556
19556
|
});
|
|
19557
19557
|
connectorClient.connect();
|
|
19558
|
-
|
|
19558
|
+
logger12.info("connector.runtime.started", {
|
|
19559
19559
|
outboundUrl,
|
|
19560
19560
|
websocketUrl: wsUrl,
|
|
19561
19561
|
agentDid: input.credentials.agentDid
|
|
@@ -21794,10 +21794,572 @@ var createOpenclawCommand = () => {
|
|
|
21794
21794
|
return openclawCommand;
|
|
21795
21795
|
};
|
|
21796
21796
|
|
|
21797
|
-
// src/commands/
|
|
21798
|
-
import {
|
|
21797
|
+
// src/commands/pair.ts
|
|
21798
|
+
import { randomBytes as randomBytes3 } from "crypto";
|
|
21799
|
+
import { mkdir as mkdir6, readFile as readFile5, writeFile as writeFile6 } from "fs/promises";
|
|
21800
|
+
import { dirname as dirname5, join as join7, resolve } from "path";
|
|
21799
21801
|
import { Command as Command8 } from "commander";
|
|
21800
|
-
|
|
21802
|
+
import jsQR from "jsqr";
|
|
21803
|
+
import { PNG } from "pngjs";
|
|
21804
|
+
import QRCode from "qrcode";
|
|
21805
|
+
var logger9 = createLogger({ service: "cli", module: "pair" });
|
|
21806
|
+
var AGENTS_DIR_NAME5 = "agents";
|
|
21807
|
+
var AIT_FILE_NAME4 = "ait.jwt";
|
|
21808
|
+
var SECRET_KEY_FILE_NAME3 = "secret.key";
|
|
21809
|
+
var PAIRING_QR_DIR_NAME = "pairing";
|
|
21810
|
+
var PAIR_START_PATH = "/pair/start";
|
|
21811
|
+
var PAIR_CONFIRM_PATH = "/pair/confirm";
|
|
21812
|
+
var OWNER_PAT_HEADER = "x-claw-owner-pat";
|
|
21813
|
+
var NONCE_SIZE2 = 24;
|
|
21814
|
+
var PAIRING_TICKET_PREFIX = "clwpair1_";
|
|
21815
|
+
var isRecord8 = (value) => {
|
|
21816
|
+
return typeof value === "object" && value !== null;
|
|
21817
|
+
};
|
|
21818
|
+
function createCliError6(code, message2) {
|
|
21819
|
+
return new AppError({
|
|
21820
|
+
code,
|
|
21821
|
+
message: message2,
|
|
21822
|
+
status: 400
|
|
21823
|
+
});
|
|
21824
|
+
}
|
|
21825
|
+
function parseNonEmptyString8(value) {
|
|
21826
|
+
if (typeof value !== "string") {
|
|
21827
|
+
return "";
|
|
21828
|
+
}
|
|
21829
|
+
return value.trim();
|
|
21830
|
+
}
|
|
21831
|
+
function parsePairingTicket(value) {
|
|
21832
|
+
const ticket = parseNonEmptyString8(value);
|
|
21833
|
+
if (!ticket.startsWith(PAIRING_TICKET_PREFIX)) {
|
|
21834
|
+
throw createCliError6(
|
|
21835
|
+
"CLI_PAIR_CONFIRM_TICKET_INVALID",
|
|
21836
|
+
"Pairing ticket is invalid"
|
|
21837
|
+
);
|
|
21838
|
+
}
|
|
21839
|
+
return ticket;
|
|
21840
|
+
}
|
|
21841
|
+
function parseTtlSeconds(value) {
|
|
21842
|
+
const raw = parseNonEmptyString8(value);
|
|
21843
|
+
if (raw.length === 0) {
|
|
21844
|
+
return void 0;
|
|
21845
|
+
}
|
|
21846
|
+
const parsed = Number.parseInt(raw, 10);
|
|
21847
|
+
if (!Number.isInteger(parsed) || parsed < 1) {
|
|
21848
|
+
throw createCliError6(
|
|
21849
|
+
"CLI_PAIR_START_INVALID_TTL",
|
|
21850
|
+
"ttlSeconds must be a positive integer"
|
|
21851
|
+
);
|
|
21852
|
+
}
|
|
21853
|
+
return parsed;
|
|
21854
|
+
}
|
|
21855
|
+
function resolveProxyUrl(overrideProxyUrl) {
|
|
21856
|
+
const candidate = parseNonEmptyString8(overrideProxyUrl) || parseNonEmptyString8(process.env.CLAWDENTITY_PROXY_URL);
|
|
21857
|
+
if (candidate.length === 0) {
|
|
21858
|
+
throw createCliError6(
|
|
21859
|
+
"CLI_PAIR_PROXY_URL_REQUIRED",
|
|
21860
|
+
"Proxy URL is required. Pass --proxy-url <url> or set CLAWDENTITY_PROXY_URL."
|
|
21861
|
+
);
|
|
21862
|
+
}
|
|
21863
|
+
try {
|
|
21864
|
+
const parsed = new URL(candidate);
|
|
21865
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
|
|
21866
|
+
throw new Error("invalid protocol");
|
|
21867
|
+
}
|
|
21868
|
+
return parsed.toString();
|
|
21869
|
+
} catch {
|
|
21870
|
+
throw createCliError6("CLI_PAIR_INVALID_PROXY_URL", "Proxy URL is invalid");
|
|
21871
|
+
}
|
|
21872
|
+
}
|
|
21873
|
+
function toProxyRequestUrl(proxyUrl, path) {
|
|
21874
|
+
const normalizedBase = proxyUrl.endsWith("/") ? proxyUrl : `${proxyUrl}/`;
|
|
21875
|
+
return new URL(path.slice(1), normalizedBase).toString();
|
|
21876
|
+
}
|
|
21877
|
+
function toPathWithQuery3(url2) {
|
|
21878
|
+
const parsed = new URL(url2);
|
|
21879
|
+
return `${parsed.pathname}${parsed.search}`;
|
|
21880
|
+
}
|
|
21881
|
+
function extractErrorCode(payload) {
|
|
21882
|
+
if (!isRecord8(payload)) {
|
|
21883
|
+
return void 0;
|
|
21884
|
+
}
|
|
21885
|
+
const envelope = payload;
|
|
21886
|
+
if (!envelope.error || typeof envelope.error.code !== "string") {
|
|
21887
|
+
return void 0;
|
|
21888
|
+
}
|
|
21889
|
+
const code = envelope.error.code.trim();
|
|
21890
|
+
return code.length > 0 ? code : void 0;
|
|
21891
|
+
}
|
|
21892
|
+
function extractErrorMessage(payload) {
|
|
21893
|
+
if (!isRecord8(payload)) {
|
|
21894
|
+
return void 0;
|
|
21895
|
+
}
|
|
21896
|
+
const envelope = payload;
|
|
21897
|
+
if (!envelope.error || typeof envelope.error.message !== "string") {
|
|
21898
|
+
return void 0;
|
|
21899
|
+
}
|
|
21900
|
+
const message2 = envelope.error.message.trim();
|
|
21901
|
+
return message2.length > 0 ? message2 : void 0;
|
|
21902
|
+
}
|
|
21903
|
+
async function parseJsonResponse5(response) {
|
|
21904
|
+
try {
|
|
21905
|
+
return await response.json();
|
|
21906
|
+
} catch {
|
|
21907
|
+
return void 0;
|
|
21908
|
+
}
|
|
21909
|
+
}
|
|
21910
|
+
async function executePairRequest(input) {
|
|
21911
|
+
try {
|
|
21912
|
+
return await input.fetchImpl(input.url, input.init);
|
|
21913
|
+
} catch {
|
|
21914
|
+
throw createCliError6(
|
|
21915
|
+
"CLI_PAIR_REQUEST_FAILED",
|
|
21916
|
+
"Unable to connect to proxy URL. Check network access and proxyUrl."
|
|
21917
|
+
);
|
|
21918
|
+
}
|
|
21919
|
+
}
|
|
21920
|
+
function mapStartPairError(status, payload) {
|
|
21921
|
+
const code = extractErrorCode(payload);
|
|
21922
|
+
const message2 = extractErrorMessage(payload);
|
|
21923
|
+
if (code === "PROXY_PAIR_OWNER_PAT_INVALID" || status === 401) {
|
|
21924
|
+
return message2 ? `Owner PAT is invalid (401): ${message2}` : "Owner PAT is invalid or expired (401).";
|
|
21925
|
+
}
|
|
21926
|
+
if (code === "PROXY_PAIR_OWNER_PAT_FORBIDDEN" || status === 403) {
|
|
21927
|
+
return message2 ? `Owner PAT does not control initiator agent DID (403): ${message2}` : "Owner PAT does not control initiator agent DID (403).";
|
|
21928
|
+
}
|
|
21929
|
+
if (status === 400) {
|
|
21930
|
+
return message2 ? `Pair start request is invalid (400): ${message2}` : "Pair start request is invalid (400).";
|
|
21931
|
+
}
|
|
21932
|
+
if (status >= 500) {
|
|
21933
|
+
return `Proxy pairing service is unavailable (${status}).`;
|
|
21934
|
+
}
|
|
21935
|
+
if (message2) {
|
|
21936
|
+
return `Pair start failed (${status}): ${message2}`;
|
|
21937
|
+
}
|
|
21938
|
+
return `Pair start failed (${status})`;
|
|
21939
|
+
}
|
|
21940
|
+
function mapConfirmPairError(status, payload) {
|
|
21941
|
+
const code = extractErrorCode(payload);
|
|
21942
|
+
const message2 = extractErrorMessage(payload);
|
|
21943
|
+
if (code === "PROXY_PAIR_TICKET_NOT_FOUND" || status === 404) {
|
|
21944
|
+
return "Pairing ticket is invalid or expired";
|
|
21945
|
+
}
|
|
21946
|
+
if (code === "PROXY_PAIR_TICKET_EXPIRED" || status === 410) {
|
|
21947
|
+
return "Pairing ticket has expired";
|
|
21948
|
+
}
|
|
21949
|
+
if (status === 400) {
|
|
21950
|
+
return message2 ? `Pair confirm request is invalid (400): ${message2}` : "Pair confirm request is invalid (400).";
|
|
21951
|
+
}
|
|
21952
|
+
if (status >= 500) {
|
|
21953
|
+
return `Proxy pairing service is unavailable (${status}).`;
|
|
21954
|
+
}
|
|
21955
|
+
if (message2) {
|
|
21956
|
+
return `Pair confirm failed (${status}): ${message2}`;
|
|
21957
|
+
}
|
|
21958
|
+
return `Pair confirm failed (${status})`;
|
|
21959
|
+
}
|
|
21960
|
+
function parsePairStartResponse(payload) {
|
|
21961
|
+
if (!isRecord8(payload)) {
|
|
21962
|
+
throw createCliError6(
|
|
21963
|
+
"CLI_PAIR_START_INVALID_RESPONSE",
|
|
21964
|
+
"Pair start response is invalid"
|
|
21965
|
+
);
|
|
21966
|
+
}
|
|
21967
|
+
const ticket = parsePairingTicket(payload.ticket);
|
|
21968
|
+
const initiatorAgentDid = parseNonEmptyString8(payload.initiatorAgentDid);
|
|
21969
|
+
const expiresAt = parseNonEmptyString8(payload.expiresAt);
|
|
21970
|
+
if (initiatorAgentDid.length === 0 || expiresAt.length === 0) {
|
|
21971
|
+
throw createCliError6(
|
|
21972
|
+
"CLI_PAIR_START_INVALID_RESPONSE",
|
|
21973
|
+
"Pair start response is invalid"
|
|
21974
|
+
);
|
|
21975
|
+
}
|
|
21976
|
+
return {
|
|
21977
|
+
ticket,
|
|
21978
|
+
initiatorAgentDid,
|
|
21979
|
+
expiresAt
|
|
21980
|
+
};
|
|
21981
|
+
}
|
|
21982
|
+
function parsePairConfirmResponse(payload) {
|
|
21983
|
+
if (!isRecord8(payload)) {
|
|
21984
|
+
throw createCliError6(
|
|
21985
|
+
"CLI_PAIR_CONFIRM_INVALID_RESPONSE",
|
|
21986
|
+
"Pair confirm response is invalid"
|
|
21987
|
+
);
|
|
21988
|
+
}
|
|
21989
|
+
const paired = payload.paired === true;
|
|
21990
|
+
const initiatorAgentDid = parseNonEmptyString8(payload.initiatorAgentDid);
|
|
21991
|
+
const responderAgentDid = parseNonEmptyString8(payload.responderAgentDid);
|
|
21992
|
+
if (!paired || initiatorAgentDid.length === 0 || responderAgentDid.length === 0) {
|
|
21993
|
+
throw createCliError6(
|
|
21994
|
+
"CLI_PAIR_CONFIRM_INVALID_RESPONSE",
|
|
21995
|
+
"Pair confirm response is invalid"
|
|
21996
|
+
);
|
|
21997
|
+
}
|
|
21998
|
+
return {
|
|
21999
|
+
paired,
|
|
22000
|
+
initiatorAgentDid,
|
|
22001
|
+
responderAgentDid
|
|
22002
|
+
};
|
|
22003
|
+
}
|
|
22004
|
+
async function readAgentProofMaterial(agentName, dependencies) {
|
|
22005
|
+
const readFileImpl = dependencies.readFileImpl ?? readFile5;
|
|
22006
|
+
const getConfigDirImpl = dependencies.getConfigDirImpl ?? getConfigDir;
|
|
22007
|
+
const normalizedAgentName = assertValidAgentName(agentName);
|
|
22008
|
+
const agentDir = join7(
|
|
22009
|
+
getConfigDirImpl(),
|
|
22010
|
+
AGENTS_DIR_NAME5,
|
|
22011
|
+
normalizedAgentName
|
|
22012
|
+
);
|
|
22013
|
+
const aitPath = join7(agentDir, AIT_FILE_NAME4);
|
|
22014
|
+
const secretKeyPath = join7(agentDir, SECRET_KEY_FILE_NAME3);
|
|
22015
|
+
let ait;
|
|
22016
|
+
try {
|
|
22017
|
+
ait = (await readFileImpl(aitPath, "utf-8")).trim();
|
|
22018
|
+
} catch (error48) {
|
|
22019
|
+
const nodeError = error48;
|
|
22020
|
+
if (nodeError.code === "ENOENT") {
|
|
22021
|
+
throw createCliError6(
|
|
22022
|
+
"CLI_PAIR_AGENT_NOT_FOUND",
|
|
22023
|
+
`Agent "${normalizedAgentName}" is missing ${AIT_FILE_NAME4}. Run agent create first.`
|
|
22024
|
+
);
|
|
22025
|
+
}
|
|
22026
|
+
throw error48;
|
|
22027
|
+
}
|
|
22028
|
+
if (ait.length === 0) {
|
|
22029
|
+
throw createCliError6(
|
|
22030
|
+
"CLI_PAIR_AGENT_NOT_FOUND",
|
|
22031
|
+
`Agent "${normalizedAgentName}" has an empty ${AIT_FILE_NAME4}`
|
|
22032
|
+
);
|
|
22033
|
+
}
|
|
22034
|
+
let encodedSecretKey;
|
|
22035
|
+
try {
|
|
22036
|
+
encodedSecretKey = (await readFileImpl(secretKeyPath, "utf-8")).trim();
|
|
22037
|
+
} catch (error48) {
|
|
22038
|
+
const nodeError = error48;
|
|
22039
|
+
if (nodeError.code === "ENOENT") {
|
|
22040
|
+
throw createCliError6(
|
|
22041
|
+
"CLI_PAIR_AGENT_NOT_FOUND",
|
|
22042
|
+
`Agent "${normalizedAgentName}" is missing ${SECRET_KEY_FILE_NAME3}. Run agent create first.`
|
|
22043
|
+
);
|
|
22044
|
+
}
|
|
22045
|
+
throw error48;
|
|
22046
|
+
}
|
|
22047
|
+
if (encodedSecretKey.length === 0) {
|
|
22048
|
+
throw createCliError6(
|
|
22049
|
+
"CLI_PAIR_AGENT_NOT_FOUND",
|
|
22050
|
+
`Agent "${normalizedAgentName}" has an empty ${SECRET_KEY_FILE_NAME3}`
|
|
22051
|
+
);
|
|
22052
|
+
}
|
|
22053
|
+
let secretKey;
|
|
22054
|
+
try {
|
|
22055
|
+
secretKey = decodeBase64url(encodedSecretKey);
|
|
22056
|
+
} catch {
|
|
22057
|
+
throw createCliError6(
|
|
22058
|
+
"CLI_PAIR_AGENT_NOT_FOUND",
|
|
22059
|
+
`Agent "${normalizedAgentName}" has invalid ${SECRET_KEY_FILE_NAME3}`
|
|
22060
|
+
);
|
|
22061
|
+
}
|
|
22062
|
+
return {
|
|
22063
|
+
ait,
|
|
22064
|
+
secretKey
|
|
22065
|
+
};
|
|
22066
|
+
}
|
|
22067
|
+
function resolveOwnerPat(options) {
|
|
22068
|
+
const ownerPat = parseNonEmptyString8(options.explicitOwnerPat) || parseNonEmptyString8(options.config.apiKey);
|
|
22069
|
+
if (ownerPat.length > 0) {
|
|
22070
|
+
return ownerPat;
|
|
22071
|
+
}
|
|
22072
|
+
throw createCliError6(
|
|
22073
|
+
"CLI_PAIR_START_OWNER_PAT_REQUIRED",
|
|
22074
|
+
"Owner PAT is required. Pass --owner-pat <token> or configure API key with `clawdentity invite redeem` / `clawdentity config set apiKey <token>`."
|
|
22075
|
+
);
|
|
22076
|
+
}
|
|
22077
|
+
async function buildSignedHeaders(input) {
|
|
22078
|
+
const signed = await signHttpRequest({
|
|
22079
|
+
method: input.method,
|
|
22080
|
+
pathWithQuery: toPathWithQuery3(input.requestUrl),
|
|
22081
|
+
timestamp: String(input.timestampSeconds),
|
|
22082
|
+
nonce: input.nonce,
|
|
22083
|
+
body: input.bodyBytes,
|
|
22084
|
+
secretKey: input.secretKey
|
|
22085
|
+
});
|
|
22086
|
+
return signed.headers;
|
|
22087
|
+
}
|
|
22088
|
+
async function encodeTicketQrPng(ticket) {
|
|
22089
|
+
const buffer = await QRCode.toBuffer(ticket, {
|
|
22090
|
+
type: "png",
|
|
22091
|
+
width: 512,
|
|
22092
|
+
margin: 2,
|
|
22093
|
+
errorCorrectionLevel: "M"
|
|
22094
|
+
});
|
|
22095
|
+
return new Uint8Array(buffer);
|
|
22096
|
+
}
|
|
22097
|
+
function decodeTicketFromPng(imageBytes) {
|
|
22098
|
+
let decodedPng;
|
|
22099
|
+
try {
|
|
22100
|
+
decodedPng = PNG.sync.read(Buffer.from(imageBytes));
|
|
22101
|
+
} catch {
|
|
22102
|
+
throw createCliError6(
|
|
22103
|
+
"CLI_PAIR_CONFIRM_QR_FILE_INVALID",
|
|
22104
|
+
"QR image file is invalid or unsupported"
|
|
22105
|
+
);
|
|
22106
|
+
}
|
|
22107
|
+
const imageData = new Uint8ClampedArray(
|
|
22108
|
+
decodedPng.data.buffer,
|
|
22109
|
+
decodedPng.data.byteOffset,
|
|
22110
|
+
decodedPng.data.byteLength
|
|
22111
|
+
);
|
|
22112
|
+
const decoded = jsQR(imageData, decodedPng.width, decodedPng.height);
|
|
22113
|
+
if (!decoded || parseNonEmptyString8(decoded.data).length === 0) {
|
|
22114
|
+
throw createCliError6(
|
|
22115
|
+
"CLI_PAIR_CONFIRM_QR_NOT_FOUND",
|
|
22116
|
+
"No pairing QR code was found in the image"
|
|
22117
|
+
);
|
|
22118
|
+
}
|
|
22119
|
+
return parsePairingTicket(decoded.data);
|
|
22120
|
+
}
|
|
22121
|
+
async function persistPairingQr(input) {
|
|
22122
|
+
const mkdirImpl = input.dependencies.mkdirImpl ?? mkdir6;
|
|
22123
|
+
const writeFileImpl = input.dependencies.writeFileImpl ?? writeFile6;
|
|
22124
|
+
const getConfigDirImpl = input.dependencies.getConfigDirImpl ?? getConfigDir;
|
|
22125
|
+
const qrEncodeImpl = input.dependencies.qrEncodeImpl ?? encodeTicketQrPng;
|
|
22126
|
+
const baseDir = join7(getConfigDirImpl(), PAIRING_QR_DIR_NAME);
|
|
22127
|
+
const outputPath = parseNonEmptyString8(input.qrOutput) ? resolve(input.qrOutput ?? "") : join7(
|
|
22128
|
+
baseDir,
|
|
22129
|
+
`${assertValidAgentName(input.agentName)}-pair-${input.nowSeconds}.png`
|
|
22130
|
+
);
|
|
22131
|
+
await mkdirImpl(dirname5(outputPath), { recursive: true });
|
|
22132
|
+
const imageBytes = await qrEncodeImpl(input.ticket);
|
|
22133
|
+
await writeFileImpl(outputPath, imageBytes);
|
|
22134
|
+
return outputPath;
|
|
22135
|
+
}
|
|
22136
|
+
function resolveConfirmTicketSource(options) {
|
|
22137
|
+
const inlineTicket = parseNonEmptyString8(options.ticket);
|
|
22138
|
+
const qrFile = parseNonEmptyString8(options.qrFile);
|
|
22139
|
+
if (inlineTicket.length > 0 && qrFile.length > 0) {
|
|
22140
|
+
throw createCliError6(
|
|
22141
|
+
"CLI_PAIR_CONFIRM_INPUT_CONFLICT",
|
|
22142
|
+
"Provide either --ticket or --qr-file, not both"
|
|
22143
|
+
);
|
|
22144
|
+
}
|
|
22145
|
+
if (inlineTicket.length > 0) {
|
|
22146
|
+
return {
|
|
22147
|
+
ticket: parsePairingTicket(inlineTicket),
|
|
22148
|
+
source: "ticket"
|
|
22149
|
+
};
|
|
22150
|
+
}
|
|
22151
|
+
if (qrFile.length > 0) {
|
|
22152
|
+
return {
|
|
22153
|
+
ticket: "",
|
|
22154
|
+
source: "qr-file",
|
|
22155
|
+
qrFilePath: resolve(qrFile)
|
|
22156
|
+
};
|
|
22157
|
+
}
|
|
22158
|
+
throw createCliError6(
|
|
22159
|
+
"CLI_PAIR_CONFIRM_TICKET_REQUIRED",
|
|
22160
|
+
"Pairing ticket is required. Pass --ticket <clwpair1_...> or --qr-file <path>."
|
|
22161
|
+
);
|
|
22162
|
+
}
|
|
22163
|
+
async function startPairing(agentName, options, dependencies = {}) {
|
|
22164
|
+
const fetchImpl = dependencies.fetchImpl ?? fetch;
|
|
22165
|
+
const resolveConfigImpl = dependencies.resolveConfigImpl ?? resolveConfig;
|
|
22166
|
+
const nowSecondsImpl = dependencies.nowSecondsImpl ?? (() => Math.floor(Date.now() / 1e3));
|
|
22167
|
+
const nonceFactoryImpl = dependencies.nonceFactoryImpl ?? (() => randomBytes3(NONCE_SIZE2).toString("base64url"));
|
|
22168
|
+
const ttlSeconds = parseTtlSeconds(options.ttlSeconds);
|
|
22169
|
+
const proxyUrl = resolveProxyUrl(options.proxyUrl);
|
|
22170
|
+
const config2 = await resolveConfigImpl();
|
|
22171
|
+
const ownerPat = resolveOwnerPat({
|
|
22172
|
+
explicitOwnerPat: options.ownerPat,
|
|
22173
|
+
config: config2
|
|
22174
|
+
});
|
|
22175
|
+
const { ait, secretKey } = await readAgentProofMaterial(
|
|
22176
|
+
agentName,
|
|
22177
|
+
dependencies
|
|
22178
|
+
);
|
|
22179
|
+
const requestUrl = toProxyRequestUrl(proxyUrl, PAIR_START_PATH);
|
|
22180
|
+
const requestBody = JSON.stringify({
|
|
22181
|
+
ttlSeconds
|
|
22182
|
+
});
|
|
22183
|
+
const bodyBytes = new TextEncoder().encode(requestBody);
|
|
22184
|
+
const timestampSeconds = nowSecondsImpl();
|
|
22185
|
+
const nonce = nonceFactoryImpl();
|
|
22186
|
+
const signedHeaders = await buildSignedHeaders({
|
|
22187
|
+
method: "POST",
|
|
22188
|
+
requestUrl,
|
|
22189
|
+
bodyBytes,
|
|
22190
|
+
secretKey,
|
|
22191
|
+
timestampSeconds,
|
|
22192
|
+
nonce
|
|
22193
|
+
});
|
|
22194
|
+
const response = await executePairRequest({
|
|
22195
|
+
fetchImpl,
|
|
22196
|
+
url: requestUrl,
|
|
22197
|
+
init: {
|
|
22198
|
+
method: "POST",
|
|
22199
|
+
headers: {
|
|
22200
|
+
authorization: `Claw ${ait}`,
|
|
22201
|
+
"content-type": "application/json",
|
|
22202
|
+
[OWNER_PAT_HEADER]: ownerPat,
|
|
22203
|
+
...signedHeaders
|
|
22204
|
+
},
|
|
22205
|
+
body: requestBody
|
|
22206
|
+
}
|
|
22207
|
+
});
|
|
22208
|
+
const responseBody = await parseJsonResponse5(response);
|
|
22209
|
+
if (!response.ok) {
|
|
22210
|
+
throw createCliError6(
|
|
22211
|
+
"CLI_PAIR_START_FAILED",
|
|
22212
|
+
mapStartPairError(response.status, responseBody)
|
|
22213
|
+
);
|
|
22214
|
+
}
|
|
22215
|
+
const parsed = parsePairStartResponse(responseBody);
|
|
22216
|
+
const result = {
|
|
22217
|
+
...parsed,
|
|
22218
|
+
proxyUrl
|
|
22219
|
+
};
|
|
22220
|
+
if (options.qr === true) {
|
|
22221
|
+
result.qrPath = await persistPairingQr({
|
|
22222
|
+
agentName,
|
|
22223
|
+
qrOutput: options.qrOutput,
|
|
22224
|
+
ticket: parsed.ticket,
|
|
22225
|
+
dependencies,
|
|
22226
|
+
nowSeconds: timestampSeconds
|
|
22227
|
+
});
|
|
22228
|
+
}
|
|
22229
|
+
return result;
|
|
22230
|
+
}
|
|
22231
|
+
async function confirmPairing(agentName, options, dependencies = {}) {
|
|
22232
|
+
const fetchImpl = dependencies.fetchImpl ?? fetch;
|
|
22233
|
+
const nowSecondsImpl = dependencies.nowSecondsImpl ?? (() => Math.floor(Date.now() / 1e3));
|
|
22234
|
+
const nonceFactoryImpl = dependencies.nonceFactoryImpl ?? (() => randomBytes3(NONCE_SIZE2).toString("base64url"));
|
|
22235
|
+
const readFileImpl = dependencies.readFileImpl ?? readFile5;
|
|
22236
|
+
const qrDecodeImpl = dependencies.qrDecodeImpl ?? decodeTicketFromPng;
|
|
22237
|
+
const ticketSource = resolveConfirmTicketSource(options);
|
|
22238
|
+
const proxyUrl = resolveProxyUrl(options.proxyUrl);
|
|
22239
|
+
let ticket = ticketSource.ticket;
|
|
22240
|
+
if (ticketSource.source === "qr-file") {
|
|
22241
|
+
if (!ticketSource.qrFilePath) {
|
|
22242
|
+
throw createCliError6(
|
|
22243
|
+
"CLI_PAIR_CONFIRM_QR_FILE_REQUIRED",
|
|
22244
|
+
"QR file path is required"
|
|
22245
|
+
);
|
|
22246
|
+
}
|
|
22247
|
+
let imageBytes;
|
|
22248
|
+
try {
|
|
22249
|
+
imageBytes = await readFileImpl(ticketSource.qrFilePath);
|
|
22250
|
+
} catch (error48) {
|
|
22251
|
+
const nodeError = error48;
|
|
22252
|
+
if (nodeError.code === "ENOENT") {
|
|
22253
|
+
throw createCliError6(
|
|
22254
|
+
"CLI_PAIR_CONFIRM_QR_FILE_NOT_FOUND",
|
|
22255
|
+
`QR file not found: ${ticketSource.qrFilePath}`
|
|
22256
|
+
);
|
|
22257
|
+
}
|
|
22258
|
+
throw error48;
|
|
22259
|
+
}
|
|
22260
|
+
ticket = parsePairingTicket(qrDecodeImpl(new Uint8Array(imageBytes)));
|
|
22261
|
+
}
|
|
22262
|
+
const { ait, secretKey } = await readAgentProofMaterial(
|
|
22263
|
+
agentName,
|
|
22264
|
+
dependencies
|
|
22265
|
+
);
|
|
22266
|
+
const requestUrl = toProxyRequestUrl(proxyUrl, PAIR_CONFIRM_PATH);
|
|
22267
|
+
const requestBody = JSON.stringify({ ticket });
|
|
22268
|
+
const bodyBytes = new TextEncoder().encode(requestBody);
|
|
22269
|
+
const timestampSeconds = nowSecondsImpl();
|
|
22270
|
+
const nonce = nonceFactoryImpl();
|
|
22271
|
+
const signedHeaders = await buildSignedHeaders({
|
|
22272
|
+
method: "POST",
|
|
22273
|
+
requestUrl,
|
|
22274
|
+
bodyBytes,
|
|
22275
|
+
secretKey,
|
|
22276
|
+
timestampSeconds,
|
|
22277
|
+
nonce
|
|
22278
|
+
});
|
|
22279
|
+
const response = await executePairRequest({
|
|
22280
|
+
fetchImpl,
|
|
22281
|
+
url: requestUrl,
|
|
22282
|
+
init: {
|
|
22283
|
+
method: "POST",
|
|
22284
|
+
headers: {
|
|
22285
|
+
authorization: `Claw ${ait}`,
|
|
22286
|
+
"content-type": "application/json",
|
|
22287
|
+
...signedHeaders
|
|
22288
|
+
},
|
|
22289
|
+
body: requestBody
|
|
22290
|
+
}
|
|
22291
|
+
});
|
|
22292
|
+
const responseBody = await parseJsonResponse5(response);
|
|
22293
|
+
if (!response.ok) {
|
|
22294
|
+
throw createCliError6(
|
|
22295
|
+
"CLI_PAIR_CONFIRM_FAILED",
|
|
22296
|
+
mapConfirmPairError(response.status, responseBody)
|
|
22297
|
+
);
|
|
22298
|
+
}
|
|
22299
|
+
const parsed = parsePairConfirmResponse(responseBody);
|
|
22300
|
+
return {
|
|
22301
|
+
...parsed,
|
|
22302
|
+
proxyUrl
|
|
22303
|
+
};
|
|
22304
|
+
}
|
|
22305
|
+
var createPairCommand = (dependencies = {}) => {
|
|
22306
|
+
const pairCommand = new Command8("pair").description(
|
|
22307
|
+
"Manage proxy trust pairing between agents"
|
|
22308
|
+
);
|
|
22309
|
+
pairCommand.command("start <agentName>").description("Start pairing and issue one-time pairing ticket").option(
|
|
22310
|
+
"--proxy-url <url>",
|
|
22311
|
+
"Initiator proxy base URL (or set CLAWDENTITY_PROXY_URL)"
|
|
22312
|
+
).option(
|
|
22313
|
+
"--owner-pat <token>",
|
|
22314
|
+
"Owner PAT override (defaults to configured API key)"
|
|
22315
|
+
).option("--ttl-seconds <seconds>", "Pairing ticket expiry in seconds").option("--qr", "Generate a local QR file for sharing").option("--qr-output <path>", "Write QR PNG to a specific file path").action(
|
|
22316
|
+
withErrorHandling(
|
|
22317
|
+
"pair start",
|
|
22318
|
+
async (agentName, options) => {
|
|
22319
|
+
const result = await startPairing(agentName, options, dependencies);
|
|
22320
|
+
logger9.info("cli.pair_started", {
|
|
22321
|
+
initiatorAgentDid: result.initiatorAgentDid,
|
|
22322
|
+
proxyUrl: result.proxyUrl,
|
|
22323
|
+
expiresAt: result.expiresAt,
|
|
22324
|
+
qrPath: result.qrPath
|
|
22325
|
+
});
|
|
22326
|
+
writeStdoutLine("Pairing ticket created");
|
|
22327
|
+
writeStdoutLine(`Ticket: ${result.ticket}`);
|
|
22328
|
+
writeStdoutLine(`Initiator Agent DID: ${result.initiatorAgentDid}`);
|
|
22329
|
+
writeStdoutLine(`Expires At: ${result.expiresAt}`);
|
|
22330
|
+
if (result.qrPath) {
|
|
22331
|
+
writeStdoutLine(`QR File: ${result.qrPath}`);
|
|
22332
|
+
}
|
|
22333
|
+
}
|
|
22334
|
+
)
|
|
22335
|
+
);
|
|
22336
|
+
pairCommand.command("confirm <agentName>").description("Confirm pairing using one-time pairing ticket").option("--ticket <ticket>", "One-time pairing ticket (clwpair1_...)").option("--qr-file <path>", "Path to pairing QR PNG file").option(
|
|
22337
|
+
"--proxy-url <url>",
|
|
22338
|
+
"Responder proxy base URL (or set CLAWDENTITY_PROXY_URL)"
|
|
22339
|
+
).action(
|
|
22340
|
+
withErrorHandling(
|
|
22341
|
+
"pair confirm",
|
|
22342
|
+
async (agentName, options) => {
|
|
22343
|
+
const result = await confirmPairing(agentName, options, dependencies);
|
|
22344
|
+
logger9.info("cli.pair_confirmed", {
|
|
22345
|
+
initiatorAgentDid: result.initiatorAgentDid,
|
|
22346
|
+
responderAgentDid: result.responderAgentDid,
|
|
22347
|
+
proxyUrl: result.proxyUrl
|
|
22348
|
+
});
|
|
22349
|
+
writeStdoutLine("Pairing confirmed");
|
|
22350
|
+
writeStdoutLine(`Initiator Agent DID: ${result.initiatorAgentDid}`);
|
|
22351
|
+
writeStdoutLine(`Responder Agent DID: ${result.responderAgentDid}`);
|
|
22352
|
+
writeStdoutLine(`Paired: ${result.paired ? "true" : "false"}`);
|
|
22353
|
+
}
|
|
22354
|
+
)
|
|
22355
|
+
);
|
|
22356
|
+
return pairCommand;
|
|
22357
|
+
};
|
|
22358
|
+
|
|
22359
|
+
// src/commands/verify.ts
|
|
22360
|
+
import { readFile as readFile6 } from "fs/promises";
|
|
22361
|
+
import { Command as Command9 } from "commander";
|
|
22362
|
+
var logger10 = createLogger({ service: "cli", module: "verify" });
|
|
21801
22363
|
var REGISTRY_KEYS_CACHE_FILE = "registry-keys.json";
|
|
21802
22364
|
var CRL_CLAIMS_CACHE_FILE = "crl-claims.json";
|
|
21803
22365
|
var REGISTRY_KEYS_CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
@@ -21808,7 +22370,7 @@ var VerifyCommandError = class extends Error {
|
|
|
21808
22370
|
this.name = "VerifyCommandError";
|
|
21809
22371
|
}
|
|
21810
22372
|
};
|
|
21811
|
-
var
|
|
22373
|
+
var isRecord9 = (value) => {
|
|
21812
22374
|
return typeof value === "object" && value !== null;
|
|
21813
22375
|
};
|
|
21814
22376
|
var normalizeRegistryUrl = (registryUrl) => {
|
|
@@ -21844,7 +22406,7 @@ var resolveToken = async (tokenOrFile) => {
|
|
|
21844
22406
|
throw new VerifyCommandError("invalid token (value is empty)");
|
|
21845
22407
|
}
|
|
21846
22408
|
try {
|
|
21847
|
-
const fileContents = await
|
|
22409
|
+
const fileContents = await readFile6(input, "utf-8");
|
|
21848
22410
|
const token = fileContents.trim();
|
|
21849
22411
|
if (token.length === 0) {
|
|
21850
22412
|
throw new VerifyCommandError(`invalid token (${input} is empty)`);
|
|
@@ -21876,7 +22438,7 @@ var parseResponseJson = async (response) => {
|
|
|
21876
22438
|
}
|
|
21877
22439
|
};
|
|
21878
22440
|
var parseSigningKeys = (payload) => {
|
|
21879
|
-
if (!
|
|
22441
|
+
if (!isRecord9(payload) || !Array.isArray(payload.keys)) {
|
|
21880
22442
|
throw new VerifyCommandError(
|
|
21881
22443
|
"verification keys unavailable (response payload is invalid)"
|
|
21882
22444
|
);
|
|
@@ -21895,7 +22457,7 @@ var parseSigningKeys = (payload) => {
|
|
|
21895
22457
|
};
|
|
21896
22458
|
var parseRegistryKeysCache = (rawCache) => {
|
|
21897
22459
|
const parsed = parseJson(rawCache);
|
|
21898
|
-
if (!
|
|
22460
|
+
if (!isRecord9(parsed)) {
|
|
21899
22461
|
return void 0;
|
|
21900
22462
|
}
|
|
21901
22463
|
const { registryUrl, fetchedAtMs, keys } = parsed;
|
|
@@ -21921,7 +22483,7 @@ var parseRegistryKeysCache = (rawCache) => {
|
|
|
21921
22483
|
};
|
|
21922
22484
|
var parseCrlCache = (rawCache) => {
|
|
21923
22485
|
const parsed = parseJson(rawCache);
|
|
21924
|
-
if (!
|
|
22486
|
+
if (!isRecord9(parsed)) {
|
|
21925
22487
|
return void 0;
|
|
21926
22488
|
}
|
|
21927
22489
|
const { registryUrl, fetchedAtMs, claims } = parsed;
|
|
@@ -22019,7 +22581,7 @@ var fetchCrlClaims = async (input) => {
|
|
|
22019
22581
|
);
|
|
22020
22582
|
}
|
|
22021
22583
|
const payload = await parseResponseJson(response);
|
|
22022
|
-
if (!
|
|
22584
|
+
if (!isRecord9(payload) || typeof payload.crl !== "string") {
|
|
22023
22585
|
throw new VerifyCommandError(
|
|
22024
22586
|
"revocation check unavailable (response payload is invalid)"
|
|
22025
22587
|
);
|
|
@@ -22064,7 +22626,7 @@ var loadCrlClaims = async (input) => {
|
|
|
22064
22626
|
return claims;
|
|
22065
22627
|
};
|
|
22066
22628
|
var toInvalidTokenReason = (error48) => {
|
|
22067
|
-
if (
|
|
22629
|
+
if (isRecord9(error48) && typeof error48.message === "string") {
|
|
22068
22630
|
return `invalid token (${error48.message})`;
|
|
22069
22631
|
}
|
|
22070
22632
|
if (error48 instanceof Error && error48.message.length > 0) {
|
|
@@ -22132,7 +22694,7 @@ var runVerify = async (tokenOrFile) => {
|
|
|
22132
22694
|
printResult(false, "revoked");
|
|
22133
22695
|
return;
|
|
22134
22696
|
}
|
|
22135
|
-
|
|
22697
|
+
logger10.info("cli.verify.success", {
|
|
22136
22698
|
did: claims.sub,
|
|
22137
22699
|
jti: claims.jti,
|
|
22138
22700
|
issuer: claims.iss
|
|
@@ -22140,7 +22702,7 @@ var runVerify = async (tokenOrFile) => {
|
|
|
22140
22702
|
printResult(true, `token verified (${claims.sub})`);
|
|
22141
22703
|
};
|
|
22142
22704
|
var createVerifyCommand = () => {
|
|
22143
|
-
return new
|
|
22705
|
+
return new Command9("verify").description("Verify an AIT using registry keys and CRL state").argument(
|
|
22144
22706
|
"<tokenOrFile>",
|
|
22145
22707
|
"Raw AIT token or file path containing the token"
|
|
22146
22708
|
).action(
|
|
@@ -22161,15 +22723,15 @@ var resolveCliVersion = () => {
|
|
|
22161
22723
|
};
|
|
22162
22724
|
var CLI_VERSION = resolveCliVersion();
|
|
22163
22725
|
var createProgram = () => {
|
|
22164
|
-
return new
|
|
22726
|
+
return new Command10("clawdentity").description("Clawdentity CLI - Agent identity management").version(CLI_VERSION).addCommand(createAdminCommand()).addCommand(createAgentCommand()).addCommand(createApiKeyCommand()).addCommand(createConnectorCommand()).addCommand(createConfigCommand()).addCommand(createInviteCommand()).addCommand(createOpenclawCommand()).addCommand(createPairCommand()).addCommand(createVerifyCommand());
|
|
22165
22727
|
};
|
|
22166
22728
|
|
|
22167
22729
|
// src/bin.ts
|
|
22168
|
-
var
|
|
22730
|
+
var logger11 = createLogger({ service: "cli", module: "bin" });
|
|
22169
22731
|
createProgram().parseAsync(process.argv).catch((error48) => {
|
|
22170
22732
|
process.exitCode = 1;
|
|
22171
22733
|
const message2 = error48 instanceof Error ? error48.message : String(error48);
|
|
22172
|
-
|
|
22734
|
+
logger11.error("cli.execution_failed", { errorMessage: message2 });
|
|
22173
22735
|
writeStderrLine(message2);
|
|
22174
22736
|
});
|
|
22175
22737
|
/*! Bundled license information:
|