From 458b5cd434309c86b1558a308201be43090357f1 Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Sun, 15 Mar 2026 20:46:15 +0000 Subject: [PATCH 1/3] fix: only show generated password when clipboard is unavailable --- packages/pds/src/cli/utils/secrets.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/pds/src/cli/utils/secrets.ts b/packages/pds/src/cli/utils/secrets.ts index 896d46a..e98fb71 100644 --- a/packages/pds/src/cli/utils/secrets.ts +++ b/packages/pds/src/cli/utils/secrets.ts @@ -78,14 +78,13 @@ export async function promptPassword(handle?: string): Promise { if (method === "generate") { const password = generatePassword(); - p.note(password, "Generated password"); const copied = await copyToClipboard(password); if (copied) { - p.log.success("Copied to clipboard"); + p.log.success("Password generated and copied to clipboard"); } else { - p.log.warn( - "Could not copy to clipboard — save this password somewhere safe!", - ); + // Clipboard unavailable — must show the password + p.note(password, "Generated password"); + p.log.warn("Could not copy to clipboard — save this password somewhere safe!"); } return password; } From 0b04bd6f17c15d4ac6677a2e724084d9c347b20f Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Sun, 15 Mar 2026 20:50:38 +0000 Subject: [PATCH 2/3] feat: offer 1Password save for generated passwords When generating a password, prompt where to save it: 1Password (as a bsky.app login), clipboard, or display. 1Password option only shown when op CLI is available. --- packages/pds/src/cli/utils/cli-helpers.ts | 50 +++++++++++++++++ packages/pds/src/cli/utils/secrets.ts | 68 +++++++++++++++++++++-- 2 files changed, 112 insertions(+), 6 deletions(-) diff --git a/packages/pds/src/cli/utils/cli-helpers.ts b/packages/pds/src/cli/utils/cli-helpers.ts index 3f67b93..47b59d4 100644 --- a/packages/pds/src/cli/utils/cli-helpers.ts +++ b/packages/pds/src/cli/utils/cli-helpers.ts @@ -203,6 +203,56 @@ export async function saveTo1Password( }); } +/** + * Save a password to 1Password as a Login item for bsky.app + */ +export async function savePasswordTo1Password( + password: string, + handle: string, +): Promise<{ success: boolean; itemName?: string; error?: string }> { + const itemName = `Bluesky - @${handle}`; + + return new Promise((resolve) => { + const child = spawn( + "op", + [ + "item", + "create", + "--category", + "Login", + "--title", + itemName, + `--username=${handle}`, + `password=${password}`, + "--url=https://bsky.app", + "--tags", + "cirrus,pds,bluesky", + ], + { stdio: ["ignore", "pipe", "pipe"] }, + ); + + let stderr = ""; + child.stderr?.on("data", (data) => { + stderr += data.toString(); + }); + + child.on("error", (err) => { + resolve({ success: false, error: err.message }); + }); + + child.on("close", (code) => { + if (code === 0) { + resolve({ success: true, itemName }); + } else { + resolve({ + success: false, + error: stderr || `1Password CLI exited with code ${code}`, + }); + } + }); + }); +} + export interface RunCommandOptions { /** If true, stream output to stdout/stderr in real-time */ stream?: boolean; diff --git a/packages/pds/src/cli/utils/secrets.ts b/packages/pds/src/cli/utils/secrets.ts index e98fb71..e7dc087 100644 --- a/packages/pds/src/cli/utils/secrets.ts +++ b/packages/pds/src/cli/utils/secrets.ts @@ -7,7 +7,12 @@ import bcrypt from "bcryptjs"; import * as p from "@clack/prompts"; import { setSecret, setVar, type SecretName } from "./wrangler.js"; import { setDevVar } from "./dotenv.js"; -import { promptSelect, copyToClipboard } from "./cli-helpers.js"; +import { + promptSelect, + copyToClipboard, + is1PasswordAvailable, + savePasswordTo1Password, +} from "./cli-helpers.js"; export interface SigningKeypair { privateKey: string; // hex-encoded @@ -78,14 +83,65 @@ export async function promptPassword(handle?: string): Promise { if (method === "generate") { const password = generatePassword(); - const copied = await copyToClipboard(password); - if (copied) { - p.log.success("Password generated and copied to clipboard"); + const has1Password = await is1PasswordAvailable(); + + type SaveOption = "1password" | "clipboard" | "show"; + const saveOptions: Array<{ + value: SaveOption; + label: string; + hint: string; + }> = []; + + if (has1Password) { + saveOptions.push({ + value: "1password", + label: "Save to 1Password", + hint: "as a bsky.app login", + }); + } + + saveOptions.push( + { value: "clipboard", label: "Copy to clipboard", hint: "paste into password manager" }, + { value: "show", label: "Display it", hint: "shown in terminal" }, + ); + + const saveChoice = await promptSelect({ + message: "Where should we save the password?", + options: saveOptions, + }); + + if (saveChoice === "1password") { + const spinner = p.spinner(); + spinner.start("Saving to 1Password..."); + const result = await savePasswordTo1Password(password, handle ?? ""); + if (result.success) { + spinner.stop("Saved to 1Password"); + p.log.success(`Created: "${result.itemName}"`); + } else { + spinner.stop("Failed to save to 1Password"); + p.log.error(result.error || "Unknown error"); + // Fall back to clipboard + const copied = await copyToClipboard(password); + if (copied) { + p.log.info("Copied to clipboard instead"); + } else { + p.note(password, "Generated password"); + p.log.warn("Save this password somewhere safe!"); + } + } + } else if (saveChoice === "clipboard") { + const copied = await copyToClipboard(password); + if (copied) { + p.log.success("Password generated and copied to clipboard"); + } else { + p.note(password, "Generated password"); + p.log.warn("Could not copy to clipboard — save this password somewhere safe!"); + } } else { - // Clipboard unavailable — must show the password p.note(password, "Generated password"); - p.log.warn("Could not copy to clipboard — save this password somewhere safe!"); + p.log.warn("Save this password somewhere safe!"); } + return password; } From b64acc0758cf0e58b8f4680789cb2dc2e41cf50d Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Sun, 15 Mar 2026 21:32:17 +0000 Subject: [PATCH 3/3] fix: use assignment syntax for op username field --- packages/pds/src/cli/utils/cli-helpers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pds/src/cli/utils/cli-helpers.ts b/packages/pds/src/cli/utils/cli-helpers.ts index 47b59d4..1ba734d 100644 --- a/packages/pds/src/cli/utils/cli-helpers.ts +++ b/packages/pds/src/cli/utils/cli-helpers.ts @@ -222,7 +222,7 @@ export async function savePasswordTo1Password( "Login", "--title", itemName, - `--username=${handle}`, + `username=${handle}`, `password=${password}`, "--url=https://bsky.app", "--tags",