@promptowl/contextnest-community 1.19.0 → 1.21.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 +83 -1
- package/README.md +1 -1
- package/dist/{chunk-FBDEESNJ.js → chunk-4KJGQHWB.js} +113 -218
- package/dist/{chunk-ENDHMQPD.js → chunk-6JGQX4GA.js} +415 -64
- package/dist/{chunk-TNAZJYL5.js → chunk-6PJQSKKV.js} +12 -4
- package/dist/{chunk-BXUVIAMP.js → chunk-IGPJ74O4.js} +60 -12
- package/dist/{chunk-HYSTFMFG.js → chunk-LPPKPEYI.js} +131 -1
- package/dist/{chunk-3GMGLYTZ.js → chunk-N2DTJFCD.js} +2 -2
- package/dist/{chunk-GSYMJ3A4.js → chunk-XWGEXQU3.js} +6 -2
- package/dist/{client-SW5I6ZBK.js → client-BD6PAUCZ.js} +1 -1
- package/dist/{engine-V4DY4PYN.js → engine-IKZQ46P7.js} +2 -2
- package/dist/{external-edit-service-3NK5DGLA.js → external-edit-service-LTON57HO.js} +3 -3
- package/dist/{grants-service-CADI6LIB.js → grants-service-LSNZN6NG.js} +2 -2
- package/dist/index.js +2704 -1156
- package/dist/{migrations.postgres-5VI4RDTS.js → migrations.postgres-JA2YGLS3.js} +66 -1
- package/dist/{review-service-LK4M2C43.js → review-service-6JK64IRT.js} +8 -6
- package/dist/{stewardship-service-JAFSU5GN.js → stewardship-service-2YKGBP63.js} +3 -3
- package/dist/{version-service-G7SFFKFO.js → version-service-PJV74PVQ.js} +11 -3
- package/dist/web3/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/dist/web3/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/dist/web3/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/dist/web3/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/dist/web3/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/dist/web3/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/dist/web3/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/dist/web3/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/dist/web3/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/dist/web3/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/dist/web3/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/dist/web3/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/dist/web3/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/dist/web3/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/dist/web3/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/dist/web3/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/dist/web3/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/dist/web3/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/dist/web3/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/dist/web3/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/dist/web3/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/dist/web3/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/dist/web3/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/dist/web3/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/dist/web3/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/dist/web3/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/dist/web3/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/dist/web3/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/dist/web3/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/dist/web3/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/dist/web3/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/dist/web3/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/dist/web3/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/dist/web3/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/dist/web3/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/dist/web3/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/dist/web3/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/dist/web3/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/dist/web3/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/dist/web3/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/dist/web3/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/dist/web3/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/dist/web3/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/dist/web3/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/dist/web3/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/dist/web3/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/dist/web3/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/dist/web3/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/dist/web3/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/dist/web3/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/dist/web3/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/dist/web3/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/dist/web3/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/dist/web3/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/dist/web3/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/dist/web3/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/dist/web3/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/dist/web3/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/dist/web3/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/dist/web3/assets/index-T4pilFNL.css +1 -0
- package/dist/web3/assets/index-deyhV93k.js +1382 -0
- package/dist/web3/index.html +2 -2
- package/package.json +6 -1
- package/dist/web3/assets/index-C26nM5Kn.css +0 -1
- package/dist/web3/assets/index-C4oalc2K.js +0 -1065
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
config,
|
|
9
9
|
getDb,
|
|
10
10
|
isEmailish
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-LPPKPEYI.js";
|
|
12
12
|
import {
|
|
13
13
|
ANON_USER_ID
|
|
14
14
|
} from "./chunk-YB3LKF7U.js";
|
|
@@ -27,7 +27,7 @@ import { createEngineApi } from "@promptowl/contextnest-engine/api";
|
|
|
27
27
|
|
|
28
28
|
// src/nests/service.ts
|
|
29
29
|
import { mkdirSync } from "fs";
|
|
30
|
-
import { v4 as
|
|
30
|
+
import { v4 as uuid3 } from "uuid";
|
|
31
31
|
import { NestStorage } from "@promptowl/contextnest-engine";
|
|
32
32
|
|
|
33
33
|
// src/shared/paths.ts
|
|
@@ -590,7 +590,7 @@ function parseAccessYaml(content) {
|
|
|
590
590
|
}
|
|
591
591
|
|
|
592
592
|
// src/governance/teams-service.ts
|
|
593
|
-
import { v4 as
|
|
593
|
+
import { v4 as uuid2 } from "uuid";
|
|
594
594
|
|
|
595
595
|
// src/governance/roles.ts
|
|
596
596
|
function collabPermToRole(permission) {
|
|
@@ -680,6 +680,14 @@ var STATUS_META = {
|
|
|
680
680
|
subjectWord: "deleted",
|
|
681
681
|
label: "Deleted",
|
|
682
682
|
color: "#dc2626"
|
|
683
|
+
},
|
|
684
|
+
// An honoured ARCHIVE request. Deliberately not the deletion palette: the
|
|
685
|
+
// nest is intact and restorable, and red "Deleted" would tell the requester
|
|
686
|
+
// the opposite of what happened.
|
|
687
|
+
deletion_archived: {
|
|
688
|
+
subjectWord: "archived",
|
|
689
|
+
label: "Archived",
|
|
690
|
+
color: "#0891b2"
|
|
683
691
|
}
|
|
684
692
|
};
|
|
685
693
|
var cap = (s) => s ? s.charAt(0).toUpperCase() + s.slice(1) : s;
|
|
@@ -777,6 +785,14 @@ var TEMPLATES = {
|
|
|
777
785
|
lineH: (x) => `${x.doc} has been deleted${x.byH}${x.noteH} \u2014 your deletion request was accepted.`,
|
|
778
786
|
rows: governanceRows
|
|
779
787
|
},
|
|
788
|
+
deletion_archived: {
|
|
789
|
+
heading: (x) => `${x.title} was ${x.meta.subjectWord}`,
|
|
790
|
+
detailsHeader: "Nest details",
|
|
791
|
+
linkLabel: "Open nest",
|
|
792
|
+
lineT: (x) => `${x.title} has been archived${x.byT}${x.noteT} \u2014 your request was accepted. Nothing was deleted: every document and version is intact, and an owner can restore it.`,
|
|
793
|
+
lineH: (x) => `${x.doc} has been archived${x.byH}${x.noteH} \u2014 your request was accepted. Nothing was deleted: every document and version is intact, and an owner can restore it.`,
|
|
794
|
+
rows: governanceRows
|
|
795
|
+
},
|
|
780
796
|
steward: {
|
|
781
797
|
heading: (x) => `You're now a steward of ${x.title}`,
|
|
782
798
|
detailsHeader: "Details",
|
|
@@ -997,6 +1013,9 @@ async function notifyEmailForNest(nestId, message, details) {
|
|
|
997
1013
|
}
|
|
998
1014
|
}
|
|
999
1015
|
}
|
|
1016
|
+
function isEmailConfigured() {
|
|
1017
|
+
return !!config.SMTP_URL && !!config.NOTIFY_EMAIL_FROM;
|
|
1018
|
+
}
|
|
1000
1019
|
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
1001
1020
|
function recipientEnvelope(to, from) {
|
|
1002
1021
|
const recipients = Array.isArray(to) ? to : [to];
|
|
@@ -1118,6 +1137,174 @@ async function buildDocContext(nestId, nodeId, baseUrl) {
|
|
|
1118
1137
|
};
|
|
1119
1138
|
}
|
|
1120
1139
|
|
|
1140
|
+
// src/governance/notify-service.ts
|
|
1141
|
+
import { v4 as uuid } from "uuid";
|
|
1142
|
+
async function listWatchers(nestId, nodeId) {
|
|
1143
|
+
return await getDb().all(
|
|
1144
|
+
`SELECT id, node_id, user_email, created_by, created_at
|
|
1145
|
+
FROM watchers WHERE nest_id = ? AND node_id = ? ORDER BY created_at`,
|
|
1146
|
+
[nestId, nodeId]
|
|
1147
|
+
);
|
|
1148
|
+
}
|
|
1149
|
+
async function addWatcher(nestId, nodeId, userEmail, createdBy) {
|
|
1150
|
+
await getDb().run(
|
|
1151
|
+
`INSERT INTO watchers (id, nest_id, node_id, user_email, created_by, created_at)
|
|
1152
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
1153
|
+
ON CONFLICT(nest_id, node_id, user_email) DO NOTHING`,
|
|
1154
|
+
[uuid(), nestId, nodeId, userEmail.toLowerCase(), createdBy, (/* @__PURE__ */ new Date()).toISOString()]
|
|
1155
|
+
);
|
|
1156
|
+
}
|
|
1157
|
+
async function removeWatcher(nestId, nodeId, userEmail) {
|
|
1158
|
+
const db = getDb();
|
|
1159
|
+
const row = await db.get(
|
|
1160
|
+
"SELECT id FROM watchers WHERE nest_id = ? AND node_id = ? AND LOWER(user_email) = LOWER(?)",
|
|
1161
|
+
[nestId, nodeId, userEmail]
|
|
1162
|
+
);
|
|
1163
|
+
if (!row) return false;
|
|
1164
|
+
await db.run("DELETE FROM watchers WHERE id = ?", [row.id]);
|
|
1165
|
+
return true;
|
|
1166
|
+
}
|
|
1167
|
+
async function notifyReviewRequested(params) {
|
|
1168
|
+
const { nestId, nodeId, requestedBy, reviewId } = params;
|
|
1169
|
+
const db = getDb();
|
|
1170
|
+
try {
|
|
1171
|
+
const rows = await db.all(
|
|
1172
|
+
`SELECT DISTINCT w.user_email, w.node_id AS via
|
|
1173
|
+
FROM watchers w
|
|
1174
|
+
WHERE w.nest_id = ?
|
|
1175
|
+
AND (w.node_id = ?
|
|
1176
|
+
OR w.node_id IN (
|
|
1177
|
+
SELECT e.from_node FROM edges e
|
|
1178
|
+
JOIN edge_types t ON t.id = e.type_id
|
|
1179
|
+
WHERE e.nest_id = ? AND e.to_node = ? AND t.is_flow = 1
|
|
1180
|
+
))`,
|
|
1181
|
+
[nestId, nodeId, nestId, nodeId]
|
|
1182
|
+
);
|
|
1183
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1184
|
+
const label = await describeNode(nestId, nodeId);
|
|
1185
|
+
let created = 0;
|
|
1186
|
+
for (const r of rows) {
|
|
1187
|
+
if (r.user_email.toLowerCase() === requestedBy.toLowerCase()) continue;
|
|
1188
|
+
await db.run(
|
|
1189
|
+
`INSERT INTO notifications (id, nest_id, user_email, kind, subject_id, message, created_at)
|
|
1190
|
+
VALUES (?, ?, ?, 'review_requested', ?, ?, ?)`,
|
|
1191
|
+
[
|
|
1192
|
+
uuid(),
|
|
1193
|
+
nestId,
|
|
1194
|
+
r.user_email.toLowerCase(),
|
|
1195
|
+
reviewId,
|
|
1196
|
+
r.via === nodeId ? `"${label}" has a new version awaiting review (submitted by ${requestedBy})` : `Agent "${await describeNode(nestId, r.via)}" produced output on "${label}" \u2014 awaiting review`,
|
|
1197
|
+
now
|
|
1198
|
+
]
|
|
1199
|
+
);
|
|
1200
|
+
created++;
|
|
1201
|
+
}
|
|
1202
|
+
return created;
|
|
1203
|
+
} catch (err) {
|
|
1204
|
+
console.error("[notify] review fan-out failed", nestId, nodeId, err);
|
|
1205
|
+
return 0;
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
async function notifyReviewResolved(params) {
|
|
1209
|
+
const { nestId, nodeId, status, resolvedBy, requestedBy, reviewId } = params;
|
|
1210
|
+
const db = getDb();
|
|
1211
|
+
try {
|
|
1212
|
+
const rows = await db.all(
|
|
1213
|
+
`SELECT DISTINCT w.user_email
|
|
1214
|
+
FROM watchers w
|
|
1215
|
+
WHERE w.nest_id = ?
|
|
1216
|
+
AND (w.node_id = ?
|
|
1217
|
+
OR w.node_id IN (
|
|
1218
|
+
SELECT e.from_node FROM edges e
|
|
1219
|
+
JOIN edge_types t ON t.id = e.type_id
|
|
1220
|
+
WHERE e.nest_id = ? AND e.to_node = ? AND t.is_flow = 1
|
|
1221
|
+
))`,
|
|
1222
|
+
[nestId, nodeId, nestId, nodeId]
|
|
1223
|
+
);
|
|
1224
|
+
const recipients = new Set(rows.map((r) => r.user_email.toLowerCase()));
|
|
1225
|
+
recipients.add(requestedBy.toLowerCase());
|
|
1226
|
+
recipients.delete(resolvedBy.toLowerCase());
|
|
1227
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1228
|
+
const label = await describeNode(nestId, nodeId);
|
|
1229
|
+
let created = 0;
|
|
1230
|
+
for (const email of recipients) {
|
|
1231
|
+
await db.run(
|
|
1232
|
+
`INSERT INTO notifications (id, nest_id, user_email, kind, subject_id, message, created_at)
|
|
1233
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
1234
|
+
[
|
|
1235
|
+
uuid(),
|
|
1236
|
+
nestId,
|
|
1237
|
+
email,
|
|
1238
|
+
`review_${status}`,
|
|
1239
|
+
reviewId,
|
|
1240
|
+
`"${label}" was ${status} by ${resolvedBy}`,
|
|
1241
|
+
now
|
|
1242
|
+
]
|
|
1243
|
+
);
|
|
1244
|
+
created++;
|
|
1245
|
+
}
|
|
1246
|
+
return created;
|
|
1247
|
+
} catch (err) {
|
|
1248
|
+
console.error("[notify] resolution fan-out failed", nestId, nodeId, err);
|
|
1249
|
+
return 0;
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
async function notifyNestInvite(params) {
|
|
1253
|
+
const { nestId, inviteeEmail, permission, nestName: nestName2, viaTeam } = params;
|
|
1254
|
+
const message = viaTeam ? `Your team "${viaTeam}" was given access to "${nestName2}" as ${permissionLabel(permission)}` : `You were added to "${nestName2}" as ${permissionLabel(permission)}`;
|
|
1255
|
+
try {
|
|
1256
|
+
await getDb().run(
|
|
1257
|
+
`INSERT INTO notifications (id, nest_id, user_email, kind, subject_id, message, created_at)
|
|
1258
|
+
VALUES (?, ?, ?, 'nest_invite', ?, ?, ?)`,
|
|
1259
|
+
[
|
|
1260
|
+
uuid(),
|
|
1261
|
+
nestId,
|
|
1262
|
+
inviteeEmail.toLowerCase(),
|
|
1263
|
+
nestId,
|
|
1264
|
+
message,
|
|
1265
|
+
(/* @__PURE__ */ new Date()).toISOString()
|
|
1266
|
+
]
|
|
1267
|
+
);
|
|
1268
|
+
} catch (err) {
|
|
1269
|
+
console.error("[notify] invite inbox insert failed", nestId, inviteeEmail, err);
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
async function listNotifications(userEmail, opts = {}) {
|
|
1273
|
+
const limit = Math.min(opts.limit ?? 50, 200);
|
|
1274
|
+
return await getDb().all(
|
|
1275
|
+
`SELECT n.id, n.nest_id, n.kind, n.subject_id, n.message, n.created_at, n.read_at,
|
|
1276
|
+
COALESCE(
|
|
1277
|
+
rr.node_id,
|
|
1278
|
+
CASE WHEN dr.target_type = 'document' THEN dr.node_id END,
|
|
1279
|
+
-- A mention names the document directly; there's no request row.
|
|
1280
|
+
CASE WHEN n.kind = 'mention' THEN n.subject_id END
|
|
1281
|
+
) AS node_id
|
|
1282
|
+
FROM notifications n
|
|
1283
|
+
LEFT JOIN review_requests rr ON rr.id = n.subject_id
|
|
1284
|
+
LEFT JOIN deletion_requests dr ON dr.id = n.subject_id
|
|
1285
|
+
WHERE LOWER(n.user_email) = LOWER(?)${opts.unreadOnly ? " AND n.read_at IS NULL" : ""}
|
|
1286
|
+
ORDER BY n.created_at DESC LIMIT ${limit}`,
|
|
1287
|
+
[userEmail]
|
|
1288
|
+
);
|
|
1289
|
+
}
|
|
1290
|
+
async function markNotificationsRead(userEmail, ids) {
|
|
1291
|
+
const db = getDb();
|
|
1292
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1293
|
+
if (ids === "all") {
|
|
1294
|
+
await db.run(
|
|
1295
|
+
"UPDATE notifications SET read_at = ? WHERE LOWER(user_email) = LOWER(?) AND read_at IS NULL",
|
|
1296
|
+
[now, userEmail]
|
|
1297
|
+
);
|
|
1298
|
+
return;
|
|
1299
|
+
}
|
|
1300
|
+
for (const id of ids.slice(0, 200)) {
|
|
1301
|
+
await db.run(
|
|
1302
|
+
"UPDATE notifications SET read_at = ? WHERE id = ? AND LOWER(user_email) = LOWER(?)",
|
|
1303
|
+
[now, id, userEmail]
|
|
1304
|
+
);
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1121
1308
|
// src/governance/teams-service.ts
|
|
1122
1309
|
var VALID_TEAM_ROLES = ["admin", "editor", "viewer"];
|
|
1123
1310
|
function parseMembers(json) {
|
|
@@ -1196,7 +1383,7 @@ async function createTeam(params) {
|
|
|
1196
1383
|
if (!name) throw new ValidationError("Team name is required");
|
|
1197
1384
|
const db = getDb();
|
|
1198
1385
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1199
|
-
const id =
|
|
1386
|
+
const id = uuid2();
|
|
1200
1387
|
await db.run(
|
|
1201
1388
|
"INSERT INTO teams (id, name, owner_id, members, source, external_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
|
1202
1389
|
[
|
|
@@ -1316,10 +1503,10 @@ async function resolveMemberIdentity(params) {
|
|
|
1316
1503
|
};
|
|
1317
1504
|
}
|
|
1318
1505
|
const { hashPassword } = await import("./keys-CTOGAG3W.js");
|
|
1319
|
-
const userId =
|
|
1506
|
+
const userId = uuid2();
|
|
1320
1507
|
await db.run(
|
|
1321
1508
|
"INSERT INTO users (id, email, name, password_hash, is_invited) VALUES (?, ?, ?, ?, 1)",
|
|
1322
|
-
[userId, requested, null, await hashPassword(
|
|
1509
|
+
[userId, requested, null, await hashPassword(uuid2())]
|
|
1323
1510
|
);
|
|
1324
1511
|
return { userId, email: requested };
|
|
1325
1512
|
}
|
|
@@ -1397,6 +1584,23 @@ function parseSharedTeams(json) {
|
|
|
1397
1584
|
return [];
|
|
1398
1585
|
}
|
|
1399
1586
|
}
|
|
1587
|
+
async function listSharedTeamMembers(refs) {
|
|
1588
|
+
const ids = [...new Set(refs.map((r) => r.teamId).filter(Boolean))];
|
|
1589
|
+
if (ids.length === 0) return [];
|
|
1590
|
+
const rows = await getDb().all(
|
|
1591
|
+
`SELECT members FROM teams WHERE id IN (${ids.map(() => "?").join(",")})`,
|
|
1592
|
+
ids
|
|
1593
|
+
);
|
|
1594
|
+
const byEmail = /* @__PURE__ */ new Map();
|
|
1595
|
+
for (const row of rows) {
|
|
1596
|
+
for (const member of parseMembers(row.members)) {
|
|
1597
|
+
const key = (member.email || "").toLowerCase();
|
|
1598
|
+
if (!key || byEmail.has(key)) continue;
|
|
1599
|
+
byEmail.set(key, { userId: member.userId, email: member.email, name: null });
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
return [...byEmail.values()];
|
|
1603
|
+
}
|
|
1400
1604
|
async function listNestTeams(nestId) {
|
|
1401
1605
|
const db = getDb();
|
|
1402
1606
|
const row = await db.get("SELECT shared_teams FROM nests WHERE id = ?", [
|
|
@@ -1408,6 +1612,7 @@ async function listNestTeams(nestId) {
|
|
|
1408
1612
|
async function shareTeamWithNest(params) {
|
|
1409
1613
|
const db = getDb();
|
|
1410
1614
|
let sharedMembers = [];
|
|
1615
|
+
let sharedTeamName = "";
|
|
1411
1616
|
const refs = await db.transaction(async (tx) => {
|
|
1412
1617
|
const team = await tx.get(
|
|
1413
1618
|
"SELECT id, name, owner_id, members FROM teams WHERE id = ?",
|
|
@@ -1434,6 +1639,7 @@ async function shareTeamWithNest(params) {
|
|
|
1434
1639
|
]);
|
|
1435
1640
|
const members = parseMembers(team.members);
|
|
1436
1641
|
sharedMembers = members;
|
|
1642
|
+
sharedTeamName = team.name;
|
|
1437
1643
|
const hasGovernanceRole = members.some(
|
|
1438
1644
|
(m) => m.role === "viewer" || m.role === "editor"
|
|
1439
1645
|
);
|
|
@@ -1468,6 +1674,16 @@ async function shareTeamWithNest(params) {
|
|
|
1468
1674
|
permission: role,
|
|
1469
1675
|
by: caller?.email || void 0
|
|
1470
1676
|
});
|
|
1677
|
+
const permission = teamRoleToPermission(role) ?? role;
|
|
1678
|
+
for (const email of emails) {
|
|
1679
|
+
await notifyNestInvite({
|
|
1680
|
+
nestId: params.nestId,
|
|
1681
|
+
inviteeEmail: email,
|
|
1682
|
+
permission,
|
|
1683
|
+
nestName: shareNestName,
|
|
1684
|
+
viaTeam: sharedTeamName
|
|
1685
|
+
});
|
|
1686
|
+
}
|
|
1471
1687
|
}
|
|
1472
1688
|
} catch (err) {
|
|
1473
1689
|
console.error("[notify] team-share email fan-out failed", params.nestId, err);
|
|
@@ -1569,9 +1785,29 @@ var PERMISSION_LEVELS = {
|
|
|
1569
1785
|
};
|
|
1570
1786
|
var higher = (a, b) => b && PERMISSION_LEVELS[b] > PERMISSION_LEVELS[a] ? b : a;
|
|
1571
1787
|
async function resolveNestAccess(nestId, userId) {
|
|
1788
|
+
const access = await resolveNestAccessRaw(nestId, userId);
|
|
1789
|
+
return {
|
|
1790
|
+
...access,
|
|
1791
|
+
rawPermission: access.permission,
|
|
1792
|
+
permission: capForArchive(access.permission, access.archived)
|
|
1793
|
+
};
|
|
1794
|
+
}
|
|
1795
|
+
async function isNestArchived(nestId) {
|
|
1796
|
+
const row = await getDb().get(
|
|
1797
|
+
"SELECT archived_at FROM nests WHERE id = ?",
|
|
1798
|
+
[nestId]
|
|
1799
|
+
);
|
|
1800
|
+
return !!row?.archived_at;
|
|
1801
|
+
}
|
|
1802
|
+
function capForArchive(permission, archived) {
|
|
1803
|
+
if (!archived) return permission;
|
|
1804
|
+
return PERMISSION_LEVELS[permission] > PERMISSION_LEVELS.read ? "read" : permission;
|
|
1805
|
+
}
|
|
1806
|
+
async function resolveNestAccessRaw(nestId, userId) {
|
|
1572
1807
|
const row = await getDb().get(
|
|
1573
1808
|
`SELECT n.user_id AS owner_id,
|
|
1574
1809
|
n.visibility AS visibility,
|
|
1810
|
+
n.archived_at AS archived_at,
|
|
1575
1811
|
n.shared_teams AS shared_teams,
|
|
1576
1812
|
u.email AS caller_email,
|
|
1577
1813
|
nc.permission AS collab_permission,
|
|
@@ -1598,7 +1834,9 @@ async function resolveNestAccess(nestId, userId) {
|
|
|
1598
1834
|
isSteward: false,
|
|
1599
1835
|
hasGrant: false,
|
|
1600
1836
|
permission: "none",
|
|
1601
|
-
|
|
1837
|
+
rawPermission: "none",
|
|
1838
|
+
isPublicReader: false,
|
|
1839
|
+
archived: false
|
|
1602
1840
|
};
|
|
1603
1841
|
if (!row) return absent;
|
|
1604
1842
|
const base = {
|
|
@@ -1610,7 +1848,8 @@ async function resolveNestAccess(nestId, userId) {
|
|
|
1610
1848
|
// Postgres returns integer 1, SQLite returns 1 — both truthy, neither a
|
|
1611
1849
|
// boolean, so normalize rather than leaking the driver's shape.
|
|
1612
1850
|
isSteward: !!row.steward_hit,
|
|
1613
|
-
hasGrant: !!row.grant_hit
|
|
1851
|
+
hasGrant: !!row.grant_hit,
|
|
1852
|
+
archived: !!row.archived_at
|
|
1614
1853
|
};
|
|
1615
1854
|
if (row.owner_id === userId) {
|
|
1616
1855
|
return { ...base, permission: "owner", isPublicReader: false };
|
|
@@ -1696,9 +1935,32 @@ async function isImportedNest(nestId) {
|
|
|
1696
1935
|
);
|
|
1697
1936
|
return !!row?.is_imported;
|
|
1698
1937
|
}
|
|
1938
|
+
var MAX_NEST_NAME_LENGTH = 100;
|
|
1939
|
+
function plainName(name, emptyMessage = "Name cannot be empty") {
|
|
1940
|
+
const typed = (name ?? "").trim();
|
|
1941
|
+
if (!typed) throw new ValidationError(emptyMessage);
|
|
1942
|
+
if (typed.length > MAX_NEST_NAME_LENGTH) {
|
|
1943
|
+
throw new ValidationError(
|
|
1944
|
+
`Nest names are capped at ${MAX_NEST_NAME_LENGTH} characters`
|
|
1945
|
+
);
|
|
1946
|
+
}
|
|
1947
|
+
if (/<\/?[a-zA-Z][^>]*>/.test(typed)) {
|
|
1948
|
+
throw new ValidationError(
|
|
1949
|
+
"Nest names are plain text \u2014 remove the HTML tags."
|
|
1950
|
+
);
|
|
1951
|
+
}
|
|
1952
|
+
return typed;
|
|
1953
|
+
}
|
|
1699
1954
|
function toSlug(name) {
|
|
1700
1955
|
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
1701
1956
|
}
|
|
1957
|
+
async function assertSlugFree(userId, slug, message, exceptId) {
|
|
1958
|
+
const conflict = await getDb().get(
|
|
1959
|
+
exceptId ? "SELECT id FROM nests WHERE user_id = ? AND slug = ? AND id != ? LIMIT 1" : "SELECT id FROM nests WHERE user_id = ? AND slug = ? LIMIT 1",
|
|
1960
|
+
exceptId ? [userId, slug, exceptId] : [userId, slug]
|
|
1961
|
+
);
|
|
1962
|
+
if (conflict) throw new ValidationError(message);
|
|
1963
|
+
}
|
|
1702
1964
|
async function uniqueNestName(userId, baseName) {
|
|
1703
1965
|
const db = getDb();
|
|
1704
1966
|
let candidate = baseName;
|
|
@@ -1730,6 +1992,22 @@ async function setStewardshipEnabled(nestId, enabled) {
|
|
|
1730
1992
|
nestId
|
|
1731
1993
|
]);
|
|
1732
1994
|
}
|
|
1995
|
+
async function setReaderMode(nestId, opts) {
|
|
1996
|
+
const db = getDb();
|
|
1997
|
+
const sets = [];
|
|
1998
|
+
const params = [];
|
|
1999
|
+
if (opts.enabled !== void 0) {
|
|
2000
|
+
sets.push("reader_mode = ?");
|
|
2001
|
+
params.push(opts.enabled ? 1 : 0);
|
|
2002
|
+
}
|
|
2003
|
+
if (opts.homeNode !== void 0) {
|
|
2004
|
+
sets.push("reader_home_node = ?");
|
|
2005
|
+
params.push(opts.homeNode ?? null);
|
|
2006
|
+
}
|
|
2007
|
+
if (sets.length === 0) return;
|
|
2008
|
+
params.push(nestId);
|
|
2009
|
+
await db.run(`UPDATE nests SET ${sets.join(", ")} WHERE id = ?`, params);
|
|
2010
|
+
}
|
|
1733
2011
|
async function nestAllowsSelfApprove(nestId) {
|
|
1734
2012
|
const db = getDb();
|
|
1735
2013
|
const row = await db.get(
|
|
@@ -1806,19 +2084,15 @@ async function renameNest(nestId, patch) {
|
|
|
1806
2084
|
const updates = [];
|
|
1807
2085
|
const params = [];
|
|
1808
2086
|
if (patch.name !== void 0) {
|
|
1809
|
-
const trimmed = patch.name
|
|
1810
|
-
if (!trimmed) throw new ValidationError("Name cannot be empty");
|
|
2087
|
+
const trimmed = plainName(patch.name);
|
|
1811
2088
|
const slug = toSlug(trimmed);
|
|
1812
2089
|
if (!slug) throw new ValidationError("Name must contain at least one alphanumeric character");
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
2090
|
+
await assertSlugFree(
|
|
2091
|
+
existing.user_id,
|
|
2092
|
+
slug,
|
|
2093
|
+
"Another nest already uses this name. Pick a different one.",
|
|
2094
|
+
nestId
|
|
1816
2095
|
);
|
|
1817
|
-
if (conflict) {
|
|
1818
|
-
throw new ValidationError(
|
|
1819
|
-
`Another nest already uses this name. Pick a different one.`
|
|
1820
|
-
);
|
|
1821
|
-
}
|
|
1822
2096
|
updates.push("name = ?", "slug = ?");
|
|
1823
2097
|
params.push(trimmed, slug);
|
|
1824
2098
|
}
|
|
@@ -1837,25 +2111,20 @@ async function renameNest(nestId, patch) {
|
|
|
1837
2111
|
return await db.get("SELECT * FROM nests WHERE id = ?", [nestId]);
|
|
1838
2112
|
}
|
|
1839
2113
|
async function createNest(userId, name, description) {
|
|
1840
|
-
const id =
|
|
2114
|
+
const id = uuid3();
|
|
1841
2115
|
const db = getDb();
|
|
1842
|
-
const trimmed = name
|
|
1843
|
-
if (!trimmed) throw new ValidationError("Name cannot be empty");
|
|
2116
|
+
const trimmed = plainName(name);
|
|
1844
2117
|
const slug = toSlug(trimmed);
|
|
1845
2118
|
if (!slug) {
|
|
1846
2119
|
throw new ValidationError(
|
|
1847
2120
|
"Name must contain at least one alphanumeric character"
|
|
1848
2121
|
);
|
|
1849
2122
|
}
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
2123
|
+
await assertSlugFree(
|
|
2124
|
+
userId,
|
|
2125
|
+
slug,
|
|
2126
|
+
"You already have a nest with this name. Pick a different one."
|
|
1853
2127
|
);
|
|
1854
|
-
if (conflict) {
|
|
1855
|
-
throw new ValidationError(
|
|
1856
|
-
"You already have a nest with this name. Pick a different one."
|
|
1857
|
-
);
|
|
1858
|
-
}
|
|
1859
2128
|
const visibility = userId === ANON_USER_ID ? "public" : "private";
|
|
1860
2129
|
await db.run(
|
|
1861
2130
|
"INSERT INTO nests (id, user_id, name, slug, description, visibility) VALUES (?, ?, ?, ?, ?, ?)",
|
|
@@ -1869,13 +2138,15 @@ async function createNest(userId, name, description) {
|
|
|
1869
2138
|
return await db.get("SELECT * FROM nests WHERE id = ?", [id]);
|
|
1870
2139
|
}
|
|
1871
2140
|
async function importNest(userId, name) {
|
|
1872
|
-
const nm = (name || "")
|
|
1873
|
-
if (!nm) {
|
|
1874
|
-
throw new ValidationError("name is required");
|
|
1875
|
-
}
|
|
2141
|
+
const nm = plainName(name || "", "name is required");
|
|
1876
2142
|
const db = getDb();
|
|
1877
|
-
const id =
|
|
2143
|
+
const id = uuid3();
|
|
1878
2144
|
const slug = toSlug(nm);
|
|
2145
|
+
await assertSlugFree(
|
|
2146
|
+
userId,
|
|
2147
|
+
slug,
|
|
2148
|
+
"A nest with this name already exists. Pick a different name."
|
|
2149
|
+
);
|
|
1879
2150
|
const visibility = userId === ANON_USER_ID ? "public" : "private";
|
|
1880
2151
|
mkdirSync(resolveNestPath(id), { recursive: true });
|
|
1881
2152
|
await db.run(
|
|
@@ -1885,26 +2156,79 @@ async function importNest(userId, name) {
|
|
|
1885
2156
|
trackEvent("nest.import", { nestId: id, userId });
|
|
1886
2157
|
return await db.get("SELECT * FROM nests WHERE id = ?", [id]);
|
|
1887
2158
|
}
|
|
1888
|
-
async function
|
|
2159
|
+
async function pinNest(userId, nestId) {
|
|
1889
2160
|
const db = getDb();
|
|
2161
|
+
const existing = await db.get(
|
|
2162
|
+
"SELECT 1 FROM nest_pins WHERE user_id = ? AND nest_id = ?",
|
|
2163
|
+
[userId, nestId]
|
|
2164
|
+
);
|
|
2165
|
+
if (existing) return;
|
|
2166
|
+
try {
|
|
2167
|
+
await db.run(
|
|
2168
|
+
"INSERT INTO nest_pins (user_id, nest_id) VALUES (?, ?)",
|
|
2169
|
+
[userId, nestId]
|
|
2170
|
+
);
|
|
2171
|
+
} catch (err) {
|
|
2172
|
+
const now = await db.get(
|
|
2173
|
+
"SELECT 1 FROM nest_pins WHERE user_id = ? AND nest_id = ?",
|
|
2174
|
+
[userId, nestId]
|
|
2175
|
+
);
|
|
2176
|
+
if (!now) throw err;
|
|
2177
|
+
}
|
|
2178
|
+
}
|
|
2179
|
+
async function unpinNest(userId, nestId) {
|
|
2180
|
+
await getDb().run(
|
|
2181
|
+
"DELETE FROM nest_pins WHERE user_id = ? AND nest_id = ?",
|
|
2182
|
+
[userId, nestId]
|
|
2183
|
+
);
|
|
2184
|
+
}
|
|
2185
|
+
async function pinnedNestIds(userId) {
|
|
2186
|
+
const rows = await getDb().all(
|
|
2187
|
+
"SELECT nest_id FROM nest_pins WHERE user_id = ?",
|
|
2188
|
+
[userId]
|
|
2189
|
+
);
|
|
2190
|
+
return new Set(rows.map((r) => r.nest_id));
|
|
2191
|
+
}
|
|
2192
|
+
var LIVE_ONLY = "archived_at IS NULL";
|
|
2193
|
+
var LIVE_ONLY_N = "n.archived_at IS NULL";
|
|
2194
|
+
async function listNests(userId, opts = {}) {
|
|
2195
|
+
const db = getDb();
|
|
2196
|
+
const scope = opts.archived ? "archived_at IS NOT NULL" : LIVE_ONLY;
|
|
1890
2197
|
if (userId === ANON_USER_ID) {
|
|
1891
2198
|
return await db.all(
|
|
1892
|
-
|
|
2199
|
+
`SELECT * FROM nests WHERE user_id = ? AND ${scope} ORDER BY created_at DESC`,
|
|
1893
2200
|
[ANON_USER_ID]
|
|
1894
2201
|
);
|
|
1895
2202
|
}
|
|
1896
2203
|
const includeAnon = config.AUTH_MODE === "open" || await isLicenseAdminUserId(userId);
|
|
1897
2204
|
if (!includeAnon) {
|
|
1898
2205
|
return await db.all(
|
|
1899
|
-
|
|
2206
|
+
`SELECT * FROM nests WHERE user_id = ? AND ${scope} ORDER BY created_at DESC`,
|
|
1900
2207
|
[userId]
|
|
1901
2208
|
);
|
|
1902
2209
|
}
|
|
1903
2210
|
return await db.all(
|
|
1904
|
-
|
|
2211
|
+
`SELECT * FROM nests WHERE (user_id = ? OR user_id = ?) AND ${scope}
|
|
2212
|
+
ORDER BY created_at DESC`,
|
|
1905
2213
|
[userId, ANON_USER_ID]
|
|
1906
2214
|
);
|
|
1907
2215
|
}
|
|
2216
|
+
async function archiveNest(nestId) {
|
|
2217
|
+
await getDb().transaction(async (tx) => {
|
|
2218
|
+
await tx.run(
|
|
2219
|
+
"UPDATE nests SET archived_at = ? WHERE id = ? AND archived_at IS NULL",
|
|
2220
|
+
[(/* @__PURE__ */ new Date()).toISOString(), nestId]
|
|
2221
|
+
);
|
|
2222
|
+
await tx.run(
|
|
2223
|
+
`DELETE FROM deletion_requests
|
|
2224
|
+
WHERE nest_id = ? AND target_type = 'nest_archive' AND status = 'pending'`,
|
|
2225
|
+
[nestId]
|
|
2226
|
+
);
|
|
2227
|
+
});
|
|
2228
|
+
}
|
|
2229
|
+
async function restoreNest(nestId) {
|
|
2230
|
+
await getDb().run("UPDATE nests SET archived_at = NULL WHERE id = ?", [nestId]);
|
|
2231
|
+
}
|
|
1908
2232
|
async function nestsSharedViaTeams(userId) {
|
|
1909
2233
|
const db = getDb();
|
|
1910
2234
|
const teams = await db.all(
|
|
@@ -1926,6 +2250,7 @@ async function nestsSharedViaTeams(userId) {
|
|
|
1926
2250
|
const candidates = await db.all(
|
|
1927
2251
|
`SELECT * FROM nests
|
|
1928
2252
|
WHERE user_id != ?
|
|
2253
|
+
AND ${LIVE_ONLY}
|
|
1929
2254
|
AND (${ids.map(() => "shared_teams LIKE ?").join(" OR ")})`,
|
|
1930
2255
|
[userId, ...ids.map((id) => `%${id}%`)]
|
|
1931
2256
|
);
|
|
@@ -1951,6 +2276,7 @@ async function listSharedNests(userId) {
|
|
|
1951
2276
|
LEFT JOIN grants g
|
|
1952
2277
|
ON g.nest_id = n.id AND g.user_id = ?
|
|
1953
2278
|
WHERE n.user_id != ?
|
|
2279
|
+
AND ${LIVE_ONLY_N}
|
|
1954
2280
|
AND (nc.user_id IS NOT NULL OR s.id IS NOT NULL OR g.id IS NOT NULL)
|
|
1955
2281
|
ORDER BY n.created_at DESC`,
|
|
1956
2282
|
[userId, userId, userId, userId]
|
|
@@ -1964,7 +2290,8 @@ async function listSharedNests(userId) {
|
|
|
1964
2290
|
}
|
|
1965
2291
|
if (await isServerAdminUserId(userId)) {
|
|
1966
2292
|
const anon = await db.all(
|
|
1967
|
-
|
|
2293
|
+
`SELECT * FROM nests WHERE user_id = ? AND ${LIVE_ONLY}
|
|
2294
|
+
ORDER BY created_at DESC`,
|
|
1968
2295
|
[ANON_USER_ID]
|
|
1969
2296
|
);
|
|
1970
2297
|
for (const n of anon) {
|
|
@@ -1998,11 +2325,12 @@ async function listVisibleNests(userId, opts = {}) {
|
|
|
1998
2325
|
ON s.nest_id = n.id AND s.is_active = 1
|
|
1999
2326
|
AND LOWER(s.user_email) = LOWER(u.email)
|
|
2000
2327
|
${opts.readable ? "LEFT JOIN grants g ON g.nest_id = n.id AND g.user_id = ?" : ""}
|
|
2001
|
-
WHERE
|
|
2328
|
+
WHERE ${LIVE_ONLY_N}
|
|
2329
|
+
AND (n.user_id = ?
|
|
2002
2330
|
OR nc.user_id IS NOT NULL
|
|
2003
2331
|
OR s.id IS NOT NULL
|
|
2004
2332
|
${opts.readable ? "OR g.id IS NOT NULL OR n.visibility = 'public'" : ""}
|
|
2005
|
-
${includeAnon ? "OR n.user_id = ?" : ""}
|
|
2333
|
+
${includeAnon ? "OR n.user_id = ?" : ""})
|
|
2006
2334
|
ORDER BY n.created_at DESC`,
|
|
2007
2335
|
params
|
|
2008
2336
|
);
|
|
@@ -2020,6 +2348,7 @@ async function listPublicNests(userId) {
|
|
|
2020
2348
|
return await db.all(
|
|
2021
2349
|
`SELECT n.* FROM nests n
|
|
2022
2350
|
WHERE n.visibility = 'public'
|
|
2351
|
+
AND ${LIVE_ONLY_N}
|
|
2023
2352
|
AND n.user_id != ?
|
|
2024
2353
|
AND NOT EXISTS (
|
|
2025
2354
|
SELECT 1 FROM nest_collaborators nc
|
|
@@ -2047,6 +2376,10 @@ async function deleteNest(nestId) {
|
|
|
2047
2376
|
} catch {
|
|
2048
2377
|
}
|
|
2049
2378
|
await tx.run("DELETE FROM api_keys WHERE nest_id = ?", [id]);
|
|
2379
|
+
try {
|
|
2380
|
+
await tx.run("DELETE FROM nest_pins WHERE nest_id = ?", [id]);
|
|
2381
|
+
} catch {
|
|
2382
|
+
}
|
|
2050
2383
|
await tx.run("DELETE FROM nests WHERE id = ?", [id]);
|
|
2051
2384
|
});
|
|
2052
2385
|
const path = resolveNestPath(nestId);
|
|
@@ -2068,6 +2401,21 @@ function isSkippedSegment(segment) {
|
|
|
2068
2401
|
return segment.startsWith(".") || segment === "node_modules" || segment === "_suggestions";
|
|
2069
2402
|
}
|
|
2070
2403
|
|
|
2404
|
+
// src/nodes/meta-files.ts
|
|
2405
|
+
var META_BASENAMES = /* @__PURE__ */ new Set([
|
|
2406
|
+
"INDEX",
|
|
2407
|
+
"CONTEXT",
|
|
2408
|
+
"CLAUDE",
|
|
2409
|
+
"GEMINI",
|
|
2410
|
+
"AGENTS",
|
|
2411
|
+
"QWEN",
|
|
2412
|
+
"README"
|
|
2413
|
+
]);
|
|
2414
|
+
function isMetaFile(idOrPath) {
|
|
2415
|
+
const base = idOrPath.split(/[/\\]/).pop() ?? "";
|
|
2416
|
+
return META_BASENAMES.has(base.replace(/\.md$/i, "").toUpperCase());
|
|
2417
|
+
}
|
|
2418
|
+
|
|
2071
2419
|
// src/shared/batch.ts
|
|
2072
2420
|
var IO_CONCURRENCY = 32;
|
|
2073
2421
|
async function mapBatched(items, fn, concurrency = IO_CONCURRENCY) {
|
|
@@ -2079,14 +2427,6 @@ async function mapBatched(items, fn, concurrency = IO_CONCURRENCY) {
|
|
|
2079
2427
|
}
|
|
2080
2428
|
|
|
2081
2429
|
// src/nodes/flat-storage.ts
|
|
2082
|
-
var META_FILES = /* @__PURE__ */ new Set([
|
|
2083
|
-
"INDEX.md",
|
|
2084
|
-
"CONTEXT.md",
|
|
2085
|
-
"CLAUDE.md",
|
|
2086
|
-
"GEMINI.md",
|
|
2087
|
-
"AGENTS.md",
|
|
2088
|
-
"QWEN.md"
|
|
2089
|
-
]);
|
|
2090
2430
|
var FlatNestStorage = class extends NestStorage2 {
|
|
2091
2431
|
async discoverDocuments() {
|
|
2092
2432
|
const root = this.root;
|
|
@@ -2100,7 +2440,7 @@ var FlatNestStorage = class extends NestStorage2 {
|
|
|
2100
2440
|
const ids = [];
|
|
2101
2441
|
for (const e of entries) {
|
|
2102
2442
|
if (!e.isFile() || !e.name.endsWith(".md")) continue;
|
|
2103
|
-
if (
|
|
2443
|
+
if (isMetaFile(e.name)) continue;
|
|
2104
2444
|
const dir = e.parentPath ?? e.path ?? root;
|
|
2105
2445
|
const rel = relative(root, join3(dir, e.name)).split(sep).join("/");
|
|
2106
2446
|
if (rel.split("/").some(isSkippedSegment)) continue;
|
|
@@ -2247,8 +2587,8 @@ async function ensureNodeIndex(nestId) {
|
|
|
2247
2587
|
return run;
|
|
2248
2588
|
}
|
|
2249
2589
|
async function rebuildNodeIndex(nestId) {
|
|
2250
|
-
const { engineCache: engineCache2 } = await import("./engine-
|
|
2251
|
-
const { documentsWithSuggestions } = await import("./external-edit-service-
|
|
2590
|
+
const { engineCache: engineCache2 } = await import("./engine-IKZQ46P7.js");
|
|
2591
|
+
const { documentsWithSuggestions } = await import("./external-edit-service-LTON57HO.js");
|
|
2252
2592
|
const { storage, dropDiscoveryCache } = await engineCache2.get(nestId);
|
|
2253
2593
|
dropDiscoveryCache();
|
|
2254
2594
|
const startedAt = writeGeneration.get(nestId) ?? 0;
|
|
@@ -2345,18 +2685,11 @@ async function indexedFolderCounts(nestId) {
|
|
|
2345
2685
|
// src/nodes/engine.ts
|
|
2346
2686
|
var DISCOVERY_TTL_MS = 3e4;
|
|
2347
2687
|
var isLive = (node) => node.frontmatter.status !== "rejected";
|
|
2348
|
-
var DERIVED_VAULT_FILES = /* @__PURE__ */ new Set([
|
|
2349
|
-
"INDEX.md",
|
|
2350
|
-
"CONTEXT.md",
|
|
2351
|
-
"CLAUDE.md",
|
|
2352
|
-
"GEMINI.md",
|
|
2353
|
-
"AGENTS.md",
|
|
2354
|
-
"README.md"
|
|
2355
|
-
]);
|
|
2356
2688
|
function withDiscoveryCache(nestId, storage) {
|
|
2357
2689
|
const entries = /* @__PURE__ */ new Map();
|
|
2358
2690
|
let inflight = null;
|
|
2359
|
-
const
|
|
2691
|
+
const rawDiscover = storage.discoverDocuments.bind(storage);
|
|
2692
|
+
const discover = async (options) => (await rawDiscover(options)).filter((d) => !isMetaFile(d.id));
|
|
2360
2693
|
const readDoc = storage.readDocument.bind(storage);
|
|
2361
2694
|
const filtersRetired = !(storage instanceof FlatNestStorage);
|
|
2362
2695
|
let generation = 0;
|
|
@@ -2407,6 +2740,7 @@ function withDiscoveryCache(nestId, storage) {
|
|
|
2407
2740
|
await markIndexStale(nestId);
|
|
2408
2741
|
};
|
|
2409
2742
|
const patch = async (id) => {
|
|
2743
|
+
if (isMetaFile(id)) return;
|
|
2410
2744
|
generation++;
|
|
2411
2745
|
inflight = null;
|
|
2412
2746
|
let node = null;
|
|
@@ -2448,8 +2782,7 @@ function withDiscoveryCache(nestId, storage) {
|
|
|
2448
2782
|
wrap("init", () => desync());
|
|
2449
2783
|
wrap("writeVaultFile", (args) => {
|
|
2450
2784
|
const rel = String(args[0] ?? "");
|
|
2451
|
-
|
|
2452
|
-
if (rel.endsWith(".md") && !DERIVED_VAULT_FILES.has(base)) return desync();
|
|
2785
|
+
if (rel.endsWith(".md") && !isMetaFile(rel)) return desync();
|
|
2453
2786
|
});
|
|
2454
2787
|
return { storage, invalidate, desync: () => void desync() };
|
|
2455
2788
|
}
|
|
@@ -2513,9 +2846,9 @@ export {
|
|
|
2513
2846
|
canEditWith,
|
|
2514
2847
|
canApproveWith,
|
|
2515
2848
|
primaryRole,
|
|
2516
|
-
permissionLabel,
|
|
2517
2849
|
verifySmtp,
|
|
2518
2850
|
notifyEmailForNest,
|
|
2851
|
+
isEmailConfigured,
|
|
2519
2852
|
sendEmailToRecipient,
|
|
2520
2853
|
nestStorageRoot,
|
|
2521
2854
|
resolveNestPath,
|
|
@@ -2525,6 +2858,7 @@ export {
|
|
|
2525
2858
|
uniqueNestName,
|
|
2526
2859
|
isStewardshipEnabled,
|
|
2527
2860
|
setStewardshipEnabled,
|
|
2861
|
+
setReaderMode,
|
|
2528
2862
|
nestAllowsSelfApprove,
|
|
2529
2863
|
setAllowSelfApprove,
|
|
2530
2864
|
nestReviewsPrimeOnly,
|
|
@@ -2535,7 +2869,12 @@ export {
|
|
|
2535
2869
|
renameNest,
|
|
2536
2870
|
createNest,
|
|
2537
2871
|
importNest,
|
|
2872
|
+
pinNest,
|
|
2873
|
+
unpinNest,
|
|
2874
|
+
pinnedNestIds,
|
|
2538
2875
|
listNests,
|
|
2876
|
+
archiveNest,
|
|
2877
|
+
restoreNest,
|
|
2539
2878
|
listSharedNests,
|
|
2540
2879
|
listVisibleNests,
|
|
2541
2880
|
listPublicNests,
|
|
@@ -2563,6 +2902,14 @@ export {
|
|
|
2563
2902
|
docLink,
|
|
2564
2903
|
resolveNodeUrl,
|
|
2565
2904
|
buildDocContext,
|
|
2905
|
+
listWatchers,
|
|
2906
|
+
addWatcher,
|
|
2907
|
+
removeWatcher,
|
|
2908
|
+
notifyReviewRequested,
|
|
2909
|
+
notifyReviewResolved,
|
|
2910
|
+
notifyNestInvite,
|
|
2911
|
+
listNotifications,
|
|
2912
|
+
markNotificationsRead,
|
|
2566
2913
|
createTeam,
|
|
2567
2914
|
findTeamByExternalId,
|
|
2568
2915
|
listTeamsForUser,
|
|
@@ -2574,12 +2921,16 @@ export {
|
|
|
2574
2921
|
replaceTeamRoster,
|
|
2575
2922
|
updateMemberRole,
|
|
2576
2923
|
removeMember,
|
|
2924
|
+
parseSharedTeams,
|
|
2925
|
+
listSharedTeamMembers,
|
|
2577
2926
|
listNestTeams,
|
|
2578
2927
|
shareTeamWithNest,
|
|
2579
2928
|
unshareTeamFromNest,
|
|
2580
2929
|
resolveTeamRolesForUser,
|
|
2581
2930
|
isServerAdminUserId,
|
|
2582
2931
|
resolveNestAccess,
|
|
2932
|
+
isNestArchived,
|
|
2933
|
+
capForArchive,
|
|
2583
2934
|
resolveNestPermission,
|
|
2584
2935
|
permissionLevel,
|
|
2585
2936
|
isPublicReader
|