@hanzo/build 0.2.79 → 0.2.81

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 (167) hide show
  1. package/README.md +22 -4
  2. package/lib/activity.d.ts +3 -7
  3. package/lib/activity.js +18 -5
  4. package/lib/api/agents.d.ts +36 -35
  5. package/lib/api/agents.js +66 -29
  6. package/lib/api/auto.d.ts +3 -23
  7. package/lib/api/auto.js +39 -22
  8. package/lib/api/billing.d.ts +0 -35
  9. package/lib/api/billing.js +6 -5
  10. package/lib/api/call.d.ts +16 -8
  11. package/lib/api/call.js +32 -1
  12. package/lib/api/capabilities.d.ts +0 -11
  13. package/lib/api/capabilities.js +2 -1
  14. package/lib/api/codebases.d.ts +0 -10
  15. package/lib/api/codebases.js +3 -2
  16. package/lib/api/coding.d.ts +3 -32
  17. package/lib/api/coding.js +13 -7
  18. package/lib/api/connectors.d.ts +1 -17
  19. package/lib/api/connectors.js +5 -4
  20. package/lib/api/environment.d.ts +6 -20
  21. package/lib/api/environment.js +22 -19
  22. package/lib/api/github.d.ts +0 -22
  23. package/lib/api/github.js +3 -2
  24. package/lib/api/keys.d.ts +0 -14
  25. package/lib/api/keys.js +4 -3
  26. package/lib/api/library.d.ts +3 -17
  27. package/lib/api/library.js +23 -22
  28. package/lib/api/machines.d.ts +0 -15
  29. package/lib/api/machines.js +6 -5
  30. package/lib/api/mcp.d.ts +0 -9
  31. package/lib/api/mcp.js +3 -2
  32. package/lib/api/members.d.ts +0 -15
  33. package/lib/api/members.js +4 -3
  34. package/lib/api/memory.d.ts +0 -11
  35. package/lib/api/memory.js +2 -1
  36. package/lib/api/places.d.ts +1 -9
  37. package/lib/api/places.js +12 -1
  38. package/lib/api/plugins.d.ts +1 -16
  39. package/lib/api/plugins.js +4 -3
  40. package/lib/api/profile.d.ts +0 -10
  41. package/lib/api/profile.js +5 -4
  42. package/lib/api/projects.d.ts +0 -14
  43. package/lib/api/projects.js +2 -1
  44. package/lib/api/provider.d.ts +2 -14
  45. package/lib/api/provider.js +12 -10
  46. package/lib/api/sandbox.d.ts +0 -17
  47. package/lib/api/sandbox.js +5 -4
  48. package/lib/api/sessions.d.ts +0 -17
  49. package/lib/api/sessions.js +5 -4
  50. package/lib/api/skills.d.ts +1 -15
  51. package/lib/api/skills.js +5 -4
  52. package/lib/api/studio.d.ts +0 -14
  53. package/lib/api/studio.js +6 -5
  54. package/lib/api/turn.d.ts +6 -28
  55. package/lib/api/turn.js +105 -49
  56. package/lib/api/verdict.d.ts +0 -9
  57. package/lib/api/verdict.js +2 -1
  58. package/lib/api/webhooks.d.ts +1 -14
  59. package/lib/api/webhooks.js +5 -4
  60. package/lib/ask.js +7 -4
  61. package/lib/automations.d.ts +5 -4
  62. package/lib/automations.js +95 -74
  63. package/lib/back.js +10 -4
  64. package/lib/builder.js +7 -5
  65. package/lib/carry.js +20 -16
  66. package/lib/customize/agents.d.ts +3 -0
  67. package/lib/customize/agents.js +152 -55
  68. package/lib/customize/connectors.js +62 -48
  69. package/lib/customize/index.js +45 -14
  70. package/lib/customize/plugins.js +34 -22
  71. package/lib/customize/say.d.ts +13 -1
  72. package/lib/customize/say.js +15 -21
  73. package/lib/customize/skills.js +31 -22
  74. package/lib/customize/ui.d.ts +5 -9
  75. package/lib/customize/ui.js +26 -16
  76. package/lib/data.d.ts +1 -1
  77. package/lib/data.js +3 -2
  78. package/lib/desk.js +45 -33
  79. package/lib/door.js +35 -10
  80. package/lib/environment.js +25 -19
  81. package/lib/files.js +43 -34
  82. package/lib/find.js +12 -9
  83. package/lib/foot.js +7 -4
  84. package/lib/forge.js +61 -40
  85. package/lib/gear.d.ts +2 -1
  86. package/lib/gear.js +6 -4
  87. package/lib/git.js +29 -19
  88. package/lib/host.d.ts +3 -1
  89. package/lib/landing.d.ts +5 -5
  90. package/lib/landing.js +52 -44
  91. package/lib/library.js +73 -51
  92. package/lib/meter.d.ts +1 -1
  93. package/lib/meter.js +14 -5
  94. package/lib/mic.js +12 -5
  95. package/lib/mind.d.ts +1 -0
  96. package/lib/mind.js +5 -3
  97. package/lib/nav.d.ts +6 -5
  98. package/lib/nav.js +37 -30
  99. package/lib/panel.d.ts +2 -1
  100. package/lib/panel.js +52 -36
  101. package/lib/plan.d.ts +4 -20
  102. package/lib/plan.js +26 -10
  103. package/lib/plans.js +49 -30
  104. package/lib/plugins/design.js +3 -2
  105. package/lib/plugins/index.d.ts +23 -4
  106. package/lib/plugins/index.js +11 -26
  107. package/lib/plugins/pdf.js +2 -1
  108. package/lib/plugins/project.js +6 -3
  109. package/lib/plugins/research.js +3 -2
  110. package/lib/plugins/sketch.js +6 -3
  111. package/lib/plugins/slides.js +3 -2
  112. package/lib/plugins/template.js +6 -3
  113. package/lib/plus.js +25 -13
  114. package/lib/prefs.d.ts +0 -14
  115. package/lib/prefs.js +2 -1
  116. package/lib/project.js +69 -62
  117. package/lib/prompt.d.ts +12 -11
  118. package/lib/prompt.js +38 -20
  119. package/lib/publish.js +17 -12
  120. package/lib/reply.js +6 -3
  121. package/lib/run.js +72 -54
  122. package/lib/say.d.ts +2 -0
  123. package/lib/say.js +7 -0
  124. package/lib/scheduled.js +54 -43
  125. package/lib/section.d.ts +1 -0
  126. package/lib/section.js +8 -2
  127. package/lib/settings/account.d.ts +1 -0
  128. package/lib/settings/account.js +31 -21
  129. package/lib/settings/billing.js +55 -28
  130. package/lib/settings/capabilities.js +26 -15
  131. package/lib/settings/card.js +19 -12
  132. package/lib/settings/code.js +22 -11
  133. package/lib/settings/credits.d.ts +4 -4
  134. package/lib/settings/credits.js +34 -21
  135. package/lib/settings/environments.js +21 -14
  136. package/lib/settings/general.js +26 -14
  137. package/lib/settings/index.js +6 -3
  138. package/lib/settings/integrations.js +42 -28
  139. package/lib/settings/keys.js +37 -19
  140. package/lib/settings/list.d.ts +0 -5
  141. package/lib/settings/list.js +24 -15
  142. package/lib/settings/machines.d.ts +3 -0
  143. package/lib/settings/machines.js +63 -38
  144. package/lib/settings/members.js +23 -14
  145. package/lib/settings/memory.js +12 -6
  146. package/lib/settings/notifications.js +32 -19
  147. package/lib/settings/plan.d.ts +1 -1
  148. package/lib/settings/plan.js +40 -17
  149. package/lib/settings/privacy.js +26 -13
  150. package/lib/settings/ui.d.ts +3 -2
  151. package/lib/settings/ui.js +9 -3
  152. package/lib/settings/usage.js +7 -3
  153. package/lib/shelf.js +18 -15
  154. package/lib/side.d.ts +1 -26
  155. package/lib/side.js +11 -5
  156. package/lib/standing.d.ts +2 -10
  157. package/lib/standing.js +5 -3
  158. package/lib/suggest.d.ts +8 -28
  159. package/lib/suggest.js +39 -4
  160. package/lib/suggestions.js +14 -5
  161. package/lib/summary.d.ts +1 -1
  162. package/lib/summary.js +4 -1
  163. package/lib/sync.js +22 -12
  164. package/lib/tools.d.ts +4 -3
  165. package/lib/tools.js +10 -7
  166. package/lib/transcript.js +10 -8
  167. package/package.json +4 -2
package/lib/api/call.d.ts CHANGED
@@ -1,11 +1,4 @@
1
- /**
2
- * One request path to the platform.
3
- *
4
- * Every typed op below goes through `call`, so the bearer, the org scope, the
5
- * no-store cache rule and the reading of a refusal are decided once. The
6
- * platform answers a refusal as RFC 9457 problem details; older handlers still
7
- * answer `{error:{message}}` or `{msg}`, and all three read the same way here.
8
- */
1
+ import type { Say } from '@hanzo/i18n/react';
9
2
  /** Where the platform is and who is asking. The host supplies it. */
10
3
  export interface Target {
11
4
  /** The platform origin, no trailing slash: calls are `${api}/v1/…`. */
@@ -20,6 +13,21 @@ export declare class Refusal extends Error {
20
13
  readonly status: number;
21
14
  constructor(status: number, message: string);
22
15
  }
16
+ /**
17
+ * A sentence this client wrote with something of the moment in it (a file's
18
+ * name, an address), thrown where no `t` is at hand: its message is the
19
+ * English, filled, and a screen says it from its source and values.
20
+ */
21
+ export declare class Sentence extends Error {
22
+ readonly source: string;
23
+ readonly values: Readonly<Record<string, string | number>>;
24
+ constructor(source: string, values?: Readonly<Record<string, string | number>>);
25
+ }
26
+ /**
27
+ * An error's own sentence, as the screen that caught it says it: one this
28
+ * client wrote, in the reader's language; the platform's, which is no key, as given.
29
+ */
30
+ export declare function told(e: unknown, t?: Say): string;
23
31
  export type Query = Record<string, string | number | boolean | null | undefined>;
24
32
  /** `?a=1&b=2` from the defined, non-empty entries of `q`, or ''. */
25
33
  export declare function query(q?: Query): string;
package/lib/api/call.js CHANGED
@@ -5,7 +5,15 @@
5
5
  * no-store cache rule and the reading of a refusal are decided once. The
6
6
  * platform answers a refusal as RFC 9457 problem details; older handlers still
7
7
  * answer `{error:{message}}` or `{msg}`, and all three read the same way here.
8
+ *
9
+ * A sentence this client writes is English, marked (`phrase`) for the
10
+ * translator: a module is not a component and asks no `t`, so a screen says
11
+ * what a module threw through its own (`told`), and a module that builds text
12
+ * takes the screen's `t` as its last argument, English (../say.ts `english`)
13
+ * when none is handed.
8
14
  */
15
+ import { phrase, translate } from '@hanzo/i18n';
16
+ import { english } from '../say.js';
9
17
  /** A refusal: the status, and the platform's own sentence for it. */
10
18
  export class Refusal extends Error {
11
19
  status;
@@ -15,6 +23,29 @@ export class Refusal extends Error {
15
23
  this.name = 'Refusal';
16
24
  }
17
25
  }
26
+ /**
27
+ * A sentence this client wrote with something of the moment in it (a file's
28
+ * name, an address), thrown where no `t` is at hand: its message is the
29
+ * English, filled, and a screen says it from its source and values.
30
+ */
31
+ export class Sentence extends Error {
32
+ source;
33
+ values;
34
+ constructor(source, values = {}) {
35
+ super(translate({}, source, values));
36
+ this.source = source;
37
+ this.values = values;
38
+ }
39
+ }
40
+ /**
41
+ * An error's own sentence, as the screen that caught it says it: one this
42
+ * client wrote, in the reader's language; the platform's, which is no key, as given.
43
+ */
44
+ export function told(e, t = english) {
45
+ if (e instanceof Sentence)
46
+ return t(e.source, e.values);
47
+ return t(e instanceof Error ? e.message : String(e));
48
+ }
18
49
  /** `?a=1&b=2` from the defined, non-empty entries of `q`, or ''. */
19
50
  export function query(q = {}) {
20
51
  const s = new URLSearchParams();
@@ -81,6 +112,6 @@ export async function call(t, method, path, body, init) {
81
112
  export function unwrap(raw) {
82
113
  const r = (raw && typeof raw === 'object' ? raw : {});
83
114
  if (r.status === 'error')
84
- throw new Refusal(400, typeof r.msg === 'string' && r.msg ? r.msg : 'The platform refused that');
115
+ throw new Refusal(400, typeof r.msg === 'string' && r.msg ? r.msg : phrase('The platform refused that'));
85
116
  return r.data;
86
117
  }
@@ -1,14 +1,3 @@
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
1
  import { type Target } from './call.ts';
13
2
  /** Where a tool comes from, in the order the platform ranks them. */
14
3
  export declare const SOURCES: readonly ['connector', 'function', 'zap-service', 'agent', 'skill', 'mcp'];
@@ -9,6 +9,7 @@
9
9
  * refused when an agent calls it. At most 256 names go in one request, so a
10
10
  * larger change is sent in turns.
11
11
  */
12
+ import { phrase } from '@hanzo/i18n';
12
13
  import { call } from './call.js';
13
14
  /** Where a tool comes from, in the order the platform ranks them. */
14
15
  export const SOURCES = ['connector', 'function', 'zap-service', 'agent', 'skill', 'mcp'];
@@ -29,7 +30,7 @@ export async function tools(t) {
29
30
  /** Switch every tool in `names` on (or off), and answer the org's activated set. */
30
31
  export async function activate(t, names, on) {
31
32
  if (!names.length)
32
- throw new Error('There is nothing to switch');
33
+ throw new Error(phrase('There is nothing to switch'));
33
34
  let enabled = [];
34
35
  for (let i = 0; i < names.length; i += BATCH) {
35
36
  const part = names.slice(i, i + BATCH);
@@ -1,13 +1,3 @@
1
- /**
2
- * Codebases on the forge, the org's own git.
3
- *
4
- * GET /v1/git/repos {data: [repo]} most recently updated first
5
- * GET /v1/git/repos/{name} one repo, with its branch names
6
- *
7
- * A coding run addresses a repository by its name. The org is the tenant on
8
- * the request, so the name is the whole address — `owner/name` is a path the
9
- * engine refuses.
10
- */
11
1
  import { type Target } from './call.ts';
12
2
  export interface Codebase {
13
3
  org: string;
@@ -8,6 +8,7 @@
8
8
  * the request, so the name is the whole address — `owner/name` is a path the
9
9
  * engine refuses.
10
10
  */
11
+ import { phrase } from '@hanzo/i18n';
11
12
  import { call, seg } from './call.js';
12
13
  const str = (v) => (typeof v === 'string' ? v : '');
13
14
  const obj = (v) => v && typeof v === 'object' ? v : {};
@@ -69,11 +70,11 @@ const NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
69
70
  export async function create(t, name, description = '') {
70
71
  const handle = name.trim().replace(/\.git$/, '');
71
72
  if (!NAME.test(handle))
72
- throw new Error('A repository name starts with a letter or number, and may contain letters, numbers, dots, underscores and hyphens.');
73
+ throw new Error(phrase('A repository name starts with a letter or number, and may contain letters, numbers, dots, underscores and hyphens.'));
73
74
  const raw = await call(t, 'POST', '/v1/git/repos', { name: handle, description: description.trim() });
74
75
  const row = codebase(raw, t.org ?? '');
75
76
  if (!row)
76
- throw new Error('The forge created a repository and did not name it');
77
+ throw new Error(phrase('The forge created a repository and did not name it'));
77
78
  return row;
78
79
  }
79
80
  export async function codebases(t, q = '') {
@@ -1,37 +1,8 @@
1
- /**
2
- * Starting a coding run, and what it left.
3
- *
4
- * POST /v1/agent/coding → 202 {sessionId, repo, branch, routed, targetId}
5
- * GET /v1/agent/coding/{session}/artifacts {session, saved, artifacts}
6
- * GET /v1/agent/coding/{session}/artifacts/{name} one stored artifact's bytes
7
- *
8
- * 202 is ADMITTED, not finished: the answer is the session the run narrates
9
- * itself in, and the session stream is how it is watched.
10
- *
11
- * Where it runs is one field. No `targetId` is the platform's sandbox; a target
12
- * id routes it to one of the org's machines, and `routed` says whether it got
13
- * there.
14
- *
15
- * `mode` is what the run may do: `build` edits, commits and pushes its branch;
16
- * `plan` reads the repository with a credential that can only read and answers
17
- * with a plan, as its final status. A plan runs in the platform's sandbox only —
18
- * a claimed machine clones and pushes with its own credential — so a plan
19
- * routed to one is refused, here before sending as on the platform.
20
- *
21
- * `model` and `effort` are taken by the dev harness the builder runs (and by
22
- * codex); the builder names no other harness, so they are always honoured.
23
- *
24
- * `setup` is a plan's shape put to one question: how this codebase is installed,
25
- * started and checked. Its agent may install and run things in its sandbox, and
26
- * its answer is kept as the codebase's proposed environment (environment.ts).
27
- *
28
- * A sandbox run keeps its sandbox after it ends, so `after` continues the earlier
29
- * run in that same sandbox and working tree, resumed if it parked.
30
- */
1
+ import type { Say } from '@hanzo/i18n/react';
31
2
  import { type Target } from './call.ts';
32
3
  export type Mode = 'build' | 'plan' | 'setup';
33
- /** Why this mode cannot run where it is sent, or '' when it can. */
34
- export declare const unhonoured: (mode: Mode | undefined, target?: string) => string;
4
+ /** Why this mode cannot run where it is sent, in the words of the `t` handed (English without one), or '' when it can. */
5
+ export declare const unhonoured: (mode: Mode | undefined, target?: string, t?: Say) => string;
35
6
  export interface Ask {
36
7
  /** The task, in the words you would use with a colleague. */
37
8
  prompt: string;
package/lib/api/coding.js CHANGED
@@ -28,11 +28,17 @@
28
28
  * A sandbox run keeps its sandbox after it ends, so `after` continues the earlier
29
29
  * run in that same sandbox and working tree, resumed if it parked.
30
30
  */
31
+ import { phrase } from '@hanzo/i18n';
32
+ import { english } from '../say.js';
31
33
  import { call, headers, reason, Refusal, seg } from './call.js';
32
- /** Why this mode cannot run where it is sent, or '' when it can. */
33
- export const unhonoured = (mode, target) => (mode === 'plan' || mode === 'setup') && target?.trim()
34
- ? `A ${mode} runs in the Hanzo sandbox: a machine clones and pushes with its own credential. Choose Cloud, or switch to Build.`
35
- : '';
34
+ /** Why this mode cannot run where it is sent, in the words of the `t` handed (English without one), or '' when it can. */
35
+ export const unhonoured = (mode, target, t = english) => !target?.trim()
36
+ ? ''
37
+ : mode === 'plan'
38
+ ? t('A plan runs in the Hanzo sandbox: a machine clones and pushes with its own credential. Choose Cloud, or switch to Build.')
39
+ : mode === 'setup'
40
+ ? t('A setup runs in the Hanzo sandbox: a machine clones and pushes with its own credential. Choose Cloud, or switch to Build.')
41
+ : '';
36
42
  /**
37
43
  * The request body: only what was asked, with no empty strings sent as values.
38
44
  * A run in the sandbox asks for one with a desktop, so its Desktop tab has a
@@ -53,14 +59,14 @@ export function body(ask) {
53
59
  }
54
60
  export async function start(t, ask) {
55
61
  if (!ask.prompt.trim())
56
- throw new Refusal(400, 'Say what the run should do');
62
+ throw new Refusal(400, phrase('Say what the run should do'));
57
63
  const why = unhonoured(ask.mode, ask.targetId);
58
64
  if (why)
59
65
  throw new Refusal(400, why);
60
66
  const r = await call(t, 'POST', '/v1/agent/coding', body(ask));
61
67
  const s = (k) => (typeof r?.[k] === 'string' ? r[k] : '');
62
68
  if (!s('sessionId'))
63
- throw new Refusal(502, 'The run was admitted but named no session');
69
+ throw new Refusal(502, phrase('The run was admitted but named no session'));
64
70
  return {
65
71
  session: s('sessionId'),
66
72
  repo: s('repo'),
@@ -154,7 +160,7 @@ export async function artifacts(t, session) {
154
160
  export async function artifact(t, session, name) {
155
161
  const parts = name.split('/');
156
162
  if (parts.some((p) => p === '' || p === '.' || p === '..'))
157
- throw new Refusal(400, 'That artifact has no name to fetch it by');
163
+ throw new Refusal(400, phrase('That artifact has no name to fetch it by'));
158
164
  const res = await fetch(`${t.api}/v1/agent/coding/${seg(session)}/artifacts/${parts.map(seg).join('/')}`, { headers: headers(t), cache: 'no-store' });
159
165
  if (!res.ok)
160
166
  throw new Refusal(res.status, (await reason(res)) || `The artifact answered ${res.status}`);
@@ -1,19 +1,3 @@
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
1
  import { type Target } from './call.ts';
18
2
  export interface Server {
19
3
  /** The server's id in the org, and the prefix of every tool it brings. */
@@ -79,7 +63,7 @@ export interface Adding {
79
63
  header?: string;
80
64
  secret?: string;
81
65
  }
82
- /** Why a server cannot be added as it stands, or '' when it can. */
66
+ /** Why a server cannot be added as it stands, or '' when it can. English, marked (`phrase`): a screen says it through `t`. */
83
67
  export declare function refuse(a: Adding): string;
84
68
  export declare function add(t: Target, a: Adding): Promise<Server>;
85
69
  export declare function remove(t: Target, id: string): Promise<void>;
@@ -14,6 +14,7 @@
14
14
  * ships only a package needs somewhere to run first. The fleet's own servers,
15
15
  * on for every run, are mcp.ts.
16
16
  */
17
+ import { phrase } from '@hanzo/i18n';
17
18
  import { call, query, seg } from './call.js';
18
19
  const str = (v) => (typeof v === 'string' ? v : '');
19
20
  const num = (v) => (typeof v === 'number' && Number.isFinite(v) ? v : 0);
@@ -67,16 +68,16 @@ export const owns = (s, toolName) => toolName.startsWith(`${s.id}_`);
67
68
  export async function servers(t) {
68
69
  return rows(obj(await call(t, 'GET', '/v1/tool/mcp/servers')).servers).map(server).filter((s) => s.id);
69
70
  }
70
- /** Why a server cannot be added as it stands, or '' when it can. */
71
+ /** Why a server cannot be added as it stands, or '' when it can. English, marked (`phrase`): a screen says it through `t`. */
71
72
  export function refuse(a) {
72
73
  if (!a.listing) {
73
74
  if (!a.name?.trim())
74
- return 'A connector needs a name';
75
+ return phrase('A connector needs a name');
75
76
  if (!/^https?:\/\/[^\s/]+/.test(a.url?.trim() ?? ''))
76
- return 'The URL is an http(s) address';
77
+ return phrase('The URL is an http(s) address');
77
78
  }
78
79
  if (a.secret && !a.header?.trim())
79
- return 'Name the header the secret is sent in';
80
+ return phrase('Name the header the secret is sent in');
80
81
  return '';
81
82
  }
82
83
  export async function add(t, a) {
@@ -1,19 +1,4 @@
1
- /**
2
- * A codebase's environment: what a run does to its checkout before the agent
3
- * starts, and which secrets it exports.
4
- *
5
- * GET /v1/environment {data: [environment]}
6
- * GET /v1/environment/{repo} one; a codebase with none answers state "none"
7
- * PUT /v1/environment/{repo} save {install, start} (org admin)
8
- * DELETE /v1/environment/{repo} forget it and its secrets (org admin)
9
- * PUT /v1/environment/{repo}/secrets/{name} set one value (org admin)
10
- * DELETE /v1/environment/{repo}/secrets/{name} remove one (org admin)
11
- *
12
- * A setup run is a coding run in mode `setup` (coding.ts): its agent explores the
13
- * checkout, installs and checks what it finds, and the platform keeps its answer
14
- * here as `proposal` until someone saves it. Secrets are names only; their values
15
- * are sealed in KMS and reach the run's environment, never this record.
16
- */
1
+ import type { Say } from '@hanzo/i18n/react';
17
2
  import { type Target } from './call.ts';
18
3
  export type State = 'none' | 'proposed' | 'ready';
19
4
  export interface Proposal {
@@ -46,9 +31,10 @@ export declare function environment(raw: unknown, repo?: string): Environment;
46
31
  export declare const unset: (e: Environment) => boolean;
47
32
  /**
48
33
  * What a refused save or setup run says to the person who pressed it: what is
49
- * wrong and what to do, in the reader's words rather than the platform's.
34
+ * wrong and what to do, in the reader's words rather than the platform's, and
35
+ * in the language of the `t` handed (English without one).
50
36
  */
51
- export declare function plain(e: unknown, repo: string, act: 'save' | 'start' | 'run'): string;
37
+ export declare function plain(e: unknown, repo: string, act: 'save' | 'start' | 'run', t?: Say): string;
52
38
  export declare function environments(t: Target): Promise<Environment[]>;
53
39
  export declare function read(t: Target, repo: string): Promise<Environment>;
54
40
  export declare function save(t: Target, repo: string, scripts: {
@@ -58,8 +44,8 @@ export declare function save(t: Target, repo: string, scripts: {
58
44
  export declare function forget(t: Target, repo: string): Promise<void>;
59
45
  /** An environment variable name the run can export, and not one of its own. */
60
46
  export declare const NAME: RegExp;
61
- /** Why a secret name cannot be set, or '' when it can. */
62
- export declare function refuse(name: string): string;
47
+ /** Why a secret name cannot be set, in the words of the `t` handed, or '' when it can. */
48
+ export declare function refuse(name: string, t?: Say): string;
63
49
  export declare function setSecret(t: Target, repo: string, name: string, value: string): Promise<Environment>;
64
50
  export declare function removeSecret(t: Target, repo: string, name: string): Promise<Environment>;
65
51
  /**
@@ -14,7 +14,9 @@
14
14
  * here as `proposal` until someone saves it. Secrets are names only; their values
15
15
  * are sealed in KMS and reach the run's environment, never this record.
16
16
  */
17
- import { call, Refusal, seg } from './call.js';
17
+ import { phrase } from '@hanzo/i18n';
18
+ import { english } from '../say.js';
19
+ import { call, Refusal, seg, told } from './call.js';
18
20
  const str = (v) => (typeof v === 'string' ? v : '');
19
21
  const obj = (v) => (v && typeof v === 'object' ? v : {});
20
22
  const names = (v) => (Array.isArray(v) ? v.filter((n) => typeof n === 'string' && n !== '') : []);
@@ -43,34 +45,35 @@ export function environment(raw, repo = '') {
43
45
  export const unset = (e) => e.state === 'none' && !e.updated;
44
46
  /**
45
47
  * What a refused save or setup run says to the person who pressed it: what is
46
- * wrong and what to do, in the reader's words rather than the platform's.
48
+ * wrong and what to do, in the reader's words rather than the platform's, and
49
+ * in the language of the `t` handed (English without one).
47
50
  */
48
- export function plain(e, repo, act) {
51
+ export function plain(e, repo, act, t = english) {
49
52
  // This page's own refusals (a secret's name, an empty value) are already a reader's words.
50
53
  if (e instanceof Error && !(e instanceof Refusal) && e.name === 'Error' && e.message)
51
- return e.message;
54
+ return told(e, t);
52
55
  if (e instanceof Refusal) {
53
56
  const why = e.message;
54
57
  if (e.status === 401)
55
- return 'Your session ended. Sign in again, then try again.';
58
+ return t('Your session ended. Sign in again, then try again.');
56
59
  if (e.status === 403 && act === 'save')
57
- return 'Only an organization admin saves an environment. Start the agent instead; an admin reviews what it proposes.';
60
+ return t('Only an organization admin saves an environment. Start the agent instead; an admin reviews what it proposes.');
58
61
  if (e.status === 402)
59
- return 'This run needs a plan with runs left. See Plans to change it.';
62
+ return t('This run needs a plan with runs left. See Plans to change it.');
60
63
  if (e.status === 403)
61
- return `This account cannot start a run on ${repo}. Ask an organization admin for access.`;
64
+ return t('This account cannot start a run on {repo}. Ask an organization admin for access.', { repo });
62
65
  if (e.status === 429)
63
- return 'Too many runs are going right now. Try again in a minute.';
66
+ return t('Too many runs are going right now. Try again in a minute.');
64
67
  if (e.status >= 500)
65
- return act === 'save' ? 'The environment store is not answering right now. Try again shortly.' : 'The code workspace is not answering right now. Try again shortly.';
68
+ return act === 'save' ? t('The environment store is not answering right now. Try again shortly.') : t('The code workspace is not answering right now. Try again shortly.');
66
69
  if (/no repository named|not one of this organization|is not a repository/.test(why))
67
- return `${repo} is not a repository this organization has on the forge. Choose one of its repositories.`;
70
+ return t('{repo} is not a repository this organization has on the forge. Choose one of its repositories.', { repo });
68
71
  if (/no account on the forge|verify .* first|no forge identity/.test(why))
69
- return 'Your forge account is not ready yet: sign in to the forge once, then try again.';
72
+ return t('Your forge account is not ready yet: sign in to the forge once, then try again.');
70
73
  }
71
74
  if (act === 'save')
72
- return `The environment for ${repo} could not be saved. Try again.`;
73
- return act === 'start' ? `The setup run on ${repo} could not start. Try again.` : `The run on ${repo} could not start. Try again.`;
75
+ return t('The environment for {repo} could not be saved. Try again.', { repo });
76
+ return act === 'start' ? t('The setup run on {repo} could not start. Try again.', { repo }) : t('The run on {repo} could not start. Try again.', { repo });
74
77
  }
75
78
  export async function environments(t) {
76
79
  const r = await call(t, 'GET', '/v1/environment');
@@ -88,13 +91,13 @@ export async function forget(t, repo) {
88
91
  /** An environment variable name the run can export, and not one of its own. */
89
92
  export const NAME = /^[A-Za-z_][A-Za-z0-9_]{0,127}$/;
90
93
  const RESERVED = ['PATH', 'HOME', 'USER', 'SHELL', 'PWD', 'HOSTNAME', 'IFS', 'ENV', 'BASH_ENV'];
91
- /** Why a secret name cannot be set, or '' when it can. */
92
- export function refuse(name) {
94
+ /** Why a secret name cannot be set, in the words of the `t` handed, or '' when it can. */
95
+ export function refuse(name, t = english) {
93
96
  if (!NAME.test(name))
94
- return `${name || 'That'} is not an environment variable name`;
97
+ return name ? t('{name} is not an environment variable name', { name }) : t('That is not an environment variable name');
95
98
  const up = name.toUpperCase();
96
99
  if (up.startsWith('HANZO_') || RESERVED.includes(up))
97
- return `${name} is reserved for the run itself`;
100
+ return t('{name} is reserved for the run itself', { name });
98
101
  return '';
99
102
  }
100
103
  export async function setSecret(t, repo, name, value) {
@@ -102,7 +105,7 @@ export async function setSecret(t, repo, name, value) {
102
105
  if (why)
103
106
  throw new Error(why);
104
107
  if (!value)
105
- throw new Error('A secret needs a value');
108
+ throw new Error(phrase('A secret needs a value'));
106
109
  return environment(await call(t, 'PUT', `/v1/environment/${seg(repo)}/secrets/${seg(name)}`, { value }), repo);
107
110
  }
108
111
  export async function removeSecret(t, repo, name) {
@@ -1,25 +1,3 @@
1
- /**
2
- * GitHub, as the platform's GitHub App sees it for the signed-in person.
3
- *
4
- * GET /v1/provider/github/repos?q=&owner=&limit=&after= most recently pushed first
5
- * GET /v1/provider/github/repos/{owner}/{repo}/branches?q=&limit=&after= default first
6
- * GET /v1/provider/github/user the person's own connection
7
- * POST /v1/provider/github/user/connect {return} → {authorizeUrl}
8
- * POST /v1/provider/github/user/complete {grant} → the connection
9
- * POST /v1/provider/github/user/disconnect
10
- * GET /v1/provider/github/installations the accounts this org has bound the App on
11
- * POST /v1/provider/github/issues/backfill {state, repo?} → mirror GitHub issues onto the board
12
- *
13
- * Paged by an opaque `after` cursor; `next` is the cursor for the page after
14
- * this one, or empty on the last. Rows are read defensively: a field the
15
- * platform left out is an empty value here, never `undefined` in a label.
16
- *
17
- * `connected` false with repositories is an org admin seeing the org's
18
- * installations; false with none is the cue to connect. GitHub returns a
19
- * connecting person to the page the connect named as its return (a Hanzo app
20
- * page; the console's /connectors otherwise) with `?complete=github&grant=<id>`,
21
- * and that page completes it (`complete`, and `landed` in ../back.ts).
22
- */
23
1
  import { type Target } from './call.ts';
24
2
  export interface Repo {
25
3
  owner: string;
package/lib/api/github.js CHANGED
@@ -20,6 +20,7 @@
20
20
  * page; the console's /connectors otherwise) with `?complete=github&grant=<id>`,
21
21
  * and that page completes it (`complete`, and `landed` in ../back.ts).
22
22
  */
23
+ import { phrase } from '@hanzo/i18n';
23
24
  import { call, query, seg } from './call.js';
24
25
  import { codebases } from './codebases.js';
25
26
  import { links } from './links.js';
@@ -79,7 +80,7 @@ export async function connect(t, back) {
79
80
  const raw = obj(await call(t, 'POST', '/v1/provider/github/user/connect', back ? { return: back } : undefined));
80
81
  const url = str(raw.authorizeUrl);
81
82
  if (!/^https:\/\/github\.com\//.test(url))
82
- throw new Error('The platform did not name a GitHub address to connect at');
83
+ throw new Error(phrase('The platform did not name a GitHub address to connect at'));
83
84
  return url;
84
85
  }
85
86
  /** Finish the connect GitHub's return handed this page as `grant`. */
@@ -177,7 +178,7 @@ export async function grants(t, signal) {
177
178
  export async function bring(t, repos) {
178
179
  const names = repos.map((n) => n.trim()).filter(Boolean);
179
180
  if (names.length === 0)
180
- throw new Error('Choose a repository first');
181
+ throw new Error(phrase('Choose a repository first'));
181
182
  const raw = obj(await call(t, 'POST', '/v1/provider/github/repos/import', { repos: names }));
182
183
  const said = Array.isArray(raw.repos) ? raw.repos : [];
183
184
  const made = Array.isArray(raw.codebases) ? raw.codebases : [];
package/lib/api/keys.d.ts CHANGED
@@ -1,17 +1,3 @@
1
- /**
2
- * A person's API keys, each its own credential: as many as they like, of either
3
- * type — secret (sk-, it belongs on a server) or publishable (pk-, safe in a
4
- * page's source).
5
- *
6
- * GET /v1/account/keys {keys: [{id, name, type, prefix, key?, status, limit?, created, …}]}
7
- * POST /v1/account/keys {name, type, limit?} → the key, its secret answered once
8
- * DELETE /v1/account/keys/{id} revoke exactly that one; it stays listed as revoked
9
- *
10
- * Creating a key never touches another. A secret key is answered once and by its
11
- * prefix after; a publishable key is public by construction and always comes back
12
- * whole. A limit only narrows what a key may reach — `model:zen5`,
13
- * `project:acme`, `product:train`, `read:*`.
14
- */
15
1
  import { type Target } from './call.ts';
16
2
  export declare const KINDS: readonly ['secret', 'publishable'];
17
3
  export type Kind = (typeof KINDS)[number];
package/lib/api/keys.js CHANGED
@@ -12,7 +12,8 @@
12
12
  * whole. A limit only narrows what a key may reach — `model:zen5`,
13
13
  * `project:acme`, `product:train`, `read:*`.
14
14
  */
15
- import { call } from './call.js';
15
+ import { phrase } from '@hanzo/i18n';
16
+ import { call, Sentence } from './call.js';
16
17
  export const KINDS = ['secret', 'publishable'];
17
18
  const str = (v) => (typeof v === 'string' ? v : '');
18
19
  const list = (v) => (Array.isArray(v) ? v.filter((s) => typeof s === 'string' && s !== '') : []);
@@ -49,7 +50,7 @@ export function limits(raw) {
49
50
  .filter(Boolean);
50
51
  const bad = out.find((s) => !LIMIT.test(s));
51
52
  if (bad)
52
- throw new Error(`${bad} is not a limit — write it as kind:name, like model:zen5`);
53
+ throw new Sentence(phrase('{limit} is not a limit — write it as kind:name, like model:zen5'), { limit: bad });
53
54
  return out;
54
55
  }
55
56
  /** Create a new key beside the ones held. The answer is the only time a secret key is shown. */
@@ -62,7 +63,7 @@ export async function mint(t, type, name = '', limit = []) {
62
63
  const r = await call(t, 'POST', '/v1/account/keys', body);
63
64
  const key = str(r?.key);
64
65
  if (!key)
65
- throw new Error('The platform answered no key');
66
+ throw new Error(phrase('The platform answered no key'));
66
67
  return { id: str(r?.id), name: str(r?.name), type: kind(r?.type) ?? type, key, limit: list(r?.limit) };
67
68
  }
68
69
  /** Revoke exactly one key, by id. Every other key keeps working. */
@@ -1,21 +1,7 @@
1
- /**
2
- * The Library: everything a person made, in Chat, Dev, Studio and workflows, in
3
- * one index.
4
- *
5
- * GET /v1/library {data: [item], next} newest first, filtered
6
- * GET /v1/library/{id} one item, with fresh signed URLs
7
- * POST /v1/library keep one: its bytes as base64, at most 10 MiB
8
- * PATCH /v1/library/{id} rename, share with the org, move to a project
9
- * DELETE /v1/library/{id} its row and its bytes
10
- *
11
- * An item is its owner's; another member of the org reads it only once its owner
12
- * shares it, and changes it never. Its `url`, `download` and `preview` are
13
- * signed and stop working at `expires`: read the item again for fresh ones.
14
- */
15
1
  import { type Target } from './call.ts';
16
2
  export declare const KIND_IDS: readonly ['image', 'video', 'audio', 'document', 'slides', 'type', 'model', 'code', 'file'];
17
3
  export type Kind = (typeof KIND_IDS)[number];
18
- /** The kinds a reader narrows to, in the order the Library lists them, '' being all. */
4
+ /** The kinds a reader narrows to, in the order the Library lists them, '' being all; each label English and marked (`phrase`), drawn through `t`. */
19
5
  export declare const KINDS: readonly {
20
6
  id: Kind | '';
21
7
  label: string;
@@ -23,7 +9,7 @@ export declare const KINDS: readonly {
23
9
  export declare const SOURCES: readonly ['chat', 'dev', 'studio', 'workflow'];
24
10
  export type Source = (typeof SOURCES)[number];
25
11
  export type Scope = 'all' | 'mine' | 'shared';
26
- /** A product, as a person names it. */
12
+ /** A product, as a person names it: Chat, Dev and Studio are names, and Workflow a word, marked (`phrase`). */
27
13
  export declare const SOURCE: Record<Source, string>;
28
14
  export interface Item {
29
15
  id: string;
@@ -117,7 +103,7 @@ export declare function change(t: Target, id: string, c: Change): Promise<Item>;
117
103
  export declare function remove(t: Target, id: string): Promise<void>;
118
104
  /** A size the way a person reads it. */
119
105
  export declare function size(bytes: number): string;
120
- /** The day an instant falls on, as a section of the Library: Today, Yesterday, then the month. */
106
+ /** The day an instant falls on, as a section of the Library: Today, Yesterday (English, marked), then the month. */
121
107
  export declare function day(iso: string, now?: Date): string;
122
108
  /** Items under the day each was made, in the order given (newest first). */
123
109
  export declare function days(list: Item[], now?: Date): {