@monster-orange/den 0.4.19 → 0.4.21

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.
Files changed (35) hide show
  1. package/CHANGELOG.md +122 -0
  2. package/README.md +7 -7
  3. package/docs/distributions.md +121 -13
  4. package/package.json +1 -1
  5. package/src/shared/attest-code.ts +125 -0
  6. package/src/shared/claim-code.ts +6 -2
  7. package/src/{ui/server/edge-exposure.ts → shared/edge/exposure.ts} +7 -1
  8. package/src/shared/github-app.ts +4 -0
  9. package/src/shared/github-device.ts +38 -1
  10. package/src/shared/owner-github.ts +100 -0
  11. package/src/shared/product.ts +45 -5
  12. package/src/shared/profile.ts +26 -2
  13. package/src/shared/secrets/index.ts +71 -3
  14. package/src/shared/secrets/onepassword.ts +87 -14
  15. package/src/shared/secrets/op-token.ts +270 -0
  16. package/src/shared/secrets/secret-env.ts +19 -6
  17. package/src/shared/secrets/strategies.ts +6 -0
  18. package/src/shared/secrets/types.ts +8 -0
  19. package/src/shared/web-session.ts +112 -6
  20. package/src/ui/cli/create.ts +92 -6
  21. package/src/ui/cli/doctor-command.ts +108 -17
  22. package/src/ui/cli/doctor-owner.ts +122 -0
  23. package/src/ui/cli/service-command.ts +16 -0
  24. package/src/ui/cli/web-command.ts +74 -12
  25. package/src/ui/server/device-flows.ts +45 -10
  26. package/src/ui/server/github-routes.ts +169 -16
  27. package/src/ui/server/index.ts +148 -13
  28. package/src/ui/server/public/error-page.js +115 -33
  29. package/src/ui/server/public/error.html +16 -0
  30. package/src/ui/server/public/header.js +5 -7
  31. package/src/ui/server/public/i18n/es.js +11 -1
  32. package/src/ui/server/public/i18n/ja.js +11 -1
  33. package/src/ui/server/public/settings-registry.js +23 -1
  34. package/src/ui/server/public/shell.js +11 -1
  35. package/src/ui/server/route-classes.ts +7 -0
@@ -0,0 +1,100 @@
1
+ /**
2
+ * `web.owner_github` (D17): the GitHub login of the person who runs this
3
+ * instance, recorded in config/local/user.yml at `web up` or `service
4
+ * install` from `gh` when it is signed in here and no owner is linked yet.
5
+ * With it, that person connects their GitHub account from any browser, on
6
+ * any machine (a devbox they reach from a laptop), and nobody else can
7
+ * claim the instance from another machine (github-routes.ts decideFlow).
8
+ *
9
+ * Only the login is kept: never a token. `gh api user` runs with a short
10
+ * timeout; gh missing or signed out records nothing.
11
+ */
12
+ import { execFileSync } from "child_process";
13
+ import { readUserConfig, writeUserConfig } from "./den-config.js";
14
+ import { parseGithubName } from "./web-session.js";
15
+ import { readWebOwner } from "./web-owner.js";
16
+
17
+ export interface OwnerGithubDeps {
18
+ /** The login `gh` is signed in as here, or null. */
19
+ ghLogin?: () => string | null;
20
+ /** Whether an owner is linked already. */
21
+ ownerLinked?: () => boolean;
22
+ read?: typeof readUserConfig;
23
+ write?: typeof writeUserConfig;
24
+ }
25
+
26
+ /** `gh api user`'s login, or null (gh missing, signed out, slow, odd output). */
27
+ export function ghLogin(env: NodeJS.ProcessEnv = process.env): string | null {
28
+ try {
29
+ const out = execFileSync("gh", ["api", "user", "--jq", ".login"], {
30
+ env,
31
+ timeout: 8_000,
32
+ stdio: ["ignore", "pipe", "ignore"],
33
+ encoding: "utf-8",
34
+ }).trim();
35
+ return parseGithubName(out, "gh api user", "gh") ?? null;
36
+ } catch {
37
+ return null;
38
+ }
39
+ }
40
+
41
+ export type OwnerGithubResult =
42
+ | { state: "linked" }
43
+ | { state: "set"; login: string }
44
+ | { state: "recorded"; login: string }
45
+ | { state: "none" };
46
+
47
+ /**
48
+ * Record `web.owner_github` from gh when no owner is linked and it isn't
49
+ * set: `recorded`. Already set: `set`. An owner linked: `linked` (nothing
50
+ * to do). gh can't say: `none`. Never throws.
51
+ */
52
+ export function recordOwnerGithub(
53
+ deps: OwnerGithubDeps = {},
54
+ ): OwnerGithubResult {
55
+ try {
56
+ if ((deps.ownerLinked ?? (() => readWebOwner() !== null))())
57
+ return { state: "linked" };
58
+ const read = deps.read ?? readUserConfig;
59
+ const cfg = read();
60
+ const web = { ...((cfg as { web?: Record<string, unknown> }).web ?? {}) };
61
+ let set: string | undefined;
62
+ try {
63
+ set = parseGithubName(
64
+ web.owner_github,
65
+ "web.owner_github",
66
+ "config/local/user.yml",
67
+ );
68
+ } catch {
69
+ return { state: "none" }; // a value someone typed: leave it alone
70
+ }
71
+ if (set) return { state: "set", login: set };
72
+ const login = (deps.ghLogin ?? ghLogin)();
73
+ if (!login) return { state: "none" };
74
+ web.owner_github = login;
75
+ (deps.write ?? writeUserConfig)({ ...cfg, web } as typeof cfg);
76
+ return { state: "recorded", login };
77
+ } catch {
78
+ return { state: "none" };
79
+ }
80
+ }
81
+
82
+ /** The line `web up` and `service install` print, or null. */
83
+ export function ownerGithubLine(
84
+ r: OwnerGithubResult,
85
+ cli = "den",
86
+ ): string | null {
87
+ if (r.state === "recorded")
88
+ return `GitHub owner: @${r.login} (from gh; web.owner_github in config/local/user.yml). Connect your GitHub account from any browser to sign in.`;
89
+ if (r.state === "none") return ghNotSignedInHint(cli);
90
+ return null;
91
+ }
92
+
93
+ /** No owner, no web.owner_github, and gh can't say who you are: the fix. */
94
+ export function ghNotSignedInHint(cli = "den"): string {
95
+ return (
96
+ "GitHub owner: not known, because gh isn't signed in here. " +
97
+ `Run \`gh auth login\`, then \`${cli} web restart\`; or set web.owner_github: <your GitHub login> in config/local/user.yml. ` +
98
+ "Then you can connect your GitHub account from any browser."
99
+ );
100
+ }
@@ -8,6 +8,9 @@
8
8
  * distribution root can be found (e.g. help text printed outside a checkout).
9
9
  */
10
10
 
11
+ import * as fs from "node:fs";
12
+ import * as path from "node:path";
13
+ import { parse as parseYaml } from "yaml";
11
14
  import { getDistRoot } from "./paths.js";
12
15
  import {
13
16
  loadProfile,
@@ -17,7 +20,11 @@ import {
17
20
  } from "./profile.js";
18
21
 
19
22
  export interface Product extends ProductProfile {
20
- /** Display name: the name with its first letter upper-cased ("Den"). */
23
+ /**
24
+ * Display name (D11): the org config's brand.name ("Acme Den") when it is
25
+ * safe to show anywhere (TITLE_SAFE), else the name with its first letter
26
+ * upper-cased ("Acme-den").
27
+ */
21
28
  title: string;
22
29
  /** The YAML manifest file name (".den.yml"). */
23
30
  manifestYml: string;
@@ -25,11 +32,43 @@ export interface Product extends ProductProfile {
25
32
  manifestJson: string;
26
33
  }
27
34
 
28
- function withDerived(p: ProductProfile): Product {
35
+ /**
36
+ * What a brand.name may hold to be the display title: letters, digits,
37
+ * spaces, ".", "_" and "-", up to 40. The title is filled into page scripts
38
+ * (inside "…" and '…' strings) and HTML attributes, so no quote, backslash,
39
+ * "<" or "&" may reach it (the 0.4.20 hyphen bug's kin).
40
+ */
41
+ export const TITLE_SAFE = /^[\p{L}\p{N}][\p{L}\p{N} ._-]{0,39}$/u;
42
+
43
+ /** The name, first letter upper-cased: "acme-den" → "Acme-den". */
44
+ export function nameTitle(name: string): string {
45
+ return name.charAt(0).toUpperCase() + name.slice(1);
46
+ }
47
+
48
+ /** The org config's brand.name, trimmed, or undefined. Never throws. */
49
+ export function brandNameAt(root: string | null, configFile: string) {
50
+ if (!root) return undefined;
51
+ try {
52
+ const raw = parseYaml(
53
+ fs.readFileSync(path.join(root, configFile), "utf-8"),
54
+ ) as { brand?: { name?: unknown } } | null;
55
+ const n = raw?.brand?.name;
56
+ return typeof n === "string" && n.trim() ? n.trim() : undefined;
57
+ } catch {
58
+ return undefined;
59
+ }
60
+ }
61
+
62
+ /** The display title: a safe brand.name, else nameTitle. */
63
+ export function titleOf(name: string, brandName?: string): string {
64
+ return brandName && TITLE_SAFE.test(brandName) ? brandName : nameTitle(name);
65
+ }
66
+
67
+ function withDerived(p: ProductProfile, root: string | null = null): Product {
29
68
  const yml = p.manifestFiles[0];
30
69
  return {
31
70
  ...p,
32
- title: p.name.charAt(0).toUpperCase() + p.name.slice(1),
71
+ title: titleOf(p.name, brandNameAt(root, p.configFile)),
33
72
  manifestYml: yml,
34
73
  manifestJson: p.manifestFiles[1] ?? yml.replace(/\.ya?ml$/, "") + ".json",
35
74
  };
@@ -40,10 +79,11 @@ let derived: { profile: ProductProfile; product: Product } | null = null;
40
79
  /** The running product. Never throws. */
41
80
  export function product(): Product {
42
81
  try {
43
- const profile = loadProfile(getDistRoot());
82
+ const root = getDistRoot();
83
+ const profile = loadProfile(root);
44
84
  // Same profile object (loadProfile caches it): same derived names.
45
85
  if (derived?.profile !== profile)
46
- derived = { profile, product: withDerived(profile) };
86
+ derived = { profile, product: withDerived(profile, root) };
47
87
  return derived.product;
48
88
  } catch {
49
89
  const pinned = process.env.DEN_PROFILE?.trim();
@@ -58,6 +58,13 @@ interface RawProduct {
58
58
  }
59
59
 
60
60
  const SLUG = /^[a-z][a-z0-9-]*$/;
61
+ /**
62
+ * A path the pages show (config, manifest, services and workspaces dirs):
63
+ * letters, digits, ".", "_", "-" and "/". The web pages carry these
64
+ * names as placeholders filled when served (product.ts productTokens), so
65
+ * a quote, backslash, "$" or "<" in one would break a page's script.
66
+ */
67
+ const SAFE_PATH = /^[A-Za-z0-9._/-]+$/;
61
68
  const PREFIX = /^[A-Z][A-Z0-9_]*$/;
62
69
 
63
70
  const str = (v: unknown): string | undefined =>
@@ -83,7 +90,7 @@ export function profileFrom(
83
90
  const name = str(raw.name) ?? "den";
84
91
  if (!SLUG.test(name))
85
92
  throw new Error(
86
- `den.yml: product.name "${name}" must be lower-case letters, digits and "-"`,
93
+ `den.yml: product.name "${name}" must be lower-case letters, digits and "-", starting with a letter`,
87
94
  );
88
95
  const envPrefix =
89
96
  str(raw.env_prefix) ?? name.toUpperCase().replace(/-/g, "_");
@@ -91,11 +98,28 @@ export function profileFrom(
91
98
  throw new Error(
92
99
  `den.yml: product.env_prefix "${envPrefix}" must be upper-case letters, digits and "_"`,
93
100
  );
101
+ const cli = str(raw.cli) ?? name;
102
+ if (!SLUG.test(cli))
103
+ throw new Error(
104
+ `den.yml: product.cli "${cli}" must be lower-case letters, digits and "-", starting with a letter`,
105
+ );
106
+ for (const key of [
107
+ "config",
108
+ "manifest",
109
+ "services_dir",
110
+ "workspaces_dir",
111
+ ] as const) {
112
+ const v = str(raw[key]);
113
+ if (v !== undefined && !SAFE_PATH.test(v))
114
+ throw new Error(
115
+ `den.yml: product.${key} "${v}" may use only letters, digits, ".", "_", "-" and "/"`,
116
+ );
117
+ }
94
118
  const manifest = str(raw.manifest) ?? `.${name}.yml`;
95
119
  const json = manifest.replace(/\.ya?ml$/, ".json");
96
120
  return {
97
121
  name,
98
- cli: str(raw.cli) ?? name,
122
+ cli,
99
123
  envPrefix,
100
124
  configFile: str(raw.config) ?? `config/${name}.yml`,
101
125
  manifestFiles: json === manifest ? [manifest] : [manifest, json],
@@ -17,6 +17,12 @@
17
17
  */
18
18
  import { readDenConfig, readUserConfig } from "../den-config.js";
19
19
  import type { DetectResult } from "../strategies/registry.js";
20
+ import { ConfigError } from "../config-error.js";
21
+ import {
22
+ describeOpTokenSource,
23
+ opTokenSourceError,
24
+ parseOpTokenSource,
25
+ } from "./op-token.js";
20
26
  import { notConfiguredMessage, SecretsNotConfiguredError } from "./none.js";
21
27
  import { secretsStrategy, type SecretsStrategy } from "./strategies.js";
22
28
  import type {
@@ -37,6 +43,7 @@ export { notConfiguredMessage, SecretsNotConfiguredError } from "./none.js";
37
43
  export { secretsStrategy, secretsStrategyIds } from "./strategies.js";
38
44
  export type { SecretsStrategy, SecretsSetupContext } from "./strategies.js";
39
45
  export * from "./secret-env.js";
46
+ export * from "./op-token.js";
40
47
 
41
48
  export interface SecretsConfig {
42
49
  provider?: SecretsProviderName;
@@ -48,7 +55,16 @@ export interface SecretsConfig {
48
55
  */
49
56
  required?: boolean;
50
57
  doppler?: { project?: string; config?: string };
51
- onepassword?: { account?: string };
58
+ onepassword?: {
59
+ account?: string;
60
+ /**
61
+ * Where the service-account token comes from: `keychain:<service>`,
62
+ * `file:<path>` (mode 600) or `env:<VAR>` (the default,
63
+ * `env:OP_SERVICE_ACCOUNT_TOKEN`). Per machine: read from
64
+ * config/local/user.yml only, never from the distribution's config.
65
+ */
66
+ token?: string;
67
+ };
52
68
  /** `"vault/item/field": FLAT_KEY` overrides for doppler/env. */
53
69
  keys?: Record<string, string>;
54
70
  /**
@@ -126,11 +142,32 @@ export function readSecretsConfig(): SecretsConfig {
126
142
  } catch {
127
143
  /* no user config */
128
144
  }
145
+ // The token source is this machine's: the distribution's config never
146
+ // names one, and a bad one is refused here, naming the key.
147
+ const tokenErr =
148
+ user.onepassword?.token === undefined || user.onepassword.token === null
149
+ ? null
150
+ : opTokenSourceError(user.onepassword.token);
151
+ if (tokenErr) {
152
+ const key = "secrets.onepassword.token";
153
+ throw new ConfigError([
154
+ {
155
+ file: "config/local/user.yml",
156
+ key,
157
+ message: tokenErr,
158
+ text: `${key} ${tokenErr}`,
159
+ },
160
+ ]);
161
+ }
129
162
  return {
130
163
  ...team,
131
164
  ...user,
132
165
  doppler: { ...team.doppler, ...user.doppler },
133
- onepassword: { ...team.onepassword, ...user.onepassword },
166
+ onepassword: {
167
+ ...team.onepassword,
168
+ ...user.onepassword,
169
+ token: user.onepassword?.token ?? undefined,
170
+ },
134
171
  keys: { ...team.keys, ...user.keys },
135
172
  refs: { ...team.refs, ...user.refs },
136
173
  };
@@ -251,6 +288,14 @@ class DetectedProvider implements SecretsProvider {
251
288
  async tryGet(ref: SecretRef): Promise<string | null> {
252
289
  return this.unavailable() ? null : this.inner.tryGet(ref);
253
290
  }
291
+ async tryGetWithReason(
292
+ ref: SecretRef,
293
+ ): Promise<{ value: string | null; reason?: string }> {
294
+ const why = this.unavailable();
295
+ if (why) return { value: null, reason: why };
296
+ if (this.inner.tryGetWithReason) return this.inner.tryGetWithReason(ref);
297
+ return { value: await this.inner.tryGet(ref) };
298
+ }
254
299
  async set(ref: SecretRef, value: string): Promise<void> {
255
300
  const why = this.unavailable();
256
301
  if (why) throw new Error(`${this.name} secrets backend ${why}`);
@@ -364,6 +409,24 @@ export function secretsProcessEnv(
364
409
  return strategyFor(name).capabilities.env?.(cfg, env) ?? {};
365
410
  }
366
411
 
412
+ /**
413
+ * Settings › Secrets: where the 1Password token comes from, as text
414
+ * ("keychain: <service>", "file: <path>", "env: <VAR>") or "not set". The
415
+ * source only, never the token; reads nothing from the keychain or the file.
416
+ */
417
+ export function opTokenSourceSetting(
418
+ cfg: SecretsConfig | (() => SecretsConfig) = readSecretsConfig,
419
+ ): string {
420
+ try {
421
+ const raw = (typeof cfg === "function" ? cfg() : cfg).onepassword?.token;
422
+ return raw === undefined || raw === null
423
+ ? "not set"
424
+ : describeOpTokenSource(parseOpTokenSource(raw));
425
+ } catch {
426
+ return "invalid: see secrets.onepassword.token";
427
+ }
428
+ }
429
+
367
430
  /** The engine's own named refs, for the legacy warning. */
368
431
  export const ENGINE_SECRET_REFS = ["sudo", "journeys_agent_key"] as const;
369
432
 
@@ -379,9 +442,14 @@ export const IMPLICIT_PROVIDER_WARNING =
379
442
  * distribution that needs no secrets never hears about it.
380
443
  */
381
444
  export function secretsConfigWarnings(
382
- cfg: SecretsConfig = readSecretsConfig(),
445
+ cfg?: SecretsConfig,
383
446
  env: NodeJS.ProcessEnv = process.env,
384
447
  ): string[] {
448
+ try {
449
+ cfg ??= readSecretsConfig();
450
+ } catch (err) {
451
+ return [(err as Error).message];
452
+ }
385
453
  let r: ResolvedProvider;
386
454
  try {
387
455
  r = resolveProvider(cfg, env);
@@ -5,27 +5,95 @@
5
5
  import { execFile } from "child_process";
6
6
  import { promisify } from "util";
7
7
  import type { PreflightResult, SecretRef, SecretsProvider } from "./types.js";
8
+ import {
9
+ DEFAULT_OP_TOKEN_SOURCE,
10
+ OP_TOKEN_ENV,
11
+ OpFailure,
12
+ mapOpFailure,
13
+ readOpToken,
14
+ type OpTokenDeps,
15
+ type OpTokenSource,
16
+ } from "./op-token.js";
8
17
 
9
18
  import { product } from "../product.js";
10
19
  const execFileAsync = promisify(execFile);
11
20
 
12
21
  export interface OnePasswordOptions {
13
22
  account?: string;
23
+ /**
24
+ * Where the service-account token comes from (`secrets.onepassword.token`).
25
+ * Unset: the default `env:OP_SERVICE_ACCOUNT_TOKEN`, and `op` is run with
26
+ * this process's own env even when that has none (a signed-in desktop app
27
+ * still works).
28
+ */
29
+ tokenSource?: OpTokenSource;
30
+ /** Test seam: the env, platform and home the token source is read with. */
31
+ tokenDeps?: OpTokenDeps;
14
32
  timeoutMs?: number;
15
33
  }
16
34
 
17
35
  export class OnePasswordProvider implements SecretsProvider {
18
36
  readonly name = "1password" as const;
19
37
  private readonly timeout: number;
38
+ private readonly tokenSource: OpTokenSource | undefined;
39
+ private readonly tokenDeps: OpTokenDeps | undefined;
20
40
 
21
41
  constructor(opts: OnePasswordOptions = {}) {
22
42
  this.timeout = opts.timeoutMs ?? 60_000;
43
+ this.tokenSource = opts.tokenSource;
44
+ this.tokenDeps = opts.tokenDeps;
23
45
  if (opts.account) process.env.OP_ACCOUNT ||= opts.account;
24
46
  }
25
47
 
48
+ /**
49
+ * The options for one `op` call. The token is read now (never cached) and
50
+ * goes only into the child's env. A configured source that yields none
51
+ * fails with its reason; the default source with no token runs `op` as
52
+ * before.
53
+ */
54
+ private async opOptions(): Promise<{
55
+ timeout: number;
56
+ env?: NodeJS.ProcessEnv;
57
+ token?: string;
58
+ }> {
59
+ const explicit = this.tokenSource !== undefined;
60
+ const t = await readOpToken(
61
+ this.tokenSource ?? DEFAULT_OP_TOKEN_SOURCE,
62
+ this.tokenDeps,
63
+ );
64
+ if (!t.ok) {
65
+ if (explicit) throw new OpFailure("token_source", t.reason);
66
+ return { timeout: this.timeout, env: process.env };
67
+ }
68
+ return {
69
+ timeout: this.timeout,
70
+ env: { ...process.env, [OP_TOKEN_ENV]: t.token },
71
+ token: t.token,
72
+ };
73
+ }
74
+
75
+ /** Run `op`; a failure carries op's reason in plain words, never the token. */
76
+ private async run(args: string[], rawErrors = false): Promise<string> {
77
+ const { token, ...opts } = await this.opOptions();
78
+ try {
79
+ const { stdout } = await execFileAsync("op", args, opts);
80
+ return String(stdout);
81
+ } catch (err) {
82
+ // `set` keeps op's own error: its caller strips the value it wrote.
83
+ if (rawErrors) throw err;
84
+ const e = err as { stderr?: unknown; killed?: boolean; code?: unknown };
85
+ if (e.killed)
86
+ throw new OpFailure("unknown", "1Password failed: op timed out");
87
+ if (e.code === "ENOENT")
88
+ throw new OpFailure("unknown", "1Password failed: op is not installed");
89
+ const m = mapOpFailure(String(e.stderr ?? ""), token ? [token] : []);
90
+ throw new OpFailure(m.code, m.text);
91
+ }
92
+ }
93
+
26
94
  async isReady(): Promise<boolean> {
27
95
  try {
28
- await execFileAsync("op", ["whoami"], { timeout: this.timeout });
96
+ await this.run(["whoami"]);
29
97
  return true;
30
98
  } catch {
31
99
  return false;
@@ -37,18 +105,26 @@ export class OnePasswordProvider implements SecretsProvider {
37
105
  const args = ["item", "get", ref.item];
38
106
  if (ref.vault) args.push("--vault", ref.vault);
39
107
  args.push("--fields", `label=${ref.field}`, "--reveal");
40
- const { stdout } = await execFileAsync("op", args, {
41
- timeout: this.timeout,
42
- });
43
- return String(stdout).trim();
108
+ return (await this.run(args)).trim();
44
109
  }
45
110
 
46
111
  async tryGet(ref: SecretRef): Promise<string | null> {
112
+ return (await this.tryGetWithReason(ref)).value;
113
+ }
114
+
115
+ async tryGetWithReason(
116
+ ref: SecretRef,
117
+ ): Promise<{ value: string | null; reason?: string }> {
47
118
  try {
48
119
  const v = await this.get(ref);
49
- return v.length > 0 ? v : null;
50
- } catch {
51
- return null;
120
+ return v.length > 0
121
+ ? { value: v }
122
+ : { value: null, reason: "the field is empty" };
123
+ } catch (err) {
124
+ return {
125
+ value: null,
126
+ reason: err instanceof OpFailure ? err.message : undefined,
127
+ };
52
128
  }
53
129
  }
54
130
 
@@ -57,13 +133,10 @@ export class OnePasswordProvider implements SecretsProvider {
57
133
  const vault = ref.vault ? ["--vault", ref.vault] : [];
58
134
  const field = `${ref.field}[password]=${value}`;
59
135
  try {
60
- await execFileAsync("op", ["item", "edit", ref.item, ...vault, field], {
61
- timeout: this.timeout,
62
- });
136
+ await this.run(["item", "edit", ref.item, ...vault, field], true);
63
137
  } catch (err) {
64
138
  try {
65
- await execFileAsync(
66
- "op",
139
+ await this.run(
67
140
  [
68
141
  "item",
69
142
  "create",
@@ -74,7 +147,7 @@ export class OnePasswordProvider implements SecretsProvider {
74
147
  ...vault,
75
148
  field,
76
149
  ],
77
- { timeout: this.timeout },
150
+ true,
78
151
  );
79
152
  } catch {
80
153
  throw err;