···11 deleteOAuthSession,
12 getOAuthStorePath,
13 listOAuthSessions,
0014} from "../lib/oauth-store";
15import { exitOnCancel } from "../lib/prompts";
16···33 handler: async ({ logout, list }) => {
34 // List sessions
35 if (list) {
36- const sessions = await listOAuthSessions();
37 if (sessions.length === 0) {
38 log.info("No OAuth sessions stored");
39 } else {
40 log.info("OAuth sessions:");
41- for (const did of sessions) {
42- console.log(` - ${did}`);
43 }
44 }
45 return;
···171 new URLSearchParams(result.params!),
172 );
173174- // Try to get the handle for display (use the original handle input as fallback)
175- let displayName = handle;
176- try {
177- // The session should have the DID, we can use the original handle they entered
178- // or we could fetch the profile to get the current handle
179- displayName = handle.startsWith("did:") ? session.did : handle;
180- } catch {
181- displayName = session.did;
182 }
000183184 s.stop(`Logged in as ${displayName}`);
185
···11 deleteOAuthSession,
12 getOAuthStorePath,
13 listOAuthSessions,
14+ listOAuthSessionsWithHandles,
15+ setOAuthHandle,
16} from "../lib/oauth-store";
17import { exitOnCancel } from "../lib/prompts";
18···35 handler: async ({ logout, list }) => {
36 // List sessions
37 if (list) {
38+ const sessions = await listOAuthSessionsWithHandles();
39 if (sessions.length === 0) {
40 log.info("No OAuth sessions stored");
41 } else {
42 log.info("OAuth sessions:");
43+ for (const { did, handle } of sessions) {
44+ console.log(` - ${handle || did} (${did})`);
45 }
46 }
47 return;
···173 new URLSearchParams(result.params!),
174 );
175176+ // Store the handle for friendly display
177+ // Use the original handle input (unless it was a DID)
178+ const handleToStore = handle.startsWith("did:") ? undefined : handle;
179+ if (handleToStore) {
180+ await setOAuthHandle(session.did, handleToStore);
000181 }
182+183+ // Try to get the handle for display (use the original handle input as fallback)
184+ const displayName = handleToStore || session.did;
185186 s.stop(`Logged in as ${displayName}`);
187
+46-6
packages/cli/src/commands/publish.ts
···5import { loadConfig, loadState, saveState, findConfig } from "../lib/config";
6import {
7 loadCredentials,
8- listCredentials,
9 getCredentials,
10} from "../lib/credentials";
011import {
12 createAgent,
13 createDocument,
···5960 // If no credentials resolved, check if we need to prompt for identity selection
61 if (!credentials) {
62- const identities = await listCredentials();
63 if (identities.length === 0) {
64- log.error("No credentials found. Run 'sequoia auth' first.");
0065 log.info(
66 "Or set ATP_IDENTIFIER and ATP_APP_PASSWORD environment variables.",
67 );
68 process.exit(1);
69 }
000000000000000007071 // Multiple identities exist but none selected - prompt user
72 log.info("Multiple identities found. Select one to use:");
73 const selected = exitOnCancel(
74 await select({
75 message: "Identity:",
76- options: identities.map((id) => ({ value: id, label: id })),
77 }),
78 );
7980- credentials = await getCredentials(selected);
000000000000000081 if (!credentials) {
82 log.error("Failed to load selected credentials.");
83 process.exit(1);
84 }
85000086 log.info(
87- `Tip: Add "identity": "${selected}" to sequoia.json to use this by default.`,
88 );
89 }
90
···5import { loadConfig, loadState, saveState, findConfig } from "../lib/config";
6import {
7 loadCredentials,
8+ listAllCredentials,
9 getCredentials,
10} from "../lib/credentials";
11+import { getOAuthHandle, getOAuthSession } from "../lib/oauth-store";
12import {
13 createAgent,
14 createDocument,
···6061 // If no credentials resolved, check if we need to prompt for identity selection
62 if (!credentials) {
63+ const identities = await listAllCredentials();
64 if (identities.length === 0) {
65+ log.error(
66+ "No credentials found. Run 'sequoia login' or 'sequoia auth' first.",
67+ );
68 log.info(
69 "Or set ATP_IDENTIFIER and ATP_APP_PASSWORD environment variables.",
70 );
71 process.exit(1);
72 }
73+74+ // Build labels with handles for OAuth sessions
75+ const options = await Promise.all(
76+ identities.map(async (cred) => {
77+ if (cred.type === "oauth") {
78+ const handle = await getOAuthHandle(cred.id);
79+ return {
80+ value: cred.id,
81+ label: `${handle || cred.id} (OAuth)`,
82+ };
83+ }
84+ return {
85+ value: cred.id,
86+ label: `${cred.id} (App Password)`,
87+ };
88+ }),
89+ );
9091 // Multiple identities exist but none selected - prompt user
92 log.info("Multiple identities found. Select one to use:");
93 const selected = exitOnCancel(
94 await select({
95 message: "Identity:",
96+ options,
97 }),
98 );
99100+ // Load the selected credentials
101+ const selectedCred = identities.find((c) => c.id === selected);
102+ if (selectedCred?.type === "oauth") {
103+ const session = await getOAuthSession(selected);
104+ if (session) {
105+ const handle = await getOAuthHandle(selected);
106+ credentials = {
107+ type: "oauth",
108+ did: selected,
109+ handle: handle || selected,
110+ pdsUrl: "https://bsky.social",
111+ };
112+ }
113+ } else {
114+ credentials = await getCredentials(selected);
115+ }
116+117 if (!credentials) {
118 log.error("Failed to load selected credentials.");
119 process.exit(1);
120 }
121122+ const displayId =
123+ credentials.type === "oauth"
124+ ? credentials.handle || credentials.did
125+ : credentials.identifier;
126 log.info(
127+ `Tip: Add "identity": "${displayId}" to sequoia.json to use this by default.`,
128 );
129 }
130
+41-5
packages/cli/src/commands/sync.ts
···5import { loadConfig, loadState, saveState, findConfig } from "../lib/config";
6import {
7 loadCredentials,
8- listCredentials,
9 getCredentials,
10} from "../lib/credentials";
011import { createAgent, listDocuments } from "../lib/atproto";
12import {
13 scanContentDirectory,
···49 let credentials = await loadCredentials(config.identity);
5051 if (!credentials) {
52- const identities = await listCredentials();
53 if (identities.length === 0) {
54- log.error("No credentials found. Run 'sequoia auth' first.");
0055 process.exit(1);
56 }
570000000000000000058 log.info("Multiple identities found. Select one to use:");
59 const selected = exitOnCancel(
60 await select({
61 message: "Identity:",
62- options: identities.map((id) => ({ value: id, label: id })),
63 }),
64 );
6566- credentials = await getCredentials(selected);
000000000000000067 if (!credentials) {
68 log.error("Failed to load selected credentials.");
69 process.exit(1);
···1import * as fs from "node:fs/promises";
2import * as os from "node:os";
3import * as path from "node:path";
4-import { getOAuthSession, listOAuthSessions } from "./oauth-store";
000005import type {
6 AppPasswordCredentials,
7 Credentials,
···86 if (profile.startsWith("did:")) {
87 const session = await getOAuthSession(profile);
88 if (session) {
089 return {
90 type: "oauth",
91 did: profile,
92- handle: profile, // We don't have the handle stored, use DID
93 pdsUrl: "https://bsky.social", // Will be resolved from DID doc
94 };
95 }
96 }
9798- // 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.
000000000101 return null;
102}
103···166 if (oauthDids.length === 1 && oauthDids[0]) {
167 const session = await getOAuthSession(oauthDids[0]);
168 if (session) {
0169 return {
170 type: "oauth",
171 did: oauthDids[0],
172- handle: oauthDids[0],
173 pdsUrl: "https://bsky.social",
174 };
175 }
···1import * as fs from "node:fs/promises";
2import * as os from "node:os";
3import * as path from "node:path";
4+import {
5+ getOAuthHandle,
6+ getOAuthSession,
7+ listOAuthSessions,
8+ listOAuthSessionsWithHandles,
9+} from "./oauth-store";
10import type {
11 AppPasswordCredentials,
12 Credentials,
···91 if (profile.startsWith("did:")) {
92 const session = await getOAuthSession(profile);
93 if (session) {
94+ const handle = await getOAuthHandle(profile);
95 return {
96 type: "oauth",
97 did: profile,
98+ handle: handle || profile,
99 pdsUrl: "https://bsky.social", // Will be resolved from DID doc
100 };
101 }
102 }
103104+ // Try to find OAuth session by handle
105+ const sessions = await listOAuthSessionsWithHandles();
106+ const match = sessions.find((s) => s.handle === profile);
107+ if (match) {
108+ return {
109+ type: "oauth",
110+ did: match.did,
111+ handle: match.handle || match.did,
112+ pdsUrl: "https://bsky.social",
113+ };
114+ }
115+116 return null;
117}
118···181 if (oauthDids.length === 1 && oauthDids[0]) {
182 const session = await getOAuthSession(oauthDids[0]);
183 if (session) {
184+ const handle = await getOAuthHandle(oauthDids[0]);
185 return {
186 type: "oauth",
187 did: oauthDids[0],
188+ handle: handle || oauthDids[0],
189 pdsUrl: "https://bsky.social",
190 };
191 }