@hanzo/build 0.2.57 → 0.2.58

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.
package/README.md CHANGED
@@ -118,13 +118,14 @@ One of each, shared with Chat (@hanzo/rooms imports them):
118
118
  |---|---|
119
119
  | `''` | New — the empty state and the composer |
120
120
  | `sess_<32 hex>` | one run, live |
121
- | `-/automations` | repeating work, read from `/v1/auto/flows` |
121
+ | `-/automations` | Scheduled tasks: your tasks, then Activity, every run across products |
122
+ | `-/automations/<id>` | one task's editor and runs; `new` writes one |
122
123
  | `-/customize` · `-/customize/connectors` · `/plugins` · `/agents` | Customize: skills, connectors, plugins, agents |
123
124
  | `-/mcp` | Connectors → Browse with the fleet's native MCP servers listed |
124
125
  | `-/projects` | the org's projects: its repositories on the forge |
125
126
  | `-/sync` | link repositories from GitHub onto the forge |
126
127
  | `-/issues` | every project's issues, merged: the forge's and the task index's |
127
- | `-/artifacts` | what the org has built |
128
+ | `-/artifacts` | the Library: everything you made in Chat, Dev and Studio; `?item=<id>` opens one; the org's sites at its foot |
128
129
  | `-/templates` | the public starters |
129
130
  | `-/plans` | the plans this brand sells, and the move to another |
130
131
  | `-/settings/<section>` | Billing, Usage, Members, Integrations, Notifications, … |
@@ -150,7 +151,7 @@ project it is for. A board's key is never taken for a repository.
150
151
  **The rail** (this page, or `rail` left on). At the top, the mark. Signed in to an organization,
151
152
  that spot is the organization switcher: the org, and the projects under it.
152
153
  New, then every place in view (nav.tsx) — Work: Projects, Issues; Make:
153
- Artifacts, Templates, Automations; Run: Machines, Environments; Setup:
154
+ Library, Templates, Scheduled tasks; Run: Machines, Environments; Setup:
154
155
  Customize, Docs — then the org's coding runs newest first with a live status
155
156
  dot. Nothing waits behind a More. At the foot, a card offering Hanzo in Slack
156
157
  (dismissed once, gone in this browser) and the account, whose menu holds the
@@ -162,10 +163,31 @@ sits beside a plan: credits live in Billing. Collapse is an explicit
162
163
  toggle kept in this browser. A host with its own rail draws `DevSection` there
163
164
  instead.
164
165
 
165
- **Artifacts.** A few starters to make something new from, each copied into a
166
- project as Templates does, then every project under the month it was last
167
- edited, public or private, with rename, visibility and delete. A project belongs
168
- to the org and names no author, so there is no Yours or Shared with you.
166
+ **Library.** Everything you made — Chat's pictures and files, Dev's patches and
167
+ reports, Studio's posters and videos, a workflow's outputs — newest first under
168
+ Today, Yesterday and the month, as a grid or a list, narrowed by kind, by the
169
+ product that made it and by whose (yours, or what a colleague shared). A tile
170
+ opens its item in the side panel (`Panel`, one tab per item; a sheet on a
171
+ phone), read fresh so its signed URLs are live. Each item opens where it was
172
+ made, downloads, is shared with the org or not, copies its link, is renamed,
173
+ moved to a project, used in Studio or Chat, or deleted; what a colleague shared
174
+ offers only what a reader may do. An item is its person's, so this list splits
175
+ yours from shared, where the sites beneath it do not.
176
+
177
+ **Sites**, at the Library's foot. A few starters to make something new from,
178
+ each copied into a project as Templates does, then every deployed site under the
179
+ month it was last edited, public or private, with rename, visibility and
180
+ delete. A site belongs to the org and names no author.
181
+
182
+ **Scheduled tasks.** Your tasks — prompts on a schedule from Chat and Dev, and
183
+ workflows — each with when it runs next, where it came from, a switch that
184
+ pauses and resumes it, and Run now, Edit and Delete; then Activity, every run
185
+ across products (a task's, Dev's background runs, Studio's renders) with its
186
+ state, how long it took, its cost and what it made, kept live by
187
+ `/v1/auto/events`. A run opens with its summary or error, its log (the Dev
188
+ run), its outputs as Library tiles and its cost. New task takes the schedule in
189
+ plain words, which `/v1/auto/cron` reads back before anything is made. A Dev
190
+ run's menu offers Schedule this, which opens New task with the run's ask.
169
191
 
170
192
  **Settings, Code.** Environments: every codebase that has one, opened in the
171
193
  same editor a run shows, forgotten by an org admin. Machines: `hanzo login` and
@@ -259,7 +281,9 @@ picker and open-in-tab; Share and Publish; the console dock under it.
259
281
  | `GET /v1/agent/sessions?kind=coding&status=&after=` | Find, by status, paged |
260
282
  | `GET`/`POST /v1/agent/targets` · `PATCH`/`DELETE …/{id}` · `POST …/{id}/key` | the org's machines, and a machine's claim key |
261
283
  | `GET`/`POST /v1/account/keys` · `DELETE …/{id}` | your API keys, as many as you need; one revoked at a time |
262
- | `GET /v1/auto/flows` · `POST /v1/auto/flows` · `POST /v1/auto/flows/{id}/enable` | automations |
284
+ | `GET`/`POST /v1/auto/automations` · `GET`/`PATCH`/`DELETE …/{id}` · `POST …/{id}/run` · `GET …/{id}/runs` | Scheduled tasks, and one task's editor |
285
+ | `GET /v1/auto/jobs` · `…/{id}` · `GET /v1/auto/events` (SSE over fetch) · `POST /v1/auto/cron` | Activity, live, and a schedule from plain words |
286
+ | `GET /v1/library` · `GET`/`PATCH`/`DELETE /v1/library/{id}` · `POST /v1/library` | the Library |
263
287
  | `GET /v1/environment` · `GET`/`PUT`/`DELETE /v1/environment/{repo}` · `PUT`/`DELETE …/secrets/{name}` | a codebase's environment |
264
288
  | `GET /v1/agent/coding/{session}/changes` · `/tree` · `/blob` | what a run pushed, and its branch's files, from the forge |
265
289
  | `POST /v1/sandbox/{id}/screen/ticket` · `…/terminal/ticket` | the run's desktop and shell, framed with a single-use ticket |
@@ -270,7 +294,7 @@ picker and open-in-tab; Share and Publish; the console dock under it.
270
294
  | `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 |
271
295
  | `GET /v1/provider/github/repos` · `…/{owner}/{repo}/branches` | kept for a host that still asks GitHub |
272
296
  | `POST /v1/provider/github/user/connect` | connect a person's GitHub |
273
- | `GET /v1/projects` · `PATCH`/`DELETE /v1/projects/{slug}` · `POST /v1/projects/fork` · `GET /v1/templates` | artifacts, templates |
297
+ | `GET /v1/projects` · `PATCH`/`DELETE /v1/projects/{slug}` · `POST /v1/projects/fork` · `GET /v1/templates` | sites, templates |
274
298
  | `GET /v1/git/repos/{name}/tree` · `/blob` | Files and Code |
275
299
  | `POST /v1/platform/apps` · `GET /v1/platform/builds` | Add to project, Publish |
276
300
  | `POST /v1/mcp` | the native MCP servers, `tools/list` |
@@ -309,7 +333,9 @@ src/
309
333
  index.ts the library surface
310
334
  builder.tsx the rail and the pane the address names
311
335
  landing.tsx New
312
- forge.tsx Automations, Projects, Issues
336
+ forge.tsx Projects, Issues, and the screens it hands on
337
+ library.tsx the Library; api/library.ts its wire
338
+ scheduled.tsx Scheduled tasks; activity.ts its words; automations.tsx one task's editor
313
339
  merge.ts what is the same project and the same issue, which repository an issue is for
314
340
  run.tsx one run
315
341
  transcript.tsx a run's transcript, as cards
@@ -325,7 +351,7 @@ src/
325
351
  files.tsx a run's files and artifacts
326
352
  environment.tsx a codebase's environment
327
353
  project.tsx a project's workspace
328
- shelf.tsx Artifacts and Templates
354
+ shelf.tsx Sites and Templates
329
355
  nav.tsx the places, once, for every rail
330
356
  customize/ Customize: the shell, then one file per tab
331
357
  publish.tsx Add to project / Publish
@@ -0,0 +1,20 @@
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 type { SessionStatus } from '@hanzo/ui/chat';
7
+ import type { Job, State } from './api/auto.ts';
8
+ /** A job's state as the rail's dot draws it, and in words. */
9
+ export declare const DOTS: Record<State, SessionStatus>;
10
+ export declare const SAID: Record<State, string>;
11
+ /** How long a run took, or has taken so far. */
12
+ export declare function lasted(started: string, finished: string | null, now?: number): string;
13
+ /** Cents as dollars. */
14
+ 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;
17
+ /** The jobs, newest first, with `j` in its place. */
18
+ export declare function merged(list: Job[], j: Job): Job[];
19
+ /** A task's name from what it does: its first line, short. */
20
+ export declare function titled(prompt: string): string;
@@ -0,0 +1,38 @@
1
+ import { ago } from './ago.js';
2
+ import { SOURCE } from './api/library.js';
3
+ /** A job's state as the rail's dot draws it, and in words. */
4
+ 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' };
6
+ /** How long a run took, or has taken so far. */
7
+ export function lasted(started, finished, now = Date.now()) {
8
+ const a = Date.parse(started);
9
+ const b = finished ? Date.parse(finished) : now;
10
+ if (Number.isNaN(a) || Number.isNaN(b) || b < a)
11
+ return '';
12
+ const s = Math.round((b - a) / 1000);
13
+ if (s < 60)
14
+ return `${s}s`;
15
+ const m = Math.floor(s / 60);
16
+ if (m < 60)
17
+ return `${m}m ${s % 60}s`;
18
+ const h = Math.floor(m / 60);
19
+ return `${h}h ${m % 60}m`;
20
+ }
21
+ /** Cents as dollars. */
22
+ 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(' · ');
27
+ }
28
+ /** The jobs, newest first, with `j` in its place. */
29
+ export function merged(list, j) {
30
+ const out = list.filter((x) => x.id !== j.id);
31
+ out.push(j);
32
+ return out.sort((a, b) => (b.started || b.updated).localeCompare(a.started || a.updated));
33
+ }
34
+ /** A task's name from what it does: its first line, short. */
35
+ export function titled(prompt) {
36
+ const first = prompt.trim().split('\n')[0].trim();
37
+ return first.length > 60 ? `${first.slice(0, 57).trimEnd()}…` : first;
38
+ }
package/lib/api/auto.d.ts CHANGED
@@ -10,18 +10,24 @@
10
10
  * GET /v1/auto/automations/{id}/runs {data: [run]} newest first
11
11
  * POST /v1/auto/automations/{id}/runs/{run}/review {post} post a held post, or discard it
12
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
13
17
  *
14
18
  * Each run is one Dev run in this organization, started as the person who last
15
19
  * saved what it does. A draft is a flow named and not yet told what to do: it
16
20
  * runs once its instructions are saved.
17
21
  */
18
22
  import { type Target } from './call.ts';
19
- export type Kind = 'manual' | 'hourly' | 'daily' | 'weekdays' | 'weekly' | 'cron';
23
+ import { type Source } from './library.ts';
24
+ export type { Source };
25
+ export type Kind = 'manual' | 'once' | 'hourly' | 'daily' | 'weekdays' | 'weekly' | 'cron';
20
26
  export type Permissions = 'ask' | 'auto';
21
27
  /** When it runs, in the person's own words and zone. */
22
28
  export interface Schedule {
23
29
  kind: Kind;
24
- /** HH:MM, 24-hour, for daily, weekdays and weekly; 00:MM for hourly. */
30
+ /** HH:MM, 24-hour, for daily, weekdays and weekly; 00:MM for hourly; YYYY-MM-DDTHH:MM for once, absent to run now. */
25
31
  at?: string;
26
32
  /** mon … sun, for weekly. */
27
33
  day?: string;
@@ -42,6 +48,8 @@ export interface Draft {
42
48
  /** The connected accounts each run's answer is posted to: x, linkedin, facebook. */
43
49
  postTo: string[];
44
50
  enabled: boolean;
51
+ /** The product that made it: chat, dev, studio or workflow. */
52
+ source: Source;
45
53
  }
46
54
  export interface Last {
47
55
  id: string;
@@ -78,6 +86,8 @@ export interface Run {
78
86
  /** The post its agent wrote, for an automation that posts. */
79
87
  draft: string;
80
88
  posts: Posted[];
89
+ /** The Library items it made. */
90
+ outputs: string[];
81
91
  }
82
92
  /** One automation as the platform answers it, or null for a row with no id. */
83
93
  export declare function read(raw: unknown): Automation | null;
@@ -109,3 +119,68 @@ export interface Starter {
109
119
  }
110
120
  /** The starters, from GET /v1/auto/templates. */
111
121
  export declare function starters(t: Target): Promise<Starter[]>;
122
+ /** What a person's words mean as a schedule: the schedule, how the platform says it back, and its next run. */
123
+ export interface Parsed {
124
+ schedule: Schedule;
125
+ says: string;
126
+ /** RFC 3339, or null for one that runs only when asked. */
127
+ next: string | null;
128
+ }
129
+ /** A schedule from a person's own words ("every monday at 9am"), read in `tz`. */
130
+ export declare function cron(t: Target, text: string, tz: string, init?: {
131
+ signal?: AbortSignal;
132
+ }): Promise<Parsed>;
133
+ /** What a job is doing, in the five words every product shares. */
134
+ export type State = 'scheduled' | 'running' | 'succeeded' | 'failed' | 'cancelled';
135
+ export declare const STATES: readonly State[];
136
+ /** One run of anything that runs without its person watching. */
137
+ export interface Job {
138
+ id: string;
139
+ product: Source;
140
+ kind: string;
141
+ title: string;
142
+ state: State;
143
+ /** The product's own word for it: queued, review, skipped… */
144
+ status: string;
145
+ /** 0 to 1, or null when the product does not say. */
146
+ progress: number | null;
147
+ automation: string | null;
148
+ /** The Dev run it started, whose transcript is its log. */
149
+ session: string | null;
150
+ ref: string;
151
+ origin: string;
152
+ summary: string;
153
+ error: string;
154
+ /** The Library items it made. */
155
+ outputs: string[];
156
+ /** Cents, or null while unknown. */
157
+ cost: number | null;
158
+ started: string;
159
+ finished: string | null;
160
+ updated: string;
161
+ }
162
+ /** One job as the platform answers it, or null for a row with no id. */
163
+ export declare function job(raw: unknown): Job | null;
164
+ export interface JobFilter {
165
+ product?: Source | '';
166
+ state?: State | '';
167
+ limit?: number;
168
+ }
169
+ export declare function jobs(t: Target, f?: JobFilter): Promise<Job[]>;
170
+ export declare function one(t: Target, id: string): Promise<Job>;
171
+ /** What the events stream says: a job or an automation, as it stands now. */
172
+ export type Heard = {
173
+ kind: 'job';
174
+ job: Job;
175
+ } | {
176
+ kind: 'automation';
177
+ automation: Automation;
178
+ };
179
+ /** One frame of /v1/auto/events, or null for one this page does not read. */
180
+ export declare function heard(event: string, data: string): Heard | null;
181
+ /**
182
+ * Read /v1/auto/events until `signal` ends it or the stream closes, calling `on`
183
+ * for each job and automation it names. A fetch, because EventSource cannot
184
+ * carry the bearer.
185
+ */
186
+ export declare function events(t: Target, on: (h: Heard) => void, signal: AbortSignal, opened?: () => void): Promise<void>;
package/lib/api/auto.js CHANGED
@@ -10,16 +10,25 @@
10
10
  * GET /v1/auto/automations/{id}/runs {data: [run]} newest first
11
11
  * POST /v1/auto/automations/{id}/runs/{run}/review {post} post a held post, or discard it
12
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
13
17
  *
14
18
  * Each run is one Dev run in this organization, started as the person who last
15
19
  * saved what it does. A draft is a flow named and not yet told what to do: it
16
20
  * runs once its instructions are saved.
17
21
  */
18
- import { call, seg } from './call.js';
22
+ import { call, headers, query, reason, Refusal, seg } from './call.js';
23
+ import { inApp } from './library.js';
24
+ import { read as stream } from './sse.js';
19
25
  const str = (v) => (typeof v === 'string' ? v : '');
20
26
  const nullable = (v) => (typeof v === 'string' && v ? v : null);
21
27
  const obj = (v) => (v && typeof v === 'object' ? v : {});
22
- const KINDS = ['manual', 'hourly', 'daily', 'weekdays', 'weekly', 'cron'];
28
+ const KINDS = ['manual', 'once', 'hourly', 'daily', 'weekdays', 'weekly', 'cron'];
29
+ const SOURCES = ['chat', 'dev', 'studio', 'workflow'];
30
+ const sourceOf = (v) => (SOURCES.includes(v) ? v : 'dev');
31
+ const strings = (v) => (Array.isArray(v) ? v : []).filter((x) => typeof x === 'string' && x !== '');
23
32
  function schedule(raw) {
24
33
  const o = obj(raw);
25
34
  const kind = KINDS.includes(o.kind) ? o.kind : 'manual';
@@ -47,6 +56,7 @@ export function read(raw) {
47
56
  notify: o.notify === true,
48
57
  postTo: (Array.isArray(o.postTo) ? o.postTo : []).filter((x) => typeof x === 'string'),
49
58
  enabled: o.enabled === true,
59
+ source: sourceOf(o.source),
50
60
  draft: o.draft === true,
51
61
  next: nullable(o.next),
52
62
  last: str(last.id) ? { id: str(last.id), status: str(last.status), at: str(last.at), summary: str(last.summary) } : null,
@@ -114,19 +124,29 @@ function run(o) {
114
124
  .map(obj)
115
125
  .filter((p) => str(p.to))
116
126
  .map((p) => ({ to: str(p.to), url: /^https:\/\//.test(str(p.url)) ? str(p.url) : '', error: str(p.error) }));
117
- return { id: str(o.id), status: str(o.status), at: str(o.at), finished: nullable(o.finished), summary: str(o.summary), session: session(o.transcript), draft: str(o.draft), posts };
127
+ return { id: str(o.id), status: str(o.status), at: str(o.at), finished: nullable(o.finished), summary: str(o.summary), session: session(o.transcript), draft: str(o.draft), posts, outputs: strings(o.outputs) };
118
128
  }
119
129
  /** Post a held post to the automation's accounts, or discard it. */
120
130
  export async function review(t, id, runId, post) {
121
131
  return run(obj(await call(t, 'POST', `/v1/auto/automations/${seg(id)}/runs/${seg(runId)}/review`, { post })));
122
132
  }
123
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
+ function once(at) {
136
+ const m = at.match(/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})$/);
137
+ if (!m)
138
+ return at;
139
+ 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]}`;
141
+ }
124
142
  /** A schedule the way a person says it. */
125
143
  export function words(s) {
126
144
  const zone = ` · ${s.tz || 'UTC'}`;
127
145
  switch (s.kind) {
128
146
  case 'manual':
129
147
  return 'When you run it';
148
+ case 'once':
149
+ return s.at ? `Once, ${once(s.at)}${zone}` : 'Once, now';
130
150
  case 'hourly':
131
151
  return `Every hour at :${(s.at ?? '00:00').slice(3) || '00'}`;
132
152
  case 'daily':
@@ -135,8 +155,16 @@ export function words(s) {
135
155
  return `Weekdays at ${s.at}${zone}`;
136
156
  case 'weekly':
137
157
  return `${DAYS[s.day ?? ''] ?? s.day} at ${s.at}${zone}`;
138
- case 'cron':
158
+ case 'cron': {
159
+ // The two a person says most, in their words; any other expression as written.
160
+ const minutes = (s.cron ?? '').match(/^\*\/(\d+) \* \* \* \*$/);
161
+ if (minutes)
162
+ return `Every ${minutes[1]} minutes${zone}`;
163
+ const hours = (s.cron ?? '').match(/^0 \*\/(\d+) \* \* \*$/);
164
+ if (hours)
165
+ return `Every ${hours[1]} hours${zone}`;
139
166
  return `Cron ${s.cron}${zone}`;
167
+ }
140
168
  }
141
169
  }
142
170
  /** The starters, from GET /v1/auto/templates. */
@@ -148,3 +176,108 @@ export async function starters(t) {
148
176
  })
149
177
  .filter((x) => x.key && x.name && x.instructions);
150
178
  }
179
+ /** A schedule from a person's own words ("every monday at 9am"), read in `tz`. */
180
+ export async function cron(t, text, tz, init) {
181
+ const o = obj(await call(t, 'POST', '/v1/auto/cron', { text, tz }, init));
182
+ return { schedule: schedule(o.schedule), says: str(o.says), next: nullable(o.next) };
183
+ }
184
+ export const STATES = ['scheduled', 'running', 'succeeded', 'failed', 'cancelled'];
185
+ /** A state the platform did not name is read from the product's own word. */
186
+ function stateOf(state, status) {
187
+ if (STATES.includes(state))
188
+ return state;
189
+ switch (status) {
190
+ case 'queued':
191
+ return 'scheduled';
192
+ case 'running':
193
+ case 'review':
194
+ return 'running';
195
+ case 'succeeded':
196
+ return 'succeeded';
197
+ case 'failed':
198
+ case 'refused':
199
+ return 'failed';
200
+ case 'skipped':
201
+ return 'cancelled';
202
+ }
203
+ return 'scheduled';
204
+ }
205
+ /** One job as the platform answers it, or null for a row with no id. */
206
+ export function job(raw) {
207
+ const o = obj(raw);
208
+ const id = str(o.id);
209
+ if (!id)
210
+ return null;
211
+ const status = str(o.status);
212
+ const progress = typeof o.progress === 'number' && Number.isFinite(o.progress) ? Math.min(1, Math.max(0, o.progress)) : null;
213
+ const cost = typeof o.cost === 'number' && Number.isFinite(o.cost) && o.cost >= 0 ? Math.round(o.cost) : null;
214
+ const sess = str(o.session);
215
+ return {
216
+ id,
217
+ product: sourceOf(o.product),
218
+ kind: str(o.kind),
219
+ title: str(o.title) || id,
220
+ state: stateOf(o.state, status),
221
+ status,
222
+ progress,
223
+ automation: nullable(o.automation),
224
+ session: /^sess_[0-9a-f]{32}$/.test(sess) ? sess : null,
225
+ ref: str(o.ref),
226
+ origin: inApp(o.origin),
227
+ summary: str(o.summary),
228
+ error: str(o.error),
229
+ outputs: strings(o.outputs),
230
+ cost,
231
+ started: str(o.started),
232
+ finished: nullable(o.finished),
233
+ updated: str(o.updated),
234
+ };
235
+ }
236
+ export async function jobs(t, f = {}) {
237
+ return list(await call(t, 'GET', `/v1/auto/jobs${query({ product: f.product, state: f.state, limit: f.limit })}`))
238
+ .map(job)
239
+ .filter((j) => j !== null);
240
+ }
241
+ export async function one(t, id) {
242
+ const j = job(await call(t, 'GET', `/v1/auto/jobs/${seg(id)}`));
243
+ if (!j)
244
+ throw new Error('The job came back without an id');
245
+ return j;
246
+ }
247
+ /** One frame of /v1/auto/events, or null for one this page does not read. */
248
+ export function heard(event, data) {
249
+ let raw;
250
+ try {
251
+ raw = JSON.parse(data);
252
+ }
253
+ catch {
254
+ return null;
255
+ }
256
+ if (event === 'job') {
257
+ const j = job(raw);
258
+ return j ? { kind: 'job', job: j } : null;
259
+ }
260
+ if (event === 'automation') {
261
+ const a = read(raw);
262
+ return a ? { kind: 'automation', automation: a } : null;
263
+ }
264
+ return null;
265
+ }
266
+ /**
267
+ * Read /v1/auto/events until `signal` ends it or the stream closes, calling `on`
268
+ * for each job and automation it names. A fetch, because EventSource cannot
269
+ * carry the bearer.
270
+ */
271
+ export async function events(t, on, signal, opened) {
272
+ const res = await fetch(`${t.api}/v1/auto/events`, { headers: headers(t, { Accept: 'text/event-stream' }), cache: 'no-store', signal });
273
+ if (!res.ok)
274
+ throw new Refusal(res.status, (await reason(res)) || `GET /v1/auto/events answered ${res.status}`);
275
+ if (!res.body)
276
+ return;
277
+ opened?.();
278
+ await stream(res.body, (f) => {
279
+ const h = heard(f.event, f.data);
280
+ if (h)
281
+ on(h);
282
+ }, signal);
283
+ }
@@ -0,0 +1,121 @@
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
+ import { type Target } from './call.ts';
16
+ export declare const KINDS: readonly ['image', 'video', 'audio', 'document', 'slides', 'type', 'model', 'code', 'file'];
17
+ export type Kind = (typeof KINDS)[number];
18
+ export declare const SOURCES: readonly ['chat', 'dev', 'studio', 'workflow'];
19
+ export type Source = (typeof SOURCES)[number];
20
+ export type Scope = 'all' | 'mine' | 'shared';
21
+ /** A product, as a person names it. */
22
+ export declare const SOURCE: Record<Source, string>;
23
+ export interface Item {
24
+ id: string;
25
+ kind: Kind;
26
+ source: Source;
27
+ name: string;
28
+ mime: string;
29
+ bytes: number;
30
+ width: number;
31
+ height: number;
32
+ seconds: number;
33
+ pages: number;
34
+ /** The subject that made it. */
35
+ owner: string;
36
+ /** The reader made it. */
37
+ mine: boolean;
38
+ /** Its owner shared it with the org. */
39
+ shared: boolean;
40
+ project: string;
41
+ /** What made it: a conversation, a Dev run, a Studio run, an automation's run. */
42
+ ref: string;
43
+ /** The app path back to what made it, or '' for none. */
44
+ origin: string;
45
+ /** The original, to open in place. */
46
+ url: string;
47
+ /** The original, as a download. */
48
+ download: string;
49
+ /** A still that shows it, or ''. An image is its own. */
50
+ preview: string;
51
+ expires: string;
52
+ created: string;
53
+ updated: string;
54
+ }
55
+ /** What a list is narrowed to. */
56
+ export interface Filter {
57
+ kind?: Kind | '';
58
+ source?: Source | '';
59
+ ref?: string;
60
+ project?: string;
61
+ q?: string;
62
+ /** RFC 3339 bounds on when an item was made. */
63
+ since?: string;
64
+ until?: string;
65
+ scope?: Scope;
66
+ limit?: number;
67
+ cursor?: string;
68
+ }
69
+ export interface Page {
70
+ data: Item[];
71
+ /** The cursor of the next page, or '' at the end. */
72
+ next: string;
73
+ }
74
+ /** An app path back to what made an item: under this origin, never another's. */
75
+ export declare const inApp: (v: unknown) => string;
76
+ /** One item as the platform answers it, or null for a row with no id. */
77
+ export declare function item(raw: unknown): Item | null;
78
+ export declare function items(t: Target, f?: Filter, init?: {
79
+ signal?: AbortSignal;
80
+ }): Promise<Page>;
81
+ export declare function get(t: Target, id: string): Promise<Item>;
82
+ /** What an upload says about where a thing came from. */
83
+ export interface Made {
84
+ source: Source;
85
+ kind?: Kind;
86
+ ref?: string;
87
+ origin?: string;
88
+ project?: string;
89
+ /** The same owner, source and key answer the item already made. */
90
+ key?: string;
91
+ width?: number;
92
+ height?: number;
93
+ seconds?: number;
94
+ pages?: number;
95
+ }
96
+ /** The largest thing one upload carries. */
97
+ export declare const INLINE: number;
98
+ /** Bytes as base64, in chunks a call stack holds. */
99
+ export declare function base64(bytes: Uint8Array): string;
100
+ /** Keep a file in the Library. */
101
+ export declare function upload(t: Target, file: {
102
+ name: string;
103
+ type: string;
104
+ bytes: Uint8Array;
105
+ }, made: Made): Promise<Item>;
106
+ export interface Change {
107
+ name?: string;
108
+ shared?: boolean;
109
+ project?: string;
110
+ }
111
+ export declare function change(t: Target, id: string, c: Change): Promise<Item>;
112
+ export declare function remove(t: Target, id: string): Promise<void>;
113
+ /** A size the way a person reads it. */
114
+ export declare function size(bytes: number): string;
115
+ /** The day an instant falls on, as a section of the Library: Today, Yesterday, then the month. */
116
+ export declare function day(iso: string, now?: Date): string;
117
+ /** Items under the day each was made, in the order given (newest first). */
118
+ export declare function days(list: Item[], now?: Date): {
119
+ label: string;
120
+ rows: Item[];
121
+ }[];