@hazem3500/palette 0.9.108 → 0.9.110

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 (115) hide show
  1. package/NODE.md +32 -30
  2. package/README.md +58 -46
  3. package/cli/agent.mjs +93 -69
  4. package/cli/config.mjs +17 -1
  5. package/cli/design-edit.mjs +9 -7
  6. package/cli/index.mjs +26 -24
  7. package/cli/snapshots.mjs +3 -3
  8. package/cli/workflow.mjs +15 -7
  9. package/dist/{accent-picker-TTIZ65XF.js → accent-picker-PPVPURA7.js} +1 -1
  10. package/dist/account-tokens-LBRVZAEJ.js +1 -0
  11. package/dist/adapters/css-variables.js +1 -1
  12. package/dist/adapters/tailwind.js +1 -1
  13. package/dist/chunk-2FX2JEMO.js +1 -0
  14. package/dist/chunk-4HBBEVFK.js +1 -0
  15. package/dist/chunk-5HZI7HRR.js +1 -0
  16. package/dist/chunk-DU7H4TEX.js +1 -0
  17. package/dist/chunk-FGZXQUAD.js +1 -0
  18. package/dist/{chunk-AYGLN3MI.js → chunk-HLXSRXTK.js} +1 -1
  19. package/dist/{chunk-DZRM3YUQ.js → chunk-J47R52WX.js} +1 -1
  20. package/dist/chunk-KELFDOEC.js +1 -0
  21. package/dist/{chunk-2J3IID4N.js → chunk-KLPQTFVH.js} +1 -1
  22. package/dist/chunk-ONUORDYE.js +1 -0
  23. package/dist/chunk-PF4NYFZK.js +1 -0
  24. package/dist/chunk-S6NEYT36.js +1 -0
  25. package/dist/chunk-U55ASP2Y.js +1 -0
  26. package/dist/chunk-VX6XCU4E.js +1 -0
  27. package/dist/color-editor-QPJQD36F.js +1 -0
  28. package/dist/config.d.ts +1 -1
  29. package/dist/design-comment-BF2KGPWU.js +1 -0
  30. package/dist/{design-export-BNNHI4JP.js → design-export-Y2GU3KER.js} +1 -1
  31. package/dist/design-geometry-HJLEXLPO.js +1 -0
  32. package/dist/design-mode-styles.d.ts +1 -1
  33. package/dist/design-mode.js +1 -1
  34. package/dist/design-panel-ZQS4WQCI.js +1 -0
  35. package/dist/design-panel-styles.d.ts +1 -1
  36. package/dist/design-popup.d.ts +1 -1
  37. package/dist/design-system.d.ts +5 -4
  38. package/dist/{field-controls-4ROTPXMO.js → field-controls-4AIIXPNQ.js} +1 -1
  39. package/dist/{frame-iframe-WBEP3R6S.js → frame-iframe-PYLYMCOK.js} +1 -1
  40. package/dist/frame-size-TF6SIUFO.js +1 -0
  41. package/dist/frame-target.d.ts +5 -2
  42. package/dist/host-motion.d.ts +1 -1
  43. package/dist/index.js +1 -1
  44. package/dist/inline-text-editor-MK6FHDAM.js +1 -0
  45. package/dist/live-colors-D426RBEC.js +1 -0
  46. package/dist/live-colors.d.ts +1 -1
  47. package/dist/node.mjs +102 -75
  48. package/dist/onboarding-6OSADOWE.js +1 -0
  49. package/dist/{pin-groups-YAYFPJYA.js → pin-groups-GF53RTHP.js} +1 -1
  50. package/dist/{pin-move-PG54WWPU.js → pin-move-G6EWHLDO.js} +1 -1
  51. package/dist/public-env.d.ts +49 -11
  52. package/dist/react-props-panel-ZSKQ44GS.js +1 -0
  53. package/dist/react.d.ts +2 -0
  54. package/dist/react.js +1 -1
  55. package/dist/select-menu-OZVNHZ73.js +1 -0
  56. package/dist/server.js +80 -57
  57. package/dist/setup.js +1 -1
  58. package/dist/shell-mode-PLV6F474.js +1 -0
  59. package/dist/shell-session.d.ts +5 -3
  60. package/dist/shell.d.ts +1 -1
  61. package/dist/shell.js +1 -1
  62. package/dist/shortcut-help-DIMOT2ZK.js +1 -0
  63. package/dist/snapshot-3PSQKXUV.js +1 -0
  64. package/dist/snapshot.d.ts +1 -1
  65. package/dist/source-info.d.ts +1 -1
  66. package/dist/storage-keys.d.ts +5 -0
  67. package/dist/tag-editor-B3I7WD4N.js +1 -0
  68. package/dist/thread-snapshot-ML2RZVID.js +1 -0
  69. package/dist/token-custom-CASAQWYW.js +1 -0
  70. package/dist/token-page-NIB5HAVS.js +1 -0
  71. package/dist/token-picker-AJ5E5IN5.js +1 -0
  72. package/dist/{toolbar-runtime-QCZONSWE.js → toolbar-runtime-TZWY2KH3.js} +1 -1
  73. package/dist/typed-custom.d.ts +1 -1
  74. package/dist/types.d.ts +1 -1
  75. package/package.json +1 -1
  76. package/server/account-page.ts +6 -6
  77. package/server/iap.ts +2 -2
  78. package/server/index.ts +30 -24
  79. package/server/project-management.ts +5 -5
  80. package/server/proxy-auth.ts +17 -16
  81. package/server/public-env.ts +67 -11
  82. package/server/setup-client.txt +173 -173
  83. package/server/setup-page.ts +1 -1
  84. package/server/tokens.ts +9 -6
  85. package/server/webhooks.ts +9 -5
  86. package/src/types.ts +1 -1
  87. package/dist/account-tokens-PINQNSNE.js +0 -1
  88. package/dist/chunk-4KEVPMJJ.js +0 -1
  89. package/dist/chunk-4XQD75A2.js +0 -1
  90. package/dist/chunk-57MG3M5P.js +0 -1
  91. package/dist/chunk-5OVDW2RU.js +0 -1
  92. package/dist/chunk-6HM4JQLV.js +0 -1
  93. package/dist/chunk-DLC3CRFV.js +0 -1
  94. package/dist/chunk-DVYSFT6O.js +0 -1
  95. package/dist/chunk-FD4GACV2.js +0 -1
  96. package/dist/chunk-IQOPM3JF.js +0 -1
  97. package/dist/chunk-UEZCLURX.js +0 -1
  98. package/dist/color-editor-ATWWSENS.js +0 -1
  99. package/dist/design-comment-RXUCDTYA.js +0 -1
  100. package/dist/design-geometry-DPMHGQEK.js +0 -1
  101. package/dist/design-panel-HTN6LR3V.js +0 -1
  102. package/dist/frame-size-MRCN6XYD.js +0 -1
  103. package/dist/inline-text-editor-AMFRKW5Z.js +0 -1
  104. package/dist/live-colors-GWOVVQSO.js +0 -1
  105. package/dist/onboarding-BVTCFEHL.js +0 -1
  106. package/dist/react-props-panel-4XO3NSX3.js +0 -1
  107. package/dist/select-menu-QAYV7NKJ.js +0 -1
  108. package/dist/shell-mode-SUYLWDWW.js +0 -1
  109. package/dist/shortcut-help-XRDNX7MZ.js +0 -1
  110. package/dist/snapshot-V264ZMQG.js +0 -1
  111. package/dist/tag-editor-MMAMOEI4.js +0 -1
  112. package/dist/thread-snapshot-HLTNHS4N.js +0 -1
  113. package/dist/token-custom-UCA6CBOV.js +0 -1
  114. package/dist/token-page-PGPWPUKU.js +0 -1
  115. package/dist/token-picker-WMWUKLKJ.js +0 -1
package/NODE.md CHANGED
@@ -17,13 +17,13 @@ The browser widget and CLI use the same API protocol on Cloudflare and Node. Thi
17
17
  --google-client-id YOUR_GOOGLE_CLIENT_ID
18
18
  ```
19
19
 
20
- This writes `.komo/node.env` and `.komo/owner-key`, adds them to `.gitignore`, and configures the existing widget and agent CLI. The preview wildcard matches one hostname label under a domain you control. The endpoint is the **public API origin**, not a preview URL. For local development it may use HTTP localhost. Keep the generated owner key private.
20
+ This writes `.palette/node.env` and `.palette/owner-key`, adds them to `.gitignore`, and configures the existing widget and agent CLI. The preview wildcard matches one hostname label under a domain you control. The endpoint is the **public API origin**, not a preview URL. For local development it may use HTTP localhost. Keep the generated owner key private.
21
21
 
22
- 2. Provide a PostgreSQL database with a dedicated user allowed to create tables and triggers. Replace the `DATABASE_URL` and `GOOGLE_CLIENT_SECRET` placeholders in `.komo/node.env`. Add the printed `https://comments.example.com/auth/google/callback` URL to the Google OAuth client's authorized redirect URIs. In Cloud Run, supply the same variables through service configuration and a secret store. Do not commit the environment file. The Node runtime does not enable hosted provisioning (`KOMO_HOSTED`).
22
+ 2. Provide a PostgreSQL database with a dedicated user allowed to create tables and triggers. Replace the `DATABASE_URL` and `GOOGLE_CLIENT_SECRET` placeholders in `.palette/node.env`. Add the printed `https://comments.example.com/auth/google/callback` URL to the Google OAuth client's authorized redirect URIs. In Cloud Run, supply the same variables through service configuration and a secret store. Do not commit the environment file. The Node runtime does not enable hosted provisioning (`PALETTE_HOSTED`).
23
23
 
24
- To enable signed webhooks, generate a 32-byte encryption key with `openssl rand -base64 32` and provide `KOMO_WEBHOOK_KEYS` through your secret manager as `{"current":"key-id","keys":{"key-id":"BASE64_KEY"}}`. Never store the key in source control or expose it in logs. Webhook endpoints must use HTTPS on port 443; the Node sender rejects private or mixed DNS answers and pins the vetted public address for the request.
24
+ To enable signed webhooks, generate a 32-byte encryption key with `openssl rand -base64 32` and provide `PALETTE_WEBHOOK_KEYS` through your secret manager as `{"current":"key-id","keys":{"key-id":"BASE64_KEY"}}`. Never store the key in source control or expose it in logs. Webhook endpoints must use HTTPS on port 443; the Node sender rejects private or mixed DNS answers and pins the vetted public address for the request.
25
25
 
26
- 3. Build this repository's image with `docker build -f Dockerfile.node -t komo-node .`. Run it with the generated variables and HTTPS in front. For a local check, set `PUBLIC_URL=http://localhost:8080` in `.komo/node.env` and run `docker run --env-file /path/to/your-website/.komo/node.env -p 8080:8080 komo-node`. For Cloud Run, set the container port to 8080. Cloud SQL or another reachable PostgreSQL service can be used through `DATABASE_URL`; the database connection URL must be reachable from the container. Startup applies the bundled schema under a database lock. `/health` returns `{"ok":true}` after migrations succeed.
26
+ 3. Build this repository's image with `docker build -f Dockerfile.node -t komo-node .`. Run it with the generated variables and HTTPS in front. For a local check, set `PUBLIC_URL=http://localhost:8080` in `.palette/node.env` and run `docker run --env-file /path/to/your-website/.palette/node.env -p 8080:8080 komo-node`. For Cloud Run, set the container port to 8080. Cloud SQL or another reachable PostgreSQL service can be used through `DATABASE_URL`; the database connection URL must be reachable from the container. Startup applies the bundled schema under a database lock. `/health` returns `{"ok":true}` after migrations succeed.
27
27
 
28
28
  The image installs the PostgreSQL client (`pg`). To run `@hazem3500/palette/node` outside it, install `pg` next to Palette: it is an optional peer, so browser installs of Palette never include it.
29
29
 
@@ -49,11 +49,11 @@ If a reverse proxy serves Palette under a path, include it in `PUBLIC_URL`, for
49
49
 
50
50
  Behind a proxy that authenticates users and signs each request, Palette can trust the proxy's identity instead of running its own OAuth client. This is off by default. One provider is supported:
51
51
 
52
- - **`KOMO_PROXY_AUTH=gcp-iap`:** Google Cloud Identity-Aware Proxy (IAP). Set **`KOMO_PROXY_AUDIENCE`** to the backend audience, `/projects/PROJECT_NUMBER/global/backendServices/SERVICE_ID`. Palette verifies the `x-goog-iap-jwt-assertion` ES256 JWT against Google's IAP keys. It caches them for an hour, and fetches them again for an unknown key ID, at most every 30 seconds. A key fetch times out after 5 seconds. After a failed fetch it waits 30 seconds before trying again, and keeps verifying with the last keys for up to 24 hours. It checks the issuer, audience, expiry and issue time, allowing 60 seconds of clock skew. The subject must be `accounts.google.com:DIGITS`. The account ID is `google:DIGITS`, the same ID Google OAuth sign-in uses, so an existing account signs in as itself. The email is stored in lower case. `GOOGLE_CLIENT_ID` and `GOOGLE_CLIENT_SECRET` aren't needed.
53
- - **`KOMO_IAP_AUDIENCE`**, the older name, still works: set alone, it means `gcp-iap` with that audience.
54
- - **`KOMO_AUTH_PATH_PREFIX`** (optional, for example `/backoffice`, older name `KOMO_AUTH_PREFIX`): serves the sign-in handlers only under this path, so the proxy can protect just that path. Without the prefix, those handlers return 404. It needs `KOMO_PROXY_AUTH`; set without one, the server doesn't start.
52
+ - **`PALETTE_PROXY_AUTH=gcp-iap`:** Google Cloud Identity-Aware Proxy (IAP). Set **`PALETTE_PROXY_AUDIENCE`** to the backend audience, `/projects/PROJECT_NUMBER/global/backendServices/SERVICE_ID`. Palette verifies the `x-goog-iap-jwt-assertion` ES256 JWT against Google's IAP keys. It caches them for an hour, and fetches them again for an unknown key ID, at most every 30 seconds. A key fetch times out after 5 seconds. After a failed fetch it waits 30 seconds before trying again, and keeps verifying with the last keys for up to 24 hours. It checks the issuer, audience, expiry and issue time, allowing 60 seconds of clock skew. The subject must be `accounts.google.com:DIGITS`. The account ID is `google:DIGITS`, the same ID Google OAuth sign-in uses, so an existing account signs in as itself. The email is stored in lower case. `GOOGLE_CLIENT_ID` and `GOOGLE_CLIENT_SECRET` aren't needed.
53
+ - **`PALETTE_IAP_AUDIENCE`**, the older name, still works: set alone, it means `gcp-iap` with that audience.
54
+ - **`PALETTE_AUTH_PATH_PREFIX`** (optional, for example `/backoffice`, older name `PALETTE_AUTH_PREFIX`): serves the sign-in handlers only under this path, so the proxy can protect just that path. Without the prefix, those handlers return 404. It needs `PALETTE_PROXY_AUTH`; set without one, the server doesn't start.
55
55
 
56
- Any other `KOMO_PROXY_AUTH` value stops the server at startup with the list of supported providers.
56
+ Any other `PALETTE_PROXY_AUTH` value stops the server at startup with the list of supported providers.
57
57
 
58
58
  The sign-in handlers are:
59
59
 
@@ -63,7 +63,7 @@ The sign-in handlers are:
63
63
  While a proxy is on:
64
64
 
65
65
  - Google is the only sign-in method, and `/config` reports `google: true`. GitHub sign-in returns 503, and the GitHub and Google OAuth handlers return 403 or 404.
66
- - Reads need a session and guests are off, whatever `KOMO_REQUIRE_SESSION_FOR_READS` and `KOMO_ALLOW_GUESTS` say.
66
+ - Reads need a session and guests are off, whatever `PALETTE_REQUIRE_SESSION_FOR_READS` and `PALETTE_ALLOW_GUESTS` say.
67
67
  - Only sessions the proxy signed in are accepted, on every route. Each session records how it was signed in. Sessions from before the proxy was turned on are refused with 401 `Your session expired. Sign in again.`, whether from Google OAuth, GitHub or a guest, and reviewers sign in again through the proxy.
68
68
  - Personal access tokens are refused with 403, and `/config` reports `tokens: false`. A token would keep working after the proxy stopped vouching for its owner. Agents use `/auth/proxy/session` instead.
69
69
  - Missing, forged, expired, future-dated or wrong-audience assertions are rejected.
@@ -78,13 +78,13 @@ Proxy sign-in completes without a consent screen: a reviewer already signed in t
78
78
 
79
79
  Each option is off by default and works on Node and on a self-hosted Worker:
80
80
 
81
- - **`KOMO_REQUIRE_SESSION_FOR_READS=true`:** listing or fetching threads and comments needs a valid session. Other reads, such as snapshots, usage and exports, already need one. Anonymous reads return 401 `Sign in to see comments.`, and `/config` leaves out `repo` for callers without a session. Guest sessions still count, unless guests are off.
82
- - **`KOMO_ALLOW_GUESTS=false`:** refuses guest sign-in on every project with `Guest comments are disabled.`, and refuses existing guest sessions with the same message. `/config` then reports `guests: false` and `snapshots.guests: false`, even if a project sets `allowGuestSnapshots`. While guests are allowed, a project's own `allowGuests: false` still applies.
83
- - **`KOMO_SESSION_MAX_AGE_DAYS=N`** (1 to 36500): new sessions expire after N days, and sessions issued more than N days ago are refused, so lowering the cap applies to every existing session. Without it, sessions last until sign-out (100 years). Sessions record when they were issued from D1 migration `0021_session_origin` and PostgreSQL migration `007_session_origin`. Older sessions are dated by the 100-year lifetime, which is exact for them. A session that some other server issued with a shorter lifetime looks older than it is, so a cap refuses it. Personal access tokens keep the expiry chosen when they were created.
81
+ - **`PALETTE_REQUIRE_SESSION_FOR_READS=true`:** listing or fetching threads and comments needs a valid session. Other reads, such as snapshots, usage and exports, already need one. Anonymous reads return 401 `Sign in to see comments.`, and `/config` leaves out `repo` for callers without a session. Guest sessions still count, unless guests are off.
82
+ - **`PALETTE_ALLOW_GUESTS=false`:** refuses guest sign-in on every project with `Guest comments are disabled.`, and refuses existing guest sessions with the same message. `/config` then reports `guests: false` and `snapshots.guests: false`, even if a project sets `allowGuestSnapshots`. While guests are allowed, a project's own `allowGuests: false` still applies.
83
+ - **`PALETTE_SESSION_MAX_AGE_DAYS=N`** (1 to 36500): new sessions expire after N days, and sessions issued more than N days ago are refused, so lowering the cap applies to every existing session. Without it, sessions last until sign-out (100 years). Sessions record when they were issued from D1 migration `0021_session_origin` and PostgreSQL migration `007_session_origin`. Older sessions are dated by the 100-year lifetime, which is exact for them. A session that some other server issued with a shorter lifetime looks older than it is, so a cap refuses it. Personal access tokens keep the expiry chosen when they were created.
84
84
 
85
85
  The flags take only `true` or `false`. An invalid value stops the Node server at startup, and makes every Worker request fail with the option named in the logs.
86
86
 
87
- On a self-hosted Worker, apply the D1 migrations (`wrangler d1 migrations apply`) before turning on `KOMO_PROXY_AUTH` or `KOMO_SESSION_MAX_AGE_DAYS`. Without migration 0021, sessions are still issued as before, but those two options fail every authenticated request with a log naming the migration. The Node server applies its migrations at startup.
87
+ On a self-hosted Worker, apply the D1 migrations (`wrangler d1 migrations apply`) before turning on `PALETTE_PROXY_AUTH` or `PALETTE_SESSION_MAX_AGE_DAYS`. Without migration 0021, sessions are still issued as before, but those two options fail every authenticated request with a log naming the migration. The Node server applies its migrations at startup.
88
88
 
89
89
  ### Deploy behind Google Cloud IAP
90
90
 
@@ -92,7 +92,9 @@ Build the image with `docker build -f Dockerfile.node -t komo-node .`. It runs `
92
92
 
93
93
  Put only `PREFIX/auth/google/authorize` and `PREFIX/auth/proxy/session` behind IAP. Leave the rest of the API outside it.
94
94
 
95
- Google Cloud's external Application Load Balancer appends two `X-Forwarded-For` entries, the client's address and then the load balancer's forwarding rule address. Behind it alone, set `KOMO_TRUSTED_PROXY_HOPS=2`. Add one for each proxy after it that appends the address it received from, such as nginx with `$proxy_add_x_forwarded_for`: a load balancer plus nginx is `3`. A proxy that passes the header through unchanged adds nothing.
95
+ Google Cloud's external Application Load Balancer appends two `X-Forwarded-For` entries, the client's address and then the load balancer's forwarding rule address. Behind it alone, set `PALETTE_TRUSTED_PROXY_HOPS=2`. Add one for each proxy after it that appends the address it received from, such as nginx with `$proxy_add_x_forwarded_for`: a load balancer plus nginx is `3`. A proxy that passes the header through unchanged adds nothing.
96
+
97
+ Deployments configured before the rename keep working unchanged: each `PALETTE_*` variable below is also read under its older `KOMO_*` name (`KOMO_ALLOW_GUESTS`, `KOMO_TRUSTED_PROXY_HOPS` and so on) when the `PALETTE_*` name is unset or empty. When both are set, `PALETTE_*` wins.
96
98
 
97
99
  | Variable | Purpose | Default | Example |
98
100
  | --- | --- | --- | --- |
@@ -100,29 +102,29 @@ Google Cloud's external Application Load Balancer appends two `X-Forwarded-For`
100
102
  | `PUBLIC_URL` | Public URL, including any path the gateway strips | required | `https://comments.example.com/komo` |
101
103
  | `PROJECTS` | Project keys, repositories and approved origins, on one line | required | `{"site":{"repo":"your-org/your-repo","origins":["https://*-web.preview.example.com","https://dev.example.com:*"],"allowGuests":false}}` |
102
104
  | `PORT` | Listen port | `8080` | `8080` |
103
- | `KOMO_PROXY_AUTH` | Identity proxy provider | off | `gcp-iap` |
104
- | `KOMO_PROXY_AUDIENCE` | IAP audience (older name `KOMO_IAP_AUDIENCE`) | unset | `/projects/PROJECT_NUMBER/global/backendServices/SERVICE_ID` |
105
- | `KOMO_AUTH_PATH_PREFIX` | Path IAP protects for sign-in (older name `KOMO_AUTH_PREFIX`) | none | `/backoffice` |
106
- | `KOMO_SESSION_MAX_AGE_DAYS` | Session lifetime cap | unset (100 years) | `30` |
107
- | `KOMO_TRUSTED_PROXY_HOPS` | `X-Forwarded-For` entries trusted proxies append (older name `KOMO_PROXY_HOPS`) | `0` | `2` behind the load balancer alone; `3` with one appending proxy, such as nginx, after it |
108
- | `KOMO_REQUIRE_SESSION_FOR_READS` | Reads need a session | `false` | forced `true` by the proxy; leave unset |
109
- | `KOMO_ALLOW_GUESTS` | Guest sign-in | `true` | forced `false` by the proxy; leave unset |
105
+ | `PALETTE_PROXY_AUTH` | Identity proxy provider | off | `gcp-iap` |
106
+ | `PALETTE_PROXY_AUDIENCE` | IAP audience (older name `PALETTE_IAP_AUDIENCE`) | unset | `/projects/PROJECT_NUMBER/global/backendServices/SERVICE_ID` |
107
+ | `PALETTE_AUTH_PATH_PREFIX` | Path IAP protects for sign-in (older name `PALETTE_AUTH_PREFIX`) | none | `/backoffice` |
108
+ | `PALETTE_SESSION_MAX_AGE_DAYS` | Session lifetime cap | unset (100 years) | `30` |
109
+ | `PALETTE_TRUSTED_PROXY_HOPS` | `X-Forwarded-For` entries trusted proxies append (older name `PALETTE_PROXY_HOPS`) | `0` | `2` behind the load balancer alone; `3` with one appending proxy, such as nginx, after it |
110
+ | `PALETTE_REQUIRE_SESSION_FOR_READS` | Reads need a session | `false` | forced `true` by the proxy; leave unset |
111
+ | `PALETTE_ALLOW_GUESTS` | Guest sign-in | `true` | forced `false` by the proxy; leave unset |
110
112
  | `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET` | Google OAuth | unset | not needed with IAP |
111
- | `KOMO_TOKEN_PEPPER` | Secret mixed into access token hashes | unset | a long random secret |
112
- | `KOMO_WEBHOOK_KEYS` | Webhook secret encryption keys | unset | see Configure, step 2 |
113
+ | `PALETTE_TOKEN_PEPPER` | Secret mixed into access token hashes | unset | a long random secret |
114
+ | `PALETTE_WEBHOOK_KEYS` | Webhook secret encryption keys | unset | see Configure, step 2 |
113
115
 
114
116
  ### Move a patched upstream server to this package
115
117
 
116
118
  For a server that ran upstream komo with a local IAP patch:
117
119
 
118
120
  1. Back up and upgrade the database as in [Upgrade a database from upstream komo](#upgrade-a-database-from-upstream-komo). Migration `007_session_origin.sql` adds two nullable columns to `sessions`, recording how and when each session was signed in. Existing rows keep NULL.
119
- 2. Keep `KOMO_IAP_AUDIENCE`, or rename it to `KOMO_PROXY_AUTH=gcp-iap` plus `KOMO_PROXY_AUDIENCE`. `KOMO_PROXY_HOPS` keeps its meaning.
120
- 3. Replace a patched session lifetime with `KOMO_SESSION_MAX_AGE_DAYS`. Behind the proxy, sessions issued before the upgrade aren't accepted, because nothing records that the proxy signed them in. Each reviewer and agent signs in through IAP once more. The popup does this without a prompt, and agents call `/auth/proxy/session`. Personal access tokens stop working behind the proxy.
121
- 4. Set `PUBLIC_URL` to the external URL, including the path the gateway strips, and `KOMO_AUTH_PATH_PREFIX` to the path IAP protects. `/auth/google/start` then returns `https://HOST/PATH/PREFIX/auth/google/authorize`, so a proxy rewrite of that response is no longer needed. Palette serves the prefixed handlers itself.
121
+ 2. Keep `PALETTE_IAP_AUDIENCE`, or rename it to `PALETTE_PROXY_AUTH=gcp-iap` plus `PALETTE_PROXY_AUDIENCE`. `PALETTE_PROXY_HOPS` keeps its meaning.
122
+ 3. Replace a patched session lifetime with `PALETTE_SESSION_MAX_AGE_DAYS`. Behind the proxy, sessions issued before the upgrade aren't accepted, because nothing records that the proxy signed them in. Each reviewer and agent signs in through IAP once more. The popup does this without a prompt, and agents call `/auth/proxy/session`. Personal access tokens stop working behind the proxy.
123
+ 4. Set `PUBLIC_URL` to the external URL, including the path the gateway strips, and `PALETTE_AUTH_PATH_PREFIX` to the path IAP protects. `/auth/google/start` then returns `https://HOST/PATH/PREFIX/auth/google/authorize`, so a proxy rewrite of that response is no longer needed. Palette serves the prefixed handlers itself.
122
124
  5. Point health checks at `GET /health`. Palette doesn't answer `GET /` with 200.
123
125
  6. Startup is stricter. A server now stops with a named error on:
124
- - a `KOMO_PROXY_HOPS` value that isn't a single digit from 0 to 8, such as `01` or ` 1`;
125
- - `KOMO_AUTH_PREFIX` set without a proxy;
126
+ - a `PALETTE_PROXY_HOPS` value that isn't a single digit from 0 to 8, such as `01` or ` 1`;
127
+ - `PALETTE_AUTH_PREFIX` set without a proxy;
126
128
  - a sign-in prefix with `.` or `..` segments;
127
129
  - a `PROJECTS` origin that isn't a valid pattern.
128
130
 
@@ -140,7 +142,7 @@ initPalette({
140
142
  });
141
143
  ```
142
144
 
143
- Point the existing CLI at the same endpoint, project, repository, and branch. `palette login` uses Google when configured; `KOMO_TOKEN` accepts a personal access token (create one at `/account?project=KEY` on your server; see Bots and CI in the README) or a previously issued session token for a non-interactive bot. Store that token as a secret. Access tokens are limited by their scopes and projects; session tokens have the same permissions as their user. Optionally set `KOMO_TOKEN_PEPPER` to a long random secret to mix into stored token hashes; changing it revokes every access token. For example:
145
+ Point the existing CLI at the same endpoint, project, repository, and branch. `palette login` uses Google when configured; `PALETTE_TOKEN` accepts a personal access token (create one at `/account?project=KEY` on your server; see Bots and CI in the README) or a previously issued session token for a non-interactive bot. Store that token as a secret. Access tokens are limited by their scopes and projects; session tokens have the same permissions as their user. Optionally set `PALETTE_TOKEN_PEPPER` to a long random secret to mix into stored token hashes; changing it revokes every access token. For example:
144
146
 
145
147
  ```sh
146
148
  palette comments list --endpoint https://comments.example.com --project YOUR_GENERATED_PROJECT_KEY --repo your-org/your-repo --branch pr-42 --origin https://pr-42.preview.example.com
@@ -163,4 +165,4 @@ To roll back, restore the backup. Redeploying the old image is not enough: the u
163
165
 
164
166
  The API's storage contract is `prepare().bind().first()/all()/run()` plus atomic `batch()`. `server/postgres.ts` implements it with a PostgreSQL pool, parameterized statements, and one transaction per batch. `server/postgres/001_initial.sql` mirrors the SQLite schema and its quota, revision, retention, and deletion triggers; later numbered files, such as `005_snapshots.sql` for element snapshots (`bytea`), mirror later D1 migrations. Add a numbered PostgreSQL migration whenever the D1 schema changes. Existing D1 migrations and the Worker entry point stay separate. The Node server runs the same request handler; transport and storage are the only new adapters.
165
167
 
166
- The Node process runs retention cleanup after requests, as the self-hosted Worker does. It handles `SIGTERM` by closing its HTTP listener and database pool. Database connection failures prevent startup; request errors return the existing API error format. Rate limits use the direct socket IP by default, and never trust a client's `X-Forwarded-For`. Behind trusted reverse proxies, set `KOMO_TRUSTED_PROXY_HOPS` (older name `KOMO_PROXY_HOPS`, 0 to 8) to the number of `X-Forwarded-For` entries those proxies append before the app. Most proxies append one; Google Cloud's external Application Load Balancer appends two (see [Deploy behind Google Cloud IAP](#deploy-behind-google-cloud-iap)). The client address is then that many entries from the right; entries further left are ignored. If the header has fewer entries, or that entry isn't an IP address, the socket IP is used. Check your load balancer’s forwarding behavior for the correct value, and only set it when requests can't bypass those proxies. Client-supplied `CF-Connecting-IP` is ignored. The Cloudflare Worker uses Cloudflare's `CF-Connecting-IP`.
168
+ The Node process runs retention cleanup after requests, as the self-hosted Worker does. It handles `SIGTERM` by closing its HTTP listener and database pool. Database connection failures prevent startup; request errors return the existing API error format. Rate limits use the direct socket IP by default, and never trust a client's `X-Forwarded-For`. Behind trusted reverse proxies, set `PALETTE_TRUSTED_PROXY_HOPS` (older name `PALETTE_PROXY_HOPS`, 0 to 8) to the number of `X-Forwarded-For` entries those proxies append before the app. Most proxies append one; Google Cloud's external Application Load Balancer appends two (see [Deploy behind Google Cloud IAP](#deploy-behind-google-cloud-iap)). The client address is then that many entries from the right; entries further left are ignored. If the header has fewer entries, or that entry isn't an IP address, the socket IP is used. Check your load balancer’s forwarding behavior for the correct value, and only set it when requests can't bypass those proxies. Client-supplied `CF-Connecting-IP` is ignored. The Cloudflare Worker uses Cloudflare's `CF-Connecting-IP`.
package/README.md CHANGED
@@ -17,21 +17,16 @@ Review actions update immediately while saving in the background. Failed writes
17
17
 
18
18
  ```sh
19
19
  npm install @hazem3500/palette
20
- npx @hazem3500/palette init
21
20
  ```
22
21
 
23
22
  Requires Node.js 22 or newer. The unscoped `komo` package on npm is unrelated; use `@hazem3500/palette`.
24
23
 
25
- The setup command detects your Git repository and generates `komo.config.js` before sign-in. Mount that helper in your app:
24
+ Palette has no hosted default. Comments go to a Palette server that you run, and every install sets `endpoint` to it. Start a server first:
26
25
 
27
- ```js
28
- import { initPalette } from './komo.config.js';
29
- initPalette();
30
- ```
26
+ - **Node and PostgreSQL**, including Cloud Run: run `npx @hazem3500/palette init --node` and follow the [Node self-hosting guide](./NODE.md).
27
+ - **Cloudflare Worker and D1**: run `npx @hazem3500/palette init --self-host`; see [Self-host](#self-host).
31
28
 
32
- Keep the setup terminal open and start your app in another terminal. Choose **Connect** in your app’s sidebar. Google sign-in opens in a separate window; Palette creates the project and returns you to feedback on your site. The CLI saves the final public settings to `.komo/project.json`, updates the helper, and prints an inline configuration. Use `--origin` for a different dev port or deployed site. If interrupted or expired, run `palette init` again to resume; completed projects are never overwritten.
33
-
34
- Hosted onboarding is available through the dedicated Palette service. Self-hosting is available through the same CLI.
29
+ Both print the `endpoint` and project key for your site. If your team already runs a Palette server with hosted setup turned on (`PALETTE_HOSTED=true`), connect to it with `npx @hazem3500/palette init --endpoint https://palette.example.com`; see [Hosted setup](#hosted-setup).
35
30
 
36
31
  ## Usage
37
32
 
@@ -39,13 +34,17 @@ Mount after the page has loaded:
39
34
 
40
35
  ```js
41
36
  import { initPalette } from '@hazem3500/palette';
42
- initPalette({ project: 'YOUR_PROJECT_KEY' });
37
+ initPalette({
38
+ endpoint: 'https://palette.example.com',
39
+ project: 'YOUR_PROJECT_KEY',
40
+ });
43
41
  ```
44
42
 
45
43
  Optional configuration stays at the call site:
46
44
 
47
45
  ```js
48
46
  initPalette({
47
+ endpoint: 'https://palette.example.com',
49
48
  project: 'YOUR_PROJECT_KEY',
50
49
  enabled: import.meta.env.DEV || import.meta.env.PUBLIC_PREVIEW === 'true',
51
50
  pageRoot: document.querySelector('#app'),
@@ -61,16 +60,19 @@ With React 18.2 or 19, call the hook once near your app root:
61
60
  import { usePalette } from '@hazem3500/palette/react';
62
61
 
63
62
  export function Palette() {
64
- usePalette({ project: 'YOUR_PROJECT_KEY' });
63
+ usePalette({
64
+ endpoint: 'https://palette.example.com',
65
+ project: 'YOUR_PROJECT_KEY',
66
+ });
65
67
  return null;
66
68
  }
67
69
  ```
68
70
 
69
71
  Render `<Palette />` in your app or layout. The hook handles mounting, cleanup, and React Strict Mode. Inline configuration objects are supported; unchanged values do not restart Palette. Changing configuration remounts it, and `enabled: false` removes it. Memoize callback options with `useCallback` and pass stable DOM elements for `pageRoot` or `drawerContainer`. Mount only one hook per page; do not combine it with a separate `initPalette()` call. Server rendering does not mount the tool.
70
72
 
71
- The **endpoint is the comments API**, not your website or preview URL. Hosted Palette defaults to `https://komo.offbr.co`. Google sign-in returns to `https://komo.offbr.co/auth/google/callback`, regardless of the website embedding Palette. For self-hosting, pass your Worker or Node API URL as `endpoint`. Your current page comes from the browser. Setup detects repository metadata from Git; include its printed `repo` value to enrich agent prompts.
73
+ The **endpoint is the comments API**, your Palette server, not your website or preview URL. It is required: without it, Palette logs an error and does not mount. Google sign-in returns to `ENDPOINT/auth/google/callback`, regardless of the website embedding Palette. Your current page comes from the browser. Setup detects repository metadata from Git; include its printed `repo` value to enrich agent prompts.
72
74
 
73
- Comments are shared across deployments by default. Use `pnpm exec palette init --branch-scope` to separate them by branch. For automatic branch detection, import from the optional generated `komo.config.js` helper and run `palette sync` before builds. It detects the current branch from deployment environment variables or Git; set `KOMO_BRANCH` if neither is available. It fails rather than silently grouping unknown branches.
75
+ Comments are shared across deployments by default. Use `pnpm exec palette init --branch-scope` to separate them by branch. For automatic branch detection, import from the optional generated `palette.config.js` helper and run `palette sync` before builds. It detects the current branch from deployment environment variables or Git; set `PALETTE_BRANCH` if neither is available. It fails rather than silently grouping unknown branches.
74
76
 
75
77
  ## Migrating from komo
76
78
 
@@ -79,7 +81,15 @@ Palette was called komo. Existing installs keep working without changes.
79
81
  - **API names.** `initPalette`, `definePalette`, `usePalette`, `usePaletteInspectable`, `usePaletteDesignTokens` and the `Palette*` types are the main names. `initKomo`, `defineKomo`, `useKomo`, `useKomoInspectable`, `useKomoDesignTokens` and the `Komo*` types still work and are marked deprecated.
80
82
  - **CLI.** The command is `palette`. `komo` still works as an alias.
81
83
  - **Package.** The package is now `@hazem3500/palette`. Replace `@hazem3500/komo` in your dependencies and imports (including subpaths such as `/react` and `/vite`); `@hazem3500/komo` is deprecated and gets no new releases.
82
- - **Unchanged for now.** Stored data and storage keys, `data-komo-*` attributes, `--komo-*` CSS custom properties, `KOMO_*` environment variables, the `komo_pat_` token prefix, the `.komo/` directory and `komo.config.js` keep their names.
84
+ - **Stored and wire names.** The Palette names are the main ones, and the komo names are still read, so sessions, settings, tokens, deployments, comments and host integrations keep working:
85
+ - **Browser storage.** Keys start with `palette:`. When Palette mounts, each of its keys from before the rename (`branch-comments:*`, `komo:*`) is copied to its `palette:` name if that is unset, and the old key stays, so an older copy of the app on the same origin keeps working. The new key wins when both exist. Clearing a value (signing out, closing the sidebar) clears both. Other keys on the origin are left alone.
86
+ - **Environment variables.** Set `PALETTE_*` (`PALETTE_TOKEN`, `PALETTE_ALLOW_GUESTS`, `PALETTE_TRUSTED_PROXY_HOPS` and the rest). Each `KOMO_*` name still works when its `PALETTE_*` name is unset or empty, on the server, the Node image and the CLI.
87
+ - **Access tokens.** New tokens start with `palette_pat_`. Tokens starting with `komo_pat_` keep authenticating everywhere until they expire or are revoked. The token list shows each one as `…_pat_<id>…<last4>`.
88
+ - **Project files.** New projects use `.palette/` and `palette.config.js`. A project set up before the rename keeps `.komo/` and `komo.config.js`, so its imports and ignored files stay valid. The CLI also reads credentials saved under `~/.config/komo`; new ones go to `~/.config/palette`, and `palette logout` revokes and removes the sessions saved in both. `palette init` rewrites the AGENTS.md workflow block's `komo:workflow` markers to `palette:workflow`.
89
+ - **Page attributes and CSS.** Use `data-palette-no-snapshot`, `data-palette-inspectable`, `data-palette-design-tokens` and `--palette-selection`. Their `data-komo-*` and `--komo-selection` forms are still read. `usePaletteInspectable` returns both `data-palette-inspectable` and `data-komo-inspectable`. Frame's viewport link uses `?palette-shell=`; `?komo-shell=` still opens it. The viewport iframe keeps the name `komo-viewport` and carries both `data-palette-viewport` and `data-komo-viewport`, so an older framed copy still knows it is framed.
90
+ - **Webhooks.** Each request carries `X-Palette-Event`, `X-Palette-Delivery` and `X-Palette-Signature`, and the same values as `X-Komo-*`.
91
+ - **Comments.** Design edits are marked `palette: "design-edit"` (fence `palette-design-edit`), and snapshot notes say `palette:`. Comments written before the rename, with `komo` markers, still parse.
92
+ - **Unchanged.** Database tables, project keys (`komo_…`), the reserved `_komo` management project and exported feedback (`komo-export`) keep their names.
83
93
 
84
94
  ## Features
85
95
 
@@ -96,7 +106,7 @@ Palette was called komo. Existing installs keep working without changes.
96
106
  - **Crowded pins** consolidate. Comments on the same target (its persistent owner, including a custom `anchorTarget`) draw as one tight avatar stack at the oldest comment's anchor; more than three collapse to the first author and a count. Hover, keyboard focus or a tap fans the stack out in creation order, and each pin opens its own thread. Pins on different targets whose 24px targets overlap at the current zoom or Frame scale merge into a count bubble; it opens a keyboard-navigable list of those comments. Grouping never changes anchors or saves anything, the open comment's pin is always drawn at its own anchor, and a pin moved with **Move pin** stays out of stacks and bubbles. Set `pins.cluster: false` to draw every pin on its own.
97
107
  - Keyboard shortcuts: C to comment, V to browse, D to toggle Design mode, L to view all comments, H to hide or show comments, Shift+K to restore. Remap or disable them with `shortcuts`; see [Choosing shortcuts](#choosing-shortcuts). In Design mode, ? or **Keyboard shortcuts** in the empty inspector lists every canvas key and gesture.
98
108
  - Copy open feedback with selectors, source paths, page URLs, replies, and geometry into an agent.
99
- - Hosted Cloudflare storage, or your own Worker/D1 or Node/PostgreSQL service.
109
+ - Your own Worker/D1 or Node/PostgreSQL service.
100
110
 
101
111
  Comment pins stay anchored during ordinary pointer drags. Authors and verified reviewers can choose **Comment actions → Move pin** to reposition one explicitly: drag it, or use arrow keys (Shift for larger steps), then Enter or **Save position**. Escape or **Cancel move** restores its previous position without saving. Completed moves use the existing thread anchor endpoint and remain attached after reload; the page element itself never moves. Dragging a floating comment card is independent of pin placement.
102
112
 
@@ -387,7 +397,7 @@ npx @hazem3500/palette comments reply THREAD_ID --body "Fixed and verified on mo
387
397
  npx @hazem3500/palette comments resolve THREAD_ID
388
398
  ```
389
399
 
390
- Run these from a project configured with `palette init`. The CLI searches parent directories for `.komo/project.json`. Google sign-in opens a local browser handoff; `--no-open` prints the local sign-in link instead. Credentials are stored outside your repository in `~/.config/komo`, with a private file per API/project. Sessions stay valid until you sign out. `palette logout` revokes the session and removes its file.
400
+ Run these from a project configured with `palette init`. The CLI searches parent directories for `.palette/project.json`. Google sign-in opens a local browser handoff; `--no-open` prints the local sign-in link instead. Credentials are stored outside your repository in `~/.config/komo`, with a private file per API/project. Sessions stay valid until you sign out. `palette logout` revokes the session and removes its file.
391
401
 
392
402
  For an existing inline installation, pass `--project YOUR_PROJECT_KEY --origin https://your-site.example`. `--origin` is an approved website address; `--endpoint` is the comments API. New setups remember the website address. Older setups default to `http://localhost:3000`; override it if that site is not approved. `--repo` and `--branch` override detected scope; project scope defaults to `shared`.
393
403
 
@@ -402,7 +412,7 @@ For an existing inline installation, pass `--project YOUR_PROJECT_KEY --origin h
402
412
  | `palette comments untag THREAD_ID TAG` | Remove a group tag from a thread. |
403
413
  | `palette comments get THREAD_ID` | Full thread, replies, selector, source reference, and anchor. |
404
414
  | `palette comments prompt` | The UI’s open-feedback prompt as Markdown; optional `--page` and `--json`. `--snapshots` saves element snapshots and lists their files. |
405
- | `palette comments snapshots THREAD_ID` | Save a thread’s element snapshots as files: `THREAD_ID.webp` (or `.png`) and `THREAD_ID.html.txt`. `--out DIR` picks the folder; the default, `.komo/snapshots`, ignores itself in Git. `comments get THREAD_ID --snapshots` does the same and adds `snapshotFiles`. |
415
+ | `palette comments snapshots THREAD_ID` | Save a thread’s element snapshots as files: `THREAD_ID.webp` (or `.png`) and `THREAD_ID.html.txt`. `--out DIR` picks the folder; the default, `.palette/snapshots`, ignores itself in Git. `comments get THREAD_ID --snapshots` does the same and adds `snapshotFiles`. |
406
416
  | `palette comments reply THREAD_ID --body TEXT` | Add a reply as the signed-in reviewer. |
407
417
  | `palette comments resolve THREAD_ID` | Mark verified work resolved. |
408
418
  | `palette comments reopen THREAD_ID` | Reopen a thread. |
@@ -416,9 +426,9 @@ For an existing inline installation, pass `--project YOUR_PROJECT_KEY --origin h
416
426
 
417
427
  Successful commands emit `{ "ok": true, "scope": { ... }, "data": ... }`. Errors emit `{ "ok": false, "error": { "message": "...", "status": ... } }` on stderr and exit with code 1. Prompts are Markdown unless `--json` is present. Design mode messages include `designEdit`, a validated `DesignEdit` (`komo: "design-edit"`, `version: 1`) with the target selector, component, token adapter and changes. `palette comments prompt` prints it after the message as a fenced `json komo-design-edit` block with a one-line instruction, and `--json` adds a `designEdits` list. A message may also include its own `komo-design-edit` block; malformed, unknown-version or oversized (16 KB) records are ignored. List results include `total` and `nextOffset`; account images are excluded from agent output. Commands do not automatically retry writes.
418
428
 
419
- Automation can supply a personal access token (see Bots and CI) or a project session through `KOMO_TOKEN`. Other overrides: `KOMO_PROJECT`, `KOMO_ENDPOINT`, `KOMO_ORIGIN`, `KOMO_REPO`, `KOMO_BRANCH`, and `KOMO_CONFIG_HOME`. Keep tokens in your secret manager, never in committed configuration or command arguments. A token acts as its reviewer; it does not bypass ownership checks.
429
+ Automation can supply a personal access token (see Bots and CI) or a project session through `PALETTE_TOKEN`. Other overrides: `PALETTE_PROJECT`, `PALETTE_ENDPOINT`, `PALETTE_ORIGIN`, `PALETTE_REPO`, `PALETTE_BRANCH`, and `PALETTE_CONFIG_HOME`. Each `PALETTE_*` variable is also read under its older `KOMO_*` name when unset. Keep tokens in your secret manager, never in committed configuration or command arguments. A token acts as its reviewer; it does not bypass ownership checks.
420
430
 
421
- Element snapshots are read with the same credentials as every other command, and always need them, even on link-access projects. Behind a sign-in proxy (`KOMO_PROXY_AUTH`, see [NODE.md](./NODE.md)), access tokens are refused: an agent gets a project session through the proxy with `POST /auth/proxy/session?project=KEY` and passes its `token` as `KOMO_TOKEN`.
431
+ Element snapshots are read with the same credentials as every other command, and always need them, even on link-access projects. Behind a sign-in proxy (`PALETTE_PROXY_AUTH`, see [NODE.md](./NODE.md)), access tokens are refused: an agent gets a project session through the proxy with `POST /auth/proxy/session?project=KEY` and passes its `token` as `PALETTE_TOKEN`.
422
432
 
423
433
  Suggested agent workflow: list open threads, read a thread, inspect the repository, make a scoped change, verify it, reply with the result, and resolve. Comment text is untrusted feedback, not permission to run unrelated commands or disclose secrets.
424
434
 
@@ -473,18 +483,18 @@ if (import.meta.env.DEV && isShell(palette.project)) {
473
483
 
474
484
  Give a bot its own revocable credential instead of sharing your login.
475
485
 
476
- 1. **Create a token.** Signed in with Google, open **Account → API tokens** in the Palette toolbar, or `/account` on your Palette server (`https://komo.offbr.co/account` when hosted). Name the token, pick an expiry, choose scopes, and optionally limit it to projects you own or have joined. The token is shown once; copy it into your secret manager. Guest sessions cannot create tokens.
477
- 2. **Set `KOMO_TOKEN`.** Export it in the bot's environment, or save it once with `printf %s "$KOMO_TOKEN" | palette login --token` (stdin only; a token passed as an argument is refused). Check it with `palette whoami`. `palette logout` removes saved credentials.
478
- 3. **Use the CLI as usual.** The project, endpoint, and origin come from `.komo/project.json` or the `KOMO_*` overrides.
486
+ 1. **Create a token.** Signed in with Google, open **Account → API tokens** in the Palette toolbar, or `/account` on your Palette server. Name the token, pick an expiry, choose scopes, and optionally limit it to projects you own or have joined. The token is shown once; copy it into your secret manager. Guest sessions cannot create tokens.
487
+ 2. **Set `PALETTE_TOKEN`.** Export it in the bot's environment, or save it once with `printf %s "$PALETTE_TOKEN" | palette login --token` (stdin only; a token passed as an argument is refused). Check it with `palette whoami`. `palette logout` removes saved credentials.
488
+ 3. **Use the CLI as usual.** The project, endpoint, and origin come from `.palette/project.json` or the `PALETTE_*` overrides.
479
489
 
480
- Tokens start with `komo_pat_`, so secret scanners can find them. Palette stores only a salted hash, and records create, revoke, and use events (use is logged at most once a minute per token).
490
+ Tokens start with `palette_pat_` (`komo_pat_` for tokens issued before the rename, which still work), so secret scanners can find them. Palette stores only a salted hash, and records create, revoke, and use events (use is logged at most once a minute per token).
481
491
 
482
492
  | Scope | Allows |
483
493
  | --- | --- |
484
494
  | `comments:read` | List and read threads, `palette whoami`. |
485
495
  | `comments:write` | Create threads, reply, edit, react, resolve, tag, move. |
486
496
 
487
- A token acts as its owner, narrowed by its scopes and project list: it never passes an ownership check or private-project invitation its owner would fail, and it cannot manage tokens, projects, members, or webhooks. Credentials resolve in this order: `KOMO_TOKEN`, a token saved by `palette login --token`, then a browser login session.
497
+ A token acts as its owner, narrowed by its scopes and project list: it never passes an ownership check or private-project invitation its owner would fail, and it cannot manage tokens, projects, members, or webhooks. Credentials resolve in this order: `PALETTE_TOKEN`, a token saved by `palette login --token`, then a browser login session.
488
498
 
489
499
  **Rotate** by creating a replacement, updating the secret, confirming `palette whoami`, then revoking the old token. Prefer an expiry of 90 days or less. **Revoke** from **Account → API tokens** or `/account`; the token stops working on its next request (HTTP 401), and so does an expired one.
490
500
 
@@ -493,11 +503,11 @@ GitHub Actions:
493
503
  ```yaml
494
504
  - name: Export open feedback
495
505
  env:
496
- KOMO_TOKEN: ${{ secrets.KOMO_TOKEN }} # a comments:read token, limited to this project
506
+ PALETTE_TOKEN: ${{ secrets.PALETTE_TOKEN }} # a comments:read token, limited to this project
497
507
  run: npx --no-install palette comments prompt
498
508
  ```
499
509
 
500
- Add the token under **Settings → Secrets and variables → Actions**. Self-hosted servers can set `KOMO_TOKEN_PEPPER` (a long random secret) to mix into stored hashes; changing it revokes every token.
510
+ Add the token under **Settings → Secrets and variables → Actions**. Self-hosted servers can set `PALETTE_TOKEN_PEPPER` (a long random secret) to mix into stored hashes; changing it revokes every token.
501
511
 
502
512
  ## How it works
503
513
 
@@ -517,13 +527,15 @@ Projects default to link access. Owners can restrict feedback to invited Google
517
527
 
518
528
  ## Hosted setup
519
529
 
530
+ A Palette server with `PALETTE_HOSTED=true` lets Google users create their own workspaces. Point the CLI at it:
531
+
520
532
  ```sh
521
- pnpm exec palette init
533
+ pnpm exec palette init --endpoint https://palette.example.com
522
534
  ```
523
535
 
524
- Mount the generated `komo.config.js` helper and open your app. Select **Connect** in the sidebar and sign in with Google. Review the detected production and preview addresses in the sidebar and add any others, one per line. Connecting approves the current site and those listed addresses, including production sites that have not launched. Remove a suggested address to leave it unapproved. Google credentials and account-wide management stay on the Palette service; your site receives a session for its new project.
536
+ Mount the generated `palette.config.js` helper and open your app. Select **Connect** in the sidebar and sign in with Google. Review the detected production and preview addresses in the sidebar and add any others, one per line. Connecting approves the current site and those listed addresses, including production sites that have not launched. Remove a suggested address to leave it unapproved. Google credentials and account-wide management stay on that Palette server; your site receives a session for its new project.
525
537
 
526
- The CLI waits up to ten minutes and replaces the temporary setup configuration after connection. Keep the terminal open. Run `palette init` again after an interruption or expiry. The temporary polling secret stays in ignored `.komo/setup.json` and is never bundled into your site. A reload can resume commenting while the CLI finishes. Run `palette sync` before builds that use the generated helper.
538
+ The CLI waits up to ten minutes and replaces the temporary setup configuration after connection. Keep the terminal open. Run `palette init` again after an interruption or expiry. The temporary polling secret stays in ignored `.palette/setup.json` and is never bundled into your site. A reload can resume commenting while the CLI finishes. Run `palette sync` before builds that use the generated helper.
527
539
 
528
540
  If the app cannot run, use the hosted recovery link printed in the terminal, sign in, and select **Create project**. The CLI still writes the final configuration.
529
541
 
@@ -531,7 +543,7 @@ Approve your deployed website in **Approved sites**. Palette detects deployment
531
543
 
532
544
  The setup page manages your account and approved sites. Leave comments on the website where you installed Palette.
533
545
 
534
- Return to `/setup?workspace=<project key>` to approve additional sites (up to ten). Only the Google owner can approve sites. The site address controls where that owner’s project can be embedded; it does not grant ownership of a domain or access to other projects. The API endpoint is the Palette service, not your website URL.
546
+ Return to `/setup?workspace=<project key>` to approve additional sites (up to ten). Only the Google owner can approve sites. The site address controls where that owner’s project can be embedded; it does not grant ownership of a domain or access to other projects. The API endpoint is your Palette server, not your website URL.
535
547
 
536
548
  Starter limits per owner/workspace:
537
549
 
@@ -558,7 +570,7 @@ Install Palette in your project, then run:
558
570
  pnpm exec palette init --self-host --origin https://preview.example.com
559
571
  ```
560
572
 
561
- The CLI signs into Cloudflare, creates a dedicated D1 database, applies migrations, deploys a Worker, and prompts for the Google client secret through Wrangler. It writes the deployment configuration into `.komo/` so you own and can change it.
573
+ The CLI signs into Cloudflare, creates a dedicated D1 database, applies migrations, deploys a Worker, and prompts for the Google client secret through Wrangler. It writes the deployment configuration into `.palette/` so you own and can change it.
562
574
 
563
575
  Create a Google OAuth **Web application** client with `openid profile email` access. Pass its public client ID with `--google-client-id`, or enter it when prompted. Add the callback printed after deployment:
564
576
 
@@ -566,9 +578,9 @@ Create a Google OAuth **Web application** client with `openid profile email` acc
566
578
  https://YOUR-WORKER.workers.dev/auth/google/callback
567
579
  ```
568
580
 
569
- Open the owner-claim link printed by setup and sign in with Google. The one-time claim key is saved in `.komo/owner-key`, excluded from Git, and only its hash is deployed. Ownership cannot be claimed again. Keep the Google secret out of browser code and configuration files.
581
+ Open the owner-claim link printed by setup and sign in with Google. The one-time claim key is saved in `.palette/owner-key`, excluded from Git, and only its hash is deployed. Ownership cannot be claimed again. Keep the Google secret out of browser code and configuration files.
570
582
 
571
- If setup is interrupted, run `pnpm exec palette deploy` to resume the existing deployment. Do not rerun initialization to create another database. The generated `.komo/wrangler.json` supports explicit allowed preview domains and single-label wildcard origins. Only allow domains whose scripts you trust.
583
+ If setup is interrupted, run `pnpm exec palette deploy` to resume the existing deployment. Do not rerun initialization to create another database. The generated `.palette/wrangler.json` supports explicit allowed preview domains and single-label wildcard origins. Only allow domains whose scripts you trust.
572
584
 
573
585
  Self-hosted storage belongs to your Cloudflare account; its service limits and charges apply. Use `palette deploy` for package migrations and `palette project export` for feedback backups. The hosted service and self-hosted installations use separate databases.
574
586
 
@@ -580,11 +592,11 @@ For a Node container with PostgreSQL (including Cloud Run), use the [Node self-h
580
592
 
581
593
  Pass these to `initPalette(config)` from `@hazem3500/palette` or `usePalette(config)` from `@hazem3500/palette/react`.
582
594
 
583
- `YOUR_PROJECT_KEY` is a placeholder for a string. Hosted Palette generates the key during `palette init`; copy that value. For self-hosting, use the project identifier configured on your server. Reuse the same key across sites that should share feedback.
595
+ `YOUR_PROJECT_KEY` is a placeholder for a string. Hosted setup (`palette init --endpoint`) generates the key; copy that value. For self-hosting, use the project identifier configured on your server. Reuse the same key across sites that should share feedback.
584
596
 
585
597
  | Option | Type | Default | Purpose |
586
598
  | --- | --- | --- | --- |
587
- | `endpoint` | `string` | Hosted Palette API | Comments API root; HTTPS outside localhost. |
599
+ | `endpoint` | `string` | Required | Your Palette server's API root; HTTPS outside localhost. Without it, Palette logs an error and does not mount. |
588
600
  | `project` | `string` | Required | Public project identifier returned by setup. |
589
601
  | `repo` | `string` | Project key | Repository identifier, usually `owner/repo`. |
590
602
  | `scope` | `"project" \| "branch"` | `"project"` | `"project"` shares comments; `"branch"` separates branches. |
@@ -694,7 +706,7 @@ When a signed-in reviewer starts a comment, Palette captures the commented eleme
694
706
  - **`image`** is a WebP image of the element with 16px of padding, at most 1200px wide and `maxBytes` in size (250,000 by default). Quality steps down from 0.8, then the size. Palette draws the element itself, with no extension or third-party library: it copies the element with its computed styles into an SVG `foreignObject` and draws that to a canvas, after `document.fonts.ready`. An image from another origin without CORS would taint the canvas, so Palette saves the HTML instead, starting with a comment that says why.
695
707
  - **`html`** is the element's `outerHTML`, trimmed to about 20 KB. Scripts, styles and inline event handlers are removed, and long attribute values and text are cut short.
696
708
  - **Redaction.** Input, textarea and password values never appear in either snapshot. `redact(element)` returning true replaces that element's text with `[redacted]`.
697
- - **Privacy.** Palette's own UI is never captured. Add `data-komo-no-snapshot` to any element to keep it out: a comment inside it captures nothing, and inside a captured element it shows as an empty box.
709
+ - **Privacy.** Palette's own UI is never captured. Add `data-palette-no-snapshot` (or the older `data-komo-no-snapshot`) to any element to keep it out: a comment inside it captures nothing, and inside a captured element it shows as an empty box.
698
710
 
699
711
  #### Viewing and removing snapshots
700
712
 
@@ -708,7 +720,7 @@ While composing, the captured snapshot appears under the comment box. **Remove s
708
720
 
709
721
  - **Opt-in.** Nothing is captured unless `snapshots.image` or `snapshots.html` is set, and the server supports snapshots.
710
722
  - **What is captured.** The commented element only, as you saw it: an image (WebP at quality 0.8 by default, stepping down to fit `maxBytes`) and its trimmed HTML. Palette's own UI, scripts, styles and event handlers are never included.
711
- - **What is redacted.** Input, textarea and password values are always replaced with `[redacted]`. `redact(element)` redacts any other element's text. Anything inside `data-komo-no-snapshot` is left out, and a comment inside such a region captures nothing.
723
+ - **What is redacted.** Input, textarea and password values are always replaced with `[redacted]`. `redact(element)` redacts any other element's text. Anything inside `data-palette-no-snapshot` is left out, and a comment inside such a region captures nothing.
712
724
  - **Who can see it.** Snapshots are never public: reading one needs a reviewer's credentials for the project (a session, or an access token where the server allows them), and HTML is served as plain text.
713
725
  - **Guests.** Guest sessions don't capture or upload unless the project sets `allowGuestSnapshots`.
714
726
  - **Retention.** A snapshot lives as long as its thread and is deleted with it: when resolved threads are cleared, by retention, or with the project. Exports include snapshots.
@@ -720,16 +732,16 @@ Limits: the image shows the element at the top of its scroll position and up to
720
732
 
721
733
  | Command / flag | Purpose |
722
734
  | --- | --- |
723
- | `palette init` | Create a Google-owned hosted workspace. |
735
+ | `palette init --endpoint URL` | Create a Google-owned workspace on a server with hosted setup turned on. |
724
736
  | `palette init --self-host` | Create your Worker and D1 database. |
725
737
  | `palette sync` | Regenerate the public client helper. Run before dev/build. |
726
738
  | `palette deploy` | Resume or redeploy generated self-host infrastructure. |
727
739
  | `--origin URL` | Optional website address. Otherwise detected from Cloudflare Pages, Vercel, or Netlify; defaults to localhost. |
728
- | `--endpoint URL` | Override hosted API or self-host deployment URL. |
740
+ | `--endpoint URL` | Your Palette server (or `PALETTE_ENDPOINT`). Required; there is no default. |
729
741
  | `--repo OWNER/REPO` | Override Git detection. |
730
742
  | `--branch-scope` | Opt into separate comments per branch. |
731
743
  | `--google-client-id ID` | Self-hosted Google OAuth client ID. |
732
- | `KOMO_BRANCH` | Explicit build-time branch override. |
744
+ | `PALETTE_BRANCH` | Explicit build-time branch override. |
733
745
 
734
746
  Branch detection also supports `CF_PAGES_BRANCH`, `WORKERS_CI_BRANCH`, `VERCEL_GIT_COMMIT_REF`, `GITHUB_HEAD_REF`, `GITHUB_REF_NAME`, and `BRANCH`, followed by the Git checkout.
735
747
 
@@ -747,10 +759,10 @@ Branch detection also supports `CF_PAGES_BRANCH`, `WORKERS_CI_BRANCH`, `VERCEL_G
747
759
  | `bootstrapHash` | Generated | SHA-256 owner-claim key hash. |
748
760
  | `suspended` | `false` | Disable a project. |
749
761
  | `writesPerDay` | `10000` self-hosted | Daily write-request allowance; hosted uses 500. |
750
- | `allowGuestSnapshots` | `false` | Let guest sessions attach element snapshots to their own threads. Has no effect when the server turns guests off (`KOMO_ALLOW_GUESTS=false`). |
762
+ | `allowGuestSnapshots` | `false` | Let guest sessions attach element snapshots to their own threads. Has no effect when the server turns guests off (`PALETTE_ALLOW_GUESTS=false`). |
751
763
  | `maxSnapshotBytes` | `52428800` (50 MiB) | Total snapshot bytes stored per project; hosted uses 5 MiB. |
752
764
 
753
- Worker bindings: `DB` (D1), optional `EDGE_LIMIT` (rate limiter). Variables: `PROJECTS`, `GOOGLE_CLIENT_ID`, optional `GITHUB_CLIENT_ID`, `KOMO_HOSTED="true"` to enable hosted provisioning, and `KOMO_PAUSED="true"` to pause service requests. Store `GOOGLE_CLIENT_SECRET` and optional `GITHUB_CLIENT_SECRET` as Worker secrets. Hosted configuration uses an hourly cleanup trigger. `/health` remains available while paused.
765
+ Worker bindings: `DB` (D1), optional `EDGE_LIMIT` (rate limiter). Variables: `PROJECTS`, `GOOGLE_CLIENT_ID`, optional `GITHUB_CLIENT_ID`, `PALETTE_HOSTED="true"` to enable hosted provisioning, and `PALETTE_PAUSED="true"` to pause service requests. Store `GOOGLE_CLIENT_SECRET` and optional `GITHUB_CLIENT_SECRET` as Worker secrets. Hosted configuration uses an hourly cleanup trigger. `/health` remains available while paused.
754
766
 
755
767
  ### Element snapshots
756
768
 
@@ -820,9 +832,9 @@ Palette is a fork of [tjcages/komo](https://github.com/tjcages/komo), which was
820
832
 
821
833
  Sign in as the Google owner with `npx @hazem3500/palette login`. In the widget, open **Account → Project settings** to manage access, download feedback, clear resolved threads, or delete a hosted project.
822
834
 
823
- Self-hosted Node deployments can also send signed webhooks for new comments, replies, and resolved threads. In **Project settings → Comment webhooks**, enter an HTTPS endpoint and a signing secret of at least 32 characters. Palette encrypts the secret at rest using the `KOMO_WEBHOOK_KEYS` environment variable; generate a 32-byte key with `openssl rand -base64 32` and configure a JSON keyring such as `{"current":"2026-10","keys":{"2026-10":"BASE64_KEY"}}`. Keep the keyring in your secret manager. Cloudflare Worker deployments currently disable webhook delivery because they do not yet have the DNS-pinning egress transport used by the Node adapter.
835
+ Self-hosted Node deployments can also send signed webhooks for new comments, replies, and resolved threads. In **Project settings → Comment webhooks**, enter an HTTPS endpoint and a signing secret of at least 32 characters. Palette encrypts the secret at rest using the `PALETTE_WEBHOOK_KEYS` environment variable; generate a 32-byte key with `openssl rand -base64 32` and configure a JSON keyring such as `{"current":"2026-10","keys":{"2026-10":"BASE64_KEY"}}`. Keep the keyring in your secret manager. Cloudflare Worker deployments currently disable webhook delivery because they do not yet have the DNS-pinning egress transport used by the Node adapter.
824
836
 
825
- Each request includes `X-Komo-Event`, `X-Komo-Delivery`, and `X-Komo-Signature: t=<unix-seconds>,v1=<hex-hmac-sha256>`. Verify the HMAC over `<timestamp>.<raw-request-body>` and reject timestamps outside your replay window. Treat `X-Komo-Delivery` as the idempotency key. Palette sends once without retries, does not follow redirects, and never logs secrets or response bodies. Master-key rotation requires replacing each project's signing secret and updating its receiver before removing the old key from the keyring.
837
+ Each request includes `X-Palette-Event`, `X-Palette-Delivery`, and `X-Palette-Signature: t=<unix-seconds>,v1=<hex-hmac-sha256>`. The same values also arrive as `X-Komo-Event`, `X-Komo-Delivery` and `X-Komo-Signature` for receivers set up before the rename. Verify the HMAC over `<timestamp>.<raw-request-body>` and reject timestamps outside your replay window. Treat `X-Palette-Delivery` as the idempotency key. Palette sends once without retries, does not follow redirects, and never logs secrets or response bodies. Master-key rotation requires replacing each project's signing secret and updating its receiver before removing the old key from the keyring.
826
838
 
827
839
  ```sh
828
840
  npx @hazem3500/palette project info
@@ -847,7 +859,7 @@ npx @hazem3500/palette project import --file /path/to/comments.json
847
859
 
848
860
  Exports include threads, replies, reactions, element snapshots, anchors, and historical profiles across all pages and branches, including resolved feedback. They exclude sessions, credentials, verified emails, and membership. Imports use the destination repository and keep imported authors unverified. Retrying the same export is safe; existing imported records are not overwritten. Destination quotas still apply. Export retries are required if feedback changes while downloading. Keep export files private.
849
861
 
850
- Upgrade self-hosted deployments with `npm install @hazem3500/palette@latest` then `npx @hazem3500/palette deploy`; the CLI applies bundled database migrations before redeploying. Self-hosted project removal is controlled by your Worker configuration. Account-wide data requests remain available at ty@offbr.co.
862
+ Upgrade self-hosted deployments with `npm install @hazem3500/palette@latest` then `npx @hazem3500/palette deploy`; the CLI applies bundled database migrations before redeploying. Self-hosted project removal is controlled by your Worker configuration.
851
863
 
852
864
  Without `pageRoot`, a sole existing content element with a layout box enables Frame. Script and style elements do not count as content roots. Multiple roots or `display: contents` use Floating; pass a suitable existing app container for Frame.
853
865
 
@@ -870,7 +882,7 @@ Handles show only when the pointer is on or near the selection, while resizing o
870
882
 
871
883
  **Hide the overlays.** In Design mode the comments visibility shortcut (**H** by default, `shortcuts.visibility`) hides every Design overlay: the selection, handles, hover outline, pins and move feedback. A tap toggles them and holding the key peeks until it's released; letting go of any key in the combo, or the window losing focus, ends the peek. The eye button in the inspector header (**Hide selection** / **Show selection**) does the same. Shortcut help lists the key.
872
884
 
873
- **Selection color.** Outlines, handles, the size chip, the hover outline and move feedback use a canvas selection color, separate from Palette's panel accent. It defaults to `#0c93f5`, a design-tool blue with 3:1 contrast on white and near-black. On a background close to it, the selection turns magenta. Set it with `design: { selectionColor: "#7c3aed" }` (with experimental geometry), or in any case from CSS with the `--komo-selection` custom property on Palette's host element (`[data-branch-comments]`).
885
+ **Selection color.** Outlines, handles, the size chip, the hover outline and move feedback use a canvas selection color, separate from Palette's panel accent. It defaults to `#0c93f5`, a design-tool blue with 3:1 contrast on white and near-black. On a background close to it, the selection turns magenta. Set it with `design: { selectionColor: "#7c3aed" }` (with experimental geometry), or in any case from CSS with the `--palette-selection` custom property (or the older `--komo-selection`) on Palette's host element (`[data-branch-comments]`).
874
886
 
875
887
  - **Drag** any handle. A W × H chip shows below the selection. The moving edge snaps to the parent's content edges and centre and to siblings' edges across the flow within 4px, with a thin guide; hold **Alt/Option** to resize freely. **Shift** on a corner keeps the box's proportions. Escape cancels.
876
888
  - **Keyboard:** press **M** on the canvas (or Tab) to focus the selection, then **Ctrl+arrows** (or **⌘+arrows** on macOS, where Ctrl+arrows switch spaces) to resize from the bottom-right by one local CSS pixel, Shift for ten; snapped grid items step one track. Escape returns to the canvas, where plain arrows and **Alt+arrows** keep their Design meaning (sibling and parent/child selection). Each step is announced politely, for example "Resized: width 241px".