agentic-comments 0.2.0 → 0.3.0
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 +65 -2
- package/dist/{chunk-RWMBGIAS.js → chunk-GMA5I5L6.js} +1 -1
- package/dist/{chunk-C3P5TAC3.js → chunk-GSAJOCDV.js} +1 -3
- package/dist/{chunk-ZCM2UDLN.js → chunk-IB6ANO32.js} +1 -1
- package/dist/{chunk-UE5ULEXU.js → chunk-TTTX3R6M.js} +3 -3
- package/dist/{chunk-C43HLFQE.js → chunk-YU5U4P22.js} +1 -1
- package/dist/cli.js +5 -5
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -2
- package/dist/local.d.ts +3 -59
- package/dist/local.js +3 -3
- package/dist/mcp-YERA77HT.js +11 -0
- package/dist/mcp-bin.js +30 -11
- package/dist/service-BmMQMT_8.d.ts +66 -0
- package/dist/{suggestions-U8tXRLZ5.d.ts → suggestions-D4ZJFzzR.d.ts} +1 -1
- package/dist/worker.d.ts +58 -0
- package/dist/worker.js +287 -0
- package/package.json +10 -2
- package/dist/mcp-BOJM5NE6.js +0 -11
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ Comment threads and suggestions anchored to any content, from people and agents.
|
|
|
11
11
|
|
|
12
12
|
## Install
|
|
13
13
|
|
|
14
|
-
Requires Node.js
|
|
14
|
+
Requires Node.js 22 or later. `yjs` is a peer dependency. Your app and this package must use the same
|
|
15
15
|
copy of `yjs`, because the package checks the Yjs types of your document. Install both:
|
|
16
16
|
|
|
17
17
|
```sh
|
|
@@ -170,6 +170,69 @@ npx agentic-comments accept <id>
|
|
|
170
170
|
`{"value": <JSON>}`.
|
|
171
171
|
- From code: `import { FileStore, CommentsService, operatorActor } from "agentic-comments/local"`.
|
|
172
172
|
|
|
173
|
+
## HTTP API
|
|
174
|
+
|
|
175
|
+
`agentic-comments/worker` is a Cloudflare Worker (Hono) with REST routes under `/api/v1`. It keeps each
|
|
176
|
+
document in a D1 database through the `Store` port of `agentic-identity`. It checks every request with the
|
|
177
|
+
`Auth` port, so the host picks who the caller is and no request carries an author.
|
|
178
|
+
|
|
179
|
+
Set the principals as a secret. It is JSON from a bearer token (32 bytes or more) to `kind:name`, or to
|
|
180
|
+
`{ "as": "kind:name", "scopes": [...] }`. One bad entry refuses the whole table. A request with no valid
|
|
181
|
+
token gets `401`.
|
|
182
|
+
|
|
183
|
+
```sh
|
|
184
|
+
wrangler secret put AGENTIC_COMMENTS_TOKENS
|
|
185
|
+
# {"<openssl rand -hex 32>": {"as": "human:ada", "scopes": ["comments:write"]}, "<another>": "agent_of_human:scribe"}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Also set `AGENTIC_COMMENTS_REGION` to `eu` or `us`. Without it every document route fails with `500 MISCONFIGURED`. Use `unknown` with
|
|
189
|
+
`AGENTIC_COMMENTS_UNSAFE_REGION=1` only under `wrangler dev`. The `wrangler.jsonc` in the repo shows the D1 binding.
|
|
190
|
+
|
|
191
|
+
| Scope | Allows |
|
|
192
|
+
| --- | --- |
|
|
193
|
+
| `comments:read` | Read documents, threads, and suggestions. |
|
|
194
|
+
| `comments:comment` | Also add threads, reply, resolve, reopen, and suggest. This is the default for a token with no scopes. |
|
|
195
|
+
| `comments:write` | Also create documents, relocate, and accept or reject. |
|
|
196
|
+
| `comments:admin` | Also let an agent accept or reject (the `comments:accept` right). |
|
|
197
|
+
|
|
198
|
+
| Route | Action |
|
|
199
|
+
| --- | --- |
|
|
200
|
+
| `GET /api/v1/me` | Who the token stands for. |
|
|
201
|
+
| `POST /api/v1/docs` `{id, text?}` | Create a document. |
|
|
202
|
+
| `GET /api/v1/docs/:doc` | Read the content text. |
|
|
203
|
+
| `GET`, `POST /api/v1/docs/:doc/threads` | List (`?status=`, `?anchorState=`, `?mentioning=`) or add. |
|
|
204
|
+
| `GET /api/v1/docs/:doc/threads/:id` | Read one thread. |
|
|
205
|
+
| `POST /api/v1/docs/:doc/threads/:id/replies` | Reply. |
|
|
206
|
+
| `POST .../threads/:id/resolve`, `reopen`, `relocate` | Change a thread. |
|
|
207
|
+
| `GET`, `POST /api/v1/docs/:doc/suggestions` | List or suggest. |
|
|
208
|
+
| `POST .../suggestions/:id/accept`, `reject` | Decide. An agent never decides its own suggestion. |
|
|
209
|
+
|
|
210
|
+
Bodies are the same as the MCP tool inputs. An error is `{"error": {"code", "message"}}`. A body with an
|
|
211
|
+
`author` key is refused with `400`.
|
|
212
|
+
|
|
213
|
+
```sh
|
|
214
|
+
curl -H "authorization: Bearer $TOKEN" -H "content-type: application/json" \
|
|
215
|
+
-d '{"anchor":{"type":"text","quote":"brave"},"body":"Word choice?"}' \
|
|
216
|
+
http://127.0.0.1:8787/api/v1/docs/d1/threads
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Live demo
|
|
220
|
+
|
|
221
|
+
A demo Worker runs at <https://agentic-comments-demo.hello-fd1.workers.dev>. It is a staging site. Check it with
|
|
222
|
+
`GET /health`. Every `/api/v1` route needs a bearer token, and the demo does not publish one, so a call
|
|
223
|
+
with no token gets `401`. The data lives in a D1 database in the EU (`weur`), and the Worker sets the
|
|
224
|
+
region to `eu`.
|
|
225
|
+
|
|
226
|
+
To deploy your own copy, log in with `wrangler login` or set `CLOUDFLARE_API_TOKEN`, then:
|
|
227
|
+
|
|
228
|
+
```sh
|
|
229
|
+
npx wrangler d1 create agentic-comments-demo --location weur # once. Put the id in wrangler.demo.jsonc.
|
|
230
|
+
npx wrangler secret put AGENTIC_COMMENTS_TOKENS --config wrangler.demo.jsonc # see HTTP API
|
|
231
|
+
npm run deploy:demo
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
`wrangler.demo.jsonc` sets no secret. Deploys run by hand, and no CI job holds a Cloudflare token.
|
|
235
|
+
|
|
173
236
|
## Notifications
|
|
174
237
|
|
|
175
238
|
`Notifications` turns changes into events that your app can send to a notifier. It watches the data, so it
|
|
@@ -225,7 +288,7 @@ const stop = notifications.subscribe(async (event) => {
|
|
|
225
288
|
|
|
226
289
|
## Limits
|
|
227
290
|
|
|
228
|
-
-
|
|
291
|
+
- The file store is for one machine. It is not a sync server. The Worker keeps each document as one D1 row and refuses a document past 600,000 bytes of state.
|
|
229
292
|
- `add` checks that a text anchor points into this document and matches its quote.
|
|
230
293
|
- The library trusts the host to call `stamp()` only for a principal it authenticated.
|
|
231
294
|
- A reply to a resolved thread is refused on the replica that sees the resolve. A reply and a
|
|
@@ -158,9 +158,6 @@ function authorLabel(author) {
|
|
|
158
158
|
return author.kind === "human" ? "person" : author.kind === "user_token" ? "person via agent" : "agent";
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
-
// src/suggestions.ts
|
|
162
|
-
import * as Y2 from "yjs";
|
|
163
|
-
|
|
164
161
|
// src/validate.ts
|
|
165
162
|
var invalid = (message) => {
|
|
166
163
|
throw new CommentsError("INVALID_INPUT", message);
|
|
@@ -182,6 +179,7 @@ var strictCopy = (value) => JSON.parse(
|
|
|
182
179
|
);
|
|
183
180
|
|
|
184
181
|
// src/suggestions.ts
|
|
182
|
+
import * as Y2 from "yjs";
|
|
185
183
|
var ACCEPT_SCOPE = "comments:accept";
|
|
186
184
|
var MAX_CHANGE = 64e3;
|
|
187
185
|
var MAX_RECEIPT = 16e3;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
VERSION
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-GMA5I5L6.js";
|
|
4
4
|
import {
|
|
5
5
|
CommentsService
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-IB6ANO32.js";
|
|
7
7
|
import {
|
|
8
8
|
CommentsError
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-GSAJOCDV.js";
|
|
10
10
|
|
|
11
11
|
// src/mcp.ts
|
|
12
12
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
package/dist/cli.js
CHANGED
|
@@ -3,17 +3,17 @@ import {
|
|
|
3
3
|
FileStore,
|
|
4
4
|
lockTimeoutFrom,
|
|
5
5
|
operatorActor
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-YU5U4P22.js";
|
|
7
7
|
import {
|
|
8
8
|
VERSION
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-GMA5I5L6.js";
|
|
10
10
|
import {
|
|
11
11
|
CommentsService,
|
|
12
12
|
TEXT_ROOT
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-IB6ANO32.js";
|
|
14
14
|
import {
|
|
15
15
|
CommentsError
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-GSAJOCDV.js";
|
|
17
17
|
|
|
18
18
|
// src/cli.ts
|
|
19
19
|
import { readFileSync, realpathSync } from "fs";
|
|
@@ -155,7 +155,7 @@ async function runCli(argv, io) {
|
|
|
155
155
|
return 0;
|
|
156
156
|
}
|
|
157
157
|
if (p.command === "mcp") {
|
|
158
|
-
const { startMcp } = await import("./mcp-
|
|
158
|
+
const { startMcp } = await import("./mcp-YERA77HT.js");
|
|
159
159
|
await startMcp(store, operatorActor(p.flags["--as"] ?? io.env.AGENTIC_COMMENTS_AS, io.env.AGENTIC_COMMENTS_SCOPES));
|
|
160
160
|
return 0;
|
|
161
161
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export {
|
|
1
|
+
import { a as Author, C as Comments, b as Suggestions } from './suggestions-D4ZJFzzR.js';
|
|
2
|
+
export { c as ACCEPT_SCOPE, A as Actor, d as Anchor, e as AnchorKind, f as AnchorState, g as ApplyContext, h as CanvasAnchor, i as CellAnchor, j as CommentsOptions, M as Message, N as NewReply, k as NewSuggestion, l as NewThread, P as PRINCIPAL_KINDS, m as PrincipalKind, R as RelativePositionJson, n as RevertContext, o as SlideAnchor, p as StampInput, S as Suggestion, q as SuggestionFilter, r as SuggestionHost, s as TextAnchor, T as Thread, t as ThreadFilter, u as authorLabel, v as stamp, w as textAnchor, x as textOf } from './suggestions-D4ZJFzzR.js';
|
|
3
3
|
import 'yjs';
|
|
4
4
|
|
|
5
5
|
declare const VERSION: string;
|
|
6
6
|
|
|
7
|
-
type CommentsErrorCode = "INVALID_AUTHOR" | "INVALID_INPUT" | "INVALID_ANCHOR" | "NO_THREAD" | "THREAD_RESOLVED" | "CORRUPT" | "NO_SUGGESTION" | "FORBIDDEN" | "ALREADY_DECIDED" | "APPLY_FAILED" | "RELOCATE_FAILED" | "STORE_MISSING" | "STORE_EXISTS" | "STORE_CORRUPT" | "STORE_LOCKED" | "STORE_FULL" | "USAGE";
|
|
7
|
+
type CommentsErrorCode = "INVALID_AUTHOR" | "INVALID_INPUT" | "INVALID_ANCHOR" | "NO_THREAD" | "THREAD_RESOLVED" | "CORRUPT" | "NO_SUGGESTION" | "FORBIDDEN" | "ALREADY_DECIDED" | "APPLY_FAILED" | "RELOCATE_FAILED" | "STORE_MISSING" | "STORE_EXISTS" | "STORE_CORRUPT" | "STORE_LOCKED" | "STORE_FULL" | "NO_DOCUMENT" | "DOCUMENT_EXISTS" | "CONFLICT" | "MISCONFIGURED" | "USAGE";
|
|
8
8
|
declare class CommentsError extends Error {
|
|
9
9
|
readonly code: CommentsErrorCode;
|
|
10
10
|
constructor(code: CommentsErrorCode, message: string);
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
VERSION
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-GMA5I5L6.js";
|
|
4
4
|
import {
|
|
5
5
|
ACCEPT_SCOPE,
|
|
6
6
|
Actor,
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
textAnchor,
|
|
15
15
|
textOf,
|
|
16
16
|
validDate
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-GSAJOCDV.js";
|
|
18
18
|
|
|
19
19
|
// src/notifications.ts
|
|
20
20
|
import * as Y from "yjs";
|
package/dist/local.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as Y from 'yjs';
|
|
2
|
-
|
|
2
|
+
export { A as AnchorSpec, C as CommentsService, L as ListFilter } from './service-BmMQMT_8.js';
|
|
3
|
+
import { r as SuggestionHost, A as Actor } from './suggestions-D4ZJFzzR.js';
|
|
3
4
|
|
|
4
5
|
/** The most bytes a saved document holds. A save over this is refused, so a file never grows past what a load accepts. */
|
|
5
6
|
declare const MAX_FILE: number;
|
|
@@ -35,63 +36,6 @@ declare class FileStore {
|
|
|
35
36
|
write<T>(fn: (ydoc: Y.Doc) => T): T;
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
/** Where a comment or suggestion points, in terms a person or agent can write down. */
|
|
39
|
-
type AnchorSpec = {
|
|
40
|
-
type: "text";
|
|
41
|
-
quote: string;
|
|
42
|
-
occurrence?: number;
|
|
43
|
-
} | {
|
|
44
|
-
type: "cell";
|
|
45
|
-
key: string;
|
|
46
|
-
} | {
|
|
47
|
-
type: "slide";
|
|
48
|
-
elementId: string;
|
|
49
|
-
} | {
|
|
50
|
-
type: "canvas";
|
|
51
|
-
elementId: string;
|
|
52
|
-
};
|
|
53
|
-
interface ListFilter {
|
|
54
|
-
status?: string;
|
|
55
|
-
anchorState?: string;
|
|
56
|
-
mentioning?: string;
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* Every action the CLI and MCP server offer, on a local file store. Each call takes the actor the operator
|
|
60
|
-
* stamped, and no input carries an author, so both faces return the same shapes and obey the same rules.
|
|
61
|
-
*/
|
|
62
|
-
declare class CommentsService {
|
|
63
|
-
private readonly store;
|
|
64
|
-
private readonly actor;
|
|
65
|
-
constructor(store: FileStore, actor: Actor);
|
|
66
|
-
private open;
|
|
67
|
-
private read;
|
|
68
|
-
private write;
|
|
69
|
-
threads(filter?: ListFilter): Thread[];
|
|
70
|
-
thread(id: string): Thread;
|
|
71
|
-
comment(input: {
|
|
72
|
-
anchor: AnchorSpec;
|
|
73
|
-
body: string;
|
|
74
|
-
mentions?: string[];
|
|
75
|
-
}): Thread;
|
|
76
|
-
reply(threadId: string, input: {
|
|
77
|
-
body: string;
|
|
78
|
-
mentions?: string[];
|
|
79
|
-
}): Message;
|
|
80
|
-
resolve(threadId: string): Thread;
|
|
81
|
-
reopen(threadId: string): Thread;
|
|
82
|
-
relocate(threadId: string): Thread;
|
|
83
|
-
suggestions(filter?: ListFilter): Suggestion[];
|
|
84
|
-
suggest(input: {
|
|
85
|
-
anchor: AnchorSpec;
|
|
86
|
-
change: unknown;
|
|
87
|
-
summary?: string;
|
|
88
|
-
}): Suggestion;
|
|
89
|
-
accept(suggestionId: string): Suggestion;
|
|
90
|
-
reject(suggestionId: string): Suggestion;
|
|
91
|
-
/** The content text of the store. */
|
|
92
|
-
text(): string;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
39
|
/** The root Y.Text that a local store keeps its content in. */
|
|
96
40
|
declare const TEXT_ROOT = "text";
|
|
97
41
|
/** The root Y.Map that holds values for cell, slide, canvas, and custom anchors. */
|
|
@@ -109,4 +53,4 @@ declare const localHost: SuggestionHost;
|
|
|
109
53
|
*/
|
|
110
54
|
declare function operatorActor(as: string | undefined, scopes: string | undefined): Actor;
|
|
111
55
|
|
|
112
|
-
export {
|
|
56
|
+
export { FileStore, type FileStoreOptions, MAX_FILE, TEXT_ROOT, VALUES_ROOT, localHost, lockTimeoutFrom, operatorActor };
|
package/dist/local.js
CHANGED
|
@@ -3,14 +3,14 @@ import {
|
|
|
3
3
|
MAX_FILE,
|
|
4
4
|
lockTimeoutFrom,
|
|
5
5
|
operatorActor
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-YU5U4P22.js";
|
|
7
7
|
import {
|
|
8
8
|
CommentsService,
|
|
9
9
|
TEXT_ROOT,
|
|
10
10
|
VALUES_ROOT,
|
|
11
11
|
localHost
|
|
12
|
-
} from "./chunk-
|
|
13
|
-
import "./chunk-
|
|
12
|
+
} from "./chunk-IB6ANO32.js";
|
|
13
|
+
import "./chunk-GSAJOCDV.js";
|
|
14
14
|
export {
|
|
15
15
|
CommentsService,
|
|
16
16
|
FileStore,
|
package/dist/mcp-bin.js
CHANGED
|
@@ -3,19 +3,38 @@ import {
|
|
|
3
3
|
FileStore,
|
|
4
4
|
lockTimeoutFrom,
|
|
5
5
|
operatorActor
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-YU5U4P22.js";
|
|
7
7
|
import {
|
|
8
8
|
startMcp
|
|
9
|
-
} from "./chunk-
|
|
10
|
-
import
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
} from "./chunk-TTTX3R6M.js";
|
|
10
|
+
import {
|
|
11
|
+
VERSION
|
|
12
|
+
} from "./chunk-GMA5I5L6.js";
|
|
13
|
+
import "./chunk-IB6ANO32.js";
|
|
14
|
+
import "./chunk-GSAJOCDV.js";
|
|
13
15
|
|
|
14
16
|
// src/mcp-bin.ts
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
var MCP_HELP = `agentic-comments-mcp ${VERSION}
|
|
18
|
+
|
|
19
|
+
Start the MCP server on stdio. Set the principal and scopes in the environment:
|
|
20
|
+
|
|
21
|
+
AGENTIC_COMMENTS_AS=kind:name Required. For example agent_of_human:scribe.
|
|
22
|
+
AGENTIC_COMMENTS_SCOPES=a,b Optional. A comma list, such as comments:accept.
|
|
23
|
+
AGENTIC_COMMENTS_FILE=./comments.ydoc The store file. The default is ./comments.ydoc.
|
|
24
|
+
AGENTIC_COMMENTS_LOCK_TIMEOUT_MS=5000 How long a write waits for another process.
|
|
25
|
+
|
|
26
|
+
Usage: agentic-comments-mcp [--help | --version]`;
|
|
27
|
+
var args = process.argv.slice(2);
|
|
28
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
29
|
+
console.log(MCP_HELP);
|
|
30
|
+
} else if (args.includes("--version") || args.includes("-v")) {
|
|
31
|
+
console.log(VERSION);
|
|
32
|
+
} else {
|
|
33
|
+
try {
|
|
34
|
+
const env = process.env;
|
|
35
|
+
await startMcp(new FileStore(env.AGENTIC_COMMENTS_FILE ?? "./comments.ydoc", { lockTimeoutMs: lockTimeoutFrom(env) }), operatorActor(env.AGENTIC_COMMENTS_AS, env.AGENTIC_COMMENTS_SCOPES));
|
|
36
|
+
} catch (error) {
|
|
37
|
+
console.error(JSON.stringify({ error: { code: error.code ?? "INTERNAL", message: error instanceof Error ? error.message : String(error) } }));
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
21
40
|
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import * as Y from 'yjs';
|
|
2
|
+
import { A as Actor, T as Thread, M as Message, S as Suggestion } from './suggestions-D4ZJFzzR.js';
|
|
3
|
+
|
|
4
|
+
/** Where a comment or suggestion points, in terms a person or agent can write down. */
|
|
5
|
+
type AnchorSpec = {
|
|
6
|
+
type: "text";
|
|
7
|
+
quote: string;
|
|
8
|
+
occurrence?: number;
|
|
9
|
+
} | {
|
|
10
|
+
type: "cell";
|
|
11
|
+
key: string;
|
|
12
|
+
} | {
|
|
13
|
+
type: "slide";
|
|
14
|
+
elementId: string;
|
|
15
|
+
} | {
|
|
16
|
+
type: "canvas";
|
|
17
|
+
elementId: string;
|
|
18
|
+
};
|
|
19
|
+
/** Where a service reads and writes its document: a file store, or a document the host loaded. */
|
|
20
|
+
interface YStore {
|
|
21
|
+
read<T>(fn: (ydoc: Y.Doc) => T): T;
|
|
22
|
+
write<T>(fn: (ydoc: Y.Doc) => T): T;
|
|
23
|
+
}
|
|
24
|
+
interface ListFilter {
|
|
25
|
+
status?: string;
|
|
26
|
+
anchorState?: string;
|
|
27
|
+
mentioning?: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Every action the CLI and MCP server offer, on a local file store. Each call takes the actor the operator
|
|
31
|
+
* stamped, and no input carries an author, so both faces return the same shapes and obey the same rules.
|
|
32
|
+
*/
|
|
33
|
+
declare class CommentsService {
|
|
34
|
+
private readonly store;
|
|
35
|
+
private readonly actor;
|
|
36
|
+
constructor(store: YStore, actor: Actor);
|
|
37
|
+
private open;
|
|
38
|
+
private read;
|
|
39
|
+
private write;
|
|
40
|
+
threads(filter?: ListFilter): Thread[];
|
|
41
|
+
thread(id: string): Thread;
|
|
42
|
+
comment(input: {
|
|
43
|
+
anchor: AnchorSpec;
|
|
44
|
+
body: string;
|
|
45
|
+
mentions?: string[];
|
|
46
|
+
}): Thread;
|
|
47
|
+
reply(threadId: string, input: {
|
|
48
|
+
body: string;
|
|
49
|
+
mentions?: string[];
|
|
50
|
+
}): Message;
|
|
51
|
+
resolve(threadId: string): Thread;
|
|
52
|
+
reopen(threadId: string): Thread;
|
|
53
|
+
relocate(threadId: string): Thread;
|
|
54
|
+
suggestions(filter?: ListFilter): Suggestion[];
|
|
55
|
+
suggest(input: {
|
|
56
|
+
anchor: AnchorSpec;
|
|
57
|
+
change: unknown;
|
|
58
|
+
summary?: string;
|
|
59
|
+
}): Suggestion;
|
|
60
|
+
accept(suggestionId: string): Suggestion;
|
|
61
|
+
reject(suggestionId: string): Suggestion;
|
|
62
|
+
/** The content text of the store. */
|
|
63
|
+
text(): string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export { type AnchorSpec as A, CommentsService as C, type ListFilter as L };
|
|
@@ -331,4 +331,4 @@ declare class Suggestions {
|
|
|
331
331
|
settle(): void;
|
|
332
332
|
}
|
|
333
333
|
|
|
334
|
-
export {
|
|
334
|
+
export { Actor as A, Comments as C, type Message as M, type NewReply as N, PRINCIPAL_KINDS as P, type RelativePositionJson as R, type Suggestion as S, type Thread as T, type Author as a, Suggestions as b, ACCEPT_SCOPE as c, type Anchor as d, type AnchorKind as e, type AnchorState as f, type ApplyContext as g, type CanvasAnchor as h, type CellAnchor as i, type CommentsOptions as j, type NewSuggestion as k, type NewThread as l, type PrincipalKind as m, type RevertContext as n, type SlideAnchor as o, type StampInput as p, type SuggestionFilter as q, type SuggestionHost as r, type TextAnchor as s, type ThreadFilter as t, authorLabel as u, stamp as v, textAnchor as w, textOf as x };
|
package/dist/worker.d.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
import { Auth, Store, PrincipalContext, Region } from 'agentic-identity';
|
|
3
|
+
import { D1Like } from 'agentic-identity/cloudflare';
|
|
4
|
+
import { A as Actor } from './suggestions-D4ZJFzzR.js';
|
|
5
|
+
import { C as CommentsService } from './service-BmMQMT_8.js';
|
|
6
|
+
import 'yjs';
|
|
7
|
+
|
|
8
|
+
interface WorkerEnv {
|
|
9
|
+
/** Where the data lives: "eu" or "us". It is a claim you make at deploy time, with no default. "unknown" needs `AGENTIC_COMMENTS_UNSAFE_REGION=1` too. */
|
|
10
|
+
AGENTIC_COMMENTS_REGION?: string;
|
|
11
|
+
/** Set to "1" to accept the region "unknown" under `wrangler dev`. Never set it in production. */
|
|
12
|
+
AGENTIC_COMMENTS_UNSAFE_REGION?: string;
|
|
13
|
+
/** D1 database: each document, with its threads, as a Yjs update. */
|
|
14
|
+
DB?: D1Like;
|
|
15
|
+
/**
|
|
16
|
+
* JSON object: bearer token (32 bytes or more) to `kind:name`, or to `{ "as": "kind:name", "scopes": ["comments:write"] }`.
|
|
17
|
+
* Set it as a secret. A short token or a bad entry refuses the whole config.
|
|
18
|
+
*/
|
|
19
|
+
AGENTIC_COMMENTS_TOKENS?: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* An `Auth` over a table of bearer tokens. One bad entry (a token under 32 bytes, a value that is not a valid principal)
|
|
23
|
+
* refuses the whole table, so a typo never leaves a weak token live. The compare visits every entry.
|
|
24
|
+
*/
|
|
25
|
+
declare function createTokenAuth(raw: string | undefined): Auth;
|
|
26
|
+
/** The documents, on the `Store` port. Each write loads the document, runs the change, and saves in one transaction. */
|
|
27
|
+
declare class CommentsDocs {
|
|
28
|
+
private readonly store;
|
|
29
|
+
constructor(store: Store);
|
|
30
|
+
private id;
|
|
31
|
+
private load;
|
|
32
|
+
private open;
|
|
33
|
+
create(id: string, text: string, actor: Actor): Promise<void>;
|
|
34
|
+
read<T>(id: string, actor: Actor, run: (service: CommentsService) => T): Promise<T>;
|
|
35
|
+
write<T>(id: string, actor: Actor, run: (service: CommentsService) => T): Promise<T>;
|
|
36
|
+
}
|
|
37
|
+
/** The docs, or a function that makes them from the Worker bindings. */
|
|
38
|
+
type DocsSource = CommentsDocs | ((env: WorkerEnv) => Promise<CommentsDocs>);
|
|
39
|
+
interface AppOptions {
|
|
40
|
+
/** The credential check. The default reads `AGENTIC_COMMENTS_TOKENS`. Pass any `Auth` from agentic-identity to replace it. */
|
|
41
|
+
auth?: Auth | ((env: WorkerEnv) => Auth);
|
|
42
|
+
}
|
|
43
|
+
type AppEnv = {
|
|
44
|
+
Bindings: WorkerEnv;
|
|
45
|
+
Variables: {
|
|
46
|
+
context: PrincipalContext;
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
declare function createApp(source: DocsSource, options?: AppOptions): Hono<AppEnv>;
|
|
50
|
+
/** The region claim, or a MISCONFIGURED error. */
|
|
51
|
+
declare function regionOf(env: WorkerEnv): Region;
|
|
52
|
+
/** The documents over D1. */
|
|
53
|
+
declare function docsFor(env: WorkerEnv): Promise<CommentsDocs>;
|
|
54
|
+
declare const _default: {
|
|
55
|
+
fetch: (request: Request, env: WorkerEnv, ctx: unknown) => Response | Promise<Response>;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export { type AppOptions, CommentsDocs, type DocsSource, type WorkerEnv, createApp, createTokenAuth, _default as default, docsFor, regionOf };
|
package/dist/worker.js
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CommentsService,
|
|
3
|
+
TEXT_ROOT
|
|
4
|
+
} from "./chunk-IB6ANO32.js";
|
|
5
|
+
import {
|
|
6
|
+
ACCEPT_SCOPE,
|
|
7
|
+
CommentsError,
|
|
8
|
+
PRINCIPAL_KINDS,
|
|
9
|
+
exact,
|
|
10
|
+
invalid,
|
|
11
|
+
stamp
|
|
12
|
+
} from "./chunk-GSAJOCDV.js";
|
|
13
|
+
|
|
14
|
+
// src/worker.ts
|
|
15
|
+
import { Hono } from "hono";
|
|
16
|
+
import { StoreConflictError, assertRegion, isScope, parseRegion, hasScope } from "agentic-identity";
|
|
17
|
+
import { D1_SCHEMA, createD1Store } from "agentic-identity/cloudflare";
|
|
18
|
+
import * as Y from "yjs";
|
|
19
|
+
var READ = "comments:read";
|
|
20
|
+
var COMMENT = "comments:comment";
|
|
21
|
+
var WRITE = "comments:write";
|
|
22
|
+
var ADMIN = "comments:admin";
|
|
23
|
+
var DEFAULT_SCOPES = [COMMENT];
|
|
24
|
+
var MIN_TOKEN_BYTES = 32;
|
|
25
|
+
var warned = /* @__PURE__ */ new Set();
|
|
26
|
+
function refuse(reason) {
|
|
27
|
+
if (!warned.has(reason)) {
|
|
28
|
+
warned.add(reason);
|
|
29
|
+
console.error(`AGENTIC_COMMENTS_TOKENS is refused: ${reason}`);
|
|
30
|
+
}
|
|
31
|
+
return void 0;
|
|
32
|
+
}
|
|
33
|
+
function same(a, b) {
|
|
34
|
+
let diff = a.length ^ b.length;
|
|
35
|
+
for (let i = 0; i < Math.max(a.length, b.length); i++) diff |= (a.charCodeAt(i) || 0) ^ (b.charCodeAt(i) || 0);
|
|
36
|
+
return diff === 0;
|
|
37
|
+
}
|
|
38
|
+
function contextOf(entry) {
|
|
39
|
+
const object = typeof entry === "object" && entry !== null && !Array.isArray(entry) ? entry : void 0;
|
|
40
|
+
const who = typeof entry === "string" ? entry : object?.as;
|
|
41
|
+
const cut = typeof who === "string" ? who.indexOf(":") : -1;
|
|
42
|
+
if (typeof who !== "string" || cut < 1 || cut === who.length - 1) return void 0;
|
|
43
|
+
const kind = who.slice(0, cut);
|
|
44
|
+
const name = who.slice(cut + 1);
|
|
45
|
+
if (!PRINCIPAL_KINDS.includes(kind)) return void 0;
|
|
46
|
+
const given = object?.scopes;
|
|
47
|
+
if (given !== void 0 && !(Array.isArray(given) && given.every(isScope))) return void 0;
|
|
48
|
+
try {
|
|
49
|
+
stamp({ kind, name });
|
|
50
|
+
} catch {
|
|
51
|
+
return void 0;
|
|
52
|
+
}
|
|
53
|
+
return { principalId: name, kind, chain: [{ id: name, kind }], scopes: given ?? [...DEFAULT_SCOPES] };
|
|
54
|
+
}
|
|
55
|
+
function createTokenAuth(raw) {
|
|
56
|
+
let table;
|
|
57
|
+
try {
|
|
58
|
+
const parsed = JSON.parse(raw ?? "{}");
|
|
59
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return { authenticate: async () => (refuse("not a JSON object"), null) };
|
|
60
|
+
table = /* @__PURE__ */ new Map();
|
|
61
|
+
for (const [token, entry] of Object.entries(parsed)) {
|
|
62
|
+
const context = contextOf(entry);
|
|
63
|
+
if (new TextEncoder().encode(token).length < MIN_TOKEN_BYTES) throw new Error(`a token is shorter than ${MIN_TOKEN_BYTES} bytes`);
|
|
64
|
+
if (!context) throw new Error("an entry is not a valid kind:name or { as, scopes }");
|
|
65
|
+
table.set(token, context);
|
|
66
|
+
}
|
|
67
|
+
} catch (error) {
|
|
68
|
+
table = void 0;
|
|
69
|
+
refuse(error instanceof SyntaxError ? "not valid JSON" : error.message);
|
|
70
|
+
}
|
|
71
|
+
const known = table;
|
|
72
|
+
return {
|
|
73
|
+
async authenticate(request) {
|
|
74
|
+
const bearer = /^Bearer (.+)$/.exec(request.headers.get("authorization") ?? "")?.[1];
|
|
75
|
+
if (bearer === void 0 || !known) return null;
|
|
76
|
+
let found = null;
|
|
77
|
+
for (const [token, context] of known) if (same(token, bearer)) found = context;
|
|
78
|
+
return found;
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function actorOf(context) {
|
|
83
|
+
const scopes = hasScope(context.scopes, ADMIN) ? [ACCEPT_SCOPE] : [];
|
|
84
|
+
return stamp({ kind: context.kind, name: context.principalId, scopes });
|
|
85
|
+
}
|
|
86
|
+
var DOCS = "comments_docs";
|
|
87
|
+
var DOC_ID = /^[a-z0-9][a-z0-9_-]{0,63}$/;
|
|
88
|
+
var MAX_STATE = 6e5;
|
|
89
|
+
var MAX_BODY = 256 * 1024;
|
|
90
|
+
var toBase64 = (bytes) => {
|
|
91
|
+
let out = "";
|
|
92
|
+
for (let i = 0; i < bytes.length; i += 32768) out += String.fromCharCode(...bytes.subarray(i, i + 32768));
|
|
93
|
+
return btoa(out);
|
|
94
|
+
};
|
|
95
|
+
var fromBase64 = (text) => Uint8Array.from(atob(text), (ch) => ch.charCodeAt(0));
|
|
96
|
+
var CommentsDocs = class {
|
|
97
|
+
constructor(store) {
|
|
98
|
+
this.store = store;
|
|
99
|
+
}
|
|
100
|
+
store;
|
|
101
|
+
id(id) {
|
|
102
|
+
if (!DOC_ID.test(id)) invalid(`invalid document id "${id}": use 1 to 64 of a-z, 0-9, "-" and "_"`);
|
|
103
|
+
return id;
|
|
104
|
+
}
|
|
105
|
+
load(row, id) {
|
|
106
|
+
if (row === null) throw new CommentsError("NO_DOCUMENT", `no document with id "${id}"`);
|
|
107
|
+
const ydoc = new Y.Doc();
|
|
108
|
+
try {
|
|
109
|
+
if (typeof row.state !== "string") throw new Error("no state");
|
|
110
|
+
Y.applyUpdate(ydoc, fromBase64(row.state));
|
|
111
|
+
} catch {
|
|
112
|
+
throw new CommentsError("CORRUPT", `document "${id}" is damaged`);
|
|
113
|
+
}
|
|
114
|
+
return ydoc;
|
|
115
|
+
}
|
|
116
|
+
open(ydoc, actor) {
|
|
117
|
+
const fixed = { read: (fn) => fn(ydoc), write: (fn) => fn(ydoc) };
|
|
118
|
+
return new CommentsService(fixed, actor);
|
|
119
|
+
}
|
|
120
|
+
async create(id, text, actor) {
|
|
121
|
+
this.id(id);
|
|
122
|
+
const ydoc = new Y.Doc();
|
|
123
|
+
ydoc.getText(TEXT_ROOT).insert(0, text);
|
|
124
|
+
const state = Y.encodeStateAsUpdate(ydoc);
|
|
125
|
+
if (state.length > MAX_STATE) throw new CommentsError("STORE_FULL", `the text is too large for one document (${MAX_STATE} bytes of state)`);
|
|
126
|
+
await this.store.transaction(async (tx) => {
|
|
127
|
+
if (await tx.get(DOCS, id) !== null) throw new CommentsError("DOCUMENT_EXISTS", `document "${id}" already exists`);
|
|
128
|
+
await tx.put(DOCS, id, { state: toBase64(state), createdBy: { kind: actor.kind, name: actor.name }, createdAt: Date.now() });
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
async read(id, actor, run) {
|
|
132
|
+
return run(this.open(this.load(await this.store.get(DOCS, this.id(id)), id), actor));
|
|
133
|
+
}
|
|
134
|
+
async write(id, actor, run) {
|
|
135
|
+
this.id(id);
|
|
136
|
+
return this.store.transaction(async (tx) => {
|
|
137
|
+
const row = await tx.get(DOCS, id);
|
|
138
|
+
const ydoc = this.load(row, id);
|
|
139
|
+
const result = run(this.open(ydoc, actor));
|
|
140
|
+
const state = Y.encodeStateAsUpdate(ydoc);
|
|
141
|
+
if (state.length > MAX_STATE) throw new CommentsError("STORE_FULL", `document "${id}" would pass ${MAX_STATE} bytes of state`);
|
|
142
|
+
await tx.put(DOCS, id, { state: toBase64(state), createdBy: row?.createdBy ?? null, createdAt: row?.createdAt ?? null });
|
|
143
|
+
return result;
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
var STATUS = {
|
|
148
|
+
INVALID_AUTHOR: 400,
|
|
149
|
+
INVALID_INPUT: 400,
|
|
150
|
+
INVALID_ANCHOR: 400,
|
|
151
|
+
NO_THREAD: 404,
|
|
152
|
+
NO_SUGGESTION: 404,
|
|
153
|
+
NO_DOCUMENT: 404,
|
|
154
|
+
THREAD_RESOLVED: 409,
|
|
155
|
+
ALREADY_DECIDED: 409,
|
|
156
|
+
DOCUMENT_EXISTS: 409,
|
|
157
|
+
CONFLICT: 409,
|
|
158
|
+
APPLY_FAILED: 409,
|
|
159
|
+
RELOCATE_FAILED: 409,
|
|
160
|
+
FORBIDDEN: 403,
|
|
161
|
+
STORE_FULL: 413,
|
|
162
|
+
CORRUPT: 500,
|
|
163
|
+
MISCONFIGURED: 500
|
|
164
|
+
};
|
|
165
|
+
var fail = (code, message, status) => Response.json({ error: { code, message } }, { status });
|
|
166
|
+
async function body(c) {
|
|
167
|
+
if (Number(c.req.header("content-length") ?? 0) > MAX_BODY) invalid(`the body is over ${MAX_BODY} bytes`);
|
|
168
|
+
const text = await c.req.text();
|
|
169
|
+
if (text.length > MAX_BODY) invalid(`the body is over ${MAX_BODY} bytes`);
|
|
170
|
+
let parsed;
|
|
171
|
+
try {
|
|
172
|
+
parsed = JSON.parse(text);
|
|
173
|
+
} catch {
|
|
174
|
+
parsed = void 0;
|
|
175
|
+
}
|
|
176
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) invalid("the body must be a JSON object");
|
|
177
|
+
return parsed;
|
|
178
|
+
}
|
|
179
|
+
function createApp(source, options = {}) {
|
|
180
|
+
const app2 = new Hono();
|
|
181
|
+
const docsOf = (c) => typeof source === "function" ? source(c.env ?? {}) : Promise.resolve(source);
|
|
182
|
+
app2.use("*", async (c, next) => {
|
|
183
|
+
await next();
|
|
184
|
+
c.header("x-content-type-options", "nosniff");
|
|
185
|
+
});
|
|
186
|
+
app2.get("/health", (c) => c.json({ ok: true, pack: "agentic-comments" }));
|
|
187
|
+
app2.use("/api/v1/*", async (c, next) => {
|
|
188
|
+
const auth = typeof options.auth === "function" ? options.auth(c.env ?? {}) : options.auth ?? createTokenAuth(c.env?.AGENTIC_COMMENTS_TOKENS);
|
|
189
|
+
const context = await auth.authenticate(c.req.raw);
|
|
190
|
+
if (!context) return fail("UNAUTHENTICATED", "send a valid bearer token", 401);
|
|
191
|
+
c.set("context", context);
|
|
192
|
+
await next();
|
|
193
|
+
});
|
|
194
|
+
app2.get("/api/v1/me", (c) => {
|
|
195
|
+
const { principalId, kind, scopes } = c.get("context");
|
|
196
|
+
return c.json({ kind, name: principalId, scopes });
|
|
197
|
+
});
|
|
198
|
+
const act = (c, need, write, run) => (async () => {
|
|
199
|
+
const context = c.get("context");
|
|
200
|
+
if (!hasScope(context.scopes, need)) throw new CommentsError("FORBIDDEN", `this token needs the ${need} scope`);
|
|
201
|
+
const docs = await docsOf(c);
|
|
202
|
+
const actor = actorOf(context);
|
|
203
|
+
const id = c.req.param("doc") ?? "";
|
|
204
|
+
return c.json(await (write ? docs.write(id, actor, run) : docs.read(id, actor, run)));
|
|
205
|
+
})();
|
|
206
|
+
const doc = "/api/v1/docs/:doc";
|
|
207
|
+
app2.post("/api/v1/docs", async (c) => {
|
|
208
|
+
const given = exact(await body(c), ["id", "text"], "a document");
|
|
209
|
+
if (typeof given.id !== "string") invalid("a document needs an id");
|
|
210
|
+
if (given.text !== void 0 && typeof given.text !== "string") invalid("text must be a string");
|
|
211
|
+
const context = c.get("context");
|
|
212
|
+
if (!hasScope(context.scopes, WRITE)) throw new CommentsError("FORBIDDEN", `this token needs the ${WRITE} scope`);
|
|
213
|
+
await (await docsOf(c)).create(given.id, given.text ?? "", actorOf(context));
|
|
214
|
+
return c.json({ id: given.id }, 201);
|
|
215
|
+
});
|
|
216
|
+
app2.get(doc, (c) => act(c, READ, false, (s) => ({ id: c.req.param("doc"), text: s.text() })));
|
|
217
|
+
app2.get(`${doc}/threads`, (c) => act(c, READ, false, (s) => s.threads(c.req.query())));
|
|
218
|
+
app2.get(`${doc}/threads/:id`, (c) => act(c, READ, false, (s) => s.thread(c.req.param("id"))));
|
|
219
|
+
app2.post(`${doc}/threads`, async (c) => {
|
|
220
|
+
const input = await body(c);
|
|
221
|
+
return act(c, COMMENT, true, (s) => s.comment(input));
|
|
222
|
+
});
|
|
223
|
+
app2.post(`${doc}/threads/:id/replies`, async (c) => {
|
|
224
|
+
const input = await body(c);
|
|
225
|
+
return act(c, COMMENT, true, (s) => s.reply(c.req.param("id"), input));
|
|
226
|
+
});
|
|
227
|
+
for (const action of ["resolve", "reopen"]) {
|
|
228
|
+
app2.post(`${doc}/threads/:id/${action}`, (c) => act(c, COMMENT, true, (s) => s[action](c.req.param("id"))));
|
|
229
|
+
}
|
|
230
|
+
app2.post(`${doc}/threads/:id/relocate`, (c) => act(c, WRITE, true, (s) => s.relocate(c.req.param("id"))));
|
|
231
|
+
app2.get(`${doc}/suggestions`, (c) => act(c, READ, false, (s) => s.suggestions(c.req.query())));
|
|
232
|
+
app2.post(`${doc}/suggestions`, async (c) => {
|
|
233
|
+
const input = await body(c);
|
|
234
|
+
return act(c, COMMENT, true, (s) => s.suggest(input));
|
|
235
|
+
});
|
|
236
|
+
for (const action of ["accept", "reject"]) {
|
|
237
|
+
app2.post(`${doc}/suggestions/:id/${action}`, (c) => act(c, WRITE, true, (s) => s[action](c.req.param("id"))));
|
|
238
|
+
}
|
|
239
|
+
app2.notFound(() => fail("NOT_FOUND", "no such route", 404));
|
|
240
|
+
app2.onError((error) => {
|
|
241
|
+
if (error instanceof CommentsError) return fail(error.code, error.message, STATUS[error.code] ?? 400);
|
|
242
|
+
if (error instanceof StoreConflictError) return fail("CONFLICT", "the document kept changing; try again", 409);
|
|
243
|
+
console.error(error instanceof Error ? error.name : "error");
|
|
244
|
+
return fail("INTERNAL", "the server could not finish the request", 500);
|
|
245
|
+
});
|
|
246
|
+
return app2;
|
|
247
|
+
}
|
|
248
|
+
var schemas = /* @__PURE__ */ new WeakMap();
|
|
249
|
+
function regionOf(env) {
|
|
250
|
+
let region;
|
|
251
|
+
try {
|
|
252
|
+
region = parseRegion(env.AGENTIC_COMMENTS_REGION);
|
|
253
|
+
} catch {
|
|
254
|
+
throw new CommentsError("MISCONFIGURED", 'set AGENTIC_COMMENTS_REGION to "eu" or "us" ("unknown" only for local development)');
|
|
255
|
+
}
|
|
256
|
+
if (region === "unknown" && env.AGENTIC_COMMENTS_UNSAFE_REGION !== "1") {
|
|
257
|
+
throw new CommentsError("MISCONFIGURED", 'the region "unknown" turns the residency check off; set AGENTIC_COMMENTS_UNSAFE_REGION=1 to accept that for local development');
|
|
258
|
+
}
|
|
259
|
+
return region;
|
|
260
|
+
}
|
|
261
|
+
async function docsFor(env) {
|
|
262
|
+
if (!env.DB) throw new CommentsError("MISCONFIGURED", "bind a D1 database as DB");
|
|
263
|
+
const region = regionOf(env);
|
|
264
|
+
const db = env.DB;
|
|
265
|
+
let ready = schemas.get(db);
|
|
266
|
+
if (!ready) {
|
|
267
|
+
ready = db.prepare(D1_SCHEMA).run().catch((error) => {
|
|
268
|
+
schemas.delete(db);
|
|
269
|
+
throw error;
|
|
270
|
+
});
|
|
271
|
+
schemas.set(db, ready);
|
|
272
|
+
}
|
|
273
|
+
await ready;
|
|
274
|
+
const store = createD1Store(db, { region });
|
|
275
|
+
assertRegion(region, store);
|
|
276
|
+
return new CommentsDocs(store);
|
|
277
|
+
}
|
|
278
|
+
var app = createApp(docsFor);
|
|
279
|
+
var worker_default = { fetch: (request, env, ctx) => app.fetch(request, env, ctx) };
|
|
280
|
+
export {
|
|
281
|
+
CommentsDocs,
|
|
282
|
+
createApp,
|
|
283
|
+
createTokenAuth,
|
|
284
|
+
worker_default as default,
|
|
285
|
+
docsFor,
|
|
286
|
+
regionOf
|
|
287
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentic-comments",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Comment threads and suggestions anchored to any content, from people and agents.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"comments",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"type": "module",
|
|
27
27
|
"private": false,
|
|
28
28
|
"engines": {
|
|
29
|
-
"node": ">=
|
|
29
|
+
"node": ">=22"
|
|
30
30
|
},
|
|
31
31
|
"files": [
|
|
32
32
|
"dist",
|
|
@@ -47,6 +47,10 @@
|
|
|
47
47
|
"./local": {
|
|
48
48
|
"types": "./dist/local.d.ts",
|
|
49
49
|
"import": "./dist/local.js"
|
|
50
|
+
},
|
|
51
|
+
"./worker": {
|
|
52
|
+
"types": "./dist/worker.d.ts",
|
|
53
|
+
"import": "./dist/worker.js"
|
|
50
54
|
}
|
|
51
55
|
},
|
|
52
56
|
"bin": {
|
|
@@ -57,12 +61,15 @@
|
|
|
57
61
|
"build": "tsup",
|
|
58
62
|
"typecheck": "tsc --noEmit",
|
|
59
63
|
"test": "vitest run",
|
|
64
|
+
"deploy:demo": "npm run build && WRANGLER_SEND_METRICS=false wrangler deploy --config wrangler.demo.jsonc",
|
|
60
65
|
"clean": "rm -rf dist",
|
|
61
66
|
"prepack": "npm run build",
|
|
62
67
|
"ci:local": "npm run build && npm run typecheck && npm test"
|
|
63
68
|
},
|
|
64
69
|
"dependencies": {
|
|
65
70
|
"@modelcontextprotocol/sdk": "^1.32.2",
|
|
71
|
+
"agentic-identity": "^0.4.0",
|
|
72
|
+
"hono": "^4.13.14",
|
|
66
73
|
"zod": "^3.25.76"
|
|
67
74
|
},
|
|
68
75
|
"peerDependencies": {
|
|
@@ -73,6 +80,7 @@
|
|
|
73
80
|
"tsup": "^8.0.0",
|
|
74
81
|
"typescript": "^5.6.0",
|
|
75
82
|
"vitest": "^3.0.0",
|
|
83
|
+
"wrangler": "^4.155.0",
|
|
76
84
|
"yjs": "^13.6.0"
|
|
77
85
|
}
|
|
78
86
|
}
|