@wrongstack/cli 0.300.0 → 0.302.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-IDNS2YVE.js → acp-DATHHPNT.js} +34 -15
- package/dist/acp-server-agent.d.ts +1 -1
- package/dist/{audit-2ZSFGNIF.js → audit-QRCLEHHW.js} +5 -1
- package/dist/{auth-6T6ZOT2R.js → auth-PJD3JRH2.js} +150 -37
- package/dist/boot/dispatch-webui.d.ts +3 -16
- package/dist/boot/launch-menu.d.ts +2 -4
- package/dist/boot/tool-registry.d.ts +3 -0
- package/dist/boot/webui-session-child.d.ts +91 -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-GYDQABMA.js → chunk-2KDOFTTM.js} +44 -32
- package/dist/{chunk-V76R7DC5.js → chunk-3MOUBRKZ.js} +311 -309
- package/dist/chunk-CSAPOCBP.js +24 -0
- package/dist/{chunk-N7ULWBO5.js → chunk-ETOEGL3V.js} +3 -7
- package/dist/{chunk-3MWUZMOL.js → chunk-GUHQQG63.js} +14 -3
- package/dist/chunk-KE7E7DPX.js +38 -0
- package/dist/{chunk-PEMN4T5O.js → chunk-MT6FGXO3.js} +23 -4
- package/dist/chunk-PFVVUH5B.js +127 -0
- package/dist/{chunk-DPJZQCVP.js → chunk-YMXXOOFN.js} +1 -1
- package/dist/{chunk-4WJKVQJY.js → chunk-ZECLGUTF.js} +15 -3
- package/dist/cli-context.d.ts +2 -0
- package/dist/{cli-main-7B3G2YSG.js → cli-main-EOJ74CI4.js} +336 -99
- package/dist/execute-deps.d.ts +3 -0
- package/dist/{execution-SHGM7BVC.js → execution-CQ772VPS.js} +246 -97
- 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/host-context.d.ts +1 -1
- package/dist/fleet/host-helpers.d.ts +14 -0
- package/dist/fleet/status-broadcast.d.ts +1 -1
- package/dist/{hq-DVDD4XPV.js → hq-LN5CJHCM.js} +2 -2
- package/dist/hq-server/routes.d.ts +10 -1
- package/dist/hq-server/ws.d.ts +17 -6
- package/dist/{hq-server-DQHLNHDL.js → hq-server-S7HZG3PA.js} +3 -2
- package/dist/index.js +142 -100
- package/dist/live-settings-input.d.ts +5 -0
- package/dist/{mcp-CYLPUEHC.js → mcp-OHPJH73U.js} +6 -2
- package/dist/{modeldiag-YOQZHBQV.js → modeldiag-3DOK4BWW.js} +2 -2
- package/dist/{plugin-usage-EOG4ET4S.js → plugin-usage-YEVLOR33.js} +7 -3
- package/dist/{plugins-56CMWMPR.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-K6RT7ABI.js → providers-models-WEZ4EGLL.js} +6 -5
- 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/subcommands/flags.d.ts +23 -0
- package/dist/terminal-format.d.ts +15 -0
- package/dist/{webui-server-G3EKFXLL.js → webui-server-NFNRGNNT.js} +54 -29
- package/dist/webui-server-options.d.ts +7 -0
- package/dist/wiring/brain-and-orchestration.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 +2 -3
- package/dist/wiring/provider-utility-tools.d.ts +1 -0
- package/dist/wiring/tools.d.ts +4 -5
- package/package.json +23 -23
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
LOCAL_LLM_PRESETS,
|
|
3
3
|
runLiveProviderPicker
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-MT6FGXO3.js";
|
|
5
5
|
import {
|
|
6
6
|
openBrowser,
|
|
7
7
|
runCodexOAuthLogin,
|
|
@@ -535,6 +535,10 @@ async function runOAuthLoginMenu(deps) {
|
|
|
535
535
|
await runOAuthLoginChoice(deps, pick);
|
|
536
536
|
}
|
|
537
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
|
+
|
|
538
542
|
// src/auth-menu/helpers.ts
|
|
539
543
|
async function loadProviders(deps) {
|
|
540
544
|
return loadConfigProviders(deps.profileConfigPath, deps.vault, {
|
|
@@ -703,284 +707,7 @@ function validateFamily(raw) {
|
|
|
703
707
|
return valid.includes(raw) ? raw : null;
|
|
704
708
|
}
|
|
705
709
|
|
|
706
|
-
// src/auth-menu/add-provider.ts
|
|
707
|
-
import { atomicWrite, color as color5 } from "@wrongstack/core/utils";
|
|
708
|
-
import { catalogProviderIdFor, PROVIDER_DEFINITIONS } from "@wrongstack/providers";
|
|
709
|
-
import * as fs from "node:fs/promises";
|
|
710
|
-
|
|
711
|
-
// src/provider-id.ts
|
|
712
|
-
function nextCustomProviderId(existing) {
|
|
713
|
-
const taken = new Set(existing);
|
|
714
|
-
let n = 1;
|
|
715
|
-
while (taken.has(`custom-${n}`)) n++;
|
|
716
|
-
return `custom-${n}`;
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
// src/auth-menu/add-provider.ts
|
|
720
|
-
function ownDefinitionsAsCatalog(known) {
|
|
721
|
-
return Object.values(PROVIDER_DEFINITIONS).filter((d) => !known.has(d.id) && d.family !== "unsupported" && d.usage !== "local").map((d) => ({
|
|
722
|
-
id: d.id,
|
|
723
|
-
name: d.name,
|
|
724
|
-
family: d.family,
|
|
725
|
-
envVars: [...d.envVars],
|
|
726
|
-
models: [],
|
|
727
|
-
// `discoveryOnlyBaseUrl` definitions keep their model-list URL out of the
|
|
728
|
-
// Base URL prompt: offering it as a default persists it as the provider's
|
|
729
|
-
// wire base, which is a different endpoint (see the AI Gateway entry).
|
|
730
|
-
...d.baseUrl && !d.discoveryOnlyBaseUrl ? { apiBase: d.baseUrl } : {},
|
|
731
|
-
...d.docsUrl ? { doc: d.docsUrl } : {}
|
|
732
|
-
}));
|
|
733
|
-
}
|
|
734
|
-
async function addFromCatalog(deps) {
|
|
735
|
-
let catalog = [];
|
|
736
|
-
try {
|
|
737
|
-
catalog = (await deps.modelsRegistry.listProviders()).filter((p) => p.family !== "unsupported");
|
|
738
|
-
} catch {
|
|
739
|
-
deps.renderer.writeWarning("Catalog unavailable \u2014 falling back to manual entry.\n");
|
|
740
|
-
}
|
|
741
|
-
if (catalog.length === 0) {
|
|
742
|
-
return addManualEntry(deps);
|
|
743
|
-
}
|
|
744
|
-
catalog = [...catalog, ...ownDefinitionsAsCatalog(new Set(catalog.map((p) => p.id)))];
|
|
745
|
-
const saved = new Set(Object.keys(await loadProviders(deps)));
|
|
746
|
-
deps.renderer.write(
|
|
747
|
-
color5.dim(
|
|
748
|
-
` Catalog: ${catalog.length} providers. Type to filter, \u2191/\u2193 navigate, Enter to select.
|
|
749
|
-
`
|
|
750
|
-
)
|
|
751
|
-
);
|
|
752
|
-
deps.renderer.write(` ${color5.dim("\u25C9 already saved \u25CB no key yet")}
|
|
753
|
-
|
|
754
|
-
`);
|
|
755
|
-
renderOAuthLoginOptions(deps);
|
|
756
|
-
deps.renderer.write("\n");
|
|
757
|
-
const chosen = await runLiveProviderPicker(catalog, saved);
|
|
758
|
-
if (!chosen) {
|
|
759
|
-
deps.renderer.write(
|
|
760
|
-
`
|
|
761
|
-
${color5.dim("OAuth login options:")} ${color5.dim("chatgpt")}, ${color5.dim("claude")}, or ${color5.dim("copilot")}?
|
|
762
|
-
`
|
|
763
|
-
);
|
|
764
|
-
const answer = (await deps.reader.readLine(
|
|
765
|
-
` ${color5.amber("?")} OAuth or q to quit ${color5.dim("[chatgpt/claude/copilot]")}: `
|
|
766
|
-
)).trim();
|
|
767
|
-
if (answer && await runOAuthLoginChoice(deps, answer, { allowNumeric: false })) {
|
|
768
|
-
return true;
|
|
769
|
-
}
|
|
770
|
-
return false;
|
|
771
|
-
}
|
|
772
|
-
return addKeyForCatalogProvider(deps, chosen);
|
|
773
|
-
}
|
|
774
|
-
async function addKeyForCatalogProvider(deps, chosen) {
|
|
775
|
-
deps.renderer.write(
|
|
776
|
-
color5.dim(`
|
|
777
|
-
Defaults from models.dev \u2014 press Enter to keep, or type overrides.
|
|
778
|
-
`)
|
|
779
|
-
);
|
|
780
|
-
const famRaw = (await deps.reader.readLine(
|
|
781
|
-
` ${color5.amber("?")} Family ${color5.dim(`[${chosen.family}]`)} ${color5.dim("(q to quit)")}: `
|
|
782
|
-
)).trim();
|
|
783
|
-
if (famRaw === "q") return false;
|
|
784
|
-
let family = chosen.family;
|
|
785
|
-
if (famRaw) {
|
|
786
|
-
const validated = validateFamily(famRaw);
|
|
787
|
-
if (!validated) {
|
|
788
|
-
deps.renderer.writeError(
|
|
789
|
-
`Invalid family: "${famRaw}" (must be: anthropic, openai, openai-compatible, google).`
|
|
790
|
-
);
|
|
791
|
-
return false;
|
|
792
|
-
}
|
|
793
|
-
family = validated;
|
|
794
|
-
}
|
|
795
|
-
const baseRaw = (await deps.reader.readLine(
|
|
796
|
-
` ${color5.amber("?")} Base URL ${color5.dim(`[${chosen.apiBase ?? "unset"}]`)} ${color5.dim("(q to quit)")}: `
|
|
797
|
-
)).trim();
|
|
798
|
-
if (baseRaw === "q") return false;
|
|
799
|
-
const baseUrl = baseRaw || chosen.apiBase;
|
|
800
|
-
const providersNow = await loadProviders(deps);
|
|
801
|
-
let suggestedAlias = chosen.id;
|
|
802
|
-
if (family !== chosen.family) {
|
|
803
|
-
let candidate = `${chosen.id}-${family}`;
|
|
804
|
-
let n = 2;
|
|
805
|
-
while (providersNow[candidate]) {
|
|
806
|
-
candidate = `${chosen.id}-${family}-${n}`;
|
|
807
|
-
n++;
|
|
808
|
-
}
|
|
809
|
-
suggestedAlias = candidate;
|
|
810
|
-
}
|
|
811
|
-
const aliasRaw = (await deps.reader.readLine(
|
|
812
|
-
` ${color5.amber("?")} Save as alias ${color5.dim(`[${suggestedAlias}]`)} ${color5.dim("(used with --provider <alias>)")}: `
|
|
813
|
-
)).trim();
|
|
814
|
-
const alias = aliasRaw || suggestedAlias;
|
|
815
|
-
const existing = providersNow[alias];
|
|
816
|
-
if (existing) {
|
|
817
|
-
const sameFamily = (existing.family ?? chosen.family) === family;
|
|
818
|
-
const sameBase = (existing.baseUrl ?? chosen.apiBase) === baseUrl;
|
|
819
|
-
if (!sameFamily || !sameBase) {
|
|
820
|
-
deps.renderer.writeError(
|
|
821
|
-
`Alias "${alias}" already exists with different family/baseUrl.
|
|
822
|
-
Existing: family=${existing.family ?? "(unset)"}, baseUrl=${existing.baseUrl ?? "(unset)"}
|
|
823
|
-
New: family=${family}, baseUrl=${baseUrl ?? "(unset)"}
|
|
824
|
-
Pick a different alias to keep them separate.`
|
|
825
|
-
);
|
|
826
|
-
return false;
|
|
827
|
-
}
|
|
828
|
-
}
|
|
829
|
-
return addKeyForProvider(alias, deps, {
|
|
830
|
-
type: chosen.id,
|
|
831
|
-
family,
|
|
832
|
-
baseUrl,
|
|
833
|
-
envVars: chosen.envVars
|
|
834
|
-
});
|
|
835
|
-
}
|
|
836
|
-
async function addCustomProvider(deps) {
|
|
837
|
-
deps.renderer.write(
|
|
838
|
-
`
|
|
839
|
-
${color5.bold("Custom provider")} ${color5.dim("\u2014 for local models or proxies not in the catalog.")}
|
|
840
|
-
`
|
|
841
|
-
);
|
|
842
|
-
const providersNow = await loadProviders(deps);
|
|
843
|
-
const idRaw = (await deps.reader.readLine(
|
|
844
|
-
` ${color5.amber("?")} Provider id ${color5.dim('(e.g. "local-llama", "my-proxy"; blank = auto custom-N, q to quit)')}: `
|
|
845
|
-
)).trim();
|
|
846
|
-
if (idRaw === "q") return false;
|
|
847
|
-
let type = idRaw;
|
|
848
|
-
if (!type) {
|
|
849
|
-
type = nextCustomProviderId(Object.keys(providersNow));
|
|
850
|
-
deps.renderer.write(` ${color5.dim("Using generated id:")} ${color5.bold(type)}
|
|
851
|
-
`);
|
|
852
|
-
} else if (providersNow[type]) {
|
|
853
|
-
deps.renderer.writeWarning(`"${type}" already exists. Pick it from the main menu to edit.`);
|
|
854
|
-
return false;
|
|
855
|
-
}
|
|
856
|
-
const familyRaw = (await deps.reader.readLine(
|
|
857
|
-
` ${color5.amber("?")} Wire family ${color5.dim("(anthropic | openai | openai-compatible | google)")} ${color5.dim("(q to quit)")}: `
|
|
858
|
-
)).trim();
|
|
859
|
-
if (familyRaw === "q") return false;
|
|
860
|
-
const family = validateFamily(familyRaw);
|
|
861
|
-
if (!family) {
|
|
862
|
-
deps.renderer.writeError(`Invalid family: "${familyRaw}"`);
|
|
863
|
-
return false;
|
|
864
|
-
}
|
|
865
|
-
const baseUrl = (await deps.reader.readLine(
|
|
866
|
-
` ${color5.amber("?")} Base URL ${color5.dim("(e.g. http://localhost:11434/v1, optional)")}: `
|
|
867
|
-
)).trim();
|
|
868
|
-
const modelsRaw = (await deps.reader.readLine(
|
|
869
|
-
` ${color5.amber("?")} Model ids ${color5.dim("(comma-separated, optional)")}: `
|
|
870
|
-
)).trim();
|
|
871
|
-
const models = modelsRaw ? modelsRaw.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
|
|
872
|
-
const envVarsRaw = (await deps.reader.readLine(
|
|
873
|
-
` ${color5.amber("?")} Env var names ${color5.dim("(comma-separated, optional)")}: `
|
|
874
|
-
)).trim();
|
|
875
|
-
const envVars = envVarsRaw ? envVarsRaw.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
|
|
876
|
-
return addKeyForProvider(type, deps, {
|
|
877
|
-
type,
|
|
878
|
-
family,
|
|
879
|
-
...baseUrl ? { baseUrl } : {},
|
|
880
|
-
...models ? { models } : {},
|
|
881
|
-
...envVars ? { envVars } : {}
|
|
882
|
-
});
|
|
883
|
-
}
|
|
884
|
-
async function addManualEntry(deps) {
|
|
885
|
-
const pid = (await deps.reader.readLine(` ${color5.amber("?")} Provider id ${color5.dim("[q to quit]")}: `)).trim();
|
|
886
|
-
if (!pid || pid === "q") return false;
|
|
887
|
-
const famRaw = (await deps.reader.readLine(
|
|
888
|
-
` ${color5.amber("?")} Family ${color5.dim("(anthropic/openai/openai-compatible/google)")}: `
|
|
889
|
-
)).trim();
|
|
890
|
-
const family = validateFamily(famRaw);
|
|
891
|
-
if (!family) {
|
|
892
|
-
deps.renderer.writeError(`Invalid family: "${famRaw}"`);
|
|
893
|
-
return false;
|
|
894
|
-
}
|
|
895
|
-
const baseUrl = (await deps.reader.readLine(` ${color5.amber("?")} Base URL ${color5.dim("(optional)")}: `)).trim();
|
|
896
|
-
return addKeyForProvider(pid, deps, {
|
|
897
|
-
type: pid,
|
|
898
|
-
family,
|
|
899
|
-
...baseUrl ? { baseUrl } : {}
|
|
900
|
-
});
|
|
901
|
-
}
|
|
902
|
-
async function addKeyForProvider(providerId, deps, template) {
|
|
903
|
-
const providers = await loadProviders(deps);
|
|
904
|
-
const existing = providers[providerId];
|
|
905
|
-
const existingKeys = existing ? normalizeKeys(existing) : [];
|
|
906
|
-
const usedLabels = new Set(existingKeys.map((k) => k.label));
|
|
907
|
-
const label = await promptForLabel(deps, usedLabels);
|
|
908
|
-
if (!label) return false;
|
|
909
|
-
const apiKey = await readKeyInput(deps, `API key for ${providerId}/${label}`);
|
|
910
|
-
if (!apiKey) return false;
|
|
911
|
-
await mutateConfigProviders(deps.profileConfigPath, deps.vault, (all) => {
|
|
912
|
-
const existingProv = all[providerId] ?? {
|
|
913
|
-
type: providerId,
|
|
914
|
-
...template
|
|
915
|
-
};
|
|
916
|
-
if (!existingProv.type) existingProv.type = providerId;
|
|
917
|
-
if (!existingProv.family && template.family) {
|
|
918
|
-
existingProv.family = template.family;
|
|
919
|
-
}
|
|
920
|
-
if (!existingProv.baseUrl && template.baseUrl) {
|
|
921
|
-
existingProv.baseUrl = template.baseUrl;
|
|
922
|
-
}
|
|
923
|
-
if (!existingProv.envVars && template.envVars) {
|
|
924
|
-
existingProv.envVars = template.envVars;
|
|
925
|
-
}
|
|
926
|
-
const list = normalizeKeys(existingProv);
|
|
927
|
-
list.push({ label, apiKey, createdAt: nowIso() });
|
|
928
|
-
writeKeysBack(existingProv, list);
|
|
929
|
-
if (!existingProv.activeKey) existingProv.activeKey = label;
|
|
930
|
-
all[providerId] = existingProv;
|
|
931
|
-
}, deps.profileConfigPath);
|
|
932
|
-
deps.renderer.write(
|
|
933
|
-
` ${color5.green("\u2713")} Saved ${color5.bold(providerId)}/${color5.bold(label)}.
|
|
934
|
-
`
|
|
935
|
-
);
|
|
936
|
-
deps.renderer.write(color5.dim(` Launch: wstack --provider ${providerId} "<task>"
|
|
937
|
-
`));
|
|
938
|
-
try {
|
|
939
|
-
await adoptAsDefaultIfUnset(deps, providerId, template);
|
|
940
|
-
} catch {
|
|
941
|
-
}
|
|
942
|
-
return true;
|
|
943
|
-
}
|
|
944
|
-
async function resolveDefaultModelId(deps, providerId, template) {
|
|
945
|
-
if (template.models && template.models.length > 0) return template.models[0];
|
|
946
|
-
const catalogId = catalogProviderIdFor(providerId, template.type);
|
|
947
|
-
const provider = await deps.modelsRegistry.getProvider(catalogId).catch(() => void 0);
|
|
948
|
-
return provider?.models?.[0]?.id;
|
|
949
|
-
}
|
|
950
|
-
async function adoptAsDefaultIfUnset(deps, providerId, template) {
|
|
951
|
-
const targetPath = deps.profileConfigPath;
|
|
952
|
-
let cfg = {};
|
|
953
|
-
try {
|
|
954
|
-
cfg = JSON.parse(await fs.readFile(targetPath, "utf8"));
|
|
955
|
-
} catch {
|
|
956
|
-
}
|
|
957
|
-
if (cfg["provider"] && cfg["model"]) return;
|
|
958
|
-
const modelId = await resolveDefaultModelId(deps, providerId, template);
|
|
959
|
-
if (!modelId) return;
|
|
960
|
-
cfg["provider"] = providerId;
|
|
961
|
-
cfg["model"] = modelId;
|
|
962
|
-
await atomicWrite(targetPath, JSON.stringify(cfg, null, 2), { mode: 384 });
|
|
963
|
-
deps.renderer.write(
|
|
964
|
-
` ${color5.green("\u2713")} Set ${color5.bold(providerId)}/${color5.bold(modelId)} as the default.
|
|
965
|
-
`
|
|
966
|
-
);
|
|
967
|
-
}
|
|
968
|
-
async function promptForLabel(deps, usedLabels) {
|
|
969
|
-
const defaultLabel = suggestLabel(usedLabels);
|
|
970
|
-
const labelRaw = (await deps.reader.readLine(
|
|
971
|
-
` ${color5.amber("?")} Label for this key ${color5.dim(`[${defaultLabel}]`)}: `
|
|
972
|
-
)).trim();
|
|
973
|
-
const label = labelRaw || defaultLabel;
|
|
974
|
-
if (usedLabels.has(label)) {
|
|
975
|
-
deps.renderer.writeError(`Label "${label}" is already used. Use update (u) instead.`);
|
|
976
|
-
return null;
|
|
977
|
-
}
|
|
978
|
-
return label;
|
|
979
|
-
}
|
|
980
|
-
|
|
981
710
|
// src/auth-menu/local.ts
|
|
982
|
-
import { color as color6 } from "@wrongstack/core/utils";
|
|
983
|
-
import { DefaultSecretScrubber as DefaultSecretScrubber2 } from "@wrongstack/core/security";
|
|
984
711
|
import { probeLocalLlm } from "@wrongstack/runtime/probe";
|
|
985
712
|
|
|
986
713
|
// src/auth-menu/auth-menu-audit.ts
|
|
@@ -1097,7 +824,7 @@ async function runAuthLocal(deps, opts = {}) {
|
|
|
1097
824
|
if (!probe.ok) {
|
|
1098
825
|
if (chosen.noAuth) {
|
|
1099
826
|
deps.renderer.write(
|
|
1100
|
-
|
|
827
|
+
color5.dim(
|
|
1101
828
|
` Saving ${chosen.label} anyway \u2014 keyless local gateway needs no key and discovers models when it comes up.
|
|
1102
829
|
`
|
|
1103
830
|
)
|
|
@@ -1106,7 +833,7 @@ async function runAuthLocal(deps, opts = {}) {
|
|
|
1106
833
|
} else {
|
|
1107
834
|
const saveAnyway = await promptSaveAnyway(deps);
|
|
1108
835
|
if (!saveAnyway) {
|
|
1109
|
-
deps.renderer.write(
|
|
836
|
+
deps.renderer.write(color5.dim(" Cancelled. Nothing saved.\n"));
|
|
1110
837
|
return 0;
|
|
1111
838
|
}
|
|
1112
839
|
probeFailedAndSaved = true;
|
|
@@ -1164,19 +891,19 @@ async function runAuthLocal(deps, opts = {}) {
|
|
|
1164
891
|
}
|
|
1165
892
|
if (apiKey) {
|
|
1166
893
|
deps.renderer.write(
|
|
1167
|
-
` ${
|
|
894
|
+
` ${color5.green("\u2713")} Saved ${color5.bold(chosen.id)}/${color5.bold(finalLabel)} \u2192 ${color5.cyan(baseUrl)}
|
|
1168
895
|
`
|
|
1169
896
|
);
|
|
1170
897
|
} else {
|
|
1171
898
|
deps.renderer.write(
|
|
1172
|
-
` ${
|
|
899
|
+
` ${color5.green("\u2713")} Saved ${color5.bold(chosen.id)} (no key) \u2192 ${color5.cyan(baseUrl)}
|
|
1173
900
|
`
|
|
1174
901
|
);
|
|
1175
902
|
}
|
|
1176
903
|
const firstModel = resolvedModels && resolvedModels.length > 0 ? resolvedModels[0] : null;
|
|
1177
|
-
const modelHint = firstModel ?
|
|
904
|
+
const modelHint = firstModel ? color5.cyan(firstModel) : color5.dim("<model-id>");
|
|
1178
905
|
deps.renderer.write(
|
|
1179
|
-
|
|
906
|
+
color5.dim(
|
|
1180
907
|
` Launch: wstack --provider ${chosen.id} --model ${modelHint} "<task>"
|
|
1181
908
|
`
|
|
1182
909
|
)
|
|
@@ -1210,51 +937,51 @@ function resolveModelList(models, probe, scrubber) {
|
|
|
1210
937
|
return out;
|
|
1211
938
|
}
|
|
1212
939
|
function renderProbeResult(deps, probe, opts = {}) {
|
|
1213
|
-
const label = opts.presetLabel ? ` ${
|
|
1214
|
-
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)`)}` : "";
|
|
1215
942
|
switch (probe.status) {
|
|
1216
943
|
case "ok": {
|
|
1217
|
-
const models = probe.modelCount !== void 0 ?
|
|
944
|
+
const models = probe.modelCount !== void 0 ? color5.dim(`, ${probe.modelCount} model${probe.modelCount === 1 ? "" : "s"}`) : "";
|
|
1218
945
|
deps.renderer.write(
|
|
1219
|
-
` ${
|
|
946
|
+
` ${color5.green("\u25CF")} ${label} health probe ok${models}${elapsed}
|
|
1220
947
|
`
|
|
1221
948
|
);
|
|
1222
949
|
break;
|
|
1223
950
|
}
|
|
1224
951
|
case "timeout": {
|
|
1225
952
|
deps.renderer.write(
|
|
1226
|
-
` ${
|
|
1227
|
-
${
|
|
1228
|
-
${
|
|
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.")}
|
|
1229
956
|
`
|
|
1230
957
|
);
|
|
1231
958
|
break;
|
|
1232
959
|
}
|
|
1233
960
|
case "unreachable": {
|
|
1234
|
-
const detail = probe.detail ? ` ${
|
|
961
|
+
const detail = probe.detail ? ` ${color5.dim(`(${probe.detail})`)}` : "";
|
|
1235
962
|
deps.renderer.write(
|
|
1236
|
-
` ${
|
|
1237
|
-
${
|
|
1238
|
-
${
|
|
1239
|
-
${
|
|
1240
|
-
${
|
|
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")}
|
|
1241
968
|
`
|
|
1242
969
|
);
|
|
1243
970
|
break;
|
|
1244
971
|
}
|
|
1245
972
|
case "http_error": {
|
|
1246
973
|
const status = probe.httpStatus !== void 0 ? ` HTTP ${probe.httpStatus}` : "";
|
|
1247
|
-
const detail = probe.detail ? ` ${
|
|
974
|
+
const detail = probe.detail ? ` ${color5.dim(`\u2014 ${probe.detail}`)}` : "";
|
|
1248
975
|
deps.renderer.write(
|
|
1249
|
-
` ${
|
|
976
|
+
` ${color5.yellow("\u25CF")} ${label} health probe got${status}${detail}
|
|
1250
977
|
`
|
|
1251
978
|
);
|
|
1252
979
|
break;
|
|
1253
980
|
}
|
|
1254
981
|
case "invalid_response": {
|
|
1255
982
|
deps.renderer.write(
|
|
1256
|
-
` ${
|
|
1257
|
-
` + (probe.detail ? ` ${
|
|
983
|
+
` ${color5.yellow("\u25CF")} ${label} health probe returned an unexpected shape
|
|
984
|
+
` + (probe.detail ? ` ${color5.dim(probe.detail)}
|
|
1258
985
|
` : "")
|
|
1259
986
|
);
|
|
1260
987
|
break;
|
|
@@ -1267,33 +994,33 @@ function renderProbeResult(deps, probe, opts = {}) {
|
|
|
1267
994
|
async function promptSaveAnyway(deps) {
|
|
1268
995
|
const answer = (await deps.reader.readLine(
|
|
1269
996
|
`
|
|
1270
|
-
${
|
|
997
|
+
${color5.amber("?")} ${color5.bold("Save anyway?")} ${color5.dim("[y/N]")}: `
|
|
1271
998
|
)).trim().toLowerCase();
|
|
1272
999
|
return answer === "y" || answer === "yes";
|
|
1273
1000
|
}
|
|
1274
1001
|
async function pickLocalPreset(deps) {
|
|
1275
1002
|
deps.renderer.write(
|
|
1276
1003
|
`
|
|
1277
|
-
${
|
|
1004
|
+
${color5.bold("Local LLM servers")} ${color5.dim("\u2014 pick one to pre-fill the base URL")}
|
|
1278
1005
|
|
|
1279
1006
|
`
|
|
1280
1007
|
);
|
|
1281
1008
|
let idx = 1;
|
|
1282
1009
|
for (const entry of LOCAL_LLM_PRESETS) {
|
|
1283
|
-
const auth = entry.noAuth ?
|
|
1010
|
+
const auth = entry.noAuth ? color5.dim("(no auth)") : color5.dim("(optional auth)");
|
|
1284
1011
|
deps.renderer.write(
|
|
1285
|
-
` ${
|
|
1012
|
+
` ${color5.dim(`${idx}.`.padStart(4))} ${color5.bold(entry.label.padEnd(12))} ${color5.cyan(entry.defaultBaseUrl.padEnd(34))} ${auth}
|
|
1286
1013
|
`
|
|
1287
1014
|
);
|
|
1288
1015
|
deps.renderer.write(
|
|
1289
|
-
` ${
|
|
1016
|
+
` ${color5.dim(entry.hint)}
|
|
1290
1017
|
`
|
|
1291
1018
|
);
|
|
1292
1019
|
idx++;
|
|
1293
1020
|
}
|
|
1294
1021
|
const answer = (await deps.reader.readLine(
|
|
1295
1022
|
`
|
|
1296
|
-
${
|
|
1023
|
+
${color5.amber("?")} Pick ${color5.dim(`(1-${LOCAL_LLM_PRESETS.length}, q to quit)`)}: `
|
|
1297
1024
|
)).trim().toLowerCase();
|
|
1298
1025
|
if (!answer || answer === "q" || answer === "quit") return void 0;
|
|
1299
1026
|
const num = Number.parseInt(answer, 10);
|
|
@@ -1307,12 +1034,287 @@ ${color6.amber("?")} Pick ${color6.dim(`(1-${LOCAL_LLM_PRESETS.length}, q to qui
|
|
|
1307
1034
|
}
|
|
1308
1035
|
async function promptOptionalKey(deps, preset) {
|
|
1309
1036
|
const answer = (await deps.reader.readSecret(
|
|
1310
|
-
` ${
|
|
1037
|
+
` ${color5.amber("?")} ${color5.bold(preset.label)} API key ${color5.dim("(hidden \u2014 Enter to skip if auth is disabled on the server)")}: `
|
|
1311
1038
|
)).trim();
|
|
1312
1039
|
if (!answer) return void 0;
|
|
1313
1040
|
return answer;
|
|
1314
1041
|
}
|
|
1315
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
|
+
|
|
1316
1318
|
export {
|
|
1317
1319
|
loadProviders,
|
|
1318
1320
|
renderProviderHeader,
|
|
@@ -1333,4 +1335,4 @@ export {
|
|
|
1333
1335
|
addKeyForProvider,
|
|
1334
1336
|
runAuthLocal
|
|
1335
1337
|
};
|
|
1336
|
-
//# sourceMappingURL=chunk-
|
|
1338
|
+
//# sourceMappingURL=chunk-3MOUBRKZ.js.map
|