chore: cleaned up types 332c80d8
Steve · 2026-02-02 10:35 7 file(s) · +49 −43
packages/cli/src/commands/init.ts +4 −1
287 287
				defaultValue: "7",
288 288
				placeholder: "7",
289 289
				validate: (value) => {
290 -
					const num = parseInt(value, 10);
290 +
					if (!value) {
291 +
						return "Please enter a number";
292 +
					}
293 +
					const num = Number.parseInt(value, 10);
291 294
					if (Number.isNaN(num) || num < 1) {
292 295
						return "Please enter a positive number";
293 296
					}
packages/cli/src/commands/login.ts +0 −1
4 4
import { resolveHandleToDid } from "../lib/atproto";
5 5
import {
6 6
	getCallbackPort,
7 -
	getCallbackUrl,
8 7
	getOAuthClient,
9 8
	getOAuthScope,
10 9
} from "../lib/oauth-client";
packages/cli/src/commands/publish.ts +1 −1
209 209
		let agent: Awaited<ReturnType<typeof createAgent>> | undefined;
210 210
		try {
211 211
			agent = await createAgent(credentials);
212 -
			s.stop(`Logged in as ${agent.session?.handle}`);
212 +
			s.stop(`Logged in as ${agent.did}`);
213 213
		} catch (error) {
214 214
			s.stop("Failed to login");
215 215
			log.error(`Failed to login: ${error}`);
packages/cli/src/commands/sync.ts +1 −1
76 76
		let agent: Awaited<ReturnType<typeof createAgent>> | undefined;
77 77
		try {
78 78
			agent = await createAgent(credentials);
79 -
			s.stop(`Logged in as ${agent.session?.handle}`);
79 +
			s.stop(`Logged in as ${agent.did}`);
80 80
		} catch (error) {
81 81
			s.stop("Failed to login");
82 82
			log.error(`Failed to login: ${error}`);
packages/cli/src/lib/atproto.ts +35 −29
13 13
} from "./types";
14 14
import { isAppPasswordCredentials, isOAuthCredentials } from "./types";
15 15
16 +
/**
17 +
 * Type guard to check if a record value is a DocumentRecord
18 +
 */
19 +
function isDocumentRecord(value: unknown): value is DocumentRecord {
20 +
	if (!value || typeof value !== "object") return false;
21 +
	const v = value as Record<string, unknown>;
22 +
	return (
23 +
		v.$type === "site.standard.document" &&
24 +
		typeof v.title === "string" &&
25 +
		typeof v.site === "string" &&
26 +
		typeof v.path === "string" &&
27 +
		typeof v.textContent === "string" &&
28 +
		typeof v.publishedAt === "string"
29 +
	);
30 +
}
31 +
16 32
async function fileExists(filePath: string): Promise<boolean> {
17 33
	try {
18 34
		await fs.access(filePath);
96 112
	showInDiscover?: boolean;
97 113
}
98 114
99 -
export async function createAgent(credentials: Credentials): Promise<AtpAgent> {
115 +
export async function createAgent(credentials: Credentials): Promise<Agent> {
100 116
	if (isOAuthCredentials(credentials)) {
101 117
		// OAuth flow - restore session from stored tokens
102 118
		const client = await getOAuthClient();
103 119
		try {
104 120
			const oauthSession = await client.restore(credentials.did);
105 121
			// Wrap the OAuth session in an Agent which provides the atproto API
106 -
			const agent = new Agent(oauthSession) as unknown as AtpAgent;
107 -
108 -
			// The Agent class doesn't have session.did like AtpAgent does
109 -
			// We need to set up a compatible session object for the rest of our code
110 -
			agent.session = {
111 -
				did: oauthSession.did,
112 -
				handle: credentials.handle,
113 -
				accessJwt: "",
114 -
				refreshJwt: "",
115 -
				active: true,
116 -
			};
117 -
118 -
			return agent;
122 +
			return new Agent(oauthSession);
119 123
		} catch (error) {
120 124
			if (error instanceof Error) {
121 125
				// Check for common OAuth errors
147 151
}
148 152
149 153
export async function uploadImage(
150 -
	agent: AtpAgent,
154 +
	agent: Agent,
151 155
	imagePath: string,
152 156
): Promise<BlobObject | undefined> {
153 157
	if (!(await fileExists(imagePath))) {
216 220
}
217 221
218 222
export async function createDocument(
219 -
	agent: AtpAgent,
223 +
	agent: Agent,
220 224
	post: BlogPost,
221 225
	config: PublisherConfig,
222 226
	coverImage?: BlobObject,
259 263
	}
260 264
261 265
	const response = await agent.com.atproto.repo.createRecord({
262 -
		repo: agent.session!.did,
266 +
		repo: agent.did!,
263 267
		collection: "site.standard.document",
264 268
		record,
265 269
	});
268 272
}
269 273
270 274
export async function updateDocument(
271 -
	agent: AtpAgent,
275 +
	agent: Agent,
272 276
	post: BlogPost,
273 277
	atUri: string,
274 278
	config: PublisherConfig,
321 325
	}
322 326
323 327
	await agent.com.atproto.repo.putRecord({
324 -
		repo: agent.session!.did,
328 +
		repo: agent.did!,
325 329
		collection: collection!,
326 330
		rkey: rkey!,
327 331
		record,
361 365
}
362 366
363 367
export async function listDocuments(
364 -
	agent: AtpAgent,
368 +
	agent: Agent,
365 369
	publicationUri?: string,
366 370
): Promise<ListDocumentsResult[]> {
367 371
	const documents: ListDocumentsResult[] = [];
369 373
370 374
	do {
371 375
		const response = await agent.com.atproto.repo.listRecords({
372 -
			repo: agent.session!.did,
376 +
			repo: agent.did!,
373 377
			collection: "site.standard.document",
374 378
			limit: 100,
375 379
			cursor,
376 380
		});
377 381
378 382
		for (const record of response.data.records) {
379 -
			const value = record.value as unknown as DocumentRecord;
383 +
			if (!isDocumentRecord(record.value)) {
384 +
				continue;
385 +
			}
380 386
381 387
			// If publicationUri is specified, only include documents from that publication
382 -
			if (publicationUri && value.site !== publicationUri) {
388 +
			if (publicationUri && record.value.site !== publicationUri) {
383 389
				continue;
384 390
			}
385 391
386 392
			documents.push({
387 393
				uri: record.uri,
388 394
				cid: record.cid,
389 -
				value,
395 +
				value: record.value,
390 396
			});
391 397
		}
392 398
397 403
}
398 404
399 405
export async function createPublication(
400 -
	agent: AtpAgent,
406 +
	agent: Agent,
401 407
	options: CreatePublicationOptions,
402 408
): Promise<string> {
403 409
	let icon: BlobObject | undefined;
428 434
	}
429 435
430 436
	const response = await agent.com.atproto.repo.createRecord({
431 -
		repo: agent.session!.did,
437 +
		repo: agent.did!,
432 438
		collection: "site.standard.publication",
433 439
		record,
434 440
	});
481 487
 * Create a Bluesky post with external link embed
482 488
 */
483 489
export async function createBlueskyPost(
484 -
	agent: AtpAgent,
490 +
	agent: Agent,
485 491
	options: CreateBlueskyPostOptions,
486 492
): Promise<StrongRef> {
487 493
	const { title, description, canonicalUrl, coverImage, publishedAt } = options;
576 582
	};
577 583
578 584
	const response = await agent.com.atproto.repo.createRecord({
579 -
		repo: agent.session!.did,
585 +
		repo: agent.did!,
580 586
		collection: "app.bsky.feed.post",
581 587
		record,
582 588
	});
591 597
 * Add bskyPostRef to an existing document record
592 598
 */
593 599
export async function addBskyPostRefToDocument(
594 -
	agent: AtpAgent,
600 +
	agent: Agent,
595 601
	documentAtUri: string,
596 602
	bskyPostRef: StrongRef,
597 603
): Promise<void> {
packages/cli/src/lib/credentials.ts +3 −8
95 95
		}
96 96
	}
97 97
98 -
	// Otherwise, check all OAuth sessions to find a matching handle
99 -
	// (This is a fallback - handle matching isn't perfect without storing handles)
100 -
	const sessions = await listOAuthSessions();
101 -
	for (const did of sessions) {
102 -
		// Could enhance this by storing handle with session, but for now
103 -
		// just return null if profile isn't a DID
104 -
	}
105 -
98 +
	// Otherwise, we would need to check all OAuth sessions to find a matching handle,
99 +
	// but handle matching isn't perfect without storing handles alongside sessions.
100 +
	// For now, just return null if profile isn't a DID.
106 101
	return null;
107 102
}
108 103
packages/cli/src/lib/oauth-client.ts +5 −2
18 18
// This prevents the "No lock mechanism provided" warning
19 19
const locks = new Map<string, Promise<void>>();
20 20
21 -
async function requestLock(key: string, fn: () => Promise<void>): Promise<void> {
21 +
async function requestLock<T>(
22 +
	key: string,
23 +
	fn: () => T | PromiseLike<T>,
24 +
): Promise<T> {
22 25
	// Wait for any existing lock on this key
23 26
	while (locks.has(key)) {
24 27
		await locks.get(key);
32 35
	locks.set(key, lockPromise);
33 36
34 37
	try {
35 -
		await fn();
38 +
		return await fn();
36 39
	} finally {
37 40
		locks.delete(key);
38 41
		resolve!();