@hanzo/build 0.2.49 → 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 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`/`DELETE /v1/account/keys` | your API keys |
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 own API keys: one secret key (sk-, it belongs on a server) and one
3
- * publishable key (pk-, safe in a page's source), at most one of each.
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 {keys: [{type, prefix, key?, limit?, createdAt}]}
6
- * POST /v1/account/keys {type, limit?} → {key, type, limit}, answered once
7
- * DELETE /v1/account/keys?type=… revoke that one
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 is rotating: a new key of a type ends the one before it. A secret key
10
- * is answered once and by its prefix after; a publishable key is public by
11
- * construction and always comes back whole. A limit only narrows what a key may
12
- * reach — `model:zen5`, `project:acme`, `product:commerce`, `model:*`.
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 last changed, as IAM records 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 of this type, ending the one before it. The answer is the only time a secret key is shown. */
40
- export declare function mint(t: Target, type: Kind, limit?: string[]): Promise<Minted>;
41
- export declare function revoke(t: Target, type: Kind): Promise<void>;
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 own API keys: one secret key (sk-, it belongs on a server) and one
3
- * publishable key (pk-, safe in a page's source), at most one of each.
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 {keys: [{type, prefix, key?, limit?, createdAt}]}
6
- * POST /v1/account/keys {type, limit?} → {key, type, limit}, answered once
7
- * DELETE /v1/account/keys?type=… revoke that one
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 is rotating: a new key of a type ends the one before it. A secret key
10
- * is answered once and by its prefix after; a publishable key is public by
11
- * construction and always comes back whole. A limit only narrows what a key may
12
- * reach — `model:zen5`, `project:acme`, `product:commerce`, `model:*`.
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, query } from './call.js';
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({ type, prefix: str(o.prefix), key: str(o.key), limit: list(o.limit), created: str(o.createdAt) });
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 of this type, ending the one before it. The answer is the only time a secret key is shown. */
46
- export async function mint(t, type, limit = []) {
47
- const r = await call(t, 'POST', '/v1/account/keys', limit.length ? { type, limit } : { type });
48
- const key = str(r?.key) || str(r?.accessKey);
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
- export async function revoke(t, type) {
54
- await call(t, 'DELETE', `/v1/account/keys${query({ type })}`);
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/choice.d.ts CHANGED
@@ -21,6 +21,28 @@ export declare const boardKey: (org: string | null) => string;
21
21
  export declare function pinCodebase(org: string | null, c: Codebase, ask?: string): void;
22
22
  /** Which board Issues is showing. '' is every board. */
23
23
  export declare function pinBoard(org: string | null, key: string): void;
24
+ /**
25
+ * A repository on GitHub, chosen from the org's installation: the run works there.
26
+ * Its `full_name` is GitHub's address, `github.com/owner/name` — what the list
27
+ * shows, so a forge codebase of the same name is never mistaken for it, and what
28
+ * a coding run takes to clone, push and propose on GitHub.
29
+ */
30
+ export interface HubRepo {
31
+ owner: string;
32
+ name: string;
33
+ full_name: string;
34
+ private: boolean;
35
+ default_branch: string;
36
+ pushed_at: string;
37
+ installation_id: number;
38
+ github: true;
39
+ }
40
+ export declare const isHub: (repo: {
41
+ github?: boolean;
42
+ full_name?: string;
43
+ } | null) => repo is HubRepo;
44
+ /** A GitHub listing row as the composer offers it. */
45
+ export declare const asHub: (r: Omit<HubRepo, 'github'>) => HubRepo;
24
46
  export declare const isForge: (repo: {
25
47
  forge?: boolean;
26
48
  name?: string;
package/lib/choice.js CHANGED
@@ -41,6 +41,9 @@ export function pinBoard(org, key) {
41
41
  }
42
42
  window.dispatchEvent(new CustomEvent('hanzo-board', { detail: key }));
43
43
  }
44
+ export const isHub = (repo) => Boolean(repo && repo.github === true && repo.full_name?.startsWith('github.com/'));
45
+ /** A GitHub listing row as the composer offers it. */
46
+ export const asHub = (r) => ({ ...r, full_name: `github.com/${r.owner}/${r.name}`, github: true });
44
47
  export const isForge = (repo) => Boolean(repo && repo.forge === true && repo.name);
45
48
  const syncingKey = (org) => `hanzo.build.sync.${org ?? 'none'}`;
46
49
  export function readPending(org) {
package/lib/landing.js CHANGED
@@ -23,12 +23,13 @@ import { useLimits } from '@hanzo/ui/product/useLimits';
23
23
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
24
24
  import { start, unhonoured } from './api/coding.js';
25
25
  import { asRepo, chosen, codebases, one } from './api/codebases.js';
26
+ import { repos as githubRepos } from './api/github.js';
26
27
  import { read, SETUP } from './api/environment.js';
27
28
  import { SetupDialog } from './environment.js';
28
29
  import { ENSO, limits as readLimits, models } from './api/models.js';
29
30
  import { usePick } from './pick.js';
30
31
  import { ready, SANDBOX } from './api/places.js';
31
- import { isForge } from './choice.js';
32
+ import { asHub, isForge, isHub } from './choice.js';
32
33
  import { useKept, usePlaces, useRead } from './data.js';
33
34
  import { useHost, useTarget } from './host.js';
34
35
  import { pane } from './pane.js';
@@ -111,12 +112,20 @@ export function Landing({ onStarted }) {
111
112
  const env = useRead(signed && codebase ? () => read(t, codebase) : null, null, [t, signed, codebase]);
112
113
  const place = places.value.find((p) => p.id === kept.place) ?? SANDBOX;
113
114
  const known = useRef(new Map());
115
+ const hubs = useRef(new Map());
116
+ // The forge's codebases, then the repositories the org's GitHub installation
117
+ // grants. A GitHub row is chosen as GitHub's address, so a run on it clones,
118
+ // pushes and proposes on GitHub, where its people work. A GitHub read that
119
+ // fails leaves the forge's list standing.
114
120
  const loadRepos = useCallback(async (q) => {
115
- const page = await codebases(t, q);
121
+ const [page, hub] = await Promise.all([codebases(t, q), githubRepos(t, { q }).catch(() => null)]);
116
122
  const repos = page.map(asRepo);
117
123
  for (const r of repos)
118
124
  known.current.set(r.name, r);
119
- return { repos, next: null };
125
+ const github = (hub?.repos ?? []).map(asHub);
126
+ for (const r of github)
127
+ hubs.current.set(r.full_name.toLowerCase(), r);
128
+ return { repos: [...repos, ...github], next: null };
120
129
  }, [t]);
121
130
  // Drawn only once a forge codebase is chosen; one the forge no longer lists offers its default branch.
122
131
  const home = kept.repo?.default_branch || 'main';
@@ -134,7 +143,8 @@ export function Landing({ onStarted }) {
134
143
  return;
135
144
  }
136
145
  const repo = isForge(kept.repo) ? kept.repo : null;
137
- if (!repo && place.id) {
146
+ const hub = isHub(kept.repo) ? kept.repo : null;
147
+ if (!repo && !hub && place.id) {
138
148
  setNote(`A run on ${place.label} works in its own checkout: choose the codebase, or run it in Cloud.`);
139
149
  return;
140
150
  }
@@ -146,8 +156,8 @@ export function Landing({ onStarted }) {
146
156
  // The name alone. The org rides the request, and a slash is not a repo name.
147
157
  // No codebase is not sent, and the run starts a new project.
148
158
  // An empty base or place is not sent: the default branch, and the sandbox.
149
- repo: repo?.name,
150
- base: repo ? kept.branch : undefined,
159
+ repo: repo?.name ?? hub?.full_name,
160
+ base: repo ? kept.branch : hub ? kept.branch || hub.default_branch : undefined,
151
161
  targetId: place.id,
152
162
  mode: kept.mode,
153
163
  model: model === ENSO ? undefined : model,
@@ -195,13 +205,18 @@ export function Landing({ onStarted }) {
195
205
  place: p,
196
206
  })), [places.value, setUp]);
197
207
  const branch = kept.branch || kept.repo?.default_branch || 'main';
198
- const head = (_jsxs(XStack, { gap: 6, items: "center", flexWrap: "wrap", children: [_jsx(ChipSelect, { name: "Where the run runs", icon: place.id ? _jsx(Monitor, { size: 13 }) : _jsx(Cloud, { size: 13 }), label: place.id ? place.label : 'Cloud', chosen: placeItems.find((i) => i.place.id === place.id), items: placeItems, onChange: (i) => set({ place: i.place.id }), placeholder: "Search machines\u2026", loading: places.loading, error: places.error?.message ?? null, footer: _jsxs(YStack, { gap: "$0.5", children: [_jsx(SizableText, { size: "$1", color: "$ink", children: "Set up remote control" }), _jsxs(SizableText, { size: "$1", color: "$soft", children: ["Run", ' ', _jsx(SizableText, { size: "$1", color: "$ink", style: { fontFamily: 'var(--f-mono, ui-monospace, monospace)' }, children: "hanzo link" }), ' ', "on a machine: it joins this list, and a run there uses its checkout and credentials."] })] }) }), _jsx(RepoSelect, { open: picking, onOpenChange: setPicking, value: isForge(kept.repo) ? kept.repo : null, onChange: (r) => {
208
+ const head = (_jsxs(XStack, { gap: 6, items: "center", flexWrap: "wrap", children: [_jsx(ChipSelect, { name: "Where the run runs", icon: place.id ? _jsx(Monitor, { size: 13 }) : _jsx(Cloud, { size: 13 }), label: place.id ? place.label : 'Cloud', chosen: placeItems.find((i) => i.place.id === place.id), items: placeItems, onChange: (i) => set({ place: i.place.id }), placeholder: "Search machines\u2026", loading: places.loading, error: places.error?.message ?? null, footer: _jsxs(YStack, { gap: "$0.5", children: [_jsx(SizableText, { size: "$1", color: "$ink", children: "Set up remote control" }), _jsxs(SizableText, { size: "$1", color: "$soft", children: ["Run", ' ', _jsx(SizableText, { size: "$1", color: "$ink", style: { fontFamily: 'var(--f-mono, ui-monospace, monospace)' }, children: "hanzo link" }), ' ', "on a machine: it joins this list, and a run there uses its checkout and credentials."] })] }) }), _jsx(RepoSelect, { open: picking, onOpenChange: setPicking, value: isForge(kept.repo) || isHub(kept.repo) ? kept.repo : null, onChange: (r) => {
209
+ const hub = r.github ? hubs.current.get((r.full_name ?? "").toLowerCase()) : undefined;
210
+ if (hub) {
211
+ set({ repo: hub, branch: hub.default_branch });
212
+ return;
213
+ }
199
214
  const row = known.current.get(r.name) ?? chosen(r);
200
215
  set({ repo: row, branch: row.default_branch });
201
216
  }, load: loadRepos, troubleshoot: {
202
217
  label: 'Missing a GitHub repository? Bring it onto the forge',
203
218
  onPress: () => host.go(path({ kind: 'screen', screen: 'sync' })),
204
- }, connect: _jsx(XStack, { justify: "flex-end", children: _jsx(FooterLink, { label: "Refresh list", onPress: () => setRound((n) => n + 1) }) }), note: "Repositories on the forge. Type to search.", action: {
219
+ }, connect: _jsx(XStack, { justify: "flex-end", children: _jsx(FooterLink, { label: "Refresh list", onPress: () => setRound((n) => n + 1) }) }), note: "Repositories on the forge and on your organization's GitHub. Type to search.", action: {
205
220
  label: 'Add to project',
206
221
  onPress: (r) => {
207
222
  const row = known.current.get(r.name);
@@ -1,9 +1,10 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  /**
3
- * API keys: your own keys for calling the Hanzo API from code — one secret key
4
- * for a server, one publishable key for a page's source. Creating one ends the
5
- * one before it, and a secret key is shown once, when it is made. A limit
6
- * narrows what a key reaches; it never reaches further than you do.
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
- const FOR = {
19
- secret: 'For a server. It acts as you, so it never goes in a page.',
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
- const held = (type) => list.value.find((k) => k.type === type);
47
- return (_jsxs(YStack, { gap: "$5", children: [_jsx(Heading, { title: "API keys", detail: "Your own keys for calling api.hanzo.ai from code. You hold one of each; creating one ends the one before it." }), list.error ? (_jsx(Soft, { children: list.error.message })) : list.loading && list.value.length === 0 ? (_jsx(Soft, { children: "Reading your keys\u2026" })) : (_jsx(Card, { children: KINDS.map((type, i) => {
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 your ${revoking ?? 'secret'} key?`, says: "It stops working within a minute, and anything using it is refused until it has a new one.", act: "Revoke",
54
- // It acts only while it is open, which is while a type is chosen.
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 or rotating one key: an optional limit, then the key itself, once. */
62
- function Making({ type, rotating, onClose, onMade }) {
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` : `${rotating ? 'Rotate' : 'Create'} your ${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: [rotating ? (_jsxs(SizableText, { size: "$2", color: "$soft", children: ["The ", type, " key you have now stops working."] })) : null, _jsx(Field, { label: "Limit", hint: "Optional. What it may reach, as kind:name \u2014 model:zen5, project:acme, product:commerce. 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: rotating ? 'Rotate' : 'Create' })] })] }))] }) }));
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.49",
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.",