+4
-2
apps/hosting-service/package.json
+4
-2
apps/hosting-service/package.json
···
6
"dev": "tsx --env-file=.env src/index.ts",
7
"build": "bun run build.ts",
8
"start": "tsx src/index.ts",
9
"backfill": "tsx src/index.ts --backfill"
10
},
11
"dependencies": {
···
18
"@wisp/safe-fetch": "workspace:*",
19
"@atproto/api": "^0.17.4",
20
"@atproto/identity": "^0.4.9",
21
-
"@atproto/lexicon": "^0.5.1",
22
"@atproto/sync": "^0.1.36",
23
"@atproto/xrpc": "^0.7.5",
24
"@hono/node-server": "^1.19.6",
···
31
"@types/bun": "^1.3.1",
32
"@types/mime-types": "^2.1.4",
33
"@types/node": "^22.10.5",
34
-
"tsx": "^4.19.2"
35
}
36
}
···
6
"dev": "tsx --env-file=.env src/index.ts",
7
"build": "bun run build.ts",
8
"start": "tsx src/index.ts",
9
+
"check": "tsc --noEmit",
10
"backfill": "tsx src/index.ts --backfill"
11
},
12
"dependencies": {
···
19
"@wisp/safe-fetch": "workspace:*",
20
"@atproto/api": "^0.17.4",
21
"@atproto/identity": "^0.4.9",
22
+
"@atproto/lexicon": "^0.5.2",
23
"@atproto/sync": "^0.1.36",
24
"@atproto/xrpc": "^0.7.5",
25
"@hono/node-server": "^1.19.6",
···
32
"@types/bun": "^1.3.1",
33
"@types/mime-types": "^2.1.4",
34
"@types/node": "^22.10.5",
35
+
"tsx": "^4.19.2",
36
+
"typescript": "^5.9.3"
37
}
38
}
+53
-3
apps/hosting-service/src/lib/utils.ts
+53
-3
apps/hosting-service/src/lib/utils.ts
···
7
import { safeFetchJson, safeFetchBlob } from '@wisp/safe-fetch';
8
import { CID } from 'multiformats';
9
import { extractBlobCid } from '@wisp/atproto-utils';
10
-
import { sanitizePath, collectFileCidsFromEntries } from '@wisp/fs-utils';
11
import { shouldCompressMimeType } from '@wisp/atproto-utils/compression';
12
13
// Re-export shared utilities for local usage and tests
14
export { extractBlobCid, sanitizePath };
···
120
}
121
122
/**
123
* Extract all subfs URIs from a directory tree with their mount paths
124
*/
125
function extractSubfsUris(directory: Directory, currentPath: string = ''): Array<{ uri: string; path: string }> {
···
300
// Expand subfs nodes before caching
301
const expandedRoot = await expandSubfsNodes(record.root, pdsEndpoint);
302
303
// Get existing cache metadata to check for incremental updates
304
const existingMetadata = await getCacheMetadata(did, rkey);
305
const existingFileCids = existingMetadata?.fileCids || {};
···
514
515
console.log(`[Cache] Fetching blob for file: ${filePath}, CID: ${cid}`);
516
517
-
// Allow up to 500MB per file blob, with 5 minute timeout
518
-
let content = await safeFetchBlob(blobUrl, { maxSize: 500 * 1024 * 1024, timeout: 300000 });
519
520
// If content is base64-encoded, decode it back to raw binary (gzipped or not)
521
if (base64) {
···
7
import { safeFetchJson, safeFetchBlob } from '@wisp/safe-fetch';
8
import { CID } from 'multiformats';
9
import { extractBlobCid } from '@wisp/atproto-utils';
10
+
import { sanitizePath, collectFileCidsFromEntries, countFilesInDirectory } from '@wisp/fs-utils';
11
import { shouldCompressMimeType } from '@wisp/atproto-utils/compression';
12
+
import { MAX_BLOB_SIZE, MAX_FILE_COUNT, MAX_SITE_SIZE } from '@wisp/constants';
13
14
// Re-export shared utilities for local usage and tests
15
export { extractBlobCid, sanitizePath };
···
121
}
122
123
/**
124
+
* Calculate total size of all blobs in a directory tree from manifest metadata
125
+
*/
126
+
function calculateTotalBlobSize(directory: Directory): number {
127
+
let totalSize = 0;
128
+
129
+
function sumBlobSizes(entries: Entry[]) {
130
+
for (const entry of entries) {
131
+
const node = entry.node;
132
+
133
+
if ('type' in node && node.type === 'directory' && 'entries' in node) {
134
+
// Recursively sum subdirectories
135
+
sumBlobSizes(node.entries);
136
+
} else if ('type' in node && node.type === 'file' && 'blob' in node) {
137
+
// Add blob size from manifest
138
+
const fileNode = node as File;
139
+
const blobSize = (fileNode.blob as any)?.size || 0;
140
+
totalSize += blobSize;
141
+
}
142
+
}
143
+
}
144
+
145
+
sumBlobSizes(directory.entries);
146
+
return totalSize;
147
+
}
148
+
149
+
/**
150
* Extract all subfs URIs from a directory tree with their mount paths
151
*/
152
function extractSubfsUris(directory: Directory, currentPath: string = ''): Array<{ uri: string; path: string }> {
···
327
// Expand subfs nodes before caching
328
const expandedRoot = await expandSubfsNodes(record.root, pdsEndpoint);
329
330
+
// Verify all subfs nodes were expanded (defensive check)
331
+
const remainingSubfs = extractSubfsUris(expandedRoot);
332
+
if (remainingSubfs.length > 0) {
333
+
console.warn(`[Cache] Warning: ${remainingSubfs.length} subfs nodes remain unexpanded after expansion`, remainingSubfs);
334
+
}
335
+
336
+
// ===== VALIDATE LIMITS BEFORE DOWNLOADING ANY BLOBS =====
337
+
338
+
// 1. Validate file count limit
339
+
const fileCount = countFilesInDirectory(expandedRoot);
340
+
if (fileCount > MAX_FILE_COUNT) {
341
+
throw new Error(`Site exceeds file count limit: ${fileCount} files (max ${MAX_FILE_COUNT})`);
342
+
}
343
+
console.log(`[Cache] File count validation passed: ${fileCount} files (limit: ${MAX_FILE_COUNT})`);
344
+
345
+
// 2. Validate total size from blob metadata in manifest (before downloading)
346
+
const totalBlobSize = calculateTotalBlobSize(expandedRoot);
347
+
if (totalBlobSize > MAX_SITE_SIZE) {
348
+
throw new Error(`Site exceeds size limit: ${(totalBlobSize / 1024 / 1024).toFixed(2)}MB (max ${(MAX_SITE_SIZE / 1024 / 1024).toFixed(0)}MB)`);
349
+
}
350
+
console.log(`[Cache] Size validation passed: ${(totalBlobSize / 1024 / 1024).toFixed(2)}MB (limit: ${(MAX_SITE_SIZE / 1024 / 1024).toFixed(0)}MB)`);
351
+
352
+
// All validations passed, proceed with caching
353
// Get existing cache metadata to check for incremental updates
354
const existingMetadata = await getCacheMetadata(did, rkey);
355
const existingFileCids = existingMetadata?.fileCids || {};
···
564
565
console.log(`[Cache] Fetching blob for file: ${filePath}, CID: ${cid}`);
566
567
+
// Allow up to MAX_BLOB_SIZE per file blob, with 5 minute timeout
568
+
let content = await safeFetchBlob(blobUrl, { maxSize: MAX_BLOB_SIZE, timeout: 300000 });
569
570
// If content is base64-encoded, decode it back to raw binary (gzipped or not)
571
if (base64) {
+3
apps/main-app/package.json
+3
apps/main-app/package.json
···
7
"dev": "bun run --watch src/index.ts",
8
"start": "bun run src/index.ts",
9
"build": "bun run build.ts",
10
"screenshot": "bun run scripts/screenshot-sites.ts"
11
},
12
"dependencies": {
···
53
"zlib": "^1.0.5"
54
},
55
"devDependencies": {
56
"@types/react": "^19.2.2",
57
"@types/react-dom": "^19.2.1",
58
"bun-types": "latest",
···
7
"dev": "bun run --watch src/index.ts",
8
"start": "bun run src/index.ts",
9
"build": "bun run build.ts",
10
+
"check": "tsc --noEmit",
11
"screenshot": "bun run scripts/screenshot-sites.ts"
12
},
13
"dependencies": {
···
54
"zlib": "^1.0.5"
55
},
56
"devDependencies": {
57
+
"@atproto-labs/handle-resolver": "^0.3.4",
58
+
"@atproto/did": "^0.2.3",
59
"@types/react": "^19.2.2",
60
"@types/react-dom": "^19.2.1",
61
"bun-types": "latest",
+53
-22
bun.lock
+53
-22
bun.lock
···
17
"dependencies": {
18
"@atproto/api": "^0.17.4",
19
"@atproto/identity": "^0.4.9",
20
-
"@atproto/lexicon": "^0.5.1",
21
"@atproto/sync": "^0.1.36",
22
"@atproto/xrpc": "^0.7.5",
23
"@hono/node-server": "^1.19.6",
···
38
"@types/mime-types": "^2.1.4",
39
"@types/node": "^22.10.5",
40
"tsx": "^4.19.2",
41
},
42
},
43
"apps/main-app": {
···
87
"zlib": "^1.0.5",
88
},
89
"devDependencies": {
90
"@types/react": "^19.2.2",
91
"@types/react-dom": "^19.2.1",
92
"bun-types": "latest",
···
103
"@wisp/lexicons": "workspace:*",
104
"multiformats": "^13.3.1",
105
},
106
},
107
"packages/@wisp/constants": {
108
"name": "@wisp/constants",
···
132
},
133
"devDependencies": {
134
"@atproto/lex-cli": "^0.9.5",
135
},
136
},
137
"packages/@wisp/observability": {
···
180
181
"@atproto-labs/fetch-node": ["@atproto-labs/fetch-node@0.2.0", "", { "dependencies": { "@atproto-labs/fetch": "0.2.3", "@atproto-labs/pipe": "0.1.1", "ipaddr.js": "^2.1.0", "undici": "^6.14.1" } }, ""],
182
183
-
"@atproto-labs/handle-resolver": ["@atproto-labs/handle-resolver@0.3.2", "", { "dependencies": { "@atproto-labs/simple-store": "0.3.0", "@atproto-labs/simple-store-memory": "0.1.4", "@atproto/did": "0.2.1", "zod": "^3.23.8" } }, ""],
184
185
"@atproto-labs/handle-resolver-node": ["@atproto-labs/handle-resolver-node@0.1.21", "", { "dependencies": { "@atproto-labs/fetch-node": "0.2.0", "@atproto-labs/handle-resolver": "0.3.2", "@atproto/did": "0.2.1" } }, ""],
186
···
200
201
"@atproto/crypto": ["@atproto/crypto@0.4.4", "", { "dependencies": { "@noble/curves": "^1.7.0", "@noble/hashes": "^1.6.1", "uint8arrays": "3.0.0" } }, ""],
202
203
-
"@atproto/did": ["@atproto/did@0.2.1", "", { "dependencies": { "zod": "^3.23.8" } }, ""],
204
205
"@atproto/identity": ["@atproto/identity@0.4.10", "", { "dependencies": { "@atproto/common-web": "^0.4.4", "@atproto/crypto": "^0.4.4" } }, "sha512-nQbzDLXOhM8p/wo0cTh5DfMSOSHzj6jizpodX37LJ4S1TZzumSxAjHEZa5Rev3JaoD5uSWMVE0MmKEGWkPPvfQ=="],
206
···
230
231
"@atproto/sync": ["@atproto/sync@0.1.38", "", { "dependencies": { "@atproto/common": "^0.5.0", "@atproto/identity": "^0.4.10", "@atproto/lexicon": "^0.5.2", "@atproto/repo": "^0.8.11", "@atproto/syntax": "^0.4.1", "@atproto/xrpc-server": "^0.10.0", "multiformats": "^9.9.0", "p-queue": "^6.6.2", "ws": "^8.12.0" } }, "sha512-2rE0SM21Nk4hWw/XcIYFnzlWO6/gBg8mrzuWbOvDhD49sA/wW4zyjaHZ5t1gvk28/SLok2VZiIR8nYBdbf7F5Q=="],
232
233
-
"@atproto/syntax": ["@atproto/syntax@0.4.1", "", {}, ""],
234
235
"@atproto/ws-client": ["@atproto/ws-client@0.0.3", "", { "dependencies": { "@atproto/common": "^0.5.0", "ws": "^8.12.0" } }, "sha512-eKqkTWBk6zuMY+6gs02eT7mS8Btewm8/qaL/Dp00NDCqpNC+U59MWvQsOWT3xkNGfd9Eip+V6VI4oyPvAfsfTA=="],
236
237
-
"@atproto/xrpc": ["@atproto/xrpc@0.7.5", "", { "dependencies": { "@atproto/lexicon": "^0.5.1", "zod": "^3.23.8" } }, ""],
238
239
"@atproto/xrpc-server": ["@atproto/xrpc-server@0.9.5", "", { "dependencies": { "@atproto/common": "^0.4.12", "@atproto/crypto": "^0.4.4", "@atproto/lexicon": "^0.5.1", "@atproto/xrpc": "^0.7.5", "cbor-x": "^1.5.1", "express": "^4.17.2", "http-errors": "^2.0.0", "mime-types": "^2.1.35", "rate-limiter-flexible": "^2.4.1", "uint8arrays": "3.0.0", "ws": "^8.12.0", "zod": "^3.23.8" } }, ""],
240
···
656
657
"ee-first": ["ee-first@1.1.1", "", {}, ""],
658
659
-
"elysia": ["elysia@1.4.17", "", { "dependencies": { "cookie": "^1.1.1", "exact-mirror": "0.2.5", "fast-decode-uri-component": "^1.0.1", "memoirist": "^0.4.0" }, "peerDependencies": { "@sinclair/typebox": ">= 0.34.0 < 1", "@types/bun": ">= 1.2.0", "file-type": ">= 20.0.0", "openapi-types": ">= 12.0.0", "typescript": ">= 5.0.0" }, "optionalPeers": ["@types/bun", "typescript"] }, "sha512-GcR7tgxk0+NgMCEqmXMs/xgND4XpmIzUdSdwchcQbYFeFisBcw9cmsvSpI10i160idwtlVyaRXX9K9IZBqnA7Q=="],
660
661
"emoji-regex": ["emoji-regex@8.0.0", "", {}, ""],
662
···
1036
1037
"zod": ["zod@3.25.76", "", {}, ""],
1038
1039
"@atproto-labs/fetch-node/ipaddr.js": ["ipaddr.js@2.2.0", "", {}, ""],
1040
1041
"@atproto/api/@atproto/lexicon": ["@atproto/lexicon@0.4.14", "", { "dependencies": { "@atproto/common-web": "^0.4.2", "@atproto/syntax": "^0.4.0", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "zod": "^3.23.8" } }, "sha512-jiKpmH1QER3Gvc7JVY5brwrfo+etFoe57tKPQX/SmPwjvUsFnJAow5xLIryuBaJgFAhnTZViXKs41t//pahGHQ=="],
1042
1043
"@atproto/api/@atproto/xrpc": ["@atproto/xrpc@0.6.12", "", { "dependencies": { "@atproto/lexicon": "^0.4.10", "zod": "^3.23.8" } }, "sha512-Ut3iISNLujlmY9Gu8sNU+SPDJDvqlVzWddU8qUr0Yae5oD4SguaUFjjhireMGhQ3M5E0KljQgDbTmnBo1kIZ3w=="],
1044
···
1054
1055
"@atproto/lex-cli/@atproto/lexicon": ["@atproto/lexicon@0.5.1", "", { "dependencies": { "@atproto/common-web": "^0.4.3", "@atproto/syntax": "^0.4.1", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "zod": "^3.23.8" } }, ""],
1056
1057
-
"@atproto/lex-data/@atproto/syntax": ["@atproto/syntax@0.4.2", "", {}, "sha512-X9XSRPinBy/0VQ677j8VXlBsYSsUXaiqxWVpGGxJYsAhugdQRb0jqaVKJFtm6RskeNkV6y9xclSUi9UYG/COrA=="],
1058
1059
"@atproto/lex-data/multiformats": ["multiformats@9.9.0", "", {}, ""],
1060
1061
-
"@atproto/lexicon/@atproto/syntax": ["@atproto/syntax@0.4.2", "", {}, "sha512-X9XSRPinBy/0VQ677j8VXlBsYSsUXaiqxWVpGGxJYsAhugdQRb0jqaVKJFtm6RskeNkV6y9xclSUi9UYG/COrA=="],
1062
1063
-
"@atproto/lexicon/multiformats": ["multiformats@9.9.0", "", {}, ""],
1064
1065
"@atproto/oauth-client/multiformats": ["multiformats@9.9.0", "", {}, ""],
1066
1067
"@atproto/repo/@atproto/common": ["@atproto/common@0.5.2", "", { "dependencies": { "@atproto/common-web": "^0.4.6", "@atproto/lex-cbor": "0.0.2", "@atproto/lex-data": "0.0.2", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "pino": "^8.21.0" } }, "sha512-7KdU8FcIfnwS2kmv7M86pKxtw/fLvPY2bSI1rXpG+AmA8O++IUGlSCujBGzbrPwnQvY/z++f6Le4rdBzu8bFaA=="],
1068
···
1072
1073
"@atproto/sync/@atproto/common": ["@atproto/common@0.5.2", "", { "dependencies": { "@atproto/common-web": "^0.4.6", "@atproto/lex-cbor": "0.0.2", "@atproto/lex-data": "0.0.2", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "pino": "^8.21.0" } }, "sha512-7KdU8FcIfnwS2kmv7M86pKxtw/fLvPY2bSI1rXpG+AmA8O++IUGlSCujBGzbrPwnQvY/z++f6Le4rdBzu8bFaA=="],
1074
1075
-
"@atproto/sync/@atproto/syntax": ["@atproto/syntax@0.4.2", "", {}, "sha512-X9XSRPinBy/0VQ677j8VXlBsYSsUXaiqxWVpGGxJYsAhugdQRb0jqaVKJFtm6RskeNkV6y9xclSUi9UYG/COrA=="],
1076
-
1077
"@atproto/sync/@atproto/xrpc-server": ["@atproto/xrpc-server@0.10.2", "", { "dependencies": { "@atproto/common": "^0.5.2", "@atproto/crypto": "^0.4.5", "@atproto/lex-cbor": "0.0.2", "@atproto/lex-data": "0.0.2", "@atproto/lexicon": "^0.5.2", "@atproto/ws-client": "^0.0.3", "@atproto/xrpc": "^0.7.6", "express": "^4.17.2", "http-errors": "^2.0.0", "mime-types": "^2.1.35", "rate-limiter-flexible": "^2.4.1", "ws": "^8.12.0", "zod": "^3.23.8" } }, "sha512-5AzN8xoV8K1Omn45z6qKH414+B3Z35D536rrScwF3aQGDEdpObAS+vya9UoSg+Gvm2+oOtVEbVri7riLTBW3Vg=="],
1078
1079
"@atproto/sync/multiformats": ["multiformats@9.9.0", "", {}, ""],
1080
1081
"@atproto/ws-client/@atproto/common": ["@atproto/common@0.5.2", "", { "dependencies": { "@atproto/common-web": "^0.4.6", "@atproto/lex-cbor": "0.0.2", "@atproto/lex-data": "0.0.2", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "pino": "^8.21.0" } }, "sha512-7KdU8FcIfnwS2kmv7M86pKxtw/fLvPY2bSI1rXpG+AmA8O++IUGlSCujBGzbrPwnQvY/z++f6Le4rdBzu8bFaA=="],
1082
1083
-
"@atproto/xrpc/@atproto/lexicon": ["@atproto/lexicon@0.5.1", "", { "dependencies": { "@atproto/common-web": "^0.4.3", "@atproto/syntax": "^0.4.1", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "zod": "^3.23.8" } }, ""],
1084
-
1085
"@atproto/xrpc-server/@atproto/lexicon": ["@atproto/lexicon@0.5.1", "", { "dependencies": { "@atproto/common-web": "^0.4.3", "@atproto/syntax": "^0.4.1", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "zod": "^3.23.8" } }, ""],
1086
1087
"@ipld/dag-cbor/multiformats": ["multiformats@9.9.0", "", {}, ""],
1088
···
1140
1141
"wisp-hosting-service/@atproto/api": ["@atproto/api@0.17.7", "", { "dependencies": { "@atproto/common-web": "^0.4.3", "@atproto/lexicon": "^0.5.1", "@atproto/syntax": "^0.4.1", "@atproto/xrpc": "^0.7.5", "await-lock": "^2.2.2", "multiformats": "^9.9.0", "tlds": "^1.234.0", "zod": "^3.23.8" } }, ""],
1142
1143
-
"wisp-hosting-service/@atproto/lexicon": ["@atproto/lexicon@0.5.1", "", { "dependencies": { "@atproto/common-web": "^0.4.3", "@atproto/syntax": "^0.4.1", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "zod": "^3.23.8" } }, ""],
1144
1145
"@atproto/lex-cli/@atproto/lexicon/@atproto/common-web": ["@atproto/common-web@0.4.3", "", { "dependencies": { "graphemer": "^1.4.0", "multiformats": "^9.9.0", "uint8arrays": "3.0.0", "zod": "^3.23.8" } }, ""],
1146
1147
"@atproto/lex-cli/@atproto/lexicon/multiformats": ["multiformats@9.9.0", "", {}, ""],
1148
1149
-
"@atproto/sync/@atproto/xrpc-server/@atproto/crypto": ["@atproto/crypto@0.4.5", "", { "dependencies": { "@noble/curves": "^1.7.0", "@noble/hashes": "^1.6.1", "uint8arrays": "3.0.0" } }, "sha512-n40aKkMoCatP0u9Yvhrdk6fXyOHFDDbkdm4h4HCyWW+KlKl8iXfD5iV+ECq+w5BM+QH25aIpt3/j6EUNerhLxw=="],
1150
1151
-
"@atproto/sync/@atproto/xrpc-server/@atproto/xrpc": ["@atproto/xrpc@0.7.6", "", { "dependencies": { "@atproto/lexicon": "^0.5.2", "zod": "^3.23.8" } }, "sha512-RvCf4j0JnKYWuz3QzsYCntJi3VuiAAybQsMIUw2wLWcHhchO9F7UaBZINLL2z0qc/cYWPv5NSwcVydMseoCZLA=="],
1152
1153
"@atproto/ws-client/@atproto/common/multiformats": ["multiformats@9.9.0", "", {}, ""],
1154
1155
"@atproto/xrpc-server/@atproto/lexicon/@atproto/common-web": ["@atproto/common-web@0.4.3", "", { "dependencies": { "graphemer": "^1.4.0", "multiformats": "^9.9.0", "uint8arrays": "3.0.0", "zod": "^3.23.8" } }, ""],
1156
1157
"@atproto/xrpc-server/@atproto/lexicon/multiformats": ["multiformats@9.9.0", "", {}, ""],
1158
1159
-
"@atproto/xrpc/@atproto/lexicon/@atproto/common-web": ["@atproto/common-web@0.4.3", "", { "dependencies": { "graphemer": "^1.4.0", "multiformats": "^9.9.0", "uint8arrays": "3.0.0", "zod": "^3.23.8" } }, ""],
1160
-
1161
-
"@atproto/xrpc/@atproto/lexicon/multiformats": ["multiformats@9.9.0", "", {}, ""],
1162
-
1163
"@tokenizer/inflate/debug/ms": ["ms@2.1.3", "", {}, ""],
1164
1165
"@wisp/main-app/@atproto/api/@atproto/common-web": ["@atproto/common-web@0.4.3", "", { "dependencies": { "graphemer": "^1.4.0", "multiformats": "^9.9.0", "uint8arrays": "3.0.0", "zod": "^3.23.8" } }, ""],
1166
1167
"@wisp/main-app/@atproto/api/@atproto/lexicon": ["@atproto/lexicon@0.5.1", "", { "dependencies": { "@atproto/common-web": "^0.4.3", "@atproto/syntax": "^0.4.1", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "zod": "^3.23.8" } }, ""],
1168
1169
"@wisp/main-app/@atproto/api/multiformats": ["multiformats@9.9.0", "", {}, ""],
1170
···
1228
1229
"wisp-hosting-service/@atproto/api/@atproto/common-web": ["@atproto/common-web@0.4.3", "", { "dependencies": { "graphemer": "^1.4.0", "multiformats": "^9.9.0", "uint8arrays": "3.0.0", "zod": "^3.23.8" } }, ""],
1230
1231
"wisp-hosting-service/@atproto/api/multiformats": ["multiformats@9.9.0", "", {}, ""],
1232
1233
-
"wisp-hosting-service/@atproto/lexicon/@atproto/common-web": ["@atproto/common-web@0.4.3", "", { "dependencies": { "graphemer": "^1.4.0", "multiformats": "^9.9.0", "uint8arrays": "3.0.0", "zod": "^3.23.8" } }, ""],
1234
1235
-
"wisp-hosting-service/@atproto/lexicon/multiformats": ["multiformats@9.9.0", "", {}, ""],
1236
}
1237
}
···
17
"dependencies": {
18
"@atproto/api": "^0.17.4",
19
"@atproto/identity": "^0.4.9",
20
+
"@atproto/lexicon": "^0.5.2",
21
"@atproto/sync": "^0.1.36",
22
"@atproto/xrpc": "^0.7.5",
23
"@hono/node-server": "^1.19.6",
···
38
"@types/mime-types": "^2.1.4",
39
"@types/node": "^22.10.5",
40
"tsx": "^4.19.2",
41
+
"typescript": "^5.9.3",
42
},
43
},
44
"apps/main-app": {
···
88
"zlib": "^1.0.5",
89
},
90
"devDependencies": {
91
+
"@atproto-labs/handle-resolver": "^0.3.4",
92
+
"@atproto/did": "^0.2.3",
93
"@types/react": "^19.2.2",
94
"@types/react-dom": "^19.2.1",
95
"bun-types": "latest",
···
106
"@wisp/lexicons": "workspace:*",
107
"multiformats": "^13.3.1",
108
},
109
+
"devDependencies": {
110
+
"@atproto/lexicon": "^0.5.2",
111
+
},
112
},
113
"packages/@wisp/constants": {
114
"name": "@wisp/constants",
···
138
},
139
"devDependencies": {
140
"@atproto/lex-cli": "^0.9.5",
141
+
"multiformats": "^13.4.1",
142
},
143
},
144
"packages/@wisp/observability": {
···
187
188
"@atproto-labs/fetch-node": ["@atproto-labs/fetch-node@0.2.0", "", { "dependencies": { "@atproto-labs/fetch": "0.2.3", "@atproto-labs/pipe": "0.1.1", "ipaddr.js": "^2.1.0", "undici": "^6.14.1" } }, ""],
189
190
+
"@atproto-labs/handle-resolver": ["@atproto-labs/handle-resolver@0.3.4", "", { "dependencies": { "@atproto-labs/simple-store": "0.3.0", "@atproto-labs/simple-store-memory": "0.1.4", "@atproto/did": "0.2.3", "zod": "^3.23.8" } }, "sha512-wsNopfzfgO3uPvfnFDgNeXgDufXxSXhjBjp2WEiSzEiLrMy0Jodnqggw4OzD9MJKf0a4Iu2/ydd537qdy91LrQ=="],
191
192
"@atproto-labs/handle-resolver-node": ["@atproto-labs/handle-resolver-node@0.1.21", "", { "dependencies": { "@atproto-labs/fetch-node": "0.2.0", "@atproto-labs/handle-resolver": "0.3.2", "@atproto/did": "0.2.1" } }, ""],
193
···
207
208
"@atproto/crypto": ["@atproto/crypto@0.4.4", "", { "dependencies": { "@noble/curves": "^1.7.0", "@noble/hashes": "^1.6.1", "uint8arrays": "3.0.0" } }, ""],
209
210
+
"@atproto/did": ["@atproto/did@0.2.3", "", { "dependencies": { "zod": "^3.23.8" } }, "sha512-VI8JJkSizvM2cHYJa37WlbzeCm5tWpojyc1/Zy8q8OOjyoy6X4S4BEfoP941oJcpxpMTObamibQIXQDo7tnIjg=="],
211
212
"@atproto/identity": ["@atproto/identity@0.4.10", "", { "dependencies": { "@atproto/common-web": "^0.4.4", "@atproto/crypto": "^0.4.4" } }, "sha512-nQbzDLXOhM8p/wo0cTh5DfMSOSHzj6jizpodX37LJ4S1TZzumSxAjHEZa5Rev3JaoD5uSWMVE0MmKEGWkPPvfQ=="],
213
···
237
238
"@atproto/sync": ["@atproto/sync@0.1.38", "", { "dependencies": { "@atproto/common": "^0.5.0", "@atproto/identity": "^0.4.10", "@atproto/lexicon": "^0.5.2", "@atproto/repo": "^0.8.11", "@atproto/syntax": "^0.4.1", "@atproto/xrpc-server": "^0.10.0", "multiformats": "^9.9.0", "p-queue": "^6.6.2", "ws": "^8.12.0" } }, "sha512-2rE0SM21Nk4hWw/XcIYFnzlWO6/gBg8mrzuWbOvDhD49sA/wW4zyjaHZ5t1gvk28/SLok2VZiIR8nYBdbf7F5Q=="],
239
240
+
"@atproto/syntax": ["@atproto/syntax@0.4.2", "", {}, "sha512-X9XSRPinBy/0VQ677j8VXlBsYSsUXaiqxWVpGGxJYsAhugdQRb0jqaVKJFtm6RskeNkV6y9xclSUi9UYG/COrA=="],
241
242
"@atproto/ws-client": ["@atproto/ws-client@0.0.3", "", { "dependencies": { "@atproto/common": "^0.5.0", "ws": "^8.12.0" } }, "sha512-eKqkTWBk6zuMY+6gs02eT7mS8Btewm8/qaL/Dp00NDCqpNC+U59MWvQsOWT3xkNGfd9Eip+V6VI4oyPvAfsfTA=="],
243
244
+
"@atproto/xrpc": ["@atproto/xrpc@0.7.6", "", { "dependencies": { "@atproto/lexicon": "^0.5.2", "zod": "^3.23.8" } }, "sha512-RvCf4j0JnKYWuz3QzsYCntJi3VuiAAybQsMIUw2wLWcHhchO9F7UaBZINLL2z0qc/cYWPv5NSwcVydMseoCZLA=="],
245
246
"@atproto/xrpc-server": ["@atproto/xrpc-server@0.9.5", "", { "dependencies": { "@atproto/common": "^0.4.12", "@atproto/crypto": "^0.4.4", "@atproto/lexicon": "^0.5.1", "@atproto/xrpc": "^0.7.5", "cbor-x": "^1.5.1", "express": "^4.17.2", "http-errors": "^2.0.0", "mime-types": "^2.1.35", "rate-limiter-flexible": "^2.4.1", "uint8arrays": "3.0.0", "ws": "^8.12.0", "zod": "^3.23.8" } }, ""],
247
···
663
664
"ee-first": ["ee-first@1.1.1", "", {}, ""],
665
666
+
"elysia": ["elysia@1.4.18", "", { "dependencies": { "cookie": "^1.1.1", "exact-mirror": "0.2.5", "fast-decode-uri-component": "^1.0.1", "memoirist": "^0.4.0" }, "peerDependencies": { "@sinclair/typebox": ">= 0.34.0 < 1", "@types/bun": ">= 1.2.0", "file-type": ">= 20.0.0", "openapi-types": ">= 12.0.0", "typescript": ">= 5.0.0" }, "optionalPeers": ["@types/bun", "typescript"] }, "sha512-A6BhlipmSvgCy69SBgWADYZSdDIj3fT2gk8/9iMAC8iD+aGcnCr0fitziX0xr36MFDs/fsvVp8dWqxeq1VCgKg=="],
667
668
"emoji-regex": ["emoji-regex@8.0.0", "", {}, ""],
669
···
1043
1044
"zod": ["zod@3.25.76", "", {}, ""],
1045
1046
+
"@atproto-labs/did-resolver/@atproto/did": ["@atproto/did@0.2.1", "", { "dependencies": { "zod": "^3.23.8" } }, ""],
1047
+
1048
"@atproto-labs/fetch-node/ipaddr.js": ["ipaddr.js@2.2.0", "", {}, ""],
1049
1050
+
"@atproto-labs/handle-resolver-node/@atproto-labs/handle-resolver": ["@atproto-labs/handle-resolver@0.3.2", "", { "dependencies": { "@atproto-labs/simple-store": "0.3.0", "@atproto-labs/simple-store-memory": "0.1.4", "@atproto/did": "0.2.1", "zod": "^3.23.8" } }, ""],
1051
+
1052
+
"@atproto-labs/handle-resolver-node/@atproto/did": ["@atproto/did@0.2.1", "", { "dependencies": { "zod": "^3.23.8" } }, ""],
1053
+
1054
+
"@atproto-labs/identity-resolver/@atproto-labs/handle-resolver": ["@atproto-labs/handle-resolver@0.3.2", "", { "dependencies": { "@atproto-labs/simple-store": "0.3.0", "@atproto-labs/simple-store-memory": "0.1.4", "@atproto/did": "0.2.1", "zod": "^3.23.8" } }, ""],
1055
+
1056
"@atproto/api/@atproto/lexicon": ["@atproto/lexicon@0.4.14", "", { "dependencies": { "@atproto/common-web": "^0.4.2", "@atproto/syntax": "^0.4.0", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "zod": "^3.23.8" } }, "sha512-jiKpmH1QER3Gvc7JVY5brwrfo+etFoe57tKPQX/SmPwjvUsFnJAow5xLIryuBaJgFAhnTZViXKs41t//pahGHQ=="],
1057
+
1058
+
"@atproto/api/@atproto/syntax": ["@atproto/syntax@0.4.1", "", {}, ""],
1059
1060
"@atproto/api/@atproto/xrpc": ["@atproto/xrpc@0.6.12", "", { "dependencies": { "@atproto/lexicon": "^0.4.10", "zod": "^3.23.8" } }, "sha512-Ut3iISNLujlmY9Gu8sNU+SPDJDvqlVzWddU8qUr0Yae5oD4SguaUFjjhireMGhQ3M5E0KljQgDbTmnBo1kIZ3w=="],
1061
···
1071
1072
"@atproto/lex-cli/@atproto/lexicon": ["@atproto/lexicon@0.5.1", "", { "dependencies": { "@atproto/common-web": "^0.4.3", "@atproto/syntax": "^0.4.1", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "zod": "^3.23.8" } }, ""],
1073
1074
+
"@atproto/lex-cli/@atproto/syntax": ["@atproto/syntax@0.4.1", "", {}, ""],
1075
1076
"@atproto/lex-data/multiformats": ["multiformats@9.9.0", "", {}, ""],
1077
1078
+
"@atproto/lexicon/multiformats": ["multiformats@9.9.0", "", {}, ""],
1079
+
1080
+
"@atproto/oauth-client/@atproto-labs/handle-resolver": ["@atproto-labs/handle-resolver@0.3.2", "", { "dependencies": { "@atproto-labs/simple-store": "0.3.0", "@atproto-labs/simple-store-memory": "0.1.4", "@atproto/did": "0.2.1", "zod": "^3.23.8" } }, ""],
1081
1082
+
"@atproto/oauth-client/@atproto/did": ["@atproto/did@0.2.1", "", { "dependencies": { "zod": "^3.23.8" } }, ""],
1083
+
1084
+
"@atproto/oauth-client/@atproto/xrpc": ["@atproto/xrpc@0.7.5", "", { "dependencies": { "@atproto/lexicon": "^0.5.1", "zod": "^3.23.8" } }, ""],
1085
1086
"@atproto/oauth-client/multiformats": ["multiformats@9.9.0", "", {}, ""],
1087
+
1088
+
"@atproto/oauth-client-node/@atproto/did": ["@atproto/did@0.2.1", "", { "dependencies": { "zod": "^3.23.8" } }, ""],
1089
+
1090
+
"@atproto/oauth-types/@atproto/did": ["@atproto/did@0.2.1", "", { "dependencies": { "zod": "^3.23.8" } }, ""],
1091
1092
"@atproto/repo/@atproto/common": ["@atproto/common@0.5.2", "", { "dependencies": { "@atproto/common-web": "^0.4.6", "@atproto/lex-cbor": "0.0.2", "@atproto/lex-data": "0.0.2", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "pino": "^8.21.0" } }, "sha512-7KdU8FcIfnwS2kmv7M86pKxtw/fLvPY2bSI1rXpG+AmA8O++IUGlSCujBGzbrPwnQvY/z++f6Le4rdBzu8bFaA=="],
1093
···
1097
1098
"@atproto/sync/@atproto/common": ["@atproto/common@0.5.2", "", { "dependencies": { "@atproto/common-web": "^0.4.6", "@atproto/lex-cbor": "0.0.2", "@atproto/lex-data": "0.0.2", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "pino": "^8.21.0" } }, "sha512-7KdU8FcIfnwS2kmv7M86pKxtw/fLvPY2bSI1rXpG+AmA8O++IUGlSCujBGzbrPwnQvY/z++f6Le4rdBzu8bFaA=="],
1099
1100
"@atproto/sync/@atproto/xrpc-server": ["@atproto/xrpc-server@0.10.2", "", { "dependencies": { "@atproto/common": "^0.5.2", "@atproto/crypto": "^0.4.5", "@atproto/lex-cbor": "0.0.2", "@atproto/lex-data": "0.0.2", "@atproto/lexicon": "^0.5.2", "@atproto/ws-client": "^0.0.3", "@atproto/xrpc": "^0.7.6", "express": "^4.17.2", "http-errors": "^2.0.0", "mime-types": "^2.1.35", "rate-limiter-flexible": "^2.4.1", "ws": "^8.12.0", "zod": "^3.23.8" } }, "sha512-5AzN8xoV8K1Omn45z6qKH414+B3Z35D536rrScwF3aQGDEdpObAS+vya9UoSg+Gvm2+oOtVEbVri7riLTBW3Vg=="],
1101
1102
"@atproto/sync/multiformats": ["multiformats@9.9.0", "", {}, ""],
1103
1104
"@atproto/ws-client/@atproto/common": ["@atproto/common@0.5.2", "", { "dependencies": { "@atproto/common-web": "^0.4.6", "@atproto/lex-cbor": "0.0.2", "@atproto/lex-data": "0.0.2", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "pino": "^8.21.0" } }, "sha512-7KdU8FcIfnwS2kmv7M86pKxtw/fLvPY2bSI1rXpG+AmA8O++IUGlSCujBGzbrPwnQvY/z++f6Le4rdBzu8bFaA=="],
1105
1106
"@atproto/xrpc-server/@atproto/lexicon": ["@atproto/lexicon@0.5.1", "", { "dependencies": { "@atproto/common-web": "^0.4.3", "@atproto/syntax": "^0.4.1", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "zod": "^3.23.8" } }, ""],
1107
+
1108
+
"@atproto/xrpc-server/@atproto/xrpc": ["@atproto/xrpc@0.7.5", "", { "dependencies": { "@atproto/lexicon": "^0.5.1", "zod": "^3.23.8" } }, ""],
1109
1110
"@ipld/dag-cbor/multiformats": ["multiformats@9.9.0", "", {}, ""],
1111
···
1163
1164
"wisp-hosting-service/@atproto/api": ["@atproto/api@0.17.7", "", { "dependencies": { "@atproto/common-web": "^0.4.3", "@atproto/lexicon": "^0.5.1", "@atproto/syntax": "^0.4.1", "@atproto/xrpc": "^0.7.5", "await-lock": "^2.2.2", "multiformats": "^9.9.0", "tlds": "^1.234.0", "zod": "^3.23.8" } }, ""],
1165
1166
+
"@atproto-labs/identity-resolver/@atproto-labs/handle-resolver/@atproto/did": ["@atproto/did@0.2.1", "", { "dependencies": { "zod": "^3.23.8" } }, ""],
1167
1168
"@atproto/lex-cli/@atproto/lexicon/@atproto/common-web": ["@atproto/common-web@0.4.3", "", { "dependencies": { "graphemer": "^1.4.0", "multiformats": "^9.9.0", "uint8arrays": "3.0.0", "zod": "^3.23.8" } }, ""],
1169
1170
"@atproto/lex-cli/@atproto/lexicon/multiformats": ["multiformats@9.9.0", "", {}, ""],
1171
1172
+
"@atproto/oauth-client/@atproto/xrpc/@atproto/lexicon": ["@atproto/lexicon@0.5.1", "", { "dependencies": { "@atproto/common-web": "^0.4.3", "@atproto/syntax": "^0.4.1", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "zod": "^3.23.8" } }, ""],
1173
1174
+
"@atproto/sync/@atproto/xrpc-server/@atproto/crypto": ["@atproto/crypto@0.4.5", "", { "dependencies": { "@noble/curves": "^1.7.0", "@noble/hashes": "^1.6.1", "uint8arrays": "3.0.0" } }, "sha512-n40aKkMoCatP0u9Yvhrdk6fXyOHFDDbkdm4h4HCyWW+KlKl8iXfD5iV+ECq+w5BM+QH25aIpt3/j6EUNerhLxw=="],
1175
1176
"@atproto/ws-client/@atproto/common/multiformats": ["multiformats@9.9.0", "", {}, ""],
1177
1178
"@atproto/xrpc-server/@atproto/lexicon/@atproto/common-web": ["@atproto/common-web@0.4.3", "", { "dependencies": { "graphemer": "^1.4.0", "multiformats": "^9.9.0", "uint8arrays": "3.0.0", "zod": "^3.23.8" } }, ""],
1179
+
1180
+
"@atproto/xrpc-server/@atproto/lexicon/@atproto/syntax": ["@atproto/syntax@0.4.1", "", {}, ""],
1181
1182
"@atproto/xrpc-server/@atproto/lexicon/multiformats": ["multiformats@9.9.0", "", {}, ""],
1183
1184
"@tokenizer/inflate/debug/ms": ["ms@2.1.3", "", {}, ""],
1185
1186
"@wisp/main-app/@atproto/api/@atproto/common-web": ["@atproto/common-web@0.4.3", "", { "dependencies": { "graphemer": "^1.4.0", "multiformats": "^9.9.0", "uint8arrays": "3.0.0", "zod": "^3.23.8" } }, ""],
1187
1188
"@wisp/main-app/@atproto/api/@atproto/lexicon": ["@atproto/lexicon@0.5.1", "", { "dependencies": { "@atproto/common-web": "^0.4.3", "@atproto/syntax": "^0.4.1", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "zod": "^3.23.8" } }, ""],
1189
+
1190
+
"@wisp/main-app/@atproto/api/@atproto/syntax": ["@atproto/syntax@0.4.1", "", {}, ""],
1191
+
1192
+
"@wisp/main-app/@atproto/api/@atproto/xrpc": ["@atproto/xrpc@0.7.5", "", { "dependencies": { "@atproto/lexicon": "^0.5.1", "zod": "^3.23.8" } }, ""],
1193
1194
"@wisp/main-app/@atproto/api/multiformats": ["multiformats@9.9.0", "", {}, ""],
1195
···
1253
1254
"wisp-hosting-service/@atproto/api/@atproto/common-web": ["@atproto/common-web@0.4.3", "", { "dependencies": { "graphemer": "^1.4.0", "multiformats": "^9.9.0", "uint8arrays": "3.0.0", "zod": "^3.23.8" } }, ""],
1255
1256
+
"wisp-hosting-service/@atproto/api/@atproto/lexicon": ["@atproto/lexicon@0.5.1", "", { "dependencies": { "@atproto/common-web": "^0.4.3", "@atproto/syntax": "^0.4.1", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "zod": "^3.23.8" } }, ""],
1257
+
1258
+
"wisp-hosting-service/@atproto/api/@atproto/syntax": ["@atproto/syntax@0.4.1", "", {}, ""],
1259
+
1260
+
"wisp-hosting-service/@atproto/api/@atproto/xrpc": ["@atproto/xrpc@0.7.5", "", { "dependencies": { "@atproto/lexicon": "^0.5.1", "zod": "^3.23.8" } }, ""],
1261
+
1262
"wisp-hosting-service/@atproto/api/multiformats": ["multiformats@9.9.0", "", {}, ""],
1263
1264
+
"@atproto/oauth-client/@atproto/xrpc/@atproto/lexicon/@atproto/common-web": ["@atproto/common-web@0.4.3", "", { "dependencies": { "graphemer": "^1.4.0", "multiformats": "^9.9.0", "uint8arrays": "3.0.0", "zod": "^3.23.8" } }, ""],
1265
1266
+
"@atproto/oauth-client/@atproto/xrpc/@atproto/lexicon/@atproto/syntax": ["@atproto/syntax@0.4.1", "", {}, ""],
1267
}
1268
}
+54
-8
cli/Cargo.lock
+54
-8
cli/Cargo.lock
···
605
checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d"
606
607
[[package]]
608
name = "const-oid"
609
version = "0.9.6"
610
source = "registry+https://github.com/rust-lang/crates.io-index"
···
957
"subtle",
958
"zeroize",
959
]
960
961
[[package]]
962
name = "encoding_rs"
···
1733
]
1734
1735
[[package]]
1736
name = "indoc"
1737
version = "2.0.7"
1738
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1810
[[package]]
1811
name = "jacquard"
1812
version = "0.9.3"
1813
-
source = "git+https://tangled.org/nekomimi.pet/jacquard#e1b90160d4026e036ab5b797e56ddd7ae5c5537e"
1814
dependencies = [
1815
"bytes",
1816
"getrandom 0.2.16",
···
1840
[[package]]
1841
name = "jacquard-api"
1842
version = "0.9.2"
1843
-
source = "git+https://tangled.org/nekomimi.pet/jacquard#e1b90160d4026e036ab5b797e56ddd7ae5c5537e"
1844
dependencies = [
1845
"bon",
1846
"bytes",
···
1858
[[package]]
1859
name = "jacquard-common"
1860
version = "0.9.2"
1861
-
source = "git+https://tangled.org/nekomimi.pet/jacquard#e1b90160d4026e036ab5b797e56ddd7ae5c5537e"
1862
dependencies = [
1863
"base64 0.22.1",
1864
"bon",
···
1900
[[package]]
1901
name = "jacquard-derive"
1902
version = "0.9.3"
1903
-
source = "git+https://tangled.org/nekomimi.pet/jacquard#e1b90160d4026e036ab5b797e56ddd7ae5c5537e"
1904
dependencies = [
1905
"heck 0.5.0",
1906
"jacquard-lexicon",
···
1912
[[package]]
1913
name = "jacquard-identity"
1914
version = "0.9.2"
1915
-
source = "git+https://tangled.org/nekomimi.pet/jacquard#e1b90160d4026e036ab5b797e56ddd7ae5c5537e"
1916
dependencies = [
1917
"bon",
1918
"bytes",
···
1938
[[package]]
1939
name = "jacquard-lexicon"
1940
version = "0.9.2"
1941
-
source = "git+https://tangled.org/nekomimi.pet/jacquard#e1b90160d4026e036ab5b797e56ddd7ae5c5537e"
1942
dependencies = [
1943
"cid",
1944
"dashmap",
···
1964
[[package]]
1965
name = "jacquard-oauth"
1966
version = "0.9.2"
1967
-
source = "git+https://tangled.org/nekomimi.pet/jacquard#e1b90160d4026e036ab5b797e56ddd7ae5c5537e"
1968
dependencies = [
1969
"base64 0.22.1",
1970
"bytes",
···
2289
[[package]]
2290
name = "mini-moka"
2291
version = "0.10.99"
2292
-
source = "git+https://tangled.org/nekomimi.pet/jacquard#e1b90160d4026e036ab5b797e56ddd7ae5c5537e"
2293
dependencies = [
2294
"crossbeam-channel",
2295
"crossbeam-utils",
···
2513
]
2514
2515
[[package]]
2516
name = "objc2"
2517
version = "0.6.3"
2518
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2756
"der",
2757
"spki",
2758
]
2759
2760
[[package]]
2761
name = "potential_utf"
···
4717
4718
[[package]]
4719
name = "windows-sys"
4720
version = "0.60.2"
4721
source = "registry+https://github.com/rust-lang/crates.io-index"
4722
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
···
5008
"futures",
5009
"globset",
5010
"ignore",
5011
"jacquard",
5012
"jacquard-api",
5013
"jacquard-common",
···
605
checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d"
606
607
[[package]]
608
+
name = "console"
609
+
version = "0.15.11"
610
+
source = "registry+https://github.com/rust-lang/crates.io-index"
611
+
checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8"
612
+
dependencies = [
613
+
"encode_unicode",
614
+
"libc",
615
+
"once_cell",
616
+
"unicode-width 0.2.2",
617
+
"windows-sys 0.59.0",
618
+
]
619
+
620
+
[[package]]
621
name = "const-oid"
622
version = "0.9.6"
623
source = "registry+https://github.com/rust-lang/crates.io-index"
···
970
"subtle",
971
"zeroize",
972
]
973
+
974
+
[[package]]
975
+
name = "encode_unicode"
976
+
version = "1.0.0"
977
+
source = "registry+https://github.com/rust-lang/crates.io-index"
978
+
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
979
980
[[package]]
981
name = "encoding_rs"
···
1752
]
1753
1754
[[package]]
1755
+
name = "indicatif"
1756
+
version = "0.17.11"
1757
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1758
+
checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235"
1759
+
dependencies = [
1760
+
"console",
1761
+
"number_prefix",
1762
+
"portable-atomic",
1763
+
"unicode-width 0.2.2",
1764
+
"web-time",
1765
+
]
1766
+
1767
+
[[package]]
1768
name = "indoc"
1769
version = "2.0.7"
1770
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1842
[[package]]
1843
name = "jacquard"
1844
version = "0.9.3"
1845
dependencies = [
1846
"bytes",
1847
"getrandom 0.2.16",
···
1871
[[package]]
1872
name = "jacquard-api"
1873
version = "0.9.2"
1874
dependencies = [
1875
"bon",
1876
"bytes",
···
1888
[[package]]
1889
name = "jacquard-common"
1890
version = "0.9.2"
1891
dependencies = [
1892
"base64 0.22.1",
1893
"bon",
···
1929
[[package]]
1930
name = "jacquard-derive"
1931
version = "0.9.3"
1932
dependencies = [
1933
"heck 0.5.0",
1934
"jacquard-lexicon",
···
1940
[[package]]
1941
name = "jacquard-identity"
1942
version = "0.9.2"
1943
dependencies = [
1944
"bon",
1945
"bytes",
···
1965
[[package]]
1966
name = "jacquard-lexicon"
1967
version = "0.9.2"
1968
dependencies = [
1969
"cid",
1970
"dashmap",
···
1990
[[package]]
1991
name = "jacquard-oauth"
1992
version = "0.9.2"
1993
dependencies = [
1994
"base64 0.22.1",
1995
"bytes",
···
2314
[[package]]
2315
name = "mini-moka"
2316
version = "0.10.99"
2317
dependencies = [
2318
"crossbeam-channel",
2319
"crossbeam-utils",
···
2537
]
2538
2539
[[package]]
2540
+
name = "number_prefix"
2541
+
version = "0.4.0"
2542
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2543
+
checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
2544
+
2545
+
[[package]]
2546
name = "objc2"
2547
version = "0.6.3"
2548
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2786
"der",
2787
"spki",
2788
]
2789
+
2790
+
[[package]]
2791
+
name = "portable-atomic"
2792
+
version = "1.11.1"
2793
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2794
+
checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
2795
2796
[[package]]
2797
name = "potential_utf"
···
4753
4754
[[package]]
4755
name = "windows-sys"
4756
+
version = "0.59.0"
4757
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4758
+
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
4759
+
dependencies = [
4760
+
"windows-targets 0.52.6",
4761
+
]
4762
+
4763
+
[[package]]
4764
+
name = "windows-sys"
4765
version = "0.60.2"
4766
source = "registry+https://github.com/rust-lang/crates.io-index"
4767
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
···
5053
"futures",
5054
"globset",
5055
"ignore",
5056
+
"indicatif",
5057
"jacquard",
5058
"jacquard-api",
5059
"jacquard-common",
+15
-7
cli/Cargo.toml
+15
-7
cli/Cargo.toml
···
8
place_wisp = []
9
10
[dependencies]
11
-
jacquard = { git = "https://tangled.org/nekomimi.pet/jacquard", features = ["loopback"] }
12
-
jacquard-oauth = { git = "https://tangled.org/nekomimi.pet/jacquard" }
13
-
jacquard-api = { git = "https://tangled.org/nekomimi.pet/jacquard", features = ["streaming"] }
14
-
jacquard-common = { git = "https://tangled.org/nekomimi.pet/jacquard", features = ["websocket"] }
15
-
jacquard-identity = { git = "https://tangled.org/nekomimi.pet/jacquard", features = ["dns"] }
16
-
jacquard-derive = { git = "https://tangled.org/nekomimi.pet/jacquard" }
17
-
jacquard-lexicon = { git = "https://tangled.org/nekomimi.pet/jacquard" }
18
clap = { version = "4.5.51", features = ["derive"] }
19
tokio = { version = "1.48", features = ["full"] }
20
miette = { version = "7.6.0", features = ["fancy"] }
···
42
regex = "1.11"
43
ignore = "0.4"
44
globset = "0.4"
···
8
place_wisp = []
9
10
[dependencies]
11
+
# jacquard = { git = "https://tangled.org/nekomimi.pet/jacquard", features = ["loopback"] }
12
+
# jacquard-oauth = { git = "https://tangled.org/nekomimi.pet/jacquard" }
13
+
# jacquard-api = { git = "https://tangled.org/nekomimi.pet/jacquard", features = ["streaming"] }
14
+
# jacquard-common = { git = "https://tangled.org/nekomimi.pet/jacquard", features = ["websocket"] }
15
+
# jacquard-identity = { git = "https://tangled.org/nekomimi.pet/jacquard", features = ["dns"] }
16
+
# jacquard-derive = { git = "https://tangled.org/nekomimi.pet/jacquard" }
17
+
# jacquard-lexicon = { git = "https://tangled.org/nekomimi.pet/jacquard" }
18
+
jacquard = { path = "../../jacquard/crates/jacquard", features = ["loopback"] }
19
+
jacquard-oauth = { path = "../../jacquard/crates/jacquard-oauth" }
20
+
jacquard-api = { path = "../../jacquard/crates/jacquard-api", features = ["streaming"] }
21
+
jacquard-common = { path = "../../jacquard/crates/jacquard-common", features = ["websocket"] }
22
+
jacquard-identity = { path = "../../jacquard/crates/jacquard-identity", features = ["dns"] }
23
+
jacquard-derive = { path = "../../jacquard/crates/jacquard-derive" }
24
+
jacquard-lexicon = { path = "../../jacquard/crates/jacquard-lexicon" }
25
clap = { version = "4.5.51", features = ["derive"] }
26
tokio = { version = "1.48", features = ["full"] }
27
miette = { version = "7.6.0", features = ["fancy"] }
···
49
regex = "1.11"
50
ignore = "0.4"
51
globset = "0.4"
52
+
indicatif = "0.17"
+39
-15
cli/src/main.rs
+39
-15
cli/src/main.rs
···
26
use std::io::Write;
27
use base64::Engine;
28
use futures::stream::{self, StreamExt};
29
30
use place_wisp::fs::*;
31
use place_wisp::settings::*;
32
33
#[derive(Parser, Debug)]
34
#[command(author, version, about = "wisp.place CLI tool")]
···
326
327
// Build directory tree with ignore patterns
328
let ignore_matcher = ignore_patterns::IgnoreMatcher::new(&path)?;
329
-
let (root_dir, total_files, reused_count) = build_directory(agent, &path, &existing_blob_map, String::new(), &ignore_matcher).await?;
330
let uploaded_count = total_files - reused_count;
331
332
// Check if we need to split into subfs records
333
const MAX_MANIFEST_SIZE: usize = 140 * 1024; // 140KB (PDS limit is 150KB)
···
606
existing_blobs: &'a HashMap<String, (jacquard_common::types::blob::BlobRef<'static>, String)>,
607
current_path: String,
608
ignore_matcher: &'a ignore_patterns::IgnoreMatcher,
609
) -> std::pin::Pin<Box<dyn std::future::Future<Output = miette::Result<(Directory<'static>, usize, usize)>> + 'a>>
610
{
611
Box::pin(async move {
···
653
}
654
}
655
656
-
// Process files concurrently with a limit of 5
657
let file_results: Vec<(Entry<'static>, bool)> = stream::iter(file_tasks)
658
.map(|(name, path, full_path)| async move {
659
-
let (file_node, reused) = process_file(agent, &path, &full_path, existing_blobs).await?;
660
let entry = Entry::new()
661
.name(CowStr::from(name))
662
.node(EntryNode::File(Box::new(file_node)))
663
.build();
664
Ok::<_, miette::Report>((entry, reused))
665
})
666
-
.buffer_unordered(5)
667
.collect::<Vec<_>>()
668
.await
669
.into_iter()
···
690
} else {
691
format!("{}/{}", current_path, name)
692
};
693
-
let (subdir, sub_total, sub_reused) = build_directory(agent, &path, existing_blobs, subdir_path, ignore_matcher).await?;
694
dir_entries.push(Entry::new()
695
.name(CowStr::from(name))
696
.node(EntryNode::Directory(Box::new(subdir)))
···
722
file_path: &Path,
723
file_path_key: &str,
724
existing_blobs: &HashMap<String, (jacquard_common::types::blob::BlobRef<'static>, String)>,
725
) -> miette::Result<(File<'static>, bool)>
726
{
727
// Read file
···
761
if let Some((existing_blob_ref, existing_cid)) = existing_blob {
762
if existing_cid == &file_cid {
763
// CIDs match - reuse existing blob
764
-
println!(" ✓ Reusing blob for {} (CID: {})", file_path_key, file_cid);
765
let mut file_builder = File::new()
766
.r#type(CowStr::from("file"))
767
.blob(existing_blob_ref.clone())
···
775
}
776
777
return Ok((file_builder.build(), true));
778
-
} else {
779
-
// CID mismatch - file changed
780
-
println!(" → File changed: {} (old CID: {}, new CID: {})", file_path_key, existing_cid, file_cid);
781
-
}
782
-
} else {
783
-
// File not in existing blob map
784
-
if file_path_key.starts_with("imgs/") {
785
-
println!(" → New file (not in blob map): {}", file_path_key);
786
}
787
}
788
···
793
MimeType::new_static("application/octet-stream")
794
};
795
796
-
println!(" ↑ Uploading {} ({} bytes, CID: {})", file_path_key, upload_bytes.len(), file_cid);
797
let blob = agent.upload_blob(upload_bytes, mime_type).await?;
798
799
let mut file_builder = File::new()
800
.r#type(CowStr::from("file"))
···
26
use std::io::Write;
27
use base64::Engine;
28
use futures::stream::{self, StreamExt};
29
+
use indicatif::{ProgressBar, ProgressStyle, MultiProgress};
30
31
use place_wisp::fs::*;
32
use place_wisp::settings::*;
33
+
34
+
/// Maximum number of concurrent file uploads to the PDS
35
+
const MAX_CONCURRENT_UPLOADS: usize = 2;
36
37
#[derive(Parser, Debug)]
38
#[command(author, version, about = "wisp.place CLI tool")]
···
330
331
// Build directory tree with ignore patterns
332
let ignore_matcher = ignore_patterns::IgnoreMatcher::new(&path)?;
333
+
334
+
// Create progress tracking (spinner style since we don't know total count upfront)
335
+
let multi_progress = MultiProgress::new();
336
+
let progress = multi_progress.add(ProgressBar::new_spinner());
337
+
progress.set_style(
338
+
ProgressStyle::default_spinner()
339
+
.template("[{elapsed_precise}] {spinner:.cyan} {pos} files {msg}")
340
+
.into_diagnostic()?
341
+
.tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈ ")
342
+
);
343
+
progress.set_message("Scanning files...");
344
+
progress.enable_steady_tick(std::time::Duration::from_millis(100));
345
+
346
+
let (root_dir, total_files, reused_count) = build_directory(agent, &path, &existing_blob_map, String::new(), &ignore_matcher, &progress).await?;
347
let uploaded_count = total_files - reused_count;
348
+
349
+
progress.finish_with_message(format!("✓ {} files ({} uploaded, {} reused)", total_files, uploaded_count, reused_count));
350
351
// Check if we need to split into subfs records
352
const MAX_MANIFEST_SIZE: usize = 140 * 1024; // 140KB (PDS limit is 150KB)
···
625
existing_blobs: &'a HashMap<String, (jacquard_common::types::blob::BlobRef<'static>, String)>,
626
current_path: String,
627
ignore_matcher: &'a ignore_patterns::IgnoreMatcher,
628
+
progress: &'a ProgressBar,
629
) -> std::pin::Pin<Box<dyn std::future::Future<Output = miette::Result<(Directory<'static>, usize, usize)>> + 'a>>
630
{
631
Box::pin(async move {
···
673
}
674
}
675
676
+
// Process files concurrently with a limit of 2
677
let file_results: Vec<(Entry<'static>, bool)> = stream::iter(file_tasks)
678
.map(|(name, path, full_path)| async move {
679
+
let (file_node, reused) = process_file(agent, &path, &full_path, existing_blobs, progress).await?;
680
+
progress.inc(1);
681
let entry = Entry::new()
682
.name(CowStr::from(name))
683
.node(EntryNode::File(Box::new(file_node)))
684
.build();
685
Ok::<_, miette::Report>((entry, reused))
686
})
687
+
.buffer_unordered(MAX_CONCURRENT_UPLOADS)
688
.collect::<Vec<_>>()
689
.await
690
.into_iter()
···
711
} else {
712
format!("{}/{}", current_path, name)
713
};
714
+
let (subdir, sub_total, sub_reused) = build_directory(agent, &path, existing_blobs, subdir_path, ignore_matcher, progress).await?;
715
dir_entries.push(Entry::new()
716
.name(CowStr::from(name))
717
.node(EntryNode::Directory(Box::new(subdir)))
···
743
file_path: &Path,
744
file_path_key: &str,
745
existing_blobs: &HashMap<String, (jacquard_common::types::blob::BlobRef<'static>, String)>,
746
+
progress: &ProgressBar,
747
) -> miette::Result<(File<'static>, bool)>
748
{
749
// Read file
···
783
if let Some((existing_blob_ref, existing_cid)) = existing_blob {
784
if existing_cid == &file_cid {
785
// CIDs match - reuse existing blob
786
+
progress.set_message(format!("✓ Reused {}", file_path_key));
787
let mut file_builder = File::new()
788
.r#type(CowStr::from("file"))
789
.blob(existing_blob_ref.clone())
···
797
}
798
799
return Ok((file_builder.build(), true));
800
}
801
}
802
···
807
MimeType::new_static("application/octet-stream")
808
};
809
810
+
// Format file size nicely
811
+
let size_str = if upload_bytes.len() < 1024 {
812
+
format!("{} B", upload_bytes.len())
813
+
} else if upload_bytes.len() < 1024 * 1024 {
814
+
format!("{:.1} KB", upload_bytes.len() as f64 / 1024.0)
815
+
} else {
816
+
format!("{:.1} MB", upload_bytes.len() as f64 / (1024.0 * 1024.0))
817
+
};
818
+
819
+
progress.set_message(format!("↑ Uploading {} ({})", file_path_key, size_str));
820
let blob = agent.upload_blob(upload_bytes, mime_type).await?;
821
+
progress.set_message(format!("✓ Uploaded {}", file_path_key));
822
823
let mut file_builder = File::new()
824
.r#type(CowStr::from("file"))
+1
package.json
+1
package.json
···
20
"build": "cd apps/main-app && bun run build.ts",
21
"build:hosting": "cd apps/hosting-service && npm run build",
22
"build:all": "bun run build && npm run build:hosting",
23
"screenshot": "bun run apps/main-app/scripts/screenshot-sites.ts",
24
"hosting:dev": "cd apps/hosting-service && npm run dev",
25
"hosting:start": "cd apps/hosting-service && npm run start"
···
20
"build": "cd apps/main-app && bun run build.ts",
21
"build:hosting": "cd apps/hosting-service && npm run build",
22
"build:all": "bun run build && npm run build:hosting",
23
+
"check": "cd apps/main-app && npm run check && cd ../hosting-service && npm run check",
24
"screenshot": "bun run apps/main-app/scripts/screenshot-sites.ts",
25
"hosting:dev": "cd apps/hosting-service && npm run dev",
26
"hosting:start": "cd apps/hosting-service && npm run start"
+3
packages/@wisp/atproto-utils/package.json
+3
packages/@wisp/atproto-utils/package.json
+1
-1
packages/@wisp/constants/src/index.ts
+1
-1
packages/@wisp/constants/src/index.ts
+2
-1
packages/@wisp/lexicons/package.json
+2
-1
packages/@wisp/lexicons/package.json