annotate-anything 0.0.0-stage → 0.1.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.
Files changed (40) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +239 -2
  3. package/agent/INSTRUCTIONS.md +70 -0
  4. package/dist/annotate-anything.iife.js +164 -0
  5. package/dist/cli.js +785 -0
  6. package/dist/client-file-B1I-YibZ.js +12 -0
  7. package/dist/index.js +1 -0
  8. package/dist/local-W-uyrETY.js +4 -0
  9. package/dist/middleware.js +18 -0
  10. package/dist/next-client.js +19 -0
  11. package/dist/next.js +49 -0
  12. package/dist/node-SB91dcoI.js +614 -0
  13. package/dist/react.js +13 -0
  14. package/dist/src-DTByJ6QG.js +2116 -0
  15. package/dist/types/anchor.d.ts +36 -0
  16. package/dist/types/annotator.d.ts +173 -0
  17. package/dist/types/auto.d.ts +1 -0
  18. package/dist/types/cli.d.ts +1 -0
  19. package/dist/types/icons.d.ts +13 -0
  20. package/dist/types/index.d.ts +19 -0
  21. package/dist/types/local.d.ts +2 -0
  22. package/dist/types/mcp.d.ts +2 -0
  23. package/dist/types/next-client.d.ts +4 -0
  24. package/dist/types/next.d.ts +10 -0
  25. package/dist/types/react.d.ts +4 -0
  26. package/dist/types/scheme.d.ts +2 -0
  27. package/dist/types/server/client-file.d.ts +1 -0
  28. package/dist/types/server/core.d.ts +57 -0
  29. package/dist/types/server/locate.d.ts +2 -0
  30. package/dist/types/server/middleware.d.ts +8 -0
  31. package/dist/types/server/node.d.ts +4 -0
  32. package/dist/types/server/serve.d.ts +9 -0
  33. package/dist/types/server/store.d.ts +43 -0
  34. package/dist/types/server/watcher.d.ts +21 -0
  35. package/dist/types/styles.d.ts +1 -0
  36. package/dist/types/transport.d.ts +23 -0
  37. package/dist/types/types.d.ts +75 -0
  38. package/dist/types/vite.d.ts +20 -0
  39. package/dist/vite.js +108 -0
  40. package/package.json +86 -4
@@ -0,0 +1,614 @@
1
+ import { n as LOOPBACK_HOST, t as LOCAL_HOST } from "./local-W-uyrETY.js";
2
+ import path from "node:path";
3
+ import fs from "node:fs";
4
+ import crypto from "node:crypto";
5
+ // src/types.ts
6
+ var LIFECYCLE = [
7
+ "status",
8
+ "sentAt",
9
+ "pickedUpAt",
10
+ "reply",
11
+ "resolvedAt"
12
+ ];
13
+ // src/server/store.ts
14
+ var KINDS = [
15
+ "pin",
16
+ "element",
17
+ "area"
18
+ ];
19
+ var STATUSES = [
20
+ "open",
21
+ "sent",
22
+ "working",
23
+ "resolved"
24
+ ];
25
+ var ID = /^[\w.-]{1,100}$/;
26
+ var MAX_NOTES = 1e3;
27
+ var LimitError = class extends Error {};
28
+ var str = (v, max) => typeof v === "string" ? v.slice(0, max) : "";
29
+ var line = (v, max) => str(v, max).replace(/[\u0000-\u001f\u007f-\u009f\u2028\u2029]+/g, " ").trim();
30
+ var prose = (v, max) => str(v, max).replace(/[\u0000-\u0008\u000b-\u001f\u007f-\u009f\u2028\u2029]/g, "");
31
+ var num = (v) => typeof v === "number" && Number.isFinite(v) ? Math.round(v * 10) / 10 : 0;
32
+ var iso = (v) => typeof v === "string" && !Number.isNaN(Date.parse(v)) ? v.slice(0, 40) : void 0;
33
+ function box(v) {
34
+ const b = v && typeof v === "object" ? v : {};
35
+ return {
36
+ x: num(b.x),
37
+ y: num(b.y),
38
+ width: num(b.width),
39
+ height: num(b.height)
40
+ };
41
+ }
42
+ function cleanNote(raw) {
43
+ if (!raw || typeof raw !== "object") return null;
44
+ const r = raw;
45
+ if (typeof r.id !== "string" || !ID.test(r.id)) return null;
46
+ const kind = KINDS.includes(r.kind) ? r.kind : null;
47
+ const comment = prose(r.comment, 2e4).trim();
48
+ if (!kind || !comment) return null;
49
+ let context = {};
50
+ if (r.context && typeof r.context === "object" && !Array.isArray(r.context) && JSON.stringify(r.context).length <= 4e3) context = r.context;
51
+ const scroll = r.scroll ?? {};
52
+ const viewport = r.viewport ?? {};
53
+ const note = {
54
+ id: r.id,
55
+ n: Number.isInteger(r.n) && r.n > 0 ? Math.min(r.n, 1e6) : 0,
56
+ kind,
57
+ route: line(r.route, 500) || "/",
58
+ comment,
59
+ createdAt: iso(r.createdAt) ?? (/* @__PURE__ */ new Date()).toISOString(),
60
+ updatedAt: iso(r.updatedAt) ?? (/* @__PURE__ */ new Date()).toISOString(),
61
+ status: STATUSES.includes(r.status) ? r.status : "open",
62
+ selector: line(r.selector, 1e3),
63
+ tag: line(r.tag, 40),
64
+ targetText: line(r.targetText, 400),
65
+ x: num(r.x),
66
+ y: num(r.y),
67
+ bounds: box(r.bounds),
68
+ scroll: {
69
+ x: num(scroll.x),
70
+ y: num(scroll.y)
71
+ },
72
+ viewport: {
73
+ width: num(viewport.width),
74
+ height: num(viewport.height)
75
+ },
76
+ inDialog: r.inDialog === true,
77
+ context
78
+ };
79
+ const origin = line(r.origin, 300);
80
+ if (origin) note.origin = origin;
81
+ const testId = line(r.testId, 200);
82
+ if (testId) note.testId = testId;
83
+ if (r.offset) note.offset = box(r.offset);
84
+ const sentAt = iso(r.sentAt);
85
+ if (sentAt) note.sentAt = sentAt;
86
+ const pickedUpAt = iso(r.pickedUpAt);
87
+ if (pickedUpAt) note.pickedUpAt = pickedUpAt;
88
+ const resolvedAt = iso(r.resolvedAt);
89
+ if (resolvedAt) note.resolvedAt = resolvedAt;
90
+ const reply = prose(r.reply, 2e4);
91
+ if (reply) note.reply = reply;
92
+ return note;
93
+ }
94
+ function sleep(ms) {
95
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
96
+ }
97
+ function alive(pid) {
98
+ if (typeof pid !== "number") return false;
99
+ try {
100
+ process.kill(pid, 0);
101
+ return true;
102
+ } catch (e) {
103
+ return e.code === "EPERM";
104
+ }
105
+ }
106
+ var FileStore = class {
107
+ dir;
108
+ feedback;
109
+ submissions;
110
+ watcher;
111
+ cursor;
112
+ lockPath;
113
+ constructor(dir) {
114
+ this.dir = dir;
115
+ this.feedback = path.join(dir, "feedback.json");
116
+ this.submissions = path.join(dir, "submissions.jsonl");
117
+ this.watcher = path.join(dir, "watcher.json");
118
+ this.cursor = path.join(dir, "watch.cursor");
119
+ this.lockPath = path.join(dir, ".lock");
120
+ }
121
+ ensure() {
122
+ fs.mkdirSync(this.dir, { recursive: true });
123
+ const ignore = path.join(this.dir, ".gitignore");
124
+ if (!fs.existsSync(ignore)) fs.writeFileSync(ignore, "*\n");
125
+ }
126
+ read() {
127
+ let raw;
128
+ try {
129
+ raw = fs.readFileSync(this.feedback, "utf8");
130
+ } catch (e) {
131
+ if (e.code === "ENOENT") return {
132
+ version: 1,
133
+ notes: [],
134
+ deleted: []
135
+ };
136
+ throw e;
137
+ }
138
+ try {
139
+ const d = JSON.parse(raw);
140
+ return {
141
+ version: 1,
142
+ notes: Array.isArray(d.notes) ? d.notes.map(cleanNote).filter(Boolean) : [],
143
+ deleted: Array.isArray(d.deleted) ? d.deleted.filter((x) => typeof x === "string") : []
144
+ };
145
+ } catch {
146
+ const kept = `${this.feedback}.corrupt-${Date.now()}`;
147
+ fs.renameSync(this.feedback, kept);
148
+ console.error(`annotate-anything: ${this.feedback} couldn't be read, so it was kept as ${kept}`);
149
+ return {
150
+ version: 1,
151
+ notes: [],
152
+ deleted: []
153
+ };
154
+ }
155
+ }
156
+ write(state) {
157
+ this.ensure();
158
+ const tmp = `${this.feedback}.${process.pid}.tmp`;
159
+ fs.writeFileSync(tmp, JSON.stringify(state, null, 1) + "\n");
160
+ fs.renameSync(tmp, this.feedback);
161
+ }
162
+ locked(fn) {
163
+ this.ensure();
164
+ const deadline = Date.now() + 5e3;
165
+ for (;;) try {
166
+ fs.closeSync(fs.openSync(this.lockPath, "wx"));
167
+ break;
168
+ } catch (e) {
169
+ if (e.code !== "EEXIST") throw e;
170
+ try {
171
+ if (Date.now() - fs.statSync(this.lockPath).mtimeMs > 1e4) fs.rmSync(this.lockPath, { force: true });
172
+ } catch {}
173
+ if (Date.now() > deadline) throw new Error(`Timed out waiting for ${this.lockPath}`);
174
+ sleep(15);
175
+ }
176
+ try {
177
+ return fn();
178
+ } finally {
179
+ fs.rmSync(this.lockPath, { force: true });
180
+ }
181
+ }
182
+ sync(incoming, deleted) {
183
+ return this.locked(() => {
184
+ const state = this.read();
185
+ const gone = /* @__PURE__ */ new Set([...state.deleted, ...deleted.filter((d) => typeof d === "string" && ID.test(d))]);
186
+ const byId = new Map(state.notes.map((n) => [n.id, n]));
187
+ const used = new Set(state.notes.map((n) => n.n));
188
+ for (const raw of incoming) {
189
+ const note = cleanNote(raw);
190
+ if (!note || gone.has(note.id)) continue;
191
+ const stored = byId.get(note.id);
192
+ if (!stored) {
193
+ if (!note.n || used.has(note.n)) note.n = Math.max(0, ...used) + 1;
194
+ used.add(note.n);
195
+ byId.set(note.id, note);
196
+ continue;
197
+ }
198
+ if (Date.parse(stored.updatedAt) > Date.parse(note.updatedAt)) continue;
199
+ note.n = stored.n;
200
+ const merged = { ...note };
201
+ for (const k of LIFECYCLE) if (stored[k] !== void 0) merged[k] = stored[k];
202
+ else delete merged[k];
203
+ byId.set(note.id, merged);
204
+ }
205
+ const next = {
206
+ version: 1,
207
+ notes: [...byId.values()].filter((n) => !gone.has(n.id)),
208
+ deleted: [...gone].slice(-5e3)
209
+ };
210
+ if (next.notes.length > 1e3) throw new LimitError(`At most ${MAX_NOTES} notes; clear the finished ones`);
211
+ this.write(next);
212
+ return next;
213
+ });
214
+ }
215
+ submit(ids, page) {
216
+ return this.locked(() => {
217
+ const state = this.read();
218
+ const want = new Set(ids.filter((x) => typeof x === "string"));
219
+ const fresh = state.notes.filter((n) => want.has(n.id) && n.status === "open");
220
+ if (!fresh.length) return {
221
+ sent: 0,
222
+ notes: []
223
+ };
224
+ const submittedAt = (/* @__PURE__ */ new Date()).toISOString();
225
+ for (const n of fresh) {
226
+ n.status = "sent";
227
+ n.sentAt = submittedAt;
228
+ }
229
+ const submission = {
230
+ id: crypto.randomUUID().replace(/-/g, ""),
231
+ submittedAt,
232
+ route: line(page.route, 500),
233
+ url: line(page.url, 2e3),
234
+ title: line(page.title, 300),
235
+ noteIds: fresh.map((n) => n.id),
236
+ count: fresh.length,
237
+ excerpts: fresh.map((n) => `#${n.n} ${n.comment.replace(/\s+/g, " ").slice(0, 140)}`)
238
+ };
239
+ this.write(state);
240
+ fs.appendFileSync(this.submissions, JSON.stringify(submission) + "\n");
241
+ return {
242
+ submission,
243
+ sent: fresh.length,
244
+ notes: fresh
245
+ };
246
+ });
247
+ }
248
+ resolveRefs(state, refs) {
249
+ const want = new Set(refs.map((ref) => ref.replace(/^#/, "")));
250
+ return state.notes.filter((n) => want.has(n.id) || want.has(String(n.n)));
251
+ }
252
+ setStatus(refs, status, reply) {
253
+ if (!STATUSES.includes(status)) throw new Error(`status must be one of ${STATUSES.join(", ")}`);
254
+ return this.locked(() => {
255
+ const state = this.read();
256
+ const notes = this.resolveRefs(state, refs);
257
+ const t = (/* @__PURE__ */ new Date()).toISOString();
258
+ for (const n of notes) {
259
+ n.status = status;
260
+ n.updatedAt = t;
261
+ if (status === "open") {
262
+ delete n.sentAt;
263
+ delete n.pickedUpAt;
264
+ }
265
+ if (status === "resolved") n.resolvedAt = t;
266
+ else delete n.resolvedAt;
267
+ if (reply !== void 0) {
268
+ const text = prose(reply, 2e4).trim();
269
+ if (text) n.reply = text;
270
+ else delete n.reply;
271
+ }
272
+ }
273
+ if (notes.length) this.write(state);
274
+ return notes;
275
+ });
276
+ }
277
+ pickUp(ids) {
278
+ this.locked(() => {
279
+ const state = this.read();
280
+ const want = new Set(ids);
281
+ const t = (/* @__PURE__ */ new Date()).toISOString();
282
+ let changed = false;
283
+ for (const n of state.notes) if (want.has(n.id) && n.status === "sent" && !n.pickedUpAt) {
284
+ n.pickedUpAt = t;
285
+ changed = true;
286
+ }
287
+ if (changed) this.write(state);
288
+ });
289
+ }
290
+ agent() {
291
+ try {
292
+ const name = JSON.parse(fs.readFileSync(path.join(this.dir, "agent.json"), "utf8")).name;
293
+ return typeof name === "string" && name.trim() ? name.trim().slice(0, 60) : void 0;
294
+ } catch {
295
+ return;
296
+ }
297
+ }
298
+ setAgent(name) {
299
+ this.ensure();
300
+ fs.writeFileSync(path.join(this.dir, "agent.json"), JSON.stringify({ name: name.trim().slice(0, 60) }) + "\n");
301
+ }
302
+ watching() {
303
+ try {
304
+ const info = JSON.parse(fs.readFileSync(this.watcher, "utf8"));
305
+ return Date.now() - Number(info.beat) < 6e3 && alive(info.pid);
306
+ } catch {
307
+ return false;
308
+ }
309
+ }
310
+ };
311
+ function dump(notes, file) {
312
+ if (!notes.length) return "No open UI notes.";
313
+ const lines = [
314
+ `${notes.length} UI note(s)`,
315
+ `file: ${file}`,
316
+ ""
317
+ ];
318
+ for (const n of [...notes].sort((a, b) => a.n - b.n)) {
319
+ lines.push(`#${n.n} [${n.status}] ${n.kind} on ${n.route} ${n.selector || n.tag || "page"}`);
320
+ lines.push(` id: ${n.id}`);
321
+ if (n.testId) lines.push(` test id: ${n.testId}`);
322
+ if (n.targetText) lines.push(` element text: ${JSON.stringify(n.targetText)}`);
323
+ const b = n.bounds;
324
+ lines.push(` element box: x=${b.x} y=${b.y} w=${b.width} h=${b.height} (viewport ${n.viewport.width}x${n.viewport.height}, scrolled ${n.scroll.x},${n.scroll.y})`);
325
+ if (n.kind === "pin") lines.push(` pin at: x=${n.x} y=${n.y}`);
326
+ if (n.kind === "area" && n.offset) lines.push(` area in element: x=${n.offset.x} y=${n.offset.y} w=${n.offset.width} h=${n.offset.height}`);
327
+ if (n.inDialog) lines.push(" inside a dialog");
328
+ if (Object.keys(n.context).length) lines.push(` context: ${JSON.stringify(n.context)}`);
329
+ lines.push(...n.comment.split("\n").map((l) => " > " + l));
330
+ if (n.reply) lines.push(` reply: ${n.reply.split("\n").join("\n ")}`);
331
+ lines.push("");
332
+ }
333
+ return lines.join("\n").trimEnd();
334
+ }
335
+ // src/server/core.ts
336
+ var quote = (s) => `'${s.replace(/'/g, `'\\''`)}'`;
337
+ function handoff(batch, command = "npx annotate-anything") {
338
+ const dir = quote(path.dirname(batch.file));
339
+ const as = batch.agent ? ` --agent ${quote(batch.agent)}` : "";
340
+ const lines = [...batch.notes].sort((a, b) => a.n - b.n).map((n) => `- #${n.n} ${n.kind} on ${n.route} (${n.selector || n.tag}${n.targetText ? ` "${n.targetText.slice(0, 80)}"` : ""}): ${n.comment.replace(/\s+/g, " ")}`);
341
+ const head = `UI notes from annotate-anything on ${batch.submission.url || batch.submission.route}:`;
342
+ const tail = `\nFull details (selectors, element text, boxes, viewport) are in ${batch.file}. For each note, run \`${command} status <n> working --dir ${dir}${as}\`, make the change, verify it, then \`${command} status <n> resolved --reply "<what changed>" --dir ${dir}${as}\`. Leave a note open and reply why if you change nothing.`;
343
+ let body = lines.join("\n");
344
+ if ((head + body + tail).length > 5800) body = body.slice(0, 5800 - head.length - tail.length - 20) + "\n- …";
345
+ return `${head}\n${body}${tail}`;
346
+ }
347
+ var SYNC_LIMIT = 524288;
348
+ var SMALL_LIMIT = 65536;
349
+ var HttpError = class extends Error {
350
+ status;
351
+ constructor(status, message) {
352
+ super(message);
353
+ this.status = status;
354
+ }
355
+ };
356
+ function isLocalOrigin(origin) {
357
+ try {
358
+ const u = new URL(origin);
359
+ return (u.protocol === "http:" || u.protocol === "https:") && LOCAL_HOST.test(u.hostname);
360
+ } catch {
361
+ return false;
362
+ }
363
+ }
364
+ function isLoopbackOrigin(origin) {
365
+ try {
366
+ const u = new URL(origin);
367
+ return (u.protocol === "http:" || u.protocol === "https:") && LOOPBACK_HOST.test(u.hostname);
368
+ } catch {
369
+ return false;
370
+ }
371
+ }
372
+ var isLoopbackAddress = (a) => /^(127\.|::1$|::ffff:127\.|localhost$)/.test(a.trim());
373
+ function fromElsewhere(req) {
374
+ if (req.remote && !isLoopbackAddress(req.remote)) return true;
375
+ return (req.header("x-forwarded-for") ?? "").split(",").some((a) => a.trim() && !isLoopbackAddress(a));
376
+ }
377
+ function isKnownHost(host, allowed) {
378
+ if (allowed === true) return true;
379
+ try {
380
+ const name = new URL(`http://${host}`).hostname;
381
+ return LOCAL_HOST.test(name) || !!allowed?.some((a) => a.startsWith(".") ? name === a.slice(1) || name.endsWith(a) : name === a);
382
+ } catch {
383
+ return false;
384
+ }
385
+ }
386
+ function inProduction(options) {
387
+ return process.env.NODE_ENV === "production" && !options.enabled;
388
+ }
389
+ function createCore(options = {}) {
390
+ const root = process.cwd();
391
+ const store = new FileStore(path.resolve(
392
+ /*turbopackIgnore: true*/
393
+ root,
394
+ options.dir ?? ".annotations"
395
+ ));
396
+ if (!inProduction(options)) try {
397
+ store.ensure();
398
+ } catch {}
399
+ const base = (options.base ?? "/__annotate").replace(/\/$/, "");
400
+ const state = () => ({
401
+ ...store.read(),
402
+ watching: store.watching(),
403
+ agent: store.agent()
404
+ });
405
+ async function json(req, limit) {
406
+ if (Number(req.header("content-length") ?? 0) > limit) throw new HttpError(413, "Request too large");
407
+ const raw = await req.body(limit);
408
+ try {
409
+ const v = JSON.parse(raw || "{}");
410
+ if (!v || typeof v !== "object" || Array.isArray(v)) throw new Error();
411
+ return v;
412
+ } catch {
413
+ throw new HttpError(400, "Body must be a JSON object");
414
+ }
415
+ }
416
+ async function route(req, route) {
417
+ if (route === "/client.js" && req.method === "GET") {
418
+ const bundle = options.clientScript?.() ?? null;
419
+ if (!bundle) throw new HttpError(404, "The browser bundle is missing; build annotate-anything first");
420
+ const host = req.header("host") ?? "localhost";
421
+ const proto = req.header("x-forwarded-proto") ?? "http";
422
+ return { script: `window.__ANNOTATE_ANYTHING__=Object.assign(${JSON.stringify({
423
+ endpoint: `${proto}://${host}${base}`,
424
+ enabled: true
425
+ })},window.__ANNOTATE_ANYTHING__||{});\n${bundle}` };
426
+ }
427
+ if (route === "/feedback" && req.method === "GET") return { data: state() };
428
+ if (route === "/targets" && req.method === "GET") return { data: { targets: await options.targets?.() ?? [] } };
429
+ if (req.method !== "POST") throw new HttpError(405, "Method not allowed");
430
+ if (!String(req.header("content-type") ?? "").includes("application/json")) throw new HttpError(415, "Use Content-Type: application/json");
431
+ if (route === "/feedback") {
432
+ const b = await json(req, SYNC_LIMIT);
433
+ const notes = Array.isArray(b.notes) ? b.notes : [];
434
+ const deleted = Array.isArray(b.deleted) ? b.deleted : [];
435
+ if (notes.length > 1e3) throw new HttpError(413, `At most ${MAX_NOTES} notes`);
436
+ store.sync(notes, deleted);
437
+ return { data: state() };
438
+ }
439
+ if (route === "/feedback/submit") {
440
+ const b = await json(req, SMALL_LIMIT);
441
+ const ids = Array.isArray(b.noteIds) ? b.noteIds : [];
442
+ const target = typeof b.target === "string" && b.target ? b.target.slice(0, 200) : void 0;
443
+ const agent = target && typeof b.agent === "string" ? b.agent.slice(0, 120) : void 0;
444
+ const result = store.submit(ids, b);
445
+ let delivery;
446
+ let prompt;
447
+ if (result.submission) {
448
+ const batch = {
449
+ notes: result.notes,
450
+ submission: result.submission,
451
+ file: store.feedback,
452
+ root,
453
+ target,
454
+ agent
455
+ };
456
+ prompt = handoff(batch, options.command);
457
+ if (options.onSubmit) try {
458
+ delivery = await options.onSubmit({
459
+ ...batch,
460
+ prompt
461
+ }) ?? void 0;
462
+ } catch (e) {
463
+ delivery = {
464
+ to: "The onSubmit hook",
465
+ ok: false,
466
+ detail: e instanceof Error ? e.message : String(e)
467
+ };
468
+ }
469
+ }
470
+ if (delivery?.ok) {
471
+ store.pickUp(result.submission.noteIds);
472
+ if (agent) store.setAgent(agent);
473
+ }
474
+ const forward = target && !options.onSubmit ? { handoff: prompt } : {};
475
+ return { data: {
476
+ submission: result.submission,
477
+ sent: result.sent,
478
+ watching: store.watching(),
479
+ agent: store.agent(),
480
+ delivery,
481
+ ...forward
482
+ } };
483
+ }
484
+ if (route === "/feedback/status") {
485
+ const b = await json(req, SMALL_LIMIT);
486
+ const refs = (Array.isArray(b.ids) ? b.ids : [b.id ?? b.n]).filter((x) => x !== void 0 && x !== null).map(String);
487
+ if (!refs.length) throw new HttpError(400, "Pass ids");
488
+ if (b.reply !== void 0 && typeof b.reply !== "string") throw new HttpError(400, "reply must be a string");
489
+ if (![
490
+ "open",
491
+ "sent",
492
+ "working",
493
+ "resolved"
494
+ ].includes(String(b.status))) throw new HttpError(400, "status must be open, sent, working or resolved");
495
+ const notes = store.setStatus(refs, b.status, b.reply);
496
+ if (!notes.length) throw new HttpError(404, "No matching notes");
497
+ return { data: { notes } };
498
+ }
499
+ throw new HttpError(404, "Not found");
500
+ }
501
+ async function handle(req) {
502
+ const pathname = req.url.split("?")[0];
503
+ if (pathname !== base && !pathname.startsWith(base + "/")) return null;
504
+ if (!options.allowRemote && fromElsewhere(req)) return {
505
+ status: 403,
506
+ headers: {
507
+ "Cache-Control": "no-store",
508
+ "Content-Type": "application/json"
509
+ },
510
+ body: JSON.stringify({ error: "annotate-anything only answers this machine; set allowRemote: true if you trust this network" })
511
+ };
512
+ const host = req.header("host");
513
+ if (host && !isKnownHost(host, options.allowedHosts)) return {
514
+ status: 403,
515
+ headers: {
516
+ "Cache-Control": "no-store",
517
+ "Content-Type": "application/json"
518
+ },
519
+ body: JSON.stringify({ error: `annotate-anything doesn't answer to ${host}; add it to allowedHosts if you trust it` })
520
+ };
521
+ const origin = req.header("origin");
522
+ let sameOrigin = true;
523
+ if (origin) try {
524
+ sameOrigin = new URL(origin).host === req.header("host");
525
+ } catch {
526
+ sameOrigin = false;
527
+ }
528
+ const cross = !!origin && !sameOrigin && !!options.allowOrigin?.(origin);
529
+ const headers = { "Cache-Control": "no-store" };
530
+ if (cross) Object.assign(headers, {
531
+ "Access-Control-Allow-Origin": origin,
532
+ Vary: "Origin"
533
+ });
534
+ const reply = (status, data) => ({
535
+ status,
536
+ headers: {
537
+ ...headers,
538
+ "Content-Type": "application/json"
539
+ },
540
+ body: JSON.stringify(data)
541
+ });
542
+ if (req.method === "OPTIONS") {
543
+ if (!cross) return reply(403, { error: "Cross-origin requests are not allowed" });
544
+ const h = {
545
+ ...headers,
546
+ "Access-Control-Allow-Methods": "GET, POST",
547
+ "Access-Control-Allow-Headers": "Content-Type",
548
+ "Access-Control-Max-Age": "600"
549
+ };
550
+ if (req.header("access-control-request-private-network")) h["Access-Control-Allow-Private-Network"] = "true";
551
+ return {
552
+ status: 204,
553
+ headers: h,
554
+ body: ""
555
+ };
556
+ }
557
+ const site = req.header("sec-fetch-site");
558
+ const script = pathname === base + "/client.js";
559
+ if (!script && origin && !sameOrigin && !cross) return reply(403, { error: "Cross-origin requests are not allowed" });
560
+ if (!script && site && site !== "same-origin" && site !== "none" && !cross) return reply(403, { error: "Cross-site requests are not allowed" });
561
+ try {
562
+ const out = await route(req, pathname.slice(base.length));
563
+ if (out.script !== void 0) return {
564
+ status: 200,
565
+ headers: {
566
+ ...headers,
567
+ "Content-Type": "text/javascript; charset=utf-8"
568
+ },
569
+ body: out.script
570
+ };
571
+ return reply(out.status ?? 200, out.data);
572
+ } catch (e) {
573
+ return reply(e instanceof HttpError ? e.status : e instanceof LimitError ? 413 : e instanceof Error && /status must be/.test(e.message) ? 400 : 500, { error: e instanceof Error ? e.message : String(e) });
574
+ }
575
+ }
576
+ return {
577
+ base,
578
+ store,
579
+ handle
580
+ };
581
+ }
582
+ // src/server/node.ts
583
+ function readBody(req, limit) {
584
+ const parsed = req.body;
585
+ if (parsed !== void 0) {
586
+ const text = typeof parsed === "string" ? parsed : Buffer.isBuffer(parsed) ? parsed.toString("utf8") : JSON.stringify(parsed);
587
+ return Buffer.byteLength(text) > limit ? Promise.reject(new HttpError(413, "Request too large")) : Promise.resolve(text);
588
+ }
589
+ if (req.readableEnded) return Promise.reject(new HttpError(400, "Something already read the request body; add annotate-anything before your body parser"));
590
+ return new Promise((resolve, reject) => {
591
+ const chunks = [];
592
+ let size = 0;
593
+ req.on("data", (c) => {
594
+ size += c.length;
595
+ if (size > limit) reject(new HttpError(413, "Request too large"));
596
+ else chunks.push(c);
597
+ });
598
+ req.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
599
+ req.on("error", reject);
600
+ });
601
+ }
602
+ function nodeRequest(req) {
603
+ return {
604
+ remote: req.socket?.remoteAddress,
605
+ method: req.method ?? "GET",
606
+ url: req.url ?? "/",
607
+ header: (name) => {
608
+ const v = req.headers[name.toLowerCase()];
609
+ return Array.isArray(v) ? v[0] : v;
610
+ },
611
+ body: (limit) => readBody(req, limit)
612
+ };
613
+ }
614
+ export { isKnownHost as a, FileStore as c, inProduction as i, dump as l, HttpError as n, isLocalOrigin as o, createCore as r, isLoopbackOrigin as s, nodeRequest as t };
package/dist/react.js ADDED
@@ -0,0 +1,13 @@
1
+ "use client";
2
+ import { t as annotate } from "./src-DTByJ6QG.js";
3
+ import { useEffect } from "react";
4
+ // src/react.ts
5
+ function Annotate(props) {
6
+ useEffect(() => {
7
+ if (process.env.NODE_ENV === "production" && !props.enabled) return;
8
+ const tool = annotate(props);
9
+ return () => tool.destroy();
10
+ }, []);
11
+ return null;
12
+ }
13
+ export { Annotate, Annotate as default };