@forgezero/vault 0.1.23 → 0.1.25

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/dist/index.d.ts CHANGED
@@ -35,7 +35,7 @@ export declare class VaultError extends Error {
35
35
  readonly code: string;
36
36
  constructor(code: string, message: string);
37
37
  }
38
- export type CredentialMode = 'managed' | 'external';
38
+ export type CredentialMode = 'managed' | 'external' | 'local';
39
39
  export interface Credential {
40
40
  mode: CredentialMode;
41
41
  /** MANAGED only — the agent socket serving the environment's RAM replica. */
@@ -43,6 +43,11 @@ export interface Credential {
43
43
  /** EXTERNAL only — a seed, never transmitted; a keypair derives from it. */
44
44
  apiKey?: string;
45
45
  }
46
+ export interface LocalVaultStorage {
47
+ getItem(key: string): string | null;
48
+ setItem(key: string, value: string): void;
49
+ removeItem(key: string): void;
50
+ }
46
51
  export interface DiscoveryEnvironment {
47
52
  env?: Record<string, string | undefined>;
48
53
  socketPath?: string;
@@ -142,6 +147,10 @@ export interface VaultOptions {
142
147
  runtimeRequest?: RuntimeRequest;
143
148
  credential?: Credential;
144
149
  discovery?: DiscoveryEnvironment;
150
+ /** Browser-compatible persistence used only by explicit `local` development mode. */
151
+ localStorage?: LocalVaultStorage;
152
+ /** Separates multiple local applications that intentionally share one Storage implementation. */
153
+ localNamespace?: string;
145
154
  }
146
155
  export interface EntryMeta {
147
156
  name: string;
@@ -168,9 +177,15 @@ export declare class ForgeZero {
168
177
  private readonly requestTimeoutMs;
169
178
  private readonly project;
170
179
  private readonly environment;
180
+ private readonly localStorage?;
181
+ private readonly localNamespace;
171
182
  constructor(options?: VaultOptions);
172
183
  private scope;
173
184
  private request;
185
+ private localStorageKey;
186
+ private localValues;
187
+ private saveLocalValues;
188
+ private localRequest;
174
189
  private managedRequest;
175
190
  /** One value, current version unless asked otherwise. */
176
191
  get(name: string, options?: {
@@ -258,6 +273,8 @@ export declare class ForgeZero {
258
273
  * constructor that happens to do I/O-adjacent work.
259
274
  */
260
275
  export declare const createVault: (options?: VaultOptions) => ForgeZero;
276
+ /** Explicit isolated laptop/browser development. This mode never performs HTTP. */
277
+ export declare const createLocalVault: (options?: Omit<VaultOptions, "credential">) => ForgeZero;
261
278
  export declare function vaultCredentials(vault: ForgeZero): {
262
279
  readonly name: string;
263
280
  get(reference: string, field: string): Promise<string>;
@@ -296,4 +313,4 @@ export declare function systemdCredentials(options?: SystemdCredentialOptions):
296
313
  readonly name: 'systemd';
297
314
  get(reference: string, field: string): Promise<string>;
298
315
  };
299
- export declare const VERSION = "0.1.23";
316
+ export declare const VERSION = "0.1.25";
package/dist/index.js CHANGED
@@ -22,8 +22,8 @@ class VaultRuntime {
22
22
  this.transport = transport;
23
23
  }
24
24
  available() {
25
- if (this.transport === "managed-read-only") {
26
- throw new VaultRuntimeClientError("MANAGED_RUNTIME_DELEGATION_REQUIRED", "Vault runtime operations require a separately granted external API key; the managed Agent replica is read-only.");
25
+ if (this.transport === "managed-read-only" || this.transport === "local-read-only") {
26
+ throw new VaultRuntimeClientError("MANAGED_RUNTIME_DELEGATION_REQUIRED", this.transport === "local-read-only" ? "Local Vault mode stores development values only; master-seed runtime operations require a separately granted live API key." : "Vault runtime operations require a separately granted external API key; the managed Agent replica is read-only.");
27
27
  }
28
28
  }
29
29
  async create(options) {
@@ -156,7 +156,9 @@ function discover(environment = {}) {
156
156
  const apiKey = env.FORGEZERO_API_KEY;
157
157
  if (apiKey)
158
158
  return { mode: "external", apiKey };
159
- throw new VaultError("NO_CREDENTIAL", `No credential found. On managed compute the agent socket at ${socketPath} provides one; ` + "elsewhere set FORGEZERO_API_KEY.");
159
+ if (env.FORGEZERO_LOCAL_VAULT === "1")
160
+ return { mode: "local" };
161
+ throw new VaultError("NO_CREDENTIAL", `No credential found. On managed compute the agent socket at ${socketPath} provides one; ` + "elsewhere set FORGEZERO_API_KEY, or explicitly set FORGEZERO_LOCAL_VAULT=1 for isolated local development.");
160
162
  }
161
163
  var unbase64url = (encoded) => {
162
164
  const padded = encoded.replaceAll("-", "+").replaceAll("_", "/") + "=".repeat((4 - encoded.length % 4) % 4);
@@ -264,6 +266,8 @@ class ForgeZero {
264
266
  requestTimeoutMs;
265
267
  project;
266
268
  environment;
269
+ localStorage;
270
+ localNamespace;
267
271
  constructor(options = {}) {
268
272
  const credential = options.credential ?? discover(options.discovery);
269
273
  this.credential = credential.mode === "managed" && !credential.socketPath ? { ...credential, socketPath: DEFAULT_SOCKET } : credential;
@@ -277,8 +281,13 @@ class ForgeZero {
277
281
  }
278
282
  this.project = options.project ?? "default";
279
283
  this.environment = options.environment ?? "production";
284
+ this.localStorage = options.localStorage ?? (this.credential.mode === "local" && typeof globalThis.localStorage !== "undefined" ? globalThis.localStorage : undefined);
285
+ this.localNamespace = options.localNamespace?.trim() || "fz.local.vault.v1";
286
+ if (this.credential.mode === "local" && !this.localStorage) {
287
+ throw new VaultError("LOCAL_STORAGE_UNAVAILABLE", "Local Vault mode requires browser localStorage or an injected LocalVaultStorage adapter.");
288
+ }
280
289
  const runtimeRequest = options.runtimeRequest ?? ((path, init) => this.request(`/v1/vault/${this.scope()}/runtime${path}`, init));
281
- this.runtime = new VaultRuntime(runtimeRequest, this.credential.mode === "external" ? "external" : options.runtimeRequest ? "managed-delegated" : "managed-read-only");
290
+ this.runtime = new VaultRuntime(runtimeRequest, this.credential.mode === "external" ? "external" : options.runtimeRequest ? "managed-delegated" : this.credential.mode === "local" ? "local-read-only" : "managed-read-only");
282
291
  }
283
292
  scope() {
284
293
  return `${encodeURIComponent(this.project)}/${encodeURIComponent(this.environment)}`;
@@ -286,6 +295,8 @@ class ForgeZero {
286
295
  async request(path, init = {}) {
287
296
  if (this.credential.mode === "managed")
288
297
  return this.managedRequest(path, init);
298
+ if (this.credential.mode === "local")
299
+ return this.localRequest(path, init);
289
300
  const body = typeof init.body === "string" ? init.body : "";
290
301
  const target = new URL(path, this.apiUrl);
291
302
  const headers = {
@@ -326,6 +337,46 @@ class ForgeZero {
326
337
  }
327
338
  return payload;
328
339
  }
340
+ localStorageKey() {
341
+ return `${this.localNamespace}:${encodeURIComponent(this.project)}:${encodeURIComponent(this.environment)}`;
342
+ }
343
+ localValues() {
344
+ const raw = this.localStorage.getItem(this.localStorageKey());
345
+ if (raw === null)
346
+ return {};
347
+ try {
348
+ const parsed = JSON.parse(raw);
349
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed) || Object.entries(parsed).some(([key, value]) => !key || typeof value !== "string"))
350
+ throw new Error("invalid");
351
+ return parsed;
352
+ } catch {
353
+ throw new VaultError("LOCAL_STORE_MALFORMED", "The local Vault store is malformed. Clear it before continuing.");
354
+ }
355
+ }
356
+ saveLocalValues(values) {
357
+ this.localStorage.setItem(this.localStorageKey(), JSON.stringify(values));
358
+ }
359
+ async localRequest(path, init) {
360
+ const method = init.method ?? "GET";
361
+ const target = new URL(path, "https://vault.local.invalid");
362
+ const values = this.localValues();
363
+ const entry = /\/entries\/([^/]+)$/.exec(target.pathname);
364
+ if (method === "GET" && entry) {
365
+ const name = decodeURIComponent(entry[1]);
366
+ if (!(name in values))
367
+ throw new VaultError("NOT_FOUND", `No local Vault value named ${name}.`);
368
+ return { value: values[name] };
369
+ }
370
+ if (method === "GET" && target.pathname.endsWith("/list")) {
371
+ return { entries: Object.keys(values).sort().map((name) => ({ name })) };
372
+ }
373
+ if (method === "GET" && target.pathname.endsWith("/entries"))
374
+ return { values: { ...values } };
375
+ if (method === "GET" && target.pathname.endsWith("/changes")) {
376
+ return { version: 0, changed: [] };
377
+ }
378
+ throw new VaultError("LOCAL_OPERATION_UNSUPPORTED", "This operation is unavailable in isolated local Vault mode.");
379
+ }
329
380
  async managedRequest(path, init) {
330
381
  const socketPath = this.credential.socketPath;
331
382
  const method = init.method ?? "GET";
@@ -406,7 +457,7 @@ class ForgeZero {
406
457
  return result.value;
407
458
  }
408
459
  async getAll() {
409
- if (this.credential.mode !== "managed") {
460
+ if (this.credential.mode !== "managed" && this.credential.mode !== "local") {
410
461
  throw new VaultError("MANAGED_ONLY", "getAll is available on managed compute only. Read entries by name with an API key.");
411
462
  }
412
463
  const result = await this.request(`/v1/vault/${this.scope()}/entries`);
@@ -441,9 +492,24 @@ class ForgeZero {
441
492
  return result.entries;
442
493
  }
443
494
  async set(name, value) {
495
+ if (this.credential.mode === "local") {
496
+ if (!/^[A-Za-z0-9_.:/-]{1,256}$/.test(name) || !value) {
497
+ throw new VaultError("LOCAL_VALUE_INVALID", "Local Vault names and values must be non-empty and bounded.");
498
+ }
499
+ const values = this.localValues();
500
+ values[name] = value;
501
+ this.saveLocalValues(values);
502
+ return 1;
503
+ }
444
504
  throw new VaultError("APPLICATION_READ_ONLY", "Application vault credentials cannot write secrets; use the authenticated platform vault UI.");
445
505
  }
446
506
  async remove(name) {
507
+ if (this.credential.mode === "local") {
508
+ const values = this.localValues();
509
+ delete values[name];
510
+ this.saveLocalValues(values);
511
+ return;
512
+ }
447
513
  throw new VaultError("APPLICATION_READ_ONLY", "Application vault credentials cannot delete secrets; use the authenticated platform vault UI.");
448
514
  }
449
515
  async* watch(options = {}) {
@@ -463,6 +529,7 @@ class ForgeZero {
463
529
  }
464
530
  }
465
531
  var createVault = (options = {}) => new ForgeZero(options);
532
+ var createLocalVault = (options = {}) => new ForgeZero({ ...options, credential: { mode: "local" } });
466
533
  function vaultCredentials(vault) {
467
534
  return {
468
535
  name: "vault",
@@ -505,7 +572,7 @@ function systemdCredentials(options = {}) {
505
572
  }
506
573
  };
507
574
  }
508
- var VERSION = "0.1.23";
575
+ var VERSION = "0.1.25";
509
576
  export {
510
577
  vaultCredentials,
511
578
  systemdCredentials,
@@ -514,6 +581,7 @@ export {
514
581
  discover,
515
582
  directCredentials,
516
583
  createVault,
584
+ createLocalVault,
517
585
  VaultRuntime,
518
586
  VaultError,
519
587
  VERSION,
package/dist/providers.js CHANGED
@@ -22,8 +22,8 @@ class VaultRuntime {
22
22
  this.transport = transport;
23
23
  }
24
24
  available() {
25
- if (this.transport === "managed-read-only") {
26
- throw new VaultRuntimeClientError("MANAGED_RUNTIME_DELEGATION_REQUIRED", "Vault runtime operations require a separately granted external API key; the managed Agent replica is read-only.");
25
+ if (this.transport === "managed-read-only" || this.transport === "local-read-only") {
26
+ throw new VaultRuntimeClientError("MANAGED_RUNTIME_DELEGATION_REQUIRED", this.transport === "local-read-only" ? "Local Vault mode stores development values only; master-seed runtime operations require a separately granted live API key." : "Vault runtime operations require a separately granted external API key; the managed Agent replica is read-only.");
27
27
  }
28
28
  }
29
29
  async create(options) {
@@ -156,7 +156,9 @@ function discover(environment = {}) {
156
156
  const apiKey = env.FORGEZERO_API_KEY;
157
157
  if (apiKey)
158
158
  return { mode: "external", apiKey };
159
- throw new VaultError("NO_CREDENTIAL", `No credential found. On managed compute the agent socket at ${socketPath} provides one; ` + "elsewhere set FORGEZERO_API_KEY.");
159
+ if (env.FORGEZERO_LOCAL_VAULT === "1")
160
+ return { mode: "local" };
161
+ throw new VaultError("NO_CREDENTIAL", `No credential found. On managed compute the agent socket at ${socketPath} provides one; ` + "elsewhere set FORGEZERO_API_KEY, or explicitly set FORGEZERO_LOCAL_VAULT=1 for isolated local development.");
160
162
  }
161
163
  var unbase64url = (encoded) => {
162
164
  const padded = encoded.replaceAll("-", "+").replaceAll("_", "/") + "=".repeat((4 - encoded.length % 4) % 4);
@@ -264,6 +266,8 @@ class ForgeZero {
264
266
  requestTimeoutMs;
265
267
  project;
266
268
  environment;
269
+ localStorage;
270
+ localNamespace;
267
271
  constructor(options = {}) {
268
272
  const credential = options.credential ?? discover(options.discovery);
269
273
  this.credential = credential.mode === "managed" && !credential.socketPath ? { ...credential, socketPath: DEFAULT_SOCKET } : credential;
@@ -277,8 +281,13 @@ class ForgeZero {
277
281
  }
278
282
  this.project = options.project ?? "default";
279
283
  this.environment = options.environment ?? "production";
284
+ this.localStorage = options.localStorage ?? (this.credential.mode === "local" && typeof globalThis.localStorage !== "undefined" ? globalThis.localStorage : undefined);
285
+ this.localNamespace = options.localNamespace?.trim() || "fz.local.vault.v1";
286
+ if (this.credential.mode === "local" && !this.localStorage) {
287
+ throw new VaultError("LOCAL_STORAGE_UNAVAILABLE", "Local Vault mode requires browser localStorage or an injected LocalVaultStorage adapter.");
288
+ }
280
289
  const runtimeRequest = options.runtimeRequest ?? ((path, init) => this.request(`/v1/vault/${this.scope()}/runtime${path}`, init));
281
- this.runtime = new VaultRuntime(runtimeRequest, this.credential.mode === "external" ? "external" : options.runtimeRequest ? "managed-delegated" : "managed-read-only");
290
+ this.runtime = new VaultRuntime(runtimeRequest, this.credential.mode === "external" ? "external" : options.runtimeRequest ? "managed-delegated" : this.credential.mode === "local" ? "local-read-only" : "managed-read-only");
282
291
  }
283
292
  scope() {
284
293
  return `${encodeURIComponent(this.project)}/${encodeURIComponent(this.environment)}`;
@@ -286,6 +295,8 @@ class ForgeZero {
286
295
  async request(path, init = {}) {
287
296
  if (this.credential.mode === "managed")
288
297
  return this.managedRequest(path, init);
298
+ if (this.credential.mode === "local")
299
+ return this.localRequest(path, init);
289
300
  const body = typeof init.body === "string" ? init.body : "";
290
301
  const target = new URL(path, this.apiUrl);
291
302
  const headers = {
@@ -326,6 +337,46 @@ class ForgeZero {
326
337
  }
327
338
  return payload;
328
339
  }
340
+ localStorageKey() {
341
+ return `${this.localNamespace}:${encodeURIComponent(this.project)}:${encodeURIComponent(this.environment)}`;
342
+ }
343
+ localValues() {
344
+ const raw = this.localStorage.getItem(this.localStorageKey());
345
+ if (raw === null)
346
+ return {};
347
+ try {
348
+ const parsed = JSON.parse(raw);
349
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed) || Object.entries(parsed).some(([key, value]) => !key || typeof value !== "string"))
350
+ throw new Error("invalid");
351
+ return parsed;
352
+ } catch {
353
+ throw new VaultError("LOCAL_STORE_MALFORMED", "The local Vault store is malformed. Clear it before continuing.");
354
+ }
355
+ }
356
+ saveLocalValues(values) {
357
+ this.localStorage.setItem(this.localStorageKey(), JSON.stringify(values));
358
+ }
359
+ async localRequest(path, init) {
360
+ const method = init.method ?? "GET";
361
+ const target = new URL(path, "https://vault.local.invalid");
362
+ const values = this.localValues();
363
+ const entry = /\/entries\/([^/]+)$/.exec(target.pathname);
364
+ if (method === "GET" && entry) {
365
+ const name = decodeURIComponent(entry[1]);
366
+ if (!(name in values))
367
+ throw new VaultError("NOT_FOUND", `No local Vault value named ${name}.`);
368
+ return { value: values[name] };
369
+ }
370
+ if (method === "GET" && target.pathname.endsWith("/list")) {
371
+ return { entries: Object.keys(values).sort().map((name) => ({ name })) };
372
+ }
373
+ if (method === "GET" && target.pathname.endsWith("/entries"))
374
+ return { values: { ...values } };
375
+ if (method === "GET" && target.pathname.endsWith("/changes")) {
376
+ return { version: 0, changed: [] };
377
+ }
378
+ throw new VaultError("LOCAL_OPERATION_UNSUPPORTED", "This operation is unavailable in isolated local Vault mode.");
379
+ }
329
380
  async managedRequest(path, init) {
330
381
  const socketPath = this.credential.socketPath;
331
382
  const method = init.method ?? "GET";
@@ -406,7 +457,7 @@ class ForgeZero {
406
457
  return result.value;
407
458
  }
408
459
  async getAll() {
409
- if (this.credential.mode !== "managed") {
460
+ if (this.credential.mode !== "managed" && this.credential.mode !== "local") {
410
461
  throw new VaultError("MANAGED_ONLY", "getAll is available on managed compute only. Read entries by name with an API key.");
411
462
  }
412
463
  const result = await this.request(`/v1/vault/${this.scope()}/entries`);
@@ -441,9 +492,24 @@ class ForgeZero {
441
492
  return result.entries;
442
493
  }
443
494
  async set(name, value) {
495
+ if (this.credential.mode === "local") {
496
+ if (!/^[A-Za-z0-9_.:/-]{1,256}$/.test(name) || !value) {
497
+ throw new VaultError("LOCAL_VALUE_INVALID", "Local Vault names and values must be non-empty and bounded.");
498
+ }
499
+ const values = this.localValues();
500
+ values[name] = value;
501
+ this.saveLocalValues(values);
502
+ return 1;
503
+ }
444
504
  throw new VaultError("APPLICATION_READ_ONLY", "Application vault credentials cannot write secrets; use the authenticated platform vault UI.");
445
505
  }
446
506
  async remove(name) {
507
+ if (this.credential.mode === "local") {
508
+ const values = this.localValues();
509
+ delete values[name];
510
+ this.saveLocalValues(values);
511
+ return;
512
+ }
447
513
  throw new VaultError("APPLICATION_READ_ONLY", "Application vault credentials cannot delete secrets; use the authenticated platform vault UI.");
448
514
  }
449
515
  async* watch(options = {}) {
@@ -463,6 +529,7 @@ class ForgeZero {
463
529
  }
464
530
  }
465
531
  var createVault = (options = {}) => new ForgeZero(options);
532
+ var createLocalVault = (options = {}) => new ForgeZero({ ...options, credential: { mode: "local" } });
466
533
  function vaultCredentials(vault) {
467
534
  return {
468
535
  name: "vault",
@@ -505,7 +572,7 @@ function systemdCredentials(options = {}) {
505
572
  }
506
573
  };
507
574
  }
508
- var VERSION = "0.1.23";
575
+ var VERSION = "0.1.25";
509
576
 
510
577
  // src/providers.ts
511
578
  var MISSING = new Set(["ENTRY_NOT_FOUND", "VERSION_NOT_FOUND"]);
package/dist/runtime.d.ts CHANGED
@@ -48,7 +48,7 @@ export declare class VaultRuntimeClientError extends Error {
48
48
  * explicit prevents membership of the local Vault-reader group from silently
49
49
  * granting signing, decryption or certificate authority.
50
50
  */
51
- export type VaultRuntimeTransport = 'external' | 'managed-read-only' | 'managed-delegated';
51
+ export type VaultRuntimeTransport = 'external' | 'managed-read-only' | 'managed-delegated' | 'local-read-only';
52
52
  /**
53
53
  * Explicit non-exporting cryptographic capabilities.
54
54
  *
package/dist/runtime.js CHANGED
@@ -22,8 +22,8 @@ class VaultRuntime {
22
22
  this.transport = transport;
23
23
  }
24
24
  available() {
25
- if (this.transport === "managed-read-only") {
26
- throw new VaultRuntimeClientError("MANAGED_RUNTIME_DELEGATION_REQUIRED", "Vault runtime operations require a separately granted external API key; the managed Agent replica is read-only.");
25
+ if (this.transport === "managed-read-only" || this.transport === "local-read-only") {
26
+ throw new VaultRuntimeClientError("MANAGED_RUNTIME_DELEGATION_REQUIRED", this.transport === "local-read-only" ? "Local Vault mode stores development values only; master-seed runtime operations require a separately granted live API key." : "Vault runtime operations require a separately granted external API key; the managed Agent replica is read-only.");
27
27
  }
28
28
  }
29
29
  async create(options) {
package/dist/schema.js CHANGED
@@ -22,8 +22,8 @@ class VaultRuntime {
22
22
  this.transport = transport;
23
23
  }
24
24
  available() {
25
- if (this.transport === "managed-read-only") {
26
- throw new VaultRuntimeClientError("MANAGED_RUNTIME_DELEGATION_REQUIRED", "Vault runtime operations require a separately granted external API key; the managed Agent replica is read-only.");
25
+ if (this.transport === "managed-read-only" || this.transport === "local-read-only") {
26
+ throw new VaultRuntimeClientError("MANAGED_RUNTIME_DELEGATION_REQUIRED", this.transport === "local-read-only" ? "Local Vault mode stores development values only; master-seed runtime operations require a separately granted live API key." : "Vault runtime operations require a separately granted external API key; the managed Agent replica is read-only.");
27
27
  }
28
28
  }
29
29
  async create(options) {
@@ -156,7 +156,9 @@ function discover(environment = {}) {
156
156
  const apiKey = env.FORGEZERO_API_KEY;
157
157
  if (apiKey)
158
158
  return { mode: "external", apiKey };
159
- throw new VaultError("NO_CREDENTIAL", `No credential found. On managed compute the agent socket at ${socketPath} provides one; ` + "elsewhere set FORGEZERO_API_KEY.");
159
+ if (env.FORGEZERO_LOCAL_VAULT === "1")
160
+ return { mode: "local" };
161
+ throw new VaultError("NO_CREDENTIAL", `No credential found. On managed compute the agent socket at ${socketPath} provides one; ` + "elsewhere set FORGEZERO_API_KEY, or explicitly set FORGEZERO_LOCAL_VAULT=1 for isolated local development.");
160
162
  }
161
163
  var unbase64url = (encoded) => {
162
164
  const padded = encoded.replaceAll("-", "+").replaceAll("_", "/") + "=".repeat((4 - encoded.length % 4) % 4);
@@ -264,6 +266,8 @@ class ForgeZero {
264
266
  requestTimeoutMs;
265
267
  project;
266
268
  environment;
269
+ localStorage;
270
+ localNamespace;
267
271
  constructor(options = {}) {
268
272
  const credential = options.credential ?? discover(options.discovery);
269
273
  this.credential = credential.mode === "managed" && !credential.socketPath ? { ...credential, socketPath: DEFAULT_SOCKET } : credential;
@@ -277,8 +281,13 @@ class ForgeZero {
277
281
  }
278
282
  this.project = options.project ?? "default";
279
283
  this.environment = options.environment ?? "production";
284
+ this.localStorage = options.localStorage ?? (this.credential.mode === "local" && typeof globalThis.localStorage !== "undefined" ? globalThis.localStorage : undefined);
285
+ this.localNamespace = options.localNamespace?.trim() || "fz.local.vault.v1";
286
+ if (this.credential.mode === "local" && !this.localStorage) {
287
+ throw new VaultError("LOCAL_STORAGE_UNAVAILABLE", "Local Vault mode requires browser localStorage or an injected LocalVaultStorage adapter.");
288
+ }
280
289
  const runtimeRequest = options.runtimeRequest ?? ((path, init) => this.request(`/v1/vault/${this.scope()}/runtime${path}`, init));
281
- this.runtime = new VaultRuntime(runtimeRequest, this.credential.mode === "external" ? "external" : options.runtimeRequest ? "managed-delegated" : "managed-read-only");
290
+ this.runtime = new VaultRuntime(runtimeRequest, this.credential.mode === "external" ? "external" : options.runtimeRequest ? "managed-delegated" : this.credential.mode === "local" ? "local-read-only" : "managed-read-only");
282
291
  }
283
292
  scope() {
284
293
  return `${encodeURIComponent(this.project)}/${encodeURIComponent(this.environment)}`;
@@ -286,6 +295,8 @@ class ForgeZero {
286
295
  async request(path, init = {}) {
287
296
  if (this.credential.mode === "managed")
288
297
  return this.managedRequest(path, init);
298
+ if (this.credential.mode === "local")
299
+ return this.localRequest(path, init);
289
300
  const body = typeof init.body === "string" ? init.body : "";
290
301
  const target = new URL(path, this.apiUrl);
291
302
  const headers = {
@@ -326,6 +337,46 @@ class ForgeZero {
326
337
  }
327
338
  return payload;
328
339
  }
340
+ localStorageKey() {
341
+ return `${this.localNamespace}:${encodeURIComponent(this.project)}:${encodeURIComponent(this.environment)}`;
342
+ }
343
+ localValues() {
344
+ const raw = this.localStorage.getItem(this.localStorageKey());
345
+ if (raw === null)
346
+ return {};
347
+ try {
348
+ const parsed = JSON.parse(raw);
349
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed) || Object.entries(parsed).some(([key, value]) => !key || typeof value !== "string"))
350
+ throw new Error("invalid");
351
+ return parsed;
352
+ } catch {
353
+ throw new VaultError("LOCAL_STORE_MALFORMED", "The local Vault store is malformed. Clear it before continuing.");
354
+ }
355
+ }
356
+ saveLocalValues(values) {
357
+ this.localStorage.setItem(this.localStorageKey(), JSON.stringify(values));
358
+ }
359
+ async localRequest(path, init) {
360
+ const method = init.method ?? "GET";
361
+ const target = new URL(path, "https://vault.local.invalid");
362
+ const values = this.localValues();
363
+ const entry = /\/entries\/([^/]+)$/.exec(target.pathname);
364
+ if (method === "GET" && entry) {
365
+ const name = decodeURIComponent(entry[1]);
366
+ if (!(name in values))
367
+ throw new VaultError("NOT_FOUND", `No local Vault value named ${name}.`);
368
+ return { value: values[name] };
369
+ }
370
+ if (method === "GET" && target.pathname.endsWith("/list")) {
371
+ return { entries: Object.keys(values).sort().map((name) => ({ name })) };
372
+ }
373
+ if (method === "GET" && target.pathname.endsWith("/entries"))
374
+ return { values: { ...values } };
375
+ if (method === "GET" && target.pathname.endsWith("/changes")) {
376
+ return { version: 0, changed: [] };
377
+ }
378
+ throw new VaultError("LOCAL_OPERATION_UNSUPPORTED", "This operation is unavailable in isolated local Vault mode.");
379
+ }
329
380
  async managedRequest(path, init) {
330
381
  const socketPath = this.credential.socketPath;
331
382
  const method = init.method ?? "GET";
@@ -406,7 +457,7 @@ class ForgeZero {
406
457
  return result.value;
407
458
  }
408
459
  async getAll() {
409
- if (this.credential.mode !== "managed") {
460
+ if (this.credential.mode !== "managed" && this.credential.mode !== "local") {
410
461
  throw new VaultError("MANAGED_ONLY", "getAll is available on managed compute only. Read entries by name with an API key.");
411
462
  }
412
463
  const result = await this.request(`/v1/vault/${this.scope()}/entries`);
@@ -441,9 +492,24 @@ class ForgeZero {
441
492
  return result.entries;
442
493
  }
443
494
  async set(name, value) {
495
+ if (this.credential.mode === "local") {
496
+ if (!/^[A-Za-z0-9_.:/-]{1,256}$/.test(name) || !value) {
497
+ throw new VaultError("LOCAL_VALUE_INVALID", "Local Vault names and values must be non-empty and bounded.");
498
+ }
499
+ const values = this.localValues();
500
+ values[name] = value;
501
+ this.saveLocalValues(values);
502
+ return 1;
503
+ }
444
504
  throw new VaultError("APPLICATION_READ_ONLY", "Application vault credentials cannot write secrets; use the authenticated platform vault UI.");
445
505
  }
446
506
  async remove(name) {
507
+ if (this.credential.mode === "local") {
508
+ const values = this.localValues();
509
+ delete values[name];
510
+ this.saveLocalValues(values);
511
+ return;
512
+ }
447
513
  throw new VaultError("APPLICATION_READ_ONLY", "Application vault credentials cannot delete secrets; use the authenticated platform vault UI.");
448
514
  }
449
515
  async* watch(options = {}) {
@@ -463,6 +529,7 @@ class ForgeZero {
463
529
  }
464
530
  }
465
531
  var createVault = (options = {}) => new ForgeZero(options);
532
+ var createLocalVault = (options = {}) => new ForgeZero({ ...options, credential: { mode: "local" } });
466
533
  function vaultCredentials(vault) {
467
534
  return {
468
535
  name: "vault",
@@ -505,7 +572,7 @@ function systemdCredentials(options = {}) {
505
572
  }
506
573
  };
507
574
  }
508
- var VERSION = "0.1.23";
575
+ var VERSION = "0.1.25";
509
576
 
510
577
  // src/schema.ts
511
578
  function managedSchemas(vault, options = {}) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forgezero/vault",
3
- "version": "0.1.23",
3
+ "version": "0.1.25",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public",
@@ -46,12 +46,14 @@
46
46
  "@types/bun": "latest"
47
47
  },
48
48
  "dependencies": {
49
- "@forgezero/runtime": "^0.1.18"
49
+ "@forgezero/runtime": "^0.1.19"
50
50
  },
51
51
  "peerDependencies": {
52
+ "@noble/ciphers": "^2.2.0",
52
53
  "@noble/curves": "^2.2.0",
53
54
  "@noble/post-quantum": "^0.6.1",
54
- "@noble/hashes": "^2.2.0"
55
+ "@noble/hashes": "^2.2.0",
56
+ "@scure/base": "^2.2.0"
55
57
  },
56
58
  "description": "ForgeZero vault client. Credential discovery and versioned secrets through one stable API origin.",
57
59
  "keywords": [