History

1p cli

Rhys Sullivan committed 7 months ago

Commit 48dbaad1 parent c8ac3876 files · +297 −178Browse files

Unified · Split

6 files
  1. bun.lock +29 −22
  2. packages/plugins/onepassword/package.json +16 −7
  3. packages/plugins/onepassword/src/sdk/client.ts +0 −115
  4. packages/plugins/onepassword/src/sdk/index.ts +8 −1
  5. packages/plugins/onepassword/src/sdk/plugin.ts +23 −33
  6. packages/plugins/onepassword/src/sdk/service.ts +221 −0
bun.lock+29−22
@@ -13,7 +13,7 @@ · 12 unmodified lines
1313 },
1414 "apps/server": {
1515 "name": "@executor/server",
16− "version": "0.0.0",
16+ "version": "1.4.0-beta.0",
1717 "dependencies": {
1818 "@effect/platform": "catalog:",
1919 "@effect/sql": "catalog:",
@@ -40,7 +40,7 @@ · 20 unmodified lines
4040 },
4141 "apps/web": {
4242 "name": "@executor/web",
43− "version": "0.0.0",
43+ "version": "1.4.0-beta.0",
4444 "dependencies": {
4545 "@executor/plugin-google-discovery": "workspace:*",
4646 "@executor/plugin-graphql": "workspace:*",
@@ -72,7 +72,7 @@ · 25 unmodified lines
7272 },
7373 "packages/clients/react": {
7474 "name": "@executor/react",
75− "version": "0.0.0",
75+ "version": "1.4.0-beta.0",
7676 "dependencies": {
7777 "@effect-atom/atom": "^0.5.3",
7878 "@effect-atom/atom-react": "^0.5.0",
@@ -91,7 +91,7 @@ · 12 unmodified lines
9191 },
9292 "packages/core/api": {
9393 "name": "@executor/api",
94− "version": "0.0.0",
94+ "version": "1.4.0-beta.0",
9595 "dependencies": {
9696 "@effect/platform": "catalog:",
9797 "@executor/sdk": "workspace:*",
@@ -105,7 +105,7 @@ · 7 unmodified lines
105105 },
106106 "packages/core/execution": {
107107 "name": "@executor/execution",
108− "version": "0.0.0",
108+ "version": "1.4.0-beta.0",
109109 "dependencies": {
110110 "@executor/codemode-core": "workspace:*",
111111 "@executor/runtime-quickjs": "workspace:*",
@@ -119,7 +119,7 @@ · 7 unmodified lines
119119 },
120120 "packages/core/sdk": {
121121 "name": "@executor/sdk",
122− "version": "0.0.1",
122+ "version": "1.4.0-beta.0",
123123 "dependencies": {
124124 "effect": "catalog:",
125125 },
@@ -131,7 +131,7 @@ · 5 unmodified lines
131131 },
132132 "packages/core/storage-file": {
133133 "name": "@executor/storage-file",
134− "version": "0.0.0",
134+ "version": "1.4.0-beta.0",
135135 "dependencies": {
136136 "@effect/sql": "catalog:",
137137 "@executor/sdk": "workspace:*",
@@ -147,7 +147,7 @@ · 9 unmodified lines
147147 },
148148 "packages/hosts/mcp": {
149149 "name": "@executor/host-mcp",
150− "version": "0.0.0",
150+ "version": "1.4.0-beta.0",
151151 "dependencies": {
152152 "@executor/execution": "workspace:*",
153153 "@executor/sdk": "workspace:*",
@@ -162,7 +162,7 @@ · 8 unmodified lines
162162 },
163163 "packages/kernel/core": {
164164 "name": "@executor/codemode-core",
165− "version": "0.0.0",
165+ "version": "1.4.0-beta.0",
166166 "dependencies": {
167167 "@standard-schema/spec": "^1.0.0",
168168 "ajv": "^8.17.1",
@@ -178,7 +178,7 @@ · 9 unmodified lines
178178 },
179179 "packages/kernel/ir": {
180180 "name": "@executor/ir",
181− "version": "0.0.0",
181+ "version": "1.4.0-beta.0",
182182 "dependencies": {
183183 "effect": "catalog:",
184184 },
@@ -191,7 +191,7 @@ · 6 unmodified lines
191191 },
192192 "packages/kernel/runtime-quickjs": {
193193 "name": "@executor/runtime-quickjs",
194− "version": "0.0.0",
194+ "version": "1.4.0-beta.0",
195195 "dependencies": {
196196 "@executor/codemode-core": "workspace:*",
197197 "effect": "catalog:",
@@ -206,7 +206,7 @@ · 8 unmodified lines
206206 },
207207 "packages/plugins/file-secrets": {
208208 "name": "@executor/plugin-file-secrets",
209− "version": "0.0.0",
209+ "version": "1.4.0-beta.0",
210210 "dependencies": {
211211 "@executor/sdk": "workspace:*",
212212 "effect": "catalog:",
@@ -220,7 +220,7 @@ · 7 unmodified lines
220220 },
221221 "packages/plugins/google-discovery": {
222222 "name": "@executor/plugin-google-discovery",
223− "version": "0.0.0",
223+ "version": "1.4.0-beta.0",
224224 "dependencies": {
225225 "@effect/platform": "catalog:",
226226 "@executor/api": "workspace:*",
@@ -255,7 +255,7 @@ · 28 unmodified lines
255255 },
256256 "packages/plugins/graphql": {
257257 "name": "@executor/plugin-graphql",
258− "version": "0.0.0",
258+ "version": "1.4.0-beta.0",
259259 "dependencies": {
260260 "@effect/platform": "catalog:",
261261 "@effect/platform-node": "catalog:",
@@ -293,7 +293,7 @@ · 31 unmodified lines
293293 },
294294 "packages/plugins/keychain": {
295295 "name": "@executor/plugin-keychain",
296− "version": "0.0.0",
296+ "version": "1.4.0-beta.0",
297297 "dependencies": {
298298 "@executor/sdk": "workspace:*",
299299 "@napi-rs/keyring": "^1.2.0",
@@ -308,7 +308,7 @@ · 8 unmodified lines
308308 },
309309 "packages/plugins/mcp": {
310310 "name": "@executor/plugin-mcp",
311− "version": "0.0.0",
311+ "version": "1.4.0-beta.0",
312312 "dependencies": {
313313 "@effect/platform": "catalog:",
314314 "@effect/platform-node": "catalog:",
@@ -347,8 +347,9 @@ · 32 unmodified lines
347347 },
348348 "packages/plugins/onepassword": {
349349 "name": "@executor/plugin-onepassword",
350− "version": "0.0.0",
350+ "version": "1.4.0-beta.0",
351351 "dependencies": {
352+ "@1password/op-js": "^0.1.13",
352353 "@1password/sdk": "^0.4.1-beta.1",
353354 "@effect-atom/atom-react": "^0.5.0",
354355 "@effect/platform": "catalog:",
@@ -377,7 +378,7 @@ · 22 unmodified lines
377378 },
378379 "packages/plugins/openapi": {
379380 "name": "@executor/plugin-openapi",
380− "version": "0.0.0",
381+ "version": "1.4.0-beta.0",
381382 "dependencies": {
382383 "@apidevtools/swagger-parser": "^12.1.0",
383384 "@effect/platform": "catalog:",
@@ -418,7 +419,7 @@ · 34 unmodified lines
418419 },
419420 "packages/ui": {
420421 "name": "@executor/ui",
421− "version": "0.0.0",
422+ "version": "1.4.0-beta.0",
422423 "dependencies": {
423424 "@base-ui/react": "^1.3.0",
424425 "@hookform/resolvers": "^5.2.2",
@@ -469,6 +470,8 @@ · 44 unmodified lines
469470 "vitest": "^3.2.4",
470471 },
471472 "packages": {
473+ "@1password/op-js": ["@1password/op-js@0.1.13", "", { "dependencies": { "lookpath": "^1.2.2", "semver": "^7.6.2" } }, "sha512-ZZBLxVqywFdvIbLv2xWw2N1ImSi183rRKf90vV19KRMReNyLwuD0dv6IrKrIdrJU33IuV3Gz85Z4K2a1PJTBDg=="],
474+
472475 "@1password/sdk": ["@1password/sdk@0.4.1-beta.1", "", { "dependencies": { "@1password/sdk-core": "0.4.1-beta.1" } }, "sha512-QWY6eIIEs9at6kAFbgjZ1vVxg7ygp/MwXJ6/BoBdPkqIk8vfulXu4pjDsVQ87HhddJAD5LVGBXSbQIa+svI3bA=="],
473476
474477 "@1password/sdk-core": ["@1password/sdk-core@0.4.1-beta.1", "", {}, "sha512-/otbg1JVhsEn6oUIeReoT9TmFr8J7KBwr9UuRVfJFwwGG3bHPF8ewT+LhRimQeJtypqQ69ZVuOYkxknD4iQHxw=="],
@@ -1603,6 +1606,8 @@ · 1128 unmodified lines
16031606
16041607 "longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="],
16051608
1609+ "lookpath": ["lookpath@1.2.3", "", { "bin": { "lookpath": "bin/lookpath.js" } }, "sha512-kthRVhf4kH4+HW3anM4UBHxsw/XFESf13euCEldhXr6GpBdmBoa7rDd7WO5G0Mhd4G5XtKTcEy8OR0iRZXpS3Q=="],
1610+
16061611 "loupe": ["loupe@3.2.1", "", {}, "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ=="],
16071612
16081613 "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
@@ -1891,7 +1896,7 @@ · 282 unmodified lines
18911896
18921897 "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
18931898
1894− "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
1899+ "semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
18951900
18961901 "send": ["send@1.2.1", "", { "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.1", "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.2" } }, "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ=="],
18971902
@@ -2081,6 +2086,10 @@ · 183 unmodified lines
20812086
20822087 "@babel/code-frame/js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
20832088
2089+ "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
2090+
2091+ "@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
2092+
20842093 "@executor/sdk/typescript": ["typescript@6.0.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ=="],
20852094
20862095 "@reduxjs/toolkit/immer": ["immer@11.1.4", "", {}, "sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw=="],
@@ -2111,8 +2120,6 @@ · 24 unmodified lines
21112120
21122121 "mermaid/marked": ["marked@16.4.2", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA=="],
21132122
2114− "node-abi/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
2115−
21162123 "parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="],
21172124
21182125 "vite-node/vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="],
packages/plugins/onepassword/package.json+16−7
@@ -14,12 +14,13 @@ · 13 unmodified lines
1414 "test:watch": "vitest"
1515 },
1616 "dependencies": {
17+ "@1password/op-js": "^0.1.13",
1718 "@1password/sdk": "^0.4.1-beta.1",
19+ "@effect-atom/atom-react": "^0.5.0",
20+ "@effect/platform": "catalog:",
1821 "@executor/api": "workspace:*",
1922 "@executor/sdk": "workspace:*",
20− "effect": "catalog:",
21− "@effect/platform": "catalog:",
22− "@effect-atom/atom-react": "^0.5.0"
23+ "effect": "catalog:"
2324 },
2425 "devDependencies": {
2526 "@effect/vitest": "catalog:",
@@ -34,9 +35,17 @@ · 8 unmodified lines
3435 "@executor/ui": "workspace:*"
3536 },
3637 "peerDependenciesMeta": {
37− "react": { "optional": true },
38− "@effect-atom/atom-react": { "optional": true },
39− "@executor/react": { "optional": true },
40− "@executor/ui": { "optional": true }
38+ "react": {
39+ "optional": true
40+ },
41+ "@effect-atom/atom-react": {
42+ "optional": true
43+ },
44+ "@executor/react": {
45+ "optional": true
46+ },
47+ "@executor/ui": {
48+ "optional": true
49+ }
4150 }
4251 }
packages/plugins/onepassword/src/sdk/client.ts+0−115
@@ -1,115 +0,0 @@
1−import { Duration, Effect } from "effect";
2−import {
3− createClient,
4− DesktopAuth,
5− type Client,
6−} from "@1password/sdk";
7−
8−import { OnePasswordError } from "./errors";
9−
10−// ---------------------------------------------------------------------------
11−// Resolved auth — raw credentials ready for the SDK
12−// ---------------------------------------------------------------------------
13−
14−export type ResolvedAuth =
15− | { readonly kind: "desktop-app"; readonly accountName: string }
16− | { readonly kind: "service-account"; readonly token: string };
17−
18−// ---------------------------------------------------------------------------
19−// 1Password client wrapper using the Effect "use" pattern
20−// ---------------------------------------------------------------------------
21−
22−export interface OnePasswordClient {
23− /** The raw 1Password SDK client (escape hatch) */
24− readonly client: Client;
25−
26− /**
27− * Execute a function against the 1Password client with automatic
28− * error handling, timeout, and tracing.
29− */
30− readonly use: <A>(
31− fn: (client: Client) => Promise<A>,
32− operation?: string,
33− ) => Effect.Effect<A, OnePasswordError>;
34−}
35−
36−// ---------------------------------------------------------------------------
37−// Factory
38−// ---------------------------------------------------------------------------
39−
40−const DEFAULT_TIMEOUT_MS = 15_000;
41−
42−export const makeOnePasswordClient = (
43− auth: ResolvedAuth,
44− timeoutMs: number = DEFAULT_TIMEOUT_MS,
45−): Effect.Effect<OnePasswordClient, OnePasswordError> =>
46− Effect.gen(function* () {
47− const timeout = Duration.millis(timeoutMs);
48−
49− const client = yield* Effect.tryPromise({
50− try: () =>
51− createClient({
52− auth:
53− auth.kind === "desktop-app"
54− ? new DesktopAuth(auth.accountName)
55− : auth.token,
56− integrationName: "Executor",
57− integrationVersion: "0.0.0",
58− }),
59− catch: (cause) =>
60− new OnePasswordError({
61− operation: "client setup",
62− message: cause instanceof Error ? cause.message : String(cause),
63− }),
64− }).pipe(
65− Effect.timeoutFail({
66− duration: timeout,
67− onTimeout: () =>
68− new OnePasswordError({
69− operation: "client setup",
70− message: [
71− `timed out after ${Math.floor(timeoutMs / 1000)}s.`,
72− "Troubleshooting:",
73− "1. Make sure the 1Password desktop app is open and unlocked",
74− "2. Check for an approval prompt in the 1Password app — it may be behind other windows",
75− "3. Ensure 'Developer > Connect with 1Password CLI' is enabled in 1Password Settings",
76− "4. Make sure no other app or terminal is waiting for 1Password approval (only one prompt at a time)",
77− "5. Try quitting 1Password completely and reopening it, then retry",
78− ].join("\n"),
79− }),
80− }),
81− );
82−
83− const use = <A>(
84− fn: (client: Client) => Promise<A>,
85− operation = "use",
86− ): Effect.Effect<A, OnePasswordError> =>
87− Effect.tryPromise({
88− try: () => fn(client),
89− catch: (cause) =>
90− new OnePasswordError({
91− operation,
92− message: cause instanceof Error ? cause.message : String(cause),
93− }),
94− }).pipe(
95− Effect.timeoutFail({
96− duration: timeout,
97− onTimeout: () =>
98− new OnePasswordError({
99− operation,
100− message: [
101− `timed out after ${Math.floor(timeoutMs / 1000)}s.`,
102− "Troubleshooting:",
103− "1. Make sure the 1Password desktop app is open and unlocked",
104− "2. Check for an approval prompt in the 1Password app — it may be behind other windows",
105− "3. Ensure 'Developer > Connect with 1Password CLI' is enabled in 1Password Settings",
106− "4. Make sure no other app or terminal is waiting for 1Password approval (only one prompt at a time)",
107− "5. Try quitting 1Password completely and reopening it, then retry",
108− ].join("\n"),
109− }),
110− }),
111− Effect.withSpan(`onepassword.${operation}`),
112− );
113−
114− return { client, use } satisfies OnePasswordClient;
115− }).pipe(Effect.withSpan("onepassword.make_client"));
packages/plugins/onepassword/src/sdk/index.ts+8−1
@@ -1,4 +1,11 @@
11 export { onepasswordPlugin, type OnePasswordExtension, type OnePasswordPluginOptions } from "./plugin";
22 export { OnePasswordConfig, Vault, ConnectionStatus, OnePasswordAuth, DesktopAppAuth, ServiceAccountAuth } from "./types";
33 export { OnePasswordError } from "./errors";
4−export { makeOnePasswordClient, type OnePasswordClient, type ResolvedAuth } from "./client";
4+export {
5+ makeOnePasswordService,
6+ makeNativeSdkService,
7+ makeCliService,
8+ OnePasswordServiceTag,
9+ type OnePasswordService,
10+ type ResolvedAuth,
11+} from "./service";
packages/plugins/onepassword/src/sdk/plugin.ts+23−33
@@ -12,7 +12,11 @@ import { · 11 unmodified lines
1212 import { OnePasswordConfig, Vault, ConnectionStatus } from "./types";
1313 import type { OnePasswordAuth } from "./types";
1414 import { OnePasswordError } from "./errors";
15−import { makeOnePasswordClient, type ResolvedAuth, type OnePasswordClient } from "./client";
15+import {
16+ makeOnePasswordService,
17+ type ResolvedAuth,
18+ type OnePasswordService,
19+} from "./service";
1620
1721 // ---------------------------------------------------------------------------
1822 // Constants
@@ -79,13 +83,13 @@ const resolveAuth = ( · 60 unmodified lines
7983 );
8084 };
8185
82−const getClientFromConfig = (
86+const getServiceFromConfig = (
8387 config: OnePasswordConfig,
8488 ctx: PluginContext,
8589 timeoutMs: number,
86−): Effect.Effect<OnePasswordClient, OnePasswordError> =>
90+): Effect.Effect<OnePasswordService, OnePasswordError> =>
8791 resolveAuth(config.auth, ctx).pipe(
88− Effect.flatMap((resolved) => makeOnePasswordClient(resolved, timeoutMs)),
92+ Effect.flatMap((resolved) => makeOnePasswordService(resolved, { timeoutMs })),
8993 );
9094
9195 // ---------------------------------------------------------------------------
@@ -109,13 +113,8 @@ const makeProvider = ( · 17 unmodified lines
109113 ? secretId
110114 : `op://${config.vaultId}/${secretId}/${CREDENTIAL_FIELD}`;
111115
112− return getClientFromConfig(config, ctx, timeoutMs).pipe(
113− Effect.flatMap((op) =>
114− op.use(
115− (client) => client.secrets.resolve(uri),
116− "secret resolution",
117− ),
118− ),
116+ return getServiceFromConfig(config, ctx, timeoutMs).pipe(
117+ Effect.flatMap((svc) => svc.resolveSecret(uri)),
119118 Effect.map((v): string | null => v),
120119 Effect.orElseSucceed(() => null),
121120 );
@@ -127,13 +126,8 @@ const makeProvider = ( · 5 unmodified lines
127126 getConfig().pipe(
128127 Effect.flatMap((config) => {
129128 if (!config) return Effect.succeed([] as { id: string; name: string }[]);
130− return getClientFromConfig(config, ctx, timeoutMs).pipe(
131− Effect.flatMap((op) =>
132− op.use(
133− (client) => client.items.list(config.vaultId),
134− "item listing",
135− ),
136− ),
129+ return getServiceFromConfig(config, ctx, timeoutMs).pipe(
130+ Effect.flatMap((svc) => svc.listItems(config.vaultId)),
137131 Effect.map((items) =>
138132 items.map((item) => ({ id: item.id, name: item.title })),
139133 ),
@@ -187,6 +181,8 @@ export interface OnePasswordPluginOptions { · 47 unmodified lines
187181 readonly kv: ScopedKv;
188182 /** Request timeout in ms (default: 15000) */
189183 readonly timeoutMs?: number;
184+ /** Force use of the native SDK instead of the CLI (default: false) */
185+ readonly preferSdk?: boolean;
190186 }
191187
192188 export const onepasswordPlugin = (
@@ -222,11 +218,8 @@ export const onepasswordPlugin = ( · 29 unmodified lines
222218 error: "Not configured",
223219 });
224220 }
225− const op = yield* getClientFromConfig(config, ctx, timeoutMs);
226− const vaults = yield* op.use(
227− (client) => client.vaults.list({ decryptDetails: true }),
228− "connection check",
229− );
221+ const svc = yield* getServiceFromConfig(config, ctx, timeoutMs);
222+ const vaults = yield* svc.listVaults();
230223 const vault = vaults.find((v) => v.id === config.vaultId);
231224 return new ConnectionStatus({
232225 connected: true,
@@ -237,11 +230,11 @@ export const onepasswordPlugin = ( · 4 unmodified lines
237230 listVaults: (auth) =>
238231 Effect.gen(function* () {
239232 const resolved = yield* resolveAuth(auth, ctx);
240− const op = yield* makeOnePasswordClient(resolved, timeoutMs);
241− const vaults = yield* op.use(
242− (client) => client.vaults.list({ decryptDetails: true }),
243− "vault listing",
244− );
233+ const svc = yield* makeOnePasswordService(resolved, {
234+ timeoutMs,
235+ preferSdk: options.preferSdk,
236+ });
237+ const vaults = yield* svc.listVaults();
245238 return vaults
246239 .map((v) => new Vault({ id: v.id, name: v.title }))
247240 .sort((a, b) => a.name.localeCompare(b.name));
@@ -256,11 +249,8 @@ export const onepasswordPlugin = ( · 8 unmodified lines
256249 message: "1Password is not configured",
257250 });
258251 }
259− const op = yield* getClientFromConfig(config, ctx, timeoutMs);
260− return yield* op.use(
261− (client) => client.secrets.resolve(uri),
262− "secret resolution",
263− );
252+ const svc = yield* getServiceFromConfig(config, ctx, timeoutMs);
253+ return yield* svc.resolveSecret(uri);
264254 }),
265255 };
266256
packages/plugins/onepassword/src/sdk/service.ts+221−0
@@ -0,0 +1,221 @@
1+import { Context, Duration, Effect } from "effect";
2+import { createClient, DesktopAuth } from "@1password/sdk";
3+import * as op from "@1password/op-js";
4+
5+import { OnePasswordError } from "./errors";
6+
7+// ---------------------------------------------------------------------------
8+// Canonical service interface — all backends (SDK, CLI) implement this
9+// ---------------------------------------------------------------------------
10+
11+export interface OnePasswordVault {
12+ readonly id: string;
13+ readonly title: string;
14+}
15+
16+export interface OnePasswordItem {
17+ readonly id: string;
18+ readonly title: string;
19+}
20+
21+export interface OnePasswordService {
22+ /** Resolve a secret by op:// URI */
23+ readonly resolveSecret: (
24+ uri: string,
25+ ) => Effect.Effect<string, OnePasswordError>;
26+
27+ /** List accessible vaults */
28+ readonly listVaults: () => Effect.Effect<
29+ ReadonlyArray<OnePasswordVault>,
30+ OnePasswordError
31+ >;
32+
33+ /** List items in a vault */
34+ readonly listItems: (
35+ vaultId: string,
36+ ) => Effect.Effect<ReadonlyArray<OnePasswordItem>, OnePasswordError>;
37+}
38+
39+export class OnePasswordServiceTag extends Context.Tag(
40+ "@executor/plugin-onepassword/OnePasswordService",
41+)<OnePasswordServiceTag, OnePasswordService>() {}
42+
43+// ---------------------------------------------------------------------------
44+// Resolved auth — raw credentials ready for any backend
45+// ---------------------------------------------------------------------------
46+
47+export type ResolvedAuth =
48+ | { readonly kind: "desktop-app"; readonly accountName: string }
49+ | { readonly kind: "service-account"; readonly token: string };
50+
51+// ---------------------------------------------------------------------------
52+// SDK backend — uses @1password/sdk native IPC
53+// ---------------------------------------------------------------------------
54+
55+const DEFAULT_TIMEOUT_MS = 15_000;
56+
57+const makeTimeoutMessage = (operation: string, timeoutMs: number): string =>
58+ [
59+ `${operation}: timed out after ${Math.floor(timeoutMs / 1000)}s.`,
60+ "Troubleshooting:",
61+ "1. Make sure the 1Password desktop app is open and unlocked",
62+ "2. Check for an approval prompt in the 1Password app — it may be behind other windows",
63+ "3. Ensure 'Developer > Connect with 1Password CLI' is enabled in 1Password Settings",
64+ "4. Make sure no other app or terminal is waiting for 1Password approval (only one prompt at a time)",
65+ "5. Try quitting 1Password completely and reopening it, then retry",
66+ ].join("\n");
67+
68+export const makeNativeSdkService = (
69+ auth: ResolvedAuth,
70+ timeoutMs: number = DEFAULT_TIMEOUT_MS,
71+): Effect.Effect<OnePasswordService, OnePasswordError> =>
72+ Effect.gen(function* () {
73+ const timeout = Duration.millis(timeoutMs);
74+
75+ const client = yield* Effect.tryPromise({
76+ try: () =>
77+ createClient({
78+ auth:
79+ auth.kind === "desktop-app"
80+ ? new DesktopAuth(auth.accountName)
81+ : auth.token,
82+ integrationName: "Executor",
83+ integrationVersion: "0.0.0",
84+ }),
85+ catch: (cause) =>
86+ new OnePasswordError({
87+ operation: "client setup",
88+ message: cause instanceof Error ? cause.message : String(cause),
89+ }),
90+ }).pipe(
91+ Effect.timeoutFail({
92+ duration: timeout,
93+ onTimeout: () =>
94+ new OnePasswordError({
95+ operation: "client setup",
96+ message: makeTimeoutMessage("client setup", timeoutMs),
97+ }),
98+ }),
99+ );
100+
101+ const wrap = <A>(
102+ fn: () => Promise<A>,
103+ operation: string,
104+ ): Effect.Effect<A, OnePasswordError> =>
105+ Effect.tryPromise({
106+ try: fn,
107+ catch: (cause) =>
108+ new OnePasswordError({
109+ operation,
110+ message: cause instanceof Error ? cause.message : String(cause),
111+ }),
112+ }).pipe(
113+ Effect.timeoutFail({
114+ duration: timeout,
115+ onTimeout: () =>
116+ new OnePasswordError({
117+ operation,
118+ message: makeTimeoutMessage(operation, timeoutMs),
119+ }),
120+ }),
121+ Effect.withSpan(`onepassword.sdk.${operation}`),
122+ );
123+
124+ return OnePasswordServiceTag.of({
125+ resolveSecret: (uri) =>
126+ wrap(() => client.secrets.resolve(uri), "secret resolution"),
127+
128+ listVaults: () =>
129+ wrap(
130+ () => client.vaults.list({ decryptDetails: true }),
131+ "vault listing",
132+ ).pipe(
133+ Effect.map((vaults) =>
134+ vaults.map((v) => ({ id: v.id, title: v.title })),
135+ ),
136+ ),
137+
138+ listItems: (vaultId) =>
139+ wrap(() => client.items.list(vaultId), "item listing").pipe(
140+ Effect.map((items) =>
141+ items.map((i) => ({ id: i.id, title: i.title })),
142+ ),
143+ ),
144+ });
145+ }).pipe(Effect.withSpan("onepassword.sdk.make_service"));
146+
147+// ---------------------------------------------------------------------------
148+// CLI backend — uses @1password/op-js (shells out to `op` CLI)
149+// ---------------------------------------------------------------------------
150+
151+export const makeCliService = (
152+ auth: ResolvedAuth,
153+): Effect.Effect<OnePasswordService, OnePasswordError> =>
154+ Effect.gen(function* () {
155+ // Configure auth
156+ if (auth.kind === "service-account") {
157+ op.setServiceAccount(auth.token);
158+ } else {
159+ op.setGlobalFlags({ account: auth.accountName });
160+ }
161+
162+ const wrapSync = <A>(
163+ fn: () => A,
164+ operation: string,
165+ ): Effect.Effect<A, OnePasswordError> =>
166+ Effect.try({
167+ try: fn,
168+ catch: (cause) =>
169+ new OnePasswordError({
170+ operation,
171+ message: cause instanceof Error ? cause.message : String(cause),
172+ }),
173+ }).pipe(Effect.withSpan(`onepassword.cli.${operation}`));
174+
175+ return OnePasswordServiceTag.of({
176+ resolveSecret: (uri) =>
177+ wrapSync(() => op.read.parse(uri), "secret resolution"),
178+
179+ listVaults: () =>
180+ wrapSync(() => op.vault.list(), "vault listing").pipe(
181+ Effect.map((vaults) =>
182+ vaults.map((v) => ({ id: v.id, title: v.name })),
183+ ),
184+ ),
185+
186+ listItems: (vaultId) =>
187+ wrapSync(
188+ () => op.item.list({ vault: vaultId }),
189+ "item listing",
190+ ).pipe(
191+ Effect.map((items) =>
192+ items.map((i) => ({ id: i.id, title: i.title })),
193+ ),
194+ ),
195+ });
196+ }).pipe(Effect.withSpan("onepassword.cli.make_service"));
197+
198+// ---------------------------------------------------------------------------
199+// Smart factory — tries CLI first (avoids IPC hang), falls back to SDK
200+// ---------------------------------------------------------------------------
201+
202+export const makeOnePasswordService = (
203+ auth: ResolvedAuth,
204+ options?: { readonly preferSdk?: boolean; readonly timeoutMs?: number },
205+): Effect.Effect<OnePasswordService, OnePasswordError> => {
206+ const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
207+
208+ if (options?.preferSdk) {
209+ return makeNativeSdkService(auth, timeoutMs);
210+ }
211+
212+ // Default: prefer CLI to avoid the IPC hang bug
213+ return makeCliService(auth).pipe(
214+ Effect.catchAll((cliError) =>
215+ // CLI unavailable (e.g. `op` not installed) — fall back to SDK
216+ makeNativeSdkService(auth, timeoutMs).pipe(
217+ Effect.mapError(() => cliError),
218+ ),
219+ ),
220+ );
221+};