@promptowl/contextnest-community 0.1.0-alpha.1
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 +118 -0
- package/LICENSE.md +142 -0
- package/README.md +105 -0
- package/dist/chunk-7K2LLJXK.js +58 -0
- package/dist/chunk-DJFEV4ET.js +199 -0
- package/dist/chunk-P6NG56CO.js +127 -0
- package/dist/chunk-Q2DCOS7V.js +491 -0
- package/dist/chunk-USIDOGVJ.js +347 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2867 -0
- package/dist/keys-YV33AJK3.js +16 -0
- package/dist/review-service-5CLVZKAR.js +23 -0
- package/dist/stewardship-service-NC67XBYO.js +31 -0
- package/dist/version-service-Z6FYJRAG.js +23 -0
- package/dist/web3/assets/hootie-C2ocYkn4.svg +8 -0
- package/dist/web3/assets/index-CemroDXg.css +1 -0
- package/dist/web3/assets/index-xLLf4lHJ.js +332 -0
- package/dist/web3/index.html +14 -0
- package/package.json +108 -0
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
// src/config.ts
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
function dataRoot() {
|
|
4
|
+
return process.env.DATA_ROOT || join(process.cwd(), "data");
|
|
5
|
+
}
|
|
6
|
+
var config = {
|
|
7
|
+
get PORT() {
|
|
8
|
+
return parseInt(process.env.PORT || "3838", 10);
|
|
9
|
+
},
|
|
10
|
+
get DATA_ROOT() {
|
|
11
|
+
return dataRoot();
|
|
12
|
+
},
|
|
13
|
+
get DATABASE_PATH() {
|
|
14
|
+
return process.env.DATABASE_PATH || join(dataRoot(), "community.db");
|
|
15
|
+
},
|
|
16
|
+
get PROMPTOWL_API_URL() {
|
|
17
|
+
return process.env.PROMPTOWL_API_URL || "https://app.promptowl.ai";
|
|
18
|
+
},
|
|
19
|
+
get PROMPTOWL_KEY() {
|
|
20
|
+
return process.env.PROMPTOWL_KEY || "";
|
|
21
|
+
},
|
|
22
|
+
get TELEMETRY_ENABLED() {
|
|
23
|
+
return process.env.TELEMETRY_ENABLED !== "false";
|
|
24
|
+
},
|
|
25
|
+
get TELEMETRY_INTERVAL_MS() {
|
|
26
|
+
return parseInt(process.env.TELEMETRY_INTERVAL_MS || "3600000", 10);
|
|
27
|
+
},
|
|
28
|
+
get AUTH_MODE() {
|
|
29
|
+
return process.env.AUTH_MODE || "key";
|
|
30
|
+
},
|
|
31
|
+
/**
|
|
32
|
+
* CORS origin allowlist. Comma-separated list of origins or "*".
|
|
33
|
+
* Defaults to "*" in open mode (read-heavy, no credentials to steal)
|
|
34
|
+
* and a locked-down list in key mode (Bearer tokens flow, CSRF matters).
|
|
35
|
+
*/
|
|
36
|
+
get CORS_ORIGINS() {
|
|
37
|
+
const raw = process.env.CORS_ORIGINS;
|
|
38
|
+
if (raw === "*") return "*";
|
|
39
|
+
if (raw) return raw.split(",").map((s) => s.trim()).filter(Boolean);
|
|
40
|
+
if ((process.env.AUTH_MODE || "key") === "open") return "*";
|
|
41
|
+
return ["http://localhost:5173", "http://localhost:3838"];
|
|
42
|
+
},
|
|
43
|
+
/**
|
|
44
|
+
* Max JSON body size in bytes. Prevents DoS via giant payloads.
|
|
45
|
+
* Default: 10 MB. Override via MAX_BODY_BYTES env.
|
|
46
|
+
*/
|
|
47
|
+
get MAX_BODY_BYTES() {
|
|
48
|
+
return parseInt(process.env.MAX_BODY_BYTES || String(10 * 1024 * 1024), 10);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// src/db/client.ts
|
|
53
|
+
import Database from "better-sqlite3";
|
|
54
|
+
import { mkdirSync } from "fs";
|
|
55
|
+
import { dirname } from "path";
|
|
56
|
+
|
|
57
|
+
// src/db/migrations.ts
|
|
58
|
+
function runMigrations(db2) {
|
|
59
|
+
db2.exec(`
|
|
60
|
+
CREATE TABLE IF NOT EXISTS users (
|
|
61
|
+
id TEXT PRIMARY KEY,
|
|
62
|
+
email TEXT UNIQUE NOT NULL,
|
|
63
|
+
name TEXT,
|
|
64
|
+
password_hash TEXT NOT NULL,
|
|
65
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
CREATE TABLE IF NOT EXISTS nests (
|
|
69
|
+
id TEXT PRIMARY KEY,
|
|
70
|
+
user_id TEXT NOT NULL REFERENCES users(id),
|
|
71
|
+
name TEXT NOT NULL,
|
|
72
|
+
slug TEXT NOT NULL,
|
|
73
|
+
description TEXT,
|
|
74
|
+
visibility TEXT NOT NULL DEFAULT 'private'
|
|
75
|
+
CHECK(visibility IN ('private', 'public')),
|
|
76
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
77
|
+
UNIQUE(user_id, slug)
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
CREATE TABLE IF NOT EXISTS api_keys (
|
|
81
|
+
id TEXT PRIMARY KEY,
|
|
82
|
+
user_id TEXT NOT NULL REFERENCES users(id),
|
|
83
|
+
key_hash TEXT UNIQUE NOT NULL,
|
|
84
|
+
key_prefix TEXT NOT NULL,
|
|
85
|
+
nest_id TEXT REFERENCES nests(id) ON DELETE CASCADE,
|
|
86
|
+
label TEXT,
|
|
87
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
88
|
+
last_used_at TEXT
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
CREATE INDEX IF NOT EXISTS idx_nests_user ON nests(user_id);
|
|
92
|
+
CREATE INDEX IF NOT EXISTS idx_keys_user ON api_keys(user_id);
|
|
93
|
+
CREATE INDEX IF NOT EXISTS idx_keys_hash ON api_keys(key_hash);
|
|
94
|
+
|
|
95
|
+
CREATE TABLE IF NOT EXISTS nest_collaborators (
|
|
96
|
+
id TEXT PRIMARY KEY,
|
|
97
|
+
nest_id TEXT NOT NULL REFERENCES nests(id) ON DELETE CASCADE,
|
|
98
|
+
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
99
|
+
permission TEXT NOT NULL CHECK(permission IN ('read', 'write', 'admin')),
|
|
100
|
+
granted_by TEXT NOT NULL REFERENCES users(id),
|
|
101
|
+
granted_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
102
|
+
);
|
|
103
|
+
CREATE INDEX IF NOT EXISTS idx_nest_collab_nest ON nest_collaborators(nest_id);
|
|
104
|
+
CREATE INDEX IF NOT EXISTS idx_nest_collab_user ON nest_collaborators(user_id);
|
|
105
|
+
|
|
106
|
+
-- License validation cache
|
|
107
|
+
CREATE TABLE IF NOT EXISTS license_cache (
|
|
108
|
+
key TEXT PRIMARY KEY,
|
|
109
|
+
tier TEXT NOT NULL,
|
|
110
|
+
org TEXT,
|
|
111
|
+
limits_json TEXT,
|
|
112
|
+
validated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
-- Telemetry event log (batched and sent to PromptOwl)
|
|
116
|
+
CREATE TABLE IF NOT EXISTS telemetry_events (
|
|
117
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
118
|
+
event TEXT NOT NULL,
|
|
119
|
+
data_json TEXT,
|
|
120
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
121
|
+
sent INTEGER NOT NULL DEFAULT 0
|
|
122
|
+
);
|
|
123
|
+
`);
|
|
124
|
+
db2.exec(`
|
|
125
|
+
-- Steward assignments (mirrors PromptOwl ContextSteward model)
|
|
126
|
+
-- scope+target combination determines what the steward governs
|
|
127
|
+
-- Resolution priority: document(1) > folder(2) > tag(3) > nest(4)
|
|
128
|
+
CREATE TABLE IF NOT EXISTS stewards (
|
|
129
|
+
id TEXT PRIMARY KEY,
|
|
130
|
+
nest_id TEXT NOT NULL REFERENCES nests(id) ON DELETE CASCADE,
|
|
131
|
+
scope TEXT NOT NULL CHECK(scope IN ('document', 'folder', 'tag', 'nest')),
|
|
132
|
+
node_pattern TEXT, -- glob for document/folder scope (e.g. "nodes/api-*")
|
|
133
|
+
tag_name TEXT, -- for tag scope
|
|
134
|
+
user_email TEXT NOT NULL,
|
|
135
|
+
user_id TEXT REFERENCES users(id),
|
|
136
|
+
role TEXT NOT NULL DEFAULT 'reviewer'
|
|
137
|
+
CHECK(role IN ('editor', 'reviewer', 'admin')),
|
|
138
|
+
can_approve INTEGER NOT NULL DEFAULT 1,
|
|
139
|
+
can_reject INTEGER NOT NULL DEFAULT 1,
|
|
140
|
+
assigned_by TEXT NOT NULL,
|
|
141
|
+
assigned_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
142
|
+
is_active INTEGER NOT NULL DEFAULT 1
|
|
143
|
+
);
|
|
144
|
+
CREATE INDEX IF NOT EXISTS idx_stewards_nest ON stewards(nest_id);
|
|
145
|
+
CREATE INDEX IF NOT EXISTS idx_stewards_email ON stewards(user_email);
|
|
146
|
+
CREATE INDEX IF NOT EXISTS idx_stewards_scope ON stewards(nest_id, scope);
|
|
147
|
+
|
|
148
|
+
-- Review requests (mirrors PromptOwl ReviewRequest model)
|
|
149
|
+
-- Only one pending review per node version (enforced in code)
|
|
150
|
+
CREATE TABLE IF NOT EXISTS review_requests (
|
|
151
|
+
id TEXT PRIMARY KEY,
|
|
152
|
+
nest_id TEXT NOT NULL REFERENCES nests(id) ON DELETE CASCADE,
|
|
153
|
+
node_id TEXT NOT NULL,
|
|
154
|
+
version INTEGER NOT NULL,
|
|
155
|
+
requested_by TEXT NOT NULL, -- email
|
|
156
|
+
requested_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
157
|
+
request_note TEXT,
|
|
158
|
+
status TEXT NOT NULL DEFAULT 'pending'
|
|
159
|
+
CHECK(status IN ('pending', 'approved', 'rejected', 'cancelled')),
|
|
160
|
+
resolved_by TEXT, -- email
|
|
161
|
+
resolved_at TEXT,
|
|
162
|
+
resolution_note TEXT,
|
|
163
|
+
is_override INTEGER NOT NULL DEFAULT 0,
|
|
164
|
+
priority TEXT NOT NULL DEFAULT 'normal'
|
|
165
|
+
CHECK(priority IN ('low', 'normal', 'high', 'urgent'))
|
|
166
|
+
);
|
|
167
|
+
CREATE INDEX IF NOT EXISTS idx_reviews_nest ON review_requests(nest_id);
|
|
168
|
+
CREATE INDEX IF NOT EXISTS idx_reviews_node ON review_requests(nest_id, node_id);
|
|
169
|
+
CREATE INDEX IF NOT EXISTS idx_reviews_status ON review_requests(nest_id, status);
|
|
170
|
+
|
|
171
|
+
-- Node versions (tracks every edit, approved version pinning)
|
|
172
|
+
-- The actual content lives on disk via NestStorage; this tracks metadata.
|
|
173
|
+
CREATE TABLE IF NOT EXISTS node_versions (
|
|
174
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
175
|
+
nest_id TEXT NOT NULL REFERENCES nests(id) ON DELETE CASCADE,
|
|
176
|
+
node_id TEXT NOT NULL,
|
|
177
|
+
version INTEGER NOT NULL,
|
|
178
|
+
content_hash TEXT NOT NULL, -- SHA-256 of content for conflict detection
|
|
179
|
+
author TEXT NOT NULL, -- email
|
|
180
|
+
status TEXT NOT NULL DEFAULT 'draft'
|
|
181
|
+
CHECK(status IN ('draft', 'pending_review', 'approved', 'rejected')),
|
|
182
|
+
change_note TEXT,
|
|
183
|
+
tags_json TEXT, -- JSON array of tags for steward resolution
|
|
184
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
185
|
+
UNIQUE(nest_id, node_id, version)
|
|
186
|
+
);
|
|
187
|
+
CREATE INDEX IF NOT EXISTS idx_versions_node ON node_versions(nest_id, node_id);
|
|
188
|
+
|
|
189
|
+
-- Approved version pinning (which version AI tools serve)
|
|
190
|
+
CREATE TABLE IF NOT EXISTS approved_versions (
|
|
191
|
+
nest_id TEXT NOT NULL REFERENCES nests(id) ON DELETE CASCADE,
|
|
192
|
+
node_id TEXT NOT NULL,
|
|
193
|
+
approved_version INTEGER NOT NULL,
|
|
194
|
+
approved_by TEXT NOT NULL, -- email
|
|
195
|
+
approved_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
196
|
+
PRIMARY KEY (nest_id, node_id)
|
|
197
|
+
);
|
|
198
|
+
`);
|
|
199
|
+
const nestCols = db2.prepare("PRAGMA table_info(nests)").all().map((c) => c.name);
|
|
200
|
+
if (!nestCols.includes("stewardship_enabled")) {
|
|
201
|
+
db2.exec("ALTER TABLE nests ADD COLUMN stewardship_enabled INTEGER NOT NULL DEFAULT 0");
|
|
202
|
+
}
|
|
203
|
+
const userCols = db2.prepare("PRAGMA table_info(users)").all().map((c) => c.name);
|
|
204
|
+
if (!userCols.includes("is_admin")) {
|
|
205
|
+
db2.exec("ALTER TABLE users ADD COLUMN is_admin INTEGER NOT NULL DEFAULT 0");
|
|
206
|
+
}
|
|
207
|
+
db2.exec(`
|
|
208
|
+
CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
209
|
+
id TEXT PRIMARY KEY,
|
|
210
|
+
applied_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
211
|
+
);
|
|
212
|
+
`);
|
|
213
|
+
const hasMigration = (id) => !!db2.prepare("SELECT id FROM schema_migrations WHERE id = ?").get(id);
|
|
214
|
+
const recordMigration = (id) => db2.prepare("INSERT OR IGNORE INTO schema_migrations (id) VALUES (?)").run(id);
|
|
215
|
+
if (!hasMigration("002_steward_parity")) {
|
|
216
|
+
db2.transaction(() => {
|
|
217
|
+
db2.exec(`
|
|
218
|
+
CREATE TABLE stewards_new (
|
|
219
|
+
id TEXT PRIMARY KEY,
|
|
220
|
+
nest_id TEXT NOT NULL REFERENCES nests(id) ON DELETE CASCADE,
|
|
221
|
+
scope TEXT NOT NULL CHECK(scope IN ('document', 'folder', 'tag', 'nest')),
|
|
222
|
+
node_pattern TEXT,
|
|
223
|
+
tag_name TEXT,
|
|
224
|
+
user_email TEXT NOT NULL,
|
|
225
|
+
user_id TEXT REFERENCES users(id),
|
|
226
|
+
role TEXT NOT NULL DEFAULT 'reviewer'
|
|
227
|
+
CHECK(role IN ('editor', 'reviewer', 'viewer')),
|
|
228
|
+
can_approve INTEGER NOT NULL DEFAULT 1,
|
|
229
|
+
can_reject INTEGER NOT NULL DEFAULT 1,
|
|
230
|
+
assigned_by TEXT NOT NULL,
|
|
231
|
+
assigned_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
232
|
+
is_active INTEGER NOT NULL DEFAULT 1
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
-- Copy rows; map legacy 'admin' role to 'reviewer', normalize tag_name + email
|
|
236
|
+
INSERT INTO stewards_new
|
|
237
|
+
(id, nest_id, scope, node_pattern, tag_name, user_email, user_id,
|
|
238
|
+
role, can_approve, can_reject, assigned_by, assigned_at, is_active)
|
|
239
|
+
SELECT
|
|
240
|
+
id, nest_id, scope, node_pattern,
|
|
241
|
+
CASE
|
|
242
|
+
WHEN tag_name IS NULL THEN NULL
|
|
243
|
+
ELSE lower(ltrim(tag_name, '#'))
|
|
244
|
+
END,
|
|
245
|
+
lower(user_email),
|
|
246
|
+
user_id,
|
|
247
|
+
CASE role WHEN 'admin' THEN 'reviewer' ELSE role END,
|
|
248
|
+
can_approve, can_reject, assigned_by, assigned_at, is_active
|
|
249
|
+
FROM stewards;
|
|
250
|
+
|
|
251
|
+
DROP TABLE stewards;
|
|
252
|
+
ALTER TABLE stewards_new RENAME TO stewards;
|
|
253
|
+
|
|
254
|
+
CREATE INDEX idx_stewards_nest ON stewards(nest_id);
|
|
255
|
+
CREATE INDEX idx_stewards_email ON stewards(user_email);
|
|
256
|
+
CREATE INDEX idx_stewards_scope ON stewards(nest_id, scope);
|
|
257
|
+
`);
|
|
258
|
+
db2.exec(`
|
|
259
|
+
CREATE UNIQUE INDEX idx_stewards_uniq_nest
|
|
260
|
+
ON stewards(nest_id, user_email)
|
|
261
|
+
WHERE scope = 'nest' AND is_active = 1;
|
|
262
|
+
|
|
263
|
+
CREATE UNIQUE INDEX idx_stewards_uniq_document
|
|
264
|
+
ON stewards(nest_id, node_pattern, user_email)
|
|
265
|
+
WHERE scope = 'document' AND node_pattern IS NOT NULL AND is_active = 1;
|
|
266
|
+
|
|
267
|
+
CREATE UNIQUE INDEX idx_stewards_uniq_folder
|
|
268
|
+
ON stewards(nest_id, node_pattern, user_email)
|
|
269
|
+
WHERE scope = 'folder' AND node_pattern IS NOT NULL AND is_active = 1;
|
|
270
|
+
|
|
271
|
+
CREATE UNIQUE INDEX idx_stewards_uniq_tag
|
|
272
|
+
ON stewards(nest_id, tag_name, user_email)
|
|
273
|
+
WHERE scope = 'tag' AND tag_name IS NOT NULL AND is_active = 1;
|
|
274
|
+
|
|
275
|
+
-- Resolution support indexes
|
|
276
|
+
CREATE INDEX idx_stewards_tag_lookup
|
|
277
|
+
ON stewards(nest_id, tag_name)
|
|
278
|
+
WHERE scope = 'tag' AND is_active = 1;
|
|
279
|
+
CREATE INDEX idx_stewards_folder_lookup
|
|
280
|
+
ON stewards(nest_id, node_pattern)
|
|
281
|
+
WHERE scope = 'folder' AND is_active = 1;
|
|
282
|
+
CREATE INDEX idx_stewards_doc_lookup
|
|
283
|
+
ON stewards(nest_id, node_pattern)
|
|
284
|
+
WHERE scope = 'document' AND is_active = 1;
|
|
285
|
+
`);
|
|
286
|
+
db2.exec(`
|
|
287
|
+
CREATE TABLE IF NOT EXISTS node_tag_index (
|
|
288
|
+
nest_id TEXT NOT NULL REFERENCES nests(id) ON DELETE CASCADE,
|
|
289
|
+
node_id TEXT NOT NULL,
|
|
290
|
+
tag_name TEXT NOT NULL,
|
|
291
|
+
PRIMARY KEY (nest_id, node_id, tag_name)
|
|
292
|
+
);
|
|
293
|
+
CREATE INDEX IF NOT EXISTS idx_node_tag_by_tag
|
|
294
|
+
ON node_tag_index(nest_id, tag_name);
|
|
295
|
+
`);
|
|
296
|
+
const rows = db2.prepare(
|
|
297
|
+
`SELECT nv.nest_id, nv.node_id, nv.tags_json
|
|
298
|
+
FROM node_versions nv
|
|
299
|
+
JOIN (
|
|
300
|
+
SELECT nest_id, node_id, MAX(version) AS v
|
|
301
|
+
FROM node_versions
|
|
302
|
+
GROUP BY nest_id, node_id
|
|
303
|
+
) latest
|
|
304
|
+
ON latest.nest_id = nv.nest_id
|
|
305
|
+
AND latest.node_id = nv.node_id
|
|
306
|
+
AND latest.v = nv.version`
|
|
307
|
+
).all();
|
|
308
|
+
const insertTag = db2.prepare(
|
|
309
|
+
"INSERT OR IGNORE INTO node_tag_index (nest_id, node_id, tag_name) VALUES (?, ?, ?)"
|
|
310
|
+
);
|
|
311
|
+
for (const row of rows) {
|
|
312
|
+
if (!row.tags_json) continue;
|
|
313
|
+
let tags;
|
|
314
|
+
try {
|
|
315
|
+
tags = JSON.parse(row.tags_json);
|
|
316
|
+
} catch {
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
if (!Array.isArray(tags)) continue;
|
|
320
|
+
for (const raw of tags) {
|
|
321
|
+
if (typeof raw !== "string") continue;
|
|
322
|
+
const norm = raw.trim().replace(/^#+/, "").toLowerCase();
|
|
323
|
+
if (norm) insertTag.run(row.nest_id, row.node_id, norm);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
recordMigration("002_steward_parity");
|
|
327
|
+
})();
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// src/db/client.ts
|
|
332
|
+
var db = null;
|
|
333
|
+
function getDb() {
|
|
334
|
+
if (!db) {
|
|
335
|
+
mkdirSync(dirname(config.DATABASE_PATH), { recursive: true });
|
|
336
|
+
db = new Database(config.DATABASE_PATH);
|
|
337
|
+
db.pragma("journal_mode = WAL");
|
|
338
|
+
db.pragma("foreign_keys = ON");
|
|
339
|
+
runMigrations(db);
|
|
340
|
+
}
|
|
341
|
+
return db;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
export {
|
|
345
|
+
config,
|
|
346
|
+
getDb
|
|
347
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|