@promptowl/contextnest-community 1.8.0 → 1.10.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.
@@ -1,9 +1,15 @@
1
1
  import {
2
- config,
3
- getDb,
2
+ ConflictError,
3
+ ValidationError
4
+ } from "./chunk-3JTODC3Y.js";
5
+ import {
4
6
  insertOrReplace,
5
7
  nowExpr
6
- } from "./chunk-PLAHCDQG.js";
8
+ } from "./chunk-XQ46F76G.js";
9
+ import {
10
+ config,
11
+ getDb
12
+ } from "./chunk-BC6KFUZH.js";
7
13
  import {
8
14
  ANON_USER_ID
9
15
  } from "./chunk-SLTQACJW.js";
@@ -104,46 +110,6 @@ function parseAccessYaml(content) {
104
110
  return result;
105
111
  }
106
112
 
107
- // src/shared/errors.ts
108
- var AppError = class extends Error {
109
- constructor(statusCode, message) {
110
- super(message);
111
- this.statusCode = statusCode;
112
- this.name = "AppError";
113
- }
114
- statusCode;
115
- };
116
- var NotFoundError = class extends AppError {
117
- constructor(message = "Not found") {
118
- super(404, message);
119
- this.name = "NotFoundError";
120
- }
121
- };
122
- var ForbiddenError = class extends AppError {
123
- constructor(message = "Forbidden") {
124
- super(403, message);
125
- this.name = "ForbiddenError";
126
- }
127
- };
128
- var ValidationError = class extends AppError {
129
- constructor(message) {
130
- super(400, message);
131
- this.name = "ValidationError";
132
- }
133
- };
134
- var ConflictError = class extends AppError {
135
- constructor(message) {
136
- super(409, message);
137
- this.name = "ConflictError";
138
- }
139
- };
140
- var LockedError = class extends AppError {
141
- constructor(message = "Locked") {
142
- super(423, message);
143
- this.name = "LockedError";
144
- }
145
- };
146
-
147
113
  // src/nodes/engine.ts
148
114
  import {
149
115
  NestStorage as NestStorage3,
@@ -269,7 +235,7 @@ async function flushTelemetry() {
269
235
  try {
270
236
  const res = await fetch(url, {
271
237
  method: "POST",
272
- headers: { "Content-Type": "application/json" },
238
+ headers: config.PROMPTOWL_FETCH_HEADERS,
273
239
  body: JSON.stringify(payload)
274
240
  });
275
241
  console.log(`[telemetry] response: ${res.status} ${res.statusText}`);
@@ -295,8 +261,53 @@ function startTelemetryLoop() {
295
261
  );
296
262
  }
297
263
 
264
+ // src/db/settings.ts
265
+ var deployBaseline = null;
266
+ function norm(v) {
267
+ return v === void 0 || v === null ? null : v;
268
+ }
269
+ function baseline() {
270
+ if (!deployBaseline) deployBaseline = { ...process.env };
271
+ return deployBaseline;
272
+ }
273
+ async function persistSetting(name, value) {
274
+ const db = getDb();
275
+ const envAtWrite = norm(baseline()[name]);
276
+ await db.run(
277
+ insertOrReplace(
278
+ db,
279
+ `INSERT INTO server_settings (key, value, env_at_write, updated_at)
280
+ VALUES (?, ?, ?, ${nowExpr(db)})`,
281
+ ["key"],
282
+ `value = excluded.value, env_at_write = excluded.env_at_write, updated_at = ${nowExpr(db)}`
283
+ ),
284
+ [name, value, envAtWrite]
285
+ );
286
+ }
287
+ async function loadServerSettings() {
288
+ deployBaseline = { ...process.env };
289
+ const base = deployBaseline;
290
+ const db = getDb();
291
+ const rows = await db.all(
292
+ "SELECT key, value, env_at_write FROM server_settings"
293
+ );
294
+ let applied = 0;
295
+ for (const row of rows) {
296
+ const envVal = norm(base[row.key]);
297
+ if (envVal !== norm(row.env_at_write)) {
298
+ await persistSetting(row.key, envVal);
299
+ if (envVal === null) delete process.env[row.key];
300
+ else process.env[row.key] = envVal;
301
+ } else {
302
+ if (row.value === null) delete process.env[row.key];
303
+ else process.env[row.key] = row.value;
304
+ }
305
+ applied++;
306
+ }
307
+ return applied;
308
+ }
309
+
298
310
  // src/auth/license.ts
299
- import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
300
311
  var currentLicense = null;
301
312
  function getCurrentLicense() {
302
313
  return currentLicense;
@@ -318,21 +329,6 @@ async function isLicenseAdminUserId(userId) {
318
329
  return false;
319
330
  }
320
331
  }
321
- function upsertEnvVar(filePath, varName, value) {
322
- const prefix = `${varName}=`;
323
- let lines = [];
324
- if (existsSync2(filePath)) {
325
- lines = readFileSync2(filePath, "utf8").split(/\r?\n/);
326
- }
327
- const filtered = lines.filter((line) => !line.trimStart().startsWith(prefix));
328
- if (value !== null) {
329
- filtered.push(`${prefix}${value}`);
330
- }
331
- while (filtered.length && filtered[filtered.length - 1] === "") {
332
- filtered.pop();
333
- }
334
- writeFileSync2(filePath, filtered.join("\n") + "\n", "utf8");
335
- }
336
332
  async function installLicenseKey(key) {
337
333
  const trimmed = key.trim();
338
334
  if (!trimmed.startsWith("pk_")) {
@@ -349,12 +345,12 @@ async function installLicenseKey(key) {
349
345
  return { ...info, persisted: false };
350
346
  }
351
347
  try {
352
- upsertEnvVar(config.ENV_FILE_PATH, "PROMPTOWL_KEY", trimmed);
348
+ await persistSetting("PROMPTOWL_KEY", trimmed);
353
349
  return { ...info, persisted: true };
354
350
  } catch (err) {
355
351
  const persistError = err instanceof Error ? err.message : String(err);
356
352
  console.warn(
357
- `[license] validated but FAILED to persist to ${config.ENV_FILE_PATH}: ${persistError}`
353
+ `[license] validated but FAILED to persist to the database: ${persistError}`
358
354
  );
359
355
  return { ...info, persisted: false, persistError };
360
356
  }
@@ -394,12 +390,12 @@ async function handleLicenseRevoked() {
394
390
  console.warn("[license] failed to wipe sessions:", err);
395
391
  }
396
392
  try {
397
- upsertEnvVar(config.ENV_FILE_PATH, "PROMPTOWL_KEY", null);
393
+ await persistSetting("PROMPTOWL_KEY", null);
398
394
  console.warn(
399
- `[license] revoked \u2014 removed PROMPTOWL_KEY from ${config.ENV_FILE_PATH}`
395
+ "[license] revoked \u2014 cleared persisted PROMPTOWL_KEY from the database"
400
396
  );
401
397
  } catch (err) {
402
- console.warn("[license] failed to strip key from .env:", err);
398
+ console.warn("[license] failed to clear persisted key:", err);
403
399
  }
404
400
  process.env.PROMPTOWL_KEY = "";
405
401
  currentLicense = {
@@ -464,7 +460,7 @@ async function _validateLicenseImpl(forceFresh) {
464
460
  const promptowlUrl = config.PROMPTOWL_API_URL.replace(/\/$/, "");
465
461
  const res = await fetch(`${promptowlUrl}/api/license/validate`, {
466
462
  method: "POST",
467
- headers: { "Content-Type": "application/json" },
463
+ headers: config.PROMPTOWL_FETCH_HEADERS,
468
464
  body: JSON.stringify({ key })
469
465
  });
470
466
  if (!res.ok) {
@@ -930,6 +926,36 @@ async function describeNode(nestId, nodeId) {
930
926
  const folder = folderForNode(nodeId);
931
927
  return folder ? `${title} (in ${folder})` : title;
932
928
  }
929
+ async function nestName(nestId) {
930
+ try {
931
+ const row = await getDb().get("SELECT name FROM nests WHERE id = ?", [
932
+ nestId
933
+ ]);
934
+ return row?.name || nestId;
935
+ } catch {
936
+ return nestId;
937
+ }
938
+ }
939
+ function docLink(nestId, nodeId, baseUrl) {
940
+ const base = baseUrl || config.PUBLIC_BASE_URL;
941
+ if (!base) return "";
942
+ const q = new URLSearchParams({ nest: nestId });
943
+ if (nodeId) q.set("doc", nodeId);
944
+ return `${base.replace(/\/$/, "")}/?${q.toString()}`;
945
+ }
946
+ async function buildDocContext(nestId, nodeId, baseUrl) {
947
+ const titles = await buildTitleMap(nestId);
948
+ const docTitle = titles.get(nodeId) || nodeId;
949
+ const folder = folderForNode(nodeId);
950
+ const name = await nestName(nestId);
951
+ return {
952
+ docTitle,
953
+ // Slack one-liner label ("Title (in Folder)"), same as describeNode().
954
+ label: folder ? `${docTitle} (in ${folder})` : docTitle,
955
+ path: folder ? `${name} / ${folder}` : name,
956
+ link: docLink(nestId, nodeId, baseUrl)
957
+ };
958
+ }
933
959
 
934
960
  // src/shared/access.ts
935
961
  var PERMISSION_LEVELS = {
@@ -1229,13 +1255,22 @@ async function createStewardRecord(params) {
1229
1255
  "SELECT id FROM users WHERE email = ?",
1230
1256
  [email]
1231
1257
  );
1258
+ let userId = userRow?.id;
1259
+ if (!userId) {
1260
+ const { hashPassword } = await import("./keys-73STFJJB.js");
1261
+ userId = uuid2();
1262
+ await db.run(
1263
+ "INSERT INTO users (id, email, name, password_hash, is_invited) VALUES (?, ?, ?, ?, 1)",
1264
+ [userId, email, null, await hashPassword(uuid2())]
1265
+ );
1266
+ }
1232
1267
  const created = await assignSteward({
1233
1268
  nestId: params.nestId,
1234
1269
  scope: params.scope,
1235
1270
  nodePattern,
1236
1271
  tagName,
1237
1272
  userEmail: email,
1238
- userId: userRow?.id,
1273
+ userId,
1239
1274
  role: user.role ?? "reviewer",
1240
1275
  assignedBy: params.assignedBy,
1241
1276
  assignedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -1549,18 +1584,13 @@ function rowToSteward(row) {
1549
1584
  }
1550
1585
 
1551
1586
  export {
1552
- AppError,
1553
- NotFoundError,
1554
- ForbiddenError,
1555
- ValidationError,
1556
- ConflictError,
1557
- LockedError,
1558
1587
  trackEvent,
1559
1588
  startTelemetryLoop,
1589
+ persistSetting,
1590
+ loadServerSettings,
1560
1591
  getCurrentLicense,
1561
1592
  isLicenseAdminEmail,
1562
1593
  isLicenseAdminUserId,
1563
- upsertEnvVar,
1564
1594
  installLicenseKey,
1565
1595
  startLicenseSafetyPoll,
1566
1596
  isSuspended,
@@ -1590,6 +1620,9 @@ export {
1590
1620
  engineCache,
1591
1621
  buildTitleMap,
1592
1622
  describeNode,
1623
+ nestName,
1624
+ docLink,
1625
+ buildDocContext,
1593
1626
  collabPermToRole,
1594
1627
  assignSteward,
1595
1628
  removeSteward,
@@ -1,8 +1,10 @@
1
1
  import {
2
- getDb,
3
2
  insertOrReplace,
4
3
  nowExpr
5
- } from "./chunk-PLAHCDQG.js";
4
+ } from "./chunk-XQ46F76G.js";
5
+ import {
6
+ getDb
7
+ } from "./chunk-BC6KFUZH.js";
6
8
 
7
9
  // src/governance/version-service.ts
8
10
  import { createHash } from "crypto";
@@ -0,0 +1,16 @@
1
+ // src/db/sql.ts
2
+ function nowExpr(db) {
3
+ return db.dialect === "sqlite" ? "datetime('now')" : "to_char((now() AT TIME ZONE 'utc'), 'YYYY-MM-DD HH24:MI:SS')";
4
+ }
5
+ function insertOrIgnore(db, insertSql) {
6
+ return db.dialect === "sqlite" ? insertSql.replace(/^\s*INSERT\s+INTO/i, "INSERT OR IGNORE INTO") : `${insertSql} ON CONFLICT DO NOTHING`;
7
+ }
8
+ function insertOrReplace(db, insertSql, conflictCols, set) {
9
+ return db.dialect === "sqlite" ? insertSql.replace(/^\s*INSERT\s+INTO/i, "INSERT OR REPLACE INTO") : `${insertSql} ON CONFLICT (${conflictCols.join(", ")}) DO UPDATE SET ${set}`;
10
+ }
11
+
12
+ export {
13
+ nowExpr,
14
+ insertOrIgnore,
15
+ insertOrReplace
16
+ };