···73737474function toUnixPath(p: string): string {
7575 // A backslash is a path separator on Windows
7676- if (process.platform === "win32") {
7777- return p.replace(/\\/g, "/")
7676+ if (process.platform === 'win32') {
7777+ return p.replace(/\\/g, '/')
7878 }
7979 // on Unix systems, you're typically allowed to have backslashes in file names
8080 return p
···513513 // Check individual file sizes
514514 for (const file of files) {
515515 if (file.size > MAX_FILE_SIZE) {
516516- console.log(pc.yellow(`\nWarning: ${file.relativePath} exceeds max size (${formatBytes(file.size)} > ${formatBytes(MAX_FILE_SIZE)})`))
516516+ console.log(
517517+ pc.yellow(
518518+ `\nWarning: ${file.relativePath} exceeds max size (${formatBytes(file.size)} > ${formatBytes(MAX_FILE_SIZE)})`,
519519+ ),
520520+ )
517521 console.log(pc.yellow('This file may not be cached by the hosting service.\n'))
518522 }
519523 }
+28-31
cli/lib/auth.ts
···1111 type NodeSavedStateStore,
1212 requestLocalLock,
1313} from '@atproto/oauth-client-node'
1414+import { confirm, log } from '@clack/prompts'
1415import { serve as honoNodeServe } from '@hono/node-server'
1616+import { Entry as KeyringEntry } from '@napi-rs/keyring'
1517import { resolvePdsFromHandle } from '@wispplace/atproto-utils'
1618import { isBun } from '@wispplace/bun-firehose'
1717-import { Entry as KeyringEntry } from '@napi-rs/keyring'
1818-import { confirm, log } from '@clack/prompts'
1919import { Hono } from 'hono'
2020import open from 'open'
2121+import { WISP_OAUTH_SCOPE } from './wisp-service'
21222223const KEYCHAIN_SERVICE = 'wispctl'
2324···3233 return false
3334 }
3435}
3535-3636-// All scopes requested upfront so the client_id is stable across commands
3737-const OAUTH_SCOPE = [
3838- 'atproto',
3939- 'repo:place.wisp.fs',
4040- 'repo:place.wisp.subfs',
4141- 'repo:place.wisp.settings',
4242- 'blob:*/*',
4343- 'rpc:place.wisp.v2.site.getList?aud=*',
4444- 'rpc:place.wisp.v2.site.delete?aud=*',
4545- 'rpc:place.wisp.v2.domain.getList?aud=*',
4646- 'rpc:place.wisp.v2.domain.claim?aud=*',
4747- 'rpc:place.wisp.v2.domain.claimSubdomain?aud=*',
4848- 'rpc:place.wisp.v2.domain.getStatus?aud=*',
4949- 'rpc:place.wisp.v2.domain.addSite?aud=*',
5050- 'rpc:place.wisp.v2.domain.delete?aud=*',
5151-].join(' ')
52365337const DEFAULT_DB_PATH = join(homedir(), '.config', 'wispctl', 'state.sqlite')
5438···9276 const prefixStmt = db.query<{ value: string }, [string]>('SELECT value FROM kv WHERE key LIKE ?')
9377 return {
9478 get: (key) => getStmt.get(key) ?? undefined,
9595- set: (key, value, expiresAt) => { setStmt.run(key, value, expiresAt) },
9696- del: (key) => { delStmt.run(key) },
7979+ set: (key, value, expiresAt) => {
8080+ setStmt.run(key, value, expiresAt)
8181+ },
8282+ del: (key) => {
8383+ delStmt.run(key)
8484+ },
9785 clear: () => db.run('DELETE FROM kv'),
9886 valuesByPrefix: (prefix) => prefixStmt.all(`${prefix}%`).map((r) => r.value),
9987 }
···10896 const prefixStmt = db.prepare('SELECT value FROM kv WHERE key LIKE ?')
10997 return {
11098 get: (key) => getStmt.get(key) as KvRow | undefined,
111111- set: (key, value, expiresAt) => { setStmt.run(key, value, expiresAt) },
112112- del: (key) => { delStmt.run(key) },
9999+ set: (key, value, expiresAt) => {
100100+ setStmt.run(key, value, expiresAt)
101101+ },
102102+ del: (key) => {
103103+ delStmt.run(key)
104104+ },
113105 clear: () => db.exec('DELETE FROM kv'),
114106 valuesByPrefix: (prefix) => (prefixStmt.all(`${prefix}%`) as { value: string }[]).map((r) => r.value),
115107 }
···163155 }
164156 },
165157 async del(sub) {
166166- try { new KeyringEntry(KEYCHAIN_SERVICE, sub).deletePassword() } catch {}
158158+ try {
159159+ new KeyringEntry(KEYCHAIN_SERVICE, sub).deletePassword()
160160+ } catch {}
167161 },
168162 }
169163 }
···225219): Promise<{ agent: Agent; did: string }> {
226220 const kv = await openKv(options.dbPath || DEFAULT_DB_PATH)
227221228228- let useKeychain = probeKeychain()
222222+ const useKeychain = probeKeychain()
229223 if (!useKeychain) {
230224 log.warn('System keychain is unavailable (no Secret Service daemon or equivalent).')
231225 const fallback = await confirm({
232232- message: 'Fall back to storing session tokens unencrypted in SQLite? (On headless systems, prefer --password instead.)',
226226+ message:
227227+ 'Fall back to storing session tokens unencrypted in SQLite? (On headless systems, prefer --password instead.)',
233228 initialValue: false,
234229 })
235230 if (!fallback) {
···240235 const redirectUri = `http://${LOOPBACK_HOST}:${LOOPBACK_PORT}/oauth/callback`
241236 const clientIdParams = new URLSearchParams()
242237 clientIdParams.append('redirect_uri', redirectUri)
243243- clientIdParams.append('scope', OAUTH_SCOPE)
238238+ clientIdParams.append('scope', WISP_OAUTH_SCOPE)
244239245240 const client = new NodeOAuthClient({
246241 clientMetadata: {
···252247 response_types: ['code'],
253248 application_type: 'web',
254249 token_endpoint_auth_method: 'none',
255255- scope: OAUTH_SCOPE,
250250+ scope: WISP_OAUTH_SCOPE,
256251 dpop_bound_access_tokens: false,
257252 },
258253 stateStore: createStateStore(kv),
···393388 }
394389 })
395390396396- const authUrl = await client.authorize(handle, { scope: OAUTH_SCOPE })
391391+ const authUrl = await client.authorize(handle, { scope: WISP_OAUTH_SCOPE })
397392398393 emitStatus(options, 'Opening browser for authentication...')
399394 emitStatus(options, `If browser does not open, visit: ${authUrl}`)
···404399405400 const tokenInfo = await session.getTokenInfo(false)
406401 const grantedScopes = new Set((tokenInfo.scope || '').split(/\s+/).filter(Boolean))
407407- const missingScopes = OAUTH_SCOPE.split(' ').filter((s) => !grantedScopes.has(decodeURIComponent(s)))
402402+ const missingScopes = WISP_OAUTH_SCOPE.split(' ').filter((s) => !grantedScopes.has(decodeURIComponent(s)))
408403 if (missingScopes.length > 0) {
409404 emitWarning(
410405 options,
···484479 // Delete any keychain entries for DIDs we know about via dir mappings
485480 const dids = kv.valuesByPrefix('dir:')
486481 for (const did of dids) {
487487- try { new KeyringEntry(KEYCHAIN_SERVICE, did).deletePassword() } catch {}
482482+ try {
483483+ new KeyringEntry(KEYCHAIN_SERVICE, did).deletePassword()
484484+ } catch {}
488485 }
489486 kv.clear()
490487 console.log('Cleared all stored OAuth sessions')