@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.
Files changed (152) hide show
  1. package/README.md +126 -23
  2. package/lib/api/agents.d.ts +80 -0
  3. package/lib/api/agents.js +148 -0
  4. package/lib/api/billing.d.ts +205 -0
  5. package/lib/api/billing.js +300 -0
  6. package/lib/api/call.d.ts +6 -0
  7. package/lib/api/call.js +11 -0
  8. package/lib/api/capabilities.d.ts +25 -0
  9. package/lib/api/capabilities.js +40 -0
  10. package/lib/api/changes.d.ts +56 -0
  11. package/lib/api/changes.js +63 -0
  12. package/lib/api/coding.d.ts +42 -4
  13. package/lib/api/coding.js +62 -3
  14. package/lib/api/connectors.d.ts +90 -0
  15. package/lib/api/connectors.js +109 -0
  16. package/lib/api/consent.d.ts +22 -0
  17. package/lib/api/consent.js +28 -0
  18. package/lib/api/environment.d.ts +57 -0
  19. package/lib/api/environment.js +85 -0
  20. package/lib/api/git.d.ts +6 -2
  21. package/lib/api/git.js +13 -5
  22. package/lib/api/github.d.ts +13 -0
  23. package/lib/api/github.js +9 -0
  24. package/lib/api/harness.d.ts +44 -0
  25. package/lib/api/harness.js +348 -0
  26. package/lib/api/keys.d.ts +41 -0
  27. package/lib/api/keys.js +55 -0
  28. package/lib/api/machines.d.ts +56 -0
  29. package/lib/api/machines.js +84 -0
  30. package/lib/api/members.d.ts +47 -0
  31. package/lib/api/members.js +88 -0
  32. package/lib/api/memory.d.ts +25 -0
  33. package/lib/api/memory.js +34 -0
  34. package/lib/api/places.d.ts +2 -2
  35. package/lib/api/places.js +3 -26
  36. package/lib/api/plugins.d.ts +55 -0
  37. package/lib/api/plugins.js +62 -0
  38. package/lib/api/pref.d.ts +49 -0
  39. package/lib/api/pref.js +68 -0
  40. package/lib/api/profile.d.ts +21 -0
  41. package/lib/api/profile.js +40 -0
  42. package/lib/api/projects.d.ts +19 -2
  43. package/lib/api/projects.js +29 -4
  44. package/lib/api/provider.d.ts +42 -0
  45. package/lib/api/provider.js +58 -0
  46. package/lib/api/sandbox.d.ts +30 -0
  47. package/lib/api/sandbox.js +49 -0
  48. package/lib/api/sessions.d.ts +39 -4
  49. package/lib/api/sessions.js +58 -6
  50. package/lib/api/skills.d.ts +70 -0
  51. package/lib/api/skills.js +90 -0
  52. package/lib/api/tools.d.ts +34 -0
  53. package/lib/api/tools.js +37 -0
  54. package/lib/api/turn.d.ts +84 -16
  55. package/lib/api/turn.js +224 -4
  56. package/lib/api/webhooks.d.ts +60 -0
  57. package/lib/api/webhooks.js +81 -0
  58. package/lib/ask.d.ts +18 -0
  59. package/lib/ask.js +69 -0
  60. package/lib/builder.js +39 -19
  61. package/lib/customize/agents.d.ts +2 -0
  62. package/lib/customize/agents.js +125 -0
  63. package/lib/customize/connectors.d.ts +2 -0
  64. package/lib/customize/connectors.js +197 -0
  65. package/lib/customize/index.d.ts +6 -0
  66. package/lib/customize/index.js +50 -0
  67. package/lib/customize/plugins.d.ts +2 -0
  68. package/lib/customize/plugins.js +93 -0
  69. package/lib/customize/skills.d.ts +2 -0
  70. package/lib/customize/skills.js +125 -0
  71. package/lib/customize/ui.d.ts +110 -0
  72. package/lib/customize/ui.js +99 -0
  73. package/lib/desk.d.ts +8 -1
  74. package/lib/desk.js +29 -35
  75. package/lib/door.d.ts +7 -0
  76. package/lib/door.js +89 -0
  77. package/lib/environment.d.ts +15 -0
  78. package/lib/environment.js +132 -0
  79. package/lib/files.d.ts +14 -0
  80. package/lib/files.js +111 -0
  81. package/lib/find.d.ts +7 -0
  82. package/lib/find.js +77 -0
  83. package/lib/foot.d.ts +16 -0
  84. package/lib/foot.js +36 -0
  85. package/lib/forge.js +8 -4
  86. package/lib/git.d.ts +9 -0
  87. package/lib/git.js +74 -0
  88. package/lib/host.d.ts +5 -0
  89. package/lib/landing.d.ts +10 -0
  90. package/lib/landing.js +47 -14
  91. package/lib/markdown.d.ts +49 -0
  92. package/lib/markdown.js +123 -0
  93. package/lib/plans.d.ts +1 -0
  94. package/lib/plans.js +113 -0
  95. package/lib/prefs.d.ts +30 -0
  96. package/lib/prefs.js +83 -0
  97. package/lib/prose.d.ts +4 -0
  98. package/lib/prose.js +53 -0
  99. package/lib/route.d.ts +16 -1
  100. package/lib/route.js +32 -1
  101. package/lib/run.js +207 -28
  102. package/lib/section.js +2 -2
  103. package/lib/settings/account.d.ts +5 -0
  104. package/lib/settings/account.js +111 -0
  105. package/lib/settings/billing.d.ts +1 -0
  106. package/lib/settings/billing.js +104 -0
  107. package/lib/settings/capabilities.d.ts +1 -0
  108. package/lib/settings/capabilities.js +55 -0
  109. package/lib/settings/card.d.ts +31 -0
  110. package/lib/settings/card.js +105 -0
  111. package/lib/settings/code.d.ts +1 -0
  112. package/lib/settings/code.js +51 -0
  113. package/lib/settings/environments.d.ts +1 -0
  114. package/lib/settings/environments.js +53 -0
  115. package/lib/settings/general.d.ts +1 -0
  116. package/lib/settings/general.js +44 -0
  117. package/lib/settings/index.d.ts +4 -0
  118. package/lib/settings/index.js +22 -0
  119. package/lib/settings/integrations.d.ts +1 -0
  120. package/lib/settings/integrations.js +81 -0
  121. package/lib/settings/keys.d.ts +1 -0
  122. package/lib/settings/keys.js +102 -0
  123. package/lib/settings/machines.d.ts +1 -0
  124. package/lib/settings/machines.js +118 -0
  125. package/lib/settings/members.d.ts +1 -0
  126. package/lib/settings/members.js +67 -0
  127. package/lib/settings/memory.d.ts +1 -0
  128. package/lib/settings/memory.js +56 -0
  129. package/lib/settings/notifications.d.ts +1 -0
  130. package/lib/settings/notifications.js +97 -0
  131. package/lib/settings/privacy.d.ts +1 -0
  132. package/lib/settings/privacy.js +63 -0
  133. package/lib/settings/sections.d.ts +16 -0
  134. package/lib/settings/sections.js +32 -0
  135. package/lib/settings/ui.d.ts +49 -0
  136. package/lib/settings/ui.js +47 -0
  137. package/lib/settings/usage.d.ts +1 -0
  138. package/lib/settings/usage.js +114 -0
  139. package/lib/shelf.d.ts +6 -0
  140. package/lib/shelf.js +86 -16
  141. package/lib/switch.js +1 -2
  142. package/lib/transcript.d.ts +12 -0
  143. package/lib/transcript.js +56 -0
  144. package/lib/voice.d.ts +3 -1
  145. package/lib/voice.js +10 -4
  146. package/package.json +1 -1
  147. package/lib/account.d.ts +0 -14
  148. package/lib/account.js +0 -38
  149. package/lib/e2e/site.spec.d.ts +0 -1
  150. package/lib/e2e/site.spec.js +0 -125
  151. package/lib/mcp.d.ts +0 -1
  152. package/lib/mcp.js +0 -28
package/lib/api/turn.js CHANGED
@@ -1,3 +1,24 @@
1
+ /**
2
+ * What a run's events SAY, as text a person reads.
3
+ *
4
+ * A curated projection, never a dump: an event carries the machinery a run
5
+ * happened on — hosts, paths, whole file bodies — and printing it would put
6
+ * someone's filesystem in front of anyone who can read the run. Each kind gets a
7
+ * sentence built from the few fields that describe the outcome; the rest is
8
+ * dropped by omission, because a projection cannot leak a field it never reads.
9
+ *
10
+ * Every string here is rendered as TEXT by the transcript. Nothing is HTML.
11
+ *
12
+ * The coding plane's vocabulary (apps/coding): `status` for a lifecycle move
13
+ * (started, routed, done, error, stopped, paused), `tool-call` for a step,
14
+ * `log` for a free line. A person's steering is a `control` {command, message}.
15
+ *
16
+ * `cards` is the transcript as it is drawn: the run's steps, and the agent's own
17
+ * output read by what each part is (harness.ts). The output a card shows is what
18
+ * the run's Terminal log already prints; a command's working directory, which is
19
+ * the sandbox's layout, is dropped.
20
+ */
21
+ import { harness } from './harness.js';
1
22
  const str = (v) => (typeof v === 'string' ? v : '');
2
23
  /** The payload as an object. A live frame may carry it as a JSON string. */
3
24
  export function decode(payload) {
@@ -34,8 +55,11 @@ function status(b, mode) {
34
55
  // A plan's answer IS its final status: the run read and wrote nothing.
35
56
  // Whether the run planned is the RECORD's word — any member of the org
36
57
  // can append an event saying `mode: plan`, and its text is not an answer.
58
+ // A setup is a plan put to one question, and answers the same way.
37
59
  if (mode === 'plan')
38
60
  return str(b.plan) || 'Planned — the run answered with no plan';
61
+ if (mode === 'setup')
62
+ return str(b.plan) || 'Set up — the run answered with no environment';
39
63
  if (b.changed === false)
40
64
  return 'Done — nothing to change';
41
65
  const pr = str(b.pr);
@@ -47,7 +71,10 @@ function status(b, mode) {
47
71
  case 'stopped':
48
72
  return branch && b.changed ? `Stopped — work kept on ${branch}` : 'Stopped';
49
73
  case 'paused':
50
- return 'Paused';
74
+ return branch && b.changed ? `Paused — work kept on ${branch}` : 'Paused';
75
+ case 'followed':
76
+ // A run redirected from a chat thread is carried on by a follow-up (apps/coding steer.go).
77
+ return str(b.next) ? 'Continued in a follow-up run' : 'The follow-up run could not start';
51
78
  }
52
79
  return str(b.status);
53
80
  }
@@ -120,7 +147,12 @@ export function shell(events) {
120
147
  }
121
148
  return out.slice(-400);
122
149
  }
123
- /** Tool calls, in the order they were first named. A later status for the same step wins. */
150
+ /**
151
+ * Tool calls, in the order they were first named. A later status for the same
152
+ * step wins, and a run's steps follow one another, so a step another has
153
+ * followed is over. `exit` is not a step: it is how the sandbox says one command
154
+ * in it ended (apps/sandbox work.go).
155
+ */
124
156
  export function steps(events) {
125
157
  const order = [];
126
158
  const done = new Map();
@@ -131,14 +163,14 @@ export function steps(events) {
131
163
  if (!body || typeof body === 'string')
132
164
  continue;
133
165
  const name = str(body.step);
134
- if (!name)
166
+ if (!name || name === 'exit')
135
167
  continue;
136
168
  if (!done.has(name))
137
169
  order.push(name);
138
170
  const status = str(body.status);
139
171
  done.set(name, status === 'ok' || status === 'done' || status === 'error');
140
172
  }
141
- return order.map((name) => ({ name, done: done.get(name) === true }));
173
+ return order.map((name, i) => ({ name, done: i < order.length - 1 || done.get(name) === true }));
142
174
  }
143
175
  /**
144
176
  * What the run narrated about how it ended, latest winning. Status text only:
@@ -187,3 +219,191 @@ export function pull(url, repo) {
187
219
  return { href: '', label: '' };
188
220
  return { href: url, label: `#${m[3]}` };
189
221
  }
222
+ /** The platform's own words for the step that starts the agent (apps/coding sandboxrunner.go). */
223
+ const TASK = 'running the task';
224
+ /** What the run's own steps are called on a card. */
225
+ const NAMES = {
226
+ lease: 'Sandbox',
227
+ clone: 'Clone',
228
+ secrets: 'Secrets',
229
+ install: 'Install',
230
+ start: 'Start',
231
+ push: 'Push',
232
+ keep: 'Keep the work',
233
+ };
234
+ /** Steps another card already says: `leased` finishes the lease, `done` is the final status, `ended` is housekeeping. */
235
+ const QUIET = new Set(['leased', 'done', 'ended']);
236
+ const ranOf = (status) => (status === 'ok' || status === 'done' ? 'done' : status === 'error' ? 'error' : 'running');
237
+ /**
238
+ * A run's events, as the cards its transcript draws. `mode` is the run's, from
239
+ * its record: a plan's final status is its plan, and nothing else draws a plan.
240
+ *
241
+ * The run's own steps arrive as `tool-call` {step, message, status}; each
242
+ * command in the sandbox narrates its output as `log` {message} chunks and ends
243
+ * with `tool-call` {step: "exit"}. The output after the step that starts the
244
+ * agent is the agent's, read by its harness's grammar (harness.ts); after any
245
+ * other step it is that step's. Once the agent has exited, the commands the run
246
+ * runs before it pushes commit its work.
247
+ */
248
+ export function cards(events, mode = '') {
249
+ const out = [];
250
+ let seq = 0;
251
+ let n = 0;
252
+ const mint = () => `${seq}.${n++}`;
253
+ let phase = null;
254
+ let agent = null;
255
+ // The agent has run and exited: output with no step of its own is the run's.
256
+ let exited = false;
257
+ const settle = () => {
258
+ agent?.end();
259
+ agent = null;
260
+ phase = null;
261
+ };
262
+ const answers = new Set();
263
+ for (const e of [...events].sort((a, b) => a.seq - b.seq)) {
264
+ seq = e.seq;
265
+ n = 0;
266
+ const body = decode(e.payload);
267
+ const b = body && typeof body === 'object' ? body : null;
268
+ switch (e.kind) {
269
+ case 'log': {
270
+ const text = typeof body === 'string' ? body : str(b?.message) || str(b?.text);
271
+ if (!text)
272
+ break;
273
+ if (!agent && !phase && exited) {
274
+ phase = { kind: 'step', key: mint(), name: 'Commit', detail: 'committing the work', output: '', ran: 'running' };
275
+ out.push(phase);
276
+ }
277
+ if (!agent && !phase)
278
+ agent = harness(out, mint);
279
+ if (agent)
280
+ agent.feed(text);
281
+ else if (phase)
282
+ phase.output += text;
283
+ break;
284
+ }
285
+ case 'tool-call': {
286
+ if (!b)
287
+ break;
288
+ const step = str(b.step);
289
+ const message = str(b.message);
290
+ if (step === 'exit') {
291
+ // One command in the sandbox ended: `exit 0`, `exit 1`, or `ended: …`.
292
+ if (agent) {
293
+ settle();
294
+ exited = true;
295
+ }
296
+ else if (phase && phase.ran === 'running')
297
+ phase.ran = message === 'exit 0' ? 'done' : 'error';
298
+ break;
299
+ }
300
+ if (message === TASK) {
301
+ settle();
302
+ exited = false;
303
+ agent = harness(out, mint);
304
+ break;
305
+ }
306
+ if (step === 'leased') {
307
+ const lease = out.find((c) => c.kind === 'step' && c.name === NAMES.lease);
308
+ if (lease)
309
+ lease.ran = 'done';
310
+ }
311
+ if (!step || QUIET.has(step))
312
+ break;
313
+ const name = NAMES[step] ?? step.charAt(0).toUpperCase() + step.slice(1);
314
+ if (phase && phase.name === name) {
315
+ phase.ran = ranOf(str(b.status));
316
+ if (message)
317
+ phase.detail = message;
318
+ break;
319
+ }
320
+ settle();
321
+ phase = { kind: 'step', key: mint(), name, detail: message, output: '', ran: ranOf(str(b.status)) };
322
+ out.push(phase);
323
+ break;
324
+ }
325
+ case 'status': {
326
+ settle();
327
+ if (!b)
328
+ break;
329
+ const text = str(b.plan);
330
+ if (str(b.status) === 'done' && (mode === 'plan' || mode === 'setup') && text) {
331
+ answers.add(text.trim());
332
+ out.push(mode === 'plan' ? { kind: 'plan', key: mint(), text } : { kind: 'said', key: mint(), who: 'agent', text });
333
+ break;
334
+ }
335
+ const line = status(b, mode);
336
+ if (line)
337
+ out.push({ kind: 'note', key: mint(), text: line });
338
+ break;
339
+ }
340
+ case 'control': {
341
+ if (!b)
342
+ break;
343
+ const command = str(b.command);
344
+ const said = str(b.message);
345
+ if (command === 'message' && said)
346
+ out.push({ kind: 'said', key: mint(), who: 'person', text: said });
347
+ else if (command === 'pause')
348
+ out.push({ kind: 'note', key: mint(), text: 'Pause asked' });
349
+ else if (command === 'resume')
350
+ out.push({ kind: 'note', key: mint(), text: 'Resume asked' });
351
+ else if (command === 'stop')
352
+ out.push({ kind: 'note', key: mint(), text: 'Stop asked' });
353
+ break;
354
+ }
355
+ case 'message': {
356
+ const text = typeof body === 'string' ? body : str(b?.text) || str(b?.content) || str(b?.message);
357
+ if (text)
358
+ out.push({ kind: 'said', key: mint(), who: str(b?.role) === 'user' ? 'person' : 'agent', text });
359
+ break;
360
+ }
361
+ }
362
+ }
363
+ settle();
364
+ // The answer the final status carries is also the agent's last message: draw it once, as the answer.
365
+ let kept = false;
366
+ for (let i = out.length - 1; i >= 0; i--) {
367
+ const c = out[i];
368
+ if (!((c.kind === 'said' && c.who === 'agent') || c.kind === 'plan') || !answers.has(c.text.trim()))
369
+ continue;
370
+ if (kept)
371
+ out.splice(i, 1);
372
+ kept = true;
373
+ }
374
+ return out;
375
+ }
376
+ /** The plan a plan run answered with, from its final status, or ''. */
377
+ export function answer(events) {
378
+ let out = '';
379
+ for (const e of [...events].sort((a, b) => a.seq - b.seq)) {
380
+ if (e.kind !== 'status')
381
+ continue;
382
+ const b = decode(e.payload);
383
+ if (b && typeof b === 'object' && str(b.status) === 'done')
384
+ out = str(b.plan);
385
+ }
386
+ return out;
387
+ }
388
+ /**
389
+ * Whether the run has kept its work yet, and whether that work is on its
390
+ * branch. A stop or a pause commits and pushes AFTER it is asked, and says so
391
+ * in its status (apps/coding coding.go interrupted): until then a follow-up
392
+ * would clone a branch the push has not reached.
393
+ */
394
+ export function settled(events) {
395
+ const out = { settled: false, pushed: false };
396
+ for (const e of [...events].sort((a, b) => a.seq - b.seq)) {
397
+ if (e.kind !== 'status')
398
+ continue;
399
+ const b = decode(e.payload);
400
+ if (!b || typeof b === 'string')
401
+ continue;
402
+ const s = str(b.status);
403
+ if (s === 'done' || s === 'error' || s === 'stopped' || s === 'paused') {
404
+ out.settled = true;
405
+ out.pushed = b.changed === true;
406
+ }
407
+ }
408
+ return out;
409
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * The organization's webhooks: https endpoints that each matching platform event
3
+ * is POSTed to, signed.
4
+ *
5
+ * GET /v1/webhook {data: [endpoint]}, newest first, secrets withheld
6
+ * POST /v1/webhook {url, events, description} → 201, the secret shown this once
7
+ * DELETE /v1/webhook/{id} 204
8
+ * POST /v1/webhook/{id}/test one signed test event, sent now → {delivered, httpStatus, durationMs, error}
9
+ * GET /v1/webhook/{id}/deliveries {data: [attempt]}, newest first
10
+ *
11
+ * The org is the caller's; an endpoint another org owns reads as not found.
12
+ * Events are subject patterns (`commerce.order.>`); none means every event.
13
+ */
14
+ import { type Target } from './call.ts';
15
+ export interface Endpoint {
16
+ id: string;
17
+ url: string;
18
+ /** Subject patterns; empty is every event. */
19
+ events: string[];
20
+ description: string;
21
+ /** active | disabled */
22
+ status: string;
23
+ /** The signing secret: present only in the answer to a create. */
24
+ secret: string;
25
+ created: string;
26
+ /** Settled in the last seven days, and how many of those failed. */
27
+ deliveries: number;
28
+ failures: number;
29
+ }
30
+ export declare function endpoint(raw: unknown): Endpoint;
31
+ export declare function endpoints(t: Target): Promise<Endpoint[]>;
32
+ /** The patterns in what a person typed: comma or space separated. */
33
+ export declare function patterns(typed: string): string[];
34
+ export interface Draft {
35
+ url: string;
36
+ events: string[];
37
+ description: string;
38
+ }
39
+ /** Why a draft cannot be sent, or ''. The platform refuses the same things. */
40
+ export declare function refuse(d: Draft): string;
41
+ export declare function add(t: Target, d: Draft): Promise<Endpoint>;
42
+ export declare function remove(t: Target, id: string): Promise<void>;
43
+ export interface Trial {
44
+ delivered: boolean;
45
+ /** 0 when the endpoint never answered. */
46
+ status: number;
47
+ ms: number;
48
+ error: string;
49
+ }
50
+ export declare function test(t: Target, id: string): Promise<Trial>;
51
+ export interface Attempt {
52
+ subject: string;
53
+ /** ok | retrying | failed */
54
+ status: string;
55
+ code: number;
56
+ attempt: number;
57
+ error: string;
58
+ at: string;
59
+ }
60
+ export declare function deliveries(t: Target, id: string, limit?: number): Promise<Attempt[]>;
@@ -0,0 +1,81 @@
1
+ /**
2
+ * The organization's webhooks: https endpoints that each matching platform event
3
+ * is POSTed to, signed.
4
+ *
5
+ * GET /v1/webhook {data: [endpoint]}, newest first, secrets withheld
6
+ * POST /v1/webhook {url, events, description} → 201, the secret shown this once
7
+ * DELETE /v1/webhook/{id} 204
8
+ * POST /v1/webhook/{id}/test one signed test event, sent now → {delivered, httpStatus, durationMs, error}
9
+ * GET /v1/webhook/{id}/deliveries {data: [attempt]}, newest first
10
+ *
11
+ * The org is the caller's; an endpoint another org owns reads as not found.
12
+ * Events are subject patterns (`commerce.order.>`); none means every event.
13
+ */
14
+ import { call, query, seg } from './call.js';
15
+ const str = (v) => (typeof v === 'string' ? v : '');
16
+ const num = (v) => (typeof v === 'number' && Number.isFinite(v) ? v : 0);
17
+ const obj = (v) => (v && typeof v === 'object' ? v : {});
18
+ const arr = (v) => (Array.isArray(v) ? v : []);
19
+ export function endpoint(raw) {
20
+ const e = obj(raw);
21
+ return {
22
+ id: str(e.id),
23
+ url: str(e.url),
24
+ events: arr(e.events).filter((s) => typeof s === 'string' && s !== ''),
25
+ description: str(e.description),
26
+ status: str(e.status) || 'active',
27
+ secret: str(e.secret),
28
+ created: str(e.created),
29
+ deliveries: num(e.deliveries7d),
30
+ failures: num(e.failures7d),
31
+ };
32
+ }
33
+ export async function endpoints(t) {
34
+ const raw = obj(await call(t, 'GET', '/v1/webhook'));
35
+ return arr(raw.data).map(endpoint).filter((e) => e.id);
36
+ }
37
+ /** The patterns in what a person typed: comma or space separated. */
38
+ export function patterns(typed) {
39
+ return [...new Set(typed.split(/[\s,]+/).map((s) => s.trim()).filter(Boolean))];
40
+ }
41
+ /** Why a draft cannot be sent, or ''. The platform refuses the same things. */
42
+ export function refuse(d) {
43
+ let u;
44
+ try {
45
+ u = new URL(d.url.trim());
46
+ }
47
+ catch {
48
+ return 'Enter the https address to deliver to';
49
+ }
50
+ if (u.protocol !== 'https:')
51
+ return 'A webhook is delivered over https only';
52
+ if (d.events.length > 64)
53
+ return 'At most 64 event patterns';
54
+ return '';
55
+ }
56
+ export async function add(t, d) {
57
+ const why = refuse(d);
58
+ if (why)
59
+ throw new Error(why);
60
+ return endpoint(await call(t, 'POST', '/v1/webhook', { url: d.url.trim(), events: d.events, description: d.description.trim() }));
61
+ }
62
+ export async function remove(t, id) {
63
+ await call(t, 'DELETE', `/v1/webhook/${seg(id)}`);
64
+ }
65
+ export async function test(t, id) {
66
+ const r = obj(await call(t, 'POST', `/v1/webhook/${seg(id)}/test`, {}));
67
+ return { delivered: r.delivered === true, status: num(r.httpStatus), ms: num(r.durationMs), error: str(r.error) };
68
+ }
69
+ export async function deliveries(t, id, limit = 10) {
70
+ const raw = obj(await call(t, 'GET', `/v1/webhook/${seg(id)}/deliveries${query({ limit })}`));
71
+ return arr(raw.data)
72
+ .map(obj)
73
+ .map((a) => ({
74
+ subject: str(a.subject),
75
+ status: str(a.status),
76
+ code: num(a.httpStatus),
77
+ attempt: num(a.attempt),
78
+ error: str(a.error),
79
+ at: str(a.created),
80
+ }));
81
+ }
package/lib/ask.d.ts ADDED
@@ -0,0 +1,18 @@
1
+ export declare function Confirm({ open, onOpenChange, title, says, act, run, }: {
2
+ open: boolean;
3
+ onOpenChange: (o: boolean) => void;
4
+ title: string;
5
+ /** The whole consequence, stated plainly. */
6
+ says: string;
7
+ /** The button's word: Delete, Remove, Forget. */
8
+ act: string;
9
+ run: () => Promise<void>;
10
+ }): import("react").JSX.Element;
11
+ export declare function Rename({ open, onOpenChange, title, name, run, }: {
12
+ open: boolean;
13
+ onOpenChange: (o: boolean) => void;
14
+ title: string;
15
+ /** The name it has now. */
16
+ name: string;
17
+ run: (next: string) => Promise<void>;
18
+ }): import("react").JSX.Element;
package/lib/ask.js ADDED
@@ -0,0 +1,69 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Asking one thing before an act: to confirm one that cannot be undone, or the
4
+ * new name for something. A refusal is said in the dialog, which stays open, so
5
+ * the reason is read where the act was asked for.
6
+ */
7
+ import { SizableText, XStack } from '@hanzo/gui';
8
+ import { Button, Dialog, DialogContent, DialogTitle, Input } from '@hanzo/ui';
9
+ import { useEffect, useState } from 'react';
10
+ /** One act in flight, and what refused it. */
11
+ function useAct(onDone) {
12
+ const [working, setWorking] = useState(false);
13
+ const [note, setNote] = useState('');
14
+ const go = async (run) => {
15
+ setWorking(true);
16
+ setNote('');
17
+ try {
18
+ await run();
19
+ setWorking(false);
20
+ onDone();
21
+ }
22
+ catch (e) {
23
+ setNote(e instanceof Error ? e.message : 'That did not work');
24
+ setWorking(false);
25
+ }
26
+ };
27
+ return { working, note, setNote, go };
28
+ }
29
+ function Said({ note }) {
30
+ if (!note)
31
+ return null;
32
+ return (_jsx(SizableText, { size: "$1", color: "$soft", role: "status", children: note }));
33
+ }
34
+ export function Confirm({ open, onOpenChange, title, says, act, run, }) {
35
+ const { working, note, setNote, go } = useAct(() => onOpenChange(false));
36
+ const close = (o) => {
37
+ if (working)
38
+ return;
39
+ setNote('');
40
+ onOpenChange(o);
41
+ };
42
+ return (_jsx(Dialog, { open: open, onOpenChange: close, children: _jsxs(DialogContent, { maxW: 420, showCloseButton: false, children: [_jsx(DialogTitle, { children: title }), _jsx(SizableText, { size: "$2", color: "$soft", children: says }), _jsx(Said, { note: note }), _jsxs(XStack, { gap: "$2", justify: "flex-end", children: [_jsx(Button, { size: "sm", variant: "ghost", disabled: working, onPress: () => close(false), children: "Cancel" }), _jsx(Button, { size: "sm", variant: "destructive", disabled: working, onPress: () => void go(run), children: act })] })] }) }));
43
+ }
44
+ export function Rename({ open, onOpenChange, title, name, run, }) {
45
+ const [value, setValue] = useState(name);
46
+ const { working, note, setNote, go } = useAct(() => onOpenChange(false));
47
+ useEffect(() => {
48
+ if (open)
49
+ setValue(name);
50
+ }, [open, name]);
51
+ const close = (o) => {
52
+ if (working)
53
+ return;
54
+ setNote('');
55
+ onOpenChange(o);
56
+ };
57
+ const save = () => {
58
+ const next = value.trim();
59
+ if (!next)
60
+ return setNote('It needs a name');
61
+ if (next === name)
62
+ return close(false);
63
+ void go(() => run(next));
64
+ };
65
+ return (_jsx(Dialog, { open: open, onOpenChange: close, children: _jsxs(DialogContent, { maxW: 420, showCloseButton: false, children: [_jsx(DialogTitle, { children: title }), _jsx(Input, { autoFocus: true, value: value, onChangeText: setValue, "aria-label": "Name", onKeyDown: (e) => {
66
+ if ((e.key ?? e.nativeEvent?.key) === 'Enter')
67
+ save();
68
+ } }), _jsx(Said, { note: note }), _jsxs(XStack, { gap: "$2", justify: "flex-end", children: [_jsx(Button, { size: "sm", variant: "ghost", disabled: working, onPress: () => close(false), children: "Cancel" }), _jsx(Button, { size: "sm", disabled: working, onPress: save, children: "Save" })] })] }) }));
69
+ }
package/lib/builder.js CHANGED
@@ -11,12 +11,14 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
11
11
  * /-/issues the forge's issues
12
12
  * /-/artifacts what this org has built
13
13
  * /-/templates the public starters
14
- * /-/mcp the fleet's native MCP servers
14
+ * /-/customize skills, connectors, plugins and agents
15
15
  * /<slug> a deployed project's workspace, in this same window
16
16
  *
17
17
  * The rail is the sessions rail every Hanzo surface shares: New, the builder's
18
- * places, the org's coding runs newest first with a live status dot, and the
19
- * account. Its collapse is an explicit toggle kept per browser.
18
+ * places with the rest under More, the org's coding runs newest first with a
19
+ * live status dot, and at the foot the offer of Hanzo in Slack and the account,
20
+ * whose menu opens Settings, Usage, the plans, help and logging out (foot.tsx).
21
+ * Its collapse is an explicit toggle kept per browser.
20
22
  *
21
23
  * A project's workspace takes the whole window — it has its own chat column,
22
24
  * bar and panes — and its mark leads back here.
@@ -26,22 +28,27 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
26
28
  * left column, never two.
27
29
  */
28
30
  import { SizableText, XStack, YStack } from '@hanzo/gui';
29
- import { Blocks, BookOpen, CircleDot, Cpu, FolderGit2, Kanban, LayoutTemplate, Menu, Puzzle, Workflow } from '@hanzogui/lucide-icons-2';
31
+ import { Blocks, BookOpen, CircleDot, Cpu, FolderGit2, Kanban, LayoutTemplate, Menu, SlidersHorizontal, Workflow } from '@hanzogui/lucide-icons-2';
30
32
  import { Button } from '@hanzo/ui';
31
33
  import { SessionRail } from '@hanzo/ui/chat';
32
34
  import { HanzoMark } from '@hanzo/ui/product';
33
- import { useEffect, useMemo, useState } from 'react';
35
+ import { useMemo, useState } from 'react';
34
36
  import { pinBoard } from './choice.js';
37
+ import { Customize } from './customize/index.js';
35
38
  import { useKept, useRecents } from './data.js';
36
39
  import { Forge } from './forge.js';
37
40
  import { HostProvider, useHost, useTarget } from './host.js';
38
41
  import { Landing } from './landing.js';
42
+ import { PrefsProvider } from './prefs.js';
39
43
  import { Project } from './project.js';
44
+ import { Plans } from './plans.js';
40
45
  import { path, route } from './route.js';
41
46
  import { Run } from './run.js';
42
47
  import { DOTS } from './section.js';
43
48
  import { Artifacts, Templates } from './shelf.js';
44
- import { Account, Find, onAccount } from './account.js';
49
+ import { Find } from './find.js';
50
+ import { DOCS, Slack, Who } from './foot.js';
51
+ import { Settings } from './settings/index.js';
45
52
  import { Where } from './switch.js';
46
53
  function Shell() {
47
54
  const host = useHost();
@@ -51,9 +58,9 @@ function Shell() {
51
58
  const [collapsed, setCollapsed] = useKept('hanzo.build.rail', false);
52
59
  const [drawer, setDrawer] = useState(false);
53
60
  const [order, setOrder] = useState('newest');
54
- const [account, setAccount] = useState(false);
55
61
  const [finding, setFinding] = useState(false);
56
- useEffect(() => onAccount(() => setAccount(true)), []);
62
+ const [menu, setMenu] = useState(false);
63
+ const [slack, setSlack] = useKept('hanzo.build.slack', false);
57
64
  const go = (p) => {
58
65
  setDrawer(false);
59
66
  host.go(p);
@@ -70,9 +77,13 @@ function Shell() {
70
77
  const switcher = Boolean(host.person && (host.org || (host.memberships?.length ?? 0) > 0));
71
78
  const top = switcher ? (_jsx(Where, {})) : (_jsx(XStack, { render: "button", "aria-label": "Hanzo", items: "center", onPress: () => go(''), children: _jsx(HanzoMark, { size: 18 }) }));
72
79
  const links = [
80
+ { id: 'projects', label: 'Projects', icon: _jsx(Kanban, { size: 16 }), onPress: () => go(path({ kind: 'screen', screen: 'projects' })), active: screen === 'projects' },
81
+ { id: 'artifacts', label: 'Artifacts', icon: _jsx(Blocks, { size: 16 }), onPress: () => go(path({ kind: 'screen', screen: 'artifacts' })), active: screen === 'artifacts' },
82
+ { id: 'customize', label: 'Customize', icon: _jsx(SlidersHorizontal, { size: 16 }), onPress: () => go(path({ kind: 'customize', tab: 'skills' })), active: r.kind === 'customize' || screen === 'mcp' },
73
83
  { id: 'automations', label: 'Automations', icon: _jsx(Workflow, { size: 16 }), onPress: () => go(path({ kind: 'screen', screen: 'automations' })), active: screen === 'automations' },
84
+ ];
85
+ const more = [
74
86
  { id: 'codebases', label: 'Codebase', icon: _jsx(FolderGit2, { size: 16 }), onPress: () => go(path({ kind: 'screen', screen: 'codebases' })), active: screen === 'codebases' },
75
- { id: 'projects', label: 'Projects', icon: _jsx(Kanban, { size: 16 }), onPress: () => go(path({ kind: 'screen', screen: 'projects' })), active: screen === 'projects' },
76
87
  {
77
88
  id: 'issues',
78
89
  label: 'Issues',
@@ -83,22 +94,25 @@ function Shell() {
83
94
  },
84
95
  active: screen === 'issues',
85
96
  },
86
- { id: 'artifacts', label: 'Artifacts', icon: _jsx(Blocks, { size: 16 }), onPress: () => go(path({ kind: 'screen', screen: 'artifacts' })), active: screen === 'artifacts' },
87
- { id: 'mcp', label: 'MCP', icon: _jsx(Puzzle, { size: 16 }), onPress: () => go(path({ kind: 'screen', screen: 'mcp' })), active: screen === 'mcp' },
88
- ];
89
- const more = [
90
97
  { id: 'templates', label: 'Templates', icon: _jsx(LayoutTemplate, { size: 16 }), onPress: () => go(path({ kind: 'screen', screen: 'templates' })), active: screen === 'templates' },
91
98
  {
92
99
  id: 'machines',
93
100
  label: 'Machines',
94
101
  icon: _jsx(Cpu, { size: 16 }),
95
- onPress: () => host.go(''),
102
+ onPress: () => go(path({ kind: 'settings', section: 'machines' })),
103
+ active: r.kind === 'settings' && r.section === 'machines',
96
104
  },
97
- { id: 'docs', label: 'Docs', icon: _jsx(BookOpen, { size: 16 }), onPress: () => window.open('https://docs.hanzo.ai/docs/dev', '_blank', 'noopener,noreferrer') },
105
+ { id: 'docs', label: 'Docs', icon: _jsx(BookOpen, { size: 16 }), onPress: () => window.open(DOCS, '_blank', 'noopener,noreferrer') },
98
106
  ];
99
- return (_jsxs(XStack, { flex: 1, minH: 0, minW: 0, bg: "$background", children: [_jsxs(YStack, { position: "relative", shrink: 0, children: [_jsx(SessionRail, { onNew: () => go(''), fresh: r.kind === 'new', links: links, more: more, recents: rows, active: r.kind === 'run' ? r.id : null, onOpen: (id) => go(id), onSort: () => setOrder(order === 'newest' ? 'running' : 'newest'), sortLabel: order === 'newest' ? 'Show running first' : 'Show newest first', empty: _jsx(SizableText, { size: "$1", color: "$soft", px: "$2", children: !host.person ? 'Sign in to see your runs.' : recents.error ? recents.error.message : recents.loading ? 'Reading…' : 'No runs yet.' }), account: host.person
100
- ? { name: host.person.email || host.person.name, onPress: () => setAccount(true) }
101
- : { name: 'Sign in', onPress: () => host.signIn?.() }, onSettings: () => setAccount(true), onSearch: () => setFinding(true), collapsed: collapsed, onCollapse: setCollapsed, mark: _jsx(Brand, { org: host.org }), pt: collapsed ? undefined : switcher ? 80 : 40, open: drawer, onOpenChange: setDrawer, label: "Runs" }), collapsed ? null : (_jsx(XStack, { position: "absolute", t: 8, l: 8, r: 8, z: 2, items: "center", "$max-md": { display: 'none' }, children: top }))] }), _jsxs(YStack, { flex: 1, minW: 0, minH: 0, position: "relative", children: [_jsxs(XStack, { height: 44, px: "$2", gap: "$2", items: "center", shrink: 0, $md: { display: 'none' }, children: [_jsx(Button, { variant: "ghost", size: "icon-sm", onPress: () => setDrawer(true), "aria-label": "Open runs", children: _jsx(Menu, { size: 18 }) }), top] }), _jsx(Pane, { onStarted: () => recents.reload() })] }), _jsx(Account, { open: account, onOpenChange: setAccount }), _jsx(Find, { open: finding, onOpenChange: setFinding, recents: rows, onOpen: (id) => { setFinding(false); go(id); } })] }));
107
+ return (_jsxs(XStack, { flex: 1, minH: 0, minW: 0, bg: "$background", children: [_jsxs(YStack, { position: "relative", shrink: 0, children: [_jsx(SessionRail, { onNew: () => go(''), fresh: r.kind === 'new', links: links, more: more, recents: rows, active: r.kind === 'run' ? r.id : null, onOpen: (id) => go(id), onSort: () => setOrder(order === 'newest' ? 'running' : 'newest'), sortLabel: order === 'newest' ? 'Show running first' : 'Show newest first', empty: _jsx(SizableText, { size: "$1", color: "$soft", px: "$2", children: !host.person ? 'Sign in to see your runs.' : recents.error ? recents.error.message : recents.loading ? 'Reading…' : 'No runs yet.' }), notice: host.person && !slack ? (_jsx(Slack, { onOpen: () => go(path({ kind: 'settings', section: 'integrations' })), onDismiss: () => setSlack(true) })) : undefined, account: host.person
108
+ ? {
109
+ name: host.person.email || host.person.name,
110
+ onPress: () => {
111
+ setDrawer(false);
112
+ setMenu(true);
113
+ },
114
+ }
115
+ : { name: 'Sign in', onPress: () => host.signIn?.() }, onSettings: () => go(path({ kind: 'settings', section: 'general' })), onSearch: () => setFinding(true), collapsed: collapsed, onCollapse: setCollapsed, mark: _jsx(Brand, { org: host.org }), pt: collapsed ? undefined : switcher ? 80 : 40, open: drawer, onOpenChange: setDrawer, label: "Runs" }), collapsed ? null : (_jsx(XStack, { position: "absolute", t: 8, l: 8, r: 8, z: 2, items: "center", "$max-md": { display: 'none' }, children: top })), host.person ? _jsx(Who, { open: menu, onOpenChange: setMenu, narrow: collapsed }) : null] }), _jsxs(YStack, { flex: 1, minW: 0, minH: 0, position: "relative", children: [_jsxs(XStack, { height: 44, px: "$2", gap: "$2", items: "center", shrink: 0, $md: { display: 'none' }, children: [_jsx(Button, { variant: "ghost", size: "icon-sm", onPress: () => setDrawer(true), "aria-label": "Open runs", children: _jsx(Menu, { size: 18 }) }), top] }), _jsx(Pane, { onStarted: () => recents.reload() })] }), _jsx(Find, { open: finding, onOpenChange: setFinding, recents: rows, onOpen: (id) => { setFinding(false); go(id); } })] }));
102
116
  }
103
117
  /** Whichever pane the address names, beside a rail — the builder's or the host's. */
104
118
  function Pane({ onStarted }) {
@@ -106,11 +120,17 @@ function Pane({ onStarted }) {
106
120
  const r = route(host.path);
107
121
  if (r.kind === 'run')
108
122
  return _jsx(Run, { id: r.id }, r.id);
123
+ if (r.kind === 'settings')
124
+ return _jsx(Settings, { section: r.section });
125
+ if (r.kind === 'customize')
126
+ return _jsx(Customize, { tab: r.tab });
109
127
  if (r.kind === 'screen') {
110
128
  if (r.screen === 'artifacts')
111
129
  return _jsx(Artifacts, {});
112
130
  if (r.screen === 'templates')
113
131
  return _jsx(Templates, {});
132
+ if (r.screen === 'plans')
133
+ return _jsx(Plans, {});
114
134
  return _jsx(Forge, { screen: r.screen }, r.screen);
115
135
  }
116
136
  return (_jsx(Landing, { onStarted: (id) => {
@@ -139,5 +159,5 @@ function Screens({ rail }) {
139
159
  * places and runs in its own rail with `DevSection`.
140
160
  */
141
161
  export function Builder({ host, rail = true }) {
142
- return (_jsx(HostProvider, { host: host, children: _jsx(YStack, { flex: 1, minH: 0, minW: 0, height: "100%", bg: "$background", children: _jsx(Screens, { rail: rail }) }) }));
162
+ return (_jsx(HostProvider, { host: host, children: _jsx(PrefsProvider, { children: _jsx(YStack, { flex: 1, minH: 0, minW: 0, height: "100%", bg: "$background", children: _jsx(Screens, { rail: rail }) }) }) }));
143
163
  }
@@ -0,0 +1,2 @@
1
+ import { type Pane } from './ui.tsx';
2
+ export declare function Agents({ view, q, adding, onAdding, onView }: Pane): import("react").JSX.Element;