@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/README.md CHANGED
@@ -100,6 +100,19 @@ nothing behind a More; `useSessions(host)` is the runs — the org's coding runs
100
100
  kept live by the org's feed — for a host that draws the rows itself. `administers(token, org)` reads the org-admin bit off a
101
101
  hanzo.id token for `Host.admin`. One left column, never two.
102
102
 
103
+ ## Words
104
+
105
+ Every word the builder authors is said through @hanzo/i18n, a required peer
106
+ (`>=0.4.0`): the host's `I18nProvider` is the one asked, and without one the
107
+ builder reads in English, unchanged. A component asks `useI18n()`; copy held as
108
+ data is `phrase()` where it is written and drawn through `t`; a helper outside a
109
+ render (`nav()`, `own()`, `tabs()`, `library()`, turn.ts, the plan sentences)
110
+ takes the caller's `t` last and falls back to `say.ts`'s `english`. Names, model
111
+ ids, what a person typed, what the platform said and figures are drawn as given;
112
+ prompts sent to a model stay in English. `src/e2e/language*.spec.ts` and
113
+ `translated.spec.ts` hold the composer, Projects, Skills and the Library in
114
+ French and right to left in Arabic.
115
+
103
116
  ## The model, the composer, the panel
104
117
 
105
118
  One of each, shared with Chat (@hanzo/rooms imports them):
@@ -226,10 +239,15 @@ are any member's.
226
239
  - Plugins: TypeScript connectors built on the platform, from source or from a
227
240
  description of an API; a failed build says why. Browse offers building one and
228
241
  lists what the deployment mounts.
229
- - Agents: Browse is the platform's presets whose tool calls run on the platform,
230
- each a Use that opens a new agent written from it; Yours is the org's own —
231
- model, instructions, tools, budget — created, edited (only what changed is
232
- sent) and deleted.
242
+ - Agents: Browse is every agent to start from — the host's default agents
243
+ (`cast`) and personas (`personas`), and the platform's presets whose tool
244
+ calls run on the platform — searched by name and filtered by category. Use
245
+ makes one agent of the org's own from one template (a name taken becomes
246
+ `-2`, `-3`), with its face, model and instructions, and opens its profile;
247
+ nothing is added in bulk. Start from scratch, Clone yourself (the person
248
+ signed in, with writing pasted or uploaded) and Create from a person make one
249
+ too. Yours is the org's own — model, instructions, tools, budget — created,
250
+ edited (only what changed is sent) and deleted.
233
251
 
234
252
  **New.** "What's up next?", and at the foot the composer: where the run runs
235
253
  (Cloud is the platform's sandbox with the codebase's environment; the org's
package/lib/activity.d.ts CHANGED
@@ -1,8 +1,4 @@
1
- /**
2
- * Activity's words: what a run's state is called and drawn as, how long it
3
- * took, what it cost, and the one line under its title. Pure, so the screen and
4
- * its tests read the same sentences.
5
- */
1
+ import type { Say } from '@hanzo/i18n/react';
6
2
  import type { SessionStatus } from '@hanzo/ui/chat';
7
3
  import type { Job, State } from './api/auto.ts';
8
4
  /** A job's state as the rail's dot draws it, and in words. */
@@ -12,8 +8,8 @@ export declare const SAID: Record<State, string>;
12
8
  export declare function lasted(started: string, finished: string | null, now?: number): string;
13
9
  /** Cents as dollars. */
14
10
  export declare const dollars: (cents: number) => string;
15
- /** One line about a job: when it started, how long, what it cost, what it made. */
16
- export declare function about(j: Job, now?: number): string;
11
+ /** One line about a job: when it started, how long, what it cost, what it made. Its words in the language of `t`. */
12
+ export declare function about(j: Job, now?: number, t?: Say): string;
17
13
  /** The jobs, newest first, with `j` in its place. */
18
14
  export declare function merged(list: Job[], j: Job): Job[];
19
15
  /** A task's name from what it does: its first line, short. */
package/lib/activity.js CHANGED
@@ -1,8 +1,21 @@
1
+ /**
2
+ * Activity's words: what a run's state is called and drawn as, how long it
3
+ * took, what it cost, and the one line under its title. Pure, so the screen and
4
+ * its tests read the same sentences.
5
+ */
6
+ import { phrase } from '@hanzo/i18n';
1
7
  import { ago } from './ago.js';
2
8
  import { SOURCE } from './api/library.js';
9
+ import { english } from './say.js';
3
10
  /** A job's state as the rail's dot draws it, and in words. */
4
11
  export const DOTS = { scheduled: 'idle', running: 'running', succeeded: 'done', failed: 'error', cancelled: 'stopped' };
5
- export const SAID = { scheduled: 'Scheduled', running: 'Running', succeeded: 'Succeeded', failed: 'Failed', cancelled: 'Cancelled' };
12
+ export const SAID = {
13
+ scheduled: phrase('Scheduled'),
14
+ running: phrase('Running'),
15
+ succeeded: phrase('Succeeded'),
16
+ failed: phrase('Failed'),
17
+ cancelled: phrase('Cancelled'),
18
+ };
6
19
  /** How long a run took, or has taken so far. */
7
20
  export function lasted(started, finished, now = Date.now()) {
8
21
  const a = Date.parse(started);
@@ -20,10 +33,10 @@ export function lasted(started, finished, now = Date.now()) {
20
33
  }
21
34
  /** Cents as dollars. */
22
35
  export const dollars = (cents) => `$${(cents / 100).toFixed(2)}`;
23
- /** One line about a job: when it started, how long, what it cost, what it made. */
24
- export function about(j, now = Date.now()) {
25
- const made = j.outputs.length ? `${j.outputs.length} ${j.outputs.length === 1 ? 'output' : 'outputs'}` : '';
26
- return [SOURCE[j.product], j.started ? ago(j.started) : '', lasted(j.started, j.finished, now), j.cost !== null ? dollars(j.cost) : '', made].filter(Boolean).join(' · ');
36
+ /** One line about a job: when it started, how long, what it cost, what it made. Its words in the language of `t`. */
37
+ export function about(j, now = Date.now(), t = english) {
38
+ const made = j.outputs.length ? (j.outputs.length === 1 ? t('{n} output', { n: 1 }) : t('{n} outputs', { n: j.outputs.length })) : '';
39
+ return [SOURCE[j.product] ? t(SOURCE[j.product]) : '', j.started ? ago(j.started) : '', lasted(j.started, j.finished, now), j.cost !== null ? dollars(j.cost) : '', made].filter(Boolean).join(' · ');
27
40
  }
28
41
  /** The jobs, newest first, with `j` in its place. */
29
42
  export function merged(list, j) {
@@ -1,22 +1,3 @@
1
- /**
2
- * Agents: the org's own — a model, instructions, the tools it may call and a
3
- * budget — and the presets one can start from.
4
- *
5
- * GET /v1/agent {agents: [agent]}, without instructions
6
- * POST /v1/agent create → 201 agent
7
- * GET /v1/agent/{ref} one, with its instructions
8
- * PATCH /v1/agent/{ref} the fields sent change, the rest stay → agent
9
- * DELETE /v1/agent/{ref} removes it and every run recorded against it → 204
10
- * GET /v1/agent/chat/presets {presets: [{id, title, systemPrompt, serverExecuted}]}
11
- *
12
- * A ref is the agent's id or its name. A budget is required: the most it may
13
- * spend in a period and in one run, in micro-USD, and the period — day, week or
14
- * month. Tools are names from the tool plane; none grants none, and `*` is
15
- * whatever the fleet's MCP server serves when it runs. An omitted model is the
16
- * deployment's default. A preset whose tool calls run on the platform
17
- * (`serverExecuted`) makes a working agent; the others hand their calls back to
18
- * the client that asked, so they are not offered here.
19
- */
20
1
  import { type Target } from './call.ts';
21
2
  export type Period = 'day' | 'week' | 'month';
22
3
  export declare const PERIODS: readonly Period[];
@@ -35,6 +16,8 @@ export interface Agent {
35
16
  task: number;
36
17
  spent: number;
37
18
  period: Period | '';
19
+ /** Its picture: an https link or a data URL; '' draws its emoji or initial. */
20
+ avatar: string;
38
21
  emoji: string;
39
22
  }
40
23
  export interface Preset {
@@ -52,6 +35,8 @@ export interface Draft {
52
35
  cap: string;
53
36
  task: string;
54
37
  period: Period;
38
+ /** Sent on create only: an https link or a data URL. */
39
+ avatar: string;
55
40
  }
56
41
  /** An agent's name, as the handler takes it. */
57
42
  export declare const NAME: RegExp;
@@ -65,7 +50,7 @@ export declare function agent(raw: unknown): Agent;
65
50
  /** A draft of an agent as it stands, to edit. */
66
51
  export declare function draft(a: Agent): Draft;
67
52
  export declare const EMPTY: Draft;
68
- /** Why a draft cannot be saved as it stands, or '' when it can. */
53
+ /** Why a draft cannot be saved as it stands, or '' when it can: English, marked (`phrase`), so a screen says it through `t`. */
69
54
  export declare function refuse(d: Draft): string;
70
55
  export declare function agents(t: Target): Promise<Agent[]>;
71
56
  export declare function one(t: Target, ref: string): Promise<Agent>;
@@ -86,29 +71,45 @@ export declare function profile(t: Target, ref: string): Promise<{
86
71
  agent: Agent;
87
72
  runs: Run[];
88
73
  }>;
89
- /** A default agent a host offers: a name, its line, its model and its instructions. */
74
+ /** An agent to start from: a default agent, a persona or a preset. */
90
75
  export interface Cast {
76
+ /** The name it is made under. */
91
77
  name: string;
78
+ /** What Browse calls it. */
79
+ title: string;
92
80
  description: string;
81
+ /** What Browse lists it under. */
82
+ category: string;
93
83
  model: string;
94
84
  instructions: string;
95
- /** Its face, an address of the host, when it has one. */
85
+ /** Reads the instructions, where they are a file the host serves. */
86
+ read?: () => Promise<string>;
87
+ /** Its face, an address of the host. */
96
88
  avatar?: string;
89
+ tools?: string[];
97
90
  }
98
- /** What a default agent may spend until its owner says otherwise, in dollars: each month, and one run. */
91
+ /** What an agent made from a template may spend until its owner says otherwise, in dollars: each month, and one run. */
99
92
  export declare const CAST_CAP = "10";
100
93
  export declare const CAST_TASK = "1";
101
- /** The cast members the org has no agent for, by name in any case. */
102
- export declare const missing: (cast: readonly Cast[], have: readonly {
103
- name: string;
104
- }[]) => Cast[];
105
- /** Create every cast member the org lacks, one at a time; the names made, and the first refusal, if any. */
106
- export declare function hire(t: Target, cast: readonly Cast[], have: readonly {
94
+ /**
95
+ * A name the API takes: accents dropped, any other run of characters a dash,
96
+ * from a letter or digit, up to 64. "Spärck Jones" is Sparck-Jones.
97
+ */
98
+ export declare const handle: (name: string) => string;
99
+ /** `name`, or `name-2`, `name-3`… : the first the org has no agent called, in any case. */
100
+ export declare function free(name: string, have: readonly {
107
101
  name: string;
108
- }[]): Promise<{
109
- made: string[];
110
- error: unknown;
111
- }>;
102
+ }[]): string;
103
+ /** An address of the page as an https link the API keeps, or '' (a page served over http has none). */
104
+ export declare const link: (at: string | undefined, base: string) => string;
105
+ /**
106
+ * Make the org's own agent from a template: under the first name the org has
107
+ * free (read now, so it is never one that exists), with its instructions read
108
+ * and its face linked. The template is not touched.
109
+ */
110
+ export declare function use(t: Target, c: Cast, base: string): Promise<Agent>;
111
+ /** Anyone as a template: their name, who they are, and writing of theirs to take the voice from. */
112
+ export declare function person(name: string, about: string, writing: string, avatar?: string): Cast;
112
113
  export declare function create(t: Target, d: Draft): Promise<Agent>;
113
114
  /** The fields that differ from `was`, and nothing else: an omitted field keeps its value. */
114
115
  export declare function changes(was: Agent, d: Draft): Record<string, unknown>;
@@ -116,5 +117,5 @@ export declare function update(t: Target, was: Agent, d: Draft): Promise<Agent>;
116
117
  export declare function remove(t: Target, ref: string): Promise<void>;
117
118
  /** The presets that make a working agent. */
118
119
  export declare function presets(t: Target): Promise<Preset[]>;
119
- /** A new agent's draft from a preset. */
120
- export declare function fromPreset(p: Preset): Draft;
120
+ /** A preset as a template. */
121
+ export declare function fromPreset(p: Preset): Cast;
package/lib/api/agents.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Agents: the org's own — a model, instructions, the tools it may call and a
3
- * budget — and the presets one can start from.
2
+ * Agents: the org's own — a model, instructions, the tools it may call, a
3
+ * budget and a face — and the templates one is made from.
4
4
  *
5
5
  * GET /v1/agent {agents: [agent]}, without instructions
6
6
  * POST /v1/agent create → 201 agent
@@ -17,6 +17,7 @@
17
17
  * (`serverExecuted`) makes a working agent; the others hand their calls back to
18
18
  * the client that asked, so they are not offered here.
19
19
  */
20
+ import { phrase } from '@hanzo/i18n';
20
21
  import { call, seg } from './call.js';
21
22
  export const PERIODS = ['day', 'week', 'month'];
22
23
  /** An agent's name, as the handler takes it. */
@@ -47,6 +48,7 @@ export function agent(raw) {
47
48
  task: num(o.max_task_micro_usd),
48
49
  spent: num(o.consumed_micro_usd),
49
50
  period: PERIODS.includes(period) ? period : '',
51
+ avatar: str(o.avatar),
50
52
  emoji: str(o.emoji),
51
53
  };
52
54
  }
@@ -61,23 +63,31 @@ export function draft(a) {
61
63
  cap: dollars(a.cap),
62
64
  task: dollars(a.task),
63
65
  period: a.period || 'month',
66
+ avatar: a.avatar,
64
67
  };
65
68
  }
66
- export const EMPTY = { name: '', description: '', model: '', instructions: '', tools: [], cap: '', task: '', period: 'month' };
67
- /** Why a draft cannot be saved as it stands, or '' when it can. */
69
+ export const EMPTY = { name: '', description: '', model: '', instructions: '', tools: [], cap: '', task: '', period: 'month', avatar: '' };
70
+ /** What a budget left unset says, and one run that outspends its period, by the period: whole sentences, so a language says the period its own way. */
71
+ const UNSET = { day: phrase('Set what it may spend each day'), week: phrase('Set what it may spend each week'), month: phrase('Set what it may spend each month') };
72
+ const OVER = {
73
+ day: phrase('One run cannot spend more than the whole day'),
74
+ week: phrase('One run cannot spend more than the whole week'),
75
+ month: phrase('One run cannot spend more than the whole month'),
76
+ };
77
+ /** Why a draft cannot be saved as it stands, or '' when it can: English, marked (`phrase`), so a screen says it through `t`. */
68
78
  export function refuse(d) {
69
79
  if (!NAME.test(d.name))
70
- return 'A name is letters, digits, . _ or -, starting with a letter or digit';
80
+ return phrase('A name is letters, digits, . _ or -, starting with a letter or digit');
71
81
  if (new TextEncoder().encode(d.instructions).length > MAX)
72
- return 'Instructions are at most 32 KB';
82
+ return phrase('Instructions are at most 32 KB');
73
83
  const cap = micros(d.cap);
74
84
  const task = micros(d.task);
75
85
  if (!(cap > 0))
76
- return 'Set what it may spend each ' + d.period;
86
+ return UNSET[d.period];
77
87
  if (!(task > 0))
78
- return 'Set what one run may spend';
88
+ return phrase('Set what one run may spend');
79
89
  if (task > cap)
80
- return 'One run cannot spend more than the whole ' + d.period;
90
+ return OVER[d.period];
81
91
  return '';
82
92
  }
83
93
  export async function agents(t) {
@@ -95,27 +105,52 @@ export async function profile(t, ref) {
95
105
  const raw = await call(t, 'GET', `/v1/agent/${seg(ref)}`);
96
106
  return { agent: agent(raw), runs: rows(obj(raw).recentRuns).map(run).filter((r) => r.id) };
97
107
  }
98
- /** What a default agent may spend until its owner says otherwise, in dollars: each month, and one run. */
108
+ /** What an agent made from a template may spend until its owner says otherwise, in dollars: each month, and one run. */
99
109
  export const CAST_CAP = '10';
100
110
  export const CAST_TASK = '1';
101
- /** The cast members the org has no agent for, by name in any case. */
102
- export const missing = (cast, have) => {
103
- const names = new Set(have.map((a) => a.name.toLowerCase()));
104
- return cast.filter((c) => !names.has(c.name.toLowerCase()));
105
- };
106
- /** Create every cast member the org lacks, one at a time; the names made, and the first refusal, if any. */
107
- export async function hire(t, cast, have) {
108
- const made = [];
109
- for (const c of missing(cast, have)) {
110
- try {
111
- await create(t, { ...EMPTY, name: c.name, description: c.description, model: c.model, instructions: c.instructions, cap: CAST_CAP, task: CAST_TASK, period: 'month' });
112
- made.push(c.name);
113
- }
114
- catch (error) {
115
- return { made, error };
116
- }
111
+ /**
112
+ * A name the API takes: accents dropped, any other run of characters a dash,
113
+ * from a letter or digit, up to 64. "Spärck Jones" is Sparck-Jones.
114
+ */
115
+ export const handle = (name) => name
116
+ .normalize('NFD')
117
+ .replace(/[\u0300-\u036f]/g, '')
118
+ .replace(/[^A-Za-z0-9._-]+/g, '-')
119
+ .replace(/^[^A-Za-z0-9]+/, '')
120
+ .slice(0, 64)
121
+ .replace(/-+$/, '');
122
+ /** `name`, or `name-2`, `name-3`… : the first the org has no agent called, in any case. */
123
+ export function free(name, have) {
124
+ const taken = new Set(have.map((a) => a.name.toLowerCase()));
125
+ if (!taken.has(name.toLowerCase()))
126
+ return name;
127
+ for (let i = 2;; i++) {
128
+ const next = `${name.slice(0, 63 - String(i).length)}-${i}`;
129
+ if (!taken.has(next.toLowerCase()))
130
+ return next;
117
131
  }
118
- return { made, error: null };
132
+ }
133
+ /** An address of the page as an https link the API keeps, or '' (a page served over http has none). */
134
+ export const link = (at, base) => {
135
+ if (!at)
136
+ return '';
137
+ const url = new URL(at, base).href;
138
+ return url.startsWith('https://') || url.startsWith('data:') ? url : '';
139
+ };
140
+ /**
141
+ * Make the org's own agent from a template: under the first name the org has
142
+ * free (read now, so it is never one that exists), with its instructions read
143
+ * and its face linked. The template is not touched.
144
+ */
145
+ export async function use(t, c, base) {
146
+ const [have, instructions] = await Promise.all([agents(t), c.read ? c.read() : c.instructions]);
147
+ return create(t, { ...EMPTY, name: free(handle(c.name), have), description: c.description, model: c.model, instructions, tools: c.tools ?? [], cap: CAST_CAP, task: CAST_TASK, avatar: link(c.avatar, base) });
148
+ }
149
+ /** Anyone as a template: their name, who they are, and writing of theirs to take the voice from. */
150
+ export function person(name, about, writing, avatar = '') {
151
+ const who = name.trim();
152
+ const lines = [`You are ${who}.`, about.trim(), writing.trim() && `Writing by ${who}:\n\n${writing.trim()}`];
153
+ return { name: who, title: who, description: about.trim(), category: phrase('Person'), model: '', instructions: lines.filter(Boolean).join('\n\n'), avatar };
119
154
  }
120
155
  export async function create(t, d) {
121
156
  const why = refuse(d);
@@ -132,6 +167,8 @@ export async function create(t, d) {
132
167
  };
133
168
  if (d.model)
134
169
  body.model = d.model;
170
+ if (d.avatar)
171
+ body.avatar = d.avatar;
135
172
  return agent(await call(t, 'POST', '/v1/agent', body));
136
173
  }
137
174
  /** The fields that differ from `was`, and nothing else: an omitted field keeps its value. */
@@ -173,7 +210,7 @@ export async function presets(t) {
173
210
  .map((p) => ({ id: str(p.id), title: str(p.title), prompt: str(p.systemPrompt) }))
174
211
  .filter((p) => p.id);
175
212
  }
176
- /** A new agent's draft from a preset. */
213
+ /** A preset as a template. */
177
214
  export function fromPreset(p) {
178
- return { ...EMPTY, name: p.id, description: p.title, instructions: p.prompt };
215
+ return { name: p.id, title: p.title || p.id, description: p.title, category: phrase('Preset'), model: '', instructions: p.prompt };
179
216
  }
package/lib/api/auto.d.ts CHANGED
@@ -1,24 +1,4 @@
1
- /**
2
- * Automations: a task that runs itself, on a schedule or when asked.
3
- *
4
- * GET /v1/auto/automations {data: [automation]}
5
- * POST /v1/auto/automations create one: name and instructions
6
- * GET /v1/auto/automations/{id} one
7
- * PATCH /v1/auto/automations/{id} change it, or switch it on or off
8
- * DELETE /v1/auto/automations/{id} delete it, its schedule and its runs
9
- * POST /v1/auto/automations/{id}/run {run: {id, status}} one run, now
10
- * GET /v1/auto/automations/{id}/runs {data: [run]} newest first
11
- * POST /v1/auto/automations/{id}/runs/{run}/review {post} post a held post, or discard it
12
- * GET /v1/auto/templates {data: [starter]} to start a new one from
13
- * POST /v1/auto/cron {schedule, says, next} a schedule from a person's words
14
- * GET /v1/auto/jobs {data: [job]} what runs without you, across products
15
- * GET /v1/auto/jobs/{id} one job
16
- * GET /v1/auto/events text/event-stream: `job` and `automation`, the reader's own
17
- *
18
- * Each run is one Dev run in this organization, started as the person who last
19
- * saved what it does. A draft is a flow named and not yet told what to do: it
20
- * runs once its instructions are saved.
21
- */
1
+ import type { Say } from '@hanzo/i18n/react';
22
2
  import { type Target } from './call.ts';
23
3
  import { type Source } from './library.ts';
24
4
  export type { Source };
@@ -107,8 +87,8 @@ export declare function start(t: Target, id: string): Promise<{
107
87
  export declare function runs(t: Target, id: string): Promise<Run[]>;
108
88
  /** Post a held post to the automation's accounts, or discard it. */
109
89
  export declare function review(t: Target, id: string, runId: string, post: boolean): Promise<Run>;
110
- /** A schedule the way a person says it. */
111
- export declare function words(s: Schedule): string;
90
+ /** A schedule the way a person says it, in the words of the `t` handed (English without one). */
91
+ export declare function words(s: Schedule, t?: Say): string;
112
92
  /** A ready-made automation to start a new one from. Its schedule names no zone: the page fills in the person's. */
113
93
  export interface Starter {
114
94
  key: string;
package/lib/api/auto.js CHANGED
@@ -19,6 +19,8 @@
19
19
  * saved what it does. A draft is a flow named and not yet told what to do: it
20
20
  * runs once its instructions are saved.
21
21
  */
22
+ import { phrase } from '@hanzo/i18n';
23
+ import { english } from '../say.js';
22
24
  import { call, headers, query, reason, Refusal, seg } from './call.js';
23
25
  import { inApp } from './library.js';
24
26
  import { read as stream } from './sse.js';
@@ -78,13 +80,13 @@ export async function automations(t) {
78
80
  export async function automation(t, id) {
79
81
  const a = read(await call(t, 'GET', `/v1/auto/automations/${seg(id)}`));
80
82
  if (!a)
81
- throw new Error('The automation came back without an id');
83
+ throw new Error(phrase('The automation came back without an id'));
82
84
  return a;
83
85
  }
84
86
  export async function create(t, d) {
85
87
  const a = read(await call(t, 'POST', '/v1/auto/automations', d));
86
88
  if (!a)
87
- throw new Error('The automation was created and came back without an id');
89
+ throw new Error(phrase('The automation was created and came back without an id'));
88
90
  return a;
89
91
  }
90
92
  /** What changed between two drafts, field by field: what a save sends. */
@@ -100,7 +102,7 @@ export function changes(was, now) {
100
102
  export async function save(t, id, change) {
101
103
  const a = read(await call(t, 'PATCH', `/v1/auto/automations/${seg(id)}`, change));
102
104
  if (!a)
103
- throw new Error('The automation was saved and came back without an id');
105
+ throw new Error(phrase('The automation was saved and came back without an id'));
104
106
  return a;
105
107
  }
106
108
  export async function remove(t, id) {
@@ -130,40 +132,55 @@ function run(o) {
130
132
  export async function review(t, id, runId, post) {
131
133
  return run(obj(await call(t, 'POST', `/v1/auto/automations/${seg(id)}/runs/${seg(runId)}/review`, { post })));
132
134
  }
133
- const DAYS = { mon: 'Mondays', tue: 'Tuesdays', wed: 'Wednesdays', thu: 'Thursdays', fri: 'Fridays', sat: 'Saturdays', sun: 'Sundays' };
134
- /** A once schedule's local date and time, as a person reads it. */
135
+ /** A weekly schedule's sentence by its day: one sentence each, so a language says the day its own way. */
136
+ const DAYS = {
137
+ mon: phrase('Mondays at {time} · {zone}'),
138
+ tue: phrase('Tuesdays at {time} · {zone}'),
139
+ wed: phrase('Wednesdays at {time} · {zone}'),
140
+ thu: phrase('Thursdays at {time} · {zone}'),
141
+ fri: phrase('Fridays at {time} · {zone}'),
142
+ sat: phrase('Saturdays at {time} · {zone}'),
143
+ sun: phrase('Sundays at {time} · {zone}'),
144
+ };
145
+ /** A once schedule's local date, as a person reads it, and its time. */
135
146
  function once(at) {
136
147
  const m = at.match(/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})$/);
137
148
  if (!m)
138
- return at;
149
+ return null;
139
150
  const d = new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]), Number(m[4]), Number(m[5]));
140
- return `${d.toLocaleDateString(undefined, { weekday: 'short', month: 'short', day: 'numeric' })} at ${m[4]}:${m[5]}`;
151
+ return { date: d.toLocaleDateString(undefined, { weekday: 'short', month: 'short', day: 'numeric' }), time: `${m[4]}:${m[5]}` };
141
152
  }
142
- /** A schedule the way a person says it. */
143
- export function words(s) {
144
- const zone = ` · ${s.tz || 'UTC'}`;
153
+ /** A schedule the way a person says it, in the words of the `t` handed (English without one). */
154
+ export function words(s, t = english) {
155
+ const zone = s.tz || 'UTC';
145
156
  switch (s.kind) {
146
157
  case 'manual':
147
- return 'When you run it';
148
- case 'once':
149
- return s.at ? `Once, ${once(s.at)}${zone}` : 'Once, now';
158
+ return t('When you run it');
159
+ case 'once': {
160
+ if (!s.at)
161
+ return t('Once, now');
162
+ const when = once(s.at);
163
+ return when ? t('Once, {date} at {time} · {zone}', { ...when, zone }) : t('Once, {at} · {zone}', { at: s.at, zone });
164
+ }
150
165
  case 'hourly':
151
- return `Every hour at :${(s.at ?? '00:00').slice(3) || '00'}`;
166
+ return t('Every hour at :{minute}', { minute: (s.at ?? '00:00').slice(3) || '00' });
152
167
  case 'daily':
153
- return `Every day at ${s.at}${zone}`;
168
+ return t('Every day at {time} · {zone}', { time: s.at ?? '', zone });
154
169
  case 'weekdays':
155
- return `Weekdays at ${s.at}${zone}`;
156
- case 'weekly':
157
- return `${DAYS[s.day ?? ''] ?? s.day} at ${s.at}${zone}`;
170
+ return t('Weekdays at {time} · {zone}', { time: s.at ?? '', zone });
171
+ case 'weekly': {
172
+ const day = DAYS[s.day ?? ''];
173
+ return day ? t(day, { time: s.at ?? '', zone }) : t('{day} at {time} · {zone}', { day: s.day ?? '', time: s.at ?? '', zone });
174
+ }
158
175
  case 'cron': {
159
176
  // The two a person says most, in their words; any other expression as written.
160
177
  const minutes = (s.cron ?? '').match(/^\*\/(\d+) \* \* \* \*$/);
161
178
  if (minutes)
162
- return `Every ${minutes[1]} minutes${zone}`;
179
+ return t('Every {n} minutes · {zone}', { n: minutes[1], zone });
163
180
  const hours = (s.cron ?? '').match(/^0 \*\/(\d+) \* \* \*$/);
164
181
  if (hours)
165
- return `Every ${hours[1]} hours${zone}`;
166
- return `Cron ${s.cron}${zone}`;
182
+ return t('Every {n} hours · {zone}', { n: hours[1], zone });
183
+ return t('Cron {cron} · {zone}', { cron: s.cron ?? '', zone });
167
184
  }
168
185
  }
169
186
  }
@@ -241,7 +258,7 @@ export async function jobs(t, f = {}) {
241
258
  export async function one(t, id) {
242
259
  const j = job(await call(t, 'GET', `/v1/auto/jobs/${seg(id)}`));
243
260
  if (!j)
244
- throw new Error('The job came back without an id');
261
+ throw new Error(phrase('The job came back without an id'));
245
262
  return j;
246
263
  }
247
264
  /** One frame of /v1/auto/events, or null for one this page does not read. */
@@ -1,38 +1,3 @@
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
- * GET /v1/billing/credits {grants}: every grant, spent and lapsed included
19
- * GET /v1/billing/tier the rung served and its tier class
20
- * GET /v1/billing/recharge the auto-reload rule
21
- * PUT /v1/billing/recharge arm it (a card on file) or disarm it
22
- * POST /v1/billing/topup charge a saved card into the balance
23
- * GET /v1/billing/usage/rollup the plan's month: included spend and request windows
24
- * GET /v1/usage/summary?range=month this month's spend, by category
25
- * GET /v1/allowance the Free plan's allowance, and the pool it is served from
26
- * GET /v1/billing/alerts spend caps (bare array)
27
- * POST /v1/billing/alerts open a cap (org admin)
28
- * PATCH /v1/billing/alerts/{id} change one (org admin)
29
- * DELETE /v1/billing/alerts/{id} lift one (org admin)
30
- *
31
- * Every figure is whole cents. The wallet, the caps and the cards are the
32
- * caller's own, resolved by the platform from the credential; nothing here names
33
- * an org or a subject. No card number passes through this file: a card is saved
34
- * from the token the processor's own form returns.
35
- */
36
1
  import { type Target } from './call.ts';
37
2
  /** `$1,234.56` from whole cents; whole dollars drop the cents. */
38
3
  export declare function money(cents: number, currency?: string): string;
@@ -33,6 +33,7 @@
33
33
  * an org or a subject. No card number passes through this file: a card is saved
34
34
  * from the token the processor's own form returns.
35
35
  */
36
+ import { phrase } from '@hanzo/i18n';
36
37
  import { call, headers, reason, Refusal, seg } from './call.js';
37
38
  const str = (v) => (typeof v === 'string' ? v : '');
38
39
  const num = (v) => (typeof v === 'number' && Number.isFinite(v) ? v : 0);
@@ -115,7 +116,7 @@ export async function subscriptions(t) {
115
116
  /** Buy a plan. No amount is sent: the catalog prices it, and the platform charges that. */
116
117
  export async function subscribe(t, p) {
117
118
  if (!p.method)
118
- throw new Error('Add a card before choosing a paid plan');
119
+ throw new Error(phrase('Add a card before choosing a paid plan'));
119
120
  const raw = obj(await call(t, 'POST', '/v1/billing/subscribe/card', { planId: p.plan, paymentMethodId: p.method, interval: p.interval }));
120
121
  return { subscription: str(raw.subscriptionId), charged: num(raw.amountCents) };
121
122
  }
@@ -183,7 +184,7 @@ export function chosen(list) {
183
184
  /** Save a card from the single-use token the processor's form returned, optionally with billing info. */
184
185
  export async function save(t, token, billingAddress) {
185
186
  if (!token)
186
- throw new Error('The card form returned no token');
187
+ throw new Error(phrase('The card form returned no token'));
187
188
  const body = { type: 'card', providerRef: token };
188
189
  if (billingAddress && Object.values(billingAddress).some(Boolean)) {
189
190
  body.billingAddress = billingAddress;
@@ -299,9 +300,9 @@ export async function arm(t, rule) {
299
300
  /** Charge a saved card and add the amount to the balance; answers the balance after. */
300
301
  export async function topup(t, amount, card) {
301
302
  if (!Number.isInteger(amount) || amount <= 0)
302
- throw new Error('Enter an amount to add');
303
+ throw new Error(phrase('Enter an amount to add'));
303
304
  if (!card)
304
- throw new Error('Add a card before buying more');
305
+ throw new Error(phrase('Add a card before buying more'));
305
306
  const r = obj(await call(t, 'POST', '/v1/billing/topup', { amountCents: amount, paymentMethodId: card }));
306
307
  return num(r.balanceCents);
307
308
  }
@@ -371,7 +372,7 @@ export function monthly(list) {
371
372
  /** Set the org's monthly cap: change the one there is, or open one that refuses spend past it. */
372
373
  export async function limit(t, list, threshold) {
373
374
  if (!Number.isInteger(threshold) || threshold <= 0)
374
- throw new Error('Enter a monthly limit');
375
+ throw new Error(phrase('Enter a monthly limit'));
375
376
  const was = monthly(list);
376
377
  if (was)
377
378
  return cap(await call(t, 'PATCH', `/v1/billing/alerts/${seg(was.id)}`, { threshold }));