@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/README.md +13 -6
- package/credential-api.ts +328 -0
- package/index.ts +501 -258
- package/package.json +4 -3
- package/ui/bordered-popups.ts +43 -13
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jmcombs/pi-1password",
|
|
3
|
-
"version": "
|
|
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.
|
|
50
|
+
"@earendil-works/pi-coding-agent": "^0.80.6",
|
|
50
51
|
"@earendil-works/pi-tui": "*",
|
|
51
|
-
"typebox": "^1.
|
|
52
|
+
"typebox": "^1.3.6"
|
|
52
53
|
}
|
|
53
54
|
}
|
package/ui/bordered-popups.ts
CHANGED
|
@@ -51,7 +51,8 @@
|
|
|
51
51
|
* are still perfectly acceptable and require less code.
|
|
52
52
|
*/
|
|
53
53
|
|
|
54
|
-
import type {
|
|
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", "
|
|
74
|
-
const bottom = theme.fg("accent", "
|
|
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:
|
|
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
|
|
125
|
-
//
|
|
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
|
|
180
|
-
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
297
|
-
|
|
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
|
},
|