@pidgeon-ai/cli 1.38.0 → 1.40.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 +15 -0
- package/dist/{chunk-ZDJG3Z7B.js → chunk-D2BUTVIS.js} +129 -3
- package/dist/{chunk-ZDJG3Z7B.js.map → chunk-D2BUTVIS.js.map} +1 -1
- package/dist/{chunk-UBA2RP6N.js → chunk-OETRER3H.js} +15 -1
- package/dist/{chunk-UBA2RP6N.js.map → chunk-OETRER3H.js.map} +1 -1
- package/dist/cli.js +2 -2
- package/dist/commands.gen.js +1 -1
- package/dist/index.js +2 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -40,6 +40,21 @@ one request open until mail arrives, and prints it with `extracted.codes` and
|
|
|
40
40
|
describes one command, with its endpoint and its flags. Documentation:
|
|
41
41
|
[pidgeon.ai/docs/cli](https://pidgeon.ai/docs/cli).
|
|
42
42
|
|
|
43
|
+
## Check who sent something
|
|
44
|
+
|
|
45
|
+
`pidgeon id` needs no key. It checks a message, or signs a request as one of
|
|
46
|
+
your agents:
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
pidgeon id verify message.eml
|
|
50
|
+
pidgeon id curl -I https://shop.example --as sunny
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`verify` asks the public verifier and says whether the signature holds, which
|
|
54
|
+
agent sent it, who it acts for and whether the body changed. `curl` signs the
|
|
55
|
+
request the way Web Bot Auth asks and prints what it sent. `pidgeon id --help`
|
|
56
|
+
lists the rest, including `init` for your own domain.
|
|
57
|
+
|
|
43
58
|
## Flags
|
|
44
59
|
|
|
45
60
|
- Repeat a list flag: `--to one@example.com --to two@example.com`.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
COMMANDS,
|
|
4
4
|
__export
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-OETRER3H.js";
|
|
6
6
|
|
|
7
7
|
// ../sdk/dist/index.js
|
|
8
8
|
var dist_exports = {};
|
|
@@ -2027,7 +2027,7 @@ function sleep(ms, signal) {
|
|
|
2027
2027
|
});
|
|
2028
2028
|
}
|
|
2029
2029
|
|
|
2030
|
-
// ../id/dist/chunk-
|
|
2030
|
+
// ../id/dist/chunk-2NWPDKWJ.js
|
|
2031
2031
|
import { createHash } from "crypto";
|
|
2032
2032
|
import { chmod, mkdir, readFile, readdir, writeFile } from "fs/promises";
|
|
2033
2033
|
import { homedir } from "os";
|
|
@@ -2496,6 +2496,34 @@ async function signRequest(identity, input) {
|
|
|
2496
2496
|
signature: `${label}=:${Buffer.from(signature).toString("base64")}:`
|
|
2497
2497
|
};
|
|
2498
2498
|
}
|
|
2499
|
+
function signedFetch(identity, options) {
|
|
2500
|
+
const base = options.fetch ?? globalThis.fetch;
|
|
2501
|
+
return async (resource, init) => {
|
|
2502
|
+
const request = new Request(resource, init);
|
|
2503
|
+
const body = request.body ? new Uint8Array(await request.arrayBuffer()) : null;
|
|
2504
|
+
const headers = {};
|
|
2505
|
+
request.headers.forEach((value, name) => {
|
|
2506
|
+
headers[name] = value;
|
|
2507
|
+
});
|
|
2508
|
+
const signature = await signRequest(identity, {
|
|
2509
|
+
method: request.method,
|
|
2510
|
+
url: request.url,
|
|
2511
|
+
headers,
|
|
2512
|
+
body,
|
|
2513
|
+
agent: options.agent,
|
|
2514
|
+
grants: options.grants
|
|
2515
|
+
});
|
|
2516
|
+
const signedHeaders = new Headers(request.headers);
|
|
2517
|
+
for (const [name, value] of Object.entries(signature)) signedHeaders.set(name, value);
|
|
2518
|
+
return base(request.url, {
|
|
2519
|
+
method: request.method,
|
|
2520
|
+
headers: signedHeaders,
|
|
2521
|
+
body: body && body.length > 0 ? body : void 0,
|
|
2522
|
+
redirect: request.redirect,
|
|
2523
|
+
signal: request.signal
|
|
2524
|
+
});
|
|
2525
|
+
};
|
|
2526
|
+
}
|
|
2499
2527
|
var ISSUANCE_TAG = "pidgeon-id";
|
|
2500
2528
|
var DEFAULT_ID_URL = "https://pidgeon.ai/api/id";
|
|
2501
2529
|
var IdApiError = class extends Error {
|
|
@@ -2735,6 +2763,8 @@ var HELP = `pidgeon id \u2014 a signed identity for your agents, on your own dom
|
|
|
2735
2763
|
revoke-grant <id> Take a grant back now
|
|
2736
2764
|
revoke-sign-ins <name> End every token <name> signed in with; its card and grants stand
|
|
2737
2765
|
sign-in <site> Sign in to a site as an agent --as <name> --grant <jws>
|
|
2766
|
+
verify [<file>] Check a message's signatures (a file, or standard input). No key
|
|
2767
|
+
curl [-I] <url> Send a request signed as an agent --as <name>
|
|
2738
2768
|
|
|
2739
2769
|
--domain <domain> Which domain, when this machine holds more than one
|
|
2740
2770
|
--json Print the API's answer as JSON
|
|
@@ -2870,6 +2900,69 @@ Or add them by hand:` : "Add these records at your DNS provider:",
|
|
|
2870
2900
|
'It renews itself. Sign with it: import { loadIdentity, signEmail, signedFetch } from "@pidgeon-ai/id".'
|
|
2871
2901
|
].join("\n");
|
|
2872
2902
|
}
|
|
2903
|
+
case "verify": {
|
|
2904
|
+
const file = rest[0];
|
|
2905
|
+
const message = file ? await (await import("fs/promises")).readFile(file, "utf8") : await (options.stdin ?? readStdin)();
|
|
2906
|
+
if (!message.trim()) {
|
|
2907
|
+
throw new UsageError("Give it a message: `pidgeon id verify < message.eml`.");
|
|
2908
|
+
}
|
|
2909
|
+
const response = await (options.fetch ?? fetch)(
|
|
2910
|
+
`${new URL(baseUrl(env)).origin}/api/v1/verify`,
|
|
2911
|
+
{
|
|
2912
|
+
method: "POST",
|
|
2913
|
+
headers: { "content-type": "application/json" },
|
|
2914
|
+
body: JSON.stringify({ message })
|
|
2915
|
+
}
|
|
2916
|
+
);
|
|
2917
|
+
const answer = await response.json();
|
|
2918
|
+
if (!response.ok || !answer.data) {
|
|
2919
|
+
throw new UsageError(
|
|
2920
|
+
answer.error?.message ?? `The verifier answered ${response.status}.`
|
|
2921
|
+
);
|
|
2922
|
+
}
|
|
2923
|
+
if (json2) return JSON.stringify(answer.data, null, 2);
|
|
2924
|
+
return describeVerification(answer.data);
|
|
2925
|
+
}
|
|
2926
|
+
case "curl": {
|
|
2927
|
+
const head = rest[0] === "-I";
|
|
2928
|
+
const target = (head ? rest[1] : rest[0])?.trim();
|
|
2929
|
+
if (!target) throw new UsageError("Name a URL: `pidgeon id curl -I shop.example`.");
|
|
2930
|
+
const url = /^https?:\/\//.test(target) ? target : `https://${target}`;
|
|
2931
|
+
const identity = await which();
|
|
2932
|
+
const named = flags.get("as");
|
|
2933
|
+
let agent;
|
|
2934
|
+
if (typeof named === "string") {
|
|
2935
|
+
agent = named.includes("@") ? named : `${named}@${identity.domain}`;
|
|
2936
|
+
} else {
|
|
2937
|
+
const agents = await client2(identity).list();
|
|
2938
|
+
if (agents.length !== 1) {
|
|
2939
|
+
throw new UsageError("Say which agent signs, with --as <name>.");
|
|
2940
|
+
}
|
|
2941
|
+
agent = agents[0].address;
|
|
2942
|
+
}
|
|
2943
|
+
const sent = {};
|
|
2944
|
+
const signed = signedFetch(identity, {
|
|
2945
|
+
agent,
|
|
2946
|
+
fetch: async (resource, init) => {
|
|
2947
|
+
new Headers(init?.headers).forEach((value, name) => {
|
|
2948
|
+
if (/^(signature|signature-input|signature-agent|from|content-digest)$/.test(name)) {
|
|
2949
|
+
sent[name] = value;
|
|
2950
|
+
}
|
|
2951
|
+
});
|
|
2952
|
+
return (options.fetch ?? fetch)(resource, init);
|
|
2953
|
+
}
|
|
2954
|
+
});
|
|
2955
|
+
const response = await signed(url, { method: head ? "HEAD" : "GET" });
|
|
2956
|
+
const lines = Object.entries(sent).map(
|
|
2957
|
+
([name, value]) => `> ${capitalise(name)}: ${value}`
|
|
2958
|
+
);
|
|
2959
|
+
lines.push(`< ${response.status} ${response.statusText}`.trimEnd());
|
|
2960
|
+
if (head) {
|
|
2961
|
+
response.headers.forEach((value, name) => lines.push(`< ${capitalise(name)}: ${value}`));
|
|
2962
|
+
return lines.join("\n");
|
|
2963
|
+
}
|
|
2964
|
+
return [...lines, "", await response.text()].join("\n");
|
|
2965
|
+
}
|
|
2873
2966
|
case "list": {
|
|
2874
2967
|
const agents = await client2(await which()).list();
|
|
2875
2968
|
if (json2) return JSON.stringify(agents, null, 2);
|
|
@@ -2985,6 +3078,39 @@ ${HELP}`);
|
|
|
2985
3078
|
throw error;
|
|
2986
3079
|
}
|
|
2987
3080
|
}
|
|
3081
|
+
function describeVerification(v) {
|
|
3082
|
+
const signer = v.signatures.envelope.domain ?? v.signatures.declaration.domain;
|
|
3083
|
+
const signed = v.signatures.envelope.result === "pass" || v.signatures.declaration.result === "pass";
|
|
3084
|
+
const broken = v.signatures.envelope.result === "fail" || v.signatures.declaration.result === "fail";
|
|
3085
|
+
const row = (ok, label, value) => `${ok === null ? "\xB7" : ok ? "\u2713" : "\u2717"} ${label.padEnd(10)} ${value}`;
|
|
3086
|
+
return [
|
|
3087
|
+
row(
|
|
3088
|
+
signed && !broken,
|
|
3089
|
+
"signature",
|
|
3090
|
+
signed && !broken ? `valid, ${signer}` : broken ? `broken${signer ? `, ${signer}` : ""}` : "none"
|
|
3091
|
+
),
|
|
3092
|
+
row(v.is_agent, "agent", v.sender?.address ?? "not declared"),
|
|
3093
|
+
row(v.sender?.on_behalf_of ? true : null, "acts for", v.sender?.on_behalf_of ?? "not said"),
|
|
3094
|
+
row(
|
|
3095
|
+
v.body === "not_checked" ? null : v.body === "intact",
|
|
3096
|
+
"body",
|
|
3097
|
+
v.body === "intact" ? "as it was sent" : v.body === "changed" ? "changed since" : "not checked"
|
|
3098
|
+
),
|
|
3099
|
+
"",
|
|
3100
|
+
v.advice
|
|
3101
|
+
].join("\n");
|
|
3102
|
+
}
|
|
3103
|
+
function capitalise(name) {
|
|
3104
|
+
return name.replace(
|
|
3105
|
+
/(^|-)([a-z])/g,
|
|
3106
|
+
(_, dash, letter) => dash + letter.toUpperCase()
|
|
3107
|
+
);
|
|
3108
|
+
}
|
|
3109
|
+
async function readStdin() {
|
|
3110
|
+
const chunks = [];
|
|
3111
|
+
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
3112
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
3113
|
+
}
|
|
2988
3114
|
|
|
2989
3115
|
// src/index.ts
|
|
2990
3116
|
var DEFAULT_BASE_URL = "https://pidgeon.ai/api/v1";
|
|
@@ -3178,4 +3304,4 @@ export {
|
|
|
3178
3304
|
find,
|
|
3179
3305
|
run
|
|
3180
3306
|
};
|
|
3181
|
-
//# sourceMappingURL=chunk-
|
|
3307
|
+
//# sourceMappingURL=chunk-D2BUTVIS.js.map
|