From 332c80d8345e81a9f00bd8d1061b54092a9f7c96 Mon Sep 17 00:00:00 2001 From: Steve Date: Mon, 2 Feb 2026 10:35:34 -0500 Subject: [PATCH] chore: cleaned up types --- packages/cli/src/commands/init.ts | 5 ++- packages/cli/src/commands/login.ts | 1 - packages/cli/src/commands/publish.ts | 2 +- packages/cli/src/commands/sync.ts | 2 +- packages/cli/src/lib/atproto.ts | 64 +++++++++++++++------------- packages/cli/src/lib/credentials.ts | 11 ++--- packages/cli/src/lib/oauth-client.ts | 7 ++- 7 files changed, 49 insertions(+), 43 deletions(-) diff --git a/packages/cli/src/commands/init.ts b/packages/cli/src/commands/init.ts index 894c236..4abdf5b 100644 --- a/packages/cli/src/commands/init.ts +++ b/packages/cli/src/commands/init.ts @@ -287,7 +287,10 @@ export const initCommand = command({ defaultValue: "7", placeholder: "7", validate: (value) => { - const num = parseInt(value, 10); + if (!value) { + return "Please enter a number"; + } + const num = Number.parseInt(value, 10); if (Number.isNaN(num) || num < 1) { return "Please enter a positive number"; } diff --git a/packages/cli/src/commands/login.ts b/packages/cli/src/commands/login.ts index 715d5ca..afe4007 100644 --- a/packages/cli/src/commands/login.ts +++ b/packages/cli/src/commands/login.ts @@ -4,7 +4,6 @@ import { command, flag, option, optional, string } from "cmd-ts"; import { resolveHandleToDid } from "../lib/atproto"; import { getCallbackPort, - getCallbackUrl, getOAuthClient, getOAuthScope, } from "../lib/oauth-client"; diff --git a/packages/cli/src/commands/publish.ts b/packages/cli/src/commands/publish.ts index 5f9b3c8..33baa92 100644 --- a/packages/cli/src/commands/publish.ts +++ b/packages/cli/src/commands/publish.ts @@ -209,7 +209,7 @@ export const publishCommand = command({ let agent: Awaited> | undefined; try { agent = await createAgent(credentials); - s.stop(`Logged in as ${agent.session?.handle}`); + s.stop(`Logged in as ${agent.did}`); } catch (error) { s.stop("Failed to login"); log.error(`Failed to login: ${error}`); diff --git a/packages/cli/src/commands/sync.ts b/packages/cli/src/commands/sync.ts index 9c64376..36d9602 100644 --- a/packages/cli/src/commands/sync.ts +++ b/packages/cli/src/commands/sync.ts @@ -76,7 +76,7 @@ export const syncCommand = command({ let agent: Awaited> | undefined; try { agent = await createAgent(credentials); - s.stop(`Logged in as ${agent.session?.handle}`); + s.stop(`Logged in as ${agent.did}`); } catch (error) { s.stop("Failed to login"); log.error(`Failed to login: ${error}`); diff --git a/packages/cli/src/lib/atproto.ts b/packages/cli/src/lib/atproto.ts index 2f0e258..7149ef5 100644 --- a/packages/cli/src/lib/atproto.ts +++ b/packages/cli/src/lib/atproto.ts @@ -13,6 +13,22 @@ import type { } from "./types"; import { isAppPasswordCredentials, isOAuthCredentials } from "./types"; +/** + * Type guard to check if a record value is a DocumentRecord + */ +function isDocumentRecord(value: unknown): value is DocumentRecord { + if (!value || typeof value !== "object") return false; + const v = value as Record; + return ( + v.$type === "site.standard.document" && + typeof v.title === "string" && + typeof v.site === "string" && + typeof v.path === "string" && + typeof v.textContent === "string" && + typeof v.publishedAt === "string" + ); +} + async function fileExists(filePath: string): Promise { try { await fs.access(filePath); @@ -96,26 +112,14 @@ export interface CreatePublicationOptions { showInDiscover?: boolean; } -export async function createAgent(credentials: Credentials): Promise { +export async function createAgent(credentials: Credentials): Promise { if (isOAuthCredentials(credentials)) { // OAuth flow - restore session from stored tokens const client = await getOAuthClient(); try { const oauthSession = await client.restore(credentials.did); // Wrap the OAuth session in an Agent which provides the atproto API - const agent = new Agent(oauthSession) as unknown as AtpAgent; - - // The Agent class doesn't have session.did like AtpAgent does - // We need to set up a compatible session object for the rest of our code - agent.session = { - did: oauthSession.did, - handle: credentials.handle, - accessJwt: "", - refreshJwt: "", - active: true, - }; - - return agent; + return new Agent(oauthSession); } catch (error) { if (error instanceof Error) { // Check for common OAuth errors @@ -147,7 +151,7 @@ export async function createAgent(credentials: Credentials): Promise { } export async function uploadImage( - agent: AtpAgent, + agent: Agent, imagePath: string, ): Promise { if (!(await fileExists(imagePath))) { @@ -216,7 +220,7 @@ export async function resolveImagePath( } export async function createDocument( - agent: AtpAgent, + agent: Agent, post: BlogPost, config: PublisherConfig, coverImage?: BlobObject, @@ -259,7 +263,7 @@ export async function createDocument( } const response = await agent.com.atproto.repo.createRecord({ - repo: agent.session!.did, + repo: agent.did!, collection: "site.standard.document", record, }); @@ -268,7 +272,7 @@ export async function createDocument( } export async function updateDocument( - agent: AtpAgent, + agent: Agent, post: BlogPost, atUri: string, config: PublisherConfig, @@ -321,7 +325,7 @@ export async function updateDocument( } await agent.com.atproto.repo.putRecord({ - repo: agent.session!.did, + repo: agent.did!, collection: collection!, rkey: rkey!, record, @@ -361,7 +365,7 @@ export interface ListDocumentsResult { } export async function listDocuments( - agent: AtpAgent, + agent: Agent, publicationUri?: string, ): Promise { const documents: ListDocumentsResult[] = []; @@ -369,24 +373,26 @@ export async function listDocuments( do { const response = await agent.com.atproto.repo.listRecords({ - repo: agent.session!.did, + repo: agent.did!, collection: "site.standard.document", limit: 100, cursor, }); for (const record of response.data.records) { - const value = record.value as unknown as DocumentRecord; + if (!isDocumentRecord(record.value)) { + continue; + } // If publicationUri is specified, only include documents from that publication - if (publicationUri && value.site !== publicationUri) { + if (publicationUri && record.value.site !== publicationUri) { continue; } documents.push({ uri: record.uri, cid: record.cid, - value, + value: record.value, }); } @@ -397,7 +403,7 @@ export async function listDocuments( } export async function createPublication( - agent: AtpAgent, + agent: Agent, options: CreatePublicationOptions, ): Promise { let icon: BlobObject | undefined; @@ -428,7 +434,7 @@ export async function createPublication( } const response = await agent.com.atproto.repo.createRecord({ - repo: agent.session!.did, + repo: agent.did!, collection: "site.standard.publication", record, }); @@ -481,7 +487,7 @@ function truncateToGraphemes(str: string, maxGraphemes: number): string { * Create a Bluesky post with external link embed */ export async function createBlueskyPost( - agent: AtpAgent, + agent: Agent, options: CreateBlueskyPostOptions, ): Promise { const { title, description, canonicalUrl, coverImage, publishedAt } = options; @@ -576,7 +582,7 @@ export async function createBlueskyPost( }; const response = await agent.com.atproto.repo.createRecord({ - repo: agent.session!.did, + repo: agent.did!, collection: "app.bsky.feed.post", record, }); @@ -591,7 +597,7 @@ export async function createBlueskyPost( * Add bskyPostRef to an existing document record */ export async function addBskyPostRefToDocument( - agent: AtpAgent, + agent: Agent, documentAtUri: string, bskyPostRef: StrongRef, ): Promise { diff --git a/packages/cli/src/lib/credentials.ts b/packages/cli/src/lib/credentials.ts index bf70cfa..0d1d8f5 100644 --- a/packages/cli/src/lib/credentials.ts +++ b/packages/cli/src/lib/credentials.ts @@ -95,14 +95,9 @@ async function tryLoadOAuthCredentials( } } - // Otherwise, check all OAuth sessions to find a matching handle - // (This is a fallback - handle matching isn't perfect without storing handles) - const sessions = await listOAuthSessions(); - for (const did of sessions) { - // Could enhance this by storing handle with session, but for now - // just return null if profile isn't a DID - } - + // Otherwise, we would need to check all OAuth sessions to find a matching handle, + // but handle matching isn't perfect without storing handles alongside sessions. + // For now, just return null if profile isn't a DID. return null; } diff --git a/packages/cli/src/lib/oauth-client.ts b/packages/cli/src/lib/oauth-client.ts index 66464f1..ba62bf6 100644 --- a/packages/cli/src/lib/oauth-client.ts +++ b/packages/cli/src/lib/oauth-client.ts @@ -18,7 +18,10 @@ let oauthClient: NodeOAuthClient | null = null; // This prevents the "No lock mechanism provided" warning const locks = new Map>(); -async function requestLock(key: string, fn: () => Promise): Promise { +async function requestLock( + key: string, + fn: () => T | PromiseLike, +): Promise { // Wait for any existing lock on this key while (locks.has(key)) { await locks.get(key); @@ -32,7 +35,7 @@ async function requestLock(key: string, fn: () => Promise): Promise locks.set(key, lockPromise); try { - await fn(); + return await fn(); } finally { locks.delete(key); resolve!(); -- 2.51.2