@hasna/switcher 0.1.3 → 0.1.5

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.
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { type CredentialChainOptions } from "@hasna/contracts/client";
2
3
  import { CommandInterrupted, type ProviderInput } from "./domain";
3
4
  declare const keychain: z.ZodObject<{
4
5
  kind: z.ZodLiteral<"keychain">;
@@ -13,7 +14,7 @@ declare const keychain: z.ZodObject<{
13
14
  service: string;
14
15
  account: string;
15
16
  }>;
16
- export declare const credentialBindingSchema: z.ZodObject<{
17
+ export declare const credentialBindingSchema: z.ZodEffects<z.ZodObject<{
17
18
  schema: z.ZodLiteral<1>;
18
19
  credentialEnv: z.ZodString;
19
20
  origins: z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">;
@@ -32,9 +33,15 @@ export declare const credentialBindingSchema: z.ZodObject<{
32
33
  }>, z.ZodObject<{
33
34
  kind: z.ZodLiteral<"vault">;
34
35
  key: z.ZodString;
35
- url: z.ZodEffects<z.ZodString, string, string>;
36
+ url: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
36
37
  executable: z.ZodEffects<z.ZodString, string, string>;
37
38
  operator: z.ZodDiscriminatedUnion<"kind", [z.ZodObject<{
39
+ kind: z.ZodLiteral<"contracts">;
40
+ }, "strict", z.ZodTypeAny, {
41
+ kind: "contracts";
42
+ }, {
43
+ kind: "contracts";
44
+ }>, z.ZodObject<{
38
45
  kind: z.ZodLiteral<"env">;
39
46
  }, "strict", z.ZodTypeAny, {
40
47
  kind: "env";
@@ -42,7 +49,7 @@ export declare const credentialBindingSchema: z.ZodObject<{
42
49
  kind: "env";
43
50
  }>, z.ZodObject<{
44
51
  kind: z.ZodLiteral<"keychain">;
45
- account: z.ZodString;
52
+ account: z.ZodEffects<z.ZodString, string, string>;
46
53
  }, "strict", z.ZodTypeAny, {
47
54
  kind: "keychain";
48
55
  account: string;
@@ -51,27 +58,31 @@ export declare const credentialBindingSchema: z.ZodObject<{
51
58
  account: string;
52
59
  }>]>;
53
60
  }, "strict", z.ZodTypeAny, {
54
- url: string;
55
61
  key: string;
56
62
  executable: string;
57
63
  kind: "vault";
58
64
  operator: {
65
+ kind: "contracts";
66
+ } | {
59
67
  kind: "env";
60
68
  } | {
61
69
  kind: "keychain";
62
70
  account: string;
63
71
  };
72
+ url?: string | undefined;
64
73
  }, {
65
- url: string;
66
74
  key: string;
67
75
  executable: string;
68
76
  kind: "vault";
69
77
  operator: {
78
+ kind: "contracts";
79
+ } | {
70
80
  kind: "env";
71
81
  } | {
72
82
  kind: "keychain";
73
83
  account: string;
74
84
  };
85
+ url?: string | undefined;
75
86
  }>]>;
76
87
  }, "strict", z.ZodTypeAny, {
77
88
  credentialEnv: string;
@@ -80,16 +91,18 @@ export declare const credentialBindingSchema: z.ZodObject<{
80
91
  service: string;
81
92
  account: string;
82
93
  } | {
83
- url: string;
84
94
  key: string;
85
95
  executable: string;
86
96
  kind: "vault";
87
97
  operator: {
98
+ kind: "contracts";
99
+ } | {
88
100
  kind: "env";
89
101
  } | {
90
102
  kind: "keychain";
91
103
  account: string;
92
104
  };
105
+ url?: string | undefined;
93
106
  };
94
107
  schema: 1;
95
108
  origins: string[];
@@ -100,16 +113,62 @@ export declare const credentialBindingSchema: z.ZodObject<{
100
113
  service: string;
101
114
  account: string;
102
115
  } | {
103
- url: string;
104
116
  key: string;
105
117
  executable: string;
106
118
  kind: "vault";
107
119
  operator: {
120
+ kind: "contracts";
121
+ } | {
108
122
  kind: "env";
109
123
  } | {
110
124
  kind: "keychain";
111
125
  account: string;
112
126
  };
127
+ url?: string | undefined;
128
+ };
129
+ schema: 1;
130
+ origins: string[];
131
+ }>, {
132
+ credentialEnv: string;
133
+ source: {
134
+ kind: "keychain";
135
+ service: string;
136
+ account: string;
137
+ } | {
138
+ key: string;
139
+ executable: string;
140
+ kind: "vault";
141
+ operator: {
142
+ kind: "contracts";
143
+ } | {
144
+ kind: "env";
145
+ } | {
146
+ kind: "keychain";
147
+ account: string;
148
+ };
149
+ url?: string | undefined;
150
+ };
151
+ schema: 1;
152
+ origins: string[];
153
+ }, {
154
+ credentialEnv: string;
155
+ source: {
156
+ kind: "keychain";
157
+ service: string;
158
+ account: string;
159
+ } | {
160
+ key: string;
161
+ executable: string;
162
+ kind: "vault";
163
+ operator: {
164
+ kind: "contracts";
165
+ } | {
166
+ kind: "env";
167
+ } | {
168
+ kind: "keychain";
169
+ account: string;
170
+ };
171
+ url?: string | undefined;
113
172
  };
114
173
  schema: 1;
115
174
  origins: string[];
@@ -130,16 +189,18 @@ export declare class CredentialBindings {
130
189
  service: string;
131
190
  account: string;
132
191
  } | {
133
- url: string;
134
192
  key: string;
135
193
  executable: string;
136
194
  kind: "vault";
137
195
  operator: {
196
+ kind: "contracts";
197
+ } | {
138
198
  kind: "env";
139
199
  } | {
140
200
  kind: "keychain";
141
201
  account: string;
142
202
  };
203
+ url?: string | undefined;
143
204
  };
144
205
  schema: 1;
145
206
  origins: string[];
@@ -173,6 +234,8 @@ export declare class CredentialResolver {
173
234
  export declare class CredentialInterrupted extends CommandInterrupted {
174
235
  constructor(exitCode: number);
175
236
  }
237
+ /** Select an operator through the shared credential seam, then pin that choice. */
238
+ export declare function vaultEnvironment(binding: CredentialBinding, env: NodeJS.ProcessEnv, options?: Pick<CredentialChainOptions, "keychain">): Promise<NodeJS.ProcessEnv>;
176
239
  /** Internal child mode: the key travels only through an authenticated loopback request. */
177
240
  export declare function deliverVaultCredential(): Promise<void>;
178
241
  export {};
package/dist/domain.d.ts CHANGED
@@ -3,7 +3,7 @@ import { type RoutingEvent } from "./model-policy-schema";
3
3
  export { modelPolicySchema, routingEventSchema, routingEventsSchema } from "./model-policy-schema";
4
4
  export type { ModelPolicy, RoutingEvent } from "./model-policy-schema";
5
5
  export type { AuthStyle } from "./auth";
6
- export declare const VERSION = "0.1.3";
6
+ export declare const VERSION = "0.1.5";
7
7
  export declare const harnessSchema: z.ZodEnum<["claude", "codex", "grok", "opencode", "opencode2", "pi", "omp", "dsh", "cline", "hermes", "prime-agent", "gemini", "aider", "kilo"]>;
8
8
  export declare const protocolSchema: z.ZodEnum<["anthropic-messages", "openai-responses", "openai-chat", "gemini-generate-content"]>;
9
9
  export declare const idSchema: z.ZodString;
package/dist/index.js CHANGED
@@ -200,7 +200,7 @@ async function boundedJson(response, maxBytes = MAX_BYTES) {
200
200
  }
201
201
 
202
202
  // src/sdk.ts
203
- import { resolveCredential } from "@hasna/contracts/client";
203
+ import { createClientTransport } from "@hasna/contracts/client";
204
204
 
205
205
  // src/presets.ts
206
206
  var route = (protocol, baseUrl, options = {}) => ({
@@ -361,6 +361,7 @@ function apiError(status, data, apiKey) {
361
361
 
362
362
  class SwitcherClient {
363
363
  options;
364
+ transport;
364
365
  baseUrl;
365
366
  constructor(options) {
366
367
  this.baseUrl = endpoint(options.baseUrl).replace(/\/v1$/, "");
@@ -368,9 +369,50 @@ class SwitcherClient {
368
369
  throw new Error("Switcher API key is required.");
369
370
  this.options = { ...options };
370
371
  }
372
+ static fromEnvironment(env = process.env, options = {}) {
373
+ const shared = createClientTransport("switcher", env, {
374
+ credentials: options.credentials,
375
+ timeoutMs: options.timeoutMs ?? 120000,
376
+ retry: false,
377
+ fetchImpl: async (input, init) => {
378
+ const address = String(input).replace(/\/v1\/(health|ready|version)$/, "/$1");
379
+ const key = new Headers(init?.headers).get("x-api-key") ?? "";
380
+ let response;
381
+ try {
382
+ response = await (options.fetch ?? fetch)(address, init);
383
+ } catch {
384
+ throw new SwitcherError(0, "connection_failed", "Switcher API request failed; check endpoint and service availability.");
385
+ }
386
+ if (response.status === 401 || response.status === 403) {
387
+ await response.body?.cancel().catch(() => {});
388
+ throw new SwitcherError(response.status, "api_error", `Switcher API returned HTTP ${response.status}. Check the configured credential; no alternate identity was selected.`);
389
+ }
390
+ let data;
391
+ try {
392
+ data = await boundedJson(response);
393
+ } catch {
394
+ throw new SwitcherError(response.status, "invalid_response", "Switcher API returned invalid JSON.");
395
+ }
396
+ if (!response.ok)
397
+ throw apiError(response.status, data, key);
398
+ return Response.json(data);
399
+ }
400
+ });
401
+ const client = new SwitcherClient({ baseUrl: shared.resolution.baseUrl, apiKey: () => {
402
+ throw new Error("Shared credential transport was not invoked.");
403
+ } });
404
+ client.transport = shared.client;
405
+ return client;
406
+ }
371
407
  async request(method, path, body, options = {}) {
372
408
  if (!/^\/v1\/[a-zA-Z0-9/?&=._%+-]+$/.test(path) && !["/health", "/ready", "/version"].includes(path) || path.includes(".."))
373
409
  throw new Error("Invalid API path.");
410
+ if (this.transport)
411
+ return this.transport.request(method, path.startsWith("/v1/") ? path.slice(3) : path, body, {
412
+ idempotencyKey: method === "GET" ? undefined : options.idempotencyKey ?? crypto.randomUUID(),
413
+ headers: options.version === undefined ? undefined : { "if-match": String(options.version) },
414
+ retry: false
415
+ });
374
416
  const apiKey = typeof this.options.apiKey === "function" ? this.options.apiKey() : this.options.apiKey;
375
417
  if (!apiKey || /[\r\n]/.test(apiKey))
376
418
  throw new Error("Switcher API key is required.");
@@ -467,11 +509,8 @@ class SwitcherClient {
467
509
  return this.request("PATCH", `/v1/runs/${encodeURIComponent(id)}`, input, { version, idempotencyKey });
468
510
  }
469
511
  }
470
- function clientFromEnv(env = process.env) {
471
- const credential = () => resolveCredential("switcher", Object.fromEntries(Object.entries(env).filter(([name]) => name === "HASNA_SWITCHER_API_KEY")), { keychain: { enabled: false } })?.apiKey ?? "";
472
- if (!env.HASNA_SWITCHER_API_URL || !credential())
473
- throw new Error("Set HASNA_SWITCHER_API_URL and HASNA_SWITCHER_API_KEY; no local database fallback is available.");
474
- return new SwitcherClient({ baseUrl: env.HASNA_SWITCHER_API_URL, apiKey: credential });
512
+ function clientFromEnv(env = process.env, options = {}) {
513
+ return SwitcherClient.fromEnvironment(env, options);
475
514
  }
476
515
  export {
477
516
  providerFromPreset,
package/dist/mcp/index.js CHANGED
@@ -55,7 +55,7 @@ var routingEventSchema = z.object({
55
55
  var routingEventsSchema = z.array(routingEventSchema).max(1000);
56
56
 
57
57
  // src/domain.ts
58
- var VERSION = "0.1.3";
58
+ var VERSION = "0.1.5";
59
59
  var harnessSchema = z2.enum(["claude", "codex", "grok", "opencode", "opencode2", "pi", "omp", "dsh", "cline", "hermes", "prime-agent", "gemini", "aider", "kilo"]);
60
60
  var protocolSchema = z2.enum(["anthropic-messages", "openai-responses", "openai-chat", "gemini-generate-content"]);
61
61
  var idSchema = z2.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,79}$/);
@@ -199,7 +199,7 @@ async function boundedJson(response, maxBytes = MAX_BYTES) {
199
199
  }
200
200
 
201
201
  // src/sdk.ts
202
- import { resolveCredential } from "@hasna/contracts/client";
202
+ import { createClientTransport } from "@hasna/contracts/client";
203
203
 
204
204
  // src/presets.ts
205
205
  var route = (protocol, baseUrl, options = {}) => ({
@@ -321,6 +321,7 @@ function apiError(status, data, apiKey) {
321
321
 
322
322
  class SwitcherClient {
323
323
  options;
324
+ transport;
324
325
  baseUrl;
325
326
  constructor(options) {
326
327
  this.baseUrl = endpoint(options.baseUrl).replace(/\/v1$/, "");
@@ -328,9 +329,50 @@ class SwitcherClient {
328
329
  throw new Error("Switcher API key is required.");
329
330
  this.options = { ...options };
330
331
  }
332
+ static fromEnvironment(env = process.env, options = {}) {
333
+ const shared = createClientTransport("switcher", env, {
334
+ credentials: options.credentials,
335
+ timeoutMs: options.timeoutMs ?? 120000,
336
+ retry: false,
337
+ fetchImpl: async (input, init) => {
338
+ const address = String(input).replace(/\/v1\/(health|ready|version)$/, "/$1");
339
+ const key = new Headers(init?.headers).get("x-api-key") ?? "";
340
+ let response;
341
+ try {
342
+ response = await (options.fetch ?? fetch)(address, init);
343
+ } catch {
344
+ throw new SwitcherError(0, "connection_failed", "Switcher API request failed; check endpoint and service availability.");
345
+ }
346
+ if (response.status === 401 || response.status === 403) {
347
+ await response.body?.cancel().catch(() => {});
348
+ throw new SwitcherError(response.status, "api_error", `Switcher API returned HTTP ${response.status}. Check the configured credential; no alternate identity was selected.`);
349
+ }
350
+ let data;
351
+ try {
352
+ data = await boundedJson(response);
353
+ } catch {
354
+ throw new SwitcherError(response.status, "invalid_response", "Switcher API returned invalid JSON.");
355
+ }
356
+ if (!response.ok)
357
+ throw apiError(response.status, data, key);
358
+ return Response.json(data);
359
+ }
360
+ });
361
+ const client = new SwitcherClient({ baseUrl: shared.resolution.baseUrl, apiKey: () => {
362
+ throw new Error("Shared credential transport was not invoked.");
363
+ } });
364
+ client.transport = shared.client;
365
+ return client;
366
+ }
331
367
  async request(method, path, body, options = {}) {
332
368
  if (!/^\/v1\/[a-zA-Z0-9/?&=._%+-]+$/.test(path) && !["/health", "/ready", "/version"].includes(path) || path.includes(".."))
333
369
  throw new Error("Invalid API path.");
370
+ if (this.transport)
371
+ return this.transport.request(method, path.startsWith("/v1/") ? path.slice(3) : path, body, {
372
+ idempotencyKey: method === "GET" ? undefined : options.idempotencyKey ?? crypto.randomUUID(),
373
+ headers: options.version === undefined ? undefined : { "if-match": String(options.version) },
374
+ retry: false
375
+ });
334
376
  const apiKey = typeof this.options.apiKey === "function" ? this.options.apiKey() : this.options.apiKey;
335
377
  if (!apiKey || /[\r\n]/.test(apiKey))
336
378
  throw new Error("Switcher API key is required.");
@@ -427,11 +469,8 @@ class SwitcherClient {
427
469
  return this.request("PATCH", `/v1/runs/${encodeURIComponent(id)}`, input, { version, idempotencyKey });
428
470
  }
429
471
  }
430
- function clientFromEnv(env = process.env) {
431
- const credential = () => resolveCredential("switcher", Object.fromEntries(Object.entries(env).filter(([name]) => name === "HASNA_SWITCHER_API_KEY")), { keychain: { enabled: false } })?.apiKey ?? "";
432
- if (!env.HASNA_SWITCHER_API_URL || !credential())
433
- throw new Error("Set HASNA_SWITCHER_API_URL and HASNA_SWITCHER_API_KEY; no local database fallback is available.");
434
- return new SwitcherClient({ baseUrl: env.HASNA_SWITCHER_API_URL, apiKey: credential });
472
+ function clientFromEnv(env = process.env, options = {}) {
473
+ return SwitcherClient.fromEnvironment(env, options);
435
474
  }
436
475
 
437
476
  // src/mcp.ts
@@ -467,6 +506,6 @@ tool("runs_get", "Get launch metadata.", { id: z3.string() }, (p) => clientFromE
467
506
  if (process.argv.includes("--version"))
468
507
  console.log(VERSION);
469
508
  else if (process.argv.includes("--help"))
470
- console.log("switcher-mcp: authenticated Switcher API tools over MCP stdio. Requires HASNA_SWITCHER_API_URL and HASNA_SWITCHER_API_KEY.");
509
+ console.log("switcher-mcp: authenticated Switcher API tools over MCP stdio. Resolves API URL/key through @hasna/contracts (Keychain, canonical config/credentials, or environment).");
471
510
  else
472
511
  await server.connect(new StdioServerTransport);
@@ -0,0 +1,17 @@
1
+ import type { HarnessLaunchInput } from "./harness-types";
2
+ /** Recognize whole SSE terminal events, never marker text inside a delta. */
3
+ export declare function terminalEventObserver(protocol: HarnessLaunchInput["protocol"], contentType: string | null): (chunk: Uint8Array) => boolean;
4
+ /** One lifecycle for all bridges: terminal SSE, caller cancellation, and real failure. */
5
+ export declare function proxyProviderStream(input: {
6
+ response: Response;
7
+ protocol: HarnessLaunchInput["protocol"];
8
+ requestSignal: AbortSignal;
9
+ abort: AbortController;
10
+ closing: () => boolean;
11
+ release: () => void;
12
+ inspect?: (chunk: Uint8Array, done?: boolean) => void;
13
+ interrupted?: () => void;
14
+ }): {
15
+ stream: ReadableStream<Uint8Array>;
16
+ cancel: () => Promise<void>;
17
+ };
package/dist/sdk.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { components } from "./generated/api";
2
+ import { type CredentialChainOptions } from "@hasna/contracts/client";
2
3
  export { providerFromPreset } from "./presets";
3
4
  export type ProviderPreset = components["schemas"]["ProviderPreset"];
4
5
  export type ProviderInput = components["schemas"]["ProviderInput"];
@@ -29,10 +30,18 @@ export type ClientOptions = {
29
30
  fetch?: typeof fetch;
30
31
  timeoutMs?: number;
31
32
  };
33
+ export type EnvironmentClientOptions = {
34
+ credentials?: CredentialChainOptions;
35
+ fetch?: typeof fetch;
36
+ timeoutMs?: number;
37
+ };
32
38
  export declare class SwitcherClient {
33
39
  private readonly options;
40
+ private transport?;
34
41
  readonly baseUrl: string;
35
42
  constructor(options: ClientOptions);
43
+ /** Use Contracts' paired authority/credential resolution on every request. */
44
+ static fromEnvironment(env?: Record<string, string | undefined>, options?: EnvironmentClientOptions): SwitcherClient;
36
45
  request<T>(method: string, path: string, body?: unknown, options?: {
37
46
  version?: number;
38
47
  idempotencyKey?: string;
@@ -617,4 +626,4 @@ export declare class SwitcherClient {
617
626
  routingEventsDropped?: number;
618
627
  }>;
619
628
  }
620
- export declare function clientFromEnv(env?: Record<string, string | undefined>): SwitcherClient;
629
+ export declare function clientFromEnv(env?: Record<string, string | undefined>, options?: EnvironmentClientOptions): SwitcherClient;
package/dist/sdk.js CHANGED
@@ -200,7 +200,7 @@ async function boundedJson(response, maxBytes = MAX_BYTES) {
200
200
  }
201
201
 
202
202
  // src/sdk.ts
203
- import { resolveCredential } from "@hasna/contracts/client";
203
+ import { createClientTransport } from "@hasna/contracts/client";
204
204
 
205
205
  // src/presets.ts
206
206
  var route = (protocol, baseUrl, options = {}) => ({
@@ -361,6 +361,7 @@ function apiError(status, data, apiKey) {
361
361
 
362
362
  class SwitcherClient {
363
363
  options;
364
+ transport;
364
365
  baseUrl;
365
366
  constructor(options) {
366
367
  this.baseUrl = endpoint(options.baseUrl).replace(/\/v1$/, "");
@@ -368,9 +369,50 @@ class SwitcherClient {
368
369
  throw new Error("Switcher API key is required.");
369
370
  this.options = { ...options };
370
371
  }
372
+ static fromEnvironment(env = process.env, options = {}) {
373
+ const shared = createClientTransport("switcher", env, {
374
+ credentials: options.credentials,
375
+ timeoutMs: options.timeoutMs ?? 120000,
376
+ retry: false,
377
+ fetchImpl: async (input, init) => {
378
+ const address = String(input).replace(/\/v1\/(health|ready|version)$/, "/$1");
379
+ const key = new Headers(init?.headers).get("x-api-key") ?? "";
380
+ let response;
381
+ try {
382
+ response = await (options.fetch ?? fetch)(address, init);
383
+ } catch {
384
+ throw new SwitcherError(0, "connection_failed", "Switcher API request failed; check endpoint and service availability.");
385
+ }
386
+ if (response.status === 401 || response.status === 403) {
387
+ await response.body?.cancel().catch(() => {});
388
+ throw new SwitcherError(response.status, "api_error", `Switcher API returned HTTP ${response.status}. Check the configured credential; no alternate identity was selected.`);
389
+ }
390
+ let data;
391
+ try {
392
+ data = await boundedJson(response);
393
+ } catch {
394
+ throw new SwitcherError(response.status, "invalid_response", "Switcher API returned invalid JSON.");
395
+ }
396
+ if (!response.ok)
397
+ throw apiError(response.status, data, key);
398
+ return Response.json(data);
399
+ }
400
+ });
401
+ const client = new SwitcherClient({ baseUrl: shared.resolution.baseUrl, apiKey: () => {
402
+ throw new Error("Shared credential transport was not invoked.");
403
+ } });
404
+ client.transport = shared.client;
405
+ return client;
406
+ }
371
407
  async request(method, path, body, options = {}) {
372
408
  if (!/^\/v1\/[a-zA-Z0-9/?&=._%+-]+$/.test(path) && !["/health", "/ready", "/version"].includes(path) || path.includes(".."))
373
409
  throw new Error("Invalid API path.");
410
+ if (this.transport)
411
+ return this.transport.request(method, path.startsWith("/v1/") ? path.slice(3) : path, body, {
412
+ idempotencyKey: method === "GET" ? undefined : options.idempotencyKey ?? crypto.randomUUID(),
413
+ headers: options.version === undefined ? undefined : { "if-match": String(options.version) },
414
+ retry: false
415
+ });
374
416
  const apiKey = typeof this.options.apiKey === "function" ? this.options.apiKey() : this.options.apiKey;
375
417
  if (!apiKey || /[\r\n]/.test(apiKey))
376
418
  throw new Error("Switcher API key is required.");
@@ -467,11 +509,8 @@ class SwitcherClient {
467
509
  return this.request("PATCH", `/v1/runs/${encodeURIComponent(id)}`, input, { version, idempotencyKey });
468
510
  }
469
511
  }
470
- function clientFromEnv(env = process.env) {
471
- const credential = () => resolveCredential("switcher", Object.fromEntries(Object.entries(env).filter(([name]) => name === "HASNA_SWITCHER_API_KEY")), { keychain: { enabled: false } })?.apiKey ?? "";
472
- if (!env.HASNA_SWITCHER_API_URL || !credential())
473
- throw new Error("Set HASNA_SWITCHER_API_URL and HASNA_SWITCHER_API_KEY; no local database fallback is available.");
474
- return new SwitcherClient({ baseUrl: env.HASNA_SWITCHER_API_URL, apiKey: credential });
512
+ function clientFromEnv(env = process.env, options = {}) {
513
+ return SwitcherClient.fromEnvironment(env, options);
475
514
  }
476
515
  export {
477
516
  providerFromPreset,
@@ -62,7 +62,7 @@ function canonicalPolicyJSON(value) {
62
62
  }
63
63
 
64
64
  // src/domain.ts
65
- var VERSION = "0.1.3";
65
+ var VERSION = "0.1.5";
66
66
  var harnessSchema = z2.enum(["claude", "codex", "grok", "opencode", "opencode2", "pi", "omp", "dsh", "cline", "hermes", "prime-agent", "gemini", "aider", "kilo"]);
67
67
  var protocolSchema = z2.enum(["anthropic-messages", "openai-responses", "openai-chat", "gemini-generate-content"]);
68
68
  var idSchema = z2.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,79}$/);
@@ -808,7 +808,7 @@ var openapi_default = {
808
808
  openapi: "3.0.3",
809
809
  info: {
810
810
  title: "Switcher API",
811
- version: "0.1.3",
811
+ version: "0.1.5",
812
812
  description: "Authenticated provider/profile/catalog control plane. Launches run locally; the API never returns provider credentials."
813
813
  },
814
814
  security: [
package/openapi.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "openapi": "3.0.3",
3
3
  "info": {
4
4
  "title": "Switcher API",
5
- "version": "0.1.3",
5
+ "version": "0.1.5",
6
6
  "description": "Authenticated provider/profile/catalog control plane. Launches run locally; the API never returns provider credentials."
7
7
  },
8
8
  "security": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/switcher",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "type": "module",
5
5
  "description": "Universal provider and model switcher for coding harnesses with an API, SDK and self-hosted storage",
6
6
  "license": "Apache-2.0",