@agent-native/pinpoint 0.1.12 → 0.1.14
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 +4 -0
- package/dist/{chunk-HC724C43.js → chunk-P4AZ2AAL.js} +21 -5
- package/dist/chunk-P4AZ2AAL.js.map +1 -0
- package/dist/index.browser.js +1 -1
- package/dist/react.js +1 -1
- package/package.json +6 -2
- package/src/scripts/create-pin.spec.ts +117 -0
- package/src/scripts/delete-pin.spec.ts +100 -0
- package/src/scripts/update-pin.spec.ts +138 -0
- package/dist/chunk-HC724C43.js.map +0 -1
package/dist/index.browser.js
CHANGED
package/dist/react.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/pinpoint",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.14",
|
|
4
4
|
"description": "Visual feedback and annotation tool for agent-native web applications",
|
|
5
5
|
"homepage": "https://github.com/BuilderIO/agent-native#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -21,6 +21,10 @@
|
|
|
21
21
|
".agents"
|
|
22
22
|
],
|
|
23
23
|
"type": "module",
|
|
24
|
+
"sideEffects": [
|
|
25
|
+
"dist/cli.js",
|
|
26
|
+
"dist/index.browser.js"
|
|
27
|
+
],
|
|
24
28
|
"exports": {
|
|
25
29
|
".": {
|
|
26
30
|
"types": "./dist/index.d.ts",
|
|
@@ -68,7 +72,7 @@
|
|
|
68
72
|
"typescript-7": "npm:typescript@^7.0.2",
|
|
69
73
|
"vite-plugin-solid": "^2.11.0",
|
|
70
74
|
"vitest": "^4.1.5",
|
|
71
|
-
"@agent-native/core": "0.
|
|
75
|
+
"@agent-native/core": "0.109.4"
|
|
72
76
|
},
|
|
73
77
|
"peerDependencies": {
|
|
74
78
|
"@agent-native/core": ">=0.8.0",
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// @agent-native/pinpoint — create-pin script tests
|
|
2
|
+
// MIT License
|
|
3
|
+
//
|
|
4
|
+
// createPin() always constructs `new FileStore()` with no explicit data
|
|
5
|
+
// directory, which resolves the storage path relative to `process.cwd()`.
|
|
6
|
+
// To exercise it against a throwaway directory (matching file-store.spec.ts's
|
|
7
|
+
// temp-dir style) without touching source, each test chdirs into a fresh
|
|
8
|
+
// temp directory before invoking the script and restores the original cwd
|
|
9
|
+
// afterward.
|
|
10
|
+
|
|
11
|
+
import fs from "node:fs";
|
|
12
|
+
import os from "node:os";
|
|
13
|
+
import path from "node:path";
|
|
14
|
+
|
|
15
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
16
|
+
|
|
17
|
+
import { FileStore } from "../storage/file-store.js";
|
|
18
|
+
import createPin from "./create-pin.js";
|
|
19
|
+
|
|
20
|
+
const tmpRoots: string[] = [];
|
|
21
|
+
const originalCwd = process.cwd();
|
|
22
|
+
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
process.chdir(originalCwd);
|
|
25
|
+
for (const root of tmpRoots.splice(0)) {
|
|
26
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
function chdirTmp(): string {
|
|
31
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), "pinpoint-create-pin-"));
|
|
32
|
+
tmpRoots.push(root);
|
|
33
|
+
process.chdir(root);
|
|
34
|
+
return root;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
describe("create-pin script", () => {
|
|
38
|
+
it("writes a new pin with the fields derived from CLI args", async () => {
|
|
39
|
+
const root = chdirTmp();
|
|
40
|
+
|
|
41
|
+
await createPin([
|
|
42
|
+
"--pageUrl",
|
|
43
|
+
"https://example.com/dashboard",
|
|
44
|
+
"--selector",
|
|
45
|
+
".card:nth-child(2)",
|
|
46
|
+
"--comment",
|
|
47
|
+
"This card is misaligned",
|
|
48
|
+
"--author",
|
|
49
|
+
"steve",
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
const store = new FileStore(path.join(root, "data/pins"));
|
|
53
|
+
const pins = await store.list();
|
|
54
|
+
|
|
55
|
+
expect(pins).toHaveLength(1);
|
|
56
|
+
const pin = pins[0]!;
|
|
57
|
+
expect(pin.pageUrl).toBe("https://example.com/dashboard");
|
|
58
|
+
expect(pin.comment).toBe("This card is misaligned");
|
|
59
|
+
expect(pin.author).toBe("steve");
|
|
60
|
+
expect(pin.element.selector).toBe(".card:nth-child(2)");
|
|
61
|
+
expect(pin.element.tagName).toBe("unknown");
|
|
62
|
+
expect(pin.status).toEqual({
|
|
63
|
+
state: "open",
|
|
64
|
+
changedAt: pin.status.changedAt,
|
|
65
|
+
changedBy: "agent",
|
|
66
|
+
});
|
|
67
|
+
expect(pin.createdAt).toBe(pin.updatedAt);
|
|
68
|
+
expect(pin.id).toMatch(
|
|
69
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/,
|
|
70
|
+
);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("omits author when not provided", async () => {
|
|
74
|
+
const root = chdirTmp();
|
|
75
|
+
|
|
76
|
+
await createPin([
|
|
77
|
+
"--pageUrl",
|
|
78
|
+
"https://example.com",
|
|
79
|
+
"--selector",
|
|
80
|
+
"#hero",
|
|
81
|
+
"--comment",
|
|
82
|
+
"note",
|
|
83
|
+
]);
|
|
84
|
+
|
|
85
|
+
const store = new FileStore(path.join(root, "data/pins"));
|
|
86
|
+
const [pin] = await store.list();
|
|
87
|
+
expect(pin!.author).toBeUndefined();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("rejects when --pageUrl is missing", async () => {
|
|
91
|
+
chdirTmp();
|
|
92
|
+
await expect(
|
|
93
|
+
createPin(["--selector", "#hero", "--comment", "note"]),
|
|
94
|
+
).rejects.toThrow("--pageUrl is required");
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("rejects when --selector is missing", async () => {
|
|
98
|
+
chdirTmp();
|
|
99
|
+
await expect(
|
|
100
|
+
createPin(["--pageUrl", "https://example.com", "--comment", "note"]),
|
|
101
|
+
).rejects.toThrow("--selector is required");
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("rejects when --comment is missing", async () => {
|
|
105
|
+
chdirTmp();
|
|
106
|
+
await expect(
|
|
107
|
+
createPin(["--pageUrl", "https://example.com", "--selector", "#hero"]),
|
|
108
|
+
).rejects.toThrow("--comment is required");
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("checks pageUrl before selector when both are missing", async () => {
|
|
112
|
+
chdirTmp();
|
|
113
|
+
await expect(createPin(["--comment", "note"])).rejects.toThrow(
|
|
114
|
+
"--pageUrl is required",
|
|
115
|
+
);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// @agent-native/pinpoint — delete-pin script tests
|
|
2
|
+
// MIT License
|
|
3
|
+
//
|
|
4
|
+
// Same temp-dir-via-chdir approach as create-pin.spec.ts / update-pin.spec.ts,
|
|
5
|
+
// since deletePin() always constructs `new FileStore()` with the default
|
|
6
|
+
// `data/pins` path resolved against `process.cwd()`.
|
|
7
|
+
|
|
8
|
+
import { randomUUID } from "node:crypto";
|
|
9
|
+
import fs from "node:fs";
|
|
10
|
+
import os from "node:os";
|
|
11
|
+
import path from "node:path";
|
|
12
|
+
|
|
13
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
14
|
+
|
|
15
|
+
import { FileStore } from "../storage/file-store.js";
|
|
16
|
+
import type { Pin } from "../types/index.js";
|
|
17
|
+
import deletePin from "./delete-pin.js";
|
|
18
|
+
|
|
19
|
+
const tmpRoots: string[] = [];
|
|
20
|
+
const originalCwd = process.cwd();
|
|
21
|
+
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
process.chdir(originalCwd);
|
|
24
|
+
for (const root of tmpRoots.splice(0)) {
|
|
25
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
function chdirTmp(): string {
|
|
30
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), "pinpoint-delete-pin-"));
|
|
31
|
+
tmpRoots.push(root);
|
|
32
|
+
process.chdir(root);
|
|
33
|
+
return root;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function makePin(overrides: Partial<Pin> = {}): Pin {
|
|
37
|
+
const now = new Date().toISOString();
|
|
38
|
+
return {
|
|
39
|
+
id: randomUUID(),
|
|
40
|
+
pageUrl: "https://example.com/page",
|
|
41
|
+
createdAt: now,
|
|
42
|
+
updatedAt: now,
|
|
43
|
+
comment: "note",
|
|
44
|
+
element: {
|
|
45
|
+
tagName: "DIV",
|
|
46
|
+
classNames: [],
|
|
47
|
+
selector: ".card",
|
|
48
|
+
boundingRect: { x: 0, y: 0, width: 10, height: 10 },
|
|
49
|
+
},
|
|
50
|
+
status: { state: "open", changedAt: now },
|
|
51
|
+
...overrides,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
describe("delete-pin script", () => {
|
|
56
|
+
it("removes the pin file for the given id", async () => {
|
|
57
|
+
const root = chdirTmp();
|
|
58
|
+
const store = new FileStore(path.join(root, "data/pins"));
|
|
59
|
+
const pin = makePin();
|
|
60
|
+
await store.save(pin);
|
|
61
|
+
expect(await store.list()).toHaveLength(1);
|
|
62
|
+
|
|
63
|
+
await deletePin(["--id", pin.id]);
|
|
64
|
+
|
|
65
|
+
expect(await store.list()).toEqual([]);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("leaves other pins untouched", async () => {
|
|
69
|
+
const root = chdirTmp();
|
|
70
|
+
const store = new FileStore(path.join(root, "data/pins"));
|
|
71
|
+
const pinA = makePin();
|
|
72
|
+
const pinB = makePin();
|
|
73
|
+
await store.save(pinA);
|
|
74
|
+
await store.save(pinB);
|
|
75
|
+
|
|
76
|
+
await deletePin(["--id", pinA.id]);
|
|
77
|
+
|
|
78
|
+
const remaining = await store.list();
|
|
79
|
+
expect(remaining).toHaveLength(1);
|
|
80
|
+
expect(remaining[0]!.id).toBe(pinB.id);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("rejects when --id is missing", async () => {
|
|
84
|
+
chdirTmp();
|
|
85
|
+
await expect(deletePin([])).rejects.toThrow("--id is required");
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("resolves without throwing when the id does not exist", async () => {
|
|
89
|
+
chdirTmp();
|
|
90
|
+
await expect(deletePin(["--id", randomUUID()])).resolves.toBeUndefined();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("resolves without throwing for a malformed id (FileStore.delete() swallows the 'Invalid pin ID' error internally)", async () => {
|
|
94
|
+
// Unlike update-pin, FileStore.delete() computes the (validated) file
|
|
95
|
+
// path *inside* its own try/catch, so an invalid id is caught silently
|
|
96
|
+
// rather than propagating — this documents that asymmetry.
|
|
97
|
+
chdirTmp();
|
|
98
|
+
await expect(deletePin(["--id", "../escape"])).resolves.toBeUndefined();
|
|
99
|
+
});
|
|
100
|
+
});
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// @agent-native/pinpoint — update-pin script tests
|
|
2
|
+
// MIT License
|
|
3
|
+
//
|
|
4
|
+
// Same temp-dir-via-chdir approach as create-pin.spec.ts, since updatePin()
|
|
5
|
+
// always constructs `new FileStore()` with the default `data/pins` path
|
|
6
|
+
// resolved against `process.cwd()`.
|
|
7
|
+
|
|
8
|
+
import { randomUUID } from "node:crypto";
|
|
9
|
+
import fs from "node:fs";
|
|
10
|
+
import os from "node:os";
|
|
11
|
+
import path from "node:path";
|
|
12
|
+
|
|
13
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
14
|
+
|
|
15
|
+
import { FileStore } from "../storage/file-store.js";
|
|
16
|
+
import type { Pin } from "../types/index.js";
|
|
17
|
+
import updatePin from "./update-pin.js";
|
|
18
|
+
|
|
19
|
+
const tmpRoots: string[] = [];
|
|
20
|
+
const originalCwd = process.cwd();
|
|
21
|
+
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
process.chdir(originalCwd);
|
|
24
|
+
for (const root of tmpRoots.splice(0)) {
|
|
25
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
function chdirTmp(): string {
|
|
30
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), "pinpoint-update-pin-"));
|
|
31
|
+
tmpRoots.push(root);
|
|
32
|
+
process.chdir(root);
|
|
33
|
+
return root;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function makePin(overrides: Partial<Pin> = {}): Pin {
|
|
37
|
+
const now = new Date().toISOString();
|
|
38
|
+
return {
|
|
39
|
+
id: randomUUID(),
|
|
40
|
+
pageUrl: "https://example.com/page",
|
|
41
|
+
createdAt: now,
|
|
42
|
+
updatedAt: now,
|
|
43
|
+
comment: "original comment",
|
|
44
|
+
element: {
|
|
45
|
+
tagName: "DIV",
|
|
46
|
+
classNames: ["card"],
|
|
47
|
+
selector: ".card",
|
|
48
|
+
boundingRect: { x: 0, y: 0, width: 10, height: 10 },
|
|
49
|
+
},
|
|
50
|
+
status: { state: "open", changedAt: now, changedBy: "user" },
|
|
51
|
+
...overrides,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
describe("update-pin script", () => {
|
|
56
|
+
it("updates the comment and bumps updatedAt, leaving status untouched", async () => {
|
|
57
|
+
const root = chdirTmp();
|
|
58
|
+
const store = new FileStore(path.join(root, "data/pins"));
|
|
59
|
+
const pin = makePin();
|
|
60
|
+
await store.save(pin);
|
|
61
|
+
|
|
62
|
+
await updatePin(["--id", pin.id, "--comment", "revised comment"]);
|
|
63
|
+
|
|
64
|
+
const [updated] = await store.list();
|
|
65
|
+
expect(updated!.comment).toBe("revised comment");
|
|
66
|
+
expect(updated!.updatedAt).not.toBe(pin.updatedAt);
|
|
67
|
+
expect(updated!.status).toEqual(pin.status);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("updates status and always stamps changedBy as 'agent', overwriting any prior value", async () => {
|
|
71
|
+
const root = chdirTmp();
|
|
72
|
+
const store = new FileStore(path.join(root, "data/pins"));
|
|
73
|
+
// Default fixture status already has changedBy: "user" — confirms the
|
|
74
|
+
// script always overwrites it with "agent" rather than preserving it.
|
|
75
|
+
const pin = makePin();
|
|
76
|
+
await store.save(pin);
|
|
77
|
+
|
|
78
|
+
await updatePin(["--id", pin.id, "--status", "resolved"]);
|
|
79
|
+
|
|
80
|
+
const [updated] = await store.list();
|
|
81
|
+
expect(updated!.status.state).toBe("resolved");
|
|
82
|
+
expect(updated!.status.changedBy).toBe("agent");
|
|
83
|
+
expect(updated!.comment).toBe(pin.comment); // untouched
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("can update comment and status together in one call", async () => {
|
|
87
|
+
const root = chdirTmp();
|
|
88
|
+
const store = new FileStore(path.join(root, "data/pins"));
|
|
89
|
+
const pin = makePin();
|
|
90
|
+
await store.save(pin);
|
|
91
|
+
|
|
92
|
+
await updatePin([
|
|
93
|
+
"--id",
|
|
94
|
+
pin.id,
|
|
95
|
+
"--comment",
|
|
96
|
+
"both changed",
|
|
97
|
+
"--status",
|
|
98
|
+
"dismissed",
|
|
99
|
+
]);
|
|
100
|
+
|
|
101
|
+
const [updated] = await store.list();
|
|
102
|
+
expect(updated!.comment).toBe("both changed");
|
|
103
|
+
expect(updated!.status.state).toBe("dismissed");
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("rejects when --id is missing", async () => {
|
|
107
|
+
chdirTmp();
|
|
108
|
+
await expect(updatePin(["--comment", "x"])).rejects.toThrow(
|
|
109
|
+
"--id is required",
|
|
110
|
+
);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("rejects when neither --comment nor --status is given", async () => {
|
|
114
|
+
chdirTmp();
|
|
115
|
+
await expect(updatePin(["--id", randomUUID()])).rejects.toThrow(
|
|
116
|
+
"--comment or --status is required",
|
|
117
|
+
);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("resolves without writing a file when the pin id does not exist (FileStore.update() is a silent no-op)", async () => {
|
|
121
|
+
const root = chdirTmp();
|
|
122
|
+
const missingId = randomUUID();
|
|
123
|
+
|
|
124
|
+
await expect(
|
|
125
|
+
updatePin(["--id", missingId, "--comment", "irrelevant"]),
|
|
126
|
+
).resolves.toBeUndefined();
|
|
127
|
+
|
|
128
|
+
const store = new FileStore(path.join(root, "data/pins"));
|
|
129
|
+
expect(await store.list()).toEqual([]);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("rejects with 'Invalid pin ID' for a malformed id (unlike delete, FileStore.update does not swallow this)", async () => {
|
|
133
|
+
chdirTmp();
|
|
134
|
+
await expect(
|
|
135
|
+
updatePin(["--id", "../escape", "--comment", "x"]),
|
|
136
|
+
).rejects.toThrow(/Invalid pin ID/);
|
|
137
|
+
});
|
|
138
|
+
});
|