@hanzo/build 0.2.10 → 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 (136) hide show
  1. package/README.md +97 -25
  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/coding.d.ts +31 -1
  11. package/lib/api/coding.js +50 -1
  12. package/lib/api/connectors.d.ts +90 -0
  13. package/lib/api/connectors.js +109 -0
  14. package/lib/api/consent.d.ts +22 -0
  15. package/lib/api/consent.js +28 -0
  16. package/lib/api/github.d.ts +13 -0
  17. package/lib/api/github.js +9 -0
  18. package/lib/api/harness.d.ts +44 -0
  19. package/lib/api/harness.js +348 -0
  20. package/lib/api/keys.d.ts +41 -0
  21. package/lib/api/keys.js +55 -0
  22. package/lib/api/machines.d.ts +56 -0
  23. package/lib/api/machines.js +84 -0
  24. package/lib/api/members.d.ts +47 -0
  25. package/lib/api/members.js +88 -0
  26. package/lib/api/memory.d.ts +25 -0
  27. package/lib/api/memory.js +34 -0
  28. package/lib/api/places.d.ts +2 -2
  29. package/lib/api/places.js +3 -26
  30. package/lib/api/plugins.d.ts +55 -0
  31. package/lib/api/plugins.js +62 -0
  32. package/lib/api/pref.d.ts +49 -0
  33. package/lib/api/pref.js +68 -0
  34. package/lib/api/profile.d.ts +21 -0
  35. package/lib/api/profile.js +40 -0
  36. package/lib/api/projects.d.ts +19 -2
  37. package/lib/api/projects.js +29 -4
  38. package/lib/api/provider.d.ts +42 -0
  39. package/lib/api/provider.js +58 -0
  40. package/lib/api/sessions.d.ts +32 -4
  41. package/lib/api/sessions.js +42 -6
  42. package/lib/api/skills.d.ts +70 -0
  43. package/lib/api/skills.js +90 -0
  44. package/lib/api/tools.d.ts +34 -0
  45. package/lib/api/tools.js +37 -0
  46. package/lib/api/turn.d.ts +84 -16
  47. package/lib/api/turn.js +221 -4
  48. package/lib/api/webhooks.d.ts +60 -0
  49. package/lib/api/webhooks.js +81 -0
  50. package/lib/ask.d.ts +18 -0
  51. package/lib/ask.js +69 -0
  52. package/lib/builder.js +39 -19
  53. package/lib/customize/agents.d.ts +2 -0
  54. package/lib/customize/agents.js +125 -0
  55. package/lib/customize/connectors.d.ts +2 -0
  56. package/lib/customize/connectors.js +197 -0
  57. package/lib/customize/index.d.ts +6 -0
  58. package/lib/customize/index.js +50 -0
  59. package/lib/customize/plugins.d.ts +2 -0
  60. package/lib/customize/plugins.js +93 -0
  61. package/lib/customize/skills.d.ts +2 -0
  62. package/lib/customize/skills.js +125 -0
  63. package/lib/customize/ui.d.ts +110 -0
  64. package/lib/customize/ui.js +99 -0
  65. package/lib/desk.d.ts +4 -1
  66. package/lib/desk.js +3 -16
  67. package/lib/find.d.ts +7 -0
  68. package/lib/find.js +77 -0
  69. package/lib/foot.d.ts +16 -0
  70. package/lib/foot.js +36 -0
  71. package/lib/forge.js +4 -4
  72. package/lib/git.d.ts +4 -0
  73. package/lib/git.js +6 -2
  74. package/lib/host.d.ts +5 -0
  75. package/lib/landing.d.ts +10 -0
  76. package/lib/landing.js +30 -14
  77. package/lib/markdown.d.ts +49 -0
  78. package/lib/markdown.js +123 -0
  79. package/lib/plans.d.ts +1 -0
  80. package/lib/plans.js +113 -0
  81. package/lib/prefs.d.ts +30 -0
  82. package/lib/prefs.js +83 -0
  83. package/lib/prose.d.ts +4 -0
  84. package/lib/prose.js +53 -0
  85. package/lib/route.d.ts +16 -1
  86. package/lib/route.js +32 -1
  87. package/lib/run.js +184 -32
  88. package/lib/section.js +2 -2
  89. package/lib/settings/account.d.ts +5 -0
  90. package/lib/settings/account.js +111 -0
  91. package/lib/settings/billing.d.ts +1 -0
  92. package/lib/settings/billing.js +104 -0
  93. package/lib/settings/capabilities.d.ts +1 -0
  94. package/lib/settings/capabilities.js +55 -0
  95. package/lib/settings/card.d.ts +31 -0
  96. package/lib/settings/card.js +105 -0
  97. package/lib/settings/code.d.ts +1 -0
  98. package/lib/settings/code.js +51 -0
  99. package/lib/settings/environments.d.ts +1 -0
  100. package/lib/settings/environments.js +53 -0
  101. package/lib/settings/general.d.ts +1 -0
  102. package/lib/settings/general.js +44 -0
  103. package/lib/settings/index.d.ts +4 -0
  104. package/lib/settings/index.js +22 -0
  105. package/lib/settings/integrations.d.ts +1 -0
  106. package/lib/settings/integrations.js +81 -0
  107. package/lib/settings/keys.d.ts +1 -0
  108. package/lib/settings/keys.js +102 -0
  109. package/lib/settings/machines.d.ts +1 -0
  110. package/lib/settings/machines.js +118 -0
  111. package/lib/settings/members.d.ts +1 -0
  112. package/lib/settings/members.js +67 -0
  113. package/lib/settings/memory.d.ts +1 -0
  114. package/lib/settings/memory.js +56 -0
  115. package/lib/settings/notifications.d.ts +1 -0
  116. package/lib/settings/notifications.js +97 -0
  117. package/lib/settings/privacy.d.ts +1 -0
  118. package/lib/settings/privacy.js +63 -0
  119. package/lib/settings/sections.d.ts +16 -0
  120. package/lib/settings/sections.js +32 -0
  121. package/lib/settings/ui.d.ts +49 -0
  122. package/lib/settings/ui.js +47 -0
  123. package/lib/settings/usage.d.ts +1 -0
  124. package/lib/settings/usage.js +114 -0
  125. package/lib/shelf.d.ts +6 -0
  126. package/lib/shelf.js +86 -16
  127. package/lib/switch.js +1 -2
  128. package/lib/transcript.d.ts +12 -0
  129. package/lib/transcript.js +56 -0
  130. package/lib/voice.d.ts +3 -1
  131. package/lib/voice.js +10 -4
  132. package/package.json +1 -1
  133. package/lib/account.d.ts +0 -14
  134. package/lib/account.js +0 -38
  135. package/lib/mcp.d.ts +0 -1
  136. package/lib/mcp.js +0 -28
@@ -0,0 +1,300 @@
1
+ /**
2
+ * The organization's money: its plan, the cards it pays with, its invoices, its
3
+ * balance and credits, what it has spent this month, and the caps on that spend.
4
+ *
5
+ * GET /v1/billing/plans the catalog the serving brand sells (public)
6
+ * GET /v1/billing/subscriptions {count, subscriptions}
7
+ * POST /v1/billing/subscribe/card buy a plan with a saved card; one paid plan at a time (409)
8
+ * POST /v1/billing/subscriptions/{id}/cancel at the end of the paid period
9
+ * POST /v1/billing/subscriptions/{id}/reactivate
10
+ * GET /v1/billing/methods saved cards, newest first
11
+ * POST /v1/billing/methods save a card from the processor's single-use token
12
+ * DELETE /v1/billing/methods/{id}
13
+ * GET /v1/billing/settings the processor a browser tokenizes a card against
14
+ * GET /v1/billing/invoices {count, cursor, invoices}
15
+ * GET /v1/billing/invoices/{id}/pdf the invoice as a PDF
16
+ * GET /v1/billing/balance prepaid balance, whole cents
17
+ * GET /v1/billing/credit-balance granted credit, per currency
18
+ * POST /v1/billing/topup charge a saved card into the balance
19
+ * GET /v1/billing/usage/rollup the plan's month: included spend and request windows
20
+ * GET /v1/usage/summary?range=month this month's spend, by category
21
+ * GET /v1/billing/alerts spend caps (bare array)
22
+ * POST /v1/billing/alerts open a cap (org admin)
23
+ * PATCH /v1/billing/alerts/{id} change one (org admin)
24
+ * DELETE /v1/billing/alerts/{id} lift one (org admin)
25
+ *
26
+ * Every figure is whole cents. The wallet, the caps and the cards are the
27
+ * caller's own, resolved by the platform from the credential; nothing here names
28
+ * an org or a subject. No card number passes through this file: a card is saved
29
+ * from the token the processor's own form returns.
30
+ */
31
+ import { call, headers, reason, Refusal, seg } from './call.js';
32
+ const str = (v) => (typeof v === 'string' ? v : '');
33
+ const num = (v) => (typeof v === 'number' && Number.isFinite(v) ? v : 0);
34
+ const obj = (v) => (v && typeof v === 'object' ? v : {});
35
+ const arr = (v) => (Array.isArray(v) ? v : []);
36
+ const words = (v) => arr(v).filter((s) => typeof s === 'string' && s !== '');
37
+ /** `$1,234.56` from whole cents; whole dollars drop the cents. */
38
+ export function money(cents, currency = 'usd') {
39
+ const whole = cents % 100 === 0;
40
+ try {
41
+ return new Intl.NumberFormat('en-US', {
42
+ style: 'currency',
43
+ currency: (currency || 'usd').toUpperCase(),
44
+ minimumFractionDigits: whole ? 0 : 2,
45
+ maximumFractionDigits: 2,
46
+ }).format(cents / 100);
47
+ }
48
+ catch {
49
+ return `${(cents / 100).toFixed(whole ? 0 : 2)} ${currency.toUpperCase()}`;
50
+ }
51
+ }
52
+ /** Whole cents from what a person typed as dollars — `25`, `$25.50` — or null. */
53
+ export function cents(typed) {
54
+ const s = typed.replace(/[$,\s]/g, '');
55
+ if (!/^\d+(\.\d{1,2})?$/.test(s))
56
+ return null;
57
+ const c = Math.round(Number(s) * 100);
58
+ return c > 0 ? c : null;
59
+ }
60
+ export function plan(raw) {
61
+ const p = obj(raw);
62
+ return {
63
+ id: str(p.slug) || str(p.id),
64
+ name: str(p.name),
65
+ description: str(p.description),
66
+ category: str(p.category),
67
+ monthly: num(p.price),
68
+ yearly: num(p.priceAnnual),
69
+ currency: str(p.currency) || 'usd',
70
+ perSeat: p.perSeat === true,
71
+ sales: p.contactSales === true,
72
+ popular: p.popular === true,
73
+ features: words(p.features),
74
+ };
75
+ }
76
+ /** The plans a person can hold. The catalog also carries add-ons (DNS, storage) that are not. */
77
+ const HELD = ['personal', 'team', 'enterprise'];
78
+ export async function plans(t) {
79
+ const raw = await call(t, 'GET', '/v1/billing/plans');
80
+ const rows = Array.isArray(raw) ? raw : arr(obj(raw).plans);
81
+ return rows.map(plan).filter((p) => p.id && p.name && (!p.category || HELD.includes(p.category)));
82
+ }
83
+ export function subscription(raw) {
84
+ const s = obj(raw);
85
+ const p = obj(s.plan);
86
+ return {
87
+ id: str(s.id),
88
+ plan: str(s.planId) || str(p.id),
89
+ name: str(p.name) || str(s.planId),
90
+ price: num(p.price),
91
+ interval: str(p.interval) || 'month',
92
+ currency: str(p.currency) || 'usd',
93
+ seats: num(s.quantity) || 1,
94
+ status: str(s.status),
95
+ ends: str(s.currentPeriodEnd),
96
+ ending: s.cancelAtPeriodEnd === true,
97
+ method: str(s.defaultPaymentMethod),
98
+ };
99
+ }
100
+ /** Only these confer the plan. */
101
+ const LIVE = ['active', 'trialing', 'past_due'];
102
+ /** The plan the org is on, or null for none (the free tier). */
103
+ export function current(list) {
104
+ return list.find((s) => LIVE.includes(s.status)) ?? null;
105
+ }
106
+ export async function subscriptions(t) {
107
+ const raw = obj(await call(t, 'GET', '/v1/billing/subscriptions'));
108
+ return arr(raw.subscriptions).map(subscription).filter((s) => s.id);
109
+ }
110
+ /** Buy a plan. No amount is sent: the catalog prices it, and the platform charges that. */
111
+ export async function subscribe(t, p) {
112
+ if (!p.method)
113
+ throw new Error('Add a card before choosing a paid plan');
114
+ const raw = obj(await call(t, 'POST', '/v1/billing/subscribe/card', { planId: p.plan, paymentMethodId: p.method, interval: p.interval }));
115
+ return { subscription: str(raw.subscriptionId), charged: num(raw.amountCents) };
116
+ }
117
+ /** End a plan when its paid period does, not before. */
118
+ export async function cancel(t, id) {
119
+ return subscription(await call(t, 'POST', `/v1/billing/subscriptions/${seg(id)}/cancel`, { atPeriodEnd: true }));
120
+ }
121
+ export async function reactivate(t, id) {
122
+ return subscription(await call(t, 'POST', `/v1/billing/subscriptions/${seg(id)}/reactivate`, {}));
123
+ }
124
+ export function method(raw) {
125
+ const m = obj(raw);
126
+ const c = obj(m.card);
127
+ const month = num(c.expMonth);
128
+ const year = num(c.expYear);
129
+ return {
130
+ id: str(m.id),
131
+ type: str(m.type) || 'card',
132
+ brand: str(c.brand),
133
+ last4: str(c.last4),
134
+ expires: month && year ? `${String(month).padStart(2, '0')}/${String(year % 100).padStart(2, '0')}` : '',
135
+ default: m.isDefault === true,
136
+ name: str(m.name),
137
+ };
138
+ }
139
+ /** How a saved method reads in a row: `Visa •••• 4242`. */
140
+ export function label(m) {
141
+ if (m.last4)
142
+ return `${m.brand ? m.brand.charAt(0).toUpperCase() + m.brand.slice(1) : 'Card'} •••• ${m.last4}`;
143
+ return m.name || m.type;
144
+ }
145
+ export async function methods(t) {
146
+ const raw = await call(t, 'GET', '/v1/billing/methods');
147
+ const rows = Array.isArray(raw) ? raw : arr(obj(raw).data);
148
+ return rows.map(method).filter((m) => m.id);
149
+ }
150
+ /** The card a charge uses: the default, or the newest. */
151
+ export function chosen(list) {
152
+ return list.find((m) => m.default) ?? list[0] ?? null;
153
+ }
154
+ /** Save a card from the single-use token the processor's form returned. */
155
+ export async function save(t, token) {
156
+ if (!token)
157
+ throw new Error('The card form returned no token');
158
+ return method(await call(t, 'POST', '/v1/billing/methods', { type: 'card', providerRef: token }));
159
+ }
160
+ export async function detach(t, id) {
161
+ await call(t, 'DELETE', `/v1/billing/methods/${seg(id)}`);
162
+ }
163
+ export async function processor(t) {
164
+ const p = obj(await call(t, 'GET', '/v1/billing/settings'));
165
+ return {
166
+ provider: str(p.provider),
167
+ application: str(p.applicationId),
168
+ location: str(p.locationId),
169
+ environment: str(p.environment),
170
+ live: p.live === true,
171
+ };
172
+ }
173
+ export function invoice(raw) {
174
+ const i = obj(raw);
175
+ const subtotal = num(i.subtotal);
176
+ const tax = num(i.tax);
177
+ const discount = num(i.discount);
178
+ return {
179
+ id: str(i.id),
180
+ number: str(i.numberStr) || (num(i.number) ? String(num(i.number)) : ''),
181
+ date: str(i.createdAt),
182
+ status: str(i.status),
183
+ currency: str(i.currency) || 'usd',
184
+ subtotal,
185
+ tax,
186
+ discount,
187
+ credit: num(i.creditApplied),
188
+ paid: num(i.amountPaid),
189
+ due: num(i.amountDue),
190
+ total: subtotal - discount + tax,
191
+ start: str(i.periodStart),
192
+ end: str(i.periodEnd),
193
+ lines: arr(i.lineItems)
194
+ .map(obj)
195
+ .map((l) => ({ description: str(l.description) || str(l.planName), amount: num(l.amount) })),
196
+ };
197
+ }
198
+ export async function invoices(t) {
199
+ const raw = obj(await call(t, 'GET', '/v1/billing/invoices'));
200
+ return arr(raw.invoices).map(invoice).filter((i) => i.id);
201
+ }
202
+ /** The invoice as a PDF, read with the caller's bearer. */
203
+ export async function pdf(t, id) {
204
+ const res = await fetch(`${t.api}/v1/billing/invoices/${seg(id)}/pdf`, { headers: headers(t), cache: 'no-store' });
205
+ if (!res.ok)
206
+ throw new Refusal(res.status, (await reason(res)) || `The invoice answered ${res.status}`);
207
+ return res.blob();
208
+ }
209
+ // ── balance, credit, spend ───────────────────────────────────────────────────
210
+ /** What the org can still spend from its prepaid balance, in cents. */
211
+ export async function balance(t) {
212
+ const b = obj(await call(t, 'GET', '/v1/billing/balance'));
213
+ return num(b.available ?? b.balance);
214
+ }
215
+ /** Granted credit the org can spend, in US cents. */
216
+ export async function credit(t) {
217
+ const raw = obj(await call(t, 'GET', '/v1/billing/credit-balance'));
218
+ return arr(raw.balances)
219
+ .map(obj)
220
+ .filter((b) => (str(b.currency) || 'usd').toLowerCase() === 'usd')
221
+ .reduce((sum, b) => sum + num(b.available), 0);
222
+ }
223
+ /** Charge a saved card and add the amount to the balance; answers the balance after. */
224
+ export async function topup(t, amount, card) {
225
+ if (!Number.isInteger(amount) || amount <= 0)
226
+ throw new Error('Enter an amount to add');
227
+ if (!card)
228
+ throw new Error('Add a card before buying more');
229
+ const r = obj(await call(t, 'POST', '/v1/billing/topup', { amountCents: amount, paymentMethodId: card }));
230
+ return num(r.balanceCents);
231
+ }
232
+ export async function month(t) {
233
+ const r = obj(await call(t, 'GET', '/v1/billing/usage/rollup'));
234
+ const inc = obj(r.included);
235
+ return {
236
+ plan: str(r.plan),
237
+ period: str(r.period),
238
+ included: num(inc.monthlyCents) + num(inc.grantedCents),
239
+ used: num(inc.consumedCents),
240
+ overage: num(r.overageCents),
241
+ windows: arr(r.windows)
242
+ .map(obj)
243
+ .map((w) => ({ span: str(w.span), limit: num(w.limit), used: num(w.used), resets: str(w.resets) }))
244
+ .filter((w) => w.span),
245
+ };
246
+ }
247
+ export async function spend(t) {
248
+ const r = obj(await call(t, 'GET', '/v1/usage/summary?range=month'));
249
+ const s = obj(r.spend);
250
+ return {
251
+ known: s.available === true,
252
+ total: num(s.mtdCents) || num(s.totalCents),
253
+ categories: arr(s.byCategory)
254
+ .map(obj)
255
+ .map((c) => ({ name: str(c.category) || 'Uncategorized', cents: num(c.cents) }))
256
+ .filter((c) => c.cents > 0),
257
+ };
258
+ }
259
+ export function cap(raw) {
260
+ const a = obj(raw);
261
+ return {
262
+ id: str(a.id),
263
+ title: str(a.title),
264
+ threshold: num(a.threshold),
265
+ enforce: a.enforce === true,
266
+ project: str(a.project),
267
+ service: str(a.service),
268
+ spent: typeof a.periodSpentCents === 'number' ? a.periodSpentCents : null,
269
+ resets: str(a.resetsAt),
270
+ };
271
+ }
272
+ export async function caps(t) {
273
+ const raw = await call(t, 'GET', '/v1/billing/alerts');
274
+ return arr(raw).map(cap).filter((c) => c.id);
275
+ }
276
+ /** The org-wide monthly cap: no project, no service, a ceiling. */
277
+ export function monthly(list) {
278
+ return list.find((c) => !c.project && !c.service && c.threshold > 0) ?? null;
279
+ }
280
+ /** Set the org's monthly cap: change the one there is, or open one that refuses spend past it. */
281
+ export async function limit(t, list, threshold) {
282
+ if (!Number.isInteger(threshold) || threshold <= 0)
283
+ throw new Error('Enter a monthly limit');
284
+ const was = monthly(list);
285
+ if (was)
286
+ return cap(await call(t, 'PATCH', `/v1/billing/alerts/${seg(was.id)}`, { threshold }));
287
+ return cap(await call(t, 'POST', '/v1/billing/alerts', {
288
+ title: 'Monthly limit',
289
+ threshold,
290
+ currency: 'usd',
291
+ project: '',
292
+ service: '',
293
+ enforce: true,
294
+ softPct: 80,
295
+ rateLimitRpm: 0,
296
+ }));
297
+ }
298
+ export async function lift(t, id) {
299
+ await call(t, 'DELETE', `/v1/billing/alerts/${seg(id)}`);
300
+ }
package/lib/api/call.d.ts CHANGED
@@ -36,3 +36,9 @@ export declare const seg: (s: string) => string;
36
36
  export declare function call<T>(t: Target, method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE', path: string, body?: unknown, init?: {
37
37
  signal?: AbortSignal;
38
38
  }): Promise<T>;
39
+ /**
40
+ * The `{status, msg, data}` envelope IAM and the model service answer with. A
41
+ * refusal there is `status: "error"` with its sentence in `msg`, whatever the
42
+ * HTTP status was — often 200 — so it is read here and thrown like any other.
43
+ */
44
+ export declare function unwrap(raw: unknown): unknown;
package/lib/api/call.js CHANGED
@@ -73,3 +73,14 @@ export async function call(t, method, path, body, init) {
73
73
  const text = await res.text();
74
74
  return (text ? JSON.parse(text) : undefined);
75
75
  }
76
+ /**
77
+ * The `{status, msg, data}` envelope IAM and the model service answer with. A
78
+ * refusal there is `status: "error"` with its sentence in `msg`, whatever the
79
+ * HTTP status was — often 200 — so it is read here and thrown like any other.
80
+ */
81
+ export function unwrap(raw) {
82
+ const r = (raw && typeof raw === 'object' ? raw : {});
83
+ if (r.status === 'error')
84
+ throw new Refusal(400, typeof r.msg === 'string' && r.msg ? r.msg : 'The platform refused that');
85
+ return r.data;
86
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * What the agent may use: every tool the org can reach, and which of them are
3
+ * switched on.
4
+ *
5
+ * GET /v1/tool → {tools: [{name, source, description, dispatchable, activated}]}
6
+ * PUT /v1/tool/activation {activate: [], deactivate: []} → {enabled: []}
7
+ *
8
+ * Activation is per org (and project): a tool that is off is listed but
9
+ * refused when an agent calls it. At most 256 names go in one request, so a
10
+ * larger change is sent in turns.
11
+ */
12
+ import { type Target } from './call.ts';
13
+ /** Where a tool comes from, in the order the platform ranks them. */
14
+ export declare const SOURCES: readonly ['connector', 'function', 'zap-service', 'agent', 'skill', 'mcp'];
15
+ export type Source = (typeof SOURCES)[number];
16
+ export interface Tool {
17
+ name: string;
18
+ source: Source;
19
+ description: string;
20
+ activated: boolean;
21
+ }
22
+ export declare function toolOf(raw: unknown): Tool | null;
23
+ export declare function tools(t: Target): Promise<Tool[]>;
24
+ /** Switch every tool in `names` on (or off), and answer the org's activated set. */
25
+ export declare function activate(t: Target, names: string[], on: boolean): Promise<string[]>;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * What the agent may use: every tool the org can reach, and which of them are
3
+ * switched on.
4
+ *
5
+ * GET /v1/tool → {tools: [{name, source, description, dispatchable, activated}]}
6
+ * PUT /v1/tool/activation {activate: [], deactivate: []} → {enabled: []}
7
+ *
8
+ * Activation is per org (and project): a tool that is off is listed but
9
+ * refused when an agent calls it. At most 256 names go in one request, so a
10
+ * larger change is sent in turns.
11
+ */
12
+ import { call } from './call.js';
13
+ /** Where a tool comes from, in the order the platform ranks them. */
14
+ export const SOURCES = ['connector', 'function', 'zap-service', 'agent', 'skill', 'mcp'];
15
+ const BATCH = 256;
16
+ const str = (v) => (typeof v === 'string' ? v : '');
17
+ export function toolOf(raw) {
18
+ const o = (raw && typeof raw === 'object' ? raw : {});
19
+ const name = str(o.name);
20
+ const source = str(o.source);
21
+ if (!name || !SOURCES.includes(source))
22
+ return null;
23
+ return { name, source, description: str(o.description), activated: o.activated === true };
24
+ }
25
+ export async function tools(t) {
26
+ const r = await call(t, 'GET', '/v1/tool');
27
+ return (Array.isArray(r?.tools) ? r.tools : []).map(toolOf).filter((x) => x !== null);
28
+ }
29
+ /** Switch every tool in `names` on (or off), and answer the org's activated set. */
30
+ export async function activate(t, names, on) {
31
+ if (!names.length)
32
+ throw new Error('There is nothing to switch');
33
+ let enabled = [];
34
+ for (let i = 0; i < names.length; i += BATCH) {
35
+ const part = names.slice(i, i + BATCH);
36
+ const r = await call(t, 'PUT', '/v1/tool/activation', on ? { activate: part, deactivate: [] } : { activate: [], deactivate: part });
37
+ enabled = Array.isArray(r?.enabled) ? r.enabled.filter((n) => typeof n === 'string') : [];
38
+ }
39
+ return enabled;
40
+ }
@@ -30,7 +30,11 @@ export declare const unhonoured: (mode: Mode | undefined, target?: string) => st
30
30
  export interface Ask {
31
31
  /** The task, in the words you would use with a colleague. */
32
32
  prompt: string;
33
- /** The repository's name in the caller's org. A slash is not a name. Omitted starts something new. */
33
+ /**
34
+ * The repository: its name in the caller's org, or `owner/name` as a run's
35
+ * record states it, which the engine resolves as it did for that run.
36
+ * Omitted starts something new.
37
+ */
34
38
  repo?: string;
35
39
  /** The branch to start from. Omitted takes the repository's default. */
36
40
  base?: string;
@@ -59,3 +63,29 @@ export interface Run {
59
63
  */
60
64
  export declare function body(ask: Ask): Record<string, string | boolean>;
61
65
  export declare function start(t: Target, ask: Ask): Promise<Run>;
66
+ /** A run a follow-up continues, as its record states it. */
67
+ export interface Earlier {
68
+ id: string;
69
+ title: string;
70
+ /** `owner/name`, as the record states it. */
71
+ repo: string;
72
+ /** The branch it started from, or '' for the repository's default. */
73
+ base: string;
74
+ /** 'sandbox', or the id of the machine it ran on. */
75
+ environment: string;
76
+ project: string;
77
+ mode: string;
78
+ /** Its work is on its own branch (turn.ts `settled`). */
79
+ pushed: boolean;
80
+ }
81
+ /** The earlier run's ask, without the codebase its title leads with (`universe: add the widget`). */
82
+ export declare function headline(title: string, repo: string): string;
83
+ /**
84
+ * A new run continuing an earlier one in its mode: what the person said, and
85
+ * which run it follows, because the new run's agent reads nothing else. With
86
+ * nothing said it goes on with the earlier ask, in the words the platform uses
87
+ * when it carries a paused run on (apps/coding steer.go followUp).
88
+ */
89
+ export declare function followUp(e: Earlier, said: string): Ask;
90
+ /** A build of what a plan run answered: its ask as the title, and the plan to carry out. */
91
+ export declare function approve(e: Earlier, plan: string): Ask;
package/lib/api/coding.js CHANGED
@@ -26,7 +26,7 @@
26
26
  import { call, Refusal } from './call.js';
27
27
  /** Why this mode cannot run where it is sent, or '' when it can. */
28
28
  export const unhonoured = (mode, target) => (mode === 'plan' || mode === 'setup') && target?.trim()
29
- ? `A ${mode} runs in the Hanzo sandbox: a machine clones and pushes with its own credential. Choose Default, or switch to Build.`
29
+ ? `A ${mode} runs in the Hanzo sandbox: a machine clones and pushes with its own credential. Choose Cloud, or switch to Build.`
30
30
  : '';
31
31
  /**
32
32
  * The request body: only what was asked, with no empty strings sent as values.
@@ -63,3 +63,52 @@ export async function start(t, ask) {
63
63
  target: s('targetId'),
64
64
  };
65
65
  }
66
+ /** The earlier run's ask, without the codebase its title leads with (`universe: add the widget`). */
67
+ export function headline(title, repo) {
68
+ const name = repo.split('/').filter(Boolean).pop() ?? '';
69
+ const t = title.trim();
70
+ return name && t.startsWith(`${name}: `) ? t.slice(name.length + 2).trim() : t;
71
+ }
72
+ /**
73
+ * The same codebase, place and project as the earlier run, starting from its
74
+ * branch when it pushed one. `after` IS that branch as the base (BaseOf,
75
+ * apps/coding coding.go), so it is named only then: a plan, a setup, or a build
76
+ * that changed nothing pushed no branch to clone, and its follow-up starts where
77
+ * it did.
78
+ */
79
+ function from(e) {
80
+ const place = e.environment && e.environment !== 'sandbox' ? e.environment : undefined;
81
+ return {
82
+ repo: e.repo || undefined,
83
+ project: e.project || undefined,
84
+ targetId: place,
85
+ ...(e.pushed ? { after: e.id } : { base: e.base || undefined }),
86
+ };
87
+ }
88
+ const modeOf = (m) => (m === 'plan' || m === 'setup' ? m : 'build');
89
+ /**
90
+ * A new run continuing an earlier one in its mode: what the person said, and
91
+ * which run it follows, because the new run's agent reads nothing else. With
92
+ * nothing said it goes on with the earlier ask, in the words the platform uses
93
+ * when it carries a paused run on (apps/coding steer.go followUp).
94
+ */
95
+ export function followUp(e, said) {
96
+ const head = headline(e.title, e.repo);
97
+ const words = said.trim();
98
+ let prompt;
99
+ if (words) {
100
+ const was = head ? `This follows an earlier run on this codebase: “${head}”.` : 'This follows an earlier run on this codebase.';
101
+ prompt = `${words}\n\n${was}${e.pushed ? ' Its work so far is on this branch; build on it.' : ''}`;
102
+ }
103
+ else {
104
+ const kept = e.pushed ? 'An earlier run already worked on this; its work so far is on this branch. Build on it. ' : '';
105
+ prompt = `${head}\n\n${kept}Continue where the earlier run left off.`.trim();
106
+ }
107
+ return { ...from(e), prompt, mode: modeOf(e.mode) };
108
+ }
109
+ /** A build of what a plan run answered: its ask as the title, and the plan to carry out. */
110
+ export function approve(e, plan) {
111
+ const head = headline(e.title, e.repo);
112
+ const ask = `Carry out this plan:\n\n${plan.trim()}`;
113
+ return { ...from(e), prompt: head ? `${head}\n\n${ask}` : ask, mode: 'build' };
114
+ }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Connectors: the MCP servers an org adds, and the shelf it picks them from.
3
+ *
4
+ * GET /v1/tool/mcp/servers {servers: [server]}
5
+ * POST /v1/tool/mcp/servers {name, url} or {listing, name?}, with {authHeader, secret} → 201 server
6
+ * DELETE /v1/tool/mcp/servers/{id} 204
7
+ * GET /v1/tool/catalog?q=&limit=&offset= {catalog: [listing], total, limit, offset}
8
+ * GET /v1/tool/catalog/{id} one listing in full
9
+ *
10
+ * A server's secret is sealed in KMS by the platform and never answered again;
11
+ * a record says only whether it has one. Its tools are GET /v1/tool?source=mcp
12
+ * (tools.ts), named `<server id>_<tool>`, and each is called only once it is on.
13
+ * A listing can be added here only when it serves streamable HTTP; one that
14
+ * ships only a package needs somewhere to run first. The fleet's own servers,
15
+ * on for every run, are mcp.ts.
16
+ */
17
+ import { type Target } from './call.ts';
18
+ export interface Server {
19
+ /** The server's id in the org, and the prefix of every tool it brings. */
20
+ id: string;
21
+ name: string;
22
+ url: string;
23
+ /** The header the sealed secret is sent in, or ''. */
24
+ header: string;
25
+ /** Whether a secret is sealed for it. */
26
+ secret: boolean;
27
+ /** The catalog listing it was added from, or '' for a URL typed in. */
28
+ listing: string;
29
+ created: number;
30
+ }
31
+ export interface Remote {
32
+ transport: string;
33
+ url: string;
34
+ }
35
+ export interface Package {
36
+ registry: string;
37
+ identifier: string;
38
+ runtime: string;
39
+ version: string;
40
+ }
41
+ export interface Listing {
42
+ id: string;
43
+ /** The publisher's reverse-DNS name, `com.stripe/mcp`. */
44
+ name: string;
45
+ title: string;
46
+ description: string;
47
+ vendor: string;
48
+ version: string;
49
+ logo: string;
50
+ featured: boolean;
51
+ official: boolean;
52
+ transports: string[];
53
+ remotes: Remote[];
54
+ packages: Package[];
55
+ repo: string;
56
+ site: string;
57
+ }
58
+ export interface Page {
59
+ listings: Listing[];
60
+ total: number;
61
+ offset: number;
62
+ }
63
+ export declare function server(raw: unknown): Server;
64
+ export declare function listing(raw: unknown): Listing;
65
+ /** What a listing is called on screen. */
66
+ export declare const titleOf: (l: Listing) => string;
67
+ /** Whether a listing can be added here and now: the platform dials its streamable-HTTP remote. */
68
+ export declare const ready: (l: Listing) => boolean;
69
+ /** A server's tools among the plane's, by the prefix its id gives them. */
70
+ export declare const owns: (s: Server, toolName: string) => boolean;
71
+ export declare function servers(t: Target): Promise<Server[]>;
72
+ export interface Adding {
73
+ /** Required with a URL; a listing takes its own title when this is empty. */
74
+ name?: string;
75
+ url?: string;
76
+ listing?: string;
77
+ header?: string;
78
+ secret?: string;
79
+ }
80
+ /** Why a server cannot be added as it stands, or '' when it can. */
81
+ export declare function refuse(a: Adding): string;
82
+ export declare function add(t: Target, a: Adding): Promise<Server>;
83
+ export declare function remove(t: Target, id: string): Promise<void>;
84
+ /** A page of the shelf, featured first, then by name. */
85
+ export declare function shelf(t: Target, q?: {
86
+ text?: string;
87
+ limit?: number;
88
+ offset?: number;
89
+ }): Promise<Page>;
90
+ export declare function one(t: Target, id: string): Promise<Listing>;