@hanzo/build 0.2.51 → 0.2.53

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 (98) hide show
  1. package/README.md +109 -55
  2. package/lib/api/billing.d.ts +39 -0
  3. package/lib/api/billing.js +41 -0
  4. package/lib/api/coding.d.ts +4 -1
  5. package/lib/api/coding.js +2 -0
  6. package/lib/api/environment.d.ts +12 -0
  7. package/lib/api/environment.js +39 -1
  8. package/lib/api/github.d.ts +42 -4
  9. package/lib/api/github.js +79 -16
  10. package/lib/api/limits.d.ts +69 -0
  11. package/lib/api/limits.js +86 -0
  12. package/lib/api/links.d.ts +42 -0
  13. package/lib/api/links.js +65 -0
  14. package/lib/api/pref.d.ts +5 -3
  15. package/lib/api/pref.js +0 -3
  16. package/lib/api/turn.d.ts +10 -1
  17. package/lib/api/turn.js +16 -4
  18. package/lib/api/work.d.ts +75 -24
  19. package/lib/api/work.js +132 -27
  20. package/lib/builder.d.ts +0 -10
  21. package/lib/builder.js +21 -54
  22. package/lib/choice.d.ts +21 -0
  23. package/lib/choice.js +40 -1
  24. package/lib/customize/agents.d.ts +1 -1
  25. package/lib/customize/agents.js +21 -19
  26. package/lib/customize/connectors.d.ts +1 -1
  27. package/lib/customize/connectors.js +80 -31
  28. package/lib/customize/index.d.ts +2 -0
  29. package/lib/customize/index.js +23 -19
  30. package/lib/customize/load.d.ts +9 -0
  31. package/lib/customize/load.js +22 -0
  32. package/lib/customize/plugins.d.ts +1 -1
  33. package/lib/customize/plugins.js +21 -19
  34. package/lib/customize/say.d.ts +1 -0
  35. package/lib/customize/say.js +38 -0
  36. package/lib/customize/skills.d.ts +1 -1
  37. package/lib/customize/skills.js +23 -17
  38. package/lib/customize/ui.d.ts +28 -11
  39. package/lib/customize/ui.js +29 -21
  40. package/lib/data.d.ts +13 -0
  41. package/lib/data.js +70 -0
  42. package/lib/desk.d.ts +6 -1
  43. package/lib/desk.js +43 -19
  44. package/lib/environment.d.ts +11 -3
  45. package/lib/environment.js +53 -13
  46. package/lib/foot.js +10 -6
  47. package/lib/forge.js +255 -92
  48. package/lib/host.d.ts +1 -1
  49. package/lib/index.d.ts +28 -2
  50. package/lib/index.js +26 -2
  51. package/lib/landing.d.ts +0 -10
  52. package/lib/landing.js +79 -47
  53. package/lib/merge.d.ts +223 -0
  54. package/lib/merge.js +380 -0
  55. package/lib/meter.d.ts +31 -0
  56. package/lib/meter.js +45 -0
  57. package/lib/mind.d.ts +60 -0
  58. package/lib/mind.js +187 -0
  59. package/lib/nav.d.ts +11 -0
  60. package/lib/nav.js +75 -0
  61. package/lib/panel.d.ts +45 -0
  62. package/lib/panel.js +115 -0
  63. package/lib/pick.d.ts +8 -0
  64. package/lib/pick.js +18 -1
  65. package/lib/plan.d.ts +78 -0
  66. package/lib/plan.js +133 -0
  67. package/lib/project.d.ts +16 -1
  68. package/lib/project.js +100 -32
  69. package/lib/prompt.d.ts +75 -0
  70. package/lib/prompt.js +125 -0
  71. package/lib/prose.js +14 -10
  72. package/lib/reply.d.ts +36 -0
  73. package/lib/reply.js +124 -0
  74. package/lib/route.d.ts +28 -4
  75. package/lib/route.js +48 -1
  76. package/lib/run.js +87 -52
  77. package/lib/section.d.ts +1 -1
  78. package/lib/section.js +7 -11
  79. package/lib/settings/billing.js +16 -9
  80. package/lib/settings/code.js +15 -12
  81. package/lib/settings/credits.d.ts +9 -0
  82. package/lib/settings/credits.js +110 -0
  83. package/lib/settings/plan.d.ts +17 -0
  84. package/lib/settings/plan.js +108 -0
  85. package/lib/settings/usage.js +11 -119
  86. package/lib/shelf.js +3 -3
  87. package/lib/standing.d.ts +44 -0
  88. package/lib/standing.js +64 -0
  89. package/lib/sync.js +23 -9
  90. package/lib/tabs.d.ts +53 -0
  91. package/lib/tabs.js +197 -0
  92. package/lib/tools.d.ts +2 -9
  93. package/lib/tools.js +20 -25
  94. package/lib/transcript.d.ts +10 -1
  95. package/lib/transcript.js +25 -10
  96. package/lib/voice.d.ts +2 -13
  97. package/lib/voice.js +7 -73
  98. package/package.json +20 -3
package/README.md CHANGED
@@ -65,6 +65,15 @@ under the mount (`path`) and how to move (`go`), where to link out
65
65
  (`theme`, `chooseTheme`), which follows the person's saved choice. Peers:
66
66
  `@hanzo/ui`, `@hanzo/gui`, `@hanzogui/lucide-icons-2`, `react`.
67
67
 
68
+ `path` is the builder's own grammar (`route`, `path`). A host that shows real
69
+ paths writes it with `href` and reads it back with `parse`: '' New,
70
+ `/run/<id>`, `/projects/<org>/<repo>` a repository, `/projects/<slug>` a site,
71
+ `/machines` and `/environments` in the rail's own words, and every other place
72
+ at its name (`/projects`, `/issues`, `/customize/connectors`, `/settings/usage`).
73
+ `PLACES` lists every address that names no record, for a host that writes a page
74
+ per address ahead of time. `@hanzo/build/route` is that module alone, with no
75
+ React, for server code and for pages that must not carry the builder.
76
+
68
77
  ### In a host that has its own rail
69
78
 
70
79
  The Hanzo app already has a left column, so it mounts `<Builder host={host}
@@ -74,7 +83,7 @@ rail={false} />` in its pane and lists the builder in its own rail:
74
83
  import { Builder, DevSection } from '@hanzo/build'
75
84
 
76
85
  <Sidebar> {/* the host's rail, @hanzo/ui/chat */}
77
- <DevSection host={host} /> {/* New run, Artifacts, Templates, the runs */}
86
+ <DevSection host={host} /> {/* every place, grouped (nav), then the runs */}
78
87
  </Sidebar>
79
88
  <Builder host={host} rail={false} />
80
89
  ```
@@ -85,11 +94,24 @@ pane tokens: `--pane-fill`, `--pane-round`, `--pane-edge`, `--pane-blur`,
85
94
  `--pane-gap`, and `--shadow-sheet-2` for the drop. A host retunes the tokens;
86
95
  it writes no rule against the builder's slots.
87
96
 
88
- `DevSection` is rows of `@hanzo/ui/chat`'s Sidebar; `useSessions(host)` is its
89
- data — the org's coding runs, kept live by the org's feed — for a host that
90
- draws the rows itself. `administers(token, org)` reads the org-admin bit off a
97
+ `DevSection` is rows of `@hanzo/ui/chat`'s Sidebar; `nav(host, go)` is the one
98
+ list of places every rail draws, in groups (Work, Make, Run, Setup) and with
99
+ nothing behind a More; `useSessions(host)` is the runs — the org's coding runs,
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
91
101
  hanzo.id token for `Host.admin`. One left column, never two.
92
102
 
103
+ ## The model, the composer, the panel
104
+
105
+ One of each, shared with Chat (@hanzo/rooms imports them):
106
+
107
+ - `useMind` — the model and effort: `GET /v1/models` read once per platform,
108
+ and the choice kept in this browser (`hanzo.mind`, Enso and Medium until
109
+ changed). `Tune` is the chip that changes it; Settings → Code writes the same
110
+ choice. `@hanzo/build/mind` serves the store alone, without the builder.
111
+ - `prompt` / `Prompt` — the composer, as wide as the text above it (`measure`).
112
+ - `Reply` — copy, Listen and Open under an answer.
113
+ - `Panel` over `useDeck` — the side panel and its kept tabs.
114
+
93
115
  ## Addresses
94
116
 
95
117
  | path | screen |
@@ -98,29 +120,45 @@ hanzo.id token for `Host.admin`. One left column, never two.
98
120
  | `sess_<32 hex>` | one run, live |
99
121
  | `-/automations` | repeating work, read from `/v1/auto/flows` |
100
122
  | `-/customize` · `-/customize/connectors` · `/plugins` · `/agents` | Customize: skills, connectors, plugins, agents |
101
- | `-/mcp` | Connectors → Discover, where the fleet's native MCP servers now live |
102
- | `-/codebases` | the forge's repositories |
103
- | `-/sync` | bring granted repositories onto the forge |
104
- | `-/projects` | the forge's boards |
105
- | `-/issues` | the forge's issues |
123
+ | `-/mcp` | Connectors → Browse with the fleet's native MCP servers listed |
124
+ | `-/projects` | the org's projects: its repositories on the forge |
125
+ | `-/sync` | link repositories from GitHub onto the forge |
126
+ | `-/issues` | every project's issues, merged: the forge's and the task index's |
106
127
  | `-/artifacts` | what the org has built |
107
128
  | `-/templates` | the public starters |
108
129
  | `-/plans` | the plans this brand sells, and the move to another |
109
130
  | `-/settings/<section>` | Billing, Usage, Members, Integrations, Notifications, … |
110
- | `<slug>` | a project's workspace |
131
+ | `<org>/<repo>` | a project's workspace: the repository, and its site when it has one |
132
+ | `<slug>` | a deployed site's workspace |
111
133
 
112
- A project slug holds no `_` and never starts with `-`, so the forms cannot
113
- collide.
134
+ A project slug holds no `_` and never starts with `-`, and a repository is the
135
+ one address of two segments, so the forms cannot collide.
114
136
 
115
137
  ## What is on the screen
116
138
 
139
+ **Issues.** Every project's issues and pull requests, the forge's and the task
140
+ index's merged (merge.ts): the same issue on two boards is one row, with every
141
+ label and assignee either carried. A column of projects — every board, and every
142
+ repository an issue is filed under — each with what is open on it; Open and
143
+ Closed with their counts; a search; Label, Assignee, Kind, Source and Sort.
144
+ Pressing an issue opens New on the issue's own repository, where the issue
145
+ lives (a GitHub issue on GitHub, a forge issue on the forge), filled with its
146
+ title, number, link and body, and the run's pull request closes it; an issue no
147
+ project answers to (a Linear team's key, a board with no repository) asks which
148
+ project it is for. A board's key is never taken for a repository.
149
+
117
150
  **The rail** (this page, or `rail` left on). At the top, the mark. Signed in to an organization,
118
151
  that spot is the organization switcher: the org, and the projects under it.
119
- New, Projects, Artifacts, Customize, Automations, then More (Codebase, Issues,
120
- Templates, Machines, Docs), then the org's coding runs newest first with a live
121
- status dot. At the foot, a card offering Hanzo in Slack (dismissed once, gone in
122
- this browser) and the account, whose menu holds the email, Settings, Usage, View
123
- all plans, Get help (the docs, in a new tab) and Log out. Collapse is an explicit
152
+ New, then every place in view (nav.tsx) — Work: Projects, Issues; Make:
153
+ Artifacts, Templates, Automations; Run: Machines, Environments; Setup:
154
+ Customize, Docs — then the org's coding runs newest first with a live status
155
+ dot. Nothing waits behind a More. At the foot, a card offering Hanzo in Slack
156
+ (dismissed once, gone in this browser) and the account, whose menu holds the
157
+ email, the plan under it
158
+ (`Meter`: Max with 20x beside it and the session, day and month as shares;
159
+ Free's allowance left today; the balance only with no plan), Settings, Usage,
160
+ Billing, View all plans, Get help (the docs, in a new tab) and Log out. No money
161
+ sits beside a plan: credits live in Billing. Collapse is an explicit
124
162
  toggle kept in this browser. A host with its own rail draws `DevSection` there
125
163
  instead.
126
164
 
@@ -136,35 +174,44 @@ drain, claim key (shown once) and removal. API keys: your secret and publishable
136
174
  key, a new one shown once, rotated or revoked.
137
175
 
138
176
  **Customize.** What the agent brings to a run, a tab each — Skills, Connectors,
139
- Plugins, Agents — and in each, Yours (what the org has) and Discover (what it
140
- can add), one search and one Add. Skills and connectors ride into every run in
141
- the org, so they are an org admin's to add, switch and remove, and a member reads
142
- them; plugins and agents are any member's.
143
-
144
- - Skills: the org's own SKILL.md skills, written, revised and deleted here, and
145
- the brand's catalogue (`/.well-known/agent-skills/`) to read and add. Adding is
146
- switching `skill_<name>` on, and a switch per skill turns it off again.
147
- - Connectors: the MCP servers the org added — by URL or off the shelf, a secret
148
- sealed in KMS — each with a switch per tool. Adding one switches its tools on.
149
- Discover is the shelf (featured first; a listing that ships only a package has
150
- no plus) and the fleet's native servers `POST /v1/mcp` lists, which every run
151
- can call and nothing adds.
177
+ Plugins, Agents — and in each, Browse (what there is to add, then what Hanzo
178
+ builds in) first and always where the tab opens, then Yours (what the org has,
179
+ counted on its button); one search and one Add. An empty Yours names the next
180
+ step as buttons. A read that fails is said in plain words with Try again, and one
181
+ that takes over fifteen seconds gives up and says so; only a 400, 409 or 422
182
+ carries the platform's own sentence (a failed build, a name taken). Skills and
183
+ connectors ride into every run in the org, so they are an org admin's to add,
184
+ switch and remove, and a member reads them and is told so; plugins and agents
185
+ are any member's.
186
+
187
+ - Skills: Browse is the brand's catalogue (`/.well-known/agent-skills/`) to read
188
+ and add; Yours is the org's own SKILL.md skills, written, revised and deleted
189
+ here, and the catalogue's it added. Adding is switching `skill_<name>` on, and
190
+ a switch per skill turns it off again.
191
+ - Connectors: Browse is the shelf (featured first; a listing that ships only a
192
+ package says so and has no Add), then the fleet's native servers, which every
193
+ run can call and nothing adds — listed by `POST /v1/mcp` only when asked for,
194
+ since that list starts every subsystem it names. Yours is the MCP servers the
195
+ org added — by URL or off the shelf, a secret sealed in KMS — each with one
196
+ switch on its card for all its tools and one per tool inside. Adding one
197
+ switches its tools on.
152
198
  - Plugins: TypeScript connectors built on the platform, from source or from a
153
- description of an API; a failed build says why. Discover lists what the
154
- deployment mounts.
155
- - Agents: the org's own — model, instructions, tools, budget — created, edited
156
- (only what changed is sent) and deleted; Discover is the platform's presets
157
- whose tool calls run on the platform.
199
+ description of an API; a failed build says why. Browse offers building one and
200
+ lists what the deployment mounts.
201
+ - Agents: Browse is the platform's presets whose tool calls run on the platform,
202
+ each a Use that opens a new agent written from it; Yours is the org's own —
203
+ model, instructions, tools, budget — created, edited (only what changed is
204
+ sent) and deleted.
158
205
 
159
206
  **New.** "What's up next?", and at the foot the composer: where the run runs
160
207
  (Cloud is the platform's sandbox with the codebase's environment; the org's
161
208
  machines follow under remote control, each linked with `hanzo link`), the
162
209
  codebase and the branch — the forge's repositories, filtered as you type, read
163
210
  again with Refresh list, and a way to Sync for a GitHub repository that is not
164
- there yet — then the ask. Under it: attach (files ride the prompt as text),
165
- dictate, Build or Plan, and the model and effort. A codebase can be added to a
166
- project. Opening a codebase or an issue from its own screen lands here with
167
- that choice already made.
211
+ there yet — then the ask, in the composer Chat draws too (`prompt`): the paperclip
212
+ and Build or Plan at its start; the model and effort (`Tune`), dictation and
213
+ send at its end. A codebase can be added to a project. Opening a codebase or an
214
+ issue from its own screen lands here with that choice already made.
168
215
 
169
216
  **A run.** The transcript as it streams, drawn by what each part is: what the
170
217
  agent says as markdown, each command it runs as a card that opens onto its
@@ -174,17 +221,14 @@ request once it pushes one. Once it has finished, a follow-up starts a new run
174
221
  from the branch it pushed — the same codebase, place and mode — and opens it; a
175
222
  paused sandbox run goes on the same way. A plan run ends with its plan and
176
223
  Approve and build. Rename it, share a project run's story publicly, and find any
177
- run by status in Find. Beside it:
178
-
179
- - Environment: the codebase's environment, and the run's facts.
180
- - Git: what it pushed, read from the forge — Diff, Review, Commits.
181
- - Desktop: its sandbox's screen, framed from the sandbox's own noVNC page. A
182
- sandbox run asks for a desktop.
183
- - Terminal: a shell in the same working tree (a tmux session named for the run,
184
- so it reattaches), and the agent's own log.
185
- - Files: the working tree live from the sandbox, or the run's branch; and
186
- Artifacts, what it produced.
187
- - Subscriptions.
224
+ run by status in Find. Beside it, the side panel (`Panel`, the one Chat opens):
225
+ Preview (what the sandbox serves, or where the run is published), Artifacts,
226
+ Files, Diff (Diff, Review, Commits) and Terminal (a shell in the working tree,
227
+ reattached by the run's name, and the agent's log); `+` opens Desktop (the
228
+ sandbox's screen), Environment and any page. Tabs are dragged to reorder (or
229
+ moved with Ctrl/⌘+Shift+←/→), shut with their cross, and kept as left; the
230
+ header's Panel button and ⌘. / Ctrl+. open and shut it, and a phone opens it as
231
+ a sheet. An answer's Open shows what it wrote there, and a publish opens Preview.
188
232
 
189
233
  The desktop and the shell are the sandbox's, so they close when the run stops.
190
234
  The setup bar says how long setup runs here have taken, once there are three.
@@ -222,7 +266,8 @@ picker and open-in-tab; Share and Publish; the console dock under it.
222
266
  | `POST /v1/sandbox/read` | the run's working tree, live |
223
267
  | `GET /v1/provider/github/repos` · `POST /v1/provider/github/repos/import` | granted repositories, and bringing them onto the forge |
224
268
  | `GET /v1/git/repos` · `GET /v1/git/repos/{name}` | the codebase chip, and its branches |
225
- | `GET /v1/task/projects` · `GET /v1/task/board` · `GET /v1/task/projects/{key}/issues` | boards and issues, read from the forge |
269
+ | `GET /v1/sync` | the repositories linked from GitHub, each with its forge copy and when it last synced |
270
+ | `GET /v1/task/projects` · `GET /v1/task/board` · `GET /v1/task/projects/{key}/issues` | boards, and every issue and pull request: the forge's and the task index's |
226
271
  | `GET /v1/provider/github/repos` · `…/{owner}/{repo}/branches` | kept for a host that still asks GitHub |
227
272
  | `POST /v1/provider/github/user/connect` | connect a person's GitHub |
228
273
  | `GET /v1/projects` · `PATCH`/`DELETE /v1/projects/{slug}` · `POST /v1/projects/fork` · `GET /v1/templates` | artifacts, templates |
@@ -245,14 +290,17 @@ picker and open-in-tab; Share and Publish; the console dock under it.
245
290
  | `GET /v1/billing/plans` · `…/subscriptions` · `POST …/subscribe/card` · `…/subscriptions/{id}/cancel`·`/reactivate` | Plans, and the plan in Billing |
246
291
  | `GET`/`POST`/`DELETE /v1/billing/methods` · `GET /v1/billing/settings` | saved cards; a card is added from the processor's own field |
247
292
  | `GET /v1/billing/invoices` · `…/invoices/{id}/pdf` | invoices |
248
- | `GET /v1/billing/balance` · `…/credit-balance` · `…/usage/rollup` · `POST …/topup` · `GET /v1/usage/summary` | Usage |
293
+ | `GET`/`PUT /v1/ai/limits` · `GET /v1/billing/tier` · `GET /v1/allowance` | the plan's windows as shares, and credits past the allowance (Usage, the menu's `Meter`) |
294
+ | `GET /v1/billing/balance` · `…/credit-balance` · `…/credits` · `…/usage/rollup` · `GET`/`PUT …/recharge` · `POST …/topup` · `GET /v1/usage/summary` | Credits, in Billing: prepaid, granted, top-up, auto-reload, spend |
249
295
  | `GET`/`POST`/`PATCH`/`DELETE /v1/billing/alerts` | the monthly limit (org admin) |
250
296
  | `GET /v1/iam/memberships?org=` · `GET`/`POST`/`DELETE /v1/iam/invitations` | Members |
251
297
  | `GET /v1/provider/{slack,github}` · `POST …/connect`·`/disconnect` · `GET /v1/provider/slack/channels` · `GET /v1/provider/github/installations` | Integrations |
252
298
  | `GET`/`POST /v1/webhook` · `DELETE /v1/webhook/{id}` · `POST …/test` · `GET …/deliveries` | Notifications |
253
299
 
254
- `mode`, `model` and `effort` are sent with a run as asked; the platform
255
- honours them where it does and nothing here simulates them.
300
+ `mode`, `model` and `effort` are sent with a run as asked — Enso as
301
+ `enso-auto`, never left out — and `issue`, the number a run started from an
302
+ issue closes; the platform honours them where it does and nothing here
303
+ simulates them.
256
304
 
257
305
  ## Layout
258
306
 
@@ -261,11 +309,16 @@ src/
261
309
  index.ts the library surface
262
310
  builder.tsx the rail and the pane the address names
263
311
  landing.tsx New
264
- forge.tsx Codebase, Automations, Projects, Issues
312
+ forge.tsx Automations, Projects, Issues
313
+ merge.ts what is the same project and the same issue, which repository an issue is for
265
314
  run.tsx one run
266
315
  transcript.tsx a run's transcript, as cards
267
316
  prose.tsx markdown, drawn as text (markdown.ts reads it)
268
317
  desk.tsx the pane beside a run
318
+ mind.ts the model and effort, one choice with Chat
319
+ prompt.tsx the composer, and Tune, the model and effort chip
320
+ reply.tsx the row under an answer
321
+ panel.tsx the side panel; tabs.ts its kept tabs
269
322
  pane.ts how a pane is cut, from the --pane-* tokens
270
323
  door.tsx a run's desktop or shell, framed
271
324
  git.tsx what a run pushed
@@ -273,6 +326,7 @@ src/
273
326
  environment.tsx a codebase's environment
274
327
  project.tsx a project's workspace
275
328
  shelf.tsx Artifacts and Templates
329
+ nav.tsx the places, once, for every rail
276
330
  customize/ Customize: the shell, then one file per tab
277
331
  publish.tsx Add to project / Publish
278
332
  find.tsx finding a run, by status, paged
@@ -15,6 +15,10 @@
15
15
  * GET /v1/billing/invoices/{id}/pdf the invoice as a PDF
16
16
  * GET /v1/billing/balance prepaid balance, whole cents
17
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
18
22
  * POST /v1/billing/topup charge a saved card into the balance
19
23
  * GET /v1/billing/usage/rollup the plan's month: included spend and request windows
20
24
  * GET /v1/usage/summary?range=month this month's spend, by category
@@ -167,6 +171,41 @@ export declare function pdf(t: Target, id: string): Promise<Blob>;
167
171
  export declare function balance(t: Target): Promise<number>;
168
172
  /** Granted credit the org can spend, in US cents. */
169
173
  export declare function credit(t: Target): Promise<number>;
174
+ /** One grant of credit, spent, lapsed and voided included: the ledger, not the spendable figure. */
175
+ export interface Grant {
176
+ id: string;
177
+ name: string;
178
+ amount: number;
179
+ remaining: number;
180
+ currency: string;
181
+ /** RFC 3339, or '' for one that does not lapse. */
182
+ expires: string;
183
+ active: boolean;
184
+ }
185
+ export declare function grants(t: Target): Promise<Grant[]>;
186
+ /** The rung an organization is served as (`max-20x`, '' with none) and the tier class it is served under (`pro`). */
187
+ export interface Tier {
188
+ plan: string;
189
+ tier: string;
190
+ }
191
+ export declare function tier(t: Target): Promise<Tier>;
192
+ /** Auto-reload: top the balance up by `amount` whenever it falls below `threshold`, charging the card on file. */
193
+ export interface Recharge {
194
+ enabled: boolean;
195
+ threshold: number;
196
+ amount: number;
197
+ currency: string;
198
+ /** RFC 3339 of the last reload, or ''. */
199
+ last: string;
200
+ }
201
+ /** The rule as stored; one never set reads as off. */
202
+ export declare function recharge(t: Target): Promise<Recharge>;
203
+ /** Arm or disarm the rule. Turning it on needs a card on file, and the platform says so when there is none. */
204
+ export declare function arm(t: Target, rule: {
205
+ enabled: boolean;
206
+ threshold: number;
207
+ amount: number;
208
+ }): Promise<Recharge>;
170
209
  /** Charge a saved card and add the amount to the balance; answers the balance after. */
171
210
  export declare function topup(t: Target, amount: number, card: string): Promise<number>;
172
211
  export interface Window {
@@ -15,6 +15,10 @@
15
15
  * GET /v1/billing/invoices/{id}/pdf the invoice as a PDF
16
16
  * GET /v1/billing/balance prepaid balance, whole cents
17
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
18
22
  * POST /v1/billing/topup charge a saved card into the balance
19
23
  * GET /v1/billing/usage/rollup the plan's month: included spend and request windows
20
24
  * GET /v1/usage/summary?range=month this month's spend, by category
@@ -255,6 +259,43 @@ export async function credit(t) {
255
259
  .filter((b) => (str(b.currency) || 'usd').toLowerCase() === 'usd')
256
260
  .reduce((sum, b) => sum + num(b.available), 0);
257
261
  }
262
+ export async function grants(t) {
263
+ const raw = obj(await call(t, 'GET', '/v1/billing/credits'));
264
+ return arr(raw.grants)
265
+ .map(obj)
266
+ .map((g) => ({
267
+ id: str(g.id),
268
+ name: str(g.name),
269
+ amount: num(g.amountCents),
270
+ remaining: num(g.remainingCents),
271
+ currency: str(g.currency) || 'usd',
272
+ expires: str(g.expiresAt),
273
+ active: g.active === true && g.voided !== true,
274
+ }))
275
+ .filter((g) => g.id);
276
+ }
277
+ export async function tier(t) {
278
+ const r = obj(await call(t, 'GET', '/v1/billing/tier'));
279
+ return { plan: str(r.plan), tier: str(obj(r.tier).name) };
280
+ }
281
+ function recharged(raw) {
282
+ const r = obj(raw);
283
+ return {
284
+ enabled: r.enabled === true,
285
+ threshold: num(r.thresholdCents),
286
+ amount: num(r.amountCents),
287
+ currency: str(r.currency) || 'usd',
288
+ last: str(r.lastRechargedAt),
289
+ };
290
+ }
291
+ /** The rule as stored; one never set reads as off. */
292
+ export async function recharge(t) {
293
+ return recharged(await call(t, 'GET', '/v1/billing/recharge'));
294
+ }
295
+ /** Arm or disarm the rule. Turning it on needs a card on file, and the platform says so when there is none. */
296
+ export async function arm(t, rule) {
297
+ return recharged(await call(t, 'PUT', '/v1/billing/recharge', { enabled: rule.enabled, thresholdCents: rule.threshold, amountCents: rule.amount, currency: 'usd' }));
298
+ }
258
299
  /** Charge a saved card and add the amount to the balance; answers the balance after. */
259
300
  export async function topup(t, amount, card) {
260
301
  if (!Number.isInteger(amount) || amount <= 0)
@@ -50,8 +50,11 @@ export interface Ask {
50
50
  /** An earlier run's session to continue from, on the branch it pushed. */
51
51
  after?: string;
52
52
  mode?: Mode;
53
+ /** The model the agent thinks with, by the id GET /v1/models lists; Enso is sent as itself. */
53
54
  model?: string;
54
55
  effort?: string;
56
+ /** The number of the issue on `repo` this run resolves: its pull request says `Closes #<n>`. */
57
+ issue?: number;
55
58
  }
56
59
  export interface Run {
57
60
  session: string;
@@ -65,7 +68,7 @@ export interface Run {
65
68
  * A run in the sandbox asks for one with a desktop, so its Desktop tab has a
66
69
  * screen to show; a machine is whatever it is.
67
70
  */
68
- export declare function body(ask: Ask): Record<string, string | boolean>;
71
+ export declare function body(ask: Ask): Record<string, string | number | boolean>;
69
72
  export declare function start(t: Target, ask: Ask): Promise<Run>;
70
73
  /** A run a follow-up continues, as its record states it. */
71
74
  export interface Earlier {
package/lib/api/coding.js CHANGED
@@ -45,6 +45,8 @@ export function body(ask) {
45
45
  if (v)
46
46
  out[k] = v;
47
47
  }
48
+ if (ask.issue && Number.isInteger(ask.issue) && ask.issue > 0)
49
+ out.issue = ask.issue;
48
50
  if (!out.targetId)
49
51
  out.desktop = true;
50
52
  return out;
@@ -37,6 +37,18 @@ export interface Environment {
37
37
  updated: string;
38
38
  }
39
39
  export declare function environment(raw: unknown, repo?: string): Environment;
40
+ /**
41
+ * Whether a codebase still waits to be set up: nothing proposed and nothing ever
42
+ * saved. Skip & save keeps an empty environment, which the platform answers as
43
+ * state `none` with the time it was saved (apps/environment `view`), so the
44
+ * time is what tells a choice made from one never made.
45
+ */
46
+ export declare const unset: (e: Environment) => boolean;
47
+ /**
48
+ * 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.
50
+ */
51
+ export declare function plain(e: unknown, repo: string, act: 'save' | 'start' | 'run'): string;
40
52
  export declare function environments(t: Target): Promise<Environment[]>;
41
53
  export declare function read(t: Target, repo: string): Promise<Environment>;
42
54
  export declare function save(t: Target, repo: string, scripts: {
@@ -14,7 +14,7 @@
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, seg } from './call.js';
17
+ import { call, Refusal, seg } from './call.js';
18
18
  const str = (v) => (typeof v === 'string' ? v : '');
19
19
  const obj = (v) => (v && typeof v === 'object' ? v : {});
20
20
  const names = (v) => (Array.isArray(v) ? v.filter((n) => typeof n === 'string' && n !== '') : []);
@@ -34,6 +34,44 @@ export function environment(raw, repo = '') {
34
34
  updated: str(o.updatedAt),
35
35
  };
36
36
  }
37
+ /**
38
+ * Whether a codebase still waits to be set up: nothing proposed and nothing ever
39
+ * saved. Skip & save keeps an empty environment, which the platform answers as
40
+ * state `none` with the time it was saved (apps/environment `view`), so the
41
+ * time is what tells a choice made from one never made.
42
+ */
43
+ export const unset = (e) => e.state === 'none' && !e.updated;
44
+ /**
45
+ * 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.
47
+ */
48
+ export function plain(e, repo, act) {
49
+ // This page's own refusals (a secret's name, an empty value) are already a reader's words.
50
+ if (e instanceof Error && !(e instanceof Refusal) && e.name === 'Error' && e.message)
51
+ return e.message;
52
+ if (e instanceof Refusal) {
53
+ const why = e.message;
54
+ if (e.status === 401)
55
+ return 'Your session ended. Sign in again, then try again.';
56
+ 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.';
58
+ if (e.status === 402)
59
+ return 'This run needs a plan with runs left. See Plans to change it.';
60
+ if (e.status === 403)
61
+ return `This account cannot start a run on ${repo}. Ask an organization admin for access.`;
62
+ if (e.status === 429)
63
+ return 'Too many runs are going right now. Try again in a minute.';
64
+ 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.';
66
+ 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.`;
68
+ 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.';
70
+ }
71
+ 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.`;
74
+ }
37
75
  export async function environments(t) {
38
76
  const r = await call(t, 'GET', '/v1/environment');
39
77
  return (Array.isArray(r?.data) ? r.data : []).map((e) => environment(e)).filter((e) => e.repo);
@@ -8,6 +8,7 @@
8
8
  * POST /v1/provider/github/user/complete {grant} → the connection
9
9
  * POST /v1/provider/github/user/disconnect
10
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
11
12
  *
12
13
  * Paged by an opaque `after` cursor; `next` is the cursor for the page after
13
14
  * this one, or empty on the last. Rows are read defensively: a field the
@@ -31,6 +32,8 @@ export interface Repo {
31
32
  pushed_at: string;
32
33
  /** The App installation that grants access, or 0 for the person's own grant. */
33
34
  installation_id: number;
35
+ /** The name it takes on the forge once brought there: `owner_name`, folded. */
36
+ codebase: string;
34
37
  }
35
38
  export interface Repos {
36
39
  repos: Repo[];
@@ -105,9 +108,11 @@ export interface Grant {
105
108
  fullName: string;
106
109
  private: boolean;
107
110
  branch: string;
108
- /** Already mirrored onto the forge. */
111
+ /** The name it takes on the forge once brought there. */
112
+ codebase: string;
113
+ /** On the forge: linked and landed, or held there under its codebase name. */
109
114
  imported: boolean;
110
- /** `synced`, `conflict`, or '' while it has not landed. */
115
+ /** `synced`, `conflict`, `pending`, `paused`, or '' while it is not linked. */
111
116
  status: string;
112
117
  }
113
118
  export interface Grants {
@@ -121,10 +126,43 @@ export interface Account {
121
126
  /** Named as unread and carrying no repositories. */
122
127
  blocked: boolean;
123
128
  }
129
+ /**
130
+ * One row of GET /v1/provider/github/repos (apps/provider github_index.go
131
+ * repoView): `owner`, `name`, `full_name`, `private`, `default_branch`,
132
+ * `pushed_at`, `installation_id`, `codebase`. Whether it is on the forge is not
133
+ * in the row — `codebase` is the name it would take, not a sign it has — so
134
+ * `imported` is decided by `grants` from the links and the forge.
135
+ */
124
136
  export declare function grant(raw: unknown): Grant | null;
125
137
  /** Accounts the grant names, plus any account that could not be read. */
126
138
  export declare function accounts(g: Grants): Account[];
127
- /** Every repository the GitHub connection grants, with whether the forge already has it. */
139
+ /**
140
+ * Every repository the GitHub connection grants, every page, with whether the
141
+ * forge already has it: a link that has landed (GET /v1/sync, its `native`
142
+ * state), or a forge repository under its codebase name (GET /v1/git/repos).
143
+ */
128
144
  export declare function grants(t: Target, signal?: AbortSignal): Promise<Grants>;
145
+ /** What an import queued: how many, and the name each takes on the forge, by `owner/name`. */
146
+ export interface Queued {
147
+ queued: number;
148
+ codebases: Record<string, string>;
149
+ }
129
150
  /** Queue a mirror of the named repositories onto the forge. The names are `owner/name`. */
130
- export declare function bring(t: Target, repos: string[]): Promise<number>;
151
+ export declare function bring(t: Target, repos: string[]): Promise<Queued>;
152
+ /** What one issue sync did. */
153
+ export interface Synced {
154
+ repos: number;
155
+ issues: number;
156
+ created: number;
157
+ updated: number;
158
+ failed: number;
159
+ /** The pass stopped at its budget; syncing again continues where it stopped. */
160
+ truncated: boolean;
161
+ }
162
+ /**
163
+ * Mirror GitHub issues onto the board — open and closed, so a closed one reads as
164
+ * closed here too. `repo` (`name` or `owner/name`) syncs one project; empty syncs
165
+ * every repository the connection grants. Idempotent: a second sync updates and
166
+ * never duplicates, and the App's webhook keeps them live afterwards.
167
+ */
168
+ export declare function syncIssues(t: Target, repo?: string): Promise<Synced>;