@narumitw/pi-image-drop 0.28.0 โ 0.31.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 +9 -3
- package/package.json +19 -8
- package/src/runtime.ts +1 -4
- package/src/server.ts +8 -2
- package/src/web/app.js +97 -537
- package/src/web/index.html +2 -102
- package/src/web/state.js +5 -114
- package/src/web/styles.css +2 -538
- package/src/web/ui/app.tsx +178 -0
- package/src/web/ui/client.ts +377 -0
- package/src/web/ui/components.tsx +615 -0
- package/src/web/ui/state.ts +149 -0
- package/src/web/ui/styles.css +572 -0
- package/src/web/ui/types.ts +56 -0
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
`@narumitw/pi-image-drop` adds one `/image-drop` command to the latest [Pi Coding Agent](https://pi.dev). It serves a private loopback page where you can paste, drop, choose, preview, reorder, retry, and remove local images. The ordered batch is attached to your next non-empty interactive Pi message.
|
|
6
6
|
|
|
7
|
-
The page never contains a prompt or Attach button: Pi remains the only place where messages are written and sent.
|
|
7
|
+
The page never contains a prompt or Attach button: Pi remains the only place where messages are written and sent. Its React and TypeScript frontend uses Radix Themes, Primitives, Colors, and Icons, and ships as local bundled assets with no CDN or runtime build step.
|
|
8
8
|
|
|
9
9
|
## โจ Features
|
|
10
10
|
|
|
@@ -14,6 +14,7 @@ The page never contains a prompt or Attach button: Pi remains the only place whe
|
|
|
14
14
|
- Supports PNG, JPEG, WebP, GIF, BMP, TIFF, HEIC/HEIF, and AVIF input.
|
|
15
15
|
- Applies orientation, strips private metadata, and enforces Pi-compatible image limits.
|
|
16
16
|
- Keeps bounded sent-image history for explicit re-attachment during the live session.
|
|
17
|
+
- Uses Radix UI for an adaptive light/dark design system, accessible dialogs and disclosures, semantic colors, and consistent action icons.
|
|
17
18
|
- Reports batch state above Pi's editor and can start automatically with each session.
|
|
18
19
|
|
|
19
20
|
## ๐ฆ Install
|
|
@@ -147,7 +148,11 @@ src/images.ts bounded image processing
|
|
|
147
148
|
src/server.ts authenticated loopback HTTP/SSE server
|
|
148
149
|
src/settings.ts extension settings
|
|
149
150
|
src/pi-settings.ts effective Pi image settings adapter
|
|
150
|
-
src/web/
|
|
151
|
+
src/web/ui/ authored React and TypeScript browser source
|
|
152
|
+
src/web/app.js generated bundled React application
|
|
153
|
+
src/web/state.js generated compatibility helper module
|
|
154
|
+
src/web/styles.css generated Radix Themes, Colors, and local styles
|
|
155
|
+
src/web/index.html minimal authenticated React shell
|
|
151
156
|
```
|
|
152
157
|
|
|
153
158
|
## ๐งช Development
|
|
@@ -155,13 +160,14 @@ src/web/ framework-free browser page
|
|
|
155
160
|
From the repository root:
|
|
156
161
|
|
|
157
162
|
```bash
|
|
163
|
+
npm --workspace @narumitw/pi-image-drop run build:web
|
|
158
164
|
npm --workspace @narumitw/pi-image-drop run check
|
|
159
165
|
npm test
|
|
160
166
|
just try image-drop
|
|
161
167
|
just pack image-drop
|
|
162
168
|
```
|
|
163
169
|
|
|
164
|
-
The dry-run package must contain the manifest, license, README, TypeScript sources, and static web assets, but no tests, fixtures, image bytes, or `node_modules`.
|
|
170
|
+
Edit browser code under `src/web/ui/`, then run `build:web`. The package typecheck runs `check:web`, which rebuilds in a temporary directory and rejects stale generated assets. The dry-run package must contain the manifest, license, README, TypeScript/TSX sources, and bundled static web assets, but no tests, fixtures, image bytes, build scripts, or `node_modules`.
|
|
165
171
|
|
|
166
172
|
## ๐ Publishing
|
|
167
173
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@narumitw/pi-image-drop",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.0",
|
|
4
4
|
"description": "Pi extension for staging browser images and attaching them to the next message.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -24,13 +24,21 @@
|
|
|
24
24
|
]
|
|
25
25
|
},
|
|
26
26
|
"scripts": {
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
27
|
+
"build:web": "node scripts/build-web.mjs",
|
|
28
|
+
"check": "biome check --vcs-use-ignore-file=false src/*.ts src/web/index.html src/web/ui test scripts package.json tsconfig.json && npm run typecheck",
|
|
29
|
+
"check:web": "node scripts/build-web.mjs --check",
|
|
30
|
+
"format": "biome check --write --vcs-use-ignore-file=false src/*.ts src/web/index.html src/web/ui test scripts package.json tsconfig.json && npm run build:web",
|
|
31
|
+
"typecheck": "npm run check:web && tsc --noEmit"
|
|
30
32
|
},
|
|
31
33
|
"dependencies": {
|
|
34
|
+
"@radix-ui/colors": "3.0.0",
|
|
35
|
+
"@radix-ui/react-icons": "1.3.2",
|
|
36
|
+
"@radix-ui/themes": "3.3.0",
|
|
32
37
|
"bmp-js": "^0.1.0",
|
|
33
38
|
"heic-decode": "^2.1.0",
|
|
39
|
+
"radix-ui": "1.6.7",
|
|
40
|
+
"react": "19.2.8",
|
|
41
|
+
"react-dom": "19.2.8",
|
|
34
42
|
"sharp": "^0.35.3"
|
|
35
43
|
},
|
|
36
44
|
"peerDependencies": {
|
|
@@ -38,10 +46,13 @@
|
|
|
38
46
|
"@earendil-works/pi-coding-agent": "*"
|
|
39
47
|
},
|
|
40
48
|
"devDependencies": {
|
|
41
|
-
"@biomejs/biome": "2.5.
|
|
42
|
-
"@earendil-works/pi-ai": "0.
|
|
43
|
-
"@earendil-works/pi-coding-agent": "0.
|
|
44
|
-
"
|
|
49
|
+
"@biomejs/biome": "2.5.5",
|
|
50
|
+
"@earendil-works/pi-ai": "0.82.1",
|
|
51
|
+
"@earendil-works/pi-coding-agent": "0.82.1",
|
|
52
|
+
"@types/react": "19.2.17",
|
|
53
|
+
"@types/react-dom": "19.2.3",
|
|
54
|
+
"esbuild": "0.28.1",
|
|
55
|
+
"typescript": "7.0.2"
|
|
45
56
|
},
|
|
46
57
|
"repository": {
|
|
47
58
|
"type": "git",
|
package/src/runtime.ts
CHANGED
|
@@ -248,10 +248,7 @@ export class ImageDropRuntime {
|
|
|
248
248
|
if (generation !== this.generation) return false;
|
|
249
249
|
if (batch.publicState().phase !== "ready") {
|
|
250
250
|
this.restoreEditor(ctx, text);
|
|
251
|
-
ctx.ui.notify(
|
|
252
|
-
"Images could not be updated for the current auto-resize setting.",
|
|
253
|
-
"warning",
|
|
254
|
-
);
|
|
251
|
+
ctx.ui.notify("Images could not be updated for the current auto-resize setting.", "warning");
|
|
255
252
|
this.updateWidget(ctx);
|
|
256
253
|
return false;
|
|
257
254
|
}
|
package/src/server.ts
CHANGED
|
@@ -11,6 +11,7 @@ import type { ImageDropSettings } from "./settings.js";
|
|
|
11
11
|
|
|
12
12
|
const JSON_LIMIT = 64 * 1024;
|
|
13
13
|
const CLIENT_ID = /^[A-Za-z0-9_-]{1,80}$/;
|
|
14
|
+
const CSP_NONCE_PLACEHOLDER = "__PI_CSP_NONCE__";
|
|
14
15
|
|
|
15
16
|
export interface ImageDropServerOptions {
|
|
16
17
|
batch: BatchStore;
|
|
@@ -47,6 +48,7 @@ export class ImageDropServer {
|
|
|
47
48
|
readonly origin: string;
|
|
48
49
|
private bootstrapToken?: string;
|
|
49
50
|
private readonly sessionSecret = token();
|
|
51
|
+
private readonly cspNonce = token();
|
|
50
52
|
private readonly cookieName = `pi_image_drop_${randomBytes(8).toString("hex")}`;
|
|
51
53
|
private readonly abortController = new AbortController();
|
|
52
54
|
private readonly sockets = new Set<Socket>();
|
|
@@ -463,7 +465,11 @@ export class ImageDropServer {
|
|
|
463
465
|
}
|
|
464
466
|
|
|
465
467
|
private async asset(response: ServerResponse, name: string, contentType: string): Promise<void> {
|
|
466
|
-
const
|
|
468
|
+
const asset = await readAsset(name);
|
|
469
|
+
const data =
|
|
470
|
+
name === "index.html"
|
|
471
|
+
? Buffer.from(asset.toString("utf8").replaceAll(CSP_NONCE_PLACEHOLDER, this.cspNonce))
|
|
472
|
+
: asset;
|
|
467
473
|
response.setHeader("Content-Type", contentType);
|
|
468
474
|
response.setHeader("Content-Length", data.byteLength);
|
|
469
475
|
response.writeHead(200).end(data);
|
|
@@ -473,7 +479,7 @@ export class ImageDropServer {
|
|
|
473
479
|
response.setHeader("Cache-Control", "no-store");
|
|
474
480
|
response.setHeader(
|
|
475
481
|
"Content-Security-Policy",
|
|
476
|
-
|
|
482
|
+
`default-src 'self'; img-src 'self' blob:; script-src 'self'; style-src 'self' 'nonce-${this.cspNonce}'; connect-src 'self'; frame-ancestors 'none'; base-uri 'none'; form-action 'none'`,
|
|
477
483
|
);
|
|
478
484
|
response.setHeader("Referrer-Policy", "no-referrer");
|
|
479
485
|
response.setHeader("X-Content-Type-Options", "nosniff");
|