@promptowl/contextnest-community 1.11.0 → 1.13.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 +6 -1
- package/README.md +3 -61
- package/dist/{chunk-3M7677XW.js → chunk-5QQ7WKAI.js} +14 -1
- package/dist/{chunk-JHIU6RZU.js → chunk-EG77SQHO.js} +34 -1
- package/dist/{chunk-XB7T3THE.js → chunk-PAIP5KHB.js} +17 -8
- package/dist/{chunk-F7EKYNXG.js → chunk-Q5NRCDD4.js} +884 -806
- package/dist/{chunk-ZMSU7BCC.js → chunk-ZQS5W45U.js} +1 -1
- package/dist/{client-YUOEVUFU.js → client-AAMF22YJ.js} +1 -1
- package/dist/{grants-service-PS5RMMEA.js → grants-service-FZD6QM6V.js} +2 -2
- package/dist/index.js +177 -93
- package/dist/{review-service-DPN26C47.js → review-service-Q3NDK5TX.js} +5 -5
- package/dist/{stewardship-service-7BHI2YXB.js → stewardship-service-B6L7UACX.js} +4 -2
- package/dist/{version-service-YQIIQ7FN.js → version-service-L52F4WVZ.js} +6 -4
- package/dist/web3/assets/{index-BiGiarBx.css → index-DcmDj50q.css} +1 -1
- package/dist/web3/assets/index-EGPZ6MAy.js +1023 -0
- package/dist/web3/index.html +2 -2
- package/package.json +1 -1
- package/dist/web3/assets/index-DekN5OmU.js +0 -1026
|
@@ -6,9 +6,9 @@ import {
|
|
|
6
6
|
listGrants,
|
|
7
7
|
listUserGrants,
|
|
8
8
|
resolveNodeGrant
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-ZQS5W45U.js";
|
|
10
10
|
import "./chunk-GUNJTORH.js";
|
|
11
|
-
import "./chunk-
|
|
11
|
+
import "./chunk-5QQ7WKAI.js";
|
|
12
12
|
import "./chunk-SLTQACJW.js";
|
|
13
13
|
export {
|
|
14
14
|
createGrant,
|
package/dist/index.js
CHANGED
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
seedDefaultEdgeTypes,
|
|
29
29
|
submitForReview,
|
|
30
30
|
verifySmtp
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-PAIP5KHB.js";
|
|
32
32
|
import {
|
|
33
33
|
createGrant,
|
|
34
34
|
deleteGrant,
|
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
listGrants,
|
|
38
38
|
listUserGrants,
|
|
39
39
|
resolveNodeGrant
|
|
40
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-ZQS5W45U.js";
|
|
41
41
|
import {
|
|
42
42
|
generateApiKey,
|
|
43
43
|
getKeyPrefix,
|
|
@@ -53,8 +53,9 @@ import {
|
|
|
53
53
|
getCurrentVersion,
|
|
54
54
|
getDisplayStatus,
|
|
55
55
|
getVersions,
|
|
56
|
-
setApprovedVersion
|
|
57
|
-
|
|
56
|
+
setApprovedVersion,
|
|
57
|
+
upsertVersion
|
|
58
|
+
} from "./chunk-EG77SQHO.js";
|
|
58
59
|
import {
|
|
59
60
|
addMember,
|
|
60
61
|
buildTitleMap,
|
|
@@ -63,7 +64,6 @@ import {
|
|
|
63
64
|
canUserAccess,
|
|
64
65
|
canUserApprove,
|
|
65
66
|
canUserEdit,
|
|
66
|
-
collabPermToRole,
|
|
67
67
|
createNest,
|
|
68
68
|
createStewardRecord,
|
|
69
69
|
createTeam,
|
|
@@ -98,6 +98,7 @@ import {
|
|
|
98
98
|
listSharedNests,
|
|
99
99
|
listStewards,
|
|
100
100
|
listTeamsForUser,
|
|
101
|
+
listVisibleNests,
|
|
101
102
|
loadAccessConfig,
|
|
102
103
|
loadGrantedSuperAdmins,
|
|
103
104
|
loadServerSettings,
|
|
@@ -111,12 +112,12 @@ import {
|
|
|
111
112
|
renameNest,
|
|
112
113
|
renameTeam,
|
|
113
114
|
replaceTeamRoster,
|
|
115
|
+
resolveGrantRoles,
|
|
114
116
|
resolveMemberIdentity,
|
|
115
117
|
resolveNestPath,
|
|
116
118
|
resolveNestPermission,
|
|
117
119
|
resolveStewardsForNode,
|
|
118
120
|
resolveStewardsWithFallback,
|
|
119
|
-
resolveTeamRolesForUser,
|
|
120
121
|
resolveUserRoles,
|
|
121
122
|
setAllowSelfApprove,
|
|
122
123
|
setStewardshipEnabled,
|
|
@@ -130,7 +131,7 @@ import {
|
|
|
130
131
|
updateMemberRole,
|
|
131
132
|
updateSteward,
|
|
132
133
|
validateLicense
|
|
133
|
-
} from "./chunk-
|
|
134
|
+
} from "./chunk-Q5NRCDD4.js";
|
|
134
135
|
import {
|
|
135
136
|
AppError,
|
|
136
137
|
ConflictError,
|
|
@@ -153,7 +154,7 @@ import {
|
|
|
153
154
|
initDb,
|
|
154
155
|
isEmailListish,
|
|
155
156
|
isEmailish
|
|
156
|
-
} from "./chunk-
|
|
157
|
+
} from "./chunk-5QQ7WKAI.js";
|
|
157
158
|
import {
|
|
158
159
|
ANON_EMAIL,
|
|
159
160
|
ANON_USER_ID
|
|
@@ -194,6 +195,12 @@ async function createSession(userId, userAgent) {
|
|
|
194
195
|
VALUES (?, ?, ?, ?)`,
|
|
195
196
|
[id, userId, expiryIso(), userAgent || null]
|
|
196
197
|
);
|
|
198
|
+
try {
|
|
199
|
+
await db.run("UPDATE users SET is_invited = 0 WHERE id = ? AND is_invited = 1", [
|
|
200
|
+
userId
|
|
201
|
+
]);
|
|
202
|
+
} catch {
|
|
203
|
+
}
|
|
197
204
|
return id;
|
|
198
205
|
}
|
|
199
206
|
async function resolveSession(id) {
|
|
@@ -895,6 +902,9 @@ authRoutes.post("/register", async (c) => {
|
|
|
895
902
|
if (!body.email || !body.password) {
|
|
896
903
|
throw new ValidationError("email and password are required");
|
|
897
904
|
}
|
|
905
|
+
if (!isEmailish(body.email)) {
|
|
906
|
+
throw new ValidationError("Enter a valid email address.");
|
|
907
|
+
}
|
|
898
908
|
assertValidPassword(body.password);
|
|
899
909
|
const email = normalizeEmail(body.email);
|
|
900
910
|
const ip = clientIp(c);
|
|
@@ -974,12 +984,6 @@ authRoutes.post("/login", async (c) => {
|
|
|
974
984
|
console.log(`[auth] login OK \u2014 counter reset ip=${ip} email=${emailLower}`);
|
|
975
985
|
if (hasIp) clear(ipKey);
|
|
976
986
|
clear(emailKey);
|
|
977
|
-
if (user.is_invited === 1) {
|
|
978
|
-
try {
|
|
979
|
-
await db.run("UPDATE users SET is_invited = 0 WHERE id = ?", [user.id]);
|
|
980
|
-
} catch {
|
|
981
|
-
}
|
|
982
|
-
}
|
|
983
987
|
if (check.needsRehash) {
|
|
984
988
|
try {
|
|
985
989
|
const newHash = await hashPassword(body.password);
|
|
@@ -1051,9 +1055,7 @@ authRoutes.post("/keys", authMiddleware, async (c) => {
|
|
|
1051
1055
|
);
|
|
1052
1056
|
});
|
|
1053
1057
|
authRoutes.post("/keys/rotate", authMiddleware, async (c) => {
|
|
1054
|
-
const body = await c.req.json().catch(
|
|
1055
|
-
() => ({})
|
|
1056
|
-
);
|
|
1058
|
+
const body = await c.req.json().catch(() => ({}));
|
|
1057
1059
|
const db = getDb();
|
|
1058
1060
|
const userId = c.get("userId");
|
|
1059
1061
|
const apiKey = generateApiKey();
|
|
@@ -1062,6 +1064,7 @@ authRoutes.post("/keys/rotate", authMiddleware, async (c) => {
|
|
|
1062
1064
|
"SELECT label, nest_id FROM api_keys WHERE user_id = ?",
|
|
1063
1065
|
[userId]
|
|
1064
1066
|
);
|
|
1067
|
+
const nestScope = "nest_id" in body ? body.nest_id || null : prior?.nest_id ?? null;
|
|
1065
1068
|
await db.transaction(async (tx) => {
|
|
1066
1069
|
await tx.run("DELETE FROM api_keys WHERE user_id = ?", [userId]);
|
|
1067
1070
|
await tx.run(
|
|
@@ -1071,7 +1074,7 @@ authRoutes.post("/keys/rotate", authMiddleware, async (c) => {
|
|
|
1071
1074
|
userId,
|
|
1072
1075
|
hashApiKey(apiKey),
|
|
1073
1076
|
getKeyPrefix(apiKey),
|
|
1074
|
-
|
|
1077
|
+
nestScope,
|
|
1075
1078
|
body.label ?? prior?.label ?? null
|
|
1076
1079
|
]
|
|
1077
1080
|
);
|
|
@@ -1422,6 +1425,9 @@ authRoutes.delete("/users/:userId", async (c) => {
|
|
|
1422
1425
|
authRoutes.post("/invite", async (c) => {
|
|
1423
1426
|
const body = await c.req.json();
|
|
1424
1427
|
if (!body.email) throw new ValidationError("email is required");
|
|
1428
|
+
if (!isEmailish(body.email)) {
|
|
1429
|
+
throw new ValidationError("Enter a valid email address.");
|
|
1430
|
+
}
|
|
1425
1431
|
const email = normalizeEmail(body.email);
|
|
1426
1432
|
const callerId = await resolveCallerUserId(c);
|
|
1427
1433
|
if (!callerId) {
|
|
@@ -1759,7 +1765,7 @@ async function approveExternalEdit(input) {
|
|
|
1759
1765
|
const node = await storage.readDocument(input.documentId);
|
|
1760
1766
|
const versionNum = result.versionEntry.version;
|
|
1761
1767
|
const tags = node.frontmatter.tags || [];
|
|
1762
|
-
const { createVersion: createVersion2, setApprovedVersion: setApprovedVersion2 } = await import("./version-service-
|
|
1768
|
+
const { createVersion: createVersion2, setApprovedVersion: setApprovedVersion2 } = await import("./version-service-L52F4WVZ.js");
|
|
1763
1769
|
await createVersion2({
|
|
1764
1770
|
nestId: input.nestId,
|
|
1765
1771
|
nodeId: input.documentId,
|
|
@@ -2051,6 +2057,11 @@ async function listNodesForCaller(nestId, userId, filters = {}) {
|
|
|
2051
2057
|
async function listNodesForCallerByEmail(nestId, userEmail, filters = {}) {
|
|
2052
2058
|
return listNodesForCaller(nestId, await userIdFromEmail(userEmail), filters);
|
|
2053
2059
|
}
|
|
2060
|
+
async function actorAutoPublishes(nestId, userEmail) {
|
|
2061
|
+
if (!await nestAllowsSelfApprove(nestId)) return false;
|
|
2062
|
+
const roles = await resolveUserRoles(nestId, userEmail);
|
|
2063
|
+
return roles.includes("owner") || roles.includes("admin");
|
|
2064
|
+
}
|
|
2054
2065
|
async function createNode(nestId, input, userEmail) {
|
|
2055
2066
|
const { storage, versions: versionManager } = await engineCache.get(nestId);
|
|
2056
2067
|
const slug = slugify(input.title);
|
|
@@ -2085,8 +2096,9 @@ async function createNode(nestId, input, userEmail) {
|
|
|
2085
2096
|
tags.push(deptTag);
|
|
2086
2097
|
}
|
|
2087
2098
|
const hasStewards = await isStewardshipEnabled(nestId);
|
|
2088
|
-
const
|
|
2089
|
-
const
|
|
2099
|
+
const autoPublish = !hasStewards || await actorAutoPublishes(nestId, userEmail);
|
|
2100
|
+
const initialStatus = autoPublish ? "published" : "draft";
|
|
2101
|
+
const initialVersion = autoPublish ? 0 : 1;
|
|
2090
2102
|
let node = {
|
|
2091
2103
|
id,
|
|
2092
2104
|
filePath: "",
|
|
@@ -2110,7 +2122,7 @@ async function createNode(nestId, input, userEmail) {
|
|
|
2110
2122
|
await storage.writeDocument(id, serializeDocument(node));
|
|
2111
2123
|
await syncNodeTags(nestId, id, tags);
|
|
2112
2124
|
let savedVersion = 1;
|
|
2113
|
-
if (
|
|
2125
|
+
if (!autoPublish) {
|
|
2114
2126
|
try {
|
|
2115
2127
|
await versionManager.createVersion(node, userEmail);
|
|
2116
2128
|
} catch (err) {
|
|
@@ -2129,7 +2141,7 @@ async function createNode(nestId, input, userEmail) {
|
|
|
2129
2141
|
try {
|
|
2130
2142
|
const result = await safePublishDocument(storage, id, {
|
|
2131
2143
|
editedBy: userEmail,
|
|
2132
|
-
note: "Auto-published on create (no stewards configured)"
|
|
2144
|
+
note: hasStewards ? "Auto-published on create (owner/admin self-approve)" : "Auto-published on create (no stewards configured)"
|
|
2133
2145
|
});
|
|
2134
2146
|
savedVersion = result.node.frontmatter.version || 1;
|
|
2135
2147
|
await createVersion({
|
|
@@ -2338,6 +2350,7 @@ async function updateNode(nestId, nodeId, patch, userEmail) {
|
|
|
2338
2350
|
node = { ...node, frontmatter: { ...node.frontmatter, metadata } };
|
|
2339
2351
|
}
|
|
2340
2352
|
const hasStewards = await isStewardshipEnabled(nestId);
|
|
2353
|
+
const autoPublish = !hasStewards || await actorAutoPublishes(nestId, userEmail);
|
|
2341
2354
|
const currentTags = node.frontmatter.tags || [];
|
|
2342
2355
|
if (hasStewards && await getPendingReview(nestId, nodeId)) {
|
|
2343
2356
|
throw new LockedError(
|
|
@@ -2345,7 +2358,7 @@ async function updateNode(nestId, nodeId, patch, userEmail) {
|
|
|
2345
2358
|
);
|
|
2346
2359
|
}
|
|
2347
2360
|
let responseVersion;
|
|
2348
|
-
if (
|
|
2361
|
+
if (!autoPublish) {
|
|
2349
2362
|
const currentVersion = await getCurrentVersion(nestId, nodeId);
|
|
2350
2363
|
const newVersion = currentVersion + 1;
|
|
2351
2364
|
node = {
|
|
@@ -2400,7 +2413,7 @@ async function updateNode(nestId, nodeId, patch, userEmail) {
|
|
|
2400
2413
|
try {
|
|
2401
2414
|
const result = await safePublishDocument(storage, nodeId, {
|
|
2402
2415
|
editedBy: userEmail,
|
|
2403
|
-
note: patch.changeNote || "Auto-published on edit (no stewards)"
|
|
2416
|
+
note: patch.changeNote || (hasStewards ? "Auto-published on edit (owner/admin self-approve)" : "Auto-published on edit (no stewards)")
|
|
2404
2417
|
});
|
|
2405
2418
|
publishedVersion = result.node.frontmatter.version || publishedVersion;
|
|
2406
2419
|
node = result.node;
|
|
@@ -2411,7 +2424,7 @@ async function updateNode(nestId, nodeId, patch, userEmail) {
|
|
|
2411
2424
|
);
|
|
2412
2425
|
throw err;
|
|
2413
2426
|
}
|
|
2414
|
-
await
|
|
2427
|
+
await upsertVersion({
|
|
2415
2428
|
nestId,
|
|
2416
2429
|
nodeId,
|
|
2417
2430
|
version: publishedVersion,
|
|
@@ -2735,6 +2748,25 @@ async function syncUnsyncedFolder(userId, folderName, callerEmail) {
|
|
|
2735
2748
|
}
|
|
2736
2749
|
return { nest, documents };
|
|
2737
2750
|
}
|
|
2751
|
+
function deleteUnsyncedFolder(folderName) {
|
|
2752
|
+
console.log(`[unsynced] delete requested folder="${folderName}"`);
|
|
2753
|
+
assertSafeFolderName(folderName);
|
|
2754
|
+
const src = join3(config.DATA_ROOT, folderName);
|
|
2755
|
+
if (isNestStorageRoot(src)) {
|
|
2756
|
+
throw new ValidationError("Folder is not eligible for delete");
|
|
2757
|
+
}
|
|
2758
|
+
let stat;
|
|
2759
|
+
try {
|
|
2760
|
+
stat = statSync(src);
|
|
2761
|
+
} catch {
|
|
2762
|
+
throw new NotFoundError(`Folder not found: ${folderName}`);
|
|
2763
|
+
}
|
|
2764
|
+
if (!stat.isDirectory()) {
|
|
2765
|
+
throw new ValidationError(`Not a directory: ${folderName}`);
|
|
2766
|
+
}
|
|
2767
|
+
rmSync(src, { recursive: true, force: true });
|
|
2768
|
+
console.log(`[unsynced] deleted folder ${src}`);
|
|
2769
|
+
}
|
|
2738
2770
|
|
|
2739
2771
|
// src/nests/routes.ts
|
|
2740
2772
|
async function effectivePermission(nestId, userId) {
|
|
@@ -2760,54 +2792,42 @@ nestRoutes.get("/", async (c) => {
|
|
|
2760
2792
|
const annotate = async (n) => {
|
|
2761
2793
|
const permission = await effectivePermission(n.id, userId);
|
|
2762
2794
|
const is_owner = permission === "owner";
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
);
|
|
2771
|
-
const stewardRoles = await getStewardRolesForUser(n.id, callerEmail);
|
|
2772
|
-
const teamRoles = await resolveTeamRolesForUser(n.id, userId);
|
|
2773
|
-
roles = [
|
|
2774
|
-
...new Set(
|
|
2775
|
-
[grantRole, ...stewardRoles, ...teamRoles].filter(Boolean)
|
|
2776
|
-
)
|
|
2777
|
-
];
|
|
2778
|
-
}
|
|
2779
|
-
if (!is_owner && n.user_id !== ANON_USER_ID) {
|
|
2780
|
-
const row = await db.get(ownerEmailSql, [n.user_id]);
|
|
2781
|
-
owner_email = row?.email ?? null;
|
|
2782
|
-
}
|
|
2795
|
+
const needOwnerEmail = !is_owner && n.user_id !== ANON_USER_ID;
|
|
2796
|
+
const [roles, owner_email] = await Promise.all([
|
|
2797
|
+
is_owner ? Promise.resolve(["owner"]) : resolveGrantRoles(n.id, callerEmail, userId),
|
|
2798
|
+
needOwnerEmail ? db.get(ownerEmailSql, [n.user_id]).then(
|
|
2799
|
+
(row) => row?.email ?? null
|
|
2800
|
+
) : Promise.resolve(null)
|
|
2801
|
+
]);
|
|
2783
2802
|
return {
|
|
2784
2803
|
...n,
|
|
2785
2804
|
permission,
|
|
2786
2805
|
is_owner,
|
|
2787
2806
|
owner_email,
|
|
2788
2807
|
roles,
|
|
2789
|
-
stewardship_enabled:
|
|
2808
|
+
stewardship_enabled: !!n.stewardship_enabled
|
|
2790
2809
|
};
|
|
2791
2810
|
};
|
|
2792
2811
|
const seen = /* @__PURE__ */ new Set();
|
|
2793
|
-
const
|
|
2812
|
+
const deduped = [];
|
|
2794
2813
|
for (const n of [...owned, ...shared, ...publicExtras]) {
|
|
2795
2814
|
if (seen.has(n.id)) continue;
|
|
2796
2815
|
seen.add(n.id);
|
|
2797
|
-
|
|
2816
|
+
deduped.push(n);
|
|
2798
2817
|
}
|
|
2818
|
+
const out = await Promise.all(deduped.map(annotate));
|
|
2799
2819
|
const docRows = await db.all(
|
|
2800
2820
|
"SELECT nest_id, COUNT(DISTINCT node_id) AS c FROM node_versions GROUP BY nest_id"
|
|
2801
2821
|
);
|
|
2802
|
-
const docByNest = new Map(docRows.map((r) => [r.nest_id, r.c]));
|
|
2822
|
+
const docByNest = new Map(docRows.map((r) => [r.nest_id, Number(r.c)]));
|
|
2803
2823
|
const collabRows = await db.all(
|
|
2804
2824
|
"SELECT nest_id, COUNT(*) AS c FROM nest_collaborators GROUP BY nest_id"
|
|
2805
2825
|
);
|
|
2806
|
-
const collabByNest = new Map(collabRows.map((r) => [r.nest_id, r.c]));
|
|
2826
|
+
const collabByNest = new Map(collabRows.map((r) => [r.nest_id, Number(r.c)]));
|
|
2807
2827
|
const stewardRows = await db.all(
|
|
2808
2828
|
"SELECT nest_id, COUNT(DISTINCT user_email) AS c FROM stewards WHERE is_active = 1 GROUP BY nest_id"
|
|
2809
2829
|
);
|
|
2810
|
-
const stewardByNest = new Map(stewardRows.map((r) => [r.nest_id, r.c]));
|
|
2830
|
+
const stewardByNest = new Map(stewardRows.map((r) => [r.nest_id, Number(r.c)]));
|
|
2811
2831
|
const stewardEmailRows = await db.all(
|
|
2812
2832
|
`SELECT nest_id, user_email FROM stewards WHERE is_active = 1
|
|
2813
2833
|
GROUP BY nest_id, user_email ORDER BY MIN(assigned_at)`
|
|
@@ -2914,6 +2934,21 @@ nestRoutes.post("/:nestId/sync", async (c) => {
|
|
|
2914
2934
|
);
|
|
2915
2935
|
return c.json(result, 201);
|
|
2916
2936
|
});
|
|
2937
|
+
nestRoutes.delete("/:nestId/sync", async (c) => {
|
|
2938
|
+
if (c.req.param("nestId") !== "unsynced") {
|
|
2939
|
+
throw new NotFoundError("Not found");
|
|
2940
|
+
}
|
|
2941
|
+
const userId = c.get("userId");
|
|
2942
|
+
if (config.AUTH_MODE !== "open" && !await isServerAdminUserId(userId)) {
|
|
2943
|
+
throw new ForbiddenError("Only the server admin can delete folders");
|
|
2944
|
+
}
|
|
2945
|
+
const body = await c.req.json().catch(() => ({}));
|
|
2946
|
+
if (!body?.name) {
|
|
2947
|
+
throw new ValidationError("name is required");
|
|
2948
|
+
}
|
|
2949
|
+
deleteUnsyncedFolder(body.name);
|
|
2950
|
+
return c.json({ ok: true });
|
|
2951
|
+
});
|
|
2917
2952
|
nestRoutes.get("/:nestId", async (c) => {
|
|
2918
2953
|
const nestId = c.req.param("nestId");
|
|
2919
2954
|
if (nestId === "unsynced") return handleUnsyncedList(c);
|
|
@@ -3038,6 +3073,9 @@ async function addCollaborator(params) {
|
|
|
3038
3073
|
const db = getDb();
|
|
3039
3074
|
let userId = params.userId;
|
|
3040
3075
|
if (!userId && params.email) {
|
|
3076
|
+
if (!isEmailish(params.email)) {
|
|
3077
|
+
throw new ValidationError("Enter a valid email address.");
|
|
3078
|
+
}
|
|
3041
3079
|
const email = normalizeEmail(params.email);
|
|
3042
3080
|
const existing = await db.get(
|
|
3043
3081
|
"SELECT id FROM users WHERE LOWER(email) = ?",
|
|
@@ -4012,7 +4050,7 @@ nodeRoutes.post("/", async (c) => {
|
|
|
4012
4050
|
nodeRoutes.get("/:nodeId{.+}/stewards", async (c) => {
|
|
4013
4051
|
const nestId = c.req.param("nestId");
|
|
4014
4052
|
const nodeId = c.req.param("nodeId");
|
|
4015
|
-
const { resolveStewardsWithFallback: resolveStewardsWithFallback2 } = await import("./stewardship-service-
|
|
4053
|
+
const { resolveStewardsWithFallback: resolveStewardsWithFallback2 } = await import("./stewardship-service-B6L7UACX.js");
|
|
4016
4054
|
const { stewards, fallbackToOwner, ownerEmail } = await resolveStewardsWithFallback2(
|
|
4017
4055
|
nestId,
|
|
4018
4056
|
nodeId
|
|
@@ -4033,7 +4071,7 @@ nodeRoutes.get("/:nodeId{.+}/stewards", async (c) => {
|
|
|
4033
4071
|
nodeRoutes.get("/:nodeId{.+}/versions", async (c) => {
|
|
4034
4072
|
const nestId = c.req.param("nestId");
|
|
4035
4073
|
const nodeId = c.req.param("nodeId");
|
|
4036
|
-
const { getVersions: getVersions2, getApprovedVersion: getApprovedVersion2 } = await import("./version-service-
|
|
4074
|
+
const { getVersions: getVersions2, getApprovedVersion: getApprovedVersion2 } = await import("./version-service-L52F4WVZ.js");
|
|
4037
4075
|
const allVersions = await getVersions2(nestId, nodeId);
|
|
4038
4076
|
const approved = await getApprovedVersion2(nestId, nodeId);
|
|
4039
4077
|
const db = getDb();
|
|
@@ -4065,7 +4103,7 @@ nodeRoutes.get("/:nodeId{.+}/versions", async (c) => {
|
|
|
4065
4103
|
nodeRoutes.get("/:nodeId{.+}/reviews", async (c) => {
|
|
4066
4104
|
const nestId = c.req.param("nestId");
|
|
4067
4105
|
const nodeId = c.req.param("nodeId");
|
|
4068
|
-
const { getReviewHistory: getReviewHistory2 } = await import("./review-service-
|
|
4106
|
+
const { getReviewHistory: getReviewHistory2 } = await import("./review-service-Q3NDK5TX.js");
|
|
4069
4107
|
const history = await getReviewHistory2(nestId, nodeId);
|
|
4070
4108
|
return c.json({ reviews: history });
|
|
4071
4109
|
});
|
|
@@ -7472,8 +7510,8 @@ ${list}`;
|
|
|
7472
7510
|
if (!target) return "target is required (a node id or folder prefix).";
|
|
7473
7511
|
if (!["read", "write"].includes(role)) return "role must be read or write.";
|
|
7474
7512
|
try {
|
|
7475
|
-
const { createGrant: createGrant2 } = await import("./grants-service-
|
|
7476
|
-
const db = (await import("./client-
|
|
7513
|
+
const { createGrant: createGrant2 } = await import("./grants-service-FZD6QM6V.js");
|
|
7514
|
+
const db = (await import("./client-AAMF22YJ.js")).getDb();
|
|
7477
7515
|
const { normalizeEmail: normalizeEmail2 } = await import("./email-R7DFS6E5.js");
|
|
7478
7516
|
const e = normalizeEmail2(String(args.email || ""));
|
|
7479
7517
|
if (!e) return "email is required.";
|
|
@@ -8573,7 +8611,7 @@ var CONTENT_TYPES = {
|
|
|
8573
8611
|
};
|
|
8574
8612
|
var VIDEO_EXTS = /* @__PURE__ */ new Set(["mp4", "webm"]);
|
|
8575
8613
|
var IMAGE_MAX_BYTES = 10 * 1024 * 1024;
|
|
8576
|
-
var VIDEO_MAX_BYTES =
|
|
8614
|
+
var VIDEO_MAX_BYTES = config.VIDEO_MAX_BYTES;
|
|
8577
8615
|
var ASSET_MAX_REQUEST_BYTES = VIDEO_MAX_BYTES + 1024 * 1024;
|
|
8578
8616
|
var SAFE_NAME = /^[0-9a-f-]{36}\.(png|jpe?g|gif|webp|mp4|webm)$/;
|
|
8579
8617
|
assetRoutes.post("/", async (c) => {
|
|
@@ -10017,6 +10055,22 @@ var flexAuthMiddleware = createMiddleware2(async (c, next) => {
|
|
|
10017
10055
|
}
|
|
10018
10056
|
return c.json({ error: "Missing or invalid credentials" }, 401);
|
|
10019
10057
|
});
|
|
10058
|
+
var APPROVE_CACHE_TTL_MS = 3e4;
|
|
10059
|
+
var approveCache = /* @__PURE__ */ new Map();
|
|
10060
|
+
async function canApproveInNest(nestId, email) {
|
|
10061
|
+
const key = `${nestId} ${email.toLowerCase()}`;
|
|
10062
|
+
const now = Date.now();
|
|
10063
|
+
const hit = approveCache.get(key);
|
|
10064
|
+
if (hit && hit.exp > now) return hit.ok;
|
|
10065
|
+
const roles = await resolveUserRoles(nestId, email);
|
|
10066
|
+
let ok = roles.includes("admin") || roles.includes("reviewer");
|
|
10067
|
+
if (!ok && roles.includes("owner")) ok = await nestAllowsSelfApprove(nestId);
|
|
10068
|
+
if (approveCache.size > 500) {
|
|
10069
|
+
for (const [k, v] of approveCache) if (v.exp <= now) approveCache.delete(k);
|
|
10070
|
+
}
|
|
10071
|
+
approveCache.set(key, { ok, exp: now + APPROVE_CACHE_TTL_MS });
|
|
10072
|
+
return ok;
|
|
10073
|
+
}
|
|
10020
10074
|
function createApp() {
|
|
10021
10075
|
const app = new Hono21({ router: new LinearRouter() });
|
|
10022
10076
|
const corsOrigins = config.CORS_ORIGINS;
|
|
@@ -10634,12 +10688,16 @@ function createApp() {
|
|
|
10634
10688
|
const userId = c.get("userId");
|
|
10635
10689
|
const me = await resolveCallerEmail(userId);
|
|
10636
10690
|
const nestName2 = /* @__PURE__ */ new Map();
|
|
10637
|
-
for (const n of
|
|
10638
|
-
...await listNests(userId),
|
|
10639
|
-
...await listSharedNests(userId)
|
|
10640
|
-
])
|
|
10691
|
+
for (const n of await listVisibleNests(userId))
|
|
10641
10692
|
nestName2.set(n.id, n.name ?? n.id);
|
|
10642
|
-
const
|
|
10693
|
+
const limit = Math.min(Math.max(Number(c.req.query("limit")) || 50, 1), 100);
|
|
10694
|
+
const offset = Math.max(Number(c.req.query("offset")) || 0, 0);
|
|
10695
|
+
const { requests, total } = await getReviewQueue({
|
|
10696
|
+
status: "pending",
|
|
10697
|
+
nestIds: [...nestName2.keys()],
|
|
10698
|
+
limit,
|
|
10699
|
+
offset
|
|
10700
|
+
});
|
|
10643
10701
|
const toItem = (r) => ({
|
|
10644
10702
|
type: "review",
|
|
10645
10703
|
nest_id: r.nestId,
|
|
@@ -10650,12 +10708,29 @@ function createApp() {
|
|
|
10650
10708
|
requested_at: r.requestedAt,
|
|
10651
10709
|
priority: r.priority
|
|
10652
10710
|
});
|
|
10653
|
-
const mine = requests.filter((r) => nestName2.has(r.nestId));
|
|
10654
10711
|
const byNewest = (a, b) => a.requested_at < b.requested_at ? 1 : -1;
|
|
10655
|
-
const
|
|
10656
|
-
const
|
|
10712
|
+
const others = requests.filter((r) => r.requestedBy !== me);
|
|
10713
|
+
const nestApprove = new Map(
|
|
10714
|
+
await Promise.all(
|
|
10715
|
+
[...new Set(others.map((r) => r.nestId))].map(
|
|
10716
|
+
async (nestId) => [
|
|
10717
|
+
nestId,
|
|
10718
|
+
await canApproveInNest(nestId, me)
|
|
10719
|
+
]
|
|
10720
|
+
)
|
|
10721
|
+
)
|
|
10722
|
+
);
|
|
10723
|
+
const items = others.filter((r) => nestApprove.get(r.nestId)).map(toItem).sort(byNewest);
|
|
10724
|
+
const waiting = requests.filter((r) => r.requestedBy === me).map(toItem).sort(byNewest);
|
|
10657
10725
|
const notifications = await listNotifications(me, { unreadOnly: true, limit: 50 });
|
|
10658
|
-
return c.json({
|
|
10726
|
+
return c.json({
|
|
10727
|
+
items,
|
|
10728
|
+
waiting,
|
|
10729
|
+
notifications,
|
|
10730
|
+
// total = all pending across visible nests (both buckets); has_more drives
|
|
10731
|
+
// the "Load more" control on the client.
|
|
10732
|
+
pagination: { total, limit, offset, has_more: offset + requests.length < total }
|
|
10733
|
+
});
|
|
10659
10734
|
});
|
|
10660
10735
|
app.get("/me/notifications", async (c) => {
|
|
10661
10736
|
const me = await resolveCallerEmail(c.get("userId"));
|
|
@@ -10673,9 +10748,7 @@ function createApp() {
|
|
|
10673
10748
|
const db = getDb();
|
|
10674
10749
|
const userId = c.get("userId");
|
|
10675
10750
|
const userEmail = await resolveCallerEmail(userId);
|
|
10676
|
-
const
|
|
10677
|
-
const sharedNests = await listSharedNests(userId);
|
|
10678
|
-
const visibleNests = [...owned, ...sharedNests];
|
|
10751
|
+
const visibleNests = await listVisibleNests(userId);
|
|
10679
10752
|
let documents = 0;
|
|
10680
10753
|
for (const nest of visibleNests) {
|
|
10681
10754
|
try {
|
|
@@ -10712,18 +10785,31 @@ function createApp() {
|
|
|
10712
10785
|
const nestId = parts[0];
|
|
10713
10786
|
const userId = c.get("userId");
|
|
10714
10787
|
const nestScope = c.get("nestScope");
|
|
10788
|
+
const isMcpTransport = parts.length === 2 && parts[1] === "mcp";
|
|
10789
|
+
const deny = async (status, error, extra) => {
|
|
10790
|
+
if (!isMcpTransport) return c.json({ error, ...extra }, status);
|
|
10791
|
+
let id = null;
|
|
10792
|
+
try {
|
|
10793
|
+
id = (await c.req.json())?.id ?? null;
|
|
10794
|
+
} catch {
|
|
10795
|
+
}
|
|
10796
|
+
const message = typeof extra?.reason === "string" ? `${error} \u2014 ${extra.reason}` : error;
|
|
10797
|
+
return c.json(
|
|
10798
|
+
{ jsonrpc: "2.0", id, error: { code: -32e3, message } },
|
|
10799
|
+
status
|
|
10800
|
+
);
|
|
10801
|
+
};
|
|
10715
10802
|
if (nestScope && nestScope !== nestId) {
|
|
10716
|
-
return
|
|
10803
|
+
return deny(
|
|
10804
|
+
403,
|
|
10805
|
+
`API key not authorized for this nest \u2014 this key is scoped to nest "${nestScope}". Use that nest's MCP URL, or mint a user-level key in the Connect dialog.`
|
|
10806
|
+
);
|
|
10717
10807
|
}
|
|
10718
10808
|
if (isSuspended() && c.req.method !== "GET") {
|
|
10719
|
-
return
|
|
10720
|
-
|
|
10721
|
-
|
|
10722
|
-
|
|
10723
|
-
contact: "support@promptowl.ai"
|
|
10724
|
-
},
|
|
10725
|
-
503
|
|
10726
|
-
);
|
|
10809
|
+
return deny(503, "Server suspended by PromptOwl", {
|
|
10810
|
+
reason: getSuspensionReason(),
|
|
10811
|
+
contact: "support@promptowl.ai"
|
|
10812
|
+
});
|
|
10727
10813
|
}
|
|
10728
10814
|
{
|
|
10729
10815
|
const path2 = c.req.path;
|
|
@@ -10735,14 +10821,10 @@ function createApp() {
|
|
|
10735
10821
|
if (needsLicense) {
|
|
10736
10822
|
const lic = getCurrentLicense();
|
|
10737
10823
|
if (!lic?.valid) {
|
|
10738
|
-
return
|
|
10739
|
-
|
|
10740
|
-
|
|
10741
|
-
|
|
10742
|
-
setup_url: "/setup"
|
|
10743
|
-
},
|
|
10744
|
-
503
|
|
10745
|
-
);
|
|
10824
|
+
return deny(503, "License required", {
|
|
10825
|
+
reason: "This server has no valid PromptOwl license installed. Ask the server admin to install one via the setup screen or POST /license/install.",
|
|
10826
|
+
setup_url: "/setup"
|
|
10827
|
+
});
|
|
10746
10828
|
}
|
|
10747
10829
|
}
|
|
10748
10830
|
}
|
|
@@ -10770,7 +10852,9 @@ function createApp() {
|
|
|
10770
10852
|
grantNestVisible = await hasAnyGrant(nestId, userId);
|
|
10771
10853
|
}
|
|
10772
10854
|
if (!grantRole && !grantNestVisible) {
|
|
10773
|
-
return
|
|
10855
|
+
return deny(404, "Nest not found", {
|
|
10856
|
+
reason: "The nest id may be wrong, or these credentials have no access to it. Ask the nest owner to share the nest with you, then reconnect."
|
|
10857
|
+
});
|
|
10774
10858
|
}
|
|
10775
10859
|
}
|
|
10776
10860
|
let required = "read";
|
|
@@ -10796,7 +10880,7 @@ function createApp() {
|
|
|
10796
10880
|
required = c.req.method === "GET" ? "write" : "admin";
|
|
10797
10881
|
} else if (parts[1] === "hooks") {
|
|
10798
10882
|
required = "write";
|
|
10799
|
-
} else if (c.req.method !== "GET" && !isStewardActionPath && !isCommentAction && !isAnnotationAction && !isReadQuery) {
|
|
10883
|
+
} else if (c.req.method !== "GET" && !isStewardActionPath && !isCommentAction && !isAnnotationAction && !isReadQuery && !isMcpTransport) {
|
|
10800
10884
|
required = "write";
|
|
10801
10885
|
}
|
|
10802
10886
|
const isNodeAction = c.req.method === "POST" && parts.length >= 4 && (parts[parts.length - 1] === "revert" || parts[parts.length - 1] === "move");
|
|
@@ -10825,13 +10909,13 @@ function createApp() {
|
|
|
10825
10909
|
const grantLevel = grantRole ? permissionLevel(grantRole) : grantNestVisible ? permissionLevel("read") : 0;
|
|
10826
10910
|
const effectiveLevel = Math.max(permissionLevel(permission), grantLevel);
|
|
10827
10911
|
if (!stewardEditorBypass && effectiveLevel < permissionLevel(required)) {
|
|
10828
|
-
return
|
|
10912
|
+
return deny(
|
|
10913
|
+
403,
|
|
10914
|
+
`You don't have access to perform this action on this nest. Required permission: '${required}', your permission: '${permission}'. Ask the nest owner or a server admin to grant you ${required} access.`,
|
|
10829
10915
|
{
|
|
10830
|
-
error: `You don't have access to perform this action on this nest. Required permission: '${required}', your permission: '${permission}'. Ask the nest owner or a server admin to grant you ${required} access.`,
|
|
10831
10916
|
required_permission: required,
|
|
10832
10917
|
your_permission: permission
|
|
10833
|
-
}
|
|
10834
|
-
403
|
|
10918
|
+
}
|
|
10835
10919
|
);
|
|
10836
10920
|
}
|
|
10837
10921
|
c.set(
|
|
@@ -6,16 +6,16 @@ import {
|
|
|
6
6
|
getReviewQueue,
|
|
7
7
|
reject,
|
|
8
8
|
submitForReview
|
|
9
|
-
} from "./chunk-
|
|
10
|
-
import "./chunk-
|
|
11
|
-
import "./chunk-
|
|
9
|
+
} from "./chunk-PAIP5KHB.js";
|
|
10
|
+
import "./chunk-ZQS5W45U.js";
|
|
11
|
+
import "./chunk-EG77SQHO.js";
|
|
12
12
|
import {
|
|
13
13
|
canUserApprove
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-Q5NRCDD4.js";
|
|
15
15
|
import "./chunk-GUNJTORH.js";
|
|
16
16
|
import "./chunk-FRQJWGN3.js";
|
|
17
17
|
import "./chunk-XQ46F76G.js";
|
|
18
|
-
import "./chunk-
|
|
18
|
+
import "./chunk-5QQ7WKAI.js";
|
|
19
19
|
import "./chunk-SLTQACJW.js";
|
|
20
20
|
export {
|
|
21
21
|
approve,
|
|
@@ -14,17 +14,18 @@ import {
|
|
|
14
14
|
getStewardsForUser,
|
|
15
15
|
listStewards,
|
|
16
16
|
removeSteward,
|
|
17
|
+
resolveGrantRoles,
|
|
17
18
|
resolveStewardsForNode,
|
|
18
19
|
resolveStewardsWithFallback,
|
|
19
20
|
resolveUserRoles,
|
|
20
21
|
syncFromConfig,
|
|
21
22
|
updateSteward,
|
|
22
23
|
updateStewardRole
|
|
23
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-Q5NRCDD4.js";
|
|
24
25
|
import "./chunk-GUNJTORH.js";
|
|
25
26
|
import "./chunk-FRQJWGN3.js";
|
|
26
27
|
import "./chunk-XQ46F76G.js";
|
|
27
|
-
import "./chunk-
|
|
28
|
+
import "./chunk-5QQ7WKAI.js";
|
|
28
29
|
import "./chunk-SLTQACJW.js";
|
|
29
30
|
export {
|
|
30
31
|
assignSteward,
|
|
@@ -42,6 +43,7 @@ export {
|
|
|
42
43
|
getStewardsForUser,
|
|
43
44
|
listStewards,
|
|
44
45
|
removeSteward,
|
|
46
|
+
resolveGrantRoles,
|
|
45
47
|
resolveStewardsForNode,
|
|
46
48
|
resolveStewardsWithFallback,
|
|
47
49
|
resolveUserRoles,
|