@jmcombs/pi-1password 1.0.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jmcombs/pi-1password",
3
- "version": "1.0.1",
3
+ "version": "2.0.0",
4
4
  "description": "1Password integration for the Pi coding agent. Read secrets and run commands with 1Password credential injection via the op CLI.",
5
5
  "homepage": "https://github.com/jmcombs/pi-extensions/tree/main/packages/1password",
6
6
  "repository": {
@@ -18,6 +18,7 @@
18
18
  "types": "./index.ts",
19
19
  "files": [
20
20
  "index.ts",
21
+ "credential-api.ts",
21
22
  "data/",
22
23
  "ui/",
23
24
  "README.md",
@@ -46,8 +47,8 @@
46
47
  "typebox": "*"
47
48
  },
48
49
  "devDependencies": {
49
- "@earendil-works/pi-coding-agent": "^0.75.4",
50
+ "@earendil-works/pi-coding-agent": "^0.80.6",
50
51
  "@earendil-works/pi-tui": "*",
51
- "typebox": "^1.1.37"
52
+ "typebox": "^1.3.6"
52
53
  }
53
54
  }
@@ -51,7 +51,8 @@
51
51
  * are still perfectly acceptable and require less code.
52
52
  */
53
53
 
54
- import type { ExtensionCommandContext, Theme } from "@earendil-works/pi-coding-agent";
54
+ import type { Theme } from "@earendil-works/pi-coding-agent";
55
+ import type { UiContext } from "../credential-api.js";
55
56
 
56
57
  // No static imports from @earendil-works/pi-tui are used for types.
57
58
  // We rely on inference for ctx.ui.custom callback parameters (sourced via the
@@ -70,8 +71,8 @@ export function renderBorderedBox(
70
71
  truncateToWidthFn: (s: string, w: number, e?: string, pad?: boolean) => string,
71
72
  ): string[] {
72
73
  const innerWidth = Math.max(20, width - 4);
73
- const top = theme.fg("accent", "╭" + "─".repeat(width - 2) + "╮");
74
- const bottom = theme.fg("accent", "╰" + "─".repeat(width - 2) + "╯");
74
+ const top = theme.fg("accent", `╭${"─".repeat(width - 2)}╮`);
75
+ const bottom = theme.fg("accent", `╰${"─".repeat(width - 2)}╯`);
75
76
 
76
77
  const rawTitle = theme.fg("accent", theme.bold(title));
77
78
  const titlePadded = truncateToWidthFn(rawTitle, innerWidth, "", true);
@@ -99,9 +100,11 @@ export function renderBorderedBox(
99
100
  * Returns the chosen `.value` or `null` (on cancel / Esc).
100
101
  */
101
102
  export async function selectInBorderedPopup<T = string>(
102
- ctx: ExtensionCommandContext,
103
+ ctx: UiContext,
103
104
  opts: {
104
105
  title: string;
106
+ /** Optional body text shown above the list. Split on `\n` into lines. */
107
+ message?: string;
105
108
  items: { value: T; label: string; description?: string }[];
106
109
  helpText?: string;
107
110
  maxVisible?: number;
@@ -121,8 +124,8 @@ export async function selectInBorderedPopup<T = string>(
121
124
  onCancel: () => void;
122
125
  }
123
126
 
124
- // Let inference provide the exact callback parameter types from the
125
- // ExtensionCommandContext (via coding-agent peer). Explicit annotations
127
+ // Let inference provide the exact callback parameter types from
128
+ // `ctx.ui.custom` (via the coding-agent peer). Explicit annotations
126
129
  // referencing TUI/KeybindingsManager etc. from pi-tui trigger the
127
130
  // "separate declarations of private property" tsc error in the monorepo.
128
131
  return await ctx.ui.custom<T | null>(
@@ -176,8 +179,18 @@ export async function selectInBorderedPopup<T = string>(
176
179
  dispose?(): void;
177
180
  } = {
178
181
  render(width: number) {
179
- const listLines = currentList ? currentList.render(Math.max(20, width - 4)) : [];
180
- return renderBorderedBox(width, opts.title, listLines, help, theme, truncateToWidthFn);
182
+ const innerWidth = Math.max(20, width - 4);
183
+ const body: string[] = [];
184
+ if (opts.message) {
185
+ for (const line of opts.message.split("\n")) {
186
+ body.push(theme.fg("text", line));
187
+ }
188
+ body.push("");
189
+ }
190
+ if (currentList) {
191
+ body.push(...currentList.render(innerWidth));
192
+ }
193
+ return renderBorderedBox(width, opts.title, body, help, theme, truncateToWidthFn);
181
194
  },
182
195
  invalidate() {
183
196
  container.invalidate();
@@ -197,7 +210,7 @@ export async function selectInBorderedPopup<T = string>(
197
210
 
198
211
  /** Yes/No (or custom labels) confirmation inside a bordered popup. */
199
212
  export async function confirmInBorderedPopup(
200
- ctx: ExtensionCommandContext,
213
+ ctx: UiContext,
201
214
  opts: {
202
215
  title: string;
203
216
  message?: string;
@@ -214,6 +227,7 @@ export async function confirmInBorderedPopup(
214
227
 
215
228
  const choice = await selectInBorderedPopup(ctx, {
216
229
  title: opts.title,
230
+ message: opts.message,
217
231
  items,
218
232
  helpText: "↑↓ • Enter to confirm • Esc = cancel",
219
233
  maxVisible: 5,
@@ -225,14 +239,22 @@ export async function confirmInBorderedPopup(
225
239
  /**
226
240
  * Bordered popup text input powered by Pi's Editor component.
227
241
  * Good for free-text entry while staying inside the custom popup aesthetic.
242
+ *
243
+ * When `mask` is set the popup renders one `•` per typed code point **instead of**
244
+ * the Editor's own glyphs — the secret is never drawn on screen. The Editor is
245
+ * still used as the (headless) input model so key decoding, paste, and submit all
246
+ * work; we simply never call `editor.render()` in masked mode, so no plaintext is
247
+ * ever emitted. `prompt` is split on `\n` so a multi-line notice renders in full.
228
248
  */
229
249
  export async function inputInBorderedPopup(
230
- ctx: ExtensionCommandContext,
250
+ ctx: UiContext,
231
251
  opts: {
232
252
  title: string;
233
253
  prompt?: string;
234
254
  defaultValue?: string;
235
255
  helpText?: string;
256
+ /** Render typed input as `•` bullets instead of the plaintext value. */
257
+ mask?: boolean;
236
258
  },
237
259
  ): Promise<string | undefined> {
238
260
  const help = opts.helpText ?? "Enter to confirm • Esc = cancel";
@@ -243,6 +265,7 @@ export async function inputInBorderedPopup(
243
265
  invalidate(): void;
244
266
  handleInput(d: string): void;
245
267
  setText(s: string): void;
268
+ getText(): string;
246
269
  onSubmit: (value: string) => void;
247
270
  }
248
271
 
@@ -289,12 +312,19 @@ export async function inputInBorderedPopup(
289
312
  const body: string[] = [];
290
313
 
291
314
  if (opts.prompt) {
292
- body.push(theme.fg("text", opts.prompt));
315
+ for (const line of opts.prompt.split("\n")) {
316
+ body.push(theme.fg("text", line));
317
+ }
293
318
  body.push("");
294
319
  }
295
320
 
296
- const editorLines = editor.render(innerWidth);
297
- body.push(...editorLines);
321
+ if (opts.mask) {
322
+ // Never draw the Editor's glyphs; render one bullet per code point.
323
+ const masked = "•".repeat([...editor.getText()].length);
324
+ body.push(theme.fg("text", masked));
325
+ } else {
326
+ body.push(...editor.render(innerWidth));
327
+ }
298
328
 
299
329
  return renderBorderedBox(width, opts.title, body, help, theme, truncateToWidthFn);
300
330
  },