Monorepo for wisp.place. A static site hosting service built on top of the AT Protocol.
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

import napi on runtime and be optional dep for ci

+56 -20
+3
build-cli.sh
··· 20 20 bun build --compile --minify --target=bun-linux-x64 "$ENTRY" --outfile "$OUTDIR/wisp-cli-x86_64-linux" 21 21 echo " ✓ x86_64-linux" 22 22 23 + bun build --compile --minify --target=bun-windows-x64 "$ENTRY" --outfile "$OUTDIR/wisp-cli-x86_64-windows.exe" 24 + echo " ✓ x86_64-windows" 25 + 23 26 lipo -create -output "$OUTDIR/wisp-cli-darwin-universal" \ 24 27 "$OUTDIR/wisp-cli-aarch64-darwin" \ 25 28 "$OUTDIR/wisp-cli-x86_64-darwin"
+8 -5
bun.lock
··· 5 5 "": { 6 6 "name": "@wisp/monorepo", 7 7 "dependencies": { 8 - "@napi-rs/keyring": "^1.2.0", 9 8 "@tailwindcss/cli": "^4.1.17", 10 9 "bun-plugin-tailwind": "^0.1.2", 11 10 "node-html-parser": "^7.1.0", ··· 166 165 "bin": { 167 166 "wispctl": "dist/index.js", 168 167 }, 169 - "dependencies": { 170 - "@napi-rs/keyring": "^1.2.0", 171 - }, 172 168 "devDependencies": { 173 169 "@atproto/api": "^0.18.17", 174 170 "@atproto/identity": "^0.4.10", ··· 198 194 "open": "^11.0.0", 199 195 "picocolors": "^1.1.1", 200 196 "typescript": "^5", 197 + }, 198 + "optionalDependencies": { 199 + "@napi-rs/keyring": "^1.2.0", 201 200 }, 202 201 }, 203 202 "packages/@wispplace/atproto-utils": { ··· 2167 2166 2168 2167 "wisp-hosting-service/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], 2169 2168 2170 - "wispctl/@types/bun": ["@types/bun@1.3.10", "", { "dependencies": { "bun-types": "1.3.10" } }, "sha512-0+rlrUrOrTSskibryHbvQkDOWRJwJZqZlxrUs1u4oOoTln8+WIXBPmAuCF35SWB2z4Zl3E84Nl/D0P7803nigQ=="], 2169 + "wispctl/@types/bun": ["@types/bun@1.3.11", "", { "dependencies": { "bun-types": "1.3.11" } }, "sha512-5vPne5QvtpjGpsGYXiFyycfpDF2ECyPcTSsFBMa0fraoxiQyMJ3SmuQIGhzPg2WJuWxVBoxWJ2kClYTcw/4fAg=="], 2171 2170 2172 2171 "wispctl/@types/node": ["@types/node@22.19.7", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw=="], 2173 2172 ··· 2433 2432 2434 2433 "wisp-hosting-service/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], 2435 2434 2435 + "wispctl/@types/bun/bun-types": ["bun-types@1.3.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-1KGPpoxQWl9f6wcZh57LvrPIInQMn2TQ7jsgxqpRzg+l0QPOFvJVH7HmvHo/AiPgwXy+/Thf6Ov3EdVn1vOabg=="], 2436 + 2436 2437 "wispctl/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], 2437 2438 2438 2439 "@atproto/lex-cli/@atproto/lexicon/@atproto/common-web/@atproto/lex-json": ["@atproto/lex-json@0.0.9", "", { "dependencies": { "@atproto/lex-data": "0.0.9", "tslib": "^2.8.1" } }, "sha512-Q2v1EVZcnd+ndyZj1r2UlGikA7q6It24CFPLbxokcf5Ba4RBupH8IkkQX7mqUDSRWPgQdmZYIdW9wUln+MKDqw=="], ··· 2480 2481 "@opentelemetry/sdk-node/@opentelemetry/exporter-metrics-otlp-proto/@opentelemetry/otlp-transformer/protobufjs": ["protobufjs@7.5.4", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg=="], 2481 2482 2482 2483 "@types/bun/bun-types/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], 2484 + 2485 + "wispctl/@types/bun/bun-types/@types/node": ["@types/node@24.10.10", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-+0/4J266CBGPUq/ELg7QUHhN25WYjE0wYTPSQJn1xeu8DOlIOPxXxrNGiLmfAWl7HMMgWFWXpt9IDjMWrF5Iow=="], 2483 2486 2484 2487 "@atproto/sync/@atproto/xrpc-server/@atproto/ws-client/@atproto/common/@atproto/common-web": ["@atproto/common-web@0.4.13", "", { "dependencies": { "@atproto/lex-data": "0.0.9", "@atproto/lex-json": "0.0.9", "@atproto/syntax": "0.4.3", "zod": "^3.23.8" } }, "sha512-TewRUyB/dVJ5PtI3QmJzEgT3wDsvpnLJ+48hPl+LuUueJPamZevXKJN6dFjtbKAMFRnl2bKfdsf79qwvdSaLKQ=="], 2485 2488
+44 -13
cli/lib/auth.ts
··· 13 13 } from '@atproto/oauth-client-node' 14 14 import { confirm, log } from '@clack/prompts' 15 15 import { serve as honoNodeServe } from '@hono/node-server' 16 - import { Entry as KeyringEntry } from '@napi-rs/keyring' 17 16 import { resolvePdsFromHandle } from '@wispplace/atproto-utils' 18 17 import { isBun } from '@wispplace/bun-firehose' 19 18 import { Hono } from 'hono' ··· 22 21 23 22 const KEYCHAIN_SERVICE = 'wispctl' 24 23 25 - function probeKeychain(): boolean { 24 + interface KeyringEntryLike { 25 + setPassword(password: string): void 26 + getPassword(): string | null 27 + deletePassword(): void 28 + } 29 + 30 + type KeyringEntryConstructor = new (service: string, account: string) => KeyringEntryLike 31 + 32 + let keyringEntryConstructor: KeyringEntryConstructor | null | undefined 33 + 34 + async function getKeyringEntryConstructor(): Promise<KeyringEntryConstructor | null> { 35 + if (keyringEntryConstructor !== undefined) { 36 + return keyringEntryConstructor 37 + } 38 + try { 39 + const module = await import('@napi-rs/keyring') 40 + keyringEntryConstructor = module.Entry as KeyringEntryConstructor 41 + } catch { 42 + keyringEntryConstructor = null 43 + } 44 + return keyringEntryConstructor 45 + } 46 + 47 + async function probeKeychain(): Promise<boolean> { 48 + const KeyringEntry = await getKeyringEntryConstructor() 49 + if (!KeyringEntry) return false 50 + 26 51 const testKey = '__wispctl_probe__' 27 52 try { 28 53 const entry = new KeyringEntry(KEYCHAIN_SERVICE, testKey) ··· 139 164 } 140 165 } 141 166 142 - function createSessionStore(kv: KvAdapter, useKeychain: boolean): NodeSavedSessionStore { 143 - if (useKeychain) { 167 + function createSessionStore( 168 + kv: KvAdapter, 169 + keyringEntryConstructor: KeyringEntryConstructor | null, 170 + ): NodeSavedSessionStore { 171 + if (keyringEntryConstructor) { 144 172 return { 145 173 async set(sub, session) { 146 - new KeyringEntry(KEYCHAIN_SERVICE, sub).setPassword(JSON.stringify(session)) 174 + new keyringEntryConstructor(KEYCHAIN_SERVICE, sub).setPassword(JSON.stringify(session)) 147 175 }, 148 176 async get(sub) { 149 177 try { 150 - const raw = new KeyringEntry(KEYCHAIN_SERVICE, sub).getPassword() 178 + const raw = new keyringEntryConstructor(KEYCHAIN_SERVICE, sub).getPassword() 151 179 if (!raw) return undefined 152 180 return JSON.parse(raw) as NodeSavedSession 153 181 } catch { ··· 156 184 }, 157 185 async del(sub) { 158 186 try { 159 - new KeyringEntry(KEYCHAIN_SERVICE, sub).deletePassword() 187 + new keyringEntryConstructor(KEYCHAIN_SERVICE, sub).deletePassword() 160 188 } catch {} 161 189 }, 162 190 } ··· 219 247 ): Promise<{ agent: Agent; did: string }> { 220 248 const kv = await openKv(options.dbPath || DEFAULT_DB_PATH) 221 249 222 - const useKeychain = probeKeychain() 250 + const useKeychain = await probeKeychain() 223 251 if (!useKeychain) { 224 252 log.warn('System keychain is unavailable (no Secret Service daemon or equivalent).') 225 253 const fallback = await confirm({ ··· 251 279 dpop_bound_access_tokens: false, 252 280 }, 253 281 stateStore: createStateStore(kv), 254 - sessionStore: createSessionStore(kv, useKeychain), 282 + sessionStore: createSessionStore(kv, useKeychain ? await getKeyringEntryConstructor() : null), 255 283 requestLock: requestLocalLock, 256 284 }) 257 285 ··· 478 506 const kv = await openKv(dbPath || DEFAULT_DB_PATH) 479 507 // Delete any keychain entries for DIDs we know about via dir mappings 480 508 const dids = kv.valuesByPrefix('dir:') 481 - for (const did of dids) { 482 - try { 483 - new KeyringEntry(KEYCHAIN_SERVICE, did).deletePassword() 484 - } catch {} 509 + const KeyringEntry = await getKeyringEntryConstructor() 510 + if (KeyringEntry) { 511 + for (const did of dids) { 512 + try { 513 + new KeyringEntry(KEYCHAIN_SERVICE, did).deletePassword() 514 + } catch {} 515 + } 485 516 } 486 517 kv.clear() 487 518 console.log('Cleared all stored OAuth sessions')
+1 -1
cli/package.json
··· 45 45 "typecheck": "tsc --noEmit" 46 46 }, 47 47 "type": "module", 48 - "dependencies": { 48 + "optionalDependencies": { 49 49 "@napi-rs/keyring": "^1.2.0" 50 50 } 51 51 }
-1
package.json
··· 11 11 "cli" 12 12 ], 13 13 "dependencies": { 14 - "@napi-rs/keyring": "^1.2.0", 15 14 "@tailwindcss/cli": "^4.1.17", 16 15 "bun-plugin-tailwind": "^0.1.2", 17 16 "node-html-parser": "^7.1.0",