@repogo/plugins 0.1.34 → 0.1.35
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 +139 -25
- package/dist/collections.d.ts +11 -1
- package/dist/context.d.ts +51 -0
- package/dist/cron.d.ts +21 -0
- package/dist/define.d.ts +6 -12
- package/dist/generated.d.ts +16 -0
- package/dist/index-83w1fk10.js +28 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +7 -28
- package/dist/logs.d.ts +55 -0
- package/dist/runtime.d.ts +158 -0
- package/dist/testing.d.ts +49 -0
- package/dist/testing.js +1979 -0
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -10,10 +10,12 @@ phone draw its data live and run its methods when you tap.
|
|
|
10
10
|
- [Methods](#methods): queries, mutations, streams
|
|
11
11
|
- [Data](#data): namespaces, collections, kv, streams, buckets
|
|
12
12
|
- [Crons](#crons)
|
|
13
|
+
- [Logging](#logging)
|
|
13
14
|
- [The context](#the-context)
|
|
14
15
|
- [Events](#events)
|
|
15
16
|
- [How cards and agents reach a plugin](#how-cards-and-agents-reach-a-plugin)
|
|
16
17
|
- [Errors and confirmations](#errors-and-confirmations)
|
|
18
|
+
- [Testing](#testing)
|
|
17
19
|
|
|
18
20
|
## Quick start
|
|
19
21
|
|
|
@@ -62,9 +64,9 @@ project plugin hides an environment one of the same name. Each folder's plugins
|
|
|
62
64
|
run together in one Worker, so a plugin's blocking code stalls the others in its
|
|
63
65
|
folder: use async APIs.
|
|
64
66
|
|
|
65
|
-
Pages beside an environment plugin (`pages/*.md`) open from
|
|
66
|
-
`~/.repogo/plugins/<name>/pages/<page>.
|
|
67
|
-
|
|
67
|
+
Pages beside an environment plugin (`pages/*.ui`, or `pages/*.md`) open from
|
|
68
|
+
any project as `~/.repogo/plugins/<name>/pages/<page>.ui`. A
|
|
69
|
+
`.ui` page is card markup with no fence; a `.md` page is writing with cards.
|
|
68
70
|
|
|
69
71
|
## Methods
|
|
70
72
|
|
|
@@ -125,8 +127,9 @@ plugin, another plugin, or another project.
|
|
|
125
127
|
|
|
126
128
|
`namespace` names where a plugin's data lives: the folder
|
|
127
129
|
`~/.repogo/plugin-data/<namespace>/`, holding `data.db` and its streams' and
|
|
128
|
-
buckets' files.
|
|
129
|
-
|
|
130
|
+
buckets' files. A plugin that keeps data (any collection, kv, stream or bucket)
|
|
131
|
+
must name one, or it fails to load. Plugins naming the same namespace share its
|
|
132
|
+
data, in any project, so they must declare the same schemas.
|
|
130
133
|
|
|
131
134
|
By convention a project's own data is `<project>.<name>` (`myproject.notes`),
|
|
132
135
|
and data meant for every project a bare name (`todos`). That's only a
|
|
@@ -145,10 +148,14 @@ await ctx.collections.tasks.get(task.id); // or n
|
|
|
145
148
|
await ctx.collections.tasks.update(task.id, { status: "done" }); // only the fields given change
|
|
146
149
|
await ctx.collections.tasks.list({ where: { status: "open" }, orderBy: "updatedAt", order: "desc", limit: 50 });
|
|
147
150
|
await ctx.collections.tasks.delete(task.id);
|
|
151
|
+
await ctx.collections.tasks.deleteMany({ where: { status: "done" }, offset: 100, limit: 500 }); // done ones past the newest 100, 500 at a time
|
|
148
152
|
```
|
|
149
153
|
|
|
150
154
|
`list` is newest first by default, at most 100 unless `limit` says (up to 500).
|
|
151
155
|
`where` matches top-level fields exactly; `null` matches a missing one.
|
|
156
|
+
`orderBy` is `id`, `createdAt`, `updatedAt` or one of the record's own fields, and
|
|
157
|
+
`offset` passes over that many first. `deleteMany` takes the same options and
|
|
158
|
+
deletes exactly what `list` would return, in one transaction, returning how many.
|
|
152
159
|
|
|
153
160
|
### Kv
|
|
154
161
|
|
|
@@ -265,28 +272,64 @@ A bucket's `put` takes `text`, or `from`, an absolute path on the environment to
|
|
|
265
272
|
copy: bytes never travel in a call. A method of your own with the same name as a
|
|
266
273
|
generated one fails the plugin's load.
|
|
267
274
|
|
|
275
|
+
Data named in `private` gets none of these, so cards and agents reach it only
|
|
276
|
+
through the plugin's own methods: `private: ["actions", "lines"]` for an audit
|
|
277
|
+
log no tap can edit. It hides methods; it isn't a lock. Another plugin naming
|
|
278
|
+
the same namespace and store can still read and write it.
|
|
279
|
+
|
|
268
280
|
## Crons
|
|
269
281
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
}
|
|
282
|
+
What runs with no one looking is listed in one file the user owns,
|
|
283
|
+
`~/.repogo/crons.json`, not in a plugin's code: a repo you clone can't schedule
|
|
284
|
+
itself. Each line names a plugin, one of its methods and a schedule:
|
|
285
|
+
|
|
286
|
+
```json
|
|
287
|
+
[
|
|
288
|
+
{ "plugin": "incidents", "method": "watch", "schedule": "*/5 * * * *" },
|
|
289
|
+
{ "project": "~/code/shop", "plugin": "backup", "method": "run", "schedule": "0 3 * * *", "timezone": "America/New_York" },
|
|
290
|
+
{ "plugin": "digest", "method": "send", "schedule": "0 9 * * 1-5", "enabled": false }
|
|
291
|
+
]
|
|
278
292
|
```
|
|
279
293
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
294
|
+
| Field | What |
|
|
295
|
+
| --- | --- |
|
|
296
|
+
| `plugin`, `method` | The plugin's folder name and one of its methods, called with `{}` as a tap would |
|
|
297
|
+
| `project` | The project whose `.repogo/plugins` holds it, an absolute path or one starting `~/`; left out, `~/.repogo/plugins` |
|
|
298
|
+
| `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 |
|
|
299
|
+
| `timezone` | Defaults to the environment's |
|
|
300
|
+
| `enabled` | `false` keeps the line without running it |
|
|
301
|
+
|
|
302
|
+
The host reads the file every minute, and again when the agent's `plugins` tool
|
|
303
|
+
lists what's there, so an edit shows at once. A folder opened only for its
|
|
304
|
+
schedules imports just the plugins they name; the others beside them don't run.
|
|
305
|
+
A line removed or disabled is never started again; a run already going finishes.
|
|
284
306
|
|
|
285
307
|
A job still running isn't started again, a time the environment slept through
|
|
286
|
-
is skipped, and nothing is retried. A
|
|
287
|
-
method that needs input or asks first,
|
|
288
|
-
|
|
289
|
-
|
|
308
|
+
is skipped, and nothing is retried. A line naming something missing, a stream, a
|
|
309
|
+
method that needs input or asks first, a bad schedule, or the same method twice
|
|
310
|
+
says why in `plugins` (each line's `error`), and the others run. A file that
|
|
311
|
+
isn't valid JSON pauses every line until it's fixed. Each line also shows
|
|
312
|
+
`nextRunAt`, `lastRunAt`, `lastError`, `lastDurationMs` and `runs`; `ctx.plugins()`
|
|
313
|
+
has the same for a plugin's own, and a query reading them re-runs around each run.
|
|
314
|
+
|
|
315
|
+
Schedule something only when the user asks for it. A cron's `ctx.root` is its
|
|
316
|
+
project, or the home folder for a plugin in `~/.repogo/plugins`. Plugins in one
|
|
317
|
+
folder share a thread: code that blocks it holds up every other cron there, and
|
|
318
|
+
the host's log names the crons that were running when it happened.
|
|
319
|
+
|
|
320
|
+
## Logging
|
|
321
|
+
|
|
322
|
+
Write `console.log` (or `warn`, `error`, `debug`); nothing else is needed. The
|
|
323
|
+
host keeps every line with the plugin, the method and the call that wrote it,
|
|
324
|
+
who called (`card`, `agent`, `cron`), and the chat for an agent's call, plus
|
|
325
|
+
the arguments as JSON. An error a method throws, or one nothing caught, is
|
|
326
|
+
kept too, with its stack. Read them with the agent's `plugin_logs` tool, or on
|
|
327
|
+
a card with `<Data method="repogo:plugins.logs">`, which reloads as lines land:
|
|
328
|
+
`plugin`, `level` (that level and worse), `method`, `since`, `until`, `query`
|
|
329
|
+
(a substring) and `limit` narrow it. The `plugins` tool shows each plugin's
|
|
330
|
+
`errors` in the last hour. Lines older than 7 days go, and the oldest hours of
|
|
331
|
+
the noisiest plugin once the log passes 2 GB. A line is cut at 8 KB: a payload
|
|
332
|
+
belongs in the plugin's own data, not its log.
|
|
290
333
|
|
|
291
334
|
## The context
|
|
292
335
|
|
|
@@ -295,18 +338,63 @@ Every method, handler and cron gets `ctx`:
|
|
|
295
338
|
| Field | What |
|
|
296
339
|
| --- | --- |
|
|
297
340
|
| `plugin` | The plugin's name |
|
|
341
|
+
| `caller` | Who started the call: `card`, `agent`, `cron`, or `code` |
|
|
298
342
|
| `root` | The calling project's checkout |
|
|
299
343
|
| `collections`, `kv`, `streams`, `buckets` | The plugin's data |
|
|
300
344
|
| `call(method, input)` | Runs a method of a plugin in the same folder; `"open"` is this plugin's own |
|
|
301
345
|
| `plugins()` | The plugins in the same folder, with their methods |
|
|
302
346
|
| `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` |
|
|
347
|
+
| `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 |
|
|
303
348
|
| `notify({ title, body, page?, collapse? })` | Alerts every paired phone; a tap opens `page`, and alerts sharing `collapse` replace each other |
|
|
349
|
+
| `agent(prompt, { agent, project?, title?, timeout? })` | Starts a chat with `prompt` and waits for the agent's whole answer: `{ chatId, text }`; see below |
|
|
304
350
|
| `emit(event, payload)` | Sends `<plugin>.<event>` to handlers |
|
|
305
351
|
| `signal` | Aborts when a stream's card goes |
|
|
306
|
-
| `log(...)` |
|
|
352
|
+
| `log(...)` | The same as `console.log` |
|
|
307
353
|
|
|
308
354
|
A query doesn't re-run when what a `ctx.host` method read changes.
|
|
309
355
|
|
|
356
|
+
### An agent's answer
|
|
357
|
+
|
|
358
|
+
`ctx.agent` hands a question to Claude or Codex and answers with what it said,
|
|
359
|
+
so a cron can watch, investigate and notify with the finding:
|
|
360
|
+
|
|
361
|
+
```ts
|
|
362
|
+
watch: method(z.object({}), async (_, ctx) => {
|
|
363
|
+
const down = await failing(ctx);
|
|
364
|
+
if (!down.length) return null;
|
|
365
|
+
const { text, chatId } = await ctx.agent(`Say in two sentences why ${down[0]} is down.`, { agent: "claude" });
|
|
366
|
+
await ctx.notify({ title: `${down[0]} is down`, body: text, page: `chat:${chatId}` });
|
|
367
|
+
return text;
|
|
368
|
+
}),
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
The chat runs in `project` (the calling project unless said), under the
|
|
372
|
+
calling chat when an agent called the plugin. It waits up to `timeout`
|
|
373
|
+
(10 minutes unless said) and then throws, naming the chat, which keeps
|
|
374
|
+
running. The same two host methods are there by hand: `ctx.host("chats.start", …)`
|
|
375
|
+
and `ctx.host("chats.wait", …)`, for a chat you want to link to rather than wait for.
|
|
376
|
+
|
|
377
|
+
### MCP servers
|
|
378
|
+
|
|
379
|
+
`ctx.mcp` reaches the MCP servers the user connected in RepoGo, by the key
|
|
380
|
+
cards name them with, using the user's own sign-in, so a plugin needs no key
|
|
381
|
+
or CLI of its own. It answers the tool's structured content, else its text
|
|
382
|
+
(parsed when it's JSON):
|
|
383
|
+
|
|
384
|
+
```ts
|
|
385
|
+
customers: method(z.object({}), async (_, ctx) => {
|
|
386
|
+
const list = await ctx.mcp("stripe", "stripe_api_read", { stripe_api_operation_id: "GetCustomers", /* … */ });
|
|
387
|
+
return list.data.map((c) => ({ id: c.id, email: c.email }));
|
|
388
|
+
}, { kind: "query" }),
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
A query may run only the server's read-only tools, as a card's `Data` may. A
|
|
392
|
+
tool marked destructive runs only from a method with a `confirm`, or one
|
|
393
|
+
called from it with `ctx.call`: the user's answer to that question is its
|
|
394
|
+
yes. A refusal throws `FORBIDDEN`; a tool's own error throws `INTERNAL` with
|
|
395
|
+
its message. Only servers switched on for `ctx.root` answer, so a cron of an
|
|
396
|
+
environment plugin, whose root is the home folder, reaches none.
|
|
397
|
+
|
|
310
398
|
## Events
|
|
311
399
|
|
|
312
400
|
Handlers in `on` run after the events they name:
|
|
@@ -330,8 +418,12 @@ Events run in memory: none is retried, and a handler that throws is logged.
|
|
|
330
418
|
|
|
331
419
|
## How cards and agents reach a plugin
|
|
332
420
|
|
|
333
|
-
**Agents** use two tools
|
|
334
|
-
|
|
421
|
+
**Agents** use two tools. `plugins` lists every plugin with a line per method
|
|
422
|
+
(its kind, whether it asks first, and its input's fields, optional ones ending
|
|
423
|
+
in `?`), and every line of `~/.repogo/crons.json` with how it's going.
|
|
424
|
+
`plugins` with `plugin` set answers that plugin in full: each method's input
|
|
425
|
+
and output JSON Schema and confirm, its `storage` (each bucket's and stream's
|
|
426
|
+
folder), the pages that show its methods, and the cron lines naming it.
|
|
335
427
|
`plugin_call` runs one. Call a new method once before a card names it.
|
|
336
428
|
|
|
337
429
|
**Cards** name methods as `plugin:<plugin>.<method>`. A `Data` element runs a
|
|
@@ -359,4 +451,26 @@ schema answers `VALIDATION` with a message per field. Anything else thrown is
|
|
|
359
451
|
A method's `confirm` is asked before a card's tap runs it. An agent's
|
|
360
452
|
`plugin_call` gets it back as a `CONFIRM` error until it calls again with
|
|
361
453
|
`confirmed: true`, once the user agrees. Only `ctx.call` from plugin code skips
|
|
362
|
-
it. Give one to anything that can't be undone.
|
|
454
|
+
it. Give one to anything that can't be undone. `{{field}}` in the question is
|
|
455
|
+
filled from the call's input, so it can say what it acts on:
|
|
456
|
+
`confirm: "Restart {{machine}}?"`.
|
|
457
|
+
|
|
458
|
+
## Testing
|
|
459
|
+
|
|
460
|
+
`@repogo/plugins/testing` runs plugins as the host does, with real data in a
|
|
461
|
+
temporary folder; `host` and `mcp` stand in for the host's methods and the
|
|
462
|
+
user's MCP servers:
|
|
463
|
+
|
|
464
|
+
```ts
|
|
465
|
+
import { testRuntime } from "@repogo/plugins/testing";
|
|
466
|
+
import notes from "./app";
|
|
467
|
+
|
|
468
|
+
const app = await testRuntime({ notes });
|
|
469
|
+
await app.call("notes.add", { text: "hi" });
|
|
470
|
+
await app.call("notes.clear", {}, { confirmed: false }); // throws CONFIRM, as an unanswered tap
|
|
471
|
+
app.setCrons([{ plugin: "notes", method: "tidy", schedule: "* * * * *" }]); // as a crons.json line would
|
|
472
|
+
await app.tickCrons(Date.now() + 60_000); // runs whatever is due by then
|
|
473
|
+
app.notices; // every ctx.notify so far
|
|
474
|
+
app.logs; // every console line so far, with its method and caller
|
|
475
|
+
await app.close();
|
|
476
|
+
```
|
package/dist/collections.d.ts
CHANGED
|
@@ -8,9 +8,12 @@ export type Record_<T> = T & {
|
|
|
8
8
|
};
|
|
9
9
|
export type ListOptions = {
|
|
10
10
|
where?: Record<string, string | number | boolean | null>;
|
|
11
|
-
|
|
11
|
+
/** `id`, `createdAt`, `updatedAt`, or one of the record's own top-level fields. */
|
|
12
|
+
orderBy?: "createdAt" | "updatedAt" | (string & {});
|
|
12
13
|
order?: "asc" | "desc";
|
|
13
14
|
limit?: number;
|
|
15
|
+
/** Records to pass over first, in the same order: `offset: 300` is everything past the newest 300. */
|
|
16
|
+
offset?: number;
|
|
14
17
|
};
|
|
15
18
|
export declare class Collection<S extends z.ZodObject> {
|
|
16
19
|
private database;
|
|
@@ -28,6 +31,13 @@ export declare class Collection<S extends z.ZodObject> {
|
|
|
28
31
|
update(id: string, patch: Partial<z.input<S>>): Promise<Record_<z.infer<S>>>;
|
|
29
32
|
delete(id: string): Promise<void>;
|
|
30
33
|
list(options?: ListOptions): Promise<Record_<z.infer<S>>[]>;
|
|
34
|
+
/**
|
|
35
|
+
* Deletes the records `list(options)` would return, with its same defaults,
|
|
36
|
+
* in one transaction, and returns how many. Each emits its `deleted` event.
|
|
37
|
+
*/
|
|
38
|
+
deleteMany(options?: ListOptions): Promise<number>;
|
|
39
|
+
/** The query `list` and `deleteMany` share, so both pick out the same records. */
|
|
40
|
+
private select;
|
|
31
41
|
private eventName;
|
|
32
42
|
private toRecord;
|
|
33
43
|
}
|
package/dist/context.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { Collection } from "./collections";
|
|
|
3
3
|
import type { Kv } from "./kv";
|
|
4
4
|
import type { Stream } from "./streams";
|
|
5
5
|
import type { MethodKind } from "./define";
|
|
6
|
+
import type { Duration } from "./duration";
|
|
6
7
|
/** An alert for the user's phones: what it says, and the page a tap opens. */
|
|
7
8
|
export type Notice = {
|
|
8
9
|
title: string;
|
|
@@ -32,6 +33,8 @@ export type ManifestCron = {
|
|
|
32
33
|
nextRunAt: number | null;
|
|
33
34
|
lastRunAt: number | null;
|
|
34
35
|
lastError: string | null;
|
|
36
|
+
/** How long its last run took: a run far past its interval blocked or waited on something. */
|
|
37
|
+
lastDurationMs: number | null;
|
|
35
38
|
running: boolean;
|
|
36
39
|
runs: number;
|
|
37
40
|
};
|
|
@@ -49,6 +52,21 @@ export type ManifestStorage = {
|
|
|
49
52
|
path: string;
|
|
50
53
|
}[];
|
|
51
54
|
};
|
|
55
|
+
/**
|
|
56
|
+
* One schedule the host gives a Worker, from a line of `~/.repogo/crons.json`: call
|
|
57
|
+
* `<plugin>.<method>` with `{}` when `schedule` comes due in `timezone` (the environment's).
|
|
58
|
+
*/
|
|
59
|
+
export type CronEntry = {
|
|
60
|
+
plugin: string;
|
|
61
|
+
method: string;
|
|
62
|
+
schedule: string;
|
|
63
|
+
timezone?: string;
|
|
64
|
+
};
|
|
65
|
+
/** A schedule and how it's going; `error` says why it couldn't be set, and then nothing runs. */
|
|
66
|
+
export type CronStatus = ManifestCron & {
|
|
67
|
+
plugin: string;
|
|
68
|
+
error: string | null;
|
|
69
|
+
};
|
|
52
70
|
export type Manifest = {
|
|
53
71
|
plugins: {
|
|
54
72
|
name: string;
|
|
@@ -58,8 +76,27 @@ export type Manifest = {
|
|
|
58
76
|
storage: ManifestStorage;
|
|
59
77
|
}[];
|
|
60
78
|
};
|
|
79
|
+
/** 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. */
|
|
80
|
+
export type Caller = "card" | "agent" | "cron" | "code";
|
|
81
|
+
/** How `ctx.agent` runs a chat: which agent, in which project, and how long to wait for its answer. */
|
|
82
|
+
export type AgentOptions = {
|
|
83
|
+
/** `claude` or `codex`, as `agents.list` names them. */
|
|
84
|
+
agent: string;
|
|
85
|
+
/** The project the chat runs in; the calling project unless said. */
|
|
86
|
+
project?: string;
|
|
87
|
+
title?: string;
|
|
88
|
+
/** How long to wait for the answer; 10 minutes unless said. */
|
|
89
|
+
timeout?: Duration;
|
|
90
|
+
};
|
|
91
|
+
/** What the agent answered, and the chat it answered in, to open or message later. */
|
|
92
|
+
export type AgentReply = {
|
|
93
|
+
chatId: string;
|
|
94
|
+
text: string;
|
|
95
|
+
};
|
|
61
96
|
export type Context = {
|
|
62
97
|
plugin: string;
|
|
98
|
+
/** Who started the outermost call; `ctx.call` passes it on. */
|
|
99
|
+
caller: Caller;
|
|
63
100
|
/** The project's checkout, for a plugin that reads the project's files. */
|
|
64
101
|
root: string;
|
|
65
102
|
collections: Record<string, Collection<any>>;
|
|
@@ -73,6 +110,13 @@ export type Context = {
|
|
|
73
110
|
* `ctx.host("git.status", { path: ctx.root })`. Its answer doesn't re-run a query.
|
|
74
111
|
*/
|
|
75
112
|
host: (method: string, params?: unknown) => Promise<unknown>;
|
|
113
|
+
/**
|
|
114
|
+
* Runs a tool of an MCP server the user switched on for the calling project:
|
|
115
|
+
* `ctx.mcp("stripe", "stripe_api_read", { … })`, answering the tool's result.
|
|
116
|
+
* A query may run only read-only tools; a destructive tool runs only from a
|
|
117
|
+
* method with a `confirm`. Its answer doesn't re-run a query.
|
|
118
|
+
*/
|
|
119
|
+
mcp: (server: string, tool: string, input?: unknown) => Promise<unknown>;
|
|
76
120
|
/**
|
|
77
121
|
* The plugins beside this one, in the same folder (the project's, or the
|
|
78
122
|
* environment's), with their methods: what `call` can reach.
|
|
@@ -82,5 +126,12 @@ export type Context = {
|
|
|
82
126
|
signal: AbortSignal;
|
|
83
127
|
/** Sends an alert to every phone paired with this environment. */
|
|
84
128
|
notify: (notice: Notice) => void;
|
|
129
|
+
/**
|
|
130
|
+
* Starts a chat with `prompt` and waits for the agent's whole answer:
|
|
131
|
+
* `await ctx.agent("Why is the build failing?", { agent: "claude" })`.
|
|
132
|
+
* Throws once `timeout` passes, naming the chat, which keeps running.
|
|
133
|
+
*/
|
|
134
|
+
agent: (prompt: string, options: AgentOptions) => Promise<AgentReply>;
|
|
135
|
+
/** The same as `console.log`: every console line is kept with the plugin and the call that wrote it. */
|
|
85
136
|
log: (...args: unknown[]) => void;
|
|
86
137
|
};
|
package/dist/cron.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type Schedule = {
|
|
2
|
+
seconds: Set<number> | null;
|
|
3
|
+
minutes: Set<number>;
|
|
4
|
+
hours: Set<number>;
|
|
5
|
+
days: Set<number>;
|
|
6
|
+
months: Set<number>;
|
|
7
|
+
weekdays: Set<number>;
|
|
8
|
+
/** Day fields given as other than `*`, for cron's either-day rule. */
|
|
9
|
+
daysRestricted: boolean;
|
|
10
|
+
weekdaysRestricted: boolean;
|
|
11
|
+
};
|
|
12
|
+
/** Reads a schedule, or throws saying what's wrong with it. */
|
|
13
|
+
export declare function parseSchedule(source: string): Schedule;
|
|
14
|
+
/** Throws unless `zone` is a time zone this machine knows, such as "America/New_York". */
|
|
15
|
+
export declare function checkTimezone(zone: string): void;
|
|
16
|
+
/**
|
|
17
|
+
* The first due time strictly after `after` (ms), in `zone`, or null when there
|
|
18
|
+
* is none within four years (`0 0 31 2 *`). A wall time that a fall-back night
|
|
19
|
+
* shows twice is due only the first time.
|
|
20
|
+
*/
|
|
21
|
+
export declare function nextDue(s: Schedule, after: number, zone: string): number | null;
|
package/dist/define.d.ts
CHANGED
|
@@ -28,23 +28,13 @@ export type Method<I extends z.ZodType = z.ZodType, O extends z.ZodType = z.ZodT
|
|
|
28
28
|
run: (input: z.infer<I>, ctx: Context) => unknown | Promise<unknown>;
|
|
29
29
|
};
|
|
30
30
|
export type Handler = (payload: any, ctx: Context) => unknown | Promise<unknown>;
|
|
31
|
-
/**
|
|
32
|
-
* Runs one of the plugin's methods on a schedule, called with `{}` as a tap
|
|
33
|
-
* would: `{ method: "watch", schedule: "*\/5 * * * *" }`. `schedule` is a cron,
|
|
34
|
-
* five fields or six with seconds first; `timezone` defaults to the environment's.
|
|
35
|
-
*/
|
|
36
|
-
export type Cron = {
|
|
37
|
-
method: string;
|
|
38
|
-
schedule: string;
|
|
39
|
-
timezone?: string;
|
|
40
|
-
};
|
|
41
31
|
export type PluginDefinition = {
|
|
42
32
|
/** One line, shown to the agent in the index of plugins. */
|
|
43
33
|
description?: string;
|
|
44
34
|
/**
|
|
45
35
|
* Where the plugin's data lives: `<namespace>/` in the data dir, holding `data.db`
|
|
46
36
|
* and its buckets' and streams' files. Plugins naming the same namespace share its
|
|
47
|
-
* data.
|
|
37
|
+
* data. Required once the plugin keeps data; by convention a project's own is
|
|
48
38
|
* `<project>.<name>` (`repogo.plans`) and data for every project a bare name (`todos`).
|
|
49
39
|
*/
|
|
50
40
|
namespace?: string;
|
|
@@ -57,7 +47,11 @@ export type PluginDefinition = {
|
|
|
57
47
|
streams?: Record<string, StreamDefinition>;
|
|
58
48
|
methods?: Record<string, Method<any, any>>;
|
|
59
49
|
on?: Record<string, Handler>;
|
|
60
|
-
|
|
50
|
+
/**
|
|
51
|
+
* Data named here gets no generated methods (list, create, delete…), so cards
|
|
52
|
+
* and agents reach it only through the plugin's own: an audit log, say.
|
|
53
|
+
*/
|
|
54
|
+
private?: string[];
|
|
61
55
|
};
|
|
62
56
|
export declare function definePlugin<T extends PluginDefinition>(plugin: T): T;
|
|
63
57
|
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?: {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { Bucket } from "./buckets";
|
|
3
|
+
import type { MethodKind } from "./define";
|
|
4
|
+
import type { Kv } from "./kv";
|
|
5
|
+
import type { Stream } from "./streams";
|
|
6
|
+
export type Generated = {
|
|
7
|
+
kind: MethodKind;
|
|
8
|
+
description: string;
|
|
9
|
+
input: z.ZodType;
|
|
10
|
+
output?: z.ZodType;
|
|
11
|
+
confirm?: string;
|
|
12
|
+
run: (input: any, signal: AbortSignal) => unknown;
|
|
13
|
+
};
|
|
14
|
+
export declare function kvMethods(name: string, kv: Kv<any>, schema: z.ZodType): Record<string, Generated>;
|
|
15
|
+
export declare function streamMethods(name: string, stream: Stream<any>, schema: z.ZodType): Record<string, Generated>;
|
|
16
|
+
export declare function bucketMethods(name: string, bucket: Bucket<any>, schema: z.ZodObject): Record<string, Generated>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
class MethodError extends Error {
|
|
5
|
+
code;
|
|
6
|
+
fieldErrors;
|
|
7
|
+
constructor(code, message, fieldErrors) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.code = code;
|
|
10
|
+
this.fieldErrors = fieldErrors;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function toCallError(error) {
|
|
14
|
+
if (error instanceof MethodError) {
|
|
15
|
+
return { code: error.code, message: error.message, ...error.fieldErrors ? { fieldErrors: error.fieldErrors } : {} };
|
|
16
|
+
}
|
|
17
|
+
if (error instanceof z.ZodError) {
|
|
18
|
+
const fieldErrors = {};
|
|
19
|
+
for (const issue of error.issues)
|
|
20
|
+
fieldErrors["/" + issue.path.join("/")] ??= issue.message;
|
|
21
|
+
const messages = Object.values(fieldErrors);
|
|
22
|
+
const message = messages.length === 1 ? messages[0] : "Check the highlighted fields.";
|
|
23
|
+
return { code: "VALIDATION", message, fieldErrors };
|
|
24
|
+
}
|
|
25
|
+
return { code: "INTERNAL", message: error instanceof Error ? error.message : String(error) };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export { MethodError, toCallError };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { definePlugin, method, stream } from "./define";
|
|
2
|
-
export type { PluginDefinition, Method, MethodKind,
|
|
3
|
-
export type { Context, Manifest, ManifestCron, ManifestMethod, ManifestStorage } from "./context";
|
|
2
|
+
export type { PluginDefinition, Method, MethodKind, StreamDefinition } from "./define";
|
|
3
|
+
export type { AgentOptions, AgentReply, Caller, Context, CronEntry, CronStatus, Manifest, ManifestCron, ManifestMethod, ManifestStorage } from "./context";
|
|
4
|
+
export type { LogLevel, LogRecord } from "./logs";
|
|
4
5
|
export type { BucketFile, FileStatus, PutFile } from "./buckets";
|
|
5
6
|
export type { KvEntry, SetOptions } from "./kv";
|
|
6
7
|
export type { Retain, StreamEntry, StreamKey } from "./streams";
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
// @bun
|
|
2
|
+
import {
|
|
3
|
+
MethodError,
|
|
4
|
+
toCallError
|
|
5
|
+
} from "./index-83w1fk10.js";
|
|
6
|
+
|
|
2
7
|
// src/define.ts
|
|
3
8
|
function definePlugin(plugin) {
|
|
4
9
|
return plugin;
|
|
@@ -16,37 +21,11 @@ function method(input, run, options = {}) {
|
|
|
16
21
|
function stream(input, run, options = {}) {
|
|
17
22
|
return { input, run, kind: "stream", description: options.description };
|
|
18
23
|
}
|
|
19
|
-
// src/errors.ts
|
|
20
|
-
import { z } from "zod";
|
|
21
|
-
|
|
22
|
-
class MethodError extends Error {
|
|
23
|
-
code;
|
|
24
|
-
fieldErrors;
|
|
25
|
-
constructor(code, message, fieldErrors) {
|
|
26
|
-
super(message);
|
|
27
|
-
this.code = code;
|
|
28
|
-
this.fieldErrors = fieldErrors;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
function toCallError(error) {
|
|
32
|
-
if (error instanceof MethodError) {
|
|
33
|
-
return { code: error.code, message: error.message, ...error.fieldErrors ? { fieldErrors: error.fieldErrors } : {} };
|
|
34
|
-
}
|
|
35
|
-
if (error instanceof z.ZodError) {
|
|
36
|
-
const fieldErrors = {};
|
|
37
|
-
for (const issue of error.issues)
|
|
38
|
-
fieldErrors["/" + issue.path.join("/")] ??= issue.message;
|
|
39
|
-
const messages = Object.values(fieldErrors);
|
|
40
|
-
const message = messages.length === 1 ? messages[0] : "Check the highlighted fields.";
|
|
41
|
-
return { code: "VALIDATION", message, fieldErrors };
|
|
42
|
-
}
|
|
43
|
-
return { code: "INTERNAL", message: error instanceof Error ? error.message : String(error) };
|
|
44
|
-
}
|
|
45
24
|
|
|
46
25
|
// src/index.ts
|
|
47
|
-
import { z
|
|
26
|
+
import { z } from "zod";
|
|
48
27
|
export {
|
|
49
|
-
|
|
28
|
+
z,
|
|
50
29
|
toCallError,
|
|
51
30
|
stream,
|
|
52
31
|
method,
|
package/dist/logs.d.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import type { Caller } from "./context";
|
|
3
|
+
export type LogLevel = "debug" | "info" | "warn" | "error";
|
|
4
|
+
export type LogRecord = {
|
|
5
|
+
/** ms since the epoch. */
|
|
6
|
+
at: number;
|
|
7
|
+
plugin: string;
|
|
8
|
+
level: LogLevel;
|
|
9
|
+
/** `<plugin>.<method>` of the run the line was written in; empty outside a run. */
|
|
10
|
+
method: string;
|
|
11
|
+
caller: Caller | "";
|
|
12
|
+
/** One id per outermost call, so a call's lines read together. */
|
|
13
|
+
callId: string;
|
|
14
|
+
/** The chat whose agent made the call, for `caller: "agent"`. */
|
|
15
|
+
chatId: string;
|
|
16
|
+
/** The arguments formatted as the console prints them, at most `textLimit` characters. */
|
|
17
|
+
text: string;
|
|
18
|
+
/** The arguments as given, as JSON can carry them. */
|
|
19
|
+
data: unknown[];
|
|
20
|
+
};
|
|
21
|
+
/** What a line is attributed to: the plugin, and the run when there is one. */
|
|
22
|
+
export type LogContext = {
|
|
23
|
+
plugin: string;
|
|
24
|
+
method?: string;
|
|
25
|
+
caller?: Caller;
|
|
26
|
+
callId?: string;
|
|
27
|
+
chatId?: string;
|
|
28
|
+
};
|
|
29
|
+
/** The run the code writing a line is in; the runtime sets it around every method, handler and cron. */
|
|
30
|
+
export declare const logContext: AsyncLocalStorage<LogContext>;
|
|
31
|
+
/**
|
|
32
|
+
* Runs `work` as an outermost run under `context`. An error nothing catches
|
|
33
|
+
* reaches its handler with no async context, so the newest run going is the
|
|
34
|
+
* best guess at whose it is.
|
|
35
|
+
*/
|
|
36
|
+
export declare function runWith<T>(context: LogContext, work: () => Promise<T>): Promise<T>;
|
|
37
|
+
/** A line longer than this is cut: a dumped payload is read in the plugin's own data, not its log. */
|
|
38
|
+
export declare const textLimit: number;
|
|
39
|
+
type Sink = (record: LogRecord) => void;
|
|
40
|
+
/**
|
|
41
|
+
* Replaces `console.log`, `info`, `debug`, `warn` and `error` with ones that
|
|
42
|
+
* record each call. `passThrough` keeps printing them too, for tests.
|
|
43
|
+
*/
|
|
44
|
+
export declare function captureConsole(to: Sink, options?: {
|
|
45
|
+
passThrough?: boolean;
|
|
46
|
+
}): void;
|
|
47
|
+
/** Puts the console back and stops recording. */
|
|
48
|
+
export declare function releaseConsole(): void;
|
|
49
|
+
/**
|
|
50
|
+
* Records one line under `context`, else the current run, else the newest run
|
|
51
|
+
* going, as an error nothing caught lands in. With no capture installed the
|
|
52
|
+
* line prints as the console would.
|
|
53
|
+
*/
|
|
54
|
+
export declare function record(level: LogLevel, args: unknown[], context?: LogContext): void;
|
|
55
|
+
export {};
|