@jami-studio/pinpoint 0.1.12
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/.agents/skills/pinpoint/SKILL.md +77 -0
- package/README.md +412 -0
- package/dist/agent-context-HBCZ4MBN.js +13 -0
- package/dist/agent-context-HBCZ4MBN.js.map +1 -0
- package/dist/chunk-B7TY4FPP.js +569 -0
- package/dist/chunk-B7TY4FPP.js.map +1 -0
- package/dist/chunk-CGJZ7BOM.js +88 -0
- package/dist/chunk-CGJZ7BOM.js.map +1 -0
- package/dist/chunk-DGUM43GV.js +11 -0
- package/dist/chunk-DGUM43GV.js.map +1 -0
- package/dist/chunk-HAEYE6KB.js +25 -0
- package/dist/chunk-HAEYE6KB.js.map +1 -0
- package/dist/chunk-J5PXKVTM.js +53 -0
- package/dist/chunk-J5PXKVTM.js.map +1 -0
- package/dist/chunk-XBX2J7WU.js +94 -0
- package/dist/chunk-XBX2J7WU.js.map +1 -0
- package/dist/chunk-ZW7IO3EW.js +4927 -0
- package/dist/chunk-ZW7IO3EW.js.map +1 -0
- package/dist/cli.js +71 -0
- package/dist/cli.js.map +1 -0
- package/dist/formatter-CR4COA5Z.js +8 -0
- package/dist/formatter-CR4COA5Z.js.map +1 -0
- package/dist/index-NvFcZ4SO.d.ts +200 -0
- package/dist/index.browser.d.ts +377 -0
- package/dist/index.browser.js +620 -0
- package/dist/index.browser.js.map +1 -0
- package/dist/index.js +936 -0
- package/dist/index.js.map +1 -0
- package/dist/open-file-WR2JHI4P.js +8 -0
- package/dist/open-file-WR2JHI4P.js.map +1 -0
- package/dist/primitives/index.js +28 -0
- package/dist/primitives/index.js.map +1 -0
- package/dist/react.d.ts +23 -0
- package/dist/react.js +20 -0
- package/dist/react.js.map +1 -0
- package/dist/server/index.js +457 -0
- package/dist/server/index.js.map +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/index.js.map +1 -0
- package/package.json +88 -0
- package/src/scripts/create-pin.ts +43 -0
- package/src/scripts/delete-pin.ts +16 -0
- package/src/scripts/get-pins.ts +36 -0
- package/src/scripts/list-sessions.ts +33 -0
- package/src/scripts/resolve-pin.ts +27 -0
- package/src/scripts/run.ts +8 -0
- package/src/scripts/update-pin.ts +32 -0
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PinSchema
|
|
3
|
+
} from "../chunk-J5PXKVTM.js";
|
|
4
|
+
import {
|
|
5
|
+
__require
|
|
6
|
+
} from "../chunk-DGUM43GV.js";
|
|
7
|
+
|
|
8
|
+
// src/server/middleware.ts
|
|
9
|
+
import { Router } from "express";
|
|
10
|
+
|
|
11
|
+
// src/storage/file-store.ts
|
|
12
|
+
import { randomUUID } from "crypto";
|
|
13
|
+
import {
|
|
14
|
+
readdir,
|
|
15
|
+
readFile,
|
|
16
|
+
writeFile,
|
|
17
|
+
unlink,
|
|
18
|
+
rename,
|
|
19
|
+
mkdir
|
|
20
|
+
} from "fs/promises";
|
|
21
|
+
import { tmpdir } from "os";
|
|
22
|
+
import { join, resolve } from "path";
|
|
23
|
+
var VALID_ID = /^[a-zA-Z0-9_-]+$/;
|
|
24
|
+
var FileStore = class {
|
|
25
|
+
dir;
|
|
26
|
+
constructor(dataDir = "data/pins") {
|
|
27
|
+
this.dir = resolve(dataDir);
|
|
28
|
+
}
|
|
29
|
+
validateId(id) {
|
|
30
|
+
if (!VALID_ID.test(id)) {
|
|
31
|
+
throw new Error(`Invalid pin ID: ${id}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
pinPath(id) {
|
|
35
|
+
this.validateId(id);
|
|
36
|
+
const resolved = join(this.dir, `${id}.json`);
|
|
37
|
+
if (!resolved.startsWith(this.dir)) {
|
|
38
|
+
throw new Error("Path traversal detected");
|
|
39
|
+
}
|
|
40
|
+
return resolved;
|
|
41
|
+
}
|
|
42
|
+
async ensureDir() {
|
|
43
|
+
await mkdir(this.dir, { recursive: true });
|
|
44
|
+
}
|
|
45
|
+
async readPin(filePath) {
|
|
46
|
+
try {
|
|
47
|
+
const content = await readFile(filePath, "utf-8");
|
|
48
|
+
const parsed = JSON.parse(content);
|
|
49
|
+
const result = PinSchema.safeParse(parsed);
|
|
50
|
+
return result.success ? result.data : null;
|
|
51
|
+
} catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async atomicWrite(filePath, data) {
|
|
56
|
+
await this.ensureDir();
|
|
57
|
+
const tempPath = join(tmpdir(), `pinpoint-${randomUUID()}.tmp`);
|
|
58
|
+
await writeFile(tempPath, data, "utf-8");
|
|
59
|
+
await rename(tempPath, filePath);
|
|
60
|
+
}
|
|
61
|
+
async load(pageUrl) {
|
|
62
|
+
return this.list({ pageUrl });
|
|
63
|
+
}
|
|
64
|
+
async save(pin) {
|
|
65
|
+
const filePath = this.pinPath(pin.id);
|
|
66
|
+
await this.atomicWrite(filePath, JSON.stringify(pin, null, 2));
|
|
67
|
+
}
|
|
68
|
+
async update(id, patch) {
|
|
69
|
+
const filePath = this.pinPath(id);
|
|
70
|
+
const existing = await this.readPin(filePath);
|
|
71
|
+
if (!existing) return;
|
|
72
|
+
const updated = {
|
|
73
|
+
...existing,
|
|
74
|
+
...patch,
|
|
75
|
+
id: existing.id,
|
|
76
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
77
|
+
};
|
|
78
|
+
await this.atomicWrite(filePath, JSON.stringify(updated, null, 2));
|
|
79
|
+
}
|
|
80
|
+
async delete(id) {
|
|
81
|
+
try {
|
|
82
|
+
await unlink(this.pinPath(id));
|
|
83
|
+
} catch {
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
async list(filter) {
|
|
87
|
+
await this.ensureDir();
|
|
88
|
+
let files;
|
|
89
|
+
try {
|
|
90
|
+
files = await readdir(this.dir);
|
|
91
|
+
} catch {
|
|
92
|
+
return [];
|
|
93
|
+
}
|
|
94
|
+
const pins = [];
|
|
95
|
+
for (const file of files) {
|
|
96
|
+
if (!file.endsWith(".json")) continue;
|
|
97
|
+
const pin = await this.readPin(join(this.dir, file));
|
|
98
|
+
if (!pin) continue;
|
|
99
|
+
if (filter?.pageUrl && pin.pageUrl !== filter.pageUrl) continue;
|
|
100
|
+
if (filter?.status && pin.status.state !== filter.status) continue;
|
|
101
|
+
pins.push(pin);
|
|
102
|
+
}
|
|
103
|
+
return pins.sort(
|
|
104
|
+
(a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
async clear(pageUrl) {
|
|
108
|
+
await this.ensureDir();
|
|
109
|
+
let files;
|
|
110
|
+
try {
|
|
111
|
+
files = await readdir(this.dir);
|
|
112
|
+
} catch {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
for (const file of files) {
|
|
116
|
+
if (!file.endsWith(".json")) continue;
|
|
117
|
+
const filePath = join(this.dir, file);
|
|
118
|
+
if (pageUrl) {
|
|
119
|
+
const pin = await this.readPin(filePath);
|
|
120
|
+
if (pin && pin.pageUrl === pageUrl) {
|
|
121
|
+
await unlink(filePath).catch(() => {
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
} else {
|
|
125
|
+
await unlink(filePath).catch(() => {
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
// src/server/middleware.ts
|
|
133
|
+
var VALID_ID2 = /^[a-zA-Z0-9_-]+$/;
|
|
134
|
+
function validateId(id) {
|
|
135
|
+
return VALID_ID2.test(id) && id.length > 0 && id.length <= 128;
|
|
136
|
+
}
|
|
137
|
+
function pagePinRoutes(options = {}) {
|
|
138
|
+
const router = Router();
|
|
139
|
+
const store = new FileStore(options.dataDir || "data/pins");
|
|
140
|
+
router.get("/", async (req, res) => {
|
|
141
|
+
try {
|
|
142
|
+
const pageUrl = String(req.query.pageUrl || "") || void 0;
|
|
143
|
+
const status = req.query.status ? String(req.query.status) : void 0;
|
|
144
|
+
const pins = await store.list({ pageUrl, status });
|
|
145
|
+
res.json(pins);
|
|
146
|
+
} catch (err) {
|
|
147
|
+
res.status(500).json({ error: "Failed to list pins" });
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
router.get("/:id", async (req, res) => {
|
|
151
|
+
try {
|
|
152
|
+
const id = String(req.params.id);
|
|
153
|
+
if (!validateId(id)) {
|
|
154
|
+
res.status(400).json({ error: "Invalid pin ID" });
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const pins = await store.list();
|
|
158
|
+
const pin = pins.find((p) => p.id === id);
|
|
159
|
+
if (!pin) {
|
|
160
|
+
res.status(404).json({ error: "Pin not found" });
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
res.json(pin);
|
|
164
|
+
} catch (err) {
|
|
165
|
+
res.status(500).json({ error: "Failed to get pin" });
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
router.post("/", async (req, res) => {
|
|
169
|
+
try {
|
|
170
|
+
const result = PinSchema.safeParse(req.body);
|
|
171
|
+
if (!result.success) {
|
|
172
|
+
res.status(400).json({ error: "Invalid pin data", details: result.error.issues });
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
await store.save(result.data);
|
|
176
|
+
res.status(201).json(result.data);
|
|
177
|
+
} catch (err) {
|
|
178
|
+
res.status(500).json({ error: "Failed to create pin" });
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
router.patch("/:id", async (req, res) => {
|
|
182
|
+
try {
|
|
183
|
+
const id = String(req.params.id);
|
|
184
|
+
if (!validateId(id)) {
|
|
185
|
+
res.status(400).json({ error: "Invalid pin ID" });
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
const result = PinSchema.partial().safeParse(req.body);
|
|
189
|
+
if (!result.success) {
|
|
190
|
+
res.status(400).json({ error: "Invalid pin data", details: result.error.issues });
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
await store.update(id, result.data);
|
|
194
|
+
res.json({ ok: true });
|
|
195
|
+
} catch (err) {
|
|
196
|
+
res.status(500).json({ error: "Failed to update pin" });
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
router.delete("/:id", async (req, res) => {
|
|
200
|
+
try {
|
|
201
|
+
const id = String(req.params.id);
|
|
202
|
+
if (!validateId(id)) {
|
|
203
|
+
res.status(400).json({ error: "Invalid pin ID" });
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
await store.delete(id);
|
|
207
|
+
res.json({ ok: true });
|
|
208
|
+
} catch (err) {
|
|
209
|
+
res.status(500).json({ error: "Failed to delete pin" });
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
router.delete("/", async (req, res) => {
|
|
213
|
+
try {
|
|
214
|
+
const pageUrl = String(req.query.pageUrl || "") || void 0;
|
|
215
|
+
await store.clear(pageUrl);
|
|
216
|
+
res.json({ ok: true });
|
|
217
|
+
} catch (err) {
|
|
218
|
+
res.status(500).json({ error: "Failed to clear pins" });
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
return router;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// src/server/a2a.ts
|
|
225
|
+
function registerPinpointA2A(app, options = {}) {
|
|
226
|
+
const store = new FileStore(options.dataDir || "data/pins");
|
|
227
|
+
const config = {
|
|
228
|
+
name: "pinpoint",
|
|
229
|
+
description: "Visual feedback and annotation tool for web applications",
|
|
230
|
+
skills: [
|
|
231
|
+
{
|
|
232
|
+
id: "get-annotations",
|
|
233
|
+
name: "Get Pins",
|
|
234
|
+
description: "Retrieve visual feedback annotations"
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
id: "resolve-annotation",
|
|
238
|
+
name: "Resolve Pin",
|
|
239
|
+
description: "Mark an annotation as resolved"
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
id: "create-annotation",
|
|
243
|
+
name: "Create Pin",
|
|
244
|
+
description: "Create a new annotation programmatically"
|
|
245
|
+
}
|
|
246
|
+
],
|
|
247
|
+
handler: async (message, _context) => {
|
|
248
|
+
const { method, params } = message;
|
|
249
|
+
switch (method) {
|
|
250
|
+
case "get-annotations": {
|
|
251
|
+
const pins = await store.list(params);
|
|
252
|
+
return { result: pins };
|
|
253
|
+
}
|
|
254
|
+
case "resolve-annotation": {
|
|
255
|
+
await store.update(params.id, {
|
|
256
|
+
status: {
|
|
257
|
+
state: "resolved",
|
|
258
|
+
changedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
259
|
+
changedBy: "agent"
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
return { result: { ok: true } };
|
|
263
|
+
}
|
|
264
|
+
case "create-annotation": {
|
|
265
|
+
const validated = PinSchema.safeParse(params?.pin);
|
|
266
|
+
if (!validated.success) {
|
|
267
|
+
return { error: { code: -32602, message: "Invalid pin data" } };
|
|
268
|
+
}
|
|
269
|
+
await store.save(validated.data);
|
|
270
|
+
return { result: { ok: true } };
|
|
271
|
+
}
|
|
272
|
+
default:
|
|
273
|
+
return { error: { code: -32601, message: "Method not found" } };
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
try {
|
|
278
|
+
const { enableA2A } = __require("@agent-native/core/a2a");
|
|
279
|
+
enableA2A(app, config);
|
|
280
|
+
} catch {
|
|
281
|
+
app.get("/.well-known/agent-card.json", (_req, res) => {
|
|
282
|
+
res.json({
|
|
283
|
+
name: config.name,
|
|
284
|
+
description: config.description,
|
|
285
|
+
skills: config.skills,
|
|
286
|
+
url: "/a2a/pinpoint"
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
app.post("/a2a/pinpoint", async (req, res) => {
|
|
290
|
+
try {
|
|
291
|
+
const result = await config.handler(req.body, {});
|
|
292
|
+
res.json(result);
|
|
293
|
+
} catch (err) {
|
|
294
|
+
res.status(500).json({ error: "A2A handler error" });
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// src/server/mcp.ts
|
|
301
|
+
function createPinpointMCPTools(options = {}) {
|
|
302
|
+
const store = new FileStore(options.dataDir || "data/pins");
|
|
303
|
+
return {
|
|
304
|
+
tools: [
|
|
305
|
+
{
|
|
306
|
+
name: "get_annotations",
|
|
307
|
+
description: "Get visual feedback annotations from the page",
|
|
308
|
+
inputSchema: {
|
|
309
|
+
type: "object",
|
|
310
|
+
properties: {
|
|
311
|
+
pageUrl: {
|
|
312
|
+
type: "string",
|
|
313
|
+
description: "Filter by page URL"
|
|
314
|
+
},
|
|
315
|
+
status: {
|
|
316
|
+
type: "string",
|
|
317
|
+
enum: ["open", "acknowledged", "resolved", "dismissed"],
|
|
318
|
+
description: "Filter by status"
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
name: "resolve_annotation",
|
|
325
|
+
description: "Mark an annotation as resolved",
|
|
326
|
+
inputSchema: {
|
|
327
|
+
type: "object",
|
|
328
|
+
properties: {
|
|
329
|
+
id: {
|
|
330
|
+
type: "string",
|
|
331
|
+
description: "The annotation ID to resolve"
|
|
332
|
+
},
|
|
333
|
+
message: {
|
|
334
|
+
type: "string",
|
|
335
|
+
description: "Optional resolution message"
|
|
336
|
+
}
|
|
337
|
+
},
|
|
338
|
+
required: ["id"]
|
|
339
|
+
}
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
name: "create_annotation",
|
|
343
|
+
description: "Create a new annotation on a page element",
|
|
344
|
+
inputSchema: {
|
|
345
|
+
type: "object",
|
|
346
|
+
properties: {
|
|
347
|
+
pageUrl: {
|
|
348
|
+
type: "string",
|
|
349
|
+
description: "The page URL"
|
|
350
|
+
},
|
|
351
|
+
selector: {
|
|
352
|
+
type: "string",
|
|
353
|
+
description: "CSS selector of the element"
|
|
354
|
+
},
|
|
355
|
+
comment: {
|
|
356
|
+
type: "string",
|
|
357
|
+
description: "The annotation comment"
|
|
358
|
+
}
|
|
359
|
+
},
|
|
360
|
+
required: ["pageUrl", "selector", "comment"]
|
|
361
|
+
}
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
name: "delete_annotation",
|
|
365
|
+
description: "Delete an annotation",
|
|
366
|
+
inputSchema: {
|
|
367
|
+
type: "object",
|
|
368
|
+
properties: {
|
|
369
|
+
id: {
|
|
370
|
+
type: "string",
|
|
371
|
+
description: "The annotation ID to delete"
|
|
372
|
+
}
|
|
373
|
+
},
|
|
374
|
+
required: ["id"]
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
],
|
|
378
|
+
async handleTool(name, args) {
|
|
379
|
+
switch (name) {
|
|
380
|
+
case "get_annotations": {
|
|
381
|
+
const pins = await store.list({
|
|
382
|
+
pageUrl: args.pageUrl,
|
|
383
|
+
status: args.status
|
|
384
|
+
});
|
|
385
|
+
return {
|
|
386
|
+
content: [
|
|
387
|
+
{
|
|
388
|
+
type: "text",
|
|
389
|
+
text: JSON.stringify(pins, null, 2)
|
|
390
|
+
}
|
|
391
|
+
]
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
case "resolve_annotation": {
|
|
395
|
+
await store.update(args.id, {
|
|
396
|
+
status: {
|
|
397
|
+
state: "resolved",
|
|
398
|
+
changedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
399
|
+
changedBy: "agent"
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
return {
|
|
403
|
+
content: [{ type: "text", text: `Resolved annotation ${args.id}` }]
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
case "create_annotation": {
|
|
407
|
+
const { randomUUID: randomUUID2 } = await import("crypto");
|
|
408
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
409
|
+
const pin = {
|
|
410
|
+
id: randomUUID2(),
|
|
411
|
+
pageUrl: args.pageUrl,
|
|
412
|
+
createdAt: now,
|
|
413
|
+
updatedAt: now,
|
|
414
|
+
comment: args.comment,
|
|
415
|
+
element: {
|
|
416
|
+
tagName: "unknown",
|
|
417
|
+
classNames: [],
|
|
418
|
+
selector: args.selector,
|
|
419
|
+
boundingRect: { x: 0, y: 0, width: 0, height: 0 }
|
|
420
|
+
},
|
|
421
|
+
status: {
|
|
422
|
+
state: "open",
|
|
423
|
+
changedAt: now,
|
|
424
|
+
changedBy: "agent"
|
|
425
|
+
}
|
|
426
|
+
};
|
|
427
|
+
await store.save(pin);
|
|
428
|
+
return {
|
|
429
|
+
content: [
|
|
430
|
+
{
|
|
431
|
+
type: "text",
|
|
432
|
+
text: `Created annotation ${pin.id} on ${args.selector}`
|
|
433
|
+
}
|
|
434
|
+
]
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
case "delete_annotation": {
|
|
438
|
+
await store.delete(args.id);
|
|
439
|
+
return {
|
|
440
|
+
content: [{ type: "text", text: `Deleted annotation ${args.id}` }]
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
default:
|
|
444
|
+
return {
|
|
445
|
+
content: [{ type: "text", text: `Unknown tool: ${name}` }]
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
export {
|
|
452
|
+
FileStore,
|
|
453
|
+
createPinpointMCPTools,
|
|
454
|
+
pagePinRoutes,
|
|
455
|
+
registerPinpointA2A
|
|
456
|
+
};
|
|
457
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/server/middleware.ts","../../src/storage/file-store.ts","../../src/server/a2a.ts","../../src/server/mcp.ts"],"sourcesContent":["// @agent-native/pinpoint — Express middleware for pin CRUD\r\n// MIT License\r\n//\r\n// REST routes: GET (list), GET/:id, POST, PATCH/:id, DELETE/:id\r\n// Path traversal validation on all IDs.\r\n\r\nimport { Router, type Request, type Response } from \"express\";\r\n\r\nimport { FileStore } from \"../storage/file-store.js\";\r\nimport { PinSchema } from \"../storage/schemas.js\";\r\n\r\nconst VALID_ID = /^[a-zA-Z0-9_-]+$/;\r\n\r\nfunction validateId(id: string): boolean {\r\n return VALID_ID.test(id) && id.length > 0 && id.length <= 128;\r\n}\r\n\r\nexport interface PinRoutesOptions {\r\n /** Directory for pin data files. Default: data/pins */\r\n dataDir?: string;\r\n}\r\n\r\n/**\r\n * Create Express router with pin CRUD endpoints.\r\n *\r\n * Usage:\r\n * ```ts\r\n * import { pagePinRoutes } from '@agent-native/pinpoint/server';\r\n * app.use('/api/pins', pagePinRoutes());\r\n * ```\r\n */\r\nexport function pagePinRoutes(options: PinRoutesOptions = {}): Router {\r\n const router = Router();\r\n const store = new FileStore(options.dataDir || \"data/pins\");\r\n\r\n // GET / — List all pins, optionally filtered\r\n router.get(\"/\", async (req: Request, res: Response) => {\r\n try {\r\n const pageUrl = String(req.query.pageUrl || \"\") || undefined;\r\n const status = req.query.status\r\n ? (String(req.query.status) as any)\r\n : undefined;\r\n const pins = await store.list({ pageUrl, status });\r\n res.json(pins);\r\n } catch (err) {\r\n res.status(500).json({ error: \"Failed to list pins\" });\r\n }\r\n });\r\n\r\n // GET /:id — Get a single pin\r\n router.get(\"/:id\", async (req: Request, res: Response) => {\r\n try {\r\n const id = String(req.params.id);\r\n if (!validateId(id)) {\r\n res.status(400).json({ error: \"Invalid pin ID\" });\r\n return;\r\n }\r\n const pins = await store.list();\r\n const pin = pins.find((p) => p.id === id);\r\n if (!pin) {\r\n res.status(404).json({ error: \"Pin not found\" });\r\n return;\r\n }\r\n res.json(pin);\r\n } catch (err) {\r\n res.status(500).json({ error: \"Failed to get pin\" });\r\n }\r\n });\r\n\r\n // POST / — Create a new pin\r\n router.post(\"/\", async (req: Request, res: Response) => {\r\n try {\r\n const result = PinSchema.safeParse(req.body);\r\n if (!result.success) {\r\n res\r\n .status(400)\r\n .json({ error: \"Invalid pin data\", details: result.error.issues });\r\n return;\r\n }\r\n await store.save(result.data as any);\r\n res.status(201).json(result.data);\r\n } catch (err) {\r\n res.status(500).json({ error: \"Failed to create pin\" });\r\n }\r\n });\r\n\r\n // PATCH /:id — Update a pin\r\n router.patch(\"/:id\", async (req: Request, res: Response) => {\r\n try {\r\n const id = String(req.params.id);\r\n if (!validateId(id)) {\r\n res.status(400).json({ error: \"Invalid pin ID\" });\r\n return;\r\n }\r\n const result = PinSchema.partial().safeParse(req.body);\r\n if (!result.success) {\r\n res\r\n .status(400)\r\n .json({ error: \"Invalid pin data\", details: result.error.issues });\r\n return;\r\n }\r\n await store.update(id, result.data);\r\n res.json({ ok: true });\r\n } catch (err) {\r\n res.status(500).json({ error: \"Failed to update pin\" });\r\n }\r\n });\r\n\r\n // DELETE /:id — Delete a pin\r\n router.delete(\"/:id\", async (req: Request, res: Response) => {\r\n try {\r\n const id = String(req.params.id);\r\n if (!validateId(id)) {\r\n res.status(400).json({ error: \"Invalid pin ID\" });\r\n return;\r\n }\r\n await store.delete(id);\r\n res.json({ ok: true });\r\n } catch (err) {\r\n res.status(500).json({ error: \"Failed to delete pin\" });\r\n }\r\n });\r\n\r\n // DELETE / — Clear pins (optionally by pageUrl)\r\n router.delete(\"/\", async (req: Request, res: Response) => {\r\n try {\r\n const pageUrl = String(req.query.pageUrl || \"\") || undefined;\r\n await store.clear(pageUrl);\r\n res.json({ ok: true });\r\n } catch (err) {\r\n res.status(500).json({ error: \"Failed to clear pins\" });\r\n }\r\n });\r\n\r\n return router;\r\n}\r\n","// @agent-native/pinpoint — Server-side file storage adapter\r\n// MIT License\r\n//\r\n// Writes to data/pins/{uuid}.json — one file per annotation.\r\n// Atomic writes (temp + rename). Path traversal validation on all IDs.\r\n\r\nimport { randomUUID } from \"crypto\";\r\nimport {\r\n readdir,\r\n readFile,\r\n writeFile,\r\n unlink,\r\n rename,\r\n mkdir,\r\n} from \"fs/promises\";\r\nimport { tmpdir } from \"os\";\r\nimport { join, resolve } from \"path\";\r\n\r\nimport type { Pin, PinStatus, PinStorage } from \"../types/index.js\";\r\nimport { PinSchema } from \"./schemas.js\";\r\n\r\nconst VALID_ID = /^[a-zA-Z0-9_-]+$/;\r\n\r\nexport class FileStore implements PinStorage {\r\n private dir: string;\r\n\r\n constructor(dataDir: string = \"data/pins\") {\r\n this.dir = resolve(dataDir);\r\n }\r\n\r\n private validateId(id: string): void {\r\n if (!VALID_ID.test(id)) {\r\n throw new Error(`Invalid pin ID: ${id}`);\r\n }\r\n }\r\n\r\n private pinPath(id: string): string {\r\n this.validateId(id);\r\n const resolved = join(this.dir, `${id}.json`);\r\n // Ensure resolved path is within the data directory\r\n if (!resolved.startsWith(this.dir)) {\r\n throw new Error(\"Path traversal detected\");\r\n }\r\n return resolved;\r\n }\r\n\r\n private async ensureDir(): Promise<void> {\r\n await mkdir(this.dir, { recursive: true });\r\n }\r\n\r\n private async readPin(filePath: string): Promise<Pin | null> {\r\n try {\r\n const content = await readFile(filePath, \"utf-8\");\r\n const parsed = JSON.parse(content);\r\n const result = PinSchema.safeParse(parsed);\r\n return result.success ? (result.data as Pin) : null;\r\n } catch {\r\n return null;\r\n }\r\n }\r\n\r\n private async atomicWrite(filePath: string, data: string): Promise<void> {\r\n await this.ensureDir();\r\n const tempPath = join(tmpdir(), `pinpoint-${randomUUID()}.tmp`);\r\n await writeFile(tempPath, data, \"utf-8\");\r\n await rename(tempPath, filePath);\r\n }\r\n\r\n async load(pageUrl: string): Promise<Pin[]> {\r\n return this.list({ pageUrl });\r\n }\r\n\r\n async save(pin: Pin): Promise<void> {\r\n const filePath = this.pinPath(pin.id);\r\n await this.atomicWrite(filePath, JSON.stringify(pin, null, 2));\r\n }\r\n\r\n async update(id: string, patch: Partial<Pin>): Promise<void> {\r\n const filePath = this.pinPath(id);\r\n const existing = await this.readPin(filePath);\r\n if (!existing) return;\r\n const updated: Pin = {\r\n ...existing,\r\n ...patch,\r\n id: existing.id,\r\n updatedAt: new Date().toISOString(),\r\n };\r\n await this.atomicWrite(filePath, JSON.stringify(updated, null, 2));\r\n }\r\n\r\n async delete(id: string): Promise<void> {\r\n try {\r\n await unlink(this.pinPath(id));\r\n } catch {\r\n // File already deleted or doesn't exist\r\n }\r\n }\r\n\r\n async list(filter?: {\r\n pageUrl?: string;\r\n status?: PinStatus;\r\n }): Promise<Pin[]> {\r\n await this.ensureDir();\r\n let files: string[];\r\n try {\r\n files = await readdir(this.dir);\r\n } catch {\r\n return [];\r\n }\r\n\r\n const pins: Pin[] = [];\r\n for (const file of files) {\r\n if (!file.endsWith(\".json\")) continue;\r\n const pin = await this.readPin(join(this.dir, file));\r\n if (!pin) continue;\r\n if (filter?.pageUrl && pin.pageUrl !== filter.pageUrl) continue;\r\n if (filter?.status && pin.status.state !== filter.status) continue;\r\n pins.push(pin);\r\n }\r\n\r\n return pins.sort(\r\n (a, b) =>\r\n new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime(),\r\n );\r\n }\r\n\r\n async clear(pageUrl?: string): Promise<void> {\r\n await this.ensureDir();\r\n let files: string[];\r\n try {\r\n files = await readdir(this.dir);\r\n } catch {\r\n return;\r\n }\r\n\r\n for (const file of files) {\r\n if (!file.endsWith(\".json\")) continue;\r\n const filePath = join(this.dir, file);\r\n if (pageUrl) {\r\n const pin = await this.readPin(filePath);\r\n if (pin && pin.pageUrl === pageUrl) {\r\n await unlink(filePath).catch(() => {});\r\n }\r\n } else {\r\n await unlink(filePath).catch(() => {});\r\n }\r\n }\r\n }\r\n}\r\n","// @agent-native/pinpoint — A2A agent registration\r\n// MIT License\r\n//\r\n// Exposes annotations via the Agent-to-Agent (A2A) protocol.\r\n\r\nimport type { Application } from \"express\";\r\n\r\nimport { FileStore } from \"../storage/file-store.js\";\r\nimport { PinSchema } from \"../storage/schemas.js\";\r\n\r\ninterface A2ASkill {\r\n id: string;\r\n name: string;\r\n description: string;\r\n}\r\n\r\ninterface A2AConfig {\r\n name: string;\r\n description: string;\r\n skills: A2ASkill[];\r\n handler: (message: any, context: any) => Promise<any>;\r\n}\r\n\r\n/**\r\n * Register Pinpoint as an A2A agent.\r\n * Call this after creating your express app.\r\n *\r\n * ```ts\r\n * import { registerPinpointA2A } from '@agent-native/pinpoint/server';\r\n * registerPinpointA2A(app);\r\n * ```\r\n */\r\nexport function registerPinpointA2A(\r\n app: Application,\r\n options: { dataDir?: string } = {},\r\n): void {\r\n const store = new FileStore(options.dataDir || \"data/pins\");\r\n\r\n const config: A2AConfig = {\r\n name: \"pinpoint\",\r\n description: \"Visual feedback and annotation tool for web applications\",\r\n skills: [\r\n {\r\n id: \"get-annotations\",\r\n name: \"Get Pins\",\r\n description: \"Retrieve visual feedback annotations\",\r\n },\r\n {\r\n id: \"resolve-annotation\",\r\n name: \"Resolve Pin\",\r\n description: \"Mark an annotation as resolved\",\r\n },\r\n {\r\n id: \"create-annotation\",\r\n name: \"Create Pin\",\r\n description: \"Create a new annotation programmatically\",\r\n },\r\n ],\r\n handler: async (message, _context) => {\r\n const { method, params } = message;\r\n\r\n switch (method) {\r\n case \"get-annotations\": {\r\n const pins = await store.list(params);\r\n return { result: pins };\r\n }\r\n case \"resolve-annotation\": {\r\n await store.update(params.id, {\r\n status: {\r\n state: \"resolved\",\r\n changedAt: new Date().toISOString(),\r\n changedBy: \"agent\",\r\n },\r\n });\r\n return { result: { ok: true } };\r\n }\r\n case \"create-annotation\": {\r\n const validated = PinSchema.safeParse(params?.pin);\r\n if (!validated.success) {\r\n return { error: { code: -32602, message: \"Invalid pin data\" } };\r\n }\r\n await store.save(validated.data as any);\r\n return { result: { ok: true } };\r\n }\r\n default:\r\n return { error: { code: -32601, message: \"Method not found\" } };\r\n }\r\n },\r\n };\r\n\r\n // Try to use @agent-native/core's enableA2A\r\n try {\r\n const { enableA2A } = require(\"@agent-native/core/a2a\");\r\n enableA2A(app, config);\r\n } catch {\r\n // If enableA2A is not available, set up basic A2A endpoints manually\r\n app.get(\"/.well-known/agent-card.json\", (_req, res) => {\r\n res.json({\r\n name: config.name,\r\n description: config.description,\r\n skills: config.skills,\r\n url: \"/a2a/pinpoint\",\r\n });\r\n });\r\n\r\n app.post(\"/a2a/pinpoint\", async (req, res) => {\r\n try {\r\n const result = await config.handler(req.body, {});\r\n res.json(result);\r\n } catch (err) {\r\n res.status(500).json({ error: \"A2A handler error\" });\r\n }\r\n });\r\n }\r\n}\r\n","// @agent-native/pinpoint — MCP server for pin tools\r\n// MIT License\r\n\r\nimport { FileStore } from \"../storage/file-store.js\";\r\n\r\n/**\r\n * Create MCP tool handlers for Pinpoint.\r\n * These can be registered with an MCP server instance.\r\n *\r\n * ```ts\r\n * import { Server } from '@modelcontextprotocol/sdk/server/index.js';\r\n * import { createPinpointMCPTools } from '@agent-native/pinpoint/server';\r\n *\r\n * const server = new Server({ name: 'pinpoint', version: '1.0.0' }, { capabilities: { tools: {} } });\r\n * const tools = createPinpointMCPTools();\r\n * // Register tools with server...\r\n * ```\r\n */\r\nexport function createPinpointMCPTools(options: { dataDir?: string } = {}) {\r\n const store = new FileStore(options.dataDir || \"data/pins\");\r\n\r\n return {\r\n tools: [\r\n {\r\n name: \"get_annotations\",\r\n description: \"Get visual feedback annotations from the page\",\r\n inputSchema: {\r\n type: \"object\" as const,\r\n properties: {\r\n pageUrl: {\r\n type: \"string\",\r\n description: \"Filter by page URL\",\r\n },\r\n status: {\r\n type: \"string\",\r\n enum: [\"open\", \"acknowledged\", \"resolved\", \"dismissed\"],\r\n description: \"Filter by status\",\r\n },\r\n },\r\n },\r\n },\r\n {\r\n name: \"resolve_annotation\",\r\n description: \"Mark an annotation as resolved\",\r\n inputSchema: {\r\n type: \"object\" as const,\r\n properties: {\r\n id: {\r\n type: \"string\",\r\n description: \"The annotation ID to resolve\",\r\n },\r\n message: {\r\n type: \"string\",\r\n description: \"Optional resolution message\",\r\n },\r\n },\r\n required: [\"id\"],\r\n },\r\n },\r\n {\r\n name: \"create_annotation\",\r\n description: \"Create a new annotation on a page element\",\r\n inputSchema: {\r\n type: \"object\" as const,\r\n properties: {\r\n pageUrl: {\r\n type: \"string\",\r\n description: \"The page URL\",\r\n },\r\n selector: {\r\n type: \"string\",\r\n description: \"CSS selector of the element\",\r\n },\r\n comment: {\r\n type: \"string\",\r\n description: \"The annotation comment\",\r\n },\r\n },\r\n required: [\"pageUrl\", \"selector\", \"comment\"],\r\n },\r\n },\r\n {\r\n name: \"delete_annotation\",\r\n description: \"Delete an annotation\",\r\n inputSchema: {\r\n type: \"object\" as const,\r\n properties: {\r\n id: {\r\n type: \"string\",\r\n description: \"The annotation ID to delete\",\r\n },\r\n },\r\n required: [\"id\"],\r\n },\r\n },\r\n ],\r\n\r\n async handleTool(\r\n name: string,\r\n args: Record<string, any>,\r\n ): Promise<{ content: Array<{ type: string; text: string }> }> {\r\n switch (name) {\r\n case \"get_annotations\": {\r\n const pins = await store.list({\r\n pageUrl: args.pageUrl,\r\n status: args.status,\r\n });\r\n return {\r\n content: [\r\n {\r\n type: \"text\",\r\n text: JSON.stringify(pins, null, 2),\r\n },\r\n ],\r\n };\r\n }\r\n\r\n case \"resolve_annotation\": {\r\n await store.update(args.id, {\r\n status: {\r\n state: \"resolved\",\r\n changedAt: new Date().toISOString(),\r\n changedBy: \"agent\",\r\n },\r\n });\r\n return {\r\n content: [{ type: \"text\", text: `Resolved annotation ${args.id}` }],\r\n };\r\n }\r\n\r\n case \"create_annotation\": {\r\n const { randomUUID } = await import(\"crypto\");\r\n const now = new Date().toISOString();\r\n const pin = {\r\n id: randomUUID(),\r\n pageUrl: args.pageUrl,\r\n createdAt: now,\r\n updatedAt: now,\r\n comment: args.comment,\r\n element: {\r\n tagName: \"unknown\",\r\n classNames: [],\r\n selector: args.selector,\r\n boundingRect: { x: 0, y: 0, width: 0, height: 0 },\r\n },\r\n status: {\r\n state: \"open\" as const,\r\n changedAt: now,\r\n changedBy: \"agent\",\r\n },\r\n };\r\n await store.save(pin as any);\r\n return {\r\n content: [\r\n {\r\n type: \"text\",\r\n text: `Created annotation ${pin.id} on ${args.selector}`,\r\n },\r\n ],\r\n };\r\n }\r\n\r\n case \"delete_annotation\": {\r\n await store.delete(args.id);\r\n return {\r\n content: [{ type: \"text\", text: `Deleted annotation ${args.id}` }],\r\n };\r\n }\r\n\r\n default:\r\n return {\r\n content: [{ type: \"text\", text: `Unknown tool: ${name}` }],\r\n };\r\n }\r\n },\r\n };\r\n}\r\n"],"mappings":"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","names":["VALID_ID","randomUUID"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jami-studio/pinpoint",
|
|
3
|
+
"version": "0.1.12",
|
|
4
|
+
"description": "Visual feedback and annotation tool for agent-native web applications",
|
|
5
|
+
"homepage": "https://github.com/studio-jami/jami-studio#readme",
|
|
6
|
+
"bugs": {
|
|
7
|
+
"url": "https://github.com/studio-jami/jami-studio/issues"
|
|
8
|
+
},
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://github.com/studio-jami/jami-studio",
|
|
13
|
+
"directory": "packages/pinpoint"
|
|
14
|
+
},
|
|
15
|
+
"bin": {
|
|
16
|
+
"pinpoint": "./dist/cli.js"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"src/scripts",
|
|
21
|
+
".agents"
|
|
22
|
+
],
|
|
23
|
+
"type": "module",
|
|
24
|
+
"exports": {
|
|
25
|
+
".": {
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"browser": "./dist/index.browser.js",
|
|
28
|
+
"default": "./dist/index.js"
|
|
29
|
+
},
|
|
30
|
+
"./react": {
|
|
31
|
+
"types": "./dist/react.d.ts",
|
|
32
|
+
"import": "./dist/react.js"
|
|
33
|
+
},
|
|
34
|
+
"./primitives": {
|
|
35
|
+
"types": "./dist/primitives/index.d.ts",
|
|
36
|
+
"import": "./dist/primitives/index.js"
|
|
37
|
+
},
|
|
38
|
+
"./server": {
|
|
39
|
+
"types": "./dist/server/index.d.ts",
|
|
40
|
+
"import": "./dist/server/index.js"
|
|
41
|
+
},
|
|
42
|
+
"./types": {
|
|
43
|
+
"types": "./dist/types/index.d.ts",
|
|
44
|
+
"import": "./dist/types/index.js"
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"publishConfig": {
|
|
48
|
+
"access": "public",
|
|
49
|
+
"provenance": true
|
|
50
|
+
},
|
|
51
|
+
"scripts": {
|
|
52
|
+
"build": "tsup",
|
|
53
|
+
"dev": "tsup --watch",
|
|
54
|
+
"typecheck": "tsc --noEmit",
|
|
55
|
+
"test": "vitest --run --passWithNoTests"
|
|
56
|
+
},
|
|
57
|
+
"dependencies": {
|
|
58
|
+
"@medv/finder": "^4.0.2",
|
|
59
|
+
"bippy": "^0.5.32",
|
|
60
|
+
"element-source": "^0.0.3",
|
|
61
|
+
"solid-js": "^1.9.10",
|
|
62
|
+
"zod": "^4.3.6"
|
|
63
|
+
},
|
|
64
|
+
"devDependencies": {
|
|
65
|
+
"@agent-native/core": "workspace:*",
|
|
66
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
67
|
+
"@types/express": "^5.0.3",
|
|
68
|
+
"@types/node": "^24.2.1",
|
|
69
|
+
"@types/react": "^19.2.14",
|
|
70
|
+
"babel-preset-solid": "^1.9.10",
|
|
71
|
+
"esbuild-plugin-solid": "^0.6.0",
|
|
72
|
+
"express": "^5.1.0",
|
|
73
|
+
"tsup": "^8.4.0",
|
|
74
|
+
"typescript": "catalog:",
|
|
75
|
+
"typescript-7": "catalog:",
|
|
76
|
+
"vite-plugin-solid": "^2.11.0",
|
|
77
|
+
"vitest": "^4.1.5"
|
|
78
|
+
},
|
|
79
|
+
"peerDependencies": {
|
|
80
|
+
"@agent-native/core": ">=0.8.0",
|
|
81
|
+
"react": ">=18.0.0"
|
|
82
|
+
},
|
|
83
|
+
"peerDependenciesMeta": {
|
|
84
|
+
"react": {
|
|
85
|
+
"optional": true
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// @agent-native/pinpoint — Create annotation script
|
|
2
|
+
// MIT License
|
|
3
|
+
|
|
4
|
+
import { randomUUID } from "crypto";
|
|
5
|
+
|
|
6
|
+
import { parseArgs, fail } from "@agent-native/core/scripts";
|
|
7
|
+
|
|
8
|
+
import { FileStore } from "../storage/file-store.js";
|
|
9
|
+
import type { Pin } from "../types/index.js";
|
|
10
|
+
|
|
11
|
+
export default async function (args: string[]) {
|
|
12
|
+
const { pageUrl, selector, comment, author } = parseArgs(args) as {
|
|
13
|
+
pageUrl?: string;
|
|
14
|
+
selector?: string;
|
|
15
|
+
comment?: string;
|
|
16
|
+
author?: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
if (!pageUrl) fail("--pageUrl is required");
|
|
20
|
+
if (!selector) fail("--selector is required");
|
|
21
|
+
if (!comment) fail("--comment is required");
|
|
22
|
+
|
|
23
|
+
const now = new Date().toISOString();
|
|
24
|
+
const pin: Pin = {
|
|
25
|
+
id: randomUUID(),
|
|
26
|
+
pageUrl: pageUrl!,
|
|
27
|
+
createdAt: now,
|
|
28
|
+
updatedAt: now,
|
|
29
|
+
author,
|
|
30
|
+
comment: comment!,
|
|
31
|
+
element: {
|
|
32
|
+
tagName: "unknown",
|
|
33
|
+
classNames: [],
|
|
34
|
+
selector: selector!,
|
|
35
|
+
boundingRect: { x: 0, y: 0, width: 0, height: 0 },
|
|
36
|
+
},
|
|
37
|
+
status: { state: "open", changedAt: now, changedBy: "agent" },
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const store = new FileStore();
|
|
41
|
+
await store.save(pin);
|
|
42
|
+
console.log(`Created annotation ${pin.id} on ${pageUrl} → ${selector}`);
|
|
43
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// @agent-native/pinpoint — Delete annotation script
|
|
2
|
+
// MIT License
|
|
3
|
+
|
|
4
|
+
import { parseArgs, fail } from "@agent-native/core/scripts";
|
|
5
|
+
|
|
6
|
+
import { FileStore } from "../storage/file-store.js";
|
|
7
|
+
|
|
8
|
+
export default async function (args: string[]) {
|
|
9
|
+
const { id } = parseArgs(args) as { id?: string };
|
|
10
|
+
|
|
11
|
+
if (!id) fail("--id is required");
|
|
12
|
+
|
|
13
|
+
const store = new FileStore();
|
|
14
|
+
await store.delete(id!);
|
|
15
|
+
console.log(`Deleted annotation ${id}`);
|
|
16
|
+
}
|