@wrongstack/cli 0.299.0 → 0.301.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{acp-S4LZQB66.js → acp-DATHHPNT.js} +36 -17
- package/dist/acp-server-agent.d.ts +1 -1
- package/dist/{audit-2ZSFGNIF.js → audit-QRCLEHHW.js} +5 -1
- package/dist/{auth-G5YKKSRH.js → auth-JASC4K3F.js} +151 -38
- package/dist/auth-menu/loopback-server.d.ts +41 -55
- package/dist/auth-menu/openai-codex-oauth.d.ts +36 -123
- package/dist/auto-discover-providers-DRW4OZHG.js +8 -0
- package/dist/boot/dispatch-webui.d.ts +3 -0
- package/dist/boot/launch-menu.d.ts +2 -4
- package/dist/boot/tool-registry.d.ts +3 -0
- package/dist/chimera-reviewer-policy.d.ts +10 -3
- package/dist/chimera-work-registry.d.ts +19 -0
- package/dist/{chronicle-QQEQFSSE.js → chronicle-63MFCZN6.js} +26 -5
- package/dist/{chunk-YWUPHRGB.js → chunk-263G3FMB.js} +33 -10
- package/dist/{chunk-2SLSUDYS.js → chunk-2KDOFTTM.js} +312 -211
- package/dist/{chunk-TGDHN4LM.js → chunk-4GVH7V3S.js} +4 -24
- package/dist/chunk-CSAPOCBP.js +24 -0
- package/dist/{chunk-66T43Q4Y.js → chunk-ETOEGL3V.js} +6 -9
- package/dist/{chunk-PEKUKUCH.js → chunk-GUHQQG63.js} +32 -14
- package/dist/chunk-KE7E7DPX.js +38 -0
- package/dist/{chunk-ZNZJ34RF.js → chunk-MHF2IJDE.js} +21 -4
- package/dist/{chunk-4HH3YGG4.js → chunk-MRLYDHQ7.js} +8 -5
- package/dist/{chunk-L5CE52XW.js → chunk-O7XNGCRT.js} +317 -309
- package/dist/chunk-PYTFS4MC.js +238 -0
- package/dist/{chunk-DPJZQCVP.js → chunk-YMXXOOFN.js} +1 -1
- package/dist/{chunk-4U3DMA2Y.js → chunk-ZECLGUTF.js} +28 -21
- package/dist/{cli-main-3MQDFSYK.js → cli-main-XWC57MGE.js} +849 -242
- package/dist/{execution-ANOYMWPO.js → execution-LHMSMDHB.js} +174 -55
- package/dist/execution-chimera-cascade.d.ts +3 -4
- package/dist/execution-chimera-review.d.ts +2 -3
- package/dist/execution-cleanup.d.ts +3 -2
- package/dist/execution.d.ts +1 -1
- package/dist/{export-TPORYVRZ.js → export-DOBGPY23.js} +12 -1
- package/dist/fleet/budget-source.d.ts +18 -0
- package/dist/fleet/host-context.d.ts +1 -1
- package/dist/fleet/host-helpers.d.ts +14 -0
- package/dist/fleet/host-status.d.ts +31 -0
- package/dist/fleet/host-types.d.ts +8 -0
- package/dist/fleet/host.d.ts +5 -0
- package/dist/fleet/status-broadcast.d.ts +1 -1
- package/dist/{hq-BDO56CX6.js → hq-LN5CJHCM.js} +22 -3
- package/dist/hq-server/auth-state.d.ts +29 -1
- package/dist/hq-server/auth.d.ts +16 -0
- package/dist/hq-server/client-address.d.ts +56 -0
- package/dist/hq-server/login-attempt-store.d.ts +30 -10
- package/dist/hq-server/routes/auth-handlers.d.ts +66 -26
- package/dist/hq-server/routes.d.ts +23 -1
- package/dist/hq-server/types.d.ts +10 -0
- package/dist/hq-server/ws.d.ts +17 -6
- package/dist/{hq-server-RJLJJZU7.js → hq-server-S7HZG3PA.js} +3 -2
- package/dist/hq-server.d.ts +19 -0
- package/dist/index.js +69 -104
- package/dist/live-settings-input.d.ts +5 -0
- package/dist/{mcp-CYLPUEHC.js → mcp-OHPJH73U.js} +6 -2
- package/dist/{modeldiag-HENYJ4N7.js → modeldiag-3DOK4BWW.js} +3 -3
- package/dist/{plugin-usage-VTL7ZTVC.js → plugin-usage-YEVLOR33.js} +7 -3
- package/dist/{plugins-ETY6W3DC.js → plugins-K3BAUP4D.js} +3 -3
- package/dist/profile-config-path.d.ts +0 -2
- package/dist/provider-helpers.d.ts +0 -5
- package/dist/{providers-models-GDELBHZK.js → providers-models-WEZ4EGLL.js} +38 -12
- package/dist/{replay-KCTXNMZQ.js → replay-FCBNHW3K.js} +5 -1
- package/dist/{rewind-EJMVFVLS.js → rewind-AOD3JIYX.js} +5 -1
- package/dist/{sessions-config-NLCMK3BY.js → sessions-config-XXN7267N.js} +8 -3
- package/dist/slash-commands/command-context.d.ts +19 -0
- package/dist/subcommands/flags.d.ts +23 -0
- package/dist/terminal-format.d.ts +15 -0
- package/dist/webui-server/setup-events.d.ts +17 -0
- package/dist/webui-server/terminal-log-view.d.ts +29 -12
- package/dist/{webui-server-PCLSXX6L.js → webui-server-DD35QFNO.js} +113 -50
- package/dist/webui-server-options.d.ts +16 -0
- package/dist/wiring/brain-and-orchestration.d.ts +4 -1
- package/dist/wiring/director-setup.d.ts +4 -0
- package/dist/wiring/fallback-gate.d.ts +31 -0
- package/dist/wiring/fleet-command-handlers.d.ts +1 -1
- package/dist/wiring/mailbox-bridge-bootstrap.d.ts +3 -1
- package/dist/wiring/management-tools.d.ts +9 -1
- package/dist/wiring/pipeline.d.ts +5 -5
- package/dist/wiring/plugins.d.ts +3 -10
- package/dist/wiring/provider-utility-tools.d.ts +27 -1
- package/dist/wiring/tools.d.ts +4 -5
- package/package.json +23 -23
- package/dist/chunk-GO3TJICK.js +0 -489
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
LOCAL_LLM_PRESETS,
|
|
3
3
|
runLiveProviderPicker
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-MHF2IJDE.js";
|
|
5
5
|
import {
|
|
6
6
|
openBrowser,
|
|
7
7
|
runCodexOAuthLogin,
|
|
8
8
|
startLoopbackServer
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-PYTFS4MC.js";
|
|
10
10
|
import {
|
|
11
11
|
activeLabel,
|
|
12
12
|
loadConfigProviders,
|
|
@@ -146,9 +146,10 @@ async function runClaudeOAuthLogin(deps, opts = {}) {
|
|
|
146
146
|
const ac = new AbortController();
|
|
147
147
|
const onSig = () => ac.abort();
|
|
148
148
|
const external = opts.signal;
|
|
149
|
+
const onExternalAbort = () => ac.abort();
|
|
149
150
|
if (external) {
|
|
150
151
|
if (external.aborted) ac.abort();
|
|
151
|
-
else external.addEventListener("abort",
|
|
152
|
+
else external.addEventListener("abort", onExternalAbort, { once: true });
|
|
152
153
|
} else {
|
|
153
154
|
process.on("SIGINT", onSig);
|
|
154
155
|
}
|
|
@@ -222,7 +223,8 @@ async function runClaudeOAuthLogin(deps, opts = {}) {
|
|
|
222
223
|
return 1;
|
|
223
224
|
} finally {
|
|
224
225
|
server.close();
|
|
225
|
-
if (
|
|
226
|
+
if (external) external.removeEventListener("abort", onExternalAbort);
|
|
227
|
+
else process.off("SIGINT", onSig);
|
|
226
228
|
}
|
|
227
229
|
}
|
|
228
230
|
async function saveClaudeTokens(deps, providerId, tokens, models) {
|
|
@@ -257,7 +259,6 @@ async function saveClaudeTokens(deps, providerId, tokens, models) {
|
|
|
257
259
|
}
|
|
258
260
|
|
|
259
261
|
// src/auth-menu/github-copilot-oauth.ts
|
|
260
|
-
import { spawn } from "node:child_process";
|
|
261
262
|
import { color as color2 } from "@wrongstack/core/utils";
|
|
262
263
|
import { FetchError as FetchError2, ParseError as ParseError2 } from "@wrongstack/core/types";
|
|
263
264
|
import { copilotBaseUrlFromToken, refreshCopilotToken } from "@wrongstack/providers";
|
|
@@ -272,17 +273,6 @@ var COPILOT_HEADERS = {
|
|
|
272
273
|
};
|
|
273
274
|
var COPILOT_API_VERSION = "2026-06-01";
|
|
274
275
|
var COPILOT_PROVIDER_ID = "github-copilot";
|
|
275
|
-
function openBrowser2(url) {
|
|
276
|
-
try {
|
|
277
|
-
const platform = process.platform;
|
|
278
|
-
const { command, args } = platform === "win32" ? { command: "cmd", args: ["/c", "start", "", url] } : platform === "darwin" ? { command: "open", args: [url] } : { command: "xdg-open", args: [url] };
|
|
279
|
-
const child = spawn(command, args, { stdio: "ignore", windowsHide: true });
|
|
280
|
-
child.on("error", () => {
|
|
281
|
-
});
|
|
282
|
-
child.unref();
|
|
283
|
-
} catch {
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
276
|
function sleep(ms, signal) {
|
|
287
277
|
return new Promise((resolve, reject) => {
|
|
288
278
|
if (signal.aborted) return reject(new DOMException("Aborted", "AbortError"));
|
|
@@ -430,7 +420,7 @@ async function runCopilotOAuthLogin(deps, opts = {}) {
|
|
|
430
420
|
|
|
431
421
|
`)
|
|
432
422
|
);
|
|
433
|
-
|
|
423
|
+
openBrowser(device.verification_uri);
|
|
434
424
|
deps.renderer.write(color2.dim(" Waiting for you to authorize in the browser...\n"));
|
|
435
425
|
const githubToken = await pollForGitHubToken(device, ac.signal);
|
|
436
426
|
deps.renderer.write(color2.dim(" Authorized. Fetching your Copilot token...\n"));
|
|
@@ -545,6 +535,10 @@ async function runOAuthLoginMenu(deps) {
|
|
|
545
535
|
await runOAuthLoginChoice(deps, pick);
|
|
546
536
|
}
|
|
547
537
|
|
|
538
|
+
// src/auth-menu/local.ts
|
|
539
|
+
import { color as color5 } from "@wrongstack/core/utils";
|
|
540
|
+
import { DefaultSecretScrubber as DefaultSecretScrubber2 } from "@wrongstack/core/security";
|
|
541
|
+
|
|
548
542
|
// src/auth-menu/helpers.ts
|
|
549
543
|
async function loadProviders(deps) {
|
|
550
544
|
return loadConfigProviders(deps.profileConfigPath, deps.vault, {
|
|
@@ -713,268 +707,7 @@ function validateFamily(raw) {
|
|
|
713
707
|
return valid.includes(raw) ? raw : null;
|
|
714
708
|
}
|
|
715
709
|
|
|
716
|
-
// src/auth-menu/add-provider.ts
|
|
717
|
-
import { atomicWrite, color as color5 } from "@wrongstack/core/utils";
|
|
718
|
-
import * as fs from "node:fs/promises";
|
|
719
|
-
|
|
720
|
-
// src/provider-id.ts
|
|
721
|
-
function nextCustomProviderId(existing) {
|
|
722
|
-
const taken = new Set(existing);
|
|
723
|
-
let n = 1;
|
|
724
|
-
while (taken.has(`custom-${n}`)) n++;
|
|
725
|
-
return `custom-${n}`;
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
// src/auth-menu/add-provider.ts
|
|
729
|
-
async function addFromCatalog(deps) {
|
|
730
|
-
let catalog = [];
|
|
731
|
-
try {
|
|
732
|
-
catalog = (await deps.modelsRegistry.listProviders()).filter((p) => p.family !== "unsupported");
|
|
733
|
-
} catch {
|
|
734
|
-
deps.renderer.writeWarning("Catalog unavailable \u2014 falling back to manual entry.\n");
|
|
735
|
-
}
|
|
736
|
-
if (catalog.length === 0) {
|
|
737
|
-
return addManualEntry(deps);
|
|
738
|
-
}
|
|
739
|
-
const saved = new Set(Object.keys(await loadProviders(deps)));
|
|
740
|
-
deps.renderer.write(
|
|
741
|
-
color5.dim(
|
|
742
|
-
` Catalog: ${catalog.length} providers. Type to filter, \u2191/\u2193 navigate, Enter to select.
|
|
743
|
-
`
|
|
744
|
-
)
|
|
745
|
-
);
|
|
746
|
-
deps.renderer.write(` ${color5.dim("\u25C9 already saved \u25CB no key yet")}
|
|
747
|
-
|
|
748
|
-
`);
|
|
749
|
-
renderOAuthLoginOptions(deps);
|
|
750
|
-
deps.renderer.write("\n");
|
|
751
|
-
const chosen = await runLiveProviderPicker(catalog, saved);
|
|
752
|
-
if (!chosen) {
|
|
753
|
-
deps.renderer.write(
|
|
754
|
-
`
|
|
755
|
-
${color5.dim("OAuth login options:")} ${color5.dim("chatgpt")}, ${color5.dim("claude")}, or ${color5.dim("copilot")}?
|
|
756
|
-
`
|
|
757
|
-
);
|
|
758
|
-
const answer = (await deps.reader.readLine(
|
|
759
|
-
` ${color5.amber("?")} OAuth or q to quit ${color5.dim("[chatgpt/claude/copilot]")}: `
|
|
760
|
-
)).trim();
|
|
761
|
-
if (answer && await runOAuthLoginChoice(deps, answer, { allowNumeric: false })) {
|
|
762
|
-
return true;
|
|
763
|
-
}
|
|
764
|
-
return false;
|
|
765
|
-
}
|
|
766
|
-
return addKeyForCatalogProvider(deps, chosen);
|
|
767
|
-
}
|
|
768
|
-
async function addKeyForCatalogProvider(deps, chosen) {
|
|
769
|
-
deps.renderer.write(
|
|
770
|
-
color5.dim(`
|
|
771
|
-
Defaults from models.dev \u2014 press Enter to keep, or type overrides.
|
|
772
|
-
`)
|
|
773
|
-
);
|
|
774
|
-
const famRaw = (await deps.reader.readLine(
|
|
775
|
-
` ${color5.amber("?")} Family ${color5.dim(`[${chosen.family}]`)} ${color5.dim("(q to quit)")}: `
|
|
776
|
-
)).trim();
|
|
777
|
-
if (famRaw === "q") return false;
|
|
778
|
-
let family = chosen.family;
|
|
779
|
-
if (famRaw) {
|
|
780
|
-
const validated = validateFamily(famRaw);
|
|
781
|
-
if (!validated) {
|
|
782
|
-
deps.renderer.writeError(
|
|
783
|
-
`Invalid family: "${famRaw}" (must be: anthropic, openai, openai-compatible, google).`
|
|
784
|
-
);
|
|
785
|
-
return false;
|
|
786
|
-
}
|
|
787
|
-
family = validated;
|
|
788
|
-
}
|
|
789
|
-
const baseRaw = (await deps.reader.readLine(
|
|
790
|
-
` ${color5.amber("?")} Base URL ${color5.dim(`[${chosen.apiBase ?? "unset"}]`)} ${color5.dim("(q to quit)")}: `
|
|
791
|
-
)).trim();
|
|
792
|
-
if (baseRaw === "q") return false;
|
|
793
|
-
const baseUrl = baseRaw || chosen.apiBase;
|
|
794
|
-
const providersNow = await loadProviders(deps);
|
|
795
|
-
let suggestedAlias = chosen.id;
|
|
796
|
-
if (family !== chosen.family) {
|
|
797
|
-
let candidate = `${chosen.id}-${family}`;
|
|
798
|
-
let n = 2;
|
|
799
|
-
while (providersNow[candidate]) {
|
|
800
|
-
candidate = `${chosen.id}-${family}-${n}`;
|
|
801
|
-
n++;
|
|
802
|
-
}
|
|
803
|
-
suggestedAlias = candidate;
|
|
804
|
-
}
|
|
805
|
-
const aliasRaw = (await deps.reader.readLine(
|
|
806
|
-
` ${color5.amber("?")} Save as alias ${color5.dim(`[${suggestedAlias}]`)} ${color5.dim("(used with --provider <alias>)")}: `
|
|
807
|
-
)).trim();
|
|
808
|
-
const alias = aliasRaw || suggestedAlias;
|
|
809
|
-
const existing = providersNow[alias];
|
|
810
|
-
if (existing) {
|
|
811
|
-
const sameFamily = (existing.family ?? chosen.family) === family;
|
|
812
|
-
const sameBase = (existing.baseUrl ?? chosen.apiBase) === baseUrl;
|
|
813
|
-
if (!sameFamily || !sameBase) {
|
|
814
|
-
deps.renderer.writeError(
|
|
815
|
-
`Alias "${alias}" already exists with different family/baseUrl.
|
|
816
|
-
Existing: family=${existing.family ?? "(unset)"}, baseUrl=${existing.baseUrl ?? "(unset)"}
|
|
817
|
-
New: family=${family}, baseUrl=${baseUrl ?? "(unset)"}
|
|
818
|
-
Pick a different alias to keep them separate.`
|
|
819
|
-
);
|
|
820
|
-
return false;
|
|
821
|
-
}
|
|
822
|
-
}
|
|
823
|
-
return addKeyForProvider(alias, deps, {
|
|
824
|
-
type: chosen.id,
|
|
825
|
-
family,
|
|
826
|
-
baseUrl,
|
|
827
|
-
envVars: chosen.envVars
|
|
828
|
-
});
|
|
829
|
-
}
|
|
830
|
-
async function addCustomProvider(deps) {
|
|
831
|
-
deps.renderer.write(
|
|
832
|
-
`
|
|
833
|
-
${color5.bold("Custom provider")} ${color5.dim("\u2014 for local models or proxies not in the catalog.")}
|
|
834
|
-
`
|
|
835
|
-
);
|
|
836
|
-
const providersNow = await loadProviders(deps);
|
|
837
|
-
const idRaw = (await deps.reader.readLine(
|
|
838
|
-
` ${color5.amber("?")} Provider id ${color5.dim('(e.g. "local-llama", "my-proxy"; blank = auto custom-N, q to quit)')}: `
|
|
839
|
-
)).trim();
|
|
840
|
-
if (idRaw === "q") return false;
|
|
841
|
-
let type = idRaw;
|
|
842
|
-
if (!type) {
|
|
843
|
-
type = nextCustomProviderId(Object.keys(providersNow));
|
|
844
|
-
deps.renderer.write(` ${color5.dim("Using generated id:")} ${color5.bold(type)}
|
|
845
|
-
`);
|
|
846
|
-
} else if (providersNow[type]) {
|
|
847
|
-
deps.renderer.writeWarning(`"${type}" already exists. Pick it from the main menu to edit.`);
|
|
848
|
-
return false;
|
|
849
|
-
}
|
|
850
|
-
const familyRaw = (await deps.reader.readLine(
|
|
851
|
-
` ${color5.amber("?")} Wire family ${color5.dim("(anthropic | openai | openai-compatible | google)")} ${color5.dim("(q to quit)")}: `
|
|
852
|
-
)).trim();
|
|
853
|
-
if (familyRaw === "q") return false;
|
|
854
|
-
const family = validateFamily(familyRaw);
|
|
855
|
-
if (!family) {
|
|
856
|
-
deps.renderer.writeError(`Invalid family: "${familyRaw}"`);
|
|
857
|
-
return false;
|
|
858
|
-
}
|
|
859
|
-
const baseUrl = (await deps.reader.readLine(
|
|
860
|
-
` ${color5.amber("?")} Base URL ${color5.dim("(e.g. http://localhost:11434/v1, optional)")}: `
|
|
861
|
-
)).trim();
|
|
862
|
-
const modelsRaw = (await deps.reader.readLine(
|
|
863
|
-
` ${color5.amber("?")} Model ids ${color5.dim("(comma-separated, optional)")}: `
|
|
864
|
-
)).trim();
|
|
865
|
-
const models = modelsRaw ? modelsRaw.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
|
|
866
|
-
const envVarsRaw = (await deps.reader.readLine(
|
|
867
|
-
` ${color5.amber("?")} Env var names ${color5.dim("(comma-separated, optional)")}: `
|
|
868
|
-
)).trim();
|
|
869
|
-
const envVars = envVarsRaw ? envVarsRaw.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
|
|
870
|
-
return addKeyForProvider(type, deps, {
|
|
871
|
-
type,
|
|
872
|
-
family,
|
|
873
|
-
...baseUrl ? { baseUrl } : {},
|
|
874
|
-
...models ? { models } : {},
|
|
875
|
-
...envVars ? { envVars } : {}
|
|
876
|
-
});
|
|
877
|
-
}
|
|
878
|
-
async function addManualEntry(deps) {
|
|
879
|
-
const pid = (await deps.reader.readLine(` ${color5.amber("?")} Provider id ${color5.dim("[q to quit]")}: `)).trim();
|
|
880
|
-
if (!pid || pid === "q") return false;
|
|
881
|
-
const famRaw = (await deps.reader.readLine(
|
|
882
|
-
` ${color5.amber("?")} Family ${color5.dim("(anthropic/openai/openai-compatible/google)")}: `
|
|
883
|
-
)).trim();
|
|
884
|
-
const family = validateFamily(famRaw);
|
|
885
|
-
if (!family) {
|
|
886
|
-
deps.renderer.writeError(`Invalid family: "${famRaw}"`);
|
|
887
|
-
return false;
|
|
888
|
-
}
|
|
889
|
-
const baseUrl = (await deps.reader.readLine(` ${color5.amber("?")} Base URL ${color5.dim("(optional)")}: `)).trim();
|
|
890
|
-
return addKeyForProvider(pid, deps, {
|
|
891
|
-
type: pid,
|
|
892
|
-
family,
|
|
893
|
-
...baseUrl ? { baseUrl } : {}
|
|
894
|
-
});
|
|
895
|
-
}
|
|
896
|
-
async function addKeyForProvider(providerId, deps, template) {
|
|
897
|
-
const providers = await loadProviders(deps);
|
|
898
|
-
const existing = providers[providerId];
|
|
899
|
-
const existingKeys = existing ? normalizeKeys(existing) : [];
|
|
900
|
-
const usedLabels = new Set(existingKeys.map((k) => k.label));
|
|
901
|
-
const label = await promptForLabel(deps, usedLabels);
|
|
902
|
-
if (!label) return false;
|
|
903
|
-
const apiKey = await readKeyInput(deps, `API key for ${providerId}/${label}`);
|
|
904
|
-
if (!apiKey) return false;
|
|
905
|
-
await mutateConfigProviders(deps.profileConfigPath, deps.vault, (all) => {
|
|
906
|
-
const existingProv = all[providerId] ?? {
|
|
907
|
-
type: providerId,
|
|
908
|
-
...template
|
|
909
|
-
};
|
|
910
|
-
if (!existingProv.type) existingProv.type = providerId;
|
|
911
|
-
if (!existingProv.family && template.family) {
|
|
912
|
-
existingProv.family = template.family;
|
|
913
|
-
}
|
|
914
|
-
if (!existingProv.baseUrl && template.baseUrl) {
|
|
915
|
-
existingProv.baseUrl = template.baseUrl;
|
|
916
|
-
}
|
|
917
|
-
if (!existingProv.envVars && template.envVars) {
|
|
918
|
-
existingProv.envVars = template.envVars;
|
|
919
|
-
}
|
|
920
|
-
const list = normalizeKeys(existingProv);
|
|
921
|
-
list.push({ label, apiKey, createdAt: nowIso() });
|
|
922
|
-
writeKeysBack(existingProv, list);
|
|
923
|
-
if (!existingProv.activeKey) existingProv.activeKey = label;
|
|
924
|
-
all[providerId] = existingProv;
|
|
925
|
-
}, deps.profileConfigPath);
|
|
926
|
-
deps.renderer.write(
|
|
927
|
-
` ${color5.green("\u2713")} Saved ${color5.bold(providerId)}/${color5.bold(label)}.
|
|
928
|
-
`
|
|
929
|
-
);
|
|
930
|
-
deps.renderer.write(color5.dim(` Launch: wstack --provider ${providerId} "<task>"
|
|
931
|
-
`));
|
|
932
|
-
try {
|
|
933
|
-
await adoptAsDefaultIfUnset(deps, providerId, template);
|
|
934
|
-
} catch {
|
|
935
|
-
}
|
|
936
|
-
return true;
|
|
937
|
-
}
|
|
938
|
-
async function resolveDefaultModelId(deps, providerId, template) {
|
|
939
|
-
if (template.models && template.models.length > 0) return template.models[0];
|
|
940
|
-
const catalogId = template.type && template.type !== providerId ? template.type : providerId;
|
|
941
|
-
const provider = await deps.modelsRegistry.getProvider(catalogId).catch(() => void 0);
|
|
942
|
-
return provider?.models?.[0]?.id;
|
|
943
|
-
}
|
|
944
|
-
async function adoptAsDefaultIfUnset(deps, providerId, template) {
|
|
945
|
-
const targetPath = deps.profileConfigPath;
|
|
946
|
-
let cfg = {};
|
|
947
|
-
try {
|
|
948
|
-
cfg = JSON.parse(await fs.readFile(targetPath, "utf8"));
|
|
949
|
-
} catch {
|
|
950
|
-
}
|
|
951
|
-
if (cfg["provider"] && cfg["model"]) return;
|
|
952
|
-
const modelId = await resolveDefaultModelId(deps, providerId, template);
|
|
953
|
-
if (!modelId) return;
|
|
954
|
-
cfg["provider"] = providerId;
|
|
955
|
-
cfg["model"] = modelId;
|
|
956
|
-
await atomicWrite(targetPath, JSON.stringify(cfg, null, 2), { mode: 384 });
|
|
957
|
-
deps.renderer.write(
|
|
958
|
-
` ${color5.green("\u2713")} Set ${color5.bold(providerId)}/${color5.bold(modelId)} as the default.
|
|
959
|
-
`
|
|
960
|
-
);
|
|
961
|
-
}
|
|
962
|
-
async function promptForLabel(deps, usedLabels) {
|
|
963
|
-
const defaultLabel = suggestLabel(usedLabels);
|
|
964
|
-
const labelRaw = (await deps.reader.readLine(
|
|
965
|
-
` ${color5.amber("?")} Label for this key ${color5.dim(`[${defaultLabel}]`)}: `
|
|
966
|
-
)).trim();
|
|
967
|
-
const label = labelRaw || defaultLabel;
|
|
968
|
-
if (usedLabels.has(label)) {
|
|
969
|
-
deps.renderer.writeError(`Label "${label}" is already used. Use update (u) instead.`);
|
|
970
|
-
return null;
|
|
971
|
-
}
|
|
972
|
-
return label;
|
|
973
|
-
}
|
|
974
|
-
|
|
975
710
|
// src/auth-menu/local.ts
|
|
976
|
-
import { color as color6 } from "@wrongstack/core/utils";
|
|
977
|
-
import { DefaultSecretScrubber as DefaultSecretScrubber2 } from "@wrongstack/core/security";
|
|
978
711
|
import { probeLocalLlm } from "@wrongstack/runtime/probe";
|
|
979
712
|
|
|
980
713
|
// src/auth-menu/auth-menu-audit.ts
|
|
@@ -1091,7 +824,7 @@ async function runAuthLocal(deps, opts = {}) {
|
|
|
1091
824
|
if (!probe.ok) {
|
|
1092
825
|
if (chosen.noAuth) {
|
|
1093
826
|
deps.renderer.write(
|
|
1094
|
-
|
|
827
|
+
color5.dim(
|
|
1095
828
|
` Saving ${chosen.label} anyway \u2014 keyless local gateway needs no key and discovers models when it comes up.
|
|
1096
829
|
`
|
|
1097
830
|
)
|
|
@@ -1100,7 +833,7 @@ async function runAuthLocal(deps, opts = {}) {
|
|
|
1100
833
|
} else {
|
|
1101
834
|
const saveAnyway = await promptSaveAnyway(deps);
|
|
1102
835
|
if (!saveAnyway) {
|
|
1103
|
-
deps.renderer.write(
|
|
836
|
+
deps.renderer.write(color5.dim(" Cancelled. Nothing saved.\n"));
|
|
1104
837
|
return 0;
|
|
1105
838
|
}
|
|
1106
839
|
probeFailedAndSaved = true;
|
|
@@ -1158,19 +891,19 @@ async function runAuthLocal(deps, opts = {}) {
|
|
|
1158
891
|
}
|
|
1159
892
|
if (apiKey) {
|
|
1160
893
|
deps.renderer.write(
|
|
1161
|
-
` ${
|
|
894
|
+
` ${color5.green("\u2713")} Saved ${color5.bold(chosen.id)}/${color5.bold(finalLabel)} \u2192 ${color5.cyan(baseUrl)}
|
|
1162
895
|
`
|
|
1163
896
|
);
|
|
1164
897
|
} else {
|
|
1165
898
|
deps.renderer.write(
|
|
1166
|
-
` ${
|
|
899
|
+
` ${color5.green("\u2713")} Saved ${color5.bold(chosen.id)} (no key) \u2192 ${color5.cyan(baseUrl)}
|
|
1167
900
|
`
|
|
1168
901
|
);
|
|
1169
902
|
}
|
|
1170
903
|
const firstModel = resolvedModels && resolvedModels.length > 0 ? resolvedModels[0] : null;
|
|
1171
|
-
const modelHint = firstModel ?
|
|
904
|
+
const modelHint = firstModel ? color5.cyan(firstModel) : color5.dim("<model-id>");
|
|
1172
905
|
deps.renderer.write(
|
|
1173
|
-
|
|
906
|
+
color5.dim(
|
|
1174
907
|
` Launch: wstack --provider ${chosen.id} --model ${modelHint} "<task>"
|
|
1175
908
|
`
|
|
1176
909
|
)
|
|
@@ -1204,51 +937,51 @@ function resolveModelList(models, probe, scrubber) {
|
|
|
1204
937
|
return out;
|
|
1205
938
|
}
|
|
1206
939
|
function renderProbeResult(deps, probe, opts = {}) {
|
|
1207
|
-
const label = opts.presetLabel ? ` ${
|
|
1208
|
-
const elapsed = probe.elapsedMs !== void 0 ? ` ${
|
|
940
|
+
const label = opts.presetLabel ? ` ${color5.bold(opts.presetLabel)}` : "";
|
|
941
|
+
const elapsed = probe.elapsedMs !== void 0 ? ` ${color5.dim(`(${probe.elapsedMs}ms)`)}` : "";
|
|
1209
942
|
switch (probe.status) {
|
|
1210
943
|
case "ok": {
|
|
1211
|
-
const models = probe.modelCount !== void 0 ?
|
|
944
|
+
const models = probe.modelCount !== void 0 ? color5.dim(`, ${probe.modelCount} model${probe.modelCount === 1 ? "" : "s"}`) : "";
|
|
1212
945
|
deps.renderer.write(
|
|
1213
|
-
` ${
|
|
946
|
+
` ${color5.green("\u25CF")} ${label} health probe ok${models}${elapsed}
|
|
1214
947
|
`
|
|
1215
948
|
);
|
|
1216
949
|
break;
|
|
1217
950
|
}
|
|
1218
951
|
case "timeout": {
|
|
1219
952
|
deps.renderer.write(
|
|
1220
|
-
` ${
|
|
1221
|
-
${
|
|
1222
|
-
${
|
|
953
|
+
` ${color5.red("\u25CF")} ${label} health probe timed out${elapsed}
|
|
954
|
+
${color5.dim("The server did not respond within the timeout.")}
|
|
955
|
+
${color5.dim("Is the local server running? Check the port and try again.")}
|
|
1223
956
|
`
|
|
1224
957
|
);
|
|
1225
958
|
break;
|
|
1226
959
|
}
|
|
1227
960
|
case "unreachable": {
|
|
1228
|
-
const detail = probe.detail ? ` ${
|
|
961
|
+
const detail = probe.detail ? ` ${color5.dim(`(${probe.detail})`)}` : "";
|
|
1229
962
|
deps.renderer.write(
|
|
1230
|
-
` ${
|
|
1231
|
-
${
|
|
1232
|
-
${
|
|
1233
|
-
${
|
|
1234
|
-
${
|
|
963
|
+
` ${color5.red("\u25CF")} ${label} server unreachable${detail}
|
|
964
|
+
${color5.dim("Could not connect. Common causes:")}
|
|
965
|
+
${color5.dim(" - The local server is not running")}
|
|
966
|
+
${color5.dim(" - The port is blocked by a firewall")}
|
|
967
|
+
${color5.dim(" - The base URL is wrong")}
|
|
1235
968
|
`
|
|
1236
969
|
);
|
|
1237
970
|
break;
|
|
1238
971
|
}
|
|
1239
972
|
case "http_error": {
|
|
1240
973
|
const status = probe.httpStatus !== void 0 ? ` HTTP ${probe.httpStatus}` : "";
|
|
1241
|
-
const detail = probe.detail ? ` ${
|
|
974
|
+
const detail = probe.detail ? ` ${color5.dim(`\u2014 ${probe.detail}`)}` : "";
|
|
1242
975
|
deps.renderer.write(
|
|
1243
|
-
` ${
|
|
976
|
+
` ${color5.yellow("\u25CF")} ${label} health probe got${status}${detail}
|
|
1244
977
|
`
|
|
1245
978
|
);
|
|
1246
979
|
break;
|
|
1247
980
|
}
|
|
1248
981
|
case "invalid_response": {
|
|
1249
982
|
deps.renderer.write(
|
|
1250
|
-
` ${
|
|
1251
|
-
` + (probe.detail ? ` ${
|
|
983
|
+
` ${color5.yellow("\u25CF")} ${label} health probe returned an unexpected shape
|
|
984
|
+
` + (probe.detail ? ` ${color5.dim(probe.detail)}
|
|
1252
985
|
` : "")
|
|
1253
986
|
);
|
|
1254
987
|
break;
|
|
@@ -1261,33 +994,33 @@ function renderProbeResult(deps, probe, opts = {}) {
|
|
|
1261
994
|
async function promptSaveAnyway(deps) {
|
|
1262
995
|
const answer = (await deps.reader.readLine(
|
|
1263
996
|
`
|
|
1264
|
-
${
|
|
997
|
+
${color5.amber("?")} ${color5.bold("Save anyway?")} ${color5.dim("[y/N]")}: `
|
|
1265
998
|
)).trim().toLowerCase();
|
|
1266
999
|
return answer === "y" || answer === "yes";
|
|
1267
1000
|
}
|
|
1268
1001
|
async function pickLocalPreset(deps) {
|
|
1269
1002
|
deps.renderer.write(
|
|
1270
1003
|
`
|
|
1271
|
-
${
|
|
1004
|
+
${color5.bold("Local LLM servers")} ${color5.dim("\u2014 pick one to pre-fill the base URL")}
|
|
1272
1005
|
|
|
1273
1006
|
`
|
|
1274
1007
|
);
|
|
1275
1008
|
let idx = 1;
|
|
1276
1009
|
for (const entry of LOCAL_LLM_PRESETS) {
|
|
1277
|
-
const auth = entry.noAuth ?
|
|
1010
|
+
const auth = entry.noAuth ? color5.dim("(no auth)") : color5.dim("(optional auth)");
|
|
1278
1011
|
deps.renderer.write(
|
|
1279
|
-
` ${
|
|
1012
|
+
` ${color5.dim(`${idx}.`.padStart(4))} ${color5.bold(entry.label.padEnd(12))} ${color5.cyan(entry.defaultBaseUrl.padEnd(34))} ${auth}
|
|
1280
1013
|
`
|
|
1281
1014
|
);
|
|
1282
1015
|
deps.renderer.write(
|
|
1283
|
-
` ${
|
|
1016
|
+
` ${color5.dim(entry.hint)}
|
|
1284
1017
|
`
|
|
1285
1018
|
);
|
|
1286
1019
|
idx++;
|
|
1287
1020
|
}
|
|
1288
1021
|
const answer = (await deps.reader.readLine(
|
|
1289
1022
|
`
|
|
1290
|
-
${
|
|
1023
|
+
${color5.amber("?")} Pick ${color5.dim(`(1-${LOCAL_LLM_PRESETS.length}, q to quit)`)}: `
|
|
1291
1024
|
)).trim().toLowerCase();
|
|
1292
1025
|
if (!answer || answer === "q" || answer === "quit") return void 0;
|
|
1293
1026
|
const num = Number.parseInt(answer, 10);
|
|
@@ -1301,12 +1034,287 @@ ${color6.amber("?")} Pick ${color6.dim(`(1-${LOCAL_LLM_PRESETS.length}, q to qui
|
|
|
1301
1034
|
}
|
|
1302
1035
|
async function promptOptionalKey(deps, preset) {
|
|
1303
1036
|
const answer = (await deps.reader.readSecret(
|
|
1304
|
-
` ${
|
|
1037
|
+
` ${color5.amber("?")} ${color5.bold(preset.label)} API key ${color5.dim("(hidden \u2014 Enter to skip if auth is disabled on the server)")}: `
|
|
1305
1038
|
)).trim();
|
|
1306
1039
|
if (!answer) return void 0;
|
|
1307
1040
|
return answer;
|
|
1308
1041
|
}
|
|
1309
1042
|
|
|
1043
|
+
// src/auth-menu/add-provider.ts
|
|
1044
|
+
import { atomicWrite, color as color6 } from "@wrongstack/core/utils";
|
|
1045
|
+
import { catalogProviderIdFor, PROVIDER_DEFINITIONS } from "@wrongstack/providers";
|
|
1046
|
+
import * as fs from "node:fs/promises";
|
|
1047
|
+
|
|
1048
|
+
// src/provider-id.ts
|
|
1049
|
+
function nextCustomProviderId(existing) {
|
|
1050
|
+
const taken = new Set(existing);
|
|
1051
|
+
let n = 1;
|
|
1052
|
+
while (taken.has(`custom-${n}`)) n++;
|
|
1053
|
+
return `custom-${n}`;
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
// src/auth-menu/add-provider.ts
|
|
1057
|
+
function ownDefinitionsAsCatalog(known) {
|
|
1058
|
+
return Object.values(PROVIDER_DEFINITIONS).filter((d) => !known.has(d.id) && d.family !== "unsupported" && d.usage !== "local").map((d) => ({
|
|
1059
|
+
id: d.id,
|
|
1060
|
+
name: d.name,
|
|
1061
|
+
family: d.family,
|
|
1062
|
+
envVars: [...d.envVars],
|
|
1063
|
+
models: [],
|
|
1064
|
+
// `discoveryOnlyBaseUrl` definitions keep their model-list URL out of the
|
|
1065
|
+
// Base URL prompt: offering it as a default persists it as the provider's
|
|
1066
|
+
// wire base, which is a different endpoint (see the AI Gateway entry).
|
|
1067
|
+
...d.baseUrl && !d.discoveryOnlyBaseUrl ? { apiBase: d.baseUrl } : {},
|
|
1068
|
+
...d.docsUrl ? { doc: d.docsUrl } : {}
|
|
1069
|
+
}));
|
|
1070
|
+
}
|
|
1071
|
+
async function addFromCatalog(deps) {
|
|
1072
|
+
let catalog = [];
|
|
1073
|
+
try {
|
|
1074
|
+
catalog = (await deps.modelsRegistry.listProviders()).filter((p) => p.family !== "unsupported");
|
|
1075
|
+
} catch {
|
|
1076
|
+
deps.renderer.writeWarning("Catalog unavailable \u2014 falling back to manual entry.\n");
|
|
1077
|
+
}
|
|
1078
|
+
if (catalog.length === 0) {
|
|
1079
|
+
return addManualEntry(deps);
|
|
1080
|
+
}
|
|
1081
|
+
catalog = [...catalog, ...ownDefinitionsAsCatalog(new Set(catalog.map((p) => p.id)))];
|
|
1082
|
+
const saved = new Set(Object.keys(await loadProviders(deps)));
|
|
1083
|
+
deps.renderer.write(
|
|
1084
|
+
color6.dim(
|
|
1085
|
+
` Catalog: ${catalog.length} providers. Type to filter, \u2191/\u2193 navigate, Enter to select.
|
|
1086
|
+
`
|
|
1087
|
+
)
|
|
1088
|
+
);
|
|
1089
|
+
deps.renderer.write(` ${color6.dim("\u25C9 already saved \u25CB no key yet")}
|
|
1090
|
+
|
|
1091
|
+
`);
|
|
1092
|
+
renderOAuthLoginOptions(deps);
|
|
1093
|
+
deps.renderer.write("\n");
|
|
1094
|
+
const chosen = await runLiveProviderPicker(catalog, saved);
|
|
1095
|
+
if (!chosen) {
|
|
1096
|
+
deps.renderer.write(
|
|
1097
|
+
`
|
|
1098
|
+
${color6.dim("OAuth login options:")} ${color6.dim("chatgpt")}, ${color6.dim("claude")}, or ${color6.dim("copilot")}?
|
|
1099
|
+
`
|
|
1100
|
+
);
|
|
1101
|
+
const answer = (await deps.reader.readLine(
|
|
1102
|
+
` ${color6.amber("?")} OAuth or q to quit ${color6.dim("[chatgpt/claude/copilot]")}: `
|
|
1103
|
+
)).trim();
|
|
1104
|
+
if (answer && await runOAuthLoginChoice(deps, answer, { allowNumeric: false })) {
|
|
1105
|
+
return true;
|
|
1106
|
+
}
|
|
1107
|
+
return false;
|
|
1108
|
+
}
|
|
1109
|
+
return addKeyForCatalogProvider(deps, chosen);
|
|
1110
|
+
}
|
|
1111
|
+
async function addKeyForCatalogProvider(deps, chosen) {
|
|
1112
|
+
deps.renderer.write(
|
|
1113
|
+
color6.dim(`
|
|
1114
|
+
Defaults from models.dev \u2014 press Enter to keep, or type overrides.
|
|
1115
|
+
`)
|
|
1116
|
+
);
|
|
1117
|
+
const famRaw = (await deps.reader.readLine(
|
|
1118
|
+
` ${color6.amber("?")} Family ${color6.dim(`[${chosen.family}]`)} ${color6.dim("(q to quit)")}: `
|
|
1119
|
+
)).trim();
|
|
1120
|
+
if (famRaw === "q") return false;
|
|
1121
|
+
let family = chosen.family;
|
|
1122
|
+
if (famRaw) {
|
|
1123
|
+
const validated = validateFamily(famRaw);
|
|
1124
|
+
if (!validated) {
|
|
1125
|
+
deps.renderer.writeError(
|
|
1126
|
+
`Invalid family: "${famRaw}" (must be: anthropic, openai, openai-compatible, google).`
|
|
1127
|
+
);
|
|
1128
|
+
return false;
|
|
1129
|
+
}
|
|
1130
|
+
family = validated;
|
|
1131
|
+
}
|
|
1132
|
+
const baseRaw = (await deps.reader.readLine(
|
|
1133
|
+
` ${color6.amber("?")} Base URL ${color6.dim(`[${chosen.apiBase ?? "unset"}]`)} ${color6.dim("(q to quit)")}: `
|
|
1134
|
+
)).trim();
|
|
1135
|
+
if (baseRaw === "q") return false;
|
|
1136
|
+
const baseUrl = baseRaw || chosen.apiBase;
|
|
1137
|
+
const providersNow = await loadProviders(deps);
|
|
1138
|
+
let suggestedAlias = chosen.id;
|
|
1139
|
+
if (family !== chosen.family) {
|
|
1140
|
+
let candidate = `${chosen.id}-${family}`;
|
|
1141
|
+
let n = 2;
|
|
1142
|
+
while (providersNow[candidate]) {
|
|
1143
|
+
candidate = `${chosen.id}-${family}-${n}`;
|
|
1144
|
+
n++;
|
|
1145
|
+
}
|
|
1146
|
+
suggestedAlias = candidate;
|
|
1147
|
+
}
|
|
1148
|
+
const aliasRaw = (await deps.reader.readLine(
|
|
1149
|
+
` ${color6.amber("?")} Save as alias ${color6.dim(`[${suggestedAlias}]`)} ${color6.dim("(used with --provider <alias>)")}: `
|
|
1150
|
+
)).trim();
|
|
1151
|
+
const alias = aliasRaw || suggestedAlias;
|
|
1152
|
+
const existing = providersNow[alias];
|
|
1153
|
+
if (existing) {
|
|
1154
|
+
const sameFamily = (existing.family ?? chosen.family) === family;
|
|
1155
|
+
const sameBase = (existing.baseUrl ?? chosen.apiBase) === baseUrl;
|
|
1156
|
+
if (!sameFamily || !sameBase) {
|
|
1157
|
+
deps.renderer.writeError(
|
|
1158
|
+
`Alias "${alias}" already exists with different family/baseUrl.
|
|
1159
|
+
Existing: family=${existing.family ?? "(unset)"}, baseUrl=${existing.baseUrl ?? "(unset)"}
|
|
1160
|
+
New: family=${family}, baseUrl=${baseUrl ?? "(unset)"}
|
|
1161
|
+
Pick a different alias to keep them separate.`
|
|
1162
|
+
);
|
|
1163
|
+
return false;
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
return addKeyForProvider(alias, deps, {
|
|
1167
|
+
type: chosen.id,
|
|
1168
|
+
family,
|
|
1169
|
+
baseUrl,
|
|
1170
|
+
envVars: chosen.envVars
|
|
1171
|
+
});
|
|
1172
|
+
}
|
|
1173
|
+
async function addCustomProvider(deps) {
|
|
1174
|
+
deps.renderer.write(
|
|
1175
|
+
`
|
|
1176
|
+
${color6.bold("Custom provider")} ${color6.dim("\u2014 for local models or proxies not in the catalog.")}
|
|
1177
|
+
`
|
|
1178
|
+
);
|
|
1179
|
+
const providersNow = await loadProviders(deps);
|
|
1180
|
+
const idRaw = (await deps.reader.readLine(
|
|
1181
|
+
` ${color6.amber("?")} Provider id ${color6.dim('(e.g. "local-llama", "my-proxy"; blank = auto custom-N, q to quit)')}: `
|
|
1182
|
+
)).trim();
|
|
1183
|
+
if (idRaw === "q") return false;
|
|
1184
|
+
let type = idRaw;
|
|
1185
|
+
if (!type) {
|
|
1186
|
+
type = nextCustomProviderId(Object.keys(providersNow));
|
|
1187
|
+
deps.renderer.write(` ${color6.dim("Using generated id:")} ${color6.bold(type)}
|
|
1188
|
+
`);
|
|
1189
|
+
} else if (providersNow[type]) {
|
|
1190
|
+
deps.renderer.writeWarning(`"${type}" already exists. Pick it from the main menu to edit.`);
|
|
1191
|
+
return false;
|
|
1192
|
+
}
|
|
1193
|
+
const familyRaw = (await deps.reader.readLine(
|
|
1194
|
+
` ${color6.amber("?")} Wire family ${color6.dim("(anthropic | openai | openai-compatible | google)")} ${color6.dim("(q to quit)")}: `
|
|
1195
|
+
)).trim();
|
|
1196
|
+
if (familyRaw === "q") return false;
|
|
1197
|
+
const family = validateFamily(familyRaw);
|
|
1198
|
+
if (!family) {
|
|
1199
|
+
deps.renderer.writeError(`Invalid family: "${familyRaw}"`);
|
|
1200
|
+
return false;
|
|
1201
|
+
}
|
|
1202
|
+
const baseUrl = (await deps.reader.readLine(
|
|
1203
|
+
` ${color6.amber("?")} Base URL ${color6.dim("(e.g. http://localhost:11434/v1, optional)")}: `
|
|
1204
|
+
)).trim();
|
|
1205
|
+
const modelsRaw = (await deps.reader.readLine(
|
|
1206
|
+
` ${color6.amber("?")} Model ids ${color6.dim("(comma-separated, optional)")}: `
|
|
1207
|
+
)).trim();
|
|
1208
|
+
const models = modelsRaw ? modelsRaw.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
|
|
1209
|
+
const envVarsRaw = (await deps.reader.readLine(
|
|
1210
|
+
` ${color6.amber("?")} Env var names ${color6.dim("(comma-separated, optional)")}: `
|
|
1211
|
+
)).trim();
|
|
1212
|
+
const envVars = envVarsRaw ? envVarsRaw.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
|
|
1213
|
+
return addKeyForProvider(type, deps, {
|
|
1214
|
+
type,
|
|
1215
|
+
family,
|
|
1216
|
+
...baseUrl ? { baseUrl } : {},
|
|
1217
|
+
...models ? { models } : {},
|
|
1218
|
+
...envVars ? { envVars } : {}
|
|
1219
|
+
});
|
|
1220
|
+
}
|
|
1221
|
+
async function addManualEntry(deps) {
|
|
1222
|
+
const pid = (await deps.reader.readLine(` ${color6.amber("?")} Provider id ${color6.dim("[q to quit]")}: `)).trim();
|
|
1223
|
+
if (!pid || pid === "q") return false;
|
|
1224
|
+
const famRaw = (await deps.reader.readLine(
|
|
1225
|
+
` ${color6.amber("?")} Family ${color6.dim("(anthropic/openai/openai-compatible/google)")}: `
|
|
1226
|
+
)).trim();
|
|
1227
|
+
const family = validateFamily(famRaw);
|
|
1228
|
+
if (!family) {
|
|
1229
|
+
deps.renderer.writeError(`Invalid family: "${famRaw}"`);
|
|
1230
|
+
return false;
|
|
1231
|
+
}
|
|
1232
|
+
const baseUrl = (await deps.reader.readLine(` ${color6.amber("?")} Base URL ${color6.dim("(optional)")}: `)).trim();
|
|
1233
|
+
return addKeyForProvider(pid, deps, {
|
|
1234
|
+
type: pid,
|
|
1235
|
+
family,
|
|
1236
|
+
...baseUrl ? { baseUrl } : {}
|
|
1237
|
+
});
|
|
1238
|
+
}
|
|
1239
|
+
async function addKeyForProvider(providerId, deps, template) {
|
|
1240
|
+
const providers = await loadProviders(deps);
|
|
1241
|
+
const existing = providers[providerId];
|
|
1242
|
+
const existingKeys = existing ? normalizeKeys(existing) : [];
|
|
1243
|
+
const usedLabels = new Set(existingKeys.map((k) => k.label));
|
|
1244
|
+
const label = await promptForLabel(deps, usedLabels);
|
|
1245
|
+
if (!label) return false;
|
|
1246
|
+
const apiKey = await readKeyInput(deps, `API key for ${providerId}/${label}`);
|
|
1247
|
+
if (!apiKey) return false;
|
|
1248
|
+
await mutateConfigProviders(deps.profileConfigPath, deps.vault, (all) => {
|
|
1249
|
+
const existingProv = all[providerId] ?? {
|
|
1250
|
+
type: providerId,
|
|
1251
|
+
...template
|
|
1252
|
+
};
|
|
1253
|
+
if (!existingProv.type) existingProv.type = providerId;
|
|
1254
|
+
if (!existingProv.family && template.family) {
|
|
1255
|
+
existingProv.family = template.family;
|
|
1256
|
+
}
|
|
1257
|
+
if (!existingProv.baseUrl && template.baseUrl) {
|
|
1258
|
+
existingProv.baseUrl = template.baseUrl;
|
|
1259
|
+
}
|
|
1260
|
+
if (!existingProv.envVars && template.envVars) {
|
|
1261
|
+
existingProv.envVars = template.envVars;
|
|
1262
|
+
}
|
|
1263
|
+
const list = normalizeKeys(existingProv);
|
|
1264
|
+
list.push({ label, apiKey, createdAt: nowIso() });
|
|
1265
|
+
writeKeysBack(existingProv, list);
|
|
1266
|
+
if (!existingProv.activeKey) existingProv.activeKey = label;
|
|
1267
|
+
all[providerId] = existingProv;
|
|
1268
|
+
}, deps.profileConfigPath);
|
|
1269
|
+
deps.renderer.write(
|
|
1270
|
+
` ${color6.green("\u2713")} Saved ${color6.bold(providerId)}/${color6.bold(label)}.
|
|
1271
|
+
`
|
|
1272
|
+
);
|
|
1273
|
+
deps.renderer.write(color6.dim(` Launch: wstack --provider ${providerId} "<task>"
|
|
1274
|
+
`));
|
|
1275
|
+
try {
|
|
1276
|
+
await adoptAsDefaultIfUnset(deps, providerId, template);
|
|
1277
|
+
} catch {
|
|
1278
|
+
}
|
|
1279
|
+
return true;
|
|
1280
|
+
}
|
|
1281
|
+
async function resolveDefaultModelId(deps, providerId, template) {
|
|
1282
|
+
if (template.models && template.models.length > 0) return template.models[0];
|
|
1283
|
+
const catalogId = catalogProviderIdFor(providerId, template.type);
|
|
1284
|
+
const provider = await deps.modelsRegistry.getProvider(catalogId).catch(() => void 0);
|
|
1285
|
+
return provider?.models?.[0]?.id;
|
|
1286
|
+
}
|
|
1287
|
+
async function adoptAsDefaultIfUnset(deps, providerId, template) {
|
|
1288
|
+
const targetPath = deps.profileConfigPath;
|
|
1289
|
+
let cfg = {};
|
|
1290
|
+
try {
|
|
1291
|
+
cfg = JSON.parse(await fs.readFile(targetPath, "utf8"));
|
|
1292
|
+
} catch {
|
|
1293
|
+
}
|
|
1294
|
+
if (cfg["provider"] && cfg["model"]) return;
|
|
1295
|
+
const modelId = await resolveDefaultModelId(deps, providerId, template);
|
|
1296
|
+
if (!modelId) return;
|
|
1297
|
+
cfg["provider"] = providerId;
|
|
1298
|
+
cfg["model"] = modelId;
|
|
1299
|
+
await atomicWrite(targetPath, JSON.stringify(cfg, null, 2), { mode: 384 });
|
|
1300
|
+
deps.renderer.write(
|
|
1301
|
+
` ${color6.green("\u2713")} Set ${color6.bold(providerId)}/${color6.bold(modelId)} as the default.
|
|
1302
|
+
`
|
|
1303
|
+
);
|
|
1304
|
+
}
|
|
1305
|
+
async function promptForLabel(deps, usedLabels) {
|
|
1306
|
+
const defaultLabel = suggestLabel(usedLabels);
|
|
1307
|
+
const labelRaw = (await deps.reader.readLine(
|
|
1308
|
+
` ${color6.amber("?")} Label for this key ${color6.dim(`[${defaultLabel}]`)}: `
|
|
1309
|
+
)).trim();
|
|
1310
|
+
const label = labelRaw || defaultLabel;
|
|
1311
|
+
if (usedLabels.has(label)) {
|
|
1312
|
+
deps.renderer.writeError(`Label "${label}" is already used. Use update (u) instead.`);
|
|
1313
|
+
return null;
|
|
1314
|
+
}
|
|
1315
|
+
return label;
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1310
1318
|
export {
|
|
1311
1319
|
loadProviders,
|
|
1312
1320
|
renderProviderHeader,
|
|
@@ -1327,4 +1335,4 @@ export {
|
|
|
1327
1335
|
addKeyForProvider,
|
|
1328
1336
|
runAuthLocal
|
|
1329
1337
|
};
|
|
1330
|
-
//# sourceMappingURL=chunk-
|
|
1338
|
+
//# sourceMappingURL=chunk-O7XNGCRT.js.map
|