@promptowl/contextnest-community 0.1.0-alpha.1 → 1.0.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 CHANGED
@@ -1,105 +1,105 @@
1
- # ContextNest Community Edition
2
-
3
- **Self-hosted context governance server for AI workflows.** Part of the [PromptOwl](https://promptowl.ai) platform.
4
-
5
- > ⚠️ **Commercial Software.** ContextNest Community Edition is proprietary software licensed by Promptowl LLC. A free [PromptOwl account](https://app.promptowl.ai) is required to use it. Redistribution, hosting-as-a-service, and competitive use are prohibited. See [LICENSE.md](./LICENSE.md) for full terms.
6
-
7
- ---
8
-
9
- ## What it is
10
-
11
- ContextNest Community Edition is a self-hosted server that lets you:
12
-
13
- - Store, version, and govern markdown-based context documents ("nests")
14
- - Apply stewardship workflows — draft, pending review, approved
15
- - Serve approved context to AI agents via MCP, HTTP, or CLI
16
- - Sync with the PromptOwl hosted platform for multi-user collaboration
17
-
18
- The server runs locally or on your own infrastructure. Your PromptOwl account handles authentication, entitlement, and governance metadata.
19
-
20
- ## Quickstart
21
-
22
- ```bash
23
- # 1. Get a license key — sign up free at https://app.promptowl.ai
24
- # Settings → License Keys → Create a Community Server key
25
-
26
- # 2. (Optional) Scaffold a local nest with the open-source CLI
27
- npx @promptowl/contextnest-cli init
28
-
29
- # 3. Run the community server
30
- PROMPTOWL_KEY=pk_... npx @promptowl/contextnest-community
31
- ```
32
-
33
- The server listens on `http://localhost:3000` by default. Without a valid `PROMPTOWL_KEY` the server still runs but some features are limited. See [CONFIGURATION.md](./CONFIGURATION.md) for all environment variables (port, auth mode, storage, telemetry).
34
-
35
- ## System requirements
36
-
37
- - **Node.js** 20.x or later
38
- - **PromptOwl account** — free signup at <https://app.promptowl.ai/signup>
39
- - **OS:** Windows, macOS, or Linux
40
- - **Disk:** ~200 MB for the server, plus storage for your nests
41
-
42
- ## What you get
43
-
44
- | Feature | Community Edition | Enterprise |
45
- |---|:---:|:---:|
46
- | Self-hosted context server | ✅ | ✅ |
47
- | Markdown + YAML frontmatter vaults | ✅ | ✅ |
48
- | Stewardship workflow (draft/review/approve) | ✅ | ✅ |
49
- | MCP server for AI agents | ✅ | ✅ |
50
- | Multi-user governance UI | — | ✅ |
51
- | SSO / SAML / SCIM | — | ✅ |
52
- | Audit log streaming | — | ✅ |
53
- | Policy transforms (redaction, summarization) | — | ✅ |
54
- | Priority support and SLA | — | ✅ |
55
-
56
- For Enterprise pricing and features, contact **hoot@promptowl.ai** or visit <https://promptowl.ai/contextnest/>.
57
-
58
- ## Licensing
59
-
60
- ContextNest Community Edition is **commercial software**. It is **not open source**.
61
-
62
- **You may:**
63
- - Install and run the Software on devices You own or control
64
- - Use the Software for internal business purposes, tied to a valid PromptOwl account
65
- - Make backup and archival copies
66
-
67
- **You may not:**
68
- - Redistribute, resell, rent, lease, or sublicense the Software
69
- - Offer the Software as a hosted, managed, or software-as-a-service product to third parties
70
- - Reverse engineer, decompile, or create derivative works
71
- - Use the Software to build a competing product or service
72
- - Remove copyright, trademark, or license notices
73
-
74
- Full license text: [LICENSE.md](./LICENSE.md)
75
-
76
- **For redistribution, hosted-service, OEM, or regulated-industry use,** contact **hoot@promptowl.ai** for a commercial license agreement.
77
-
78
- ## Platform terms
79
-
80
- Because the Software requires a PromptOwl account, the following terms also apply to Your use:
81
-
82
- - **End User License Agreement** — <https://promptowl.ai/eula/>
83
- - **Terms of Service** — <https://promptowl.ai/terms-of-service/>
84
- - **Privacy Policy** — <https://promptowl.ai/privacy-policy/>
85
- - **Acceptable Use Policy** — <https://promptowl.ai/acceptable-use/>
86
- - **Disclaimer** — <https://promptowl.ai/disclaimer/>
87
- - **Cookie Policy** — <https://promptowl.ai/cookies/>
88
-
89
- ## Support
90
-
91
- - **Documentation:** <https://promptowl.ai/contextnest/>
92
- - **Product questions:** <https://promptowl.ai/contact-us/>
93
- - **Support & bugs:** `hoot@promptowl.ai`
94
- - **Commercial licensing:** `hoot@promptowl.ai` (subject: *ContextNest Commercial License*)
95
-
96
- ## AI output disclaimer
97
-
98
- The Software injects content into large language models. AI output may be inaccurate, incomplete, or inappropriate for your use case. You are responsible for reviewing and validating any AI-generated content before relying on it, particularly in business-critical or regulated contexts. Do not deploy the Software in medical, legal, financial-advisory, or safety-critical environments without appropriate human oversight.
99
-
100
- ---
101
-
102
- **Copyright © 2026 Promptowl LLC.** All rights reserved.
103
- "ContextNest" and "PromptOwl" are trademarks of Promptowl LLC.
104
-
105
- Promptowl LLC · 3060 Mercer University Dr Ste 110 · Atlanta, GA 30341 · USA
1
+ # ContextNest Community Edition
2
+
3
+ **Self-hosted context governance server for AI workflows.** Part of the [PromptOwl](https://promptowl.ai) platform.
4
+
5
+ > ⚠️ **Commercial Software.** ContextNest Community Edition is proprietary software licensed by Promptowl LLC. A free [PromptOwl account](https://app.promptowl.ai) is required to use it. Redistribution, hosting-as-a-service, and competitive use are prohibited. See [LICENSE.md](./LICENSE.md) for full terms.
6
+
7
+ ---
8
+
9
+ ## What it is
10
+
11
+ ContextNest Community Edition is a self-hosted server that lets you:
12
+
13
+ - Store, version, and govern markdown-based context documents ("nests")
14
+ - Apply stewardship workflows — draft, pending review, approved
15
+ - Serve approved context to AI agents via MCP, HTTP, or CLI
16
+ - Sync with the PromptOwl hosted platform for multi-user collaboration
17
+
18
+ The server runs locally or on your own infrastructure. Your PromptOwl account handles authentication, entitlement, and governance metadata.
19
+
20
+ ## Quickstart
21
+
22
+ ```bash
23
+ # 1. Get a license key — sign up free at https://app.promptowl.ai
24
+ # Settings → License Keys → Create a Community Server key
25
+
26
+ # 2. (Optional) Scaffold a local nest with the open-source CLI
27
+ npx @promptowl/contextnest-cli init
28
+
29
+ # 3. Run the community server
30
+ PROMPTOWL_KEY=pk_... npx @promptowl/contextnest-community
31
+ ```
32
+
33
+ The server listens on `http://localhost:3000` by default. Without a valid `PROMPTOWL_KEY` the server still runs but some features are limited. See [CONFIGURATION.md](./CONFIGURATION.md) for all environment variables (port, auth mode, storage, telemetry).
34
+
35
+ ## System requirements
36
+
37
+ - **Node.js** 20.x or later
38
+ - **PromptOwl account** — free signup at <https://app.promptowl.ai/signup>
39
+ - **OS:** Windows, macOS, or Linux
40
+ - **Disk:** ~200 MB for the server, plus storage for your nests
41
+
42
+ ## What you get
43
+
44
+ | Feature | Community Edition | Enterprise |
45
+ |---|:---:|:---:|
46
+ | Self-hosted context server | ✅ | ✅ |
47
+ | Markdown + YAML frontmatter vaults | ✅ | ✅ |
48
+ | Stewardship workflow (draft/review/approve) | ✅ | ✅ |
49
+ | MCP server for AI agents | ✅ | ✅ |
50
+ | Multi-user governance UI | — | ✅ |
51
+ | SSO / SAML / SCIM | — | ✅ |
52
+ | Audit log streaming | — | ✅ |
53
+ | Policy transforms (redaction, summarization) | — | ✅ |
54
+ | Priority support and SLA | — | ✅ |
55
+
56
+ For Enterprise pricing and features, contact **hoot@promptowl.ai** or visit <https://promptowl.ai/contextnest/>.
57
+
58
+ ## Licensing
59
+
60
+ ContextNest Community Edition is **commercial software**. It is **not open source**.
61
+
62
+ **You may:**
63
+ - Install and run the Software on devices You own or control
64
+ - Use the Software for internal business purposes, tied to a valid PromptOwl account
65
+ - Make backup and archival copies
66
+
67
+ **You may not:**
68
+ - Redistribute, resell, rent, lease, or sublicense the Software
69
+ - Offer the Software as a hosted, managed, or software-as-a-service product to third parties
70
+ - Reverse engineer, decompile, or create derivative works
71
+ - Use the Software to build a competing product or service
72
+ - Remove copyright, trademark, or license notices
73
+
74
+ Full license text: [LICENSE.md](./LICENSE.md)
75
+
76
+ **For redistribution, hosted-service, OEM, or regulated-industry use,** contact **hoot@promptowl.ai** for a commercial license agreement.
77
+
78
+ ## Platform terms
79
+
80
+ Because the Software requires a PromptOwl account, the following terms also apply to Your use:
81
+
82
+ - **End User License Agreement** — <https://promptowl.ai/eula/>
83
+ - **Terms of Service** — <https://promptowl.ai/terms-of-service/>
84
+ - **Privacy Policy** — <https://promptowl.ai/privacy-policy/>
85
+ - **Acceptable Use Policy** — <https://promptowl.ai/acceptable-use/>
86
+ - **Disclaimer** — <https://promptowl.ai/disclaimer/>
87
+ - **Cookie Policy** — <https://promptowl.ai/cookies/>
88
+
89
+ ## Support
90
+
91
+ - **Documentation:** <https://promptowl.ai/contextnest/>
92
+ - **Product questions:** <https://promptowl.ai/contact-us/>
93
+ - **Support & bugs:** `hoot@promptowl.ai`
94
+ - **Commercial licensing:** `hoot@promptowl.ai` (subject: *ContextNest Commercial License*)
95
+
96
+ ## AI output disclaimer
97
+
98
+ The Software injects content into large language models. AI output may be inaccurate, incomplete, or inappropriate for your use case. You are responsible for reviewing and validating any AI-generated content before relying on it, particularly in business-critical or regulated contexts. Do not deploy the Software in medical, legal, financial-advisory, or safety-critical environments without appropriate human oversight.
99
+
100
+ ---
101
+
102
+ **Copyright © 2026 Promptowl LLC.** All rights reserved.
103
+ "ContextNest" and "PromptOwl" are trademarks of Promptowl LLC.
104
+
105
+ Promptowl LLC · 3060 Mercer University Dr Ste 110 · Atlanta, GA 30341 · USA
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  config,
3
3
  getDb
4
- } from "./chunk-USIDOGVJ.js";
4
+ } from "./chunk-2TW25QEA.js";
5
5
 
6
6
  // src/governance/stewardship-service.ts
7
7
  import { v4 as uuid } from "uuid";
@@ -170,7 +170,34 @@ function listStewards(params) {
170
170
  sql += " ORDER BY scope, COALESCE(node_pattern, tag_name, ''), user_email";
171
171
  return db.prepare(sql).all(...args).map(rowToSteward);
172
172
  }
173
- function createStewardRecord(params) {
173
+ function rolePermission(role) {
174
+ return role === "editor" ? "write" : "read";
175
+ }
176
+ async function ensureCollaborator(nestId, email, permission, grantedBy) {
177
+ const db = getDb();
178
+ let userRow = db.prepare("SELECT id FROM users WHERE email = ?").get(email);
179
+ if (!userRow) {
180
+ const { hashPassword } = await import("./keys-YV33AJK3.js");
181
+ const newId = uuid();
182
+ db.prepare(
183
+ "INSERT INTO users (id, email, name, password_hash, is_invited) VALUES (?, ?, ?, ?, 1)"
184
+ ).run(newId, email, null, await hashPassword(uuid()));
185
+ userRow = { id: newId };
186
+ }
187
+ const nestRow = db.prepare("SELECT user_id FROM nests WHERE id = ?").get(nestId);
188
+ if (nestRow && nestRow.user_id === userRow.id) return;
189
+ const existing = db.prepare(
190
+ "SELECT id FROM nest_collaborators WHERE nest_id = ? AND user_id = ?"
191
+ ).get(nestId, userRow.id);
192
+ if (existing) return;
193
+ const granterRow = db.prepare("SELECT id FROM users WHERE email = ?").get(grantedBy);
194
+ const granterId = granterRow?.id ?? nestRow?.user_id;
195
+ if (!granterId) return;
196
+ db.prepare(
197
+ "INSERT INTO nest_collaborators (id, nest_id, user_id, permission, granted_by) VALUES (?, ?, ?, ?, ?)"
198
+ ).run(uuid(), nestId, userRow.id, permission, granterId);
199
+ }
200
+ async function createStewardRecord(params) {
174
201
  if (params.users.length === 0) {
175
202
  throw new Error("At least one user is required");
176
203
  }
@@ -229,6 +256,12 @@ function createStewardRecord(params) {
229
256
  isActive: true
230
257
  });
231
258
  results.push(created);
259
+ await ensureCollaborator(
260
+ params.nestId,
261
+ email,
262
+ rolePermission(user.role),
263
+ params.assignedBy
264
+ );
232
265
  }
233
266
  db.prepare(
234
267
  "UPDATE nests SET stewardship_enabled = 1 WHERE id = ? AND stewardship_enabled = 0"
@@ -255,7 +288,10 @@ function resolve(nestId, nodeId) {
255
288
  WHERE s.nest_id = ? AND s.is_active = 1 AND s.scope = 'folder'
256
289
  AND s.node_pattern IS NOT NULL
257
290
  AND instr(s.node_pattern, '*') = 0
258
- AND ? LIKE s.node_pattern || '/%'
291
+ AND (
292
+ ? LIKE s.node_pattern || '/%'
293
+ OR s.node_pattern = ?
294
+ )
259
295
  UNION ALL
260
296
  SELECT s.*, 3 AS priority, ('tag: ' || s.tag_name) AS match_source
261
297
  FROM stewards s
@@ -270,7 +306,20 @@ function resolve(nestId, nodeId) {
270
306
  WHERE s.nest_id = ? AND s.is_active = 1 AND s.scope = 'nest'
271
307
  ORDER BY priority ASC, user_email ASC
272
308
  `
273
- ).all(nestId, nodeId, nestId, nodeId, nestId, nodeId, nestId);
309
+ ).all(
310
+ nestId,
311
+ nodeId,
312
+ // document branch
313
+ nestId,
314
+ nodeId,
315
+ nestId,
316
+ // folder branch (path-prefix OR whole-nest folder)
317
+ nestId,
318
+ nodeId,
319
+ // tag branch
320
+ nestId
321
+ // nest branch
322
+ );
274
323
  const resolved = rows.map((row) => ({
275
324
  steward: rowToSteward(row),
276
325
  priority: row.priority,
@@ -1,5 +1,21 @@
1
1
  // src/config.ts
2
- import { join } from "path";
2
+ import { join, dirname } from "path";
3
+ import { existsSync } from "fs";
4
+ import { fileURLToPath } from "url";
5
+ import dotenv from "dotenv";
6
+ var __filename = fileURLToPath(import.meta.url);
7
+ var __dirname = dirname(__filename);
8
+ var envCandidates = [
9
+ join(process.cwd(), ".env"),
10
+ join(__dirname, "..", ".env")
11
+ ];
12
+ var envFileLoaded = envCandidates.find((p) => existsSync(p)) || null;
13
+ if (envFileLoaded) {
14
+ dotenv.config({ path: envFileLoaded, override: true });
15
+ }
16
+ console.log(
17
+ `[config] dotenv: ${envFileLoaded ? `loaded ${envFileLoaded}` : "no .env file found"}`
18
+ );
3
19
  function dataRoot() {
4
20
  return process.env.DATA_ROOT || join(process.cwd(), "data");
5
21
  }
@@ -19,6 +35,14 @@ var config = {
19
35
  get PROMPTOWL_KEY() {
20
36
  return process.env.PROMPTOWL_KEY || "";
21
37
  },
38
+ /**
39
+ * Path to the .env file the server reads its config from. Used by
40
+ * the license install flow to persist PROMPTOWL_KEY alongside any
41
+ * existing env vars, instead of a separate sidecar file.
42
+ */
43
+ get ENV_FILE_PATH() {
44
+ return process.env.ENV_FILE_PATH || join(process.cwd(), ".env");
45
+ },
22
46
  get TELEMETRY_ENABLED() {
23
47
  return process.env.TELEMETRY_ENABLED !== "false";
24
48
  },
@@ -52,7 +76,7 @@ var config = {
52
76
  // src/db/client.ts
53
77
  import Database from "better-sqlite3";
54
78
  import { mkdirSync } from "fs";
55
- import { dirname } from "path";
79
+ import { dirname as dirname2 } from "path";
56
80
 
57
81
  // src/db/migrations.ts
58
82
  function runMigrations(db2) {
@@ -204,6 +228,9 @@ function runMigrations(db2) {
204
228
  if (!userCols.includes("is_admin")) {
205
229
  db2.exec("ALTER TABLE users ADD COLUMN is_admin INTEGER NOT NULL DEFAULT 0");
206
230
  }
231
+ if (!userCols.includes("is_invited")) {
232
+ db2.exec("ALTER TABLE users ADD COLUMN is_invited INTEGER NOT NULL DEFAULT 0");
233
+ }
207
234
  db2.exec(`
208
235
  CREATE TABLE IF NOT EXISTS schema_migrations (
209
236
  id TEXT PRIMARY KEY,
@@ -326,16 +353,65 @@ function runMigrations(db2) {
326
353
  recordMigration("002_steward_parity");
327
354
  })();
328
355
  }
356
+ if (!hasMigration("003_sessions_and_single_api_key")) {
357
+ db2.transaction(() => {
358
+ db2.exec(`
359
+ CREATE TABLE IF NOT EXISTS sessions (
360
+ id TEXT PRIMARY KEY,
361
+ user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
362
+ expires_at TEXT NOT NULL,
363
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
364
+ last_seen_at TEXT NOT NULL DEFAULT (datetime('now')),
365
+ user_agent TEXT
366
+ );
367
+ CREATE INDEX IF NOT EXISTS idx_sessions_user ON sessions(user_id);
368
+ CREATE INDEX IF NOT EXISTS idx_sessions_expires ON sessions(expires_at);
369
+ `);
370
+ db2.exec(`
371
+ DELETE FROM api_keys
372
+ WHERE id IN (
373
+ SELECT id FROM (
374
+ SELECT
375
+ id,
376
+ ROW_NUMBER() OVER (
377
+ PARTITION BY user_id
378
+ ORDER BY
379
+ COALESCE(last_used_at, '') DESC,
380
+ created_at DESC,
381
+ id DESC
382
+ ) AS rn
383
+ FROM api_keys
384
+ )
385
+ WHERE rn > 1
386
+ );
387
+ `);
388
+ db2.exec(`
389
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_api_keys_user_unique
390
+ ON api_keys(user_id);
391
+ `);
392
+ recordMigration("003_sessions_and_single_api_key");
393
+ })();
394
+ }
395
+ if (!hasMigration("004_license_cache_owner_email")) {
396
+ db2.transaction(() => {
397
+ const cols = db2.prepare("PRAGMA table_info(license_cache)").all().map((c) => c.name);
398
+ if (!cols.includes("owner_email")) {
399
+ db2.exec("ALTER TABLE license_cache ADD COLUMN owner_email TEXT");
400
+ }
401
+ recordMigration("004_license_cache_owner_email");
402
+ })();
403
+ }
329
404
  }
330
405
 
331
406
  // src/db/client.ts
332
407
  var db = null;
333
408
  function getDb() {
334
409
  if (!db) {
335
- mkdirSync(dirname(config.DATABASE_PATH), { recursive: true });
410
+ mkdirSync(dirname2(config.DATABASE_PATH), { recursive: true });
336
411
  db = new Database(config.DATABASE_PATH);
337
412
  db.pragma("journal_mode = WAL");
338
413
  db.pragma("foreign_keys = ON");
414
+ db.pragma("busy_timeout = 5000");
339
415
  runMigrations(db);
340
416
  }
341
417
  return db;
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  getDb
3
- } from "./chunk-USIDOGVJ.js";
3
+ } from "./chunk-2TW25QEA.js";
4
4
 
5
5
  // src/governance/version-service.ts
6
6
  import { createHash } from "crypto";
@@ -102,6 +102,28 @@ function getNodeTags(nestId, nodeId) {
102
102
  return [];
103
103
  }
104
104
  }
105
+ function getDisplayStatus(nestId, nodeId) {
106
+ const db = getDb();
107
+ const pending = db.prepare(
108
+ "SELECT 1 FROM review_requests WHERE nest_id = ? AND node_id = ? AND status = 'pending' LIMIT 1"
109
+ ).get(nestId, nodeId);
110
+ if (pending) return "pending_review";
111
+ const current = db.prepare(
112
+ `SELECT version, status FROM node_versions
113
+ WHERE nest_id = ? AND node_id = ?
114
+ ORDER BY version DESC LIMIT 1`
115
+ ).get(nestId, nodeId);
116
+ if (!current) return "draft";
117
+ if (current.status === "approved") {
118
+ const approved = db.prepare(
119
+ "SELECT approved_version FROM approved_versions WHERE nest_id = ? AND node_id = ?"
120
+ ).get(nestId, nodeId);
121
+ if (approved?.approved_version === current.version) return "approved";
122
+ return "draft";
123
+ }
124
+ if (current.status === "rejected") return "rejected";
125
+ return "draft";
126
+ }
105
127
  function rowToVersion(row) {
106
128
  return {
107
129
  version: row.version,
@@ -123,5 +145,6 @@ export {
123
145
  getApprovedVersion,
124
146
  setApprovedVersion,
125
147
  checkConflict,
126
- getNodeTags
148
+ getNodeTags,
149
+ getDisplayStatus
127
150
  };
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  canUserApprove,
3
3
  resolveStewardsForNode
4
- } from "./chunk-Q2DCOS7V.js";
4
+ } from "./chunk-2FXVMVZJ.js";
5
5
  import {
6
6
  getDb
7
- } from "./chunk-USIDOGVJ.js";
7
+ } from "./chunk-2TW25QEA.js";
8
8
 
9
9
  // src/governance/review-service.ts
10
10
  import { v4 as uuid } from "uuid";