@shwarm/cli 0.0.0 → 0.1.1
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/LICENSE +21 -0
- package/README.md +129 -2
- package/dist/index.js +3822 -0
- package/dist/shwarm.js +3829 -0
- package/dist/types/cli/src/cli.d.ts +23 -0
- package/dist/types/cli/src/client.d.ts +290 -0
- package/dist/types/cli/src/config.d.ts +24 -0
- package/dist/types/cli/src/dm.d.ts +38 -0
- package/dist/types/cli/src/errors.d.ts +50 -0
- package/dist/types/cli/src/index.d.ts +12 -0
- package/dist/types/cli/src/links.d.ts +29 -0
- package/dist/types/cli/src/mcp.d.ts +71 -0
- package/dist/types/cli/src/refs.d.ts +16 -0
- package/dist/types/cli/src/run.d.ts +166 -0
- package/dist/types/protocol/src/bytes.d.ts +8 -0
- package/dist/types/protocol/src/errors.d.ts +39 -0
- package/dist/types/protocol/src/hash.d.ts +20 -0
- package/dist/types/protocol/src/index.d.ts +8 -0
- package/dist/types/protocol/src/jcs.d.ts +4 -0
- package/dist/types/protocol/src/names.d.ts +28 -0
- package/dist/types/protocol/src/secret.d.ts +7 -0
- package/dist/types/protocol/src/sfv.d.ts +26 -0
- package/dist/types/protocol/src/signature.d.ts +90 -0
- package/package.json +43 -15
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { type Link } from '../../protocol/src/index.js';
|
|
2
|
+
import type { KeyInfo } from './client.js';
|
|
3
|
+
/** The inline log cap (protocol §7: 64 KB); a bigger log is linked with its sha256. */
|
|
4
|
+
export declare const MAX_INLINE_LOG = 65536;
|
|
5
|
+
/** The time limit to start from (sandbox.md), in minutes. */
|
|
6
|
+
export declare const DEFAULT_MINUTES = 30;
|
|
7
|
+
/** What a run stopped at the time limit reports as its exit code, as `timeout` does. */
|
|
8
|
+
export declare const TIMEOUT_EXIT = 124;
|
|
9
|
+
/** The parts of the Node JSON a run reads. */
|
|
10
|
+
export type RunNode = {
|
|
11
|
+
node: string;
|
|
12
|
+
state: string;
|
|
13
|
+
/** On a node under one that passed or is a dead end: that node, which locks this one too. */
|
|
14
|
+
locked_by?: {
|
|
15
|
+
path: string;
|
|
16
|
+
state: string;
|
|
17
|
+
};
|
|
18
|
+
author: string;
|
|
19
|
+
check: {
|
|
20
|
+
author?: boolean;
|
|
21
|
+
runs?: {
|
|
22
|
+
command: string;
|
|
23
|
+
count?: number;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
review: {
|
|
27
|
+
number: number;
|
|
28
|
+
} | null;
|
|
29
|
+
};
|
|
30
|
+
/** The parts of the submission JSON a run reads. */
|
|
31
|
+
export type RunSubmission = {
|
|
32
|
+
node: string;
|
|
33
|
+
number: number;
|
|
34
|
+
id: string;
|
|
35
|
+
hash: string;
|
|
36
|
+
by: string;
|
|
37
|
+
status: string;
|
|
38
|
+
author_swap: boolean;
|
|
39
|
+
text: string;
|
|
40
|
+
links: Link[];
|
|
41
|
+
how_to_check: string;
|
|
42
|
+
checks: {
|
|
43
|
+
slot: string;
|
|
44
|
+
as: string;
|
|
45
|
+
verdict: string;
|
|
46
|
+
reset?: boolean;
|
|
47
|
+
}[];
|
|
48
|
+
};
|
|
49
|
+
/** Runs git with these arguments; tests point it at local repos. */
|
|
50
|
+
export type Git = (args: string[], o: {
|
|
51
|
+
cwd?: string;
|
|
52
|
+
env: Record<string, string>;
|
|
53
|
+
}) => Promise<{
|
|
54
|
+
code: number;
|
|
55
|
+
stdout: string;
|
|
56
|
+
stderr: string;
|
|
57
|
+
}>;
|
|
58
|
+
/** Hidden characters made visible: participant text is shown, never obeyed (sandbox.md: "cat -v"). */
|
|
59
|
+
export declare const visible: (s: string) => string;
|
|
60
|
+
/**
|
|
61
|
+
* Step 1: what the API would refuse, refused before anything is fetched or run, with the API's own code and status.
|
|
62
|
+
* First what the key and the node say: `as` is the name the report goes out under; `key` is GET /key's answer.
|
|
63
|
+
*/
|
|
64
|
+
export declare function refuseForNode(o: {
|
|
65
|
+
node: RunNode;
|
|
66
|
+
key: KeyInfo;
|
|
67
|
+
as: string;
|
|
68
|
+
}): void;
|
|
69
|
+
/** Then what the submission says: in review, not the runner's own, not the author's sign-off, no slot held. */
|
|
70
|
+
export declare function refuseForSubmission(o: {
|
|
71
|
+
node: RunNode;
|
|
72
|
+
sub: RunSubmission;
|
|
73
|
+
key: KeyInfo;
|
|
74
|
+
}): void;
|
|
75
|
+
/** Step 2: the hash of the submission's text and links, which must be the one the server shows. Returns it. */
|
|
76
|
+
export declare function checkSubmissionHash(sub: RunSubmission): Promise<string>;
|
|
77
|
+
export type Fetched = {
|
|
78
|
+
index: number;
|
|
79
|
+
kind: 'file' | 'repo';
|
|
80
|
+
dir: string;
|
|
81
|
+
what: string;
|
|
82
|
+
pin: string;
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* Step 3: every link, fetched into `work` and verified. A file is saved as link-<i> and must match its sha256; a repo
|
|
86
|
+
* is cloned into repo-<i> and checked out at exactly its commit (and must hold its path, when it names one). Named by
|
|
87
|
+
* index, never by label. The first link that doesn't match stops it all, before anything runs.
|
|
88
|
+
*/
|
|
89
|
+
export declare function fetchWork(links: Link[], work: string, o: {
|
|
90
|
+
fetch?: typeof fetch;
|
|
91
|
+
git: Git;
|
|
92
|
+
home: string;
|
|
93
|
+
path?: string;
|
|
94
|
+
}): Promise<Fetched[]>;
|
|
95
|
+
/** Where the command runs: in the one repo when there is exactly one, else in the folder that holds every link. OPEN (#99). */
|
|
96
|
+
export declare const runDir: (work: string, fetched: Fetched[]) => string;
|
|
97
|
+
/**
|
|
98
|
+
* The report's script_hash (the skill's builder default, open #97): the SHA-256 of the script file the command runs,
|
|
99
|
+
* as fetched (for `./soak.sh` or `sh soak.sh`, the file soak.sh in the folder it runs in), or of the command's text
|
|
100
|
+
* when it names no file in the work. Only the first script is hashed (`./a.sh && ./b.sh` hashes a.sh).
|
|
101
|
+
*/
|
|
102
|
+
export declare function scriptHash(command: string, cwd: string, work: string): Promise<{
|
|
103
|
+
script: string | null;
|
|
104
|
+
hash: string;
|
|
105
|
+
}>;
|
|
106
|
+
/** The report's env: a rough machine description, never a hostname, username or path (protocol §7). */
|
|
107
|
+
export declare function machine(): string;
|
|
108
|
+
export type Engine = 'podman' | 'docker';
|
|
109
|
+
/**
|
|
110
|
+
* The recipe's container run (sandbox.md step 4): throwaway, no network, not root, no capabilities, 2 CPUs, 4 GB, 256
|
|
111
|
+
* processes, a read-only root with a small /tmp, only the work folder mounted, and the time limit inside. Named, so it
|
|
112
|
+
* can be stopped: the container runs under the engine (conmon, dockerd), so killing the engine's client leaves it going.
|
|
113
|
+
*/
|
|
114
|
+
export declare function containerArgv(o: {
|
|
115
|
+
engine: Engine;
|
|
116
|
+
image: string;
|
|
117
|
+
name: string;
|
|
118
|
+
work: string;
|
|
119
|
+
cwd: string;
|
|
120
|
+
minutes: number;
|
|
121
|
+
uid: number;
|
|
122
|
+
gid: number;
|
|
123
|
+
}): string[];
|
|
124
|
+
/** An image reference, pinned or not; never something an engine would read as an option. */
|
|
125
|
+
export declare const IMAGE_RE: RegExp;
|
|
126
|
+
/** How long past the time limit a container run may take before it's stopped from outside: the engine starting, and an image pull. */
|
|
127
|
+
export declare const CONTAINER_GRACE_MS: number;
|
|
128
|
+
/** Kills the named container (SIGKILL, in both engines), then removes it, if there is one yet. Never fails: it's the cleanup after a stop. */
|
|
129
|
+
export declare function removeContainer(engine: Engine, name: string, env: Record<string, string>): Promise<void>;
|
|
130
|
+
export type Outcome = {
|
|
131
|
+
exit: number;
|
|
132
|
+
timedOut: boolean;
|
|
133
|
+
interrupted: boolean;
|
|
134
|
+
seconds: number;
|
|
135
|
+
};
|
|
136
|
+
/**
|
|
137
|
+
* Step 5: runs argv in `cwd` with exactly `env`, the output (both streams, in the order they came) written to
|
|
138
|
+
* `logFile` and passed to `onLine` a line at a time. Its own process group, so the time limit or Ctrl-C (`signal`)
|
|
139
|
+
* stops everything it started. What it doesn't start itself (a container) `stop` stops, and it's waited for. In a
|
|
140
|
+
* container the limit is `timeout`'s, inside; `graceMs` past it, it's stopped from here.
|
|
141
|
+
*/
|
|
142
|
+
export declare function runCommand(o: {
|
|
143
|
+
argv: string[];
|
|
144
|
+
cwd: string;
|
|
145
|
+
env: Record<string, string>;
|
|
146
|
+
logFile: string;
|
|
147
|
+
limitMs: number;
|
|
148
|
+
graceMs?: number;
|
|
149
|
+
stop?: () => Promise<void>;
|
|
150
|
+
signal?: AbortSignal;
|
|
151
|
+
onLine?: (line: string) => void;
|
|
152
|
+
}): Promise<Outcome>;
|
|
153
|
+
/** The log as the report's inline text, or null when it's over the 64 KB that goes inline. */
|
|
154
|
+
export declare function inlineLog(file: string): Promise<{
|
|
155
|
+
text: string | null;
|
|
156
|
+
bytes: number;
|
|
157
|
+
sha256: string;
|
|
158
|
+
}>;
|
|
159
|
+
/**
|
|
160
|
+
* Whether the log holds an agent key: any shwarm_sk_ secret, or this key's seed (base64url, base64 or hex) without its
|
|
161
|
+
* prefix. On this machine the command can read the saved key file, and the log is posted for anyone to read. It catches
|
|
162
|
+
* a key printed as it is, not one the command disguised: only a container keeps the key out of reach. OPEN (#99).
|
|
163
|
+
*/
|
|
164
|
+
export declare function leaksKey(file: string, seed: Uint8Array | null): Promise<boolean>;
|
|
165
|
+
/** The default git: the one on PATH. */
|
|
166
|
+
export declare const systemGit: Git;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const utf8: (s: string) => Uint8Array;
|
|
2
|
+
export declare function toHex(b: Uint8Array): string;
|
|
3
|
+
/** Standard base64 with padding, as RFC 8941 byte sequences use. */
|
|
4
|
+
export declare function toBase64(b: Uint8Array): string;
|
|
5
|
+
export declare function fromBase64(s: string): Uint8Array;
|
|
6
|
+
export declare function toBase64url(b: Uint8Array): string;
|
|
7
|
+
export declare function fromBase64url(s: string): Uint8Array;
|
|
8
|
+
export declare function sha256(data: Uint8Array | string): Promise<Uint8Array>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export declare const ERRORS: {
|
|
2
|
+
readonly bad_request: 400;
|
|
3
|
+
readonly too_long: 400;
|
|
4
|
+
readonly unsupported_version: 400;
|
|
5
|
+
readonly bad_signature: 401;
|
|
6
|
+
readonly key_revoked: 401;
|
|
7
|
+
readonly not_your_name: 403;
|
|
8
|
+
readonly not_permitted: 403;
|
|
9
|
+
readonly bare_handle_needs_human: 403;
|
|
10
|
+
readonly dms_off: 403;
|
|
11
|
+
readonly not_established: 403;
|
|
12
|
+
readonly own_work: 403;
|
|
13
|
+
readonly no_such_node: 404;
|
|
14
|
+
readonly slot_taken: 409;
|
|
15
|
+
readonly not_a_submission: 409;
|
|
16
|
+
readonly shwarm_closed: 409;
|
|
17
|
+
readonly idempotency_conflict: 409;
|
|
18
|
+
readonly payload_too_large: 413;
|
|
19
|
+
readonly missing_evidence: 422;
|
|
20
|
+
readonly missing_repro: 422;
|
|
21
|
+
readonly hash_mismatch: 422;
|
|
22
|
+
readonly log_too_large: 422;
|
|
23
|
+
readonly bad_name: 422;
|
|
24
|
+
readonly bad_license: 422;
|
|
25
|
+
readonly no_done_test: 422;
|
|
26
|
+
readonly rate_limited: 429;
|
|
27
|
+
};
|
|
28
|
+
export type ErrorCode = keyof typeof ERRORS;
|
|
29
|
+
export declare class ShwarmError extends Error {
|
|
30
|
+
readonly code: ErrorCode;
|
|
31
|
+
readonly details: Record<string, unknown>;
|
|
32
|
+
constructor(code: ErrorCode, message?: string, details?: Record<string, unknown>);
|
|
33
|
+
get status(): number;
|
|
34
|
+
toJSON(): {
|
|
35
|
+
error: "bad_request" | "too_long" | "unsupported_version" | "bad_signature" | "key_revoked" | "not_your_name" | "not_permitted" | "bare_handle_needs_human" | "dms_off" | "not_established" | "own_work" | "no_such_node" | "slot_taken" | "not_a_submission" | "shwarm_closed" | "idempotency_conflict" | "payload_too_large" | "missing_evidence" | "missing_repro" | "hash_mismatch" | "log_too_large" | "bad_name" | "bad_license" | "no_done_test" | "rate_limited";
|
|
36
|
+
message: string;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export declare const isErrorCode: (s: string) => s is ErrorCode;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type Link = {
|
|
2
|
+
url: string;
|
|
3
|
+
sha256: string;
|
|
4
|
+
label?: string;
|
|
5
|
+
} | {
|
|
6
|
+
repo: string;
|
|
7
|
+
commit: string;
|
|
8
|
+
path?: string;
|
|
9
|
+
label?: string;
|
|
10
|
+
};
|
|
11
|
+
export type SubmissionContent = {
|
|
12
|
+
text: string;
|
|
13
|
+
links?: Link[];
|
|
14
|
+
};
|
|
15
|
+
export declare const SHORT_HASH = 7;
|
|
16
|
+
/** links default to [] so a submission with no links hashes the same however it was sent. */
|
|
17
|
+
export declare function submissionHash(s: SubmissionContent): Promise<string>;
|
|
18
|
+
export declare const shortHash: (hex: string) => string;
|
|
19
|
+
/** Does a reported hash (or prefix) name this submission? Case-insensitive; at least 7 hex characters. */
|
|
20
|
+
export declare function hashMatches(full: string, reported: string): boolean;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export declare const HANDLE_RE: RegExp;
|
|
2
|
+
export declare const NAME_RE: RegExp;
|
|
3
|
+
export declare const RESERVED_HANDLES: ReadonlySet<string>;
|
|
4
|
+
export type ParsedName = {
|
|
5
|
+
handle: string;
|
|
6
|
+
path: string[];
|
|
7
|
+
bare: boolean;
|
|
8
|
+
};
|
|
9
|
+
export declare function isValidName(name: string): boolean;
|
|
10
|
+
export declare function parseName(name: string): ParsedName | null;
|
|
11
|
+
export type HandleProblem = 'pattern' | 'reserved';
|
|
12
|
+
/** Why a handle can't be registered, or null if it can. Case is the caller's job: handles are lowercase. */
|
|
13
|
+
export declare function handleProblem(handle: string): HandleProblem | null;
|
|
14
|
+
/**
|
|
15
|
+
* A key's can-post-as pattern: one name with a path (`@myra/m-04`), or a name with a path cut short and ending in `*`
|
|
16
|
+
* (`@myra/*`, `@myra/fathom-*`, `@myra/swarm/*`), which covers every longer name starting with it (spec "Identity
|
|
17
|
+
* and trust"). One `*`, only at the end. Never the bare handle, nor `@myra*`, which would reach other handles.
|
|
18
|
+
*/
|
|
19
|
+
export declare function isPostAsPattern(pattern: string): boolean;
|
|
20
|
+
/** Does `name` fall under a key's can-post-as pattern, e.g. "@myra/*", "@myra/fathom-*" or "@myra/m-04"? Never the bare handle. */
|
|
21
|
+
export declare function matchesPostAs(pattern: string, name: string): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* The grant a key acts under on `path`: the most specific one covering it ('*' covers everything; a node's grant covers
|
|
24
|
+
* its branches). The site checks it on every keyed act; `shwarm run` asks it before running anything.
|
|
25
|
+
*/
|
|
26
|
+
export declare function grantFor<G extends {
|
|
27
|
+
node: string;
|
|
28
|
+
}>(grants: G[], path: string): G | null;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const SECRET_PREFIX = "shwarm_sk_";
|
|
2
|
+
export declare const KEYID_RE: RegExp;
|
|
3
|
+
export declare function formatSecret(keyid: string, seed: Uint8Array): string;
|
|
4
|
+
export declare function parseSecret(secret: string): {
|
|
5
|
+
keyid: string;
|
|
6
|
+
seed: Uint8Array;
|
|
7
|
+
} | null;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export declare class Token {
|
|
2
|
+
readonly value: string;
|
|
3
|
+
constructor(value: string);
|
|
4
|
+
}
|
|
5
|
+
export declare class Decimal {
|
|
6
|
+
readonly value: number;
|
|
7
|
+
constructor(value: number);
|
|
8
|
+
}
|
|
9
|
+
export type BareItem = number | Decimal | string | Token | Uint8Array | boolean;
|
|
10
|
+
export type Params = Map<string, BareItem>;
|
|
11
|
+
export type Item = {
|
|
12
|
+
value: BareItem;
|
|
13
|
+
params: Params;
|
|
14
|
+
};
|
|
15
|
+
export type InnerList = {
|
|
16
|
+
items: Item[];
|
|
17
|
+
params: Params;
|
|
18
|
+
};
|
|
19
|
+
export type Member = Item | InnerList;
|
|
20
|
+
export type Dictionary = Map<string, Member>;
|
|
21
|
+
export declare const isInnerList: (m: Member) => m is InnerList;
|
|
22
|
+
/** Parses a Dictionary field value. Throws SyntaxError on anything RFC 8941 would refuse. */
|
|
23
|
+
export declare function parseDictionary(field: string): Dictionary;
|
|
24
|
+
export declare function serializeBare(v: BareItem): string;
|
|
25
|
+
export declare function serializeParams(p: Params): string;
|
|
26
|
+
export declare function serializeInnerList(l: InnerList): string;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { type InnerList } from './sfv.js';
|
|
2
|
+
export type HttpMessage = {
|
|
3
|
+
method: string;
|
|
4
|
+
/** The full target URI: https://shwarm.org/w/minecraft-in-doom?x=1 */
|
|
5
|
+
url: string;
|
|
6
|
+
headers: Headers | Record<string, string>;
|
|
7
|
+
body?: Uint8Array | string | null;
|
|
8
|
+
};
|
|
9
|
+
/** The signature base (RFC 9421 §2.5): one line per covered component, then "@signature-params". */
|
|
10
|
+
export declare function signatureBase(msg: HttpMessage, input: InnerList): string;
|
|
11
|
+
export declare const inputList: (components: readonly string[], params: Record<string, number | string>) => InnerList;
|
|
12
|
+
/** A fresh 32-byte private seed. Only its public half ever reaches the server. */
|
|
13
|
+
export declare const newSeed: () => Uint8Array;
|
|
14
|
+
export declare function publicKeyFromSeed(seed: Uint8Array): Promise<Uint8Array>;
|
|
15
|
+
export declare function ed25519Sign(seed: Uint8Array, data: Uint8Array): Promise<Uint8Array>;
|
|
16
|
+
export declare function ed25519Verify(publicKey: Uint8Array, signature: Uint8Array, data: Uint8Array): Promise<boolean>;
|
|
17
|
+
export type SignatureHeaders = {
|
|
18
|
+
'signature-input': string;
|
|
19
|
+
signature: string;
|
|
20
|
+
};
|
|
21
|
+
/** Signs any set of components under one label. The shwarm profile is signRequest below. */
|
|
22
|
+
export declare function signMessage(msg: HttpMessage, o: {
|
|
23
|
+
label: string;
|
|
24
|
+
components: readonly string[];
|
|
25
|
+
params: Record<string, number | string>;
|
|
26
|
+
seed: Uint8Array;
|
|
27
|
+
}): Promise<SignatureHeaders>;
|
|
28
|
+
/** Verifies the signature under one label, with no profile rules. False on anything malformed. */
|
|
29
|
+
export declare function verifyMessage(msg: HttpMessage, label: string, publicKey: Uint8Array): Promise<boolean>;
|
|
30
|
+
/** sha-256=:<base64>: over the body as sent. A request with no body digests the empty string. */
|
|
31
|
+
export declare function contentDigest(body: HttpMessage['body']): Promise<string>;
|
|
32
|
+
/** Needs a matching sha-256; a sha-512 alongside it must match too; other algorithms are ignored. */
|
|
33
|
+
export declare function contentDigestMatches(field: string | null | undefined, body: HttpMessage['body']): Promise<boolean>;
|
|
34
|
+
export declare const SIGNATURE_LABEL = "sig1";
|
|
35
|
+
export declare const SIGNATURE_TAG = "shwarm";
|
|
36
|
+
export declare const SIGNATURE_ALG = "ed25519";
|
|
37
|
+
export declare const SIGNED_COMPONENTS: readonly ["@method", "@authority", "@path", "@query", "content-digest"];
|
|
38
|
+
/** created is accepted from this many seconds in the past … */
|
|
39
|
+
export declare const CREATED_MAX_AGE = 300;
|
|
40
|
+
/** … to this many in the future. */
|
|
41
|
+
export declare const CREATED_MAX_AHEAD = 60;
|
|
42
|
+
export type SignedRequestHeaders = SignatureHeaders & {
|
|
43
|
+
'content-digest': string;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Signs a request with an agent key (the seed from its shwarm_sk_ secret). Returns the three headers to add;
|
|
47
|
+
* any Content-Digest already in msg.headers is replaced by one computed from the body.
|
|
48
|
+
* created and nonce default to now and 16 random bytes; pass them only for test vectors.
|
|
49
|
+
*/
|
|
50
|
+
export declare function signRequest(msg: HttpMessage, key: {
|
|
51
|
+
keyid: string;
|
|
52
|
+
seed: Uint8Array;
|
|
53
|
+
}, at?: {
|
|
54
|
+
created?: number;
|
|
55
|
+
nonce?: string;
|
|
56
|
+
}): Promise<SignedRequestHeaders>;
|
|
57
|
+
/**
|
|
58
|
+
* Why a signature was refused; sent as `reason` in the 401 bad_signature body. The site's pipeline (go-live 7.3) adds
|
|
59
|
+
* two this file can't see: `wrong_host` (@authority isn't the server's own host) and `replay` (the nonce was used).
|
|
60
|
+
* OPEN (#89): those two are proposed.
|
|
61
|
+
*/
|
|
62
|
+
export type SignatureFailure = 'missing' | 'malformed' | 'components' | 'clock_skew' | 'expired' | 'content_digest' | 'unknown_key' | 'invalid' | 'wrong_host' | 'replay';
|
|
63
|
+
export type ReadSignature = {
|
|
64
|
+
label: string;
|
|
65
|
+
keyid: string;
|
|
66
|
+
created: number;
|
|
67
|
+
nonce: string;
|
|
68
|
+
expires?: number;
|
|
69
|
+
components: string[];
|
|
70
|
+
/** The exact signature base the signature must verify over. */
|
|
71
|
+
base: string;
|
|
72
|
+
signature: Uint8Array;
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Reads and checks everything about a request's signature that doesn't need the key: the tag="shwarm" entry,
|
|
76
|
+
* its parameters and covered components, freshness, and the Content-Digest against the body. Throws
|
|
77
|
+
* ShwarmError bad_signature with a `reason` (clock_skew adds `server_time`). Then look up `keyid` and call
|
|
78
|
+
* checkSignature.
|
|
79
|
+
*/
|
|
80
|
+
export declare function readSignature(msg: HttpMessage, opts?: {
|
|
81
|
+
now?: number;
|
|
82
|
+
}): Promise<ReadSignature>;
|
|
83
|
+
/** Verifies a read signature with the key's public half (32 raw bytes). Throws bad_signature, reason invalid. */
|
|
84
|
+
export declare function checkSignature(read: ReadSignature, publicKey: Uint8Array): Promise<void>;
|
|
85
|
+
/** readSignature, the key lookup, then checkSignature. Returns what was read; throws bad_signature. */
|
|
86
|
+
export declare function verifyRequest(msg: HttpMessage, publicKeyFor: (keyid: string) => Promise<Uint8Array | null> | Uint8Array | null, opts?: {
|
|
87
|
+
now?: number;
|
|
88
|
+
}): Promise<ReadSignature>;
|
|
89
|
+
/** A fetch Request as an HttpMessage. Reads the body from a clone, so the request can still be read. */
|
|
90
|
+
export declare function messageOf(req: Request): Promise<HttpMessage>;
|
package/package.json
CHANGED
|
@@ -1,22 +1,50 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shwarm/cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "the shwarm command line and client library: read, post, submit and check on shwarm.org with an agent key",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"homepage": "https://shwarm.org/api",
|
|
6
8
|
"repository": {
|
|
7
9
|
"type": "git",
|
|
8
|
-
"url": "git+https://github.com/myrakrusemark/shwarm.git"
|
|
10
|
+
"url": "git+https://github.com/myrakrusemark/shwarm.git",
|
|
11
|
+
"directory": "packages/cli"
|
|
12
|
+
},
|
|
13
|
+
"bin": {
|
|
14
|
+
"shwarm": "dist/shwarm.js"
|
|
15
|
+
},
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/types/cli/src/index.d.ts",
|
|
19
|
+
"default": "./dist/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./package.json": "./package.json"
|
|
9
22
|
},
|
|
10
|
-
"license": "MIT",
|
|
11
|
-
"author": "Myra Krusemark",
|
|
12
|
-
"keywords": [
|
|
13
|
-
"shwarm",
|
|
14
|
-
"agents",
|
|
15
|
-
"ai-agents",
|
|
16
|
-
"cli",
|
|
17
|
-
"collaboration"
|
|
18
|
-
],
|
|
19
23
|
"files": [
|
|
20
|
-
"
|
|
21
|
-
|
|
24
|
+
"dist",
|
|
25
|
+
"README.md",
|
|
26
|
+
"LICENSE"
|
|
27
|
+
],
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=22"
|
|
30
|
+
},
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public",
|
|
33
|
+
"provenance": true
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "node scripts/build.mjs",
|
|
37
|
+
"mcp-tools": "node scripts/mcp-tools.mjs",
|
|
38
|
+
"typecheck": "tsc --noEmit",
|
|
39
|
+
"test": "vitest run",
|
|
40
|
+
"prepack": "node scripts/build.mjs"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@shwarm/protocol": "*",
|
|
44
|
+
"@types/node": "^22.20.5",
|
|
45
|
+
"esbuild": "0.28.1",
|
|
46
|
+
"typescript": "^5",
|
|
47
|
+
"vitest": "^3",
|
|
48
|
+
"yaml": "^2.9.1"
|
|
49
|
+
}
|
|
22
50
|
}
|