@promptowl/contextnest-community 1.3.0 → 1.5.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/CONFIGURATION.md CHANGED
@@ -50,6 +50,8 @@ The server prints a loud warning at startup when `AUTH_MODE=open` is active.
50
50
  | `PROMPTOWL_API_URL` | `https://app.promptowl.ai` | PromptOwl's API origin — used for device auth, license validation, telemetry. Override for air-gapped or test setups. |
51
51
  | `PROMPTOWL_KEY` | `""` | Your PromptOwl Community License key (`pk_...`). Unlicensed instances still run and serve reads, but every write returns `503` until a valid key is installed. Can also be set via the browser License Setup Page, which persists it to `ENV_FILE_PATH`. |
52
52
  | `PROMPTOWL_SIGN_IN_GATE` | `open` | Restrict "Sign in with PromptOwl". `open` = anyone may; `admin-only` = only the license owner (admin) may, everyone else uses email/password (admin opens the login page with `?admin=1`); `disabled` = nobody may. Enforced server-side at `POST /auth/promptowl` and surfaced on the health endpoint. Unknown values fall back to `open`. |
53
+ | `OFFICIAL_COMMUNITY_SSO_SECRET` | `""` | **Official deployment only — leave unset on self-hosted.** Shared HMAC secret enabling the one-click "Open Community" SSO auto-login from PromptOwl. Must exactly match the same-named var on PromptOwl. When unset, `GET /auth/sso` returns `404` and the feature is disabled; self-hosted users keep using the manual device-code flow. |
54
+ | `PUBLIC_BASE_URL` | `""` | This server's canonical external URL (e.g. `https://community.promptowl.ai`). Checked against the SSO ticket's `aud` claim so a ticket minted for this server can't be replayed against another. Only relevant when `OFFICIAL_COMMUNITY_SSO_SECRET` is set; when unset, the audience check is skipped. |
53
55
  | `ENV_FILE_PATH` | `$DATA_ROOT/.env` | Path to the `.env` file the license install flow writes `PROMPTOWL_KEY` into (alongside existing vars), and which the server also reads at boot. Defaults **under `DATA_ROOT`** so the browser License Setup Page persists durably in containers — `$cwd` is `/app` in the official image (root-owned, discarded on container recreate), which silently lost the key. Override only if your writable, persisted `.env` lives elsewhere. In containers, providing `PROMPTOWL_KEY` directly via the environment also works and is read at boot. |
54
56
  | `TELEMETRY_ENABLED` | `"true"` (set to `"false"` to disable) | Batched, anonymized usage events sent to PromptOwl. Off disables the loop entirely. |
55
57
  | `TELEMETRY_INTERVAL_MS` | `3600000` (1 hour) | How often buffered telemetry is flushed to PromptOwl. |
@@ -2,7 +2,7 @@ import {
2
2
  ANON_USER_ID,
3
3
  config,
4
4
  getDb
5
- } from "./chunk-UEHFNBNR.js";
5
+ } from "./chunk-RMU3LOPH.js";
6
6
 
7
7
  // src/governance/stewardship-service.ts
8
8
  import { v4 as uuid2 } from "uuid";
@@ -1464,6 +1464,7 @@ export {
1464
1464
  getCurrentLicense,
1465
1465
  isLicenseAdminEmail,
1466
1466
  isLicenseAdminUserId,
1467
+ upsertEnvVar,
1467
1468
  installLicenseKey,
1468
1469
  startLicenseSafetyPoll,
1469
1470
  isSuspended,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  getDb
3
- } from "./chunk-UEHFNBNR.js";
3
+ } from "./chunk-RMU3LOPH.js";
4
4
 
5
5
  // src/governance/version-service.ts
6
6
  import { createHash } from "crypto";
@@ -1,16 +1,16 @@
1
1
  import {
2
2
  createVersion,
3
3
  setApprovedVersion
4
- } from "./chunk-6AXBB65N.js";
4
+ } from "./chunk-HIH7I232.js";
5
5
  import {
6
6
  buildTitleMap,
7
7
  canUserApprove,
8
8
  engineCache,
9
9
  resolveStewardsForNode
10
- } from "./chunk-SO74PQWI.js";
10
+ } from "./chunk-EMOE53KX.js";
11
11
  import {
12
12
  getDb
13
- } from "./chunk-UEHFNBNR.js";
13
+ } from "./chunk-RMU3LOPH.js";
14
14
 
15
15
  // src/governance/review-service.ts
16
16
  import { v4 as uuid } from "uuid";
@@ -44,6 +44,32 @@ var config = {
44
44
  get PROMPTOWL_KEY() {
45
45
  return process.env.PROMPTOWL_KEY || "";
46
46
  },
47
+ /**
48
+ * Shared secret for one-click SSO auto-login from PromptOwl (PO).
49
+ *
50
+ * THIS IS THE DOMAIN LOCK. Only the ONE official community deployment is
51
+ * given this secret (it matches OFFICIAL_COMMUNITY_SSO_SECRET on PO). PO
52
+ * signs a short-lived JWT ticket with it; this server verifies the ticket
53
+ * with the same secret at GET /auth/sso.
54
+ *
55
+ * Self-hosted deployments leave this UNSET — GET /auth/sso then returns 404,
56
+ * so the auto-login feature is disabled for every domain except ours. Those
57
+ * users keep using the manual device-code flow (/auth/promptowl).
58
+ */
59
+ get OFFICIAL_COMMUNITY_SSO_SECRET() {
60
+ return process.env.OFFICIAL_COMMUNITY_SSO_SECRET || "";
61
+ },
62
+ /**
63
+ * This server's own canonical, externally-reachable base URL (e.g.
64
+ * "https://community.promptowl.ai"). Used to validate the `aud` claim on
65
+ * incoming SSO tickets — a ticket minted for a different audience is
66
+ * rejected, so a ticket can't be replayed against another server. Compared
67
+ * with the trailing slash stripped. When unset, the `aud` check is skipped
68
+ * (the shared-secret check is still the primary gate).
69
+ */
70
+ get PUBLIC_BASE_URL() {
71
+ return (process.env.PUBLIC_BASE_URL || "").replace(/\/$/, "");
72
+ },
47
73
  /**
48
74
  * Restrict "Sign in with PromptOwl":
49
75
  * "open" — anyone may sign in with PromptOwl (default)
@@ -176,6 +202,35 @@ function runMigrations(db2) {
176
202
  CREATE INDEX IF NOT EXISTS idx_nest_collab_nest ON nest_collaborators(nest_id);
177
203
  CREATE INDEX IF NOT EXISTS idx_nest_collab_user ON nest_collaborators(user_id);
178
204
 
205
+ -- Annotation threads on hosted artifacts. Deliberately kept OUT of the
206
+ -- versioned/hash-chained node layer: annotations are high-churn human
207
+ -- feedback, not canonical node content. They reference the node + the
208
+ -- snapshot (node version) they were anchored against.
209
+ CREATE TABLE IF NOT EXISTS annotation_threads (
210
+ id TEXT PRIMARY KEY,
211
+ nest_id TEXT NOT NULL REFERENCES nests(id) ON DELETE CASCADE,
212
+ node_id TEXT NOT NULL,
213
+ snapshot_version INTEGER,
214
+ anchor_json TEXT, -- JSON {line?,quote,before,after}; NULL = whole-artifact
215
+ status TEXT NOT NULL DEFAULT 'open'
216
+ CHECK(status IN ('open', 'resolved')),
217
+ created_by TEXT NOT NULL, -- email
218
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
219
+ resolved_by TEXT,
220
+ resolved_at TEXT
221
+ );
222
+ CREATE TABLE IF NOT EXISTS annotation_comments (
223
+ id TEXT PRIMARY KEY,
224
+ thread_id TEXT NOT NULL REFERENCES annotation_threads(id) ON DELETE CASCADE,
225
+ author TEXT NOT NULL, -- email
226
+ body TEXT NOT NULL,
227
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
228
+ );
229
+ CREATE INDEX IF NOT EXISTS idx_annot_threads_node
230
+ ON annotation_threads(nest_id, node_id);
231
+ CREATE INDEX IF NOT EXISTS idx_annot_comments_thread
232
+ ON annotation_comments(thread_id);
233
+
179
234
  -- License validation cache
180
235
  CREATE TABLE IF NOT EXISTS license_cache (
181
236
  key TEXT PRIMARY KEY,
@@ -603,6 +658,50 @@ function runMigrations(db2) {
603
658
  })();
604
659
  recordMigration("009_lowercase_emails");
605
660
  }
661
+ if (!hasMigration("010_sso_used_jti")) {
662
+ db2.transaction(() => {
663
+ db2.exec(`
664
+ CREATE TABLE IF NOT EXISTS sso_used_jti (
665
+ jti TEXT PRIMARY KEY,
666
+ used_at TEXT NOT NULL DEFAULT (datetime('now')),
667
+ expires_at TEXT NOT NULL
668
+ );
669
+ CREATE INDEX IF NOT EXISTS idx_sso_used_jti_expires
670
+ ON sso_used_jti(expires_at);
671
+ `);
672
+ })();
673
+ recordMigration("010_sso_used_jti");
674
+ }
675
+ if (!hasMigration("011_comments")) {
676
+ db2.transaction(() => {
677
+ db2.exec(`
678
+ CREATE TABLE IF NOT EXISTS comments (
679
+ id TEXT PRIMARY KEY,
680
+ nest_id TEXT NOT NULL REFERENCES nests(id) ON DELETE CASCADE,
681
+ node_id TEXT NOT NULL,
682
+ version INTEGER, -- optional: comment pinned to a version
683
+ anchor_start INTEGER, -- optional: highlight span start (char offset)
684
+ anchor_end INTEGER, -- optional: highlight span end
685
+ anchor_text TEXT, -- optional: quoted highlighted text
686
+ parent_id TEXT REFERENCES comments(id) ON DELETE CASCADE, -- threading
687
+ author TEXT NOT NULL, -- email
688
+ body TEXT NOT NULL,
689
+ status TEXT NOT NULL DEFAULT 'open'
690
+ CHECK(status IN ('open', 'resolved')),
691
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
692
+ resolved_by TEXT, -- email
693
+ resolved_at TEXT
694
+ );
695
+ CREATE INDEX IF NOT EXISTS idx_comments_node
696
+ ON comments(nest_id, node_id, created_at);
697
+ CREATE INDEX IF NOT EXISTS idx_comments_open
698
+ ON comments(nest_id, node_id, status);
699
+ CREATE INDEX IF NOT EXISTS idx_comments_thread
700
+ ON comments(parent_id);
701
+ `);
702
+ })();
703
+ recordMigration("011_comments");
704
+ }
606
705
  }
607
706
 
608
707
  // src/db/client.ts