Suite of AT Protocol TypeScript libraries built on web standards
21
fork

Configure Feed

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

remove cli

-391
-14
cli/deno.json
··· 1 - { 2 - "name": "@atp/cli", 3 - "version": "0.1.0-alpha.1", 4 - "exports": "./mod.ts", 5 - "license": "MIT", 6 - "imports": { 7 - "@atproto/api": "npm:@atproto/api@^0.18.0", 8 - "@cliffy/command": "jsr:@cliffy/command@^1.0.0-rc.8", 9 - "@cliffy/prompt": "jsr:@cliffy/prompt@^1.0.0-rc.8" 10 - }, 11 - "tasks": { 12 - "start": "deno run --allow-read --allow-write --allow-env mod.ts" 13 - } 14 - }
-330
cli/get.ts
··· 1 - import { AtUri } from "@atp/syntax"; 2 - import { IdResolver } from "@atp/identity"; 3 - import { XrpcClient } from "@atp/xrpc"; 4 - import { prompt, Select } from "@cliffy/prompt"; 5 - import { lexicons } from "@atproto/api"; 6 - import process from "node:process"; 7 - 8 - const isDeno = typeof Deno !== "undefined"; 9 - 10 - interface RecordInfo { 11 - uri: string; 12 - value?: Record<string, unknown>; 13 - } 14 - 15 - interface PromptState { 16 - collection?: string; 17 - rkey?: string; 18 - collections?: string[]; 19 - records?: RecordInfo[]; 20 - recordAlreadyShown: boolean; 21 - } 22 - 23 - async function resolveDid( 24 - input: string, 25 - idResolver: IdResolver, 26 - ): Promise<string> { 27 - if (!input.startsWith("did:")) { 28 - const handleResolution = await idResolver.handle.resolve(input); 29 - if (!handleResolution) { 30 - throw new Error(`Could not resolve handle: ${input}`); 31 - } 32 - return handleResolution; 33 - } 34 - 35 - return input; 36 - } 37 - 38 - async function getPdsUrl(did: string, idResolver: IdResolver): Promise<string> { 39 - const didDoc = await idResolver.did.resolve(did); 40 - 41 - if (!didDoc?.service) { 42 - throw new Error(`Could not resolve DID document for ${did}`); 43 - } 44 - 45 - const pdsService = didDoc.service.find( 46 - (s) => s.id === "#atproto_pds" || s.type === "AtprotoPersonalDataServer", 47 - ); 48 - 49 - if (!pdsService?.serviceEndpoint) { 50 - throw new Error(`Could not find PDS endpoint for ${did}`); 51 - } 52 - 53 - return typeof pdsService.serviceEndpoint === "string" 54 - ? pdsService.serviceEndpoint 55 - : String(pdsService.serviceEndpoint); 56 - } 57 - 58 - function createXrpcClient(pdsUrl: string): XrpcClient { 59 - return new XrpcClient({ service: pdsUrl }, lexicons); 60 - } 61 - 62 - async function loadCollections( 63 - xrpcClient: XrpcClient, 64 - did: string, 65 - ): Promise<string[]> { 66 - const response = await xrpcClient.call("com.atproto.repo.describeRepo", { 67 - repo: did, 68 - }); 69 - 70 - const collections = response.data.collections || []; 71 - 72 - if (collections.length === 0) { 73 - throw new Error(`No collections found for ${did}`); 74 - } 75 - 76 - return collections; 77 - } 78 - 79 - async function listRecords( 80 - xrpcClient: XrpcClient, 81 - did: string, 82 - collection: string, 83 - ): Promise<RecordInfo[]> { 84 - const response = await xrpcClient.call("com.atproto.repo.listRecords", { 85 - repo: did, 86 - collection, 87 - limit: 100, 88 - }); 89 - 90 - return response.data.records || []; 91 - } 92 - 93 - async function loadRecords( 94 - xrpcClient: XrpcClient, 95 - did: string, 96 - collection: string, 97 - ): Promise<RecordInfo[]> { 98 - const records = await listRecords(xrpcClient, did, collection); 99 - 100 - if (records.length === 0) { 101 - throw new Error(`No records found in collection ${collection}`); 102 - } 103 - 104 - return records; 105 - } 106 - 107 - async function fetchRecord( 108 - xrpcClient: XrpcClient, 109 - did: string, 110 - collection: string, 111 - rkey: string, 112 - ): Promise<unknown> { 113 - const response = await xrpcClient.call("com.atproto.repo.getRecord", { 114 - repo: did, 115 - collection, 116 - rkey, 117 - }); 118 - 119 - return response.data; 120 - } 121 - 122 - function formatRecordOption( 123 - record: RecordInfo, 124 - ): { name: string; value: RecordInfo } { 125 - const rkey = record.uri.split("/").pop() || ""; 126 - const value = record.value || {}; 127 - const displayName = 128 - (value.displayName || value.name || value.title || "") as string; 129 - 130 - const name = `${rkey}${displayName ? ` - ${displayName}` : ""}`; 131 - 132 - return { name, value: record }; 133 - } 134 - 135 - function createCollectionPrompt( 136 - did: string, 137 - xrpcClient: XrpcClient, 138 - state: PromptState, 139 - ) { 140 - const collectionPrompt = { 141 - name: "collection", 142 - message: `Select a collection from ${did}:`, 143 - type: Select, 144 - before: async ( 145 - _answers: unknown, 146 - next: (skip?: number | boolean) => Promise<void>, 147 - ) => { 148 - state.collections = await loadCollections(xrpcClient, did); 149 - collectionPrompt.options = state.collections.map((col: string) => ({ 150 - name: col, 151 - value: col, 152 - })); 153 - collectionPrompt.search = state.collections.length > 5; 154 - await next(); 155 - }, 156 - options: [] as Array<{ name: string; value: unknown }>, 157 - search: false, 158 - maxRows: 10, 159 - after: async ( 160 - answers: Record<string, unknown>, 161 - next: () => Promise<void>, 162 - ) => { 163 - state.collection = answers.collection as string; 164 - await next(); 165 - }, 166 - }; 167 - 168 - return collectionPrompt; 169 - } 170 - 171 - function createRecordPrompt( 172 - did: string, 173 - xrpcClient: XrpcClient, 174 - state: PromptState, 175 - ) { 176 - const recordPrompt = { 177 - name: "record", 178 - message: `Select a record from ${state.collection || "collection"}:`, 179 - type: Select, 180 - before: async ( 181 - _answers: unknown, 182 - next: (skip?: number | boolean) => Promise<void>, 183 - ) => { 184 - if (state.rkey) { 185 - await next(1); 186 - return; 187 - } 188 - 189 - if (!state.collection) { 190 - throw new Error("Collection is required"); 191 - } 192 - 193 - state.records = await loadRecords(xrpcClient, did, state.collection); 194 - 195 - if (state.records.length === 1) { 196 - const singleRecord = state.records[0].value || state.records[0]; 197 - state.rkey = state.records[0].uri.split("/").pop() || ""; 198 - console.log(JSON.stringify(singleRecord, null, 2)); 199 - state.recordAlreadyShown = true; 200 - await next(true); 201 - return; 202 - } 203 - 204 - recordPrompt.options = state.records.map(formatRecordOption); 205 - recordPrompt.search = state.records.length > 5; 206 - await next(); 207 - }, 208 - options: [] as Array<{ name: string; value: unknown }>, 209 - search: false, 210 - maxRows: 10, 211 - after: async ( 212 - answers: Record<string, unknown>, 213 - next: () => Promise<void>, 214 - ) => { 215 - const selected = answers.record as RecordInfo | string | undefined; 216 - if (selected) { 217 - if (typeof selected === "object" && "uri" in selected) { 218 - state.rkey = selected.uri.split("/").pop() || ""; 219 - } else if (typeof selected === "string") { 220 - state.rkey = selected; 221 - } 222 - } 223 - await next(); 224 - }, 225 - }; 226 - 227 - return recordPrompt; 228 - } 229 - 230 - function createFetchPrompt( 231 - did: string, 232 - xrpcClient: XrpcClient, 233 - state: PromptState, 234 - ) { 235 - return { 236 - name: "fetch", 237 - message: "", 238 - type: Select, 239 - before: async ( 240 - _answers: unknown, 241 - next: (skip?: number | boolean) => Promise<void>, 242 - ) => { 243 - if (state.recordAlreadyShown) { 244 - await next(true); 245 - return; 246 - } 247 - 248 - if (!state.collection || !state.rkey) { 249 - throw new Error("Collection and rkey are required"); 250 - } 251 - 252 - const record = await fetchRecord( 253 - xrpcClient, 254 - did, 255 - state.collection, 256 - state.rkey, 257 - ); 258 - console.log(JSON.stringify(record, null, 2)); 259 - await next(true); 260 - }, 261 - options: [], 262 - }; 263 - } 264 - 265 - export async function handleGetCommand(input: string) { 266 - try { 267 - const idResolver = new IdResolver({}); 268 - const isFullUri = input.includes("/"); 269 - let atUri: AtUri | null = null; 270 - let did: string; 271 - 272 - if (isFullUri) { 273 - atUri = new AtUri(input); 274 - did = await resolveDid(atUri.hostname, idResolver); 275 - } else { 276 - did = await resolveDid(input, idResolver); 277 - } 278 - 279 - const pdsUrl = await getPdsUrl(did, idResolver); 280 - const xrpcClient = createXrpcClient(pdsUrl); 281 - 282 - const state: PromptState = { 283 - collection: atUri?.collection, 284 - rkey: atUri?.rkey, 285 - recordAlreadyShown: false, 286 - }; 287 - 288 - if (state.collection && state.rkey) { 289 - const record = await fetchRecord( 290 - xrpcClient, 291 - did, 292 - state.collection, 293 - state.rkey, 294 - ); 295 - console.log(JSON.stringify(record, null, 2)); 296 - return; 297 - } 298 - 299 - const prompts: Array<{ 300 - name: string; 301 - message: string; 302 - type: typeof Select; 303 - before?: ( 304 - answers: unknown, 305 - next: (skip?: number | boolean) => Promise<void>, 306 - ) => Promise<void>; 307 - after?: ( 308 - answers: Record<string, unknown>, 309 - next: () => Promise<void>, 310 - ) => Promise<void>; 311 - options: Array<{ name: string; value: unknown }>; 312 - search?: boolean; 313 - maxRows?: number; 314 - }> = []; 315 - 316 - if (!state.collection) { 317 - prompts.push(createCollectionPrompt(did, xrpcClient, state)); 318 - } 319 - 320 - prompts.push(createRecordPrompt(did, xrpcClient, state)); 321 - prompts.push(createFetchPrompt(did, xrpcClient, state)); 322 - 323 - await prompt(prompts as unknown as Parameters<typeof prompt>[0]); 324 - } catch (error) { 325 - const errorMessage = error instanceof Error ? error.message : String(error); 326 - console.error(`Error fetching record: ${errorMessage}`); 327 - if (isDeno) Deno.exit(1); 328 - else process.exit(1); 329 - } 330 - }
-47
cli/mod.ts
··· 1 - /** 2 - * # ATP CLI 3 - * 4 - * A general command-line interface for ATP development tools. 5 - * 6 - * ## Installation 7 - * ```bash 8 - * deno install -g jsr:@atp/cli --name atp 9 - * ``` 10 - * Alternatively, you can use it without installation by referring to 11 - * it as `jsr:@atp/cli` instead of `atp`. 12 - * 13 - * @example Fetch Record from AT URI 14 - * ```bash 15 - * atp at://bsky.app/app.bsky.feed.post/3jrq7y2h2ts2b 16 - * ``` 17 - * 18 - * @module 19 - */ 20 - import { Command } from "@cliffy/command"; 21 - import { handleGetCommand } from "./get.ts"; 22 - import process from "node:process"; 23 - 24 - const isDeno = typeof Deno !== "undefined"; 25 - 26 - const args = isDeno ? Deno.args : process.argv.slice(2); 27 - 28 - await new Command() 29 - .name("atp") 30 - .description( 31 - "ATP Development CLI.", 32 - ) 33 - .arguments("<uri>") 34 - .action(async (_options, input: string) => { 35 - if ( 36 - input.startsWith("did:") || input.includes(".") || 37 - input.startsWith("at://") 38 - ) { 39 - await handleGetCommand(input); 40 - } else { 41 - console.error(`Invalid input format: ${input}`); 42 - console.error("Expected: did:, handle.domain, or at://..."); 43 - if (isDeno) Deno.exit(1); 44 - else process.exit(1); 45 - } 46 - }) 47 - .parse(args);