···287287 defaultValue: "7",
288288 placeholder: "7",
289289 validate: (value) => {
290290- const num = parseInt(value, 10);
290290+ if (!value) {
291291+ return "Please enter a number";
292292+ }
293293+ const num = Number.parseInt(value, 10);
291294 if (Number.isNaN(num) || num < 1) {
292295 return "Please enter a positive number";
293296 }
-1
packages/cli/src/commands/login.ts
···44import { resolveHandleToDid } from "../lib/atproto";
55import {
66 getCallbackPort,
77- getCallbackUrl,
87 getOAuthClient,
98 getOAuthScope,
109} from "../lib/oauth-client";
+1-1
packages/cli/src/commands/publish.ts
···209209 let agent: Awaited<ReturnType<typeof createAgent>> | undefined;
210210 try {
211211 agent = await createAgent(credentials);
212212- s.stop(`Logged in as ${agent.session?.handle}`);
212212+ s.stop(`Logged in as ${agent.did}`);
213213 } catch (error) {
214214 s.stop("Failed to login");
215215 log.error(`Failed to login: ${error}`);
+1-1
packages/cli/src/commands/sync.ts
···7676 let agent: Awaited<ReturnType<typeof createAgent>> | undefined;
7777 try {
7878 agent = await createAgent(credentials);
7979- s.stop(`Logged in as ${agent.session?.handle}`);
7979+ s.stop(`Logged in as ${agent.did}`);
8080 } catch (error) {
8181 s.stop("Failed to login");
8282 log.error(`Failed to login: ${error}`);
+35-29
packages/cli/src/lib/atproto.ts
···1313} from "./types";
1414import { isAppPasswordCredentials, isOAuthCredentials } from "./types";
15151616+/**
1717+ * Type guard to check if a record value is a DocumentRecord
1818+ */
1919+function isDocumentRecord(value: unknown): value is DocumentRecord {
2020+ if (!value || typeof value !== "object") return false;
2121+ const v = value as Record<string, unknown>;
2222+ return (
2323+ v.$type === "site.standard.document" &&
2424+ typeof v.title === "string" &&
2525+ typeof v.site === "string" &&
2626+ typeof v.path === "string" &&
2727+ typeof v.textContent === "string" &&
2828+ typeof v.publishedAt === "string"
2929+ );
3030+}
3131+1632async function fileExists(filePath: string): Promise<boolean> {
1733 try {
1834 await fs.access(filePath);
···96112 showInDiscover?: boolean;
97113}
981149999-export async function createAgent(credentials: Credentials): Promise<AtpAgent> {
115115+export async function createAgent(credentials: Credentials): Promise<Agent> {
100116 if (isOAuthCredentials(credentials)) {
101117 // OAuth flow - restore session from stored tokens
102118 const client = await getOAuthClient();
103119 try {
104120 const oauthSession = await client.restore(credentials.did);
105121 // Wrap the OAuth session in an Agent which provides the atproto API
106106- const agent = new Agent(oauthSession) as unknown as AtpAgent;
107107-108108- // The Agent class doesn't have session.did like AtpAgent does
109109- // We need to set up a compatible session object for the rest of our code
110110- agent.session = {
111111- did: oauthSession.did,
112112- handle: credentials.handle,
113113- accessJwt: "",
114114- refreshJwt: "",
115115- active: true,
116116- };
117117-118118- return agent;
122122+ return new Agent(oauthSession);
119123 } catch (error) {
120124 if (error instanceof Error) {
121125 // Check for common OAuth errors
···147151}
148152149153export async function uploadImage(
150150- agent: AtpAgent,
154154+ agent: Agent,
151155 imagePath: string,
152156): Promise<BlobObject | undefined> {
153157 if (!(await fileExists(imagePath))) {
···216220}
217221218222export async function createDocument(
219219- agent: AtpAgent,
223223+ agent: Agent,
220224 post: BlogPost,
221225 config: PublisherConfig,
222226 coverImage?: BlobObject,
···259263 }
260264261265 const response = await agent.com.atproto.repo.createRecord({
262262- repo: agent.session!.did,
266266+ repo: agent.did!,
263267 collection: "site.standard.document",
264268 record,
265269 });
···268272}
269273270274export async function updateDocument(
271271- agent: AtpAgent,
275275+ agent: Agent,
272276 post: BlogPost,
273277 atUri: string,
274278 config: PublisherConfig,
···321325 }
322326323327 await agent.com.atproto.repo.putRecord({
324324- repo: agent.session!.did,
328328+ repo: agent.did!,
325329 collection: collection!,
326330 rkey: rkey!,
327331 record,
···361365}
362366363367export async function listDocuments(
364364- agent: AtpAgent,
368368+ agent: Agent,
365369 publicationUri?: string,
366370): Promise<ListDocumentsResult[]> {
367371 const documents: ListDocumentsResult[] = [];
···369373370374 do {
371375 const response = await agent.com.atproto.repo.listRecords({
372372- repo: agent.session!.did,
376376+ repo: agent.did!,
373377 collection: "site.standard.document",
374378 limit: 100,
375379 cursor,
376380 });
377381378382 for (const record of response.data.records) {
379379- const value = record.value as unknown as DocumentRecord;
383383+ if (!isDocumentRecord(record.value)) {
384384+ continue;
385385+ }
380386381387 // If publicationUri is specified, only include documents from that publication
382382- if (publicationUri && value.site !== publicationUri) {
388388+ if (publicationUri && record.value.site !== publicationUri) {
383389 continue;
384390 }
385391386392 documents.push({
387393 uri: record.uri,
388394 cid: record.cid,
389389- value,
395395+ value: record.value,
390396 });
391397 }
392398···397403}
398404399405export async function createPublication(
400400- agent: AtpAgent,
406406+ agent: Agent,
401407 options: CreatePublicationOptions,
402408): Promise<string> {
403409 let icon: BlobObject | undefined;
···428434 }
429435430436 const response = await agent.com.atproto.repo.createRecord({
431431- repo: agent.session!.did,
437437+ repo: agent.did!,
432438 collection: "site.standard.publication",
433439 record,
434440 });
···481487 * Create a Bluesky post with external link embed
482488 */
483489export async function createBlueskyPost(
484484- agent: AtpAgent,
490490+ agent: Agent,
485491 options: CreateBlueskyPostOptions,
486492): Promise<StrongRef> {
487493 const { title, description, canonicalUrl, coverImage, publishedAt } = options;
···576582 };
577583578584 const response = await agent.com.atproto.repo.createRecord({
579579- repo: agent.session!.did,
585585+ repo: agent.did!,
580586 collection: "app.bsky.feed.post",
581587 record,
582588 });
···591597 * Add bskyPostRef to an existing document record
592598 */
593599export async function addBskyPostRefToDocument(
594594- agent: AtpAgent,
600600+ agent: Agent,
595601 documentAtUri: string,
596602 bskyPostRef: StrongRef,
597603): Promise<void> {
+3-8
packages/cli/src/lib/credentials.ts
···9595 }
9696 }
97979898- // Otherwise, check all OAuth sessions to find a matching handle
9999- // (This is a fallback - handle matching isn't perfect without storing handles)
100100- const sessions = await listOAuthSessions();
101101- for (const did of sessions) {
102102- // Could enhance this by storing handle with session, but for now
103103- // just return null if profile isn't a DID
104104- }
105105-9898+ // Otherwise, we would need to check all OAuth sessions to find a matching handle,
9999+ // but handle matching isn't perfect without storing handles alongside sessions.
100100+ // For now, just return null if profile isn't a DID.
106101 return null;
107102}
108103
+5-2
packages/cli/src/lib/oauth-client.ts
···1818// This prevents the "No lock mechanism provided" warning
1919const locks = new Map<string, Promise<void>>();
20202121-async function requestLock(key: string, fn: () => Promise<void>): Promise<void> {
2121+async function requestLock<T>(
2222+ key: string,
2323+ fn: () => T | PromiseLike<T>,
2424+): Promise<T> {
2225 // Wait for any existing lock on this key
2326 while (locks.has(key)) {
2427 await locks.get(key);
···3235 locks.set(key, lockPromise);
33363437 try {
3535- await fn();
3838+ return await fn();
3639 } finally {
3740 locks.delete(key);
3841 resolve!();