@repogo/plugins 0.1.43 → 0.1.44

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
@@ -3,11 +3,12 @@
3
3
  Plugins are small apps that run on your environment, the machine the RepoGo
4
4
  host runs on. A plugin keeps its own data, does work with your tools and APIs,
5
5
  and is reached two ways: agents call its methods as tools, and cards on your
6
- phone draw its data live and run its methods when you tap.
6
+ phone draw its data live and run its methods when you tap. A method may also
7
+ opt into plain HTTP, for this machine's containers or the public.
7
8
 
8
9
  - [Quick start](#quick-start)
9
10
  - [Where plugins live](#where-plugins-live)
10
- - [Methods](#methods): reads, writes, destructive methods, streams, cached reads
11
+ - [Methods](#methods): reads, writes, destructive methods, streams, cached reads, [serving over HTTP](#serving-over-http)
11
12
  - [Data](#data): namespaces, collections, kv, streams, buckets, uploads
12
13
  - [Workflows](#workflows)
13
14
  - [Crons](#crons)
@@ -15,7 +16,7 @@ phone draw its data live and run its methods when you tap.
15
16
  - [The context](#the-context)
16
17
  - [Events](#events)
17
18
  - [How cards and agents reach a plugin](#how-cards-and-agents-reach-a-plugin)
18
- - [Errors and confirmations](#errors-and-confirmations)
19
+ - [Errors and approvals](#errors-and-approvals)
19
20
  - [Testing](#testing)
20
21
 
21
22
  ## Quick start
@@ -36,7 +37,7 @@ export default definePlugin({
36
37
  clear: method(z.object({}), async (_, ctx) => {
37
38
  for (const note of await ctx.collections.notes.list({ limit: 500 })) await ctx.collections.notes.delete(note.id);
38
39
  return null;
39
- }, { effect: "destructive", confirm: "Delete every note?" }),
40
+ }, { effect: "destructive", approve: "Delete every note?" }),
40
41
  },
41
42
  });
42
43
  ```
@@ -73,7 +74,10 @@ lives beside its plugin and takes the project as state (`?project=…`).
73
74
 
74
75
  A plugin's name is its folder's: lowercase letters, digits and hyphens. The
75
76
  environment's plugins run together in one Worker, so a plugin's blocking code
76
- stalls the others: use async APIs.
77
+ stalls the others: use async APIs. They load together too: one that fails to
78
+ load (a throw at import, a destructive method without an `approve`, data without
79
+ a `namespace`, a method named like a generated one) stops every plugin in the
80
+ folder, and each call answers its error, until it's fixed.
77
81
 
78
82
  Pages beside an environment plugin (`pages/*.ui`, or `pages/*.md`) open from
79
83
  any project as `~/.repogo/plugins/<name>/pages/<page>.ui`. A
@@ -89,7 +93,8 @@ A user-facing plugin needs both its saved page and a link from `~/.repogo/plugin
89
93
  ## Methods
90
94
 
91
95
  ```ts
92
- method(input, run, { effect?, description?, output?, confirm? })
96
+ method(input, run, { effect?, description?, output?, approve?, approveUpTo?, http? })
97
+ cached(input, run, { fresh, shared?, timeout?, description?, output?, http? })
93
98
  stream(input, async function* run, { description? })
94
99
  ```
95
100
 
@@ -101,14 +106,15 @@ JSON can carry.
101
106
  | --- | --- | --- |
102
107
  | `read` | Only reads; writing throws | Cards on their own, and again whenever data it read changes |
103
108
  | `write` (the default) | Reads and writes | Only when someone acts: a tap, an agent, a cron, other code |
104
- | `destructive` | Writes what can't be undone; needs a `confirm` | Only after the user answers its question, on a tap |
109
+ | `destructive` | Writes what can't be undone; needs an `approve` | Only after the user answers its question |
105
110
  | `stream()` | A read that yields items as they happen | A card while it shows it |
106
111
 
107
112
  Mark every method that only reads `effect: "read"`: that's what lets a card
108
113
  show it and keep it current. Mark what can't be undone (a delete, a send, a
109
- deploy) `effect: "destructive"` with a `confirm` question; a destructive method
110
- without one doesn't load. A method with a `confirm` never runs from an agent's
111
- `plugin_call` or a cron: the agent draws a button, and the user's tap asks. Give each a `description`; agents read it to choose
114
+ deploy) `effect: "destructive"` with an `approve` question; a destructive method
115
+ without one doesn't load. A method with an `approve` asks before it runs, from
116
+ a tap or an agent ([Errors and approvals](#errors-and-approvals)), and never
117
+ runs from a cron. Give each a `description`; agents read it to choose
112
118
  a method. `output`, a zod schema of the result, tells agents and cards its
113
119
  field names.
114
120
 
@@ -126,8 +132,9 @@ open: method(z.object({ limit: z.number().int().min(1).max(100).default(20) }),
126
132
 
127
133
  A **stream** sends each `yield` to the card as it happens, for as long as the
128
134
  card shows it. `ctx.signal` aborts when the card goes, so hand it to what you
129
- start. Give items an `id`. An agent calling a stream gets what arrives within
130
- 5 seconds, at most 200 items.
135
+ start. Give items an `id`. An agent calling a stream, or `ctx.call` on one,
136
+ gets `{ items, done }`: what arrives within 5 seconds, at most 200 items, with
137
+ `done` false when it was cut off.
131
138
 
132
139
  ```ts
133
140
  tail: stream(z.object({ file: z.string() }), async function* ({ file }, ctx) {
@@ -162,6 +169,86 @@ projects: cached(z.object({ limit: z.number().int().min(1).max(200).default(100)
162
169
  needs a `namespace`. An answer over 1 MB is returned but not saved, and one
163
170
  nobody has asked for in 30 days is deleted.
164
171
 
172
+ ### Serving over HTTP
173
+
174
+ Cards, agents and crons reach every method. A method answers plain HTTP too
175
+ when its `http` option names an address, with who may call it there. `method`
176
+ and `cached` take it; a stream or a method with an `approve` is never served,
177
+ since no one is there to watch or answer.
178
+
179
+ ```ts
180
+ today: cached(z.object({}), async () => sales(), { fresh: "5m", http: { device: "none" } }),
181
+ ```
182
+
183
+ | Address | Where | Reached at |
184
+ | --- | --- | --- |
185
+ | `device` | This machine's loopback and its Apple containers' gateways, port 47444; Docker Desktop's containers reach it through `host.docker.internal` | `http://127.0.0.1:47444/<plugin>/<method>` |
186
+ | `public` | The environment's own name, through the tunnel | `https://h-<host id>.repogo.dev/<plugin>/<method>` |
187
+
188
+ | Access | Who may call |
189
+ | --- | --- |
190
+ | `key` | Only a caller sending the plugin's key as `Authorization: Bearer <key>`, which the host checks before the method runs |
191
+ | `none` | Anyone who reaches the address. On `device` the network is the boundary; on `public` it's anyone with the URL, since a method doesn't see the request's headers to check a signature |
192
+
193
+ `http: true` is `{ public: "key" }`. An address a method doesn't name answers
194
+ 404, as do a method that doesn't exist and a missing or wrong key, so callers
195
+ can't learn which plugins are installed.
196
+
197
+ `repogo urls shop` lists each of a plugin's methods served over HTTP with its
198
+ URLs, filled in with this host's id.
199
+
200
+ Each plugin has one key, for all its methods that take one, at both
201
+ addresses. `repogo keys shop` prints it, making it the first time; running it
202
+ again shows the same key. `repogo keys shop --new` replaces it, and the old
203
+ one stops working at once; `repogo keys shop --delete` leaves the methods
204
+ answering no one. RepoGo's tools for agents and a plugin's `ctx.host` can't
205
+ reach keys, though anything that runs commands as you can run `repogo keys`,
206
+ as it can read your plugins' own secrets. The method itself never sees the key.
207
+
208
+ A POST's JSON body is the input as it is. A GET's query is the input, as
209
+ strings, and only a `read` may be called that way:
210
+ link previews and crawlers fetch any URL they're shown, so a write answers
211
+ 405 to a GET. The answer is the result as JSON, or `{ error, fields }` with
212
+ 400, 403 or 404 for a `MethodError` with those codes, in its own words. Any
213
+ other error, such as a failed `fetch`, answers 500 `Internal server error`,
214
+ since its text may hold a token or a path; the host logs the real one. The
215
+ device address takes calls only from this machine and its containers'
216
+ networks, and refuses a browser's, since any web page can reach loopback.
217
+
218
+ A body that isn't JSON answers 400, as does one over 1 MB at the device
219
+ address (413 at the public one), and the public address answers 429 to a
220
+ caller sending too many. A plugin folder that fails to load answers 502. HEAD is a
221
+ GET, and a query key given twice keeps its first value. HTTP calls have no
222
+ project: `ctx.root` is the home folder. `ctx.caller` is `http` for a public call and `device`
223
+ for one at the device address.
224
+
225
+ When an HTTP caller hangs up, or the public address stops waiting after 60
226
+ seconds, `ctx.signal` aborts: hand it to `fetch` and `Bun.spawn` so the work
227
+ stops. The public address runs at most 32 calls at once and answers 503 to
228
+ another; a call counts until its method returns, so one that ignores the
229
+ signal keeps its place until it's done.
230
+
231
+ A method served over HTTP runs with all of `ctx`: one that starts an agent,
232
+ runs an action or calls another method lets its caller do the same. Every
233
+ container on this machine reaches every method open to `device`, and so does
234
+ every account and app on it. Open only what you'd show anyone who can reach
235
+ the address: reads, never a send or a delete.
236
+
237
+ **A plugin elsewhere that stands in for one here.** `GET /<plugin>` at the
238
+ device address lists the methods it serves there with no key, with their
239
+ schemas, and `remotePlugin` builds a plugin from it. In a container, a plugin
240
+ of the same name whose `app.ts` is only this serves the Mac's plugin, and its
241
+ pages, copied or mounted, work unchanged; the code, keys and data stay on the Mac:
242
+
243
+ ```ts
244
+ import { remotePlugin } from "@repogo/plugins";
245
+ export default await remotePlugin("http://192.168.64.1:47444", "shop");
246
+ ```
247
+
248
+ It reads the list when the plugin loads, so it fails to load while the Mac
249
+ can't be reached. A page's button for a method the Mac doesn't serve there
250
+ fails, as it would for any missing method.
251
+
165
252
  ## Data
166
253
 
167
254
  A plugin declares its data in four kinds, each with a schema, and gets typed
@@ -206,7 +293,9 @@ await ctx.collections.tasks.delete(task.id);
206
293
  await ctx.collections.tasks.deleteMany({ where: { status: "done" }, offset: 100, limit: 500 }); // done ones past the newest 100, 500 at a time
207
294
  ```
208
295
 
209
- `list` is newest first by default, at most 100 unless `limit` says (the generated `list` takes up to 500).
296
+ `list` is newest first by default, at most 100 unless `limit` says. The
297
+ generated `list` cards and agents call takes `limit` (up to 500), `offset`,
298
+ `orderBy` and `order` but no `where`: write a `read` method to filter.
210
299
  `where` matches top-level fields exactly; `null` matches a missing one.
211
300
  `orderBy` is `id`, `createdAt`, `updatedAt` or one of the record's own fields, and
212
301
  `offset` passes over that many first. `deleteMany` takes the same options and
@@ -390,7 +479,7 @@ export default definePlugin({
390
479
  await ctx.kv.reports.set("latest", report);
391
480
  return report;
392
481
  });
393
- }, { timeout: "30d", maxOffline: "14d", retention: "30d" }),
482
+ }, { timeout: "30d", maxOffline: "14d", retention: "30d" }), // the defaults
394
483
  },
395
484
  kv: { reports: z.object({ title: z.string() }) },
396
485
  });
@@ -398,7 +487,7 @@ export default definePlugin({
398
487
 
399
488
  For a plugin folder named `reports`, the host generates:
400
489
 
401
- - `reports.report.list({ limit?, status?, cursor? })`: bounded summaries and a next cursor.
490
+ - `reports.report.list({ limit?, status?, cursor? })`: bounded summaries (20 unless `limit` says, at most 100) and a next cursor. A status is `pending`, `running`, `waiting`, `attention`, `completed`, `failed`, `cancelled`, `expired`, `unknown` or `blocked`.
402
491
  - `reports.report.get({ id })`: one summary, input, result, error and the latest 20 operations (steps and sleeps).
403
492
  - `reports.report.start({ input, key? })`: persist and queue a run; returns its summary immediately.
404
493
  - `reports.report.cancel({ id })`: persist cancellation intent and abort active work.
@@ -407,9 +496,9 @@ For a plugin folder named `reports`, the host generates:
407
496
  - `reports.report.restart({ id })`: start a run that needs attention over from the beginning, on the current code.
408
497
 
409
498
  These use the existing plugin methods, `ctx.call`, cards and subscriptions.
410
- The namespace is required. `confirm` on the workflow confirms its start;
499
+ The namespace is required. `approve` on the workflow asks before its start;
411
500
  it does not preapprove later host actions. An unattended workflow refuses
412
- confirmation-gated plugin methods. The calling project and caller
501
+ plugin methods that ask. The calling project and caller
413
502
  are preserved. A start `key` deduplicates within that project, plugin and
414
503
  workflow while its run is retained; reusing it with different input fails.
415
504
 
@@ -441,8 +530,10 @@ with `kind: "sleep"`. Terminal runs have no pending wake time.
441
530
  `attempts` counts the first invocation and defaults to 1. Only steps declared
442
531
  `kind: "query"` may automatically retry; they cannot write SDK data or call
443
532
  mutations. Within query steps, use `ctx.mcp` for read-aware host access;
444
- generic `ctx.host`, notifications and agent starts are refused. Query backoff defaults to full jitter with initial `2s`, factor 2
445
- and maximum `1m`. Import `NonRetryable` and throw it for a permanent failure.
533
+ generic `ctx.host`, notifications and agent starts are refused. A step's `kind`
534
+ defaults to `"mutation"`, and `attempts` is at most 100. Query backoff,
535
+ `backoff: { initial, factor, max }`, defaults to full jitter with initial `2s`,
536
+ factor 2 and maximum `1m`. Import `NonRetryable` and throw it for a permanent failure.
446
537
  Step `timeout` defaults to `5m`. The callback receives `signal`, `attempt`,
447
538
  `attempts`, `runId`, `stepId` and an `idempotencyKey` stable across its attempts.
448
539
  Pass its signal to fetch or subprocess APIs. A timeout aborts cooperatively;
@@ -513,7 +604,7 @@ itself. Each line names a plugin, one of its methods and a schedule:
513
604
 
514
605
  | Field | What |
515
606
  | --- | --- |
516
- | `plugin`, `method` | The plugin's folder name and one of its methods, called with `{}` as a tap would |
607
+ | `plugin`, `method` | The plugin's folder name and one of the methods it wrote (not a generated or workflow one), called with `{}` as a tap would |
517
608
  | `project` | The project the run gets as `ctx.root`, an absolute path or one starting `~/`; left out, the home folder. The plugin is always the environment's |
518
609
  | `schedule` | A cron: five fields (minute hour day-of-month month day-of-week), or six with seconds first (`*/5 * * * * *`); each `*`, a number, a range, a list or a step |
519
610
  | `timezone` | Defaults to the environment's |
@@ -541,7 +632,7 @@ that were running when it happened.
541
632
 
542
633
  Write `console.log` (or `warn`, `error`, `debug`); nothing else is needed. The
543
634
  host keeps every line with the plugin, the method and the call that wrote it,
544
- who called (`card`, `agent`, `cron`), and the chat for an agent's call, plus
635
+ who called (`card`, `agent`, `cron`, `code`, or `http` and `device` for HTTP), and the chat for an agent's call, plus
545
636
  the arguments as JSON. An error a method throws, or one nothing caught, is
546
637
  kept too, with its stack. Read them with the agent's `plugins_logs` tool, or on
547
638
  a card with `<Data method="repogo:plugins.logs">`, which reloads as lines land:
@@ -558,18 +649,18 @@ Every method, handler and cron gets `ctx`:
558
649
  | Field | What |
559
650
  | --- | --- |
560
651
  | `plugin` | The plugin's name |
561
- | `caller` | Who started the call: `card`, `agent`, `cron`, or `code` |
652
+ | `caller` | Who started the call: `card`, `agent`, `cron`, `code`, or a request at the public (`http`) or device (`device`) address |
562
653
  | `root` | The calling project's checkout |
563
654
  | `collections`, `kv`, `streams`, `buckets` | The plugin's data |
564
655
  | `call(method, input)` | Runs a method of a plugin in the same folder; `"open"` is this plugin's own |
565
656
  | `plugins()` | The plugins in the same folder, with their methods |
566
- | `host(method, params)` | Runs one of the host's methods that agents and cards may call (`git.status`, `chats.list`, `actions.run`, …); others fail with `FORBIDDEN` |
657
+ | `host(method, params)` | Runs one of the host's methods that agents and cards may call (`git.status`, `chats.list`, `actions.run`, …); others fail with `FORBIDDEN`, and one that doesn't exist with `INTERNAL` |
567
658
  | `mcp(server, tool, input)` | Runs a tool of an MCP server the user connected in RepoGo and switched on for the calling project; see below |
568
659
  | `notify({ title, body, page?, url?, collapse? })` | Alerts every paired phone; a tap opens `url` (an `https` or `itms-services` link) if set, else `page`, and alerts sharing `collapse` replace each other |
569
660
  | `activity({ id, title, body?, page?, symbol?, tint?, label?, compactLabel?, progress?, timer?, status? })` | Shows or updates a Live Activity on every paired phone; see below |
570
661
  | `agent(prompt, { agent, project?, title?, timeout? })` | Starts a chat with `prompt` and waits for the agent's whole answer: `{ chatId, text }`; see below |
571
662
  | `emit(event, payload)` | Sends `<plugin>.<event>` to handlers |
572
- | `signal` | Aborts when a stream's card goes |
663
+ | `signal` | Aborts when a stream's card goes, or when an HTTP caller leaves before a read or write answers |
573
664
  | `log(...)` | The same as `console.log` |
574
665
 
575
666
  A query doesn't re-run when what a `ctx.host` method read changes.
@@ -650,7 +741,7 @@ customers: method(z.object({}), async (_, ctx) => {
650
741
  ```
651
742
 
652
743
  A read may run only the server's read-only tools, as a card's `Data` may. A
653
- tool marked destructive runs only from a method with a `confirm`, or one
744
+ tool marked destructive runs only from a method with an `approve`, or one
654
745
  called from it with `ctx.call`: the user's answer to that question is its
655
746
  yes. A refusal throws `FORBIDDEN`; a tool's own error throws `INTERNAL` with
656
747
  its message. Only servers switched on for `ctx.root` answer, so a cron of an
@@ -674,7 +765,9 @@ on: {
674
765
  | Bucket | `<name>.created`, `<name>.changed` (the file's bytes), `<name>.metaChanged`, `<name>.deleted` (the file) |
675
766
  | Any method | `<method>` (`{ input, result }`) |
676
767
 
677
- Changes to a bucket's folder made by hand emit too, once the host sees them.
768
+ Changes to a bucket's folder made by hand emit too, once the host sees them;
769
+ streams emit none. A plugin's `on` hears only its own events, and runs inside
770
+ the call that emitted them, so a slow handler delays that call's answer.
678
771
  Events run in memory: none is retried, and a handler that throws is logged.
679
772
 
680
773
  ## How cards and agents reach a plugin
@@ -683,7 +776,7 @@ Events run in memory: none is retried, and a handler that throws is logged.
683
776
  (its effect, whether it asks first, and its input's fields, optional ones ending
684
777
  in `?`), and every line of `~/.repogo/crons.json` with how it's going.
685
778
  `plugins` with `plugin` set answers that plugin in full: each method's input
686
- and output JSON Schema and confirm, its `storage` (each bucket's and stream's
779
+ and output JSON Schema, `approve` and `approveUpTo`, its `storage` (each bucket's and stream's
687
780
  folder), the pages that show its methods, and the cron lines naming it.
688
781
  `plugin_call` runs one. Inspect a method's schema before naming it in a card.
689
782
  Verify a read's fields with a read-only call. Do not execute writes just to
@@ -751,20 +844,47 @@ Inside plugin code, `ctx.kv.flags.set("alerts", true)` saves the same value.
751
844
  The generated `preferences.flags.get({ key: "alerts" })` returns an entry with
752
845
  `value` and timestamps, while `ctx.kv.flags.get("alerts")` returns the value alone.
753
846
 
754
- ## Errors and confirmations
847
+ ## Errors and approvals
848
+
849
+ Throw `new MethodError(code, message, fieldErrors?)` for a failure the caller
850
+ should read: `VALIDATION`, `NOT_FOUND`, `FORBIDDEN` or `INTERNAL`.
851
+ `fieldErrors` maps a JSON pointer (`"/minutes"`) to its message, and reaches an
852
+ HTTP caller as `fields`. Input that fails its schema answers `VALIDATION` with
853
+ a message per field. Anything else thrown is
854
+ `INTERNAL` with its message. An HTTP caller gets only the first three in their
855
+ words; `INTERNAL` reaches it as `Internal server error`
856
+ ([Serving over HTTP](#serving-over-http)).
857
+
858
+ A method's `approve` is the question asked before it runs. `{{field}}` in it
859
+ is filled from the call's input, so it can say what it acts on:
860
+ `approve: "Restart {{machine}}?"`. A `destructive` method must have one.
861
+
862
+ - **A card's tap** always asks it first.
863
+ - **An agent's `plugin_call`** asks it in the chat, as a tool approval, and runs
864
+ the method only on the user's yes. `approveUpTo` names the least strict chat
865
+ mode that still asks; the method asks in that mode and every stricter one,
866
+ and runs unasked in the rest. The modes, strictest first, are
867
+ `approval-required`, `auto-accept-edits` and `full-access`; unset is
868
+ `full-access`, so it asks in every mode. A chat whose mode the host can't
869
+ tell asks.
870
+ - **Plan mode:** an agent's call to any method that isn't a `read` is refused,
871
+ with or without an `approve`.
872
+ - **Code:** `ctx.call` from plugin code skips it, and an unattended workflow
873
+ or cron refuses methods that ask.
755
874
 
756
- Throw `new MethodError(code, message)` for a failure the caller should read:
757
- `VALIDATION`, `NOT_FOUND`, `FORBIDDEN` or `INTERNAL`. Input that fails its
758
- schema answers `VALIDATION` with a message per field. Anything else thrown is
759
- `INTERNAL` with its message.
875
+ ```ts
876
+ upload: method(Build, run, {
877
+ effect: "destructive",
878
+ approve: "Upload build {{buildNumber}}?", // asks in every mode
879
+ }),
880
+ rename: method(Rename, run, {
881
+ approve: "Rename {{name}}?",
882
+ approveUpTo: "auto-accept-edits", // full access runs it unasked
883
+ }),
884
+ ```
760
885
 
761
- A method's `confirm` is asked before a card's tap runs it. An agent's
762
- `plugin_call` never runs it: it gets the question back as a `CONFIRM` error and
763
- draws a button, and the user's tap asks. Ordinary `ctx.call` from plugin code skips
764
- it; an unattended workflow refuses confirmation-gated methods. A `destructive`
765
- method must have one. `{{field}}` in the question is
766
- filled from the call's input, so it can say what it acts on:
767
- `confirm: "Restart {{machine}}?"`.
886
+ In full access an agent can run commands without asking, so there the
887
+ question catches a careless call rather than stopping a determined one.
768
888
 
769
889
  ## Testing
770
890
 
@@ -776,12 +896,16 @@ user's MCP servers:
776
896
  import { testRuntime } from "@repogo/plugins/testing";
777
897
  import notes from "./app";
778
898
 
779
- const app = await testRuntime({ notes });
780
- await app.call("notes.add", { text: "hi" });
899
+ const app = await testRuntime({ notes }); // { root?, mcp?, host? } as a second argument
900
+ await app.call("notes.notes.create", { text: "hi" });
781
901
  await app.call("notes.clear", {}, { confirmed: false }); // throws CONFIRM, as an unanswered tap
782
- app.setCrons([{ plugin: "notes", method: "tidy", schedule: "* * * * *" }]); // as a crons.json line would
902
+ app.setCrons([{ plugin: "notes", method: "open", schedule: "* * * * *" }]); // a crons.json line, with root for project
783
903
  await app.tickCrons(Date.now() + 60_000); // runs whatever is due by then
784
904
  app.notices; // every ctx.notify so far
785
905
  app.logs; // every console line so far, with its method and caller
906
+ app.manifest(); // the plugins and methods, as the host lists them
786
907
  await app.close();
787
908
  ```
909
+
910
+ `call` takes `{ confirmed?, caller?, root?, chat?, files? }`; leaving
911
+ `confirmed` out runs a `confirm` method without asking, as `ctx.call` does.
package/dist/context.d.ts CHANGED
@@ -2,13 +2,13 @@ import type { Bucket } from "./buckets";
2
2
  import type { Collection } from "./collections";
3
3
  import type { Kv } from "./kv";
4
4
  import type { Stream } from "./streams";
5
- import type { Effect, HTTPAccess } from "./define";
5
+ import type { Effect, HTTPAddresses, PermissionMode } from "./define";
6
6
  import type { Duration } from "./duration";
7
7
  /** An alert for the user's phones: what it says, and the page a tap opens. */
8
8
  export type Notice = {
9
9
  title: string;
10
10
  body: string;
11
- /** A page target, `.repogo/tabs/vercel.md?project=a`; a tap opens it. */
11
+ /** A page target, `~/.repogo/plugins/vercel/pages/index.ui?project=a`; a tap opens it. */
12
12
  page?: string;
13
13
  /** An `https` or `itms-services` link a tap opens instead of the page, such as a build's install. */
14
14
  url?: string;
@@ -53,7 +53,7 @@ export type Activity = {
53
53
  title: string;
54
54
  /** At most 120 characters, as a notice's body. */
55
55
  body?: string;
56
- /** A page target of the calling project, `.repogo/tabs/deploys.ui?project=web`; a tap opens it. */
56
+ /** A page target, `~/.repogo/plugins/deploys/pages/index.ui?project=web`; a tap opens it. */
57
57
  page?: string;
58
58
  /** An SF Symbol name, `shippingbox`; `circle.dotted`, or a check or a cross once ended, unless said. */
59
59
  symbol?: string;
@@ -79,9 +79,11 @@ export type ManifestMethod = {
79
79
  stream: boolean;
80
80
  description: string | null;
81
81
  /** What a card asks before running it; null runs on tap. */
82
- confirm: string | null;
83
- /** Who may call it at the environment's own address; null, no one. */
84
- http: HTTPAccess | null;
82
+ approve: string | null;
83
+ /** The least strict chat mode an agent's call still asks in; null with no approve. */
84
+ approveUpTo: PermissionMode | null;
85
+ /** Who may call it over HTTP, at each address; null, no one anywhere. */
86
+ http: HTTPAddresses | null;
85
87
  /** JSON Schema of the input, and of the output when the method declares one. */
86
88
  input: unknown;
87
89
  output: unknown;
@@ -139,8 +141,8 @@ export type Manifest = {
139
141
  storage: ManifestStorage;
140
142
  }[];
141
143
  };
142
- /** Who started a call: a tap or a card's query, an agent's `plugin_call`, a cron, or plugin code with none of those behind it. */
143
- export type Caller = "card" | "agent" | "cron" | "code" | "http";
144
+ /** Who started a call: a tap or a card's query, an agent's `plugin_call`, a cron, plugin code with none of those behind it, or a request at the public (`http`) or device address. */
145
+ export type Caller = "card" | "agent" | "cron" | "code" | "http" | "device";
144
146
  /** How `ctx.agent` runs a chat: which agent, in which project, and how long to wait for its answer. */
145
147
  export type AgentOptions = {
146
148
  /** `claude` or `codex`, as `agents.list` names them. */
@@ -177,15 +179,15 @@ export type Context = {
177
179
  * Runs a tool of an MCP server the user switched on for the calling project:
178
180
  * `ctx.mcp("stripe", "stripe_api_read", { … })`, answering the tool's result.
179
181
  * A query may run only read-only tools; a destructive tool runs only from a
180
- * method with a `confirm`. Its answer doesn't re-run a query.
182
+ * method with an `approve`. Its answer doesn't re-run a query.
181
183
  */
182
184
  mcp: (server: string, tool: string, input?: unknown) => Promise<unknown>;
183
185
  /**
184
- * The plugins beside this one, in the same folder (the project's, or the
185
- * environment's), with their methods: what `call` can reach.
186
+ * The plugins beside this one in the environment's plugins folder, with
187
+ * their methods: what `call` can reach.
186
188
  */
187
189
  plugins: () => Manifest["plugins"];
188
- /** Aborts when a stream's last watcher leaves; a read or write's never does. */
190
+ /** Aborts when a stream's last watcher leaves, or when an HTTP caller of a read or write goes away first. */
189
191
  signal: AbortSignal;
190
192
  /** Sends an alert to every phone paired with this environment. */
191
193
  notify: (notice: Notice) => void;
package/dist/define.d.ts CHANGED
@@ -13,10 +13,16 @@ export type StreamDefinition = z.ZodType | {
13
13
  /**
14
14
  * What a method does. `read` only reads, so cards may run it on their own and
15
15
  * again when its data changes. `write` changes data, and runs only when the
16
- * user acts. `destructive` can't be undone, and must ask first (`confirm`).
16
+ * user acts. `destructive` can't be undone, and must ask first (`approve`).
17
17
  * Unmarked methods write: repeating one is never assumed safe.
18
18
  */
19
19
  export type Effect = "read" | "write" | "destructive";
20
+ /**
21
+ * The chat's permission modes, strictest first, as RepoGo names them for every
22
+ * agent. `approveUpTo` names the least strict one a method still asks in.
23
+ */
24
+ export declare const permissionModes: readonly ["approval-required", "auto-accept-edits", "full-access"];
25
+ export type PermissionMode = (typeof permissionModes)[number];
20
26
  export type Method<I extends z.ZodType = z.ZodType, O extends z.ZodType = z.ZodType> = {
21
27
  input: I;
22
28
  /** What it returns; the agent and cards read field names from it. */
@@ -29,18 +35,34 @@ export type Method<I extends z.ZodType = z.ZodType, O extends z.ZodType = z.ZodT
29
35
  * The question asked before it runs, such as "Delete {{name}}?", filled from
30
36
  * its input; required for a destructive method. Methods without one run on tap.
31
37
  */
32
- confirm?: string;
33
- /** Who may call it at the environment's own address; unset, no one. */
34
- http?: HTTPAccess;
38
+ approve?: string;
39
+ /**
40
+ * The least strict chat mode in which an agent's call still asks `approve`:
41
+ * it asks in that mode and every stricter one, and runs unasked in the rest.
42
+ * Unset is "full-access", every mode. A card's tap always asks.
43
+ */
44
+ approveUpTo?: PermissionMode;
45
+ /** Who may call it over HTTP, at each address; an address it doesn't name serves it to no one. */
46
+ http?: HTTPAddresses;
35
47
  /** Set by `cached`: the read answers from what it saved, and checks again when that's old. */
36
48
  cache?: CacheOptions;
37
49
  run: (input: z.infer<I>, ctx: Context) => unknown | Promise<unknown>;
38
50
  };
39
51
  /**
40
- * `key`: the host checks a key the caller sends. `none`: anyone reaches the
41
- * method, which checks the caller itself, such as a provider's signature.
52
+ * `key`: the caller sends the plugin's key as a bearer token, which the host
53
+ * checks before the method runs. `none`: anyone who reaches the address may call.
42
54
  */
43
55
  export type HTTPAccess = "key" | "none";
56
+ /**
57
+ * `public`: the environment's own address, through the tunnel. `device`: this
58
+ * machine's loopback and its containers' gateway, where the network is the boundary.
59
+ */
60
+ export type HTTPAddresses = {
61
+ device?: HTTPAccess;
62
+ public?: HTTPAccess;
63
+ };
64
+ /** `true` is `{ public: "key" }`. */
65
+ export type HTTPOption = true | HTTPAddresses;
44
66
  export type Handler = (payload: any, ctx: Context) => unknown | Promise<unknown>;
45
67
  export type PluginDefinition = {
46
68
  /** One line, shown to the agent in the index of plugins. */
@@ -72,15 +94,14 @@ export declare function definePlugin<T extends PluginDefinition>(plugin: T): T;
72
94
  type MethodOptions<O extends z.ZodType> = {
73
95
  output?: O;
74
96
  description?: string;
75
- http?: true | {
76
- auth: HTTPAccess;
77
- };
97
+ http?: HTTPOption;
98
+ approveUpTo?: PermissionMode;
78
99
  } & ({
79
100
  effect: "destructive";
80
- confirm: string;
101
+ approve: string;
81
102
  } | {
82
103
  effect?: "read" | "write";
83
- confirm?: string;
104
+ approve?: string;
84
105
  });
85
106
  export declare function method<I extends z.ZodType, O extends z.ZodType = z.ZodType>(input: I, run: (input: z.infer<I>, ctx: Context) => unknown | Promise<unknown>, options?: MethodOptions<O>): Method<I, O>;
86
107
  /**
@@ -97,6 +118,7 @@ export declare function cached<I extends z.ZodType, O extends z.ZodType = z.ZodT
97
118
  timeout?: Duration;
98
119
  output?: O;
99
120
  description?: string;
121
+ http?: HTTPOption;
100
122
  }): Method<I, O>;
101
123
  /**
102
124
  * A method that sends items as they happen: a log tail, a build's output. Each
@@ -9,7 +9,7 @@ export type Generated = {
9
9
  description: string;
10
10
  input: z.ZodType;
11
11
  output?: z.ZodType;
12
- confirm?: string;
12
+ approve?: string;
13
13
  run: (input: any, signal: AbortSignal) => unknown;
14
14
  };
15
15
  export declare function kvMethods(name: string, kv: Kv<any>, schema: z.ZodType): Record<string, Generated>;
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export { cached, definePlugin, method, stream } from "./define";
2
+ export { remotePlugin } from "./remote";
2
3
  export { upload, type UploadedFile } from "./uploads";
3
- export type { PluginDefinition, Method, Effect, HTTPAccess, StreamDefinition } from "./define";
4
+ export type { PluginDefinition, Method, Effect, PermissionMode, HTTPAccess, HTTPAddresses, HTTPOption, StreamDefinition } from "./define";
4
5
  export type { Activity, ActivityProgress, ActivityTimer, ActivityTint, AgentOptions, AgentReply, Caller, Context, CronEntry, CronStatus, Manifest, ManifestCron, ManifestMethod, ManifestStorage } from "./context";
5
6
  export type { LogLevel, LogRecord } from "./logs";
6
7
  export type { BucketFile, FileStatus, PutFile } from "./buckets";