@hanzo/build 0.2.9 → 0.2.11
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 +126 -23
- package/lib/api/agents.d.ts +80 -0
- package/lib/api/agents.js +148 -0
- package/lib/api/billing.d.ts +205 -0
- package/lib/api/billing.js +300 -0
- package/lib/api/call.d.ts +6 -0
- package/lib/api/call.js +11 -0
- package/lib/api/capabilities.d.ts +25 -0
- package/lib/api/capabilities.js +40 -0
- package/lib/api/changes.d.ts +56 -0
- package/lib/api/changes.js +63 -0
- package/lib/api/coding.d.ts +42 -4
- package/lib/api/coding.js +62 -3
- package/lib/api/connectors.d.ts +90 -0
- package/lib/api/connectors.js +109 -0
- package/lib/api/consent.d.ts +22 -0
- package/lib/api/consent.js +28 -0
- package/lib/api/environment.d.ts +57 -0
- package/lib/api/environment.js +85 -0
- package/lib/api/git.d.ts +6 -2
- package/lib/api/git.js +13 -5
- package/lib/api/github.d.ts +13 -0
- package/lib/api/github.js +9 -0
- package/lib/api/harness.d.ts +44 -0
- package/lib/api/harness.js +348 -0
- package/lib/api/keys.d.ts +41 -0
- package/lib/api/keys.js +55 -0
- package/lib/api/machines.d.ts +56 -0
- package/lib/api/machines.js +84 -0
- package/lib/api/members.d.ts +47 -0
- package/lib/api/members.js +88 -0
- package/lib/api/memory.d.ts +25 -0
- package/lib/api/memory.js +34 -0
- package/lib/api/places.d.ts +2 -2
- package/lib/api/places.js +3 -26
- package/lib/api/plugins.d.ts +55 -0
- package/lib/api/plugins.js +62 -0
- package/lib/api/pref.d.ts +49 -0
- package/lib/api/pref.js +68 -0
- package/lib/api/profile.d.ts +21 -0
- package/lib/api/profile.js +40 -0
- package/lib/api/projects.d.ts +19 -2
- package/lib/api/projects.js +29 -4
- package/lib/api/provider.d.ts +42 -0
- package/lib/api/provider.js +58 -0
- package/lib/api/sandbox.d.ts +30 -0
- package/lib/api/sandbox.js +49 -0
- package/lib/api/sessions.d.ts +39 -4
- package/lib/api/sessions.js +58 -6
- package/lib/api/skills.d.ts +70 -0
- package/lib/api/skills.js +90 -0
- package/lib/api/tools.d.ts +34 -0
- package/lib/api/tools.js +37 -0
- package/lib/api/turn.d.ts +84 -16
- package/lib/api/turn.js +224 -4
- package/lib/api/webhooks.d.ts +60 -0
- package/lib/api/webhooks.js +81 -0
- package/lib/ask.d.ts +18 -0
- package/lib/ask.js +69 -0
- package/lib/builder.js +39 -19
- package/lib/customize/agents.d.ts +2 -0
- package/lib/customize/agents.js +125 -0
- package/lib/customize/connectors.d.ts +2 -0
- package/lib/customize/connectors.js +197 -0
- package/lib/customize/index.d.ts +6 -0
- package/lib/customize/index.js +50 -0
- package/lib/customize/plugins.d.ts +2 -0
- package/lib/customize/plugins.js +93 -0
- package/lib/customize/skills.d.ts +2 -0
- package/lib/customize/skills.js +125 -0
- package/lib/customize/ui.d.ts +110 -0
- package/lib/customize/ui.js +99 -0
- package/lib/desk.d.ts +8 -1
- package/lib/desk.js +29 -35
- package/lib/door.d.ts +7 -0
- package/lib/door.js +89 -0
- package/lib/environment.d.ts +15 -0
- package/lib/environment.js +132 -0
- package/lib/files.d.ts +14 -0
- package/lib/files.js +111 -0
- package/lib/find.d.ts +7 -0
- package/lib/find.js +77 -0
- package/lib/foot.d.ts +16 -0
- package/lib/foot.js +36 -0
- package/lib/forge.js +8 -4
- package/lib/git.d.ts +9 -0
- package/lib/git.js +74 -0
- package/lib/host.d.ts +5 -0
- package/lib/landing.d.ts +10 -0
- package/lib/landing.js +47 -14
- package/lib/markdown.d.ts +49 -0
- package/lib/markdown.js +123 -0
- package/lib/plans.d.ts +1 -0
- package/lib/plans.js +113 -0
- package/lib/prefs.d.ts +30 -0
- package/lib/prefs.js +83 -0
- package/lib/prose.d.ts +4 -0
- package/lib/prose.js +53 -0
- package/lib/route.d.ts +16 -1
- package/lib/route.js +32 -1
- package/lib/run.js +207 -28
- package/lib/section.js +2 -2
- package/lib/settings/account.d.ts +5 -0
- package/lib/settings/account.js +111 -0
- package/lib/settings/billing.d.ts +1 -0
- package/lib/settings/billing.js +104 -0
- package/lib/settings/capabilities.d.ts +1 -0
- package/lib/settings/capabilities.js +55 -0
- package/lib/settings/card.d.ts +31 -0
- package/lib/settings/card.js +105 -0
- package/lib/settings/code.d.ts +1 -0
- package/lib/settings/code.js +51 -0
- package/lib/settings/environments.d.ts +1 -0
- package/lib/settings/environments.js +53 -0
- package/lib/settings/general.d.ts +1 -0
- package/lib/settings/general.js +44 -0
- package/lib/settings/index.d.ts +4 -0
- package/lib/settings/index.js +22 -0
- package/lib/settings/integrations.d.ts +1 -0
- package/lib/settings/integrations.js +81 -0
- package/lib/settings/keys.d.ts +1 -0
- package/lib/settings/keys.js +102 -0
- package/lib/settings/machines.d.ts +1 -0
- package/lib/settings/machines.js +118 -0
- package/lib/settings/members.d.ts +1 -0
- package/lib/settings/members.js +67 -0
- package/lib/settings/memory.d.ts +1 -0
- package/lib/settings/memory.js +56 -0
- package/lib/settings/notifications.d.ts +1 -0
- package/lib/settings/notifications.js +97 -0
- package/lib/settings/privacy.d.ts +1 -0
- package/lib/settings/privacy.js +63 -0
- package/lib/settings/sections.d.ts +16 -0
- package/lib/settings/sections.js +32 -0
- package/lib/settings/ui.d.ts +49 -0
- package/lib/settings/ui.js +47 -0
- package/lib/settings/usage.d.ts +1 -0
- package/lib/settings/usage.js +114 -0
- package/lib/shelf.d.ts +6 -0
- package/lib/shelf.js +86 -16
- package/lib/switch.js +1 -2
- package/lib/transcript.d.ts +12 -0
- package/lib/transcript.js +56 -0
- package/lib/voice.d.ts +3 -1
- package/lib/voice.js +10 -4
- package/package.json +1 -1
- package/lib/account.d.ts +0 -14
- package/lib/account.js +0 -38
- package/lib/e2e/site.spec.d.ts +0 -1
- package/lib/e2e/site.spec.js +0 -125
- package/lib/mcp.d.ts +0 -1
- package/lib/mcp.js +0 -28
package/lib/route.d.ts
CHANGED
|
@@ -4,13 +4,22 @@
|
|
|
4
4
|
* '' the empty state: a new run
|
|
5
5
|
* sess_<32 hex> one run
|
|
6
6
|
* -/<screen> a builder screen
|
|
7
|
+
* -/settings[/<section>] a section of Settings
|
|
8
|
+
* -/customize[/<tab>] skills, connectors and plugins: what the agent brings to a run
|
|
9
|
+
* -/plans the plans an organization can be on
|
|
7
10
|
* <slug> a project's workspace
|
|
8
11
|
*
|
|
9
12
|
* Unambiguous by construction: a project slug is `^[a-z0-9]([a-z0-9-]{0,38}[a-z0-9])?$`,
|
|
10
13
|
* so it can hold neither the `_` a session id carries nor start with `-`.
|
|
11
14
|
* Anything else is not an address here and reads as the empty state.
|
|
12
15
|
*/
|
|
13
|
-
export type Screen = 'artifacts' | 'templates' | 'codebases' | 'projects' | 'issues' | 'automations' | 'sync' | 'mcp';
|
|
16
|
+
export type Screen = 'artifacts' | 'templates' | 'codebases' | 'projects' | 'issues' | 'automations' | 'sync' | 'mcp' | 'plans';
|
|
17
|
+
/** Settings, one section each. Every setting the builder has lives on this page, none elsewhere. */
|
|
18
|
+
export declare const SECTIONS: readonly ['general', 'account', 'privacy', 'billing', 'usage', 'capabilities', 'memory', 'code', 'environments', 'machines', 'keys', 'members', 'integrations', 'notifications'];
|
|
19
|
+
export type Section = (typeof SECTIONS)[number];
|
|
20
|
+
/** Customize, one tab each: what the agent brings to a run, yours and to discover. */
|
|
21
|
+
export declare const TABS: readonly ['skills', 'connectors', 'plugins', 'agents'];
|
|
22
|
+
export type Tab = (typeof TABS)[number];
|
|
14
23
|
export type Route = {
|
|
15
24
|
kind: 'new';
|
|
16
25
|
} | {
|
|
@@ -19,6 +28,12 @@ export type Route = {
|
|
|
19
28
|
} | {
|
|
20
29
|
kind: 'screen';
|
|
21
30
|
screen: Screen;
|
|
31
|
+
} | {
|
|
32
|
+
kind: 'settings';
|
|
33
|
+
section: Section;
|
|
34
|
+
} | {
|
|
35
|
+
kind: 'customize';
|
|
36
|
+
tab: Tab;
|
|
22
37
|
} | {
|
|
23
38
|
kind: 'project';
|
|
24
39
|
slug: string;
|
package/lib/route.js
CHANGED
|
@@ -1,10 +1,37 @@
|
|
|
1
|
+
/** Settings, one section each. Every setting the builder has lives on this page, none elsewhere. */
|
|
2
|
+
export const SECTIONS = [
|
|
3
|
+
'general',
|
|
4
|
+
'account',
|
|
5
|
+
'privacy',
|
|
6
|
+
'billing',
|
|
7
|
+
'usage',
|
|
8
|
+
'capabilities',
|
|
9
|
+
'memory',
|
|
10
|
+
'code',
|
|
11
|
+
'environments',
|
|
12
|
+
'machines',
|
|
13
|
+
'keys',
|
|
14
|
+
'members',
|
|
15
|
+
'integrations',
|
|
16
|
+
'notifications',
|
|
17
|
+
];
|
|
18
|
+
/** Customize, one tab each: what the agent brings to a run, yours and to discover. */
|
|
19
|
+
export const TABS = ['skills', 'connectors', 'plugins', 'agents'];
|
|
1
20
|
export const SESSION = /^sess_[0-9a-f]{32}$/;
|
|
2
21
|
export const SLUG = /^[a-z0-9]([a-z0-9-]{0,38}[a-z0-9])?$/;
|
|
3
|
-
const SCREENS = ['artifacts', 'templates', 'codebases', 'projects', 'issues', 'automations', 'sync', 'mcp'];
|
|
22
|
+
const SCREENS = ['artifacts', 'templates', 'codebases', 'projects', 'issues', 'automations', 'sync', 'mcp', 'plans'];
|
|
4
23
|
export function route(path) {
|
|
5
24
|
const p = path.replace(/^\/+|\/+$/g, '');
|
|
6
25
|
if (SESSION.test(p))
|
|
7
26
|
return { kind: 'run', id: p };
|
|
27
|
+
if (p === '-/settings' || p.startsWith('-/settings/')) {
|
|
28
|
+
const section = (p.slice('-/settings/'.length) || 'general');
|
|
29
|
+
return SECTIONS.includes(section) ? { kind: 'settings', section } : { kind: 'new' };
|
|
30
|
+
}
|
|
31
|
+
if (p === '-/customize' || p.startsWith('-/customize/')) {
|
|
32
|
+
const tab = (p.slice('-/customize/'.length) || 'skills');
|
|
33
|
+
return TABS.includes(tab) ? { kind: 'customize', tab } : { kind: 'new' };
|
|
34
|
+
}
|
|
8
35
|
if (p.startsWith('-/')) {
|
|
9
36
|
const s = p.slice(2);
|
|
10
37
|
return SCREENS.includes(s) ? { kind: 'screen', screen: s } : { kind: 'new' };
|
|
@@ -22,6 +49,10 @@ export function path(r) {
|
|
|
22
49
|
return r.id;
|
|
23
50
|
case 'screen':
|
|
24
51
|
return `-/${r.screen}`;
|
|
52
|
+
case 'settings':
|
|
53
|
+
return r.section === 'general' ? '-/settings' : `-/settings/${r.section}`;
|
|
54
|
+
case 'customize':
|
|
55
|
+
return r.tab === 'skills' ? '-/customize' : `-/customize/${r.tab}`;
|
|
25
56
|
case 'project':
|
|
26
57
|
return r.slug;
|
|
27
58
|
}
|
package/lib/run.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
/**
|
|
3
|
-
* One run, live: its transcript as it streams, the
|
|
4
|
-
*
|
|
3
|
+
* One run, live: its transcript as it streams, the controls that reach it —
|
|
4
|
+
* steer, pause, resume, stop — the pull request once it pushes one, and the
|
|
5
|
+
* follow-up that continues it once it has finished.
|
|
5
6
|
*
|
|
6
7
|
* The detail read is the record and the stream adds to it; the two overlap
|
|
7
8
|
* whenever a turn lands between the read and the subscribe, so turns are merged
|
|
@@ -10,19 +11,26 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
10
11
|
*
|
|
11
12
|
* Steering is a queue the run drains between steps: a press is confirmed as
|
|
12
13
|
* RECORDED, and the status is left to the feed to correct.
|
|
14
|
+
*
|
|
15
|
+
* A sandbox run is one agent invocation that takes its task on its command line
|
|
16
|
+
* (apps/coding steer.go), so what continues it is a new run: a paused run is
|
|
17
|
+
* carried on, and a finished one followed up, by `POST /v1/agent/coding` from
|
|
18
|
+
* the branch its work was kept on — the platform's own continue, done here.
|
|
13
19
|
*/
|
|
14
20
|
import { SizableText, XStack, YStack } from '@hanzo/gui';
|
|
15
|
-
import { ExternalLink, GitPullRequest, PanelRight } from '@hanzogui/lucide-icons-2';
|
|
16
|
-
import { Button } from '@hanzo/ui';
|
|
17
|
-
import {
|
|
21
|
+
import { Copy, ExternalLink, GitPullRequest, Globe, MoreHorizontal, PanelRight, Pencil } from '@hanzogui/lucide-icons-2';
|
|
22
|
+
import { Button, Dialog, DialogContent, DialogTitle, DropdownMenu, Input } from '@hanzo/ui';
|
|
23
|
+
import { Steer } from '@hanzo/ui/agents';
|
|
18
24
|
import { Composer } from '@hanzo/ui/chat';
|
|
19
25
|
import { useEffect, useMemo, useState } from 'react';
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
26
|
+
import { approve, followUp, start } from './api/coding.js';
|
|
27
|
+
import { list, message, pause, publish, rename, resume, stop, story, took } from './api/sessions.js';
|
|
28
|
+
import { answer, cards, outcome, pull, settled, steps } from './api/turn.js';
|
|
29
|
+
import { useKept, useRead, useRun } from './data.js';
|
|
23
30
|
import { Desk } from './desk.js';
|
|
24
31
|
import { useHost, useTarget } from './host.js';
|
|
25
32
|
import { Out } from './out.js';
|
|
33
|
+
import { Transcript } from './transcript.js';
|
|
26
34
|
const LIVE = new Set(['running', 'paused', '']);
|
|
27
35
|
export function Run({ id }) {
|
|
28
36
|
const host = useHost();
|
|
@@ -35,15 +43,49 @@ export function Run({ id }) {
|
|
|
35
43
|
const [busy, setBusy] = useState(false);
|
|
36
44
|
const [notify, setNotify] = useState(false);
|
|
37
45
|
const [desk, setDesk] = useKept('hanzo.build.desk', true);
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
46
|
+
// Stop was pressed here: the run keeps its work after it answers, and says so with its status.
|
|
47
|
+
const [stopping, setStopping] = useState(false);
|
|
48
|
+
const [naming, setNaming] = useState(false);
|
|
49
|
+
const [name, setName] = useState('');
|
|
50
|
+
const [copied, setCopied] = useState('');
|
|
51
|
+
const mode = record?.mode ?? '';
|
|
52
|
+
const shown = useMemo(() => cards(events, mode), [events, mode]);
|
|
41
53
|
const end = outcome(events);
|
|
42
54
|
const plan = useMemo(() => steps(events), [events]);
|
|
55
|
+
const kept = useMemo(() => settled(events), [events]);
|
|
43
56
|
const state = status || end.status;
|
|
44
57
|
const pr = pull(record?.pr ?? '', record?.repo ?? '');
|
|
45
|
-
const
|
|
46
|
-
const
|
|
58
|
+
const live = LIVE.has(state);
|
|
59
|
+
const running = signed && live && !detail.error;
|
|
60
|
+
const paused = running && state === 'paused';
|
|
61
|
+
const finished = signed && !live && !detail.error && Boolean(record);
|
|
62
|
+
// A sandbox run is held by the coding service, which pauses it with its work
|
|
63
|
+
// kept and has no resume of its own; a machine drains its commands itself.
|
|
64
|
+
const held = (record?.environment || 'sandbox') === 'sandbox';
|
|
65
|
+
// A follow-up clones what the run kept, so it waits until the run has said where that is.
|
|
66
|
+
const waiting = (paused && held && !kept.settled) || (finished && stopping && !kept.settled);
|
|
67
|
+
const title = record?.title || detail.value?.title || (detail.loading ? '' : 'Untitled run');
|
|
68
|
+
// A setup run explores and installs before it answers, which takes minutes.
|
|
69
|
+
const setup = mode === 'setup';
|
|
70
|
+
const [seen, setSeen] = useKept('hanzo.build.setup.seen', false);
|
|
71
|
+
// How long setup runs have taken here: the ones this person can see, finished.
|
|
72
|
+
const past = useRead(setup && running ? () => list(t, { kind: 'coding', status: 'done', limit: 500 }) : null, [], [t, setup, running]);
|
|
73
|
+
const span = took(past.value, 'setup');
|
|
74
|
+
const earlier = record
|
|
75
|
+
? {
|
|
76
|
+
id,
|
|
77
|
+
title,
|
|
78
|
+
repo: record.repo,
|
|
79
|
+
base: record.base,
|
|
80
|
+
environment: record.environment,
|
|
81
|
+
project: record.project,
|
|
82
|
+
mode,
|
|
83
|
+
pushed: kept.pushed,
|
|
84
|
+
}
|
|
85
|
+
: null;
|
|
86
|
+
const planned = mode === 'plan' && finished && state === 'done' ? answer(events) : '';
|
|
87
|
+
// What the recorded read left out: it carries the latest fifty turns.
|
|
88
|
+
const hidden = Math.max(0, (detail.value?.events ?? 0) - (detail.value?.recent.length ?? 0));
|
|
47
89
|
useEffect(() => {
|
|
48
90
|
if (!notify || running)
|
|
49
91
|
return;
|
|
@@ -66,35 +108,172 @@ export function Run({ id }) {
|
|
|
66
108
|
setNotify(true);
|
|
67
109
|
setNote('');
|
|
68
110
|
};
|
|
69
|
-
|
|
70
|
-
|
|
111
|
+
/** Something that answers a sentence for the note, or throws the reason. */
|
|
112
|
+
const act = async (work, failed) => {
|
|
113
|
+
if (busy)
|
|
71
114
|
return;
|
|
72
115
|
setBusy(true);
|
|
73
116
|
setNote('');
|
|
74
117
|
try {
|
|
75
|
-
await
|
|
76
|
-
setDraft('');
|
|
77
|
-
setNote('Sent — the run reads it before its next step');
|
|
118
|
+
setNote(await work());
|
|
78
119
|
}
|
|
79
120
|
catch (e) {
|
|
80
|
-
setNote(e instanceof Error ? e.message :
|
|
121
|
+
setNote(e instanceof Error ? e.message : failed);
|
|
81
122
|
}
|
|
82
123
|
finally {
|
|
83
124
|
setBusy(false);
|
|
84
125
|
}
|
|
85
126
|
};
|
|
86
|
-
|
|
87
|
-
|
|
127
|
+
/** A new run from this one's work, opened. A paused run it carries on is let go once the new one is admitted. */
|
|
128
|
+
const carry = async (said, how = 'follow') => {
|
|
129
|
+
if (!earlier)
|
|
130
|
+
throw new Error('This run is still being read');
|
|
131
|
+
const run = await start(t, how === 'approve' ? approve(earlier, said) : followUp(earlier, said));
|
|
132
|
+
if (paused)
|
|
133
|
+
await stop(t, id, 'Continued in a follow-up run').catch(() => undefined);
|
|
134
|
+
setDraft('');
|
|
135
|
+
host.go(run.session);
|
|
136
|
+
return '';
|
|
137
|
+
};
|
|
138
|
+
const send = () => {
|
|
139
|
+
const words = draft.trim();
|
|
140
|
+
if (!words)
|
|
141
|
+
return;
|
|
142
|
+
if (running && !(paused && held)) {
|
|
143
|
+
void act(async () => {
|
|
144
|
+
await message(t, id, words);
|
|
145
|
+
setDraft('');
|
|
146
|
+
return 'Sent — recorded on this run';
|
|
147
|
+
}, 'Could not reach this run');
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
void act(() => carry(words), 'The follow-up could not start');
|
|
151
|
+
};
|
|
152
|
+
const command = (c) => {
|
|
153
|
+
switch (c) {
|
|
154
|
+
case 'stop':
|
|
155
|
+
void act(async () => {
|
|
156
|
+
await stop(t, id);
|
|
157
|
+
setStopping(true);
|
|
158
|
+
return 'Stop requested — the run keeps its work on its branch';
|
|
159
|
+
}, 'Could not stop this run');
|
|
160
|
+
return;
|
|
161
|
+
case 'pause':
|
|
162
|
+
void act(async () => {
|
|
163
|
+
await pause(t, id);
|
|
164
|
+
return held ? 'Pause requested — the run keeps its work on its branch and waits' : 'Pause asked — the machine pauses when it reads it';
|
|
165
|
+
}, 'Could not pause this run');
|
|
166
|
+
return;
|
|
167
|
+
case 'resume':
|
|
168
|
+
if (held)
|
|
169
|
+
void act(() => carry(''), 'The run could not go on');
|
|
170
|
+
else
|
|
171
|
+
void act(async () => {
|
|
172
|
+
await resume(t, id);
|
|
173
|
+
return 'Resume asked — the machine goes on when it reads it';
|
|
174
|
+
}, 'Could not resume this run');
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
const copy = async (what, value) => {
|
|
88
179
|
try {
|
|
89
|
-
await
|
|
90
|
-
|
|
180
|
+
await navigator.clipboard.writeText(value);
|
|
181
|
+
setCopied(what);
|
|
91
182
|
}
|
|
92
|
-
catch
|
|
93
|
-
setNote(
|
|
183
|
+
catch {
|
|
184
|
+
setNote('This browser would not copy.');
|
|
94
185
|
}
|
|
95
186
|
};
|
|
96
|
-
|
|
97
|
-
|
|
187
|
+
const share = (on) => act(async () => {
|
|
188
|
+
await publish(t, id, on);
|
|
189
|
+
detail.reload();
|
|
190
|
+
return on ? 'Shared — anyone with the link can read this run’s story' : 'No longer shared';
|
|
191
|
+
}, 'Could not change who can read this run');
|
|
192
|
+
const link = record ? story(t, record.org || host.org || '', record.project) : '';
|
|
193
|
+
const menu = [
|
|
194
|
+
...(signed && record
|
|
195
|
+
? [
|
|
196
|
+
{
|
|
197
|
+
key: 'rename',
|
|
198
|
+
label: 'Rename',
|
|
199
|
+
icon: _jsx(Pencil, { size: 16 }),
|
|
200
|
+
onSelect: () => {
|
|
201
|
+
setName(title);
|
|
202
|
+
setNaming(true);
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
]
|
|
206
|
+
: []),
|
|
207
|
+
// Sharing opens the public build route, which is addressed by the project a run built.
|
|
208
|
+
...(signed && record?.project
|
|
209
|
+
? [
|
|
210
|
+
record.published
|
|
211
|
+
? { key: 'unshare', label: 'Stop sharing', icon: _jsx(Globe, { size: 16 }), onSelect: () => void share(false) }
|
|
212
|
+
: { key: 'share', label: 'Share publicly', icon: _jsx(Globe, { size: 16 }), description: 'Anyone with the link can read it', onSelect: () => void share(true) },
|
|
213
|
+
]
|
|
214
|
+
: []),
|
|
215
|
+
...(record?.published && link
|
|
216
|
+
? [{ key: 'link', label: copied === 'link' ? 'Copied' : 'Copy public link', icon: _jsx(Copy, { size: 16 }), description: link, onSelect: () => void copy('link', link) }]
|
|
217
|
+
: []),
|
|
218
|
+
{ key: 'copy', label: copied === 'id' ? 'Copied' : 'Copy run id', icon: _jsx(Copy, { size: 16 }), description: id, onSelect: () => void copy('id', id) },
|
|
219
|
+
];
|
|
220
|
+
const save = () => void act(async () => {
|
|
221
|
+
await rename(t, id, name);
|
|
222
|
+
setNaming(false);
|
|
223
|
+
detail.reload();
|
|
224
|
+
return 'Renamed';
|
|
225
|
+
}, 'Could not rename this run');
|
|
226
|
+
const placeholder = !signed
|
|
227
|
+
? 'Sign in to follow up'
|
|
228
|
+
: waiting
|
|
229
|
+
? paused
|
|
230
|
+
? 'Pausing — the run is keeping its work…'
|
|
231
|
+
: 'Stopping — the run is keeping its work…'
|
|
232
|
+
: paused && held
|
|
233
|
+
? 'Continue this run with a follow up'
|
|
234
|
+
: running
|
|
235
|
+
? setup
|
|
236
|
+
? 'Add a follow up for the setup agent'
|
|
237
|
+
: 'Add a follow up'
|
|
238
|
+
: finished
|
|
239
|
+
? 'Follow up — continues in a new run'
|
|
240
|
+
: 'Reading this run…';
|
|
241
|
+
return (_jsxs(XStack, { flex: 1, minH: 0, minW: 0, width: "100%", children: [_jsxs(YStack, { flex: 1, minH: 0, minW: 0, width: "100%", px: "$6", "$max-md": { px: '$4' }, children: [_jsxs(XStack, { pt: "$3", pb: "$2", gap: "$3", items: "center", minH: 44, children: [_jsxs(YStack, { flex: 1, minW: 0, children: [_jsx(SizableText, { render: "h1", size: "$5", color: "$ink", numberOfLines: 1, children: title }), _jsx(SizableText, { size: "$1", color: "$soft", numberOfLines: 1, children: [state || (detail.loading ? 'reading' : ''), record?.repo, record?.branch, record?.published ? 'shared' : ''].filter(Boolean).join(' · ') })] }), desk ? null : (_jsx(XStack, { render: "button", "aria-label": "Show the side pane", px: "$2", py: "$1", rounded: "$2", hoverStyle: { bg: '$hover' }, onPress: () => setDesk(true), children: _jsx(PanelRight, { size: 16 }) })), pr.href ? (_jsx(Out, { href: pr.href, label: `Open pull request ${pr.label}`, children: _jsxs(XStack, { items: "center", gap: "$1.5", px: "$2.5", py: "$1.5", rounded: "$3", borderWidth: 1, borderColor: "$borderColor", hoverStyle: { bg: '$hover' }, children: [_jsx(GitPullRequest, { size: 14 }), _jsx(SizableText, { size: "$2", color: "$ink", children: pr.label }), _jsx(ExternalLink, { size: 12, opacity: 0.6 })] }) })) : null, menu.length ? (_jsx(DropdownMenu, { trigger: _jsx(XStack, { render: "button", "aria-label": "Manage this run", px: "$2", py: "$1", rounded: "$2", hoverStyle: { bg: '$hover' }, children: _jsx(MoreHorizontal, { size: 16 }) }), items: menu })) : null] }), _jsx(Transcript, { cards: shown,
|
|
242
|
+
// A paused run's agent was stopped where it stood; what it was running is cut off.
|
|
243
|
+
live: running && !paused, header: hidden ? (_jsx(SizableText, { size: "$1", color: "$soft", children: `${hidden} earlier ${hidden === 1 ? 'turn is' : 'turns are'} not shown. The run’s Git tab has everything it pushed.` })) : null, onApprove: planned && earlier ? (p) => void act(() => carry(p, 'approve'), 'The build could not start') : undefined, approving: busy, empty: signed ? (_jsx(SizableText, { size: "$2", color: "$soft", children: detail.error
|
|
244
|
+
? detail.error.message
|
|
245
|
+
: detail.loading
|
|
246
|
+
? 'Reading this run…'
|
|
247
|
+
: running
|
|
248
|
+
? setup
|
|
249
|
+
? 'Setting up environment'
|
|
250
|
+
: 'Starting…'
|
|
251
|
+
: 'Waiting for the run’s first step…' })) : (_jsxs(YStack, { gap: "$3", items: "flex-start", children: [_jsx(SizableText, { size: "$2", color: "$soft", children: "Sign in to follow this run." }), _jsx(Button, { size: "sm", onPress: () => host.signIn?.(), children: "Sign in" })] })) }), _jsxs(YStack, { pb: "$4", pt: "$2", gap: "$2", children: [end.problem ? (_jsx(SizableText, { size: "$1", color: "$soft", children: "The branch is pushed, and the pull request could not be opened." })) : null, note || (signed && refused) ? (_jsx(SizableText, { size: "$1", color: "$soft", role: "status", children: note || refused })) : null, plan.length ? (_jsxs(YStack, { borderWidth: 1, borderColor: "$borderColor", rounded: "$3", overflow: "hidden", children: [_jsxs(XStack, { px: "$3", py: "$2", justify: "space-between", items: "center", children: [_jsx(SizableText, { size: "$2", color: "$ink", children: "Steps" }), _jsx(SizableText, { size: "$1", color: "$soft", children: String(plan.length) })] }), plan.map((s, i) => {
|
|
252
|
+
// Only a run still working is on a step.
|
|
253
|
+
const current = running && !s.done && plan.findIndex((x) => !x.done) === i;
|
|
98
254
|
return (_jsxs(XStack, { items: "center", gap: "$2", px: "$3", py: "$1.5", borderTopWidth: 1, borderColor: "$borderColor", children: [_jsx(SizableText, { size: "$2", color: s.done || current ? '$ink' : '$soft', children: s.done ? '✓' : current ? '●' : '○' }), _jsx(SizableText, { size: "$2", color: s.done ? '$soft' : '$ink', numberOfLines: 1, children: s.name })] }, s.name));
|
|
99
|
-
})] })) : null, running ? (_jsxs(XStack, { items: "center",
|
|
255
|
+
})] })) : null, setup && running && !seen ? _jsx(Onboarding, { onDone: () => setSeen(true) }) : null, running ? (_jsxs(XStack, { items: "center", gap: "$2", px: "$3", py: "$2", rounded: "$10", borderWidth: 1, borderColor: "$borderColor", flexWrap: "wrap", rowGap: "$1", children: [_jsx(SizableText, { size: "$2", color: "$ink", flex: 1, minW: 160, children: paused
|
|
256
|
+
? kept.pushed
|
|
257
|
+
? 'Paused — its work so far is on its branch.'
|
|
258
|
+
: 'Paused.'
|
|
259
|
+
: setup
|
|
260
|
+
? span
|
|
261
|
+
? `Environment setup takes ~${span[0] === span[1] ? span[0] : `${span[0]}–${span[1]}`} minutes.`
|
|
262
|
+
: 'Environment setup takes several minutes.'
|
|
263
|
+
: 'This run is still working.' }), _jsx(Steer, { onCommand: command, withhold: [
|
|
264
|
+
...(paused ? ['pause'] : ['resume']),
|
|
265
|
+
...(busy || waiting ? ['pause', 'resume', 'stop'] : []),
|
|
266
|
+
] }), _jsx(Button, { size: "sm", disabled: notify, onPress: () => void ask(), children: notify ? 'You will be notified' : 'Notify me' })] })) : null, _jsx(Composer, { inline: true, value: draft, onChange: setDraft, onSend: send, disabled: !signed || busy || waiting || !(running || finished), placeholder: placeholder, label: running && !(paused && held) ? 'Steer this run' : 'Follow up on this run' })] })] }), desk ? (_jsx(Desk, { id: id, repo: record?.repo ?? '', branch: record?.branch ?? '', base: record?.base ?? '', environment: record?.environment ?? '', mode: mode, pr: pr, title: title, project: record?.project ?? '', sandbox: record?.sandbox ?? '', events: events, live: running, menu: menu, refused: signed ? refused || (detail.error ? detail.error.message : '') : 'Sign in to follow this run.', retry: signed ? 'Retry' : 'Sign in', onRetry: signed ? detail.reload : () => host.signIn?.(), onHide: () => setDesk(false) })) : null, _jsx(Dialog, { open: naming, onOpenChange: setNaming, children: _jsxs(DialogContent, { maxW: 480, children: [_jsx(DialogTitle, { children: "Rename this run" }), _jsx(Input, { autoFocus: true, value: name, onChangeText: setName, "aria-label": "The run\u2019s name", maxLength: 512, onKeyDown: (e) => {
|
|
267
|
+
if ((e.key ?? e.nativeEvent?.key) === 'Enter')
|
|
268
|
+
save();
|
|
269
|
+
} }), _jsxs(XStack, { gap: "$2", justify: "flex-end", children: [_jsx(Button, { size: "sm", variant: "outline", onPress: () => setNaming(false), children: "Cancel" }), _jsx(Button, { size: "sm", disabled: busy || !name.trim(), onPress: save, children: "Save" })] })] }) })] }));
|
|
270
|
+
}
|
|
271
|
+
/** What a setup run does, said once per browser the first time one is watched. */
|
|
272
|
+
function Onboarding({ onDone }) {
|
|
273
|
+
const steps = [
|
|
274
|
+
'Explore, install and check the codebase',
|
|
275
|
+
'Name the secrets it needs — you set their values',
|
|
276
|
+
'Propose the environment for you to review and save',
|
|
277
|
+
];
|
|
278
|
+
return (_jsxs(YStack, { gap: "$3", p: "$4", rounded: "$4", borderWidth: 1, borderColor: "$borderColor", bg: "$panel", children: [_jsx(SizableText, { size: "$4", color: "$ink", children: "Set up a cloud environment" }), _jsx(SizableText, { size: "$2", color: "$soft", children: "An environment lets every later run install, start, test and check its changes the way an engineer does. Setup is agent-led and takes several minutes. The agent will:" }), _jsx(YStack, { gap: "$1.5", children: steps.map((s, i) => (_jsxs(XStack, { gap: "$3", children: [_jsx(SizableText, { size: "$2", color: "$soft", width: 12, children: String(i + 1) }), _jsx(SizableText, { size: "$2", color: "$ink", children: s })] }, s))) }), _jsx(SizableText, { size: "$2", color: "$soft", children: "Interrupt anytime to steer the agent or ask it questions." }), _jsx(Button, { size: "sm", onPress: onDone, children: "Got it" })] }));
|
|
100
279
|
}
|
package/lib/section.js
CHANGED
|
@@ -11,7 +11,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
11
11
|
* host's own `go`.
|
|
12
12
|
*/
|
|
13
13
|
import { SizableText } from '@hanzo/gui';
|
|
14
|
-
import { Blocks, CircleDot, FolderGit2, Kanban, LayoutTemplate,
|
|
14
|
+
import { Blocks, CircleDot, FolderGit2, Kanban, LayoutTemplate, SlidersHorizontal, SquarePen, Workflow } from '@hanzogui/lucide-icons-2';
|
|
15
15
|
import { SidebarItem, SidebarSection, StatusDot } from '@hanzo/ui/chat';
|
|
16
16
|
import { useMemo } from 'react';
|
|
17
17
|
import { pinBoard } from './choice.js';
|
|
@@ -48,5 +48,5 @@ export function DevSection({ host, onPick, label = 'Runs', children, }) {
|
|
|
48
48
|
return (_jsxs(_Fragment, { children: [_jsx(SidebarItem, { icon: _jsx(SquarePen, { size: 16, "aria-hidden": true }), active: r.kind === 'new', onPress: () => go(''), children: "New run" }), _jsx(SidebarItem, { icon: _jsx(Workflow, { size: 16, "aria-hidden": true }), active: screen === 'automations', onPress: () => go(path({ kind: 'screen', screen: 'automations' })), children: "Automations" }), _jsx(SidebarItem, { icon: _jsx(FolderGit2, { size: 16, "aria-hidden": true }), active: screen === 'codebases', onPress: () => go(path({ kind: 'screen', screen: 'codebases' })), children: "Codebase" }), _jsx(SidebarItem, { icon: _jsx(Kanban, { size: 16, "aria-hidden": true }), active: screen === 'projects', onPress: () => go(path({ kind: 'screen', screen: 'projects' })), children: "Projects" }), _jsx(SidebarItem, { icon: _jsx(CircleDot, { size: 16, "aria-hidden": true }), active: screen === 'issues', onPress: () => {
|
|
49
49
|
pinBoard(host.org, '');
|
|
50
50
|
go(path({ kind: 'screen', screen: 'issues' }));
|
|
51
|
-
}, children: "Issues" }), _jsx(SidebarItem, { icon: _jsx(
|
|
51
|
+
}, children: "Issues" }), _jsx(SidebarItem, { icon: _jsx(SlidersHorizontal, { size: 16, "aria-hidden": true }), active: r.kind === 'customize', onPress: () => go(path({ kind: 'customize', tab: 'connectors' })), children: "Customize" }), _jsx(SidebarItem, { icon: _jsx(Blocks, { size: 16, "aria-hidden": true }), active: screen === 'artifacts', onPress: () => go(path({ kind: 'screen', screen: 'artifacts' })), children: "Artifacts" }), _jsx(SidebarItem, { icon: _jsx(LayoutTemplate, { size: 16, "aria-hidden": true }), active: screen === 'templates', onPress: () => go(path({ kind: 'screen', screen: 'templates' })), children: "Templates" }), children, _jsx(SidebarSection, { label: label, children: !host.person ? (_jsx(Note, { children: "Sign in to see your runs." })) : sessions.value.length ? (sessions.value.map((s) => (_jsx(SidebarItem, { icon: _jsx(StatusDot, { status: DOTS[s.status] ?? 'idle' }), active: r.kind === 'run' && r.id === s.id, onPress: () => go(s.id), children: s.title || 'Untitled run' }, s.id)))) : (_jsx(Note, { children: sessions.error ? sessions.error.message : sessions.loading ? 'Reading…' : 'No runs yet. Say what to build and it lands here.' })) })] }));
|
|
52
52
|
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Account: who is signed in and how Hanzo should know them — the photo and
|
|
4
|
+
* full name on their Hanzo identity, what to call them, what they work on and
|
|
5
|
+
* the instructions every coding run reads — then the organizations they belong
|
|
6
|
+
* to, the one this page acts in, a way to act in another, and logging out.
|
|
7
|
+
*
|
|
8
|
+
* The email is how the person signs in, so it is shown and not edited here.
|
|
9
|
+
*/
|
|
10
|
+
import { SizableText, XStack, YStack } from '@hanzo/gui';
|
|
11
|
+
import { Check, Copy, LogOut } from '@hanzogui/lucide-icons-2';
|
|
12
|
+
import { Button, Input, Textarea } from '@hanzo/ui';
|
|
13
|
+
import { ChipSelect } from '@hanzo/ui/product';
|
|
14
|
+
import { useEffect, useRef, useState } from 'react';
|
|
15
|
+
import { PHOTO, photo, rename } from '../api/profile.js';
|
|
16
|
+
import { useHost, useTarget } from '../host.js';
|
|
17
|
+
import { usePrefs } from '../prefs.js';
|
|
18
|
+
import { Card, Field, Group, Heading, Note, Row, Soft } from './ui.js';
|
|
19
|
+
export const WORK = [
|
|
20
|
+
{ id: 'engineering', label: 'Software engineering' },
|
|
21
|
+
{ id: 'data', label: 'Data science' },
|
|
22
|
+
{ id: 'design', label: 'Design' },
|
|
23
|
+
{ id: 'product', label: 'Product management' },
|
|
24
|
+
{ id: 'research', label: 'Research' },
|
|
25
|
+
{ id: 'writing', label: 'Writing' },
|
|
26
|
+
{ id: 'education', label: 'Education' },
|
|
27
|
+
{ id: 'operations', label: 'Operations' },
|
|
28
|
+
{ id: 'marketing', label: 'Marketing and sales' },
|
|
29
|
+
{ id: 'other', label: 'Something else' },
|
|
30
|
+
];
|
|
31
|
+
/** Long enough for real guidance, short enough to sit in the settings document beside everything else. */
|
|
32
|
+
const INSTRUCTIONS = 3000;
|
|
33
|
+
export function Account() {
|
|
34
|
+
const host = useHost();
|
|
35
|
+
const t = useTarget();
|
|
36
|
+
const { prefs, save } = usePrefs();
|
|
37
|
+
const who = host.person;
|
|
38
|
+
const [name, setName] = useState(who?.name ?? '');
|
|
39
|
+
const [shownName, setShownName] = useState(who?.name ?? '');
|
|
40
|
+
const [face, setFace] = useState(who?.avatar ?? '');
|
|
41
|
+
const [callName, setCallName] = useState('');
|
|
42
|
+
const [work, setWork] = useState('');
|
|
43
|
+
const [instructions, setInstructions] = useState('');
|
|
44
|
+
const [busy, setBusy] = useState(false);
|
|
45
|
+
const [note, setNote] = useState('');
|
|
46
|
+
const picker = useRef(null);
|
|
47
|
+
const [copied, setCopied] = useState(false);
|
|
48
|
+
// The identity can arrive after the page does.
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
setName(who?.name ?? '');
|
|
51
|
+
setShownName(who?.name ?? '');
|
|
52
|
+
setFace(who?.avatar ?? '');
|
|
53
|
+
}, [who?.name, who?.avatar]);
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
setCallName(prefs.callName ?? '');
|
|
56
|
+
setWork(prefs.work ?? '');
|
|
57
|
+
setInstructions(prefs.instructions ?? '');
|
|
58
|
+
}, [prefs.callName, prefs.work, prefs.instructions]);
|
|
59
|
+
if (!who) {
|
|
60
|
+
return (_jsxs(YStack, { gap: "$5", children: [_jsx(Heading, { title: "Account" }), _jsx(Soft, { children: "Sign in to see your account." }), host.signIn ? (_jsx(XStack, { justify: "center", children: _jsx(Button, { size: "sm", onPress: () => host.signIn?.(), children: "Sign in" }) })) : null] }));
|
|
61
|
+
}
|
|
62
|
+
const patch = {};
|
|
63
|
+
if (callName.trim() !== (prefs.callName ?? ''))
|
|
64
|
+
patch.callName = callName.trim() || null;
|
|
65
|
+
if (work !== (prefs.work ?? ''))
|
|
66
|
+
patch.work = work || null;
|
|
67
|
+
if (instructions.trim() !== (prefs.instructions ?? '').trim())
|
|
68
|
+
patch.instructions = instructions.trim() || null;
|
|
69
|
+
const renamed = name.trim() !== shownName.trim();
|
|
70
|
+
const changed = renamed || Object.keys(patch).length > 0;
|
|
71
|
+
const submit = async () => {
|
|
72
|
+
setBusy(true);
|
|
73
|
+
setNote('');
|
|
74
|
+
try {
|
|
75
|
+
if (renamed)
|
|
76
|
+
setShownName((await rename(t, name)).displayName || name.trim());
|
|
77
|
+
if (Object.keys(patch).length)
|
|
78
|
+
await save(patch);
|
|
79
|
+
setNote('Saved');
|
|
80
|
+
}
|
|
81
|
+
catch (e) {
|
|
82
|
+
setNote(e instanceof Error ? e.message : 'That was not saved');
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
setBusy(false);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
const upload = async (file) => {
|
|
89
|
+
setBusy(true);
|
|
90
|
+
setNote('');
|
|
91
|
+
try {
|
|
92
|
+
setFace(await photo(t, file));
|
|
93
|
+
setNote('Your photo is saved');
|
|
94
|
+
}
|
|
95
|
+
catch (e) {
|
|
96
|
+
setNote(e instanceof Error ? e.message : 'The photo was not saved');
|
|
97
|
+
}
|
|
98
|
+
finally {
|
|
99
|
+
setBusy(false);
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
const orgs = host.memberships ?? (host.org ? [host.org] : []);
|
|
103
|
+
const chosenWork = WORK.find((w) => w.id === work) ?? (work ? { id: work, label: work } : null);
|
|
104
|
+
return (_jsxs(YStack, { gap: "$6", children: [_jsx(Heading, { title: "Account", detail: "Your Hanzo identity, and what Hanzo should know about you.", action: host.signOut ? (_jsxs(Button, { size: "sm", variant: "outline", onPress: () => host.signOut?.(), children: [_jsx(LogOut, { size: 14 }), " Log out"] })) : undefined }), _jsx(Group, { title: "Profile", children: _jsxs(YStack, { gap: "$4", children: [_jsxs(XStack, { items: "center", gap: "$3", children: [face ? (_jsx("img", { src: face, alt: "", width: 48, height: 48, style: { borderRadius: 999, objectFit: 'cover' } })) : (_jsx(YStack, { width: 48, height: 48, rounded: 999, bg: "$raised", items: "center", justify: "center", children: _jsx(SizableText, { size: "$5", color: "$ink", children: (shownName || who.email || '?').charAt(0).toUpperCase() }) })), _jsxs(YStack, { flex: 1, minW: 0, children: [_jsx(SizableText, { size: "$4", color: "$ink", numberOfLines: 1, children: shownName || who.email }), _jsx(SizableText, { size: "$2", color: "$soft", numberOfLines: 1, children: who.email })] }), _jsx(Button, { size: "sm", variant: "outline", disabled: busy, onPress: () => picker.current?.click(), children: "Change photo" }), _jsx("input", { ref: picker, type: "file", accept: PHOTO, hidden: true, style: { display: 'none' }, tabIndex: -1, "aria-label": "Photo", onChange: (e) => {
|
|
105
|
+
const input = e.currentTarget;
|
|
106
|
+
const file = input.files?.[0];
|
|
107
|
+
input.value = '';
|
|
108
|
+
if (file)
|
|
109
|
+
void upload(file);
|
|
110
|
+
} })] }), _jsx(Field, { label: "Full name", children: _jsx(Input, { value: name, onChangeText: setName, "aria-label": "Full name", placeholder: "Your name" }) }), _jsx(Field, { label: "Email", hint: "You sign in with this address, so it is not changed here.", children: _jsx(SizableText, { size: "$2", color: "$soft", numberOfLines: 1, children: who.email || 'No email on this account' }) }), _jsx(Field, { label: "What should Hanzo call you?", children: _jsx(Input, { value: callName, onChangeText: setCallName, "aria-label": "What should Hanzo call you?", placeholder: shownName.split(' ')[0] || 'A first name' }) }), _jsx(Field, { label: "What best describes your work?", children: _jsx(XStack, { children: _jsx(ChipSelect, { name: "Work", label: chosenWork?.label ?? 'Choose one', chosen: chosenWork, items: WORK, onChange: (w) => setWork(w.id), placement: "bottom-start", width: 260 }) }) }), _jsx(Field, { label: "Instructions for Hanzo", hint: `Every coding run reads these before it starts. ${instructions.length}/${INSTRUCTIONS}`, children: _jsx(Textarea, { value: instructions, onChangeText: (v) => setInstructions(v.slice(0, INSTRUCTIONS)), "aria-label": "Instructions for Hanzo", placeholder: "How you like to work: the languages and tools you prefer, the style to follow, what to avoid.", rows: 5 }) }), _jsxs(XStack, { items: "center", gap: "$3", children: [_jsx(Button, { size: "sm", disabled: busy || !changed, onPress: () => void submit(), children: "Save" }), _jsx(Note, { children: note })] })] }) }), host.org ? (_jsx(Group, { title: "Organization", detail: "The organization this page acts in. Runs, codebases and settings belong to it.", children: _jsx(Card, { children: _jsx(Row, { first: true, mono: true, title: host.org, detail: `Organization ID${host.admin ? ' · you are an admin' : ''}`, trailing: _jsx(Button, { size: "icon-sm", variant: "ghost", "aria-label": copied ? 'Organization ID copied' : 'Copy organization ID', onPress: () => void navigator.clipboard?.writeText(host.org ?? '').then(() => setCopied(true)), children: copied ? _jsx(Check, { size: 14 }) : _jsx(Copy, { size: 14 }) }) }) }) })) : null, _jsx(Group, { title: "Organizations", detail: "Every organization you belong to. Switch to act in another.", children: orgs.length ? (_jsx(Card, { children: orgs.map((o, i) => (_jsx(Row, { first: i === 0, title: o, detail: o === host.org ? `Acting in ${o}${host.admin ? ' as an admin' : ''}` : undefined, trailing: o === host.org ? (_jsx(Check, { size: 16 })) : host.chooseOrg ? (_jsx(Button, { size: "sm", variant: "ghost", onPress: () => host.chooseOrg?.(o), children: "Switch" })) : null }, o))) })) : (_jsx(Soft, { children: "You belong to no organization yet." })) })] }));
|
|
111
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function Billing(): import("react").JSX.Element;
|