@coldtea/pr-lens-cli 0.7.0 → 0.8.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 +31 -3
- package/dist/account.d.ts +76 -0
- package/dist/account.d.ts.map +1 -0
- package/dist/account.js +211 -0
- package/dist/account.js.map +1 -0
- package/dist/auth.d.ts +40 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +121 -0
- package/dist/auth.js.map +1 -0
- package/dist/canvas/api.d.ts +35 -1
- package/dist/canvas/api.d.ts.map +1 -1
- package/dist/canvas/api.js +57 -1
- package/dist/canvas/api.js.map +1 -1
- package/dist/canvas/claim.d.ts +3 -0
- package/dist/canvas/claim.d.ts.map +1 -0
- package/dist/canvas/claim.js +84 -0
- package/dist/canvas/claim.js.map +1 -0
- package/dist/canvas/delete.js +3 -3
- package/dist/canvas/delete.js.map +1 -1
- package/dist/canvas/registry.d.ts +8 -2
- package/dist/canvas/registry.d.ts.map +1 -1
- package/dist/canvas/registry.js +15 -2
- package/dist/canvas/registry.js.map +1 -1
- package/dist/canvas/write.d.ts +11 -2
- package/dist/canvas/write.d.ts.map +1 -1
- package/dist/canvas/write.js +27 -8
- package/dist/canvas/write.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +8 -2
- package/dist/cli.js.map +1 -1
- package/dist/commands/auth.d.ts +9 -0
- package/dist/commands/auth.d.ts.map +1 -0
- package/dist/commands/auth.js +369 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/canvas.d.ts.map +1 -1
- package/dist/commands/canvas.js +162 -21
- package/dist/commands/canvas.js.map +1 -1
- package/dist/commands/render.d.ts +1 -1
- package/dist/commands/render.d.ts.map +1 -1
- package/dist/commands/render.js +6 -3
- package/dist/commands/render.js.map +1 -1
- package/dist/config-home.d.ts +4 -0
- package/dist/config-home.d.ts.map +1 -0
- package/dist/config-home.js +38 -0
- package/dist/config-home.js.map +1 -0
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/install.d.ts +5 -0
- package/dist/install.d.ts.map +1 -0
- package/dist/install.js +83 -0
- package/dist/install.js.map +1 -0
- package/dist/skill-content.generated.d.ts +1 -1
- package/dist/skill-content.generated.d.ts.map +1 -1
- package/dist/skill-content.generated.js +1 -1
- package/dist/skill-content.generated.js.map +1 -1
- package/dist/slug.d.ts +7 -0
- package/dist/slug.d.ts.map +1 -0
- package/dist/slug.js +23 -0
- package/dist/slug.js.map +1 -0
- package/dist/terminal.d.ts +5 -0
- package/dist/terminal.d.ts.map +1 -1
- package/dist/terminal.js +7 -0
- package/dist/terminal.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/workspace.d.ts +4 -0
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +26 -4
- package/dist/workspace.js.map +1 -1
- package/package.json +1 -1
- package/src/account.ts +302 -0
- package/src/auth.ts +183 -0
- package/src/canvas/api.ts +114 -2
- package/src/canvas/claim.ts +127 -0
- package/src/canvas/delete.ts +3 -3
- package/src/canvas/registry.ts +24 -2
- package/src/canvas/write.ts +37 -6
- package/src/cli.ts +8 -2
- package/src/commands/auth.ts +560 -0
- package/src/commands/canvas.ts +238 -18
- package/src/commands/render.ts +7 -3
- package/src/config-home.ts +45 -0
- package/src/errors.ts +5 -1
- package/src/install.ts +107 -0
- package/src/skill-content.generated.ts +1 -1
- package/src/slug.ts +26 -0
- package/src/terminal.ts +14 -0
- package/src/version.ts +1 -1
- package/src/workspace.ts +31 -4
package/README.md
CHANGED
|
@@ -80,7 +80,7 @@ The renderer also hands back an atlas of where every lane, node, edge and flow s
|
|
|
80
80
|
### `comment`
|
|
81
81
|
|
|
82
82
|
```bash
|
|
83
|
-
pr-lens comment --graph .pr-lens
|
|
83
|
+
pr-lens comment --graph .pr-lens/<drawing>/drawn.graph.json --manifest .pr-lens/<drawing>/manifest.json \
|
|
84
84
|
--asset-base-url https://raw.githubusercontent.com/owner/repo/pr-lens/42
|
|
85
85
|
```
|
|
86
86
|
|
|
@@ -112,7 +112,9 @@ pr-lens canvas push
|
|
|
112
112
|
|
|
113
113
|
Puts the document on prlens.dev as a canvas: a page anyone you share it with can read, and an SVG a README can embed.
|
|
114
114
|
|
|
115
|
-
`push` sends the JSON document, never an SVG; the app draws it.
|
|
115
|
+
`push` sends the JSON document, never an SVG; the app draws it. Give it the `drawn.graph.json` a render wrote, so the page shows what the diagrams show. With one drawing in the checkout a bare `push` finds it; with several it lists them and asks which.
|
|
116
|
+
|
|
117
|
+
The first push of a document mints a canvas, and every push after that updates the same one. It is matched by the path the document came from, or by the document's title when no recorded path points at a canvas. The title match is what lets `pull`, `render`, `push` land back on the canvas you pulled: the pull writes the source document, the render writes the drawing somewhere else, and only one of them can be the recorded path. So a corrected diagram becomes a new revision of its canvas instead of a near copy. Because `render` gives each drawing a directory named after its title, a second diagram gets its own path and its own canvas without you asking for one. `--canvas <id|name>` picks a canvas for a single push and `--name` says what to call a new one; the document's title is the default. It prints the view link, the embed, and the way to take it down again:
|
|
116
118
|
|
|
117
119
|
```
|
|
118
120
|
✓ https://prlens.dev/c/{id} — rev 1 · 4 diagrams
|
|
@@ -125,6 +127,8 @@ The view link is the one to share. The edit link is the same page with the write
|
|
|
125
127
|
|
|
126
128
|
A canvas plays the document's walkthrough when it carries one. The app checks that every step resolves against the diagrams it drew, and a push it refuses arrives here as `CANVAS_REJECTED` carrying the app's own words rather than a generic failure.
|
|
127
129
|
|
|
130
|
+
Changing a canvas takes its write token or a sign-in as its owner. The CLI sends the write token when this checkout has one, since it works signed in or not. A second laptop, a fresh CI runner, or a checkout that never pulled the edit link sends the account token instead. `canvas claim` accepts only the write token, because claiming is how a canvas gets an owner in the first place.
|
|
131
|
+
|
|
128
132
|
`pull` fetches the document back, by the view link or the bare id, into `.pr-lens/graph.json` unless `-o` says otherwise, and records the revision. Pull the edit link, the one ending in `#w=…`, and its token is recorded too: that is how a fresh checkout, or one that lost `.pr-lens/canvas.json`, gets the canvas back. A push carries the revision it last saw, and one that has been overtaken is refused rather than applied: pull, then push again.
|
|
129
133
|
|
|
130
134
|
`rotate` mints a new write token and retires the old one. Use it when an edit link has leaked. The CLI saves the new token before it sends the request, so if the connection drops, the next command finishes the rotation instead of losing the token. The token lives in `.pr-lens/canvas.json`, keyed by canvas id, and git ignores it. Lose that file and the canvas is still readable by everyone; pushing to it again needs the token, which the edit link still carries.
|
|
@@ -139,6 +143,30 @@ The CLI refuses to write the registry where git could commit it. If a checkout t
|
|
|
139
143
|
|
|
140
144
|
`--api` points at another PR Lens app, or set `PR_LENS_API_URL`. The protocol between the CLI and the app is five routes and one error envelope, written up in [the canvas API contract](https://github.com/coldteadotai/pr-lens/blob/main/docs/canvas-api.md) so a private server can answer it and documents stay on your network.
|
|
141
145
|
|
|
146
|
+
### `auth`
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
pr-lens auth login
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Signs this machine in, so the canvases it pushes belong to your account. Signed out, they are unlisted pages that only a link reaches. `push`, `pull` and `render` all work signed out, and always will.
|
|
153
|
+
|
|
154
|
+
It prints a code and opens the browser. The code travels in the link, so you never type it; check that it matches the one on the page:
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
Code WDJB-MJHT · opening https://prlens.dev/device?code=WDJB-MJHT
|
|
158
|
+
Check the page shows the same code, then approve. The code lasts 15 minutes.
|
|
159
|
+
Waiting for it…
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Approving links this machine to your account. Every canvas the machine has already pushed becomes yours, and so does every one it pushes after that. `--no-browser` prints the link instead of opening it, for a machine you reach over SSH.
|
|
163
|
+
|
|
164
|
+
`status` says which app this machine is signed in to and asks the app whether the sign-in still works. `--json` gives the same answer for scripts. Neither prints the token. When the app can't be reached, `status` says it couldn't check; it does not call the sign-in bad.
|
|
165
|
+
|
|
166
|
+
`logout` forgets the sign-in kept on this machine. The machine stays linked, so the canvases it pushed stay yours. To remove the machine from your account, use the app's settings; signing in again from a removed machine links it back.
|
|
167
|
+
|
|
168
|
+
The sign-in lives in `~/.config/pr-lens/auth/`, one file per app and readable by you alone, so one machine can be signed in to prlens.dev and to a private store at the same time. `PR_LENS_TOKEN` overrides it, which is how CI signs in without a browser. [The Action](https://github.com/coldteadotai/pr-lens/tree/main/packages/action) takes it as its `token` input.
|
|
169
|
+
|
|
142
170
|
## Corrections
|
|
143
171
|
|
|
144
172
|
A repository's `.github/pr-lens.yml` is picked up automatically by `render` and applied at draw time: renames, exclusions, lane pins, groupings. It is an overlay: inference never writes back into it, so a correction keeps holding as the code moves and the model renames things between runs, and the document on disk stays the record of what was inferred.
|
|
@@ -159,7 +187,7 @@ A lane pin may name a lane the document never declared; the band is created and
|
|
|
159
187
|
|
|
160
188
|
## Failures
|
|
161
189
|
|
|
162
|
-
Every failure carries a code, so a script can branch on it: `USAGE`, `UNREADABLE_FILE`, `UNKNOWN_DOCUMENT`, `INVALID_DOCUMENT`, `GIT_FAILED`, `EMPTY_DIFF`, `REPOSITORY_UNKNOWN`, `MISSING_API_KEY`, `PROVIDER_FAILED`, `MODEL_OUTPUT_INVALID`, `RENDER_FAILED`, and for `canvas`: `CANVAS_UNREGISTERED`, `CANVAS_UNKNOWN`, `CANVAS_CONFLICT`, `CANVAS_REJECTED`, `CANVAS_RATE_LIMITED`, `CANVAS_UNAVAILABLE`, `CANVAS_REGISTRY_EXPOSED`. Misuse exits 2, everything else exits 1.
|
|
190
|
+
Every failure carries a code, so a script can branch on it: `USAGE`, `UNREADABLE_FILE`, `UNKNOWN_DOCUMENT`, `INVALID_DOCUMENT`, `GIT_FAILED`, `EMPTY_DIFF`, `REPOSITORY_UNKNOWN`, `MISSING_API_KEY`, `PROVIDER_FAILED`, `MODEL_OUTPUT_INVALID`, `RENDER_FAILED`, and for `canvas`: `CANVAS_UNREGISTERED`, `CANVAS_UNKNOWN`, `CANVAS_CONFLICT`, `CANVAS_REJECTED`, `CANVAS_RATE_LIMITED`, `CANVAS_UNAVAILABLE`, `CANVAS_REGISTRY_EXPOSED`, and for `auth`: `AUTH_REQUIRED`, `MACHINE_REVOKED`, `APP_UNAVAILABLE`. Misuse exits 2, everything else exits 1.
|
|
163
191
|
|
|
164
192
|
---
|
|
165
193
|
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export type StartedSignIn = {
|
|
2
|
+
deviceCode: string;
|
|
3
|
+
userCode: string;
|
|
4
|
+
/** Carries the code, so nobody types it. */
|
|
5
|
+
approveUrl: string;
|
|
6
|
+
expiresInSeconds: number;
|
|
7
|
+
intervalSeconds: number;
|
|
8
|
+
};
|
|
9
|
+
/** RFC 8628 §3.2: the floor, and the default when none is served. */
|
|
10
|
+
export declare const DEFAULT_INTERVAL_SECONDS = 5;
|
|
11
|
+
/** The install id goes with the request: the approving browser may be on a phone and cannot know which machine asked. */
|
|
12
|
+
export declare const startSignIn: (api: string, installId: string, client: string, name: string) => Promise<StartedSignIn>;
|
|
13
|
+
export type PollOutcome = {
|
|
14
|
+
type: "token";
|
|
15
|
+
token: string;
|
|
16
|
+
claimed: number | undefined;
|
|
17
|
+
} | {
|
|
18
|
+
type: "pending";
|
|
19
|
+
} | {
|
|
20
|
+
type: "slow_down";
|
|
21
|
+
intervalSeconds: number | undefined;
|
|
22
|
+
} | {
|
|
23
|
+
type: "denied";
|
|
24
|
+
} | {
|
|
25
|
+
type: "expired";
|
|
26
|
+
};
|
|
27
|
+
export declare const pollSignIn: (api: string, deviceCode: string) => Promise<PollOutcome>;
|
|
28
|
+
/**
|
|
29
|
+
* Must match the app's hash. Duplicated on purpose: asking the app for ours
|
|
30
|
+
* would put the install id in a URL.
|
|
31
|
+
*/
|
|
32
|
+
export declare const installHash: (installId: string) => string;
|
|
33
|
+
/** `unknown`: this machine has no install id to look up. */
|
|
34
|
+
export type MachineState = "linked" | "revoked" | "unlinked" | "unknown";
|
|
35
|
+
export type SignInCheck = {
|
|
36
|
+
type: "live";
|
|
37
|
+
machine: MachineState;
|
|
38
|
+
} | {
|
|
39
|
+
type: "rejected";
|
|
40
|
+
}
|
|
41
|
+
/** The app could not be asked; says nothing about the credential. */
|
|
42
|
+
| {
|
|
43
|
+
type: "unknown";
|
|
44
|
+
why: string;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* `/api/machines` also answers whether this machine is linked. A store without
|
|
48
|
+
* accounts reads as `unknown`, never as a revoked credential.
|
|
49
|
+
*/
|
|
50
|
+
export declare const checkSignIn: (api: string, token: string, ourInstallId: string | undefined) => Promise<SignInCheck>;
|
|
51
|
+
/** Undefined on any failure: the sign-in already succeeded and the email is only for the greeting. */
|
|
52
|
+
export declare const whoAmI: (api: string, token: string) => Promise<string | undefined>;
|
|
53
|
+
/**
|
|
54
|
+
* Unlike `whoAmI`, keeps a dead token (`ended`) apart from an unreachable store
|
|
55
|
+
* (`unknown`), which the sign-in flow will report itself.
|
|
56
|
+
*/
|
|
57
|
+
export type Session = {
|
|
58
|
+
type: "active";
|
|
59
|
+
email: string;
|
|
60
|
+
} | {
|
|
61
|
+
type: "ended";
|
|
62
|
+
} | {
|
|
63
|
+
type: "unknown";
|
|
64
|
+
};
|
|
65
|
+
export declare const checkSession: (api: string, token: string) => Promise<Session>;
|
|
66
|
+
export type SignOut = "ended" | "already" | {
|
|
67
|
+
type: "unreachable";
|
|
68
|
+
why: string;
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* Ends the session and leaves the machine linked (`DELETE /api/machines/{id}`
|
|
72
|
+
* would unlink it). Unreachable is not thrown: the caller forgets the token
|
|
73
|
+
* locally either way.
|
|
74
|
+
*/
|
|
75
|
+
export declare const endSession: (api: string, token: string) => Promise<SignOut>;
|
|
76
|
+
//# sourceMappingURL=account.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"account.d.ts","sourceRoot":"","sources":["../src/account.ts"],"names":[],"mappings":"AAsFA,MAAM,MAAM,aAAa,GAAG;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,4CAA4C;IAC5C,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,qEAAqE;AACrE,eAAO,MAAM,wBAAwB,IAAI,CAAC;AAE1C,yHAAyH;AACzH,eAAO,MAAM,WAAW,QACjB,MAAM,aACA,MAAM,UACT,MAAM,QACR,MAAM,KACX,OAAO,CAAC,aAAa,CAkCvB,CAAC;AAaF,MAAM,MAAM,WAAW,GACnB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,GAC7D;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,eAAe,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,GAC1D;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,GAClB;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC;AAExB,eAAO,MAAM,UAAU,QAAe,MAAM,cAAc,MAAM,KAAG,OAAO,CAAC,WAAW,CA8BrF,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,WAAW,cAAe,MAAM,KAAG,MACc,CAAC;AAM/D,4DAA4D;AAC5D,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,CAAC;AAEzE,MAAM,MAAM,WAAW,GACnB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,YAAY,CAAA;CAAE,GACvC;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE;AACtB,qEAAqE;GACnE;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAErC;;;GAGG;AACH,eAAO,MAAM,WAAW,QACjB,MAAM,SACJ,MAAM,gBACC,MAAM,GAAG,SAAS,KAC/B,OAAO,CAAC,WAAW,CAwBrB,CAAC;AAIF,sGAAsG;AACtG,eAAO,MAAM,MAAM,QAAe,MAAM,SAAS,MAAM,KAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAQnF,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,OAAO,GACf;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACjC;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,GACjB;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC;AAExB,eAAO,MAAM,YAAY,QAAe,MAAM,SAAS,MAAM,KAAG,OAAO,CAAC,OAAO,CAe9E,CAAC;AAEF,MAAM,MAAM,OAAO,GAAG,OAAO,GAAG,SAAS,GAAG;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjF;;;;GAIG;AACH,eAAO,MAAM,UAAU,QAAe,MAAM,SAAS,MAAM,KAAG,OAAO,CAAC,OAAO,CAW5E,CAAC"}
|
package/dist/account.js
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sign-in is the OAuth 2.0 device grant (RFC 8628), the one flow that works
|
|
3
|
+
* over SSH and in containers. Its wire codes are the RFC's, not this CLI's.
|
|
4
|
+
*/
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { createHash } from "node:crypto";
|
|
7
|
+
import { assertNever } from "@coldtea/pr-lens-schema";
|
|
8
|
+
import { CLI_VERSION } from "./version.js";
|
|
9
|
+
import { PrLensCliError } from "./errors.js";
|
|
10
|
+
const REQUEST_TIMEOUT_MS = 30_000;
|
|
11
|
+
const hostOf = (api) => new URL(api).host;
|
|
12
|
+
const unavailable = (api, details, status) => new PrLensCliError("APP_UNAVAILABLE", status === undefined
|
|
13
|
+
? `${hostOf(api)} did not answer`
|
|
14
|
+
: `${hostOf(api)} answered ${status}`, details);
|
|
15
|
+
const parseJson = (text) => {
|
|
16
|
+
try {
|
|
17
|
+
return JSON.parse(text);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
const send = async (api, path, init) => {
|
|
24
|
+
const headers = {
|
|
25
|
+
accept: "application/json",
|
|
26
|
+
"user-agent": `pr-lens-cli/${CLI_VERSION}`,
|
|
27
|
+
};
|
|
28
|
+
if (init.token !== undefined)
|
|
29
|
+
headers.authorization = `Bearer ${init.token}`;
|
|
30
|
+
if (init.body !== undefined)
|
|
31
|
+
headers["content-type"] = "application/json";
|
|
32
|
+
const response = await fetch(`${api}${path}`, {
|
|
33
|
+
method: init.method,
|
|
34
|
+
headers,
|
|
35
|
+
body: init.body === undefined ? undefined : JSON.stringify(init.body),
|
|
36
|
+
// A redirect would carry the token to whatever host answered.
|
|
37
|
+
redirect: "manual",
|
|
38
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
39
|
+
}).catch(() => {
|
|
40
|
+
// The runtime's message leaks addresses and internals.
|
|
41
|
+
throw unavailable(api, "check the address and the connection, then try again");
|
|
42
|
+
});
|
|
43
|
+
if (response.status >= 300 && response.status < 400)
|
|
44
|
+
throw unavailable(api, "this address redirects, and the sign-in routes must answer directly", response.status);
|
|
45
|
+
const text = await response.text().catch(() => {
|
|
46
|
+
throw unavailable(api, "the answer was cut off; check the connection, then try again", response.status);
|
|
47
|
+
});
|
|
48
|
+
return { status: response.status, body: parseJson(text) };
|
|
49
|
+
};
|
|
50
|
+
/** Refusals outside the device flow use the app's own envelope. */
|
|
51
|
+
const Refusal = z.object({
|
|
52
|
+
error: z.looseObject({ code: z.string(), message: z.string() }),
|
|
53
|
+
});
|
|
54
|
+
const Started = z.object({
|
|
55
|
+
device_code: z.string().min(1),
|
|
56
|
+
user_code: z.string().min(1),
|
|
57
|
+
verification_uri: z.string().min(1),
|
|
58
|
+
verification_uri_complete: z.string().min(1).optional(),
|
|
59
|
+
expires_in: z.number().int().positive(),
|
|
60
|
+
interval: z.number().int().nonnegative().optional(),
|
|
61
|
+
});
|
|
62
|
+
/** RFC 8628 §3.2: the floor, and the default when none is served. */
|
|
63
|
+
export const DEFAULT_INTERVAL_SECONDS = 5;
|
|
64
|
+
/** The install id goes with the request: the approving browser may be on a phone and cannot know which machine asked. */
|
|
65
|
+
export const startSignIn = async (api, installId, client, name) => {
|
|
66
|
+
const answer = await send(api, "/api/device/code", {
|
|
67
|
+
method: "POST",
|
|
68
|
+
body: { install_id: installId, client, name },
|
|
69
|
+
});
|
|
70
|
+
if (answer.status !== 200) {
|
|
71
|
+
const refused = Refusal.safeParse(answer.body);
|
|
72
|
+
const code = refused.success ? refused.data.error.code : undefined;
|
|
73
|
+
// Twenty codes an hour per address. Usually a retry after a typo, so say what to do next.
|
|
74
|
+
throw code === "RATE_LIMITED"
|
|
75
|
+
? new PrLensCliError("APP_UNAVAILABLE", `${hostOf(api)} has handed this machine enough sign-in codes for now`, "it goes by the hour, so a short wait clears it; the sign-in you already have is unaffected")
|
|
76
|
+
: unavailable(api, refused.success ? refused.data.error.message : "the answer was not in the shape the sign-in expects", answer.status);
|
|
77
|
+
}
|
|
78
|
+
const started = Started.safeParse(answer.body);
|
|
79
|
+
if (!started.success)
|
|
80
|
+
throw unavailable(api, "the answer was not in the shape the sign-in expects", answer.status);
|
|
81
|
+
return {
|
|
82
|
+
deviceCode: started.data.device_code,
|
|
83
|
+
userCode: started.data.user_code,
|
|
84
|
+
approveUrl: started.data.verification_uri_complete ?? started.data.verification_uri,
|
|
85
|
+
expiresInSeconds: started.data.expires_in,
|
|
86
|
+
intervalSeconds: started.data.interval ?? DEFAULT_INTERVAL_SECONDS,
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
/** `claimed` is an app extension (RFC 6749 §5.1 allows it); optional so a sign-in never fails over it. */
|
|
90
|
+
const Granted = z.object({
|
|
91
|
+
access_token: z.string().min(1),
|
|
92
|
+
claimed: z.coerce.number().int().min(0).optional(),
|
|
93
|
+
});
|
|
94
|
+
const Pending = z.object({
|
|
95
|
+
error: z.enum(["authorization_pending", "slow_down", "access_denied", "expired_token"]),
|
|
96
|
+
interval: z.number().int().nonnegative().optional(),
|
|
97
|
+
});
|
|
98
|
+
export const pollSignIn = async (api, deviceCode) => {
|
|
99
|
+
const answer = await send(api, "/api/device/token", {
|
|
100
|
+
method: "POST",
|
|
101
|
+
body: { device_code: deviceCode, grant_type: "urn:ietf:params:oauth:grant-type:device_code" },
|
|
102
|
+
});
|
|
103
|
+
if (answer.status === 200) {
|
|
104
|
+
const granted = Granted.safeParse(answer.body);
|
|
105
|
+
if (!granted.success)
|
|
106
|
+
throw unavailable(api, "the sign-in was approved and the answer carried no token", answer.status);
|
|
107
|
+
return { type: "token", token: granted.data.access_token, claimed: granted.data.claimed };
|
|
108
|
+
}
|
|
109
|
+
const pending = Pending.safeParse(answer.body);
|
|
110
|
+
if (!pending.success)
|
|
111
|
+
// Includes `invalid_request`, which waiting will not fix.
|
|
112
|
+
throw unavailable(api, "the answer was not one the device flow defines", answer.status);
|
|
113
|
+
switch (pending.data.error) {
|
|
114
|
+
case "authorization_pending":
|
|
115
|
+
return { type: "pending" };
|
|
116
|
+
case "slow_down":
|
|
117
|
+
return { type: "slow_down", intervalSeconds: pending.data.interval };
|
|
118
|
+
case "access_denied":
|
|
119
|
+
return { type: "denied" };
|
|
120
|
+
case "expired_token":
|
|
121
|
+
return { type: "expired" };
|
|
122
|
+
default:
|
|
123
|
+
return assertNever(pending.data.error, "Unhandled device flow answer");
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
/**
|
|
127
|
+
* Must match the app's hash. Duplicated on purpose: asking the app for ours
|
|
128
|
+
* would put the install id in a URL.
|
|
129
|
+
*/
|
|
130
|
+
export const installHash = (installId) => createHash("sha256").update(installId, "utf8").digest("hex");
|
|
131
|
+
const Machines = z.object({
|
|
132
|
+
machines: z.array(z.object({ id: z.string(), revoked: z.boolean() })),
|
|
133
|
+
});
|
|
134
|
+
/**
|
|
135
|
+
* `/api/machines` also answers whether this machine is linked. A store without
|
|
136
|
+
* accounts reads as `unknown`, never as a revoked credential.
|
|
137
|
+
*/
|
|
138
|
+
export const checkSignIn = async (api, token, ourInstallId) => {
|
|
139
|
+
let answer;
|
|
140
|
+
try {
|
|
141
|
+
answer = await send(api, "/api/machines", { method: "GET", token });
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
if (!(error instanceof PrLensCliError))
|
|
145
|
+
throw error;
|
|
146
|
+
return { type: "unknown", why: error.message };
|
|
147
|
+
}
|
|
148
|
+
if (answer.status === 401 || answer.status === 403)
|
|
149
|
+
return { type: "rejected" };
|
|
150
|
+
const machines = Machines.safeParse(answer.body);
|
|
151
|
+
if (!machines.success)
|
|
152
|
+
return { type: "unknown", why: `${hostOf(api)} answered ${answer.status}, which this CLI cannot read` };
|
|
153
|
+
if (ourInstallId === undefined)
|
|
154
|
+
return { type: "live", machine: "unknown" };
|
|
155
|
+
const ours = installHash(ourInstallId);
|
|
156
|
+
const machine = machines.data.machines.find((entry) => entry.id === ours);
|
|
157
|
+
return {
|
|
158
|
+
type: "live",
|
|
159
|
+
machine: machine === undefined ? "unlinked" : machine.revoked ? "revoked" : "linked",
|
|
160
|
+
};
|
|
161
|
+
};
|
|
162
|
+
const Account = z.object({ email: z.string().min(3) });
|
|
163
|
+
/** Undefined on any failure: the sign-in already succeeded and the email is only for the greeting. */
|
|
164
|
+
export const whoAmI = async (api, token) => {
|
|
165
|
+
try {
|
|
166
|
+
const answer = await send(api, "/api/account", { method: "GET", token });
|
|
167
|
+
const account = Account.safeParse(answer.body);
|
|
168
|
+
return answer.status === 200 && account.success ? account.data.email : undefined;
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
return undefined;
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
export const checkSession = async (api, token) => {
|
|
175
|
+
let answer;
|
|
176
|
+
try {
|
|
177
|
+
answer = await send(api, "/api/account", { method: "GET", token });
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
return { type: "unknown" };
|
|
181
|
+
}
|
|
182
|
+
// Any other status is an outage, not evidence about the credential.
|
|
183
|
+
if (answer.status === 401 || answer.status === 403)
|
|
184
|
+
return { type: "ended" };
|
|
185
|
+
const account = Account.safeParse(answer.body);
|
|
186
|
+
return answer.status === 200 && account.success
|
|
187
|
+
? { type: "active", email: account.data.email }
|
|
188
|
+
: { type: "unknown" };
|
|
189
|
+
};
|
|
190
|
+
/**
|
|
191
|
+
* Ends the session and leaves the machine linked (`DELETE /api/machines/{id}`
|
|
192
|
+
* would unlink it). Unreachable is not thrown: the caller forgets the token
|
|
193
|
+
* locally either way.
|
|
194
|
+
*/
|
|
195
|
+
export const endSession = async (api, token) => {
|
|
196
|
+
try {
|
|
197
|
+
const answer = await send(api, "/api/session", { method: "DELETE", token });
|
|
198
|
+
if (answer.status === 200)
|
|
199
|
+
return "ended";
|
|
200
|
+
// No such route, or the token was already dead.
|
|
201
|
+
if (answer.status === 404 || answer.status === 401)
|
|
202
|
+
return "already";
|
|
203
|
+
return { type: "unreachable", why: `${hostOf(api)} answered ${answer.status}` };
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
if (!(error instanceof PrLensCliError))
|
|
207
|
+
throw error;
|
|
208
|
+
return { type: "unreachable", why: error.message };
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
//# sourceMappingURL=account.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"account.js","sourceRoot":"","sources":["../src/account.ts"],"names":[],"mappings":"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"}
|
package/dist/auth.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read with `||`, never `??`: the Action sets it to "" when no token was
|
|
3
|
+
* given, since Actions cannot omit an `env` key conditionally.
|
|
4
|
+
*/
|
|
5
|
+
export declare const TOKEN_ENV = "PR_LENS_TOKEN";
|
|
6
|
+
export declare const authPath: (env: Record<string, string | undefined>, api: string) => string | undefined;
|
|
7
|
+
export type Credential = {
|
|
8
|
+
token: string;
|
|
9
|
+
signedInAt: string | undefined;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* An unreadable file is kept apart from a missing one and never deleted here:
|
|
13
|
+
* it may hold a working token. Only `auth logout` removes it.
|
|
14
|
+
*/
|
|
15
|
+
export type CredentialRead = {
|
|
16
|
+
type: "credential";
|
|
17
|
+
path: string;
|
|
18
|
+
credential: Credential;
|
|
19
|
+
} | {
|
|
20
|
+
type: "none";
|
|
21
|
+
} | {
|
|
22
|
+
type: "unreadable";
|
|
23
|
+
path: string;
|
|
24
|
+
why: string;
|
|
25
|
+
};
|
|
26
|
+
export declare const readCredential: (env: Record<string, string | undefined>, api: string) => Promise<CredentialRead>;
|
|
27
|
+
/** Every failure answers undefined, so a push never starts failing over an account. */
|
|
28
|
+
export declare const readToken: (env: Record<string, string | undefined>, api: string) => Promise<string | undefined>;
|
|
29
|
+
/**
|
|
30
|
+
* For account commands, which must tell an unreadable file apart from being
|
|
31
|
+
* signed out rather than advise signing in again.
|
|
32
|
+
*/
|
|
33
|
+
export declare const requireToken: (env: Record<string, string | undefined>, api: string) => Promise<string>;
|
|
34
|
+
export declare const writeCredential: (env: Record<string, string | undefined>, api: string, token: string, now: string) => Promise<string>;
|
|
35
|
+
/**
|
|
36
|
+
* The only place that removes the file, readable or not. No read first: a
|
|
37
|
+
* sign-in from another shell in between would be thrown away unseen.
|
|
38
|
+
*/
|
|
39
|
+
export declare const forgetCredential: (env: Record<string, string | undefined>, api: string) => Promise<boolean>;
|
|
40
|
+
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAiBA;;;GAGG;AACH,eAAO,MAAM,SAAS,kBAAkB,CAAC;AAIzC,eAAO,MAAM,QAAQ,QACd,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,OAClC,MAAM,KACV,MAAM,GAAG,SAA8C,CAAC;AAE3D,MAAM,MAAM,UAAU,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAAC;AAE3E;;;GAGG;AACH,MAAM,MAAM,cAAc,GACtB;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,UAAU,CAAA;CAAE,GAC5D;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAiCtD,eAAO,MAAM,cAAc,QACpB,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,OAClC,MAAM,KACV,OAAO,CAAC,cAAc,CAiBxB,CAAC;AAEF,uFAAuF;AACvF,eAAO,MAAM,SAAS,QACf,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,OAClC,MAAM,KACV,OAAO,CAAC,MAAM,GAAG,SAAS,CAM5B,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,YAAY,QAClB,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,OAClC,MAAM,KACV,OAAO,CAAC,MAAM,CA2BhB,CAAC;AAEF,eAAO,MAAM,eAAe,QACrB,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,OAClC,MAAM,SACJ,MAAM,OACR,MAAM,KACV,OAAO,CAAC,MAAM,CAWhB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,gBAAgB,QACtB,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,OAClC,MAAM,KACV,OAAO,CAAC,OAAO,CAejB,CAAC"}
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The sign-in credential on disk, one file per store so a token never reaches
|
|
3
|
+
* the wrong one. A file per origin also means two shells signing in at once
|
|
4
|
+
* can only race on the same origin, where the last write wins correctly.
|
|
5
|
+
* `0600` on the file, `0700` on the directory: the file names reveal which
|
|
6
|
+
* stores this person signs in to.
|
|
7
|
+
*/
|
|
8
|
+
import { dirname } from "node:path";
|
|
9
|
+
import { mkdir, readFile, rm } from "node:fs/promises";
|
|
10
|
+
import { assertNever } from "@coldtea/pr-lens-schema";
|
|
11
|
+
import { PrLensCliError } from "./errors.js";
|
|
12
|
+
import { writeSecretJsonFile } from "./io.js";
|
|
13
|
+
import { originPath } from "./config-home.js";
|
|
14
|
+
const CREDENTIALS = "auth";
|
|
15
|
+
/**
|
|
16
|
+
* Read with `||`, never `??`: the Action sets it to "" when no token was
|
|
17
|
+
* given, since Actions cannot omit an `env` key conditionally.
|
|
18
|
+
*/
|
|
19
|
+
export const TOKEN_ENV = "PR_LENS_TOKEN";
|
|
20
|
+
const ACCOUNT_TOKEN = /^prl_u_[A-Za-z0-9_-]{22}$/;
|
|
21
|
+
export const authPath = (env, api) => originPath(env, CREDENTIALS, api);
|
|
22
|
+
const describe = (error) => error instanceof Error ? error.message : String(error);
|
|
23
|
+
const missing = (error) => typeof error === "object" &&
|
|
24
|
+
error !== null &&
|
|
25
|
+
"code" in error &&
|
|
26
|
+
error.code === "ENOENT";
|
|
27
|
+
const credentialIn = (text) => {
|
|
28
|
+
const contents = (() => {
|
|
29
|
+
try {
|
|
30
|
+
return JSON.parse(text);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
})();
|
|
36
|
+
if (typeof contents !== "object" || contents === null)
|
|
37
|
+
return undefined;
|
|
38
|
+
const { token, signedInAt } = contents;
|
|
39
|
+
if (typeof token !== "string" || !ACCOUNT_TOKEN.test(token))
|
|
40
|
+
return undefined;
|
|
41
|
+
return {
|
|
42
|
+
token,
|
|
43
|
+
signedInAt: typeof signedInAt === "string" ? signedInAt : undefined,
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
export const readCredential = async (env, api) => {
|
|
47
|
+
const path = authPath(env, api);
|
|
48
|
+
if (path === undefined)
|
|
49
|
+
return { type: "none" };
|
|
50
|
+
let text;
|
|
51
|
+
try {
|
|
52
|
+
text = await readFile(path, "utf8");
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
return missing(error)
|
|
56
|
+
? { type: "none" }
|
|
57
|
+
: { type: "unreadable", path, why: describe(error) };
|
|
58
|
+
}
|
|
59
|
+
const credential = credentialIn(text);
|
|
60
|
+
return credential === undefined
|
|
61
|
+
? { type: "unreadable", path, why: "it holds no token this CLI can read" }
|
|
62
|
+
: { type: "credential", path, credential };
|
|
63
|
+
};
|
|
64
|
+
/** Every failure answers undefined, so a push never starts failing over an account. */
|
|
65
|
+
export const readToken = async (env, api) => {
|
|
66
|
+
const given = env[TOKEN_ENV];
|
|
67
|
+
if (given)
|
|
68
|
+
return given;
|
|
69
|
+
const stored = await readCredential(env, api);
|
|
70
|
+
return stored.type === "credential" ? stored.credential.token : undefined;
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* For account commands, which must tell an unreadable file apart from being
|
|
74
|
+
* signed out rather than advise signing in again.
|
|
75
|
+
*/
|
|
76
|
+
export const requireToken = async (env, api) => {
|
|
77
|
+
const given = env[TOKEN_ENV];
|
|
78
|
+
if (given)
|
|
79
|
+
return given;
|
|
80
|
+
const host = new URL(api).host;
|
|
81
|
+
const stored = await readCredential(env, api);
|
|
82
|
+
switch (stored.type) {
|
|
83
|
+
case "credential":
|
|
84
|
+
return stored.credential.token;
|
|
85
|
+
case "unreadable":
|
|
86
|
+
throw new PrLensCliError("UNREADABLE_FILE", `the sign-in for ${host} cannot be read`, [
|
|
87
|
+
`${stored.path}: ${stored.why}`,
|
|
88
|
+
"pr-lens auth logout clears it, and then auth login writes a fresh one",
|
|
89
|
+
].join("\n"));
|
|
90
|
+
case "none":
|
|
91
|
+
throw new PrLensCliError("AUTH_REQUIRED", `not signed in to ${host}`, "pr-lens auth login");
|
|
92
|
+
default:
|
|
93
|
+
return assertNever(stored, "Unhandled credential read");
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
export const writeCredential = async (env, api, token, now) => {
|
|
97
|
+
const path = authPath(env, api);
|
|
98
|
+
if (path === undefined)
|
|
99
|
+
throw new PrLensCliError("UNREADABLE_FILE", "there is no home directory to keep this sign-in in", "set HOME, or XDG_CONFIG_HOME, to somewhere this machine can write");
|
|
100
|
+
await mkdir(dirname(path), { recursive: true, mode: 0o700 });
|
|
101
|
+
return writeSecretJsonFile(path, { api, token, signedInAt: now });
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* The only place that removes the file, readable or not. No read first: a
|
|
105
|
+
* sign-in from another shell in between would be thrown away unseen.
|
|
106
|
+
*/
|
|
107
|
+
export const forgetCredential = async (env, api) => {
|
|
108
|
+
const path = authPath(env, api);
|
|
109
|
+
if (path === undefined)
|
|
110
|
+
return false;
|
|
111
|
+
try {
|
|
112
|
+
await rm(path);
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
if (missing(error))
|
|
117
|
+
return false;
|
|
118
|
+
throw new PrLensCliError("UNREADABLE_FILE", `cannot remove ${path}`, describe(error));
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
//# sourceMappingURL=auth.js.map
|
package/dist/auth.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
|
package/dist/canvas/api.d.ts
CHANGED
|
@@ -54,7 +54,8 @@ export type Rotated = z.infer<typeof Rotated>;
|
|
|
54
54
|
export type Fetched = Omit<z.infer<typeof Fetched>, "document"> & {
|
|
55
55
|
document: GraphDoc;
|
|
56
56
|
};
|
|
57
|
-
|
|
57
|
+
/** The bearer is how CI attributes a mint: a runner's install id is never linked. */
|
|
58
|
+
export declare const mintCanvas: (api: string, install: string | undefined, token: string | undefined) => Promise<Minted>;
|
|
58
59
|
export declare const fetchCanvas: (api: string, id: string) => Promise<Fetched>;
|
|
59
60
|
export declare const pushCanvas: (api: string, id: string, token: string, rev: number, document: GraphDoc) => Promise<Pushed>;
|
|
60
61
|
/** A rotation onto itself changes nothing and is answered "rotated" only for the current token. */
|
|
@@ -65,5 +66,38 @@ export declare const deleteCanvas: (api: string, id: string, token: string) => P
|
|
|
65
66
|
id: string;
|
|
66
67
|
deleted: true;
|
|
67
68
|
}>;
|
|
69
|
+
/** An unreadable revision must not read as an empty canvas. */
|
|
70
|
+
declare const Preview: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
71
|
+
type: z.ZodLiteral<"drawn">;
|
|
72
|
+
title: z.ZodString;
|
|
73
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
74
|
+
type: z.ZodLiteral<"not_drawn">;
|
|
75
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
76
|
+
type: z.ZodLiteral<"unreadable">;
|
|
77
|
+
}, z.core.$strip>], "type">;
|
|
78
|
+
declare const Owned: z.ZodObject<{
|
|
79
|
+
id: z.ZodString;
|
|
80
|
+
rev: z.ZodNumber;
|
|
81
|
+
preview: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
82
|
+
type: z.ZodLiteral<"drawn">;
|
|
83
|
+
title: z.ZodString;
|
|
84
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
85
|
+
type: z.ZodLiteral<"not_drawn">;
|
|
86
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
87
|
+
type: z.ZodLiteral<"unreadable">;
|
|
88
|
+
}, z.core.$strip>], "type">;
|
|
89
|
+
}, z.core.$strip>;
|
|
90
|
+
export type CanvasPreview = z.infer<typeof Preview>;
|
|
91
|
+
export type OwnedCanvas = z.infer<typeof Owned>;
|
|
92
|
+
/**
|
|
93
|
+
* Carries no write tokens: the app keeps only their hashes. A store without
|
|
94
|
+
* accounts 404s here, which reads as unavailable, not as a missing canvas.
|
|
95
|
+
*/
|
|
96
|
+
export declare const listOwnedCanvases: (api: string, token: string) => Promise<OwnedCanvas[]>;
|
|
97
|
+
/**
|
|
98
|
+
* The account token takes the header, so the write tokens go in the body.
|
|
99
|
+
* The caller mints the next token so a lost answer can be replayed.
|
|
100
|
+
*/
|
|
101
|
+
export declare const claimCanvas: (api: string, id: string, accountToken: string, writeToken: string, nextWriteToken: string) => Promise<Rotated>;
|
|
68
102
|
export {};
|
|
69
103
|
//# sourceMappingURL=api.d.ts.map
|
package/dist/canvas/api.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/canvas/api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAGL,KAAK,QAAQ,EACd,MAAM,yBAAyB,CAAC;AAmBjC,QAAA,MAAM,MAAM;;;;;;;iBAOV,CAAC;AAEH,QAAA,MAAM,OAAO;;;;;;;;;;;;;;;;;iBAOX,CAAC;AAEH,QAAA,MAAM,MAAM;;;;;;;;;;;;;;;;;iBAOV,CAAC;AAEH,QAAA,MAAM,OAAO;;;iBAAoD,CAAC;AAElE,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,MAAM,CAAC,CAAC;AAC5C,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,MAAM,CAAC,CAAC;AAC5C,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,OAAO,CAAC,CAAC;AAC9C,MAAM,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,OAAO,CAAC,EAAE,UAAU,CAAC,GAAG;IAChE,QAAQ,EAAE,QAAQ,CAAC;CACpB,CAAC;
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/canvas/api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAGL,KAAK,QAAQ,EACd,MAAM,yBAAyB,CAAC;AAmBjC,QAAA,MAAM,MAAM;;;;;;;iBAOV,CAAC;AAEH,QAAA,MAAM,OAAO;;;;;;;;;;;;;;;;;iBAOX,CAAC;AAEH,QAAA,MAAM,MAAM;;;;;;;;;;;;;;;;;iBAOV,CAAC;AAEH,QAAA,MAAM,OAAO;;;iBAAoD,CAAC;AAElE,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,MAAM,CAAC,CAAC;AAC5C,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,MAAM,CAAC,CAAC;AAC5C,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,OAAO,CAAC,CAAC;AAC9C,MAAM,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,OAAO,CAAC,EAAE,UAAU,CAAC,GAAG;IAChE,QAAQ,EAAE,QAAQ,CAAC;CACpB,CAAC;AAkOF,qFAAqF;AACrF,eAAO,MAAM,UAAU,QAChB,MAAM,WACF,MAAM,GAAG,SAAS,SACpB,MAAM,GAAG,SAAS,KACxB,OAAO,CAAC,MAAM,CAKd,CAAC;AAEJ,eAAO,MAAM,WAAW,QACjB,MAAM,MACP,MAAM,KACT,OAAO,CAAC,OAAO,CAgBjB,CAAC;AAEF,eAAO,MAAM,UAAU,QAChB,MAAM,MACP,MAAM,SACH,MAAM,OACR,MAAM,YACD,QAAQ,KACjB,OAAO,CAAC,MAAM,CAYd,CAAC;AAEJ,mGAAmG;AACnG,eAAO,MAAM,gBAAgB,QACtB,MAAM,MACP,MAAM,SACH,MAAM,KACZ,OAAO,CAAC,OAAO,CAQf,CAAC;AAEJ,gFAAgF;AAChF,eAAO,MAAM,YAAY,QAClB,MAAM,MACP,MAAM,SACH,MAAM,aACF,MAAM,KAChB,OAAO,CAAC,OAAO,CAWf,CAAC;AAEJ,eAAO,MAAM,YAAY,QAClB,MAAM,MACP,MAAM,SACH,MAAM,KACZ,OAAO,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,IAAI,CAAA;CAAE,CAKrC,CAAC;AAEJ,+DAA+D;AAC/D,QAAA,MAAM,OAAO;;;;;;;2BAIX,CAAC;AAEH,QAAA,MAAM,KAAK;;;;;;;;;;;iBAIT,CAAC;AAIH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,OAAO,CAAC,CAAC;AACpD,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,KAAK,CAAC,CAAC;AAEhD;;;GAGG;AACH,eAAO,MAAM,iBAAiB,QACvB,MAAM,SACJ,MAAM,KACZ,OAAO,CAAC,WAAW,EAAE,CAKY,CAAC;AAErC;;;GAGG;AACH,eAAO,MAAM,WAAW,QACjB,MAAM,MACP,MAAM,gBACI,MAAM,cACR,MAAM,kBACF,MAAM,KACrB,OAAO,CAAC,OAAO,CAWf,CAAC"}
|