@hanzo/build 0.2.50 → 0.2.51
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 +1 -1
- package/lib/api/keys.d.ts +21 -13
- package/lib/api/keys.js +35 -19
- package/lib/settings/keys.js +22 -27
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -214,7 +214,7 @@ picker and open-in-tab; Share and Publish; the console dock under it.
|
|
|
214
214
|
| `PATCH /v1/agent/sessions/{id}` `{title}` · `{published}` | rename a run, share its story at `GET /v1/agent/builds/{org}/{project}` |
|
|
215
215
|
| `GET /v1/agent/sessions?kind=coding&status=&after=` | Find, by status, paged |
|
|
216
216
|
| `GET`/`POST /v1/agent/targets` · `PATCH`/`DELETE …/{id}` · `POST …/{id}/key` | the org's machines, and a machine's claim key |
|
|
217
|
-
| `GET`/`POST
|
|
217
|
+
| `GET`/`POST /v1/account/keys` · `DELETE …/{id}` | your API keys, as many as you need; one revoked at a time |
|
|
218
218
|
| `GET /v1/auto/flows` · `POST /v1/auto/flows` · `POST /v1/auto/flows/{id}/enable` | automations |
|
|
219
219
|
| `GET /v1/environment` · `GET`/`PUT`/`DELETE /v1/environment/{repo}` · `PUT`/`DELETE …/secrets/{name}` | a codebase's environment |
|
|
220
220
|
| `GET /v1/agent/coding/{session}/changes` · `/tree` · `/blob` | what a run pushed, and its branch's files, from the forge |
|
package/lib/api/keys.d.ts
CHANGED
|
@@ -1,31 +1,38 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* A person's
|
|
3
|
-
*
|
|
2
|
+
* A person's API keys, each its own credential: as many as they like, of either
|
|
3
|
+
* type — secret (sk-, it belongs on a server) or publishable (pk-, safe in a
|
|
4
|
+
* page's source).
|
|
4
5
|
*
|
|
5
|
-
* GET /v1/account/keys
|
|
6
|
-
* POST /v1/account/keys
|
|
7
|
-
* DELETE /v1/account/keys
|
|
6
|
+
* GET /v1/account/keys {keys: [{id, name, type, prefix, key?, status, limit?, created, …}]}
|
|
7
|
+
* POST /v1/account/keys {name, type, limit?} → the key, its secret answered once
|
|
8
|
+
* DELETE /v1/account/keys/{id} revoke exactly that one; it stays listed as revoked
|
|
8
9
|
*
|
|
9
|
-
* Creating
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* Creating a key never touches another. A secret key is answered once and by its
|
|
11
|
+
* prefix after; a publishable key is public by construction and always comes back
|
|
12
|
+
* whole. A limit only narrows what a key may reach — `model:zen5`,
|
|
13
|
+
* `project:acme`, `product:train`, `read:*`.
|
|
13
14
|
*/
|
|
14
15
|
import { type Target } from './call.ts';
|
|
15
16
|
export declare const KINDS: readonly ['secret', 'publishable'];
|
|
16
17
|
export type Kind = (typeof KINDS)[number];
|
|
17
18
|
export interface Key {
|
|
19
|
+
id: string;
|
|
20
|
+
name: string;
|
|
18
21
|
type: Kind;
|
|
19
22
|
/** The recognizable head, never enough to use the key. */
|
|
20
23
|
prefix: string;
|
|
21
24
|
/** The whole key: a publishable key's only. */
|
|
22
25
|
key: string;
|
|
26
|
+
/** active, expired, revoked or disabled. */
|
|
27
|
+
status: string;
|
|
23
28
|
/** `kind:name` entries; none reaches whatever its holder does. */
|
|
24
29
|
limit: string[];
|
|
25
|
-
/** When it
|
|
30
|
+
/** When it was made, as IAM records it. */
|
|
26
31
|
created: string;
|
|
27
32
|
}
|
|
28
33
|
export interface Minted {
|
|
34
|
+
id: string;
|
|
35
|
+
name: string;
|
|
29
36
|
type: Kind;
|
|
30
37
|
/** The key itself. A secret one is never readable again. */
|
|
31
38
|
key: string;
|
|
@@ -36,6 +43,7 @@ export declare function keys(t: Target): Promise<Key[]>;
|
|
|
36
43
|
export declare const LIMIT: RegExp;
|
|
37
44
|
/** The limits a person typed, split on commas and spaces; throws on the first that is not one. */
|
|
38
45
|
export declare function limits(raw: string): string[];
|
|
39
|
-
/** Create a key
|
|
40
|
-
export declare function mint(t: Target, type: Kind, limit?: string[]): Promise<Minted>;
|
|
41
|
-
|
|
46
|
+
/** Create a new key beside the ones held. The answer is the only time a secret key is shown. */
|
|
47
|
+
export declare function mint(t: Target, type: Kind, name?: string, limit?: string[]): Promise<Minted>;
|
|
48
|
+
/** Revoke exactly one key, by id. Every other key keeps working. */
|
|
49
|
+
export declare function revoke(t: Target, id: string): Promise<void>;
|
package/lib/api/keys.js
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* A person's
|
|
3
|
-
*
|
|
2
|
+
* A person's API keys, each its own credential: as many as they like, of either
|
|
3
|
+
* type — secret (sk-, it belongs on a server) or publishable (pk-, safe in a
|
|
4
|
+
* page's source).
|
|
4
5
|
*
|
|
5
|
-
* GET /v1/account/keys
|
|
6
|
-
* POST /v1/account/keys
|
|
7
|
-
* DELETE /v1/account/keys
|
|
6
|
+
* GET /v1/account/keys {keys: [{id, name, type, prefix, key?, status, limit?, created, …}]}
|
|
7
|
+
* POST /v1/account/keys {name, type, limit?} → the key, its secret answered once
|
|
8
|
+
* DELETE /v1/account/keys/{id} revoke exactly that one; it stays listed as revoked
|
|
8
9
|
*
|
|
9
|
-
* Creating
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* Creating a key never touches another. A secret key is answered once and by its
|
|
11
|
+
* prefix after; a publishable key is public by construction and always comes back
|
|
12
|
+
* whole. A limit only narrows what a key may reach — `model:zen5`,
|
|
13
|
+
* `project:acme`, `product:train`, `read:*`.
|
|
13
14
|
*/
|
|
14
|
-
import { call
|
|
15
|
+
import { call } from './call.js';
|
|
15
16
|
export const KINDS = ['secret', 'publishable'];
|
|
16
17
|
const str = (v) => (typeof v === 'string' ? v : '');
|
|
17
18
|
const list = (v) => (Array.isArray(v) ? v.filter((s) => typeof s === 'string' && s !== '') : []);
|
|
@@ -23,9 +24,18 @@ export async function keys(t) {
|
|
|
23
24
|
for (const raw of rows) {
|
|
24
25
|
const o = (raw && typeof raw === 'object' ? raw : {});
|
|
25
26
|
const type = kind(o.type);
|
|
26
|
-
if (!type)
|
|
27
|
+
if (!type || !str(o.id))
|
|
27
28
|
continue;
|
|
28
|
-
out.push({
|
|
29
|
+
out.push({
|
|
30
|
+
id: str(o.id),
|
|
31
|
+
name: str(o.name),
|
|
32
|
+
type,
|
|
33
|
+
prefix: str(o.prefix),
|
|
34
|
+
key: str(o.key),
|
|
35
|
+
status: str(o.status) || 'active',
|
|
36
|
+
limit: list(o.limit),
|
|
37
|
+
created: str(o.created),
|
|
38
|
+
});
|
|
29
39
|
}
|
|
30
40
|
return out;
|
|
31
41
|
}
|
|
@@ -42,14 +52,20 @@ export function limits(raw) {
|
|
|
42
52
|
throw new Error(`${bad} is not a limit — write it as kind:name, like model:zen5`);
|
|
43
53
|
return out;
|
|
44
54
|
}
|
|
45
|
-
/** Create a key
|
|
46
|
-
export async function mint(t, type, limit = []) {
|
|
47
|
-
const
|
|
48
|
-
|
|
55
|
+
/** Create a new key beside the ones held. The answer is the only time a secret key is shown. */
|
|
56
|
+
export async function mint(t, type, name = '', limit = []) {
|
|
57
|
+
const body = { type };
|
|
58
|
+
if (name.trim())
|
|
59
|
+
body.name = name.trim();
|
|
60
|
+
if (limit.length)
|
|
61
|
+
body.limit = limit;
|
|
62
|
+
const r = await call(t, 'POST', '/v1/account/keys', body);
|
|
63
|
+
const key = str(r?.key);
|
|
49
64
|
if (!key)
|
|
50
65
|
throw new Error('The platform answered no key');
|
|
51
|
-
return { type: kind(r?.type) ?? type, key, limit: list(r?.limit) };
|
|
66
|
+
return { id: str(r?.id), name: str(r?.name), type: kind(r?.type) ?? type, key, limit: list(r?.limit) };
|
|
52
67
|
}
|
|
53
|
-
|
|
54
|
-
|
|
68
|
+
/** Revoke exactly one key, by id. Every other key keeps working. */
|
|
69
|
+
export async function revoke(t, id) {
|
|
70
|
+
await call(t, 'DELETE', `/v1/account/keys/${encodeURIComponent(id)}`);
|
|
55
71
|
}
|
package/lib/settings/keys.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
/**
|
|
3
|
-
* API keys: your
|
|
4
|
-
* for a server,
|
|
5
|
-
*
|
|
6
|
-
*
|
|
3
|
+
* API keys: your keys for calling the Hanzo API from code, as many as you need —
|
|
4
|
+
* a secret key for a server, a publishable key for a page's source. Each is its
|
|
5
|
+
* own credential: creating one never touches another, and revoking one stops only
|
|
6
|
+
* that one. A secret key is shown once, when it is made. A limit narrows what a
|
|
7
|
+
* key reaches; it never reaches further than you do.
|
|
7
8
|
*/
|
|
8
9
|
import { SizableText, XStack, YStack } from '@hanzo/gui';
|
|
9
10
|
import { Button, Dialog, DialogContent, DialogTitle, Input } from '@hanzo/ui';
|
|
@@ -15,19 +16,15 @@ import { useRead } from '../data.js';
|
|
|
15
16
|
import { useHost, useTarget } from '../host.js';
|
|
16
17
|
import { Card, Field, Heading, Note, Once, Row, Soft } from './ui.js';
|
|
17
18
|
const NAME = { secret: 'Secret key', publishable: 'Publishable key' };
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
publishable: 'Safe in a page’s source.',
|
|
21
|
-
};
|
|
22
|
-
/** The line under a key: what it is for, how it starts, what it reaches, when it was made. */
|
|
23
|
-
function about(type, k) {
|
|
24
|
-
if (!k)
|
|
25
|
-
return `${FOR[type]} None yet.`;
|
|
19
|
+
/** The line under a key: its head, what it reaches, when it was made, and where it stands. */
|
|
20
|
+
function about(k) {
|
|
26
21
|
const when = k.created ? new Date(k.created) : null;
|
|
27
22
|
return [
|
|
28
|
-
type === 'secret' && k.prefix ? `${k.prefix}…` : '',
|
|
23
|
+
k.type === 'secret' && k.prefix ? `${k.prefix}…` : '',
|
|
24
|
+
k.type === 'publishable' ? 'Publishable' : '',
|
|
29
25
|
k.limit.length ? `Reaches ${k.limit.join(', ')}` : 'Unrestricted',
|
|
30
26
|
when && !Number.isNaN(when.getTime()) ? `Created ${when.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' })}` : '',
|
|
27
|
+
k.status !== 'active' ? k.status[0].toUpperCase() + k.status.slice(1) : '',
|
|
31
28
|
]
|
|
32
29
|
.filter(Boolean)
|
|
33
30
|
.join(' · ');
|
|
@@ -43,24 +40,21 @@ export function Keys() {
|
|
|
43
40
|
if (!signed) {
|
|
44
41
|
return (_jsxs(YStack, { gap: "$5", children: [_jsx(Heading, { title: "API keys" }), _jsx(Soft, { children: "Sign in to see your API keys." })] }));
|
|
45
42
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const k = held(type);
|
|
49
|
-
return (_jsxs(YStack, { borderTopWidth: i ? 1 : 0, borderColor: "$borderColor", children: [_jsx(Row, { first: true, title: NAME[type], detail: about(type, k), trailing: _jsxs(XStack, { gap: "$1.5", shrink: 0, children: [k ? (_jsx(Button, { size: "sm", variant: "ghost", "aria-label": `Revoke the ${type} key`, onPress: () => setRevoking(type), children: "Revoke" })) : null, _jsx(Button, { size: "sm", variant: "outline", "aria-label": `${k ? 'Rotate' : 'Create'} the ${type} key`, onPress: () => setMaking(type), children: k ? 'Rotate' : 'Create' })] }) }), type === 'publishable' && k?.key ? (_jsxs(XStack, { items: "center", gap: "$2", px: "$3", pb: "$2.5", children: [_jsx(SizableText, { flex: 1, minW: 0, size: "$1", color: "$soft", numberOfLines: 1, "aria-label": "Publishable key", style: { fontFamily: 'var(--f-mono, ui-monospace, monospace)' }, children: k.key }), _jsx(CopyButton, { value: k.key, label: "Copy publishable key" })] })) : null] }, type));
|
|
50
|
-
}) })), _jsx(Note, { children: note }), _jsx(Making, { type: making, rotating: making ? Boolean(held(making)) : false, onClose: () => setMaking(null), onMade: (m) => {
|
|
51
|
-
setNote(`${NAME[m.type]} created`);
|
|
43
|
+
return (_jsxs(YStack, { gap: "$5", children: [_jsx(Heading, { title: "API keys", detail: "Your keys for calling api.hanzo.ai from code. Make one for each app; revoking one stops only that one." }), _jsx(XStack, { gap: "$2", children: KINDS.map((type) => (_jsx(Button, { size: "sm", variant: "outline", "aria-label": `Create a ${type} key`, onPress: () => setMaking(type), children: `Create ${NAME[type].toLowerCase()}` }, type))) }), list.error ? (_jsx(Soft, { children: list.error.message })) : list.loading && list.value.length === 0 ? (_jsx(Soft, { children: "Reading your keys\u2026" })) : list.value.length === 0 ? (_jsx(Soft, { children: "No keys yet." })) : (_jsx(Card, { children: list.value.map((k, i) => (_jsxs(YStack, { borderTopWidth: i ? 1 : 0, borderColor: "$borderColor", opacity: k.status === 'active' ? 1 : 0.55, children: [_jsx(Row, { first: true, title: k.name || NAME[k.type], detail: about(k), trailing: k.status === 'revoked' ? null : (_jsx(Button, { size: "sm", variant: "ghost", "aria-label": `Revoke ${k.name || k.id}`, onPress: () => setRevoking(k), children: "Revoke" })) }), k.type === 'publishable' && k.key ? (_jsxs(XStack, { items: "center", gap: "$2", px: "$3", pb: "$2.5", children: [_jsx(SizableText, { flex: 1, minW: 0, size: "$1", color: "$soft", numberOfLines: 1, "aria-label": "Publishable key", style: { fontFamily: 'var(--f-mono, ui-monospace, monospace)' }, children: k.key }), _jsx(CopyButton, { value: k.key, label: "Copy publishable key" })] })) : null] }, k.id))) })), _jsx(Note, { children: note }), _jsx(Making, { type: making, onClose: () => setMaking(null), onMade: (m) => {
|
|
44
|
+
setNote(`${m.name || NAME[m.type]} created`);
|
|
52
45
|
list.reload();
|
|
53
|
-
} }), _jsx(Confirm, { open: revoking !== null, onOpenChange: (o) => !o && setRevoking(null), title: `Revoke
|
|
54
|
-
// It acts only while it is open, which is while a
|
|
46
|
+
} }), _jsx(Confirm, { open: revoking !== null, onOpenChange: (o) => !o && setRevoking(null), title: `Revoke ${revoking?.name || 'this key'}?`, says: "It stops working within a minute, and anything using it is refused. Your other keys keep working.", act: "Revoke",
|
|
47
|
+
// It acts only while it is open, which is while a key is chosen.
|
|
55
48
|
run: async () => {
|
|
56
|
-
await revoke(t, revoking);
|
|
57
|
-
setNote(`${NAME[revoking]} revoked`);
|
|
49
|
+
await revoke(t, revoking.id);
|
|
50
|
+
setNote(`${revoking.name || NAME[revoking.type]} revoked`);
|
|
58
51
|
list.reload();
|
|
59
52
|
} })] }));
|
|
60
53
|
}
|
|
61
|
-
/** Creating
|
|
62
|
-
function Making({ type,
|
|
54
|
+
/** Creating one key: a name and an optional limit, then the key itself, once. */
|
|
55
|
+
function Making({ type, onClose, onMade }) {
|
|
63
56
|
const t = useTarget();
|
|
57
|
+
const [name, setName] = useState('');
|
|
64
58
|
const [limit, setLimit] = useState('');
|
|
65
59
|
const [made, setMade] = useState(null);
|
|
66
60
|
const [working, setWorking] = useState(false);
|
|
@@ -68,6 +62,7 @@ function Making({ type, rotating, onClose, onMade }) {
|
|
|
68
62
|
const close = () => {
|
|
69
63
|
if (working)
|
|
70
64
|
return;
|
|
65
|
+
setName('');
|
|
71
66
|
setLimit('');
|
|
72
67
|
setMade(null);
|
|
73
68
|
setNote('');
|
|
@@ -86,7 +81,7 @@ function Making({ type, rotating, onClose, onMade }) {
|
|
|
86
81
|
}
|
|
87
82
|
setWorking(true);
|
|
88
83
|
try {
|
|
89
|
-
const m = await mint(t, type, list);
|
|
84
|
+
const m = await mint(t, type, name, list);
|
|
90
85
|
setMade(m);
|
|
91
86
|
onMade(m);
|
|
92
87
|
}
|
|
@@ -97,5 +92,5 @@ function Making({ type, rotating, onClose, onMade }) {
|
|
|
97
92
|
setWorking(false);
|
|
98
93
|
}
|
|
99
94
|
};
|
|
100
|
-
return (_jsx(Dialog, { open: type !== null, onOpenChange: (o) => !o && close(), children: _jsxs(DialogContent, { maxW: 480, showCloseButton: false, children: [_jsx(DialogTitle, { children: made ? `Your new ${made.type} key` :
|
|
95
|
+
return (_jsx(Dialog, { open: type !== null, onOpenChange: (o) => !o && close(), children: _jsxs(DialogContent, { maxW: 480, showCloseButton: false, children: [_jsx(DialogTitle, { children: made ? `Your new ${made.type} key` : `Create a ${type ?? ''} key` }), made ? (_jsxs(YStack, { gap: "$3", children: [made.type === 'secret' ? (_jsx(Once, { value: made.key, label: "Secret key" })) : (_jsxs(XStack, { items: "center", gap: "$2", px: "$3", py: "$3", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", bg: "$raised", children: [_jsx(SizableText, { flex: 1, minW: 0, size: "$2", color: "$ink", style: { fontFamily: 'var(--f-mono, ui-monospace, monospace)', wordBreak: 'break-all' }, children: made.key }), _jsx(CopyButton, { value: made.key, label: "Copy publishable key" })] })), made.limit.length ? (_jsxs(SizableText, { size: "$1", color: "$soft", children: ["It reaches ", made.limit.join(', '), " and nothing else."] })) : null, _jsx(XStack, { justify: "flex-end", children: _jsx(Button, { size: "sm", onPress: close, children: "Done" }) })] })) : (_jsxs(YStack, { gap: "$3", children: [_jsx(Field, { label: "Name", hint: "What it is for, so you can tell it from your other keys.", children: _jsx(Input, { value: name, onChangeText: setName, placeholder: "production", "aria-label": "Name" }) }), _jsx(Field, { label: "Limit", hint: "Optional. What it may reach, as kind:name \u2014 model:zen5, project:acme, product:train, read:* for read-only. Empty reaches whatever you do.", children: _jsx(Input, { value: limit, onChangeText: setLimit, placeholder: "model:zen5, project:acme", "aria-label": "Limit", autoCapitalize: "none" }) }), note ? (_jsx(SizableText, { size: "$1", color: "$soft", role: "status", children: note })) : null, _jsxs(XStack, { gap: "$2", justify: "flex-end", children: [_jsx(Button, { size: "sm", variant: "ghost", disabled: working, onPress: close, children: "Cancel" }), _jsx(Button, { size: "sm", disabled: working, onPress: () => void go(type), children: "Create" })] })] }))] }) }));
|
|
101
96
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hanzo/build",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.51",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0 OR MIT",
|
|
6
6
|
"description": "Hanzo Build — say what you want, watch it built, deployed and running. The builder as a component, mounted by the Hanzo app at hanzo.ai.",
|