siclaw 0.1.0 → 0.1.2
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/README.md +75 -114
- package/dist/agentbox/gateway-client.d.ts +2 -1
- package/dist/agentbox/gateway-client.js +6 -2
- package/dist/agentbox/gateway-client.js.map +1 -1
- package/dist/agentbox/http-server.js +184 -19
- package/dist/agentbox/http-server.js.map +1 -1
- package/dist/agentbox/resource-handlers.d.ts +1 -0
- package/dist/agentbox/resource-handlers.js +23 -23
- package/dist/agentbox/resource-handlers.js.map +1 -1
- package/dist/agentbox/session.js +85 -5
- package/dist/agentbox/session.js.map +1 -1
- package/dist/agentbox-main.d.ts +2 -1
- package/dist/agentbox-main.js +65 -18
- package/dist/agentbox-main.js.map +1 -1
- package/dist/cli-credentials.d.ts +1 -0
- package/dist/cli-credentials.js +109 -0
- package/dist/cli-credentials.js.map +1 -0
- package/dist/cli-first-run.d.ts +11 -0
- package/dist/cli-first-run.js +99 -0
- package/dist/cli-first-run.js.map +1 -0
- package/dist/cli-main.js +33 -11
- package/dist/cli-main.js.map +1 -1
- package/dist/cli-setup.d.ts +5 -11
- package/dist/cli-setup.js +12 -225
- package/dist/cli-setup.js.map +1 -1
- package/dist/core/agent-factory.d.ts +4 -0
- package/dist/core/agent-factory.js +102 -151
- package/dist/core/agent-factory.js.map +1 -1
- package/dist/core/config.d.ts +10 -3
- package/dist/core/config.js +11 -95
- package/dist/core/config.js.map +1 -1
- package/dist/core/extensions/deep-investigation.d.ts +2 -1
- package/dist/core/extensions/deep-investigation.js +144 -24
- package/dist/core/extensions/deep-investigation.js.map +1 -1
- package/dist/core/extensions/setup.d.ts +8 -0
- package/dist/core/extensions/setup.js +669 -0
- package/dist/core/extensions/setup.js.map +1 -0
- package/dist/core/llm-proxy.js +7 -3
- package/dist/core/llm-proxy.js.map +1 -1
- package/dist/core/mcp-client.d.ts +0 -10
- package/dist/core/mcp-client.js +0 -65
- package/dist/core/mcp-client.js.map +1 -1
- package/dist/core/prompt.d.ts +1 -1
- package/dist/core/prompt.js +42 -5
- package/dist/core/prompt.js.map +1 -1
- package/dist/core/provider-presets.d.ts +14 -0
- package/dist/core/provider-presets.js +81 -0
- package/dist/core/provider-presets.js.map +1 -0
- package/dist/cron/cron-coordinator.d.ts +2 -0
- package/dist/cron/cron-coordinator.js +46 -14
- package/dist/cron/cron-coordinator.js.map +1 -1
- package/dist/cron/cron-executor.js +33 -8
- package/dist/cron/cron-executor.js.map +1 -1
- package/dist/cron/cron-scheduler.d.ts +1 -1
- package/dist/cron/gateway-client.d.ts +5 -0
- package/dist/cron/gateway-client.js +43 -8
- package/dist/cron/gateway-client.js.map +1 -1
- package/dist/cron-main.js +39 -9
- package/dist/cron-main.js.map +1 -1
- package/dist/gateway/agentbox/client.d.ts +11 -0
- package/dist/gateway/agentbox/client.js +18 -0
- package/dist/gateway/agentbox/client.js.map +1 -1
- package/dist/gateway/agentbox/k8s-spawner.d.ts +11 -2
- package/dist/gateway/agentbox/k8s-spawner.js +95 -52
- package/dist/gateway/agentbox/k8s-spawner.js.map +1 -1
- package/dist/gateway/agentbox/local-spawner.d.ts +1 -1
- package/dist/gateway/agentbox/local-spawner.js +4 -2
- package/dist/gateway/agentbox/local-spawner.js.map +1 -1
- package/dist/gateway/agentbox/manager.d.ts +0 -10
- package/dist/gateway/agentbox/manager.js +11 -30
- package/dist/gateway/agentbox/manager.js.map +1 -1
- package/dist/gateway/agentbox/types.d.ts +6 -4
- package/dist/gateway/cron/cron-service.d.ts +49 -0
- package/dist/gateway/cron/cron-service.js +259 -0
- package/dist/gateway/cron/cron-service.js.map +1 -0
- package/dist/gateway/db/init-schema.js +44 -0
- package/dist/gateway/db/init-schema.js.map +1 -1
- package/dist/gateway/db/migrate-sqlite.js +73 -4
- package/dist/gateway/db/migrate-sqlite.js.map +1 -1
- package/dist/gateway/db/repositories/chat-repo.d.ts +56 -2
- package/dist/gateway/db/repositories/chat-repo.js +132 -2
- package/dist/gateway/db/repositories/chat-repo.js.map +1 -1
- package/dist/gateway/db/repositories/config-repo.d.ts +31 -2
- package/dist/gateway/db/repositories/config-repo.js +57 -7
- package/dist/gateway/db/repositories/config-repo.js.map +1 -1
- package/dist/gateway/db/repositories/env-repo.d.ts +14 -0
- package/dist/gateway/db/repositories/env-repo.js +15 -2
- package/dist/gateway/db/repositories/env-repo.js.map +1 -1
- package/dist/gateway/db/repositories/model-config-repo.d.ts +1 -1
- package/dist/gateway/db/repositories/model-config-repo.js +26 -12
- package/dist/gateway/db/repositories/model-config-repo.js.map +1 -1
- package/dist/gateway/db/repositories/skill-repo.d.ts +0 -5
- package/dist/gateway/db/repositories/skill-review-repo.d.ts +1 -0
- package/dist/gateway/db/repositories/skill-review-repo.js +4 -1
- package/dist/gateway/db/repositories/skill-review-repo.js.map +1 -1
- package/dist/gateway/db/repositories/skill-version-repo.js +0 -1
- package/dist/gateway/db/repositories/skill-version-repo.js.map +1 -1
- package/dist/gateway/db/repositories/system-config-repo.d.ts +1 -1
- package/dist/gateway/db/repositories/system-config-repo.js +2 -1
- package/dist/gateway/db/repositories/system-config-repo.js.map +1 -1
- package/dist/gateway/db/repositories/user-env-config-repo.d.ts +13 -0
- package/dist/gateway/db/repositories/user-env-config-repo.js +11 -0
- package/dist/gateway/db/repositories/user-env-config-repo.js.map +1 -1
- package/dist/gateway/db/repositories/workspace-repo.d.ts +3 -2
- package/dist/gateway/db/repositories/workspace-repo.js +6 -2
- package/dist/gateway/db/repositories/workspace-repo.js.map +1 -1
- package/dist/gateway/db/schema-mysql.d.ts +473 -51
- package/dist/gateway/db/schema-mysql.js +35 -4
- package/dist/gateway/db/schema-mysql.js.map +1 -1
- package/dist/gateway/db/schema-sqlite.d.ts +522 -57
- package/dist/gateway/db/schema-sqlite.js +38 -6
- package/dist/gateway/db/schema-sqlite.js.map +1 -1
- package/dist/gateway/db/schema.d.ts +471 -51
- package/dist/gateway/db/schema.js +1 -1
- package/dist/gateway/db/schema.js.map +1 -1
- package/dist/gateway/metrics-aggregator.d.ts +65 -0
- package/dist/gateway/metrics-aggregator.js +244 -0
- package/dist/gateway/metrics-aggregator.js.map +1 -0
- package/dist/gateway/plugins/channel-bridge.d.ts +4 -1
- package/dist/gateway/plugins/channel-bridge.js +78 -86
- package/dist/gateway/plugins/channel-bridge.js.map +1 -1
- package/dist/gateway/rpc-methods.d.ts +4 -2
- package/dist/gateway/rpc-methods.js +962 -163
- package/dist/gateway/rpc-methods.js.map +1 -1
- package/dist/gateway/security/cert-manager.d.ts +2 -2
- package/dist/gateway/security/cert-manager.js +4 -2
- package/dist/gateway/security/cert-manager.js.map +1 -1
- package/dist/gateway/server.d.ts +4 -8
- package/dist/gateway/server.js +297 -261
- package/dist/gateway/server.js.map +1 -1
- package/dist/gateway/skills/file-writer.js +17 -11
- package/dist/gateway/skills/file-writer.js.map +1 -1
- package/dist/gateway/skills/script-evaluator.js +12 -9
- package/dist/gateway/skills/script-evaluator.js.map +1 -1
- package/dist/gateway/web/dist/assets/index-0p17ZeTP.js +740 -0
- package/dist/gateway/web/dist/assets/index-9eP6nPUq.js +741 -0
- package/dist/gateway/web/dist/assets/index-9eP6nPUq.js.map +1 -0
- package/dist/gateway/web/dist/assets/index-CAmSY91d.js +675 -0
- package/dist/gateway/web/dist/assets/index-DMFEh8Pp.css +1 -0
- package/dist/gateway/web/dist/assets/index-DyowBCEj.css +1 -0
- package/dist/gateway/web/dist/assets/index-PDK5JJDO.css +1 -0
- package/dist/gateway/web/dist/index.html +2 -2
- package/dist/gateway-main.js +27 -10
- package/dist/gateway-main.js.map +1 -1
- package/dist/memory/embeddings.js +5 -4
- package/dist/memory/embeddings.js.map +1 -1
- package/dist/memory/indexer.d.ts +23 -3
- package/dist/memory/indexer.js +235 -23
- package/dist/memory/indexer.js.map +1 -1
- package/dist/memory/schema.js +15 -1
- package/dist/memory/schema.js.map +1 -1
- package/dist/memory/types.d.ts +18 -0
- package/dist/memory/types.js +6 -1
- package/dist/memory/types.js.map +1 -1
- package/dist/shared/detect-language.d.ts +12 -0
- package/dist/shared/detect-language.js +78 -0
- package/dist/shared/detect-language.js.map +1 -0
- package/dist/shared/diagnostic-events.d.ts +70 -0
- package/dist/shared/diagnostic-events.js +38 -0
- package/dist/shared/diagnostic-events.js.map +1 -0
- package/dist/shared/local-collector.d.ts +56 -0
- package/dist/shared/local-collector.js +284 -0
- package/dist/shared/local-collector.js.map +1 -0
- package/dist/shared/metrics-types.d.ts +64 -0
- package/dist/shared/metrics-types.js +25 -0
- package/dist/shared/metrics-types.js.map +1 -0
- package/dist/shared/metrics.d.ts +19 -0
- package/dist/shared/metrics.js +185 -0
- package/dist/shared/metrics.js.map +1 -0
- package/dist/shared/path-utils.d.ts +15 -0
- package/dist/shared/path-utils.js +23 -0
- package/dist/shared/path-utils.js.map +1 -0
- package/dist/shared/retry.d.ts +35 -0
- package/dist/shared/retry.js +61 -0
- package/dist/shared/retry.js.map +1 -0
- package/dist/tools/command-sets.d.ts +18 -2
- package/dist/tools/command-sets.js +207 -32
- package/dist/tools/command-sets.js.map +1 -1
- package/dist/tools/command-validator.d.ts +56 -0
- package/dist/tools/command-validator.js +357 -0
- package/dist/tools/command-validator.js.map +1 -0
- package/dist/tools/create-skill.js +26 -1
- package/dist/tools/create-skill.js.map +1 -1
- package/dist/tools/credential-list.js +1 -23
- package/dist/tools/credential-list.js.map +1 -1
- package/dist/tools/credential-manager.d.ts +98 -0
- package/dist/tools/credential-manager.js +313 -0
- package/dist/tools/credential-manager.js.map +1 -0
- package/dist/tools/deep-search/engine.js +184 -127
- package/dist/tools/deep-search/engine.js.map +1 -1
- package/dist/tools/deep-search/prompts.d.ts +10 -2
- package/dist/tools/deep-search/prompts.js +37 -36
- package/dist/tools/deep-search/prompts.js.map +1 -1
- package/dist/tools/deep-search/schemas.d.ts +87 -0
- package/dist/tools/deep-search/schemas.js +85 -0
- package/dist/tools/deep-search/schemas.js.map +1 -0
- package/dist/tools/deep-search/sub-agent.d.ts +21 -0
- package/dist/tools/deep-search/sub-agent.js +153 -4
- package/dist/tools/deep-search/sub-agent.js.map +1 -1
- package/dist/tools/deep-search/tool.js +1 -0
- package/dist/tools/deep-search/tool.js.map +1 -1
- package/dist/tools/deep-search/types.d.ts +2 -0
- package/dist/tools/deep-search/types.js.map +1 -1
- package/dist/tools/dp-tools.js +29 -5
- package/dist/tools/dp-tools.js.map +1 -1
- package/dist/tools/exec-utils.d.ts +85 -0
- package/dist/tools/exec-utils.js +294 -0
- package/dist/tools/exec-utils.js.map +1 -0
- package/dist/tools/fork-skill.js +14 -2
- package/dist/tools/fork-skill.js.map +1 -1
- package/dist/tools/investigation-feedback.d.ts +3 -0
- package/dist/tools/investigation-feedback.js +71 -0
- package/dist/tools/investigation-feedback.js.map +1 -0
- package/dist/tools/manage-schedule.js +16 -6
- package/dist/tools/manage-schedule.js.map +1 -1
- package/dist/tools/netns-script.js +27 -281
- package/dist/tools/netns-script.js.map +1 -1
- package/dist/tools/node-exec.d.ts +2 -14
- package/dist/tools/node-exec.js +18 -225
- package/dist/tools/node-exec.js.map +1 -1
- package/dist/tools/node-script.js +14 -168
- package/dist/tools/node-script.js.map +1 -1
- package/dist/tools/pod-exec.d.ts +1 -1
- package/dist/tools/pod-exec.js +10 -26
- package/dist/tools/pod-exec.js.map +1 -1
- package/dist/tools/pod-nsenter-exec.js +21 -225
- package/dist/tools/pod-nsenter-exec.js.map +1 -1
- package/dist/tools/pod-script.js +10 -19
- package/dist/tools/pod-script.js.map +1 -1
- package/dist/tools/restricted-bash.d.ts +1 -17
- package/dist/tools/restricted-bash.js +38 -252
- package/dist/tools/restricted-bash.js.map +1 -1
- package/dist/tools/run-skill.d.ts +3 -1
- package/dist/tools/run-skill.js +21 -1
- package/dist/tools/run-skill.js.map +1 -1
- package/dist/tools/script-resolver.d.ts +3 -1
- package/dist/tools/script-resolver.js +74 -30
- package/dist/tools/script-resolver.js.map +1 -1
- package/dist/tools/update-skill.js +17 -6
- package/dist/tools/update-skill.js.map +1 -1
- package/package.json +8 -6
- package/siclaw.mjs +10 -1
- package/skills/core/cluster-events/SKILL.md +1 -1
- package/skills/core/deep-investigation/SKILL.md +11 -0
- package/skills/core/deployment-rollout-debug/SKILL.md +1 -1
- package/skills/core/dns-debug/SKILL.md +1 -0
- package/skills/core/meta.json +12 -1
- package/skills/core/networkpolicy-debug/SKILL.md +332 -0
- package/skills/core/node-logs/scripts/get-node-logs.sh +19 -9
- package/skills/core/pod-pending-debug/SKILL.md +1 -0
- package/skills/core/quota-debug/SKILL.md +203 -0
- package/skills/core/service-debug/SKILL.md +1 -0
- package/skills/core/statefulset-debug/SKILL.md +280 -0
- package/skills/core/volcano-diagnose-pod/SKILL.md +196 -0
- package/skills/core/volcano-diagnose-pod/scripts/diagnose-pod.sh +175 -0
- package/skills/core/volcano-gang-scheduling/SKILL.md +299 -0
- package/skills/core/volcano-job-diagnose/SKILL.md +319 -0
- package/skills/core/volcano-job-diagnose/scripts/diagnose-job.sh +253 -0
- package/skills/core/volcano-node-resources/SKILL.md +334 -0
- package/skills/core/volcano-node-resources/scripts/get-node-resources.sh +281 -0
- package/skills/core/volcano-queue-diagnose/SKILL.md +294 -0
- package/skills/core/volcano-queue-diagnose/scripts/diagnose-queue.sh +283 -0
- package/skills/core/volcano-resource-insufficient/SKILL.md +315 -0
- package/skills/core/volcano-scheduler-config/SKILL.md +371 -0
- package/skills/core/volcano-scheduler-config/scripts/get-scheduler-config.sh +297 -0
- package/skills/core/volcano-scheduler-logs/SKILL.md +241 -0
- package/skills/core/volcano-scheduler-logs/scripts/get-scheduler-logs.sh +159 -0
- package/skills/platform/create-skill/SKILL.md +35 -3
- package/skills/platform/manage-skill/SKILL.md +9 -2
- package/skills/platform/update-skill/SKILL.md +17 -6
|
@@ -0,0 +1,669 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /setup extension — in-session environment configuration for TUI mode.
|
|
3
|
+
*
|
|
4
|
+
* Provides credential management (kubeconfig, SSH, API tokens) and
|
|
5
|
+
* model provider configuration via interactive dialogs.
|
|
6
|
+
*/
|
|
7
|
+
import fs from "node:fs";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import { listCredentials, registerKubeconfig, registerSshPassword, registerSshKey, registerApiToken, registerApiBasicAuth, removeCredential, probeKubeconfig, } from "../../tools/credential-manager.js";
|
|
10
|
+
import { getConfigPath, loadConfig, reloadConfig, } from "../config.js";
|
|
11
|
+
import { PRESETS } from "../provider-presets.js";
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Credential type labels
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
const CREDENTIAL_TYPE_LABELS = {
|
|
16
|
+
kubeconfig: "Kubeconfig",
|
|
17
|
+
ssh_password: "SSH Password",
|
|
18
|
+
ssh_key: "SSH Key",
|
|
19
|
+
api_token: "API Token",
|
|
20
|
+
api_basic_auth: "API Basic Auth",
|
|
21
|
+
};
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Extension factory
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
export default function setupExtension(api, credentialsDir) {
|
|
26
|
+
// --- Status bar: show config summary on session start ---
|
|
27
|
+
api.on("session_start", async (_event, ctx) => {
|
|
28
|
+
updateSetupStatus(ctx, credentialsDir);
|
|
29
|
+
});
|
|
30
|
+
api.registerCommand("setup", {
|
|
31
|
+
description: "Configure credentials and model provider",
|
|
32
|
+
handler: async (_args, ctx) => {
|
|
33
|
+
if (!ctx.hasUI) {
|
|
34
|
+
ctx.ui.notify("Use web UI to manage credentials", "warning");
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
// Main menu loop
|
|
38
|
+
let running = true;
|
|
39
|
+
while (running) {
|
|
40
|
+
const action = await ctx.ui.select("Setup", [
|
|
41
|
+
"Credentials",
|
|
42
|
+
"Models",
|
|
43
|
+
"Exit",
|
|
44
|
+
]);
|
|
45
|
+
if (!action || action === "Exit") {
|
|
46
|
+
running = false;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
switch (action) {
|
|
50
|
+
case "Credentials":
|
|
51
|
+
await credentialsSubmenu(ctx, credentialsDir);
|
|
52
|
+
break;
|
|
53
|
+
case "Models":
|
|
54
|
+
await modelsSubmenu(ctx);
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// Refresh status bar after any config changes
|
|
59
|
+
updateSetupStatus(ctx, credentialsDir);
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
// Sub-menus
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
async function credentialsSubmenu(ctx, credentialsDir) {
|
|
67
|
+
let running = true;
|
|
68
|
+
while (running) {
|
|
69
|
+
const action = await ctx.ui.select("Credentials", [
|
|
70
|
+
"Add",
|
|
71
|
+
"List",
|
|
72
|
+
"Remove",
|
|
73
|
+
"Back",
|
|
74
|
+
]);
|
|
75
|
+
if (!action || action === "Back") {
|
|
76
|
+
running = false;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
switch (action) {
|
|
80
|
+
case "Add":
|
|
81
|
+
await handleAddCredential(ctx, credentialsDir);
|
|
82
|
+
break;
|
|
83
|
+
case "List":
|
|
84
|
+
await handleListCredentials(ctx, credentialsDir);
|
|
85
|
+
break;
|
|
86
|
+
case "Remove":
|
|
87
|
+
await handleRemoveCredential(ctx, credentialsDir);
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
async function modelsSubmenu(ctx) {
|
|
93
|
+
let running = true;
|
|
94
|
+
while (running) {
|
|
95
|
+
const action = await ctx.ui.select("Models", [
|
|
96
|
+
"List",
|
|
97
|
+
"Set default",
|
|
98
|
+
"Add model",
|
|
99
|
+
"Add provider",
|
|
100
|
+
"Remove model",
|
|
101
|
+
"Remove provider",
|
|
102
|
+
"Back",
|
|
103
|
+
]);
|
|
104
|
+
if (!action || action === "Back") {
|
|
105
|
+
running = false;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
switch (action) {
|
|
109
|
+
case "List":
|
|
110
|
+
handleListProviders(ctx);
|
|
111
|
+
break;
|
|
112
|
+
case "Set default":
|
|
113
|
+
await handleSetDefault(ctx);
|
|
114
|
+
break;
|
|
115
|
+
case "Add model":
|
|
116
|
+
await handleAddModel(ctx);
|
|
117
|
+
break;
|
|
118
|
+
case "Add provider":
|
|
119
|
+
await handleModelProvider(ctx);
|
|
120
|
+
break;
|
|
121
|
+
case "Remove model":
|
|
122
|
+
await handleRemoveModel(ctx);
|
|
123
|
+
break;
|
|
124
|
+
case "Remove provider":
|
|
125
|
+
await handleRemoveProvider(ctx);
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
// Add credential flow
|
|
132
|
+
// ---------------------------------------------------------------------------
|
|
133
|
+
async function handleAddCredential(ctx, credentialsDir) {
|
|
134
|
+
const typeLabel = await ctx.ui.select("Credential Type", Object.values(CREDENTIAL_TYPE_LABELS));
|
|
135
|
+
if (!typeLabel)
|
|
136
|
+
return;
|
|
137
|
+
const type = (Object.entries(CREDENTIAL_TYPE_LABELS).find(([, label]) => label === typeLabel)?.[0] ?? "kubeconfig");
|
|
138
|
+
switch (type) {
|
|
139
|
+
case "kubeconfig":
|
|
140
|
+
await addKubeconfig(ctx, credentialsDir);
|
|
141
|
+
break;
|
|
142
|
+
case "ssh_password":
|
|
143
|
+
await addSshPassword(ctx, credentialsDir);
|
|
144
|
+
break;
|
|
145
|
+
case "ssh_key":
|
|
146
|
+
await addSshKey(ctx, credentialsDir);
|
|
147
|
+
break;
|
|
148
|
+
case "api_token":
|
|
149
|
+
await addApiToken(ctx, credentialsDir);
|
|
150
|
+
break;
|
|
151
|
+
case "api_basic_auth":
|
|
152
|
+
await addApiBasicAuth(ctx, credentialsDir);
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
async function addKubeconfig(ctx, credentialsDir) {
|
|
157
|
+
const inputMethod = await ctx.ui.select("Kubeconfig source", [
|
|
158
|
+
"File path",
|
|
159
|
+
"Paste content",
|
|
160
|
+
]);
|
|
161
|
+
if (!inputMethod)
|
|
162
|
+
return;
|
|
163
|
+
let kubeconfigContent;
|
|
164
|
+
let resolvedPath;
|
|
165
|
+
let defaultName = "cluster";
|
|
166
|
+
if (inputMethod === "Paste content") {
|
|
167
|
+
const raw = await ctx.ui.editor("Paste kubeconfig YAML/JSON");
|
|
168
|
+
if (!raw)
|
|
169
|
+
return;
|
|
170
|
+
kubeconfigContent = raw;
|
|
171
|
+
try {
|
|
172
|
+
const yamlMod = await import("js-yaml");
|
|
173
|
+
const kc = yamlMod.load(raw);
|
|
174
|
+
if (kc?.["current-context"]) {
|
|
175
|
+
defaultName = String(kc["current-context"]);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
catch { /* use default */ }
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
const sourcePath = await ctx.ui.input("Kubeconfig path", "~/.kube/config");
|
|
182
|
+
if (!sourcePath)
|
|
183
|
+
return;
|
|
184
|
+
resolvedPath = sourcePath.startsWith("~")
|
|
185
|
+
? path.join(process.env.HOME ?? "", sourcePath.slice(1))
|
|
186
|
+
: path.resolve(sourcePath);
|
|
187
|
+
defaultName = path.basename(resolvedPath, path.extname(resolvedPath));
|
|
188
|
+
try {
|
|
189
|
+
const yamlMod = await import("js-yaml");
|
|
190
|
+
const content = fs.readFileSync(resolvedPath, "utf-8");
|
|
191
|
+
const kc = yamlMod.load(content);
|
|
192
|
+
if (kc?.["current-context"]) {
|
|
193
|
+
defaultName = String(kc["current-context"]);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
catch { /* use filename */ }
|
|
197
|
+
}
|
|
198
|
+
const name = await ctx.ui.input("Credential name", defaultName);
|
|
199
|
+
if (!name)
|
|
200
|
+
return;
|
|
201
|
+
const result = registerKubeconfig(credentialsDir, {
|
|
202
|
+
name,
|
|
203
|
+
...(kubeconfigContent ? { content: kubeconfigContent } : { sourcePath: resolvedPath }),
|
|
204
|
+
});
|
|
205
|
+
if (result.error || !result.entry) {
|
|
206
|
+
ctx.ui.notify(result.error ?? "Registration failed", "error");
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
// Probe connectivity
|
|
210
|
+
const kubeconfigFile = path.join(credentialsDir, result.entry.files[0]);
|
|
211
|
+
ctx.ui.notify(`Probing ${name}...`);
|
|
212
|
+
const probe = await probeKubeconfig(kubeconfigFile);
|
|
213
|
+
if (probe.reachable) {
|
|
214
|
+
ctx.ui.notify(`Kubeconfig "${name}" added (server ${probe.version})`);
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
ctx.ui.notify(`Kubeconfig "${name}" added but unreachable: ${probe.error}`, "warning");
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
async function addSshPassword(ctx, credentialsDir) {
|
|
221
|
+
const name = await ctx.ui.input("Credential name", "my-server");
|
|
222
|
+
if (!name)
|
|
223
|
+
return;
|
|
224
|
+
const host = await ctx.ui.input("Host", "192.168.1.100");
|
|
225
|
+
if (!host)
|
|
226
|
+
return;
|
|
227
|
+
const portStr = await ctx.ui.input("Port", "22");
|
|
228
|
+
const port = portStr ? parseInt(portStr, 10) : 22;
|
|
229
|
+
const username = await ctx.ui.input("Username", "root");
|
|
230
|
+
if (!username)
|
|
231
|
+
return;
|
|
232
|
+
const password = await ctx.ui.input("Password");
|
|
233
|
+
if (!password)
|
|
234
|
+
return;
|
|
235
|
+
try {
|
|
236
|
+
registerSshPassword(credentialsDir, { name, host, port, username, password });
|
|
237
|
+
}
|
|
238
|
+
catch (err) {
|
|
239
|
+
ctx.ui.notify(err instanceof Error ? err.message : String(err), "error");
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
ctx.ui.notify(`SSH password credential "${name}" added`);
|
|
243
|
+
}
|
|
244
|
+
async function addSshKey(ctx, credentialsDir) {
|
|
245
|
+
const name = await ctx.ui.input("Credential name", "my-server");
|
|
246
|
+
if (!name)
|
|
247
|
+
return;
|
|
248
|
+
const host = await ctx.ui.input("Host", "192.168.1.100");
|
|
249
|
+
if (!host)
|
|
250
|
+
return;
|
|
251
|
+
const portStr = await ctx.ui.input("Port", "22");
|
|
252
|
+
const port = portStr ? parseInt(portStr, 10) : 22;
|
|
253
|
+
const username = await ctx.ui.input("Username", "root");
|
|
254
|
+
if (!username)
|
|
255
|
+
return;
|
|
256
|
+
const keyPath = await ctx.ui.input("Private key path", "~/.ssh/id_rsa");
|
|
257
|
+
if (!keyPath)
|
|
258
|
+
return;
|
|
259
|
+
const resolvedKey = keyPath.startsWith("~")
|
|
260
|
+
? path.join(process.env.HOME ?? "", keyPath.slice(1))
|
|
261
|
+
: path.resolve(keyPath);
|
|
262
|
+
const { error } = registerSshKey(credentialsDir, {
|
|
263
|
+
name, host, port, username, keyPath: resolvedKey,
|
|
264
|
+
});
|
|
265
|
+
if (error) {
|
|
266
|
+
ctx.ui.notify(error, "error");
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
ctx.ui.notify(`SSH key credential "${name}" added`);
|
|
270
|
+
}
|
|
271
|
+
async function addApiToken(ctx, credentialsDir) {
|
|
272
|
+
const name = await ctx.ui.input("Credential name", "my-api");
|
|
273
|
+
if (!name)
|
|
274
|
+
return;
|
|
275
|
+
const url = await ctx.ui.input("API base URL (optional)");
|
|
276
|
+
const token = await ctx.ui.input("Token");
|
|
277
|
+
if (!token)
|
|
278
|
+
return;
|
|
279
|
+
try {
|
|
280
|
+
registerApiToken(credentialsDir, { name, url: url || undefined, token });
|
|
281
|
+
}
|
|
282
|
+
catch (err) {
|
|
283
|
+
ctx.ui.notify(err instanceof Error ? err.message : String(err), "error");
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
ctx.ui.notify(`API token credential "${name}" added`);
|
|
287
|
+
}
|
|
288
|
+
async function addApiBasicAuth(ctx, credentialsDir) {
|
|
289
|
+
const name = await ctx.ui.input("Credential name", "my-api");
|
|
290
|
+
if (!name)
|
|
291
|
+
return;
|
|
292
|
+
const url = await ctx.ui.input("API base URL (optional)");
|
|
293
|
+
const username = await ctx.ui.input("Username");
|
|
294
|
+
if (!username)
|
|
295
|
+
return;
|
|
296
|
+
const password = await ctx.ui.input("Password");
|
|
297
|
+
if (!password)
|
|
298
|
+
return;
|
|
299
|
+
try {
|
|
300
|
+
registerApiBasicAuth(credentialsDir, {
|
|
301
|
+
name, url: url || undefined, username, password,
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
catch (err) {
|
|
305
|
+
ctx.ui.notify(err instanceof Error ? err.message : String(err), "error");
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
ctx.ui.notify(`API basic auth credential "${name}" added`);
|
|
309
|
+
}
|
|
310
|
+
// ---------------------------------------------------------------------------
|
|
311
|
+
// List credentials flow
|
|
312
|
+
// ---------------------------------------------------------------------------
|
|
313
|
+
async function handleListCredentials(ctx, credentialsDir) {
|
|
314
|
+
const entries = await listCredentials(credentialsDir);
|
|
315
|
+
if (entries.length === 0) {
|
|
316
|
+
ctx.ui.notify("No credentials configured. Use /setup to add one.");
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
const lines = ["Credentials:"];
|
|
320
|
+
for (const e of entries) {
|
|
321
|
+
let status = "";
|
|
322
|
+
if (e.type === "kubeconfig") {
|
|
323
|
+
status = e.reachable ? ` [connected, ${e.server_version}]` : ` [unreachable: ${e.probe_error}]`;
|
|
324
|
+
}
|
|
325
|
+
lines.push(` ${e.name} (${e.type})${status}`);
|
|
326
|
+
}
|
|
327
|
+
ctx.ui.notify(lines.join("\n"));
|
|
328
|
+
}
|
|
329
|
+
// ---------------------------------------------------------------------------
|
|
330
|
+
// Remove credential flow
|
|
331
|
+
// ---------------------------------------------------------------------------
|
|
332
|
+
async function handleRemoveCredential(ctx, credentialsDir) {
|
|
333
|
+
const entries = await listCredentials(credentialsDir);
|
|
334
|
+
if (entries.length === 0) {
|
|
335
|
+
ctx.ui.notify("No credentials to remove.");
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
const labels = entries.map((e) => `${e.name} (${e.type})`);
|
|
339
|
+
const selected = await ctx.ui.select("Remove credential", labels);
|
|
340
|
+
if (!selected)
|
|
341
|
+
return;
|
|
342
|
+
const idx = labels.indexOf(selected);
|
|
343
|
+
const name = entries[idx].name;
|
|
344
|
+
const confirmed = await ctx.ui.confirm("Confirm removal", `Remove credential "${name}" and its files?`);
|
|
345
|
+
if (!confirmed)
|
|
346
|
+
return;
|
|
347
|
+
const { removed } = removeCredential(credentialsDir, name);
|
|
348
|
+
if (removed) {
|
|
349
|
+
ctx.ui.notify(`Credential "${name}" removed`);
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
ctx.ui.notify(`Credential "${name}" not found`, "warning");
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
// ---------------------------------------------------------------------------
|
|
356
|
+
// Config read/write helpers
|
|
357
|
+
// ---------------------------------------------------------------------------
|
|
358
|
+
/** Read the raw settings.json (not merged with defaults like loadConfig). */
|
|
359
|
+
function readRawConfig() {
|
|
360
|
+
const configPath = getConfigPath();
|
|
361
|
+
try {
|
|
362
|
+
return JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
363
|
+
}
|
|
364
|
+
catch {
|
|
365
|
+
return {};
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
/** Write a partial config object back to settings.json and reload cache. */
|
|
369
|
+
function writeRawConfig(config) {
|
|
370
|
+
const configPath = getConfigPath();
|
|
371
|
+
const configDir = path.dirname(configPath);
|
|
372
|
+
if (!fs.existsSync(configDir)) {
|
|
373
|
+
fs.mkdirSync(configDir, { recursive: true, mode: 0o700 });
|
|
374
|
+
}
|
|
375
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", { mode: 0o600 });
|
|
376
|
+
reloadConfig();
|
|
377
|
+
}
|
|
378
|
+
function saveProviderToConfig(providerName, provider) {
|
|
379
|
+
const existing = readRawConfig();
|
|
380
|
+
const providers = existing.providers ?? {};
|
|
381
|
+
providers[providerName] = provider;
|
|
382
|
+
writeRawConfig({ ...existing, providers });
|
|
383
|
+
}
|
|
384
|
+
// ---------------------------------------------------------------------------
|
|
385
|
+
// List providers flow
|
|
386
|
+
// ---------------------------------------------------------------------------
|
|
387
|
+
function handleListProviders(ctx) {
|
|
388
|
+
const config = loadConfig();
|
|
389
|
+
const entries = Object.entries(config.providers);
|
|
390
|
+
if (entries.length === 0) {
|
|
391
|
+
ctx.ui.notify("No providers configured. Use /setup → Models → Add provider.");
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
const defaultProvider = config.default?.provider ?? entries[0][0];
|
|
395
|
+
const defaultModelId = config.default?.modelId;
|
|
396
|
+
const lines = ["Providers:"];
|
|
397
|
+
for (const [name, provider] of entries) {
|
|
398
|
+
const isDefault = name === defaultProvider;
|
|
399
|
+
const models = provider.models.map((m, i) => {
|
|
400
|
+
const active = isDefault && (defaultModelId ? defaultModelId === m.id : i === 0);
|
|
401
|
+
return active ? `*${m.name || m.id}` : (m.name || m.id);
|
|
402
|
+
});
|
|
403
|
+
lines.push(` ${isDefault ? ">" : " "} ${name}: ${provider.baseUrl || "(no URL)"}`);
|
|
404
|
+
lines.push(` Models: ${models.join(", ")}`);
|
|
405
|
+
}
|
|
406
|
+
ctx.ui.notify(lines.join("\n"));
|
|
407
|
+
}
|
|
408
|
+
// ---------------------------------------------------------------------------
|
|
409
|
+
// Set default provider/model
|
|
410
|
+
// ---------------------------------------------------------------------------
|
|
411
|
+
async function handleSetDefault(ctx) {
|
|
412
|
+
const config = loadConfig();
|
|
413
|
+
const entries = Object.entries(config.providers);
|
|
414
|
+
if (entries.length === 0) {
|
|
415
|
+
ctx.ui.notify("No providers configured.", "warning");
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
// Build flat list: "providerName / modelName (modelId)"
|
|
419
|
+
const options = [];
|
|
420
|
+
for (const [name, provider] of entries) {
|
|
421
|
+
for (const m of provider.models) {
|
|
422
|
+
options.push({
|
|
423
|
+
label: `${name} / ${m.name || m.id}`,
|
|
424
|
+
provider: name,
|
|
425
|
+
modelId: m.id,
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
const selected = await ctx.ui.select("Set default model", options.map((o) => o.label));
|
|
430
|
+
if (!selected)
|
|
431
|
+
return;
|
|
432
|
+
const choice = options.find((o) => o.label === selected);
|
|
433
|
+
if (!choice)
|
|
434
|
+
return;
|
|
435
|
+
// Write to config
|
|
436
|
+
const existing = readRawConfig();
|
|
437
|
+
existing.default = {
|
|
438
|
+
provider: choice.provider,
|
|
439
|
+
modelId: choice.modelId,
|
|
440
|
+
};
|
|
441
|
+
writeRawConfig(existing);
|
|
442
|
+
ctx.ui.notify(`Default set to: ${choice.provider} / ${choice.modelId}\nRestart session to activate.`);
|
|
443
|
+
}
|
|
444
|
+
// ---------------------------------------------------------------------------
|
|
445
|
+
// Remove provider flow
|
|
446
|
+
// ---------------------------------------------------------------------------
|
|
447
|
+
async function handleRemoveProvider(ctx) {
|
|
448
|
+
const config = loadConfig();
|
|
449
|
+
const entries = Object.entries(config.providers);
|
|
450
|
+
if (entries.length === 0) {
|
|
451
|
+
ctx.ui.notify("No providers to remove.");
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
const labels = entries.map(([name, p]) => {
|
|
455
|
+
const model = p.models[0]?.name || p.models[0]?.id || "?";
|
|
456
|
+
return `${name} (${model})`;
|
|
457
|
+
});
|
|
458
|
+
const selected = await ctx.ui.select("Remove provider", labels);
|
|
459
|
+
if (!selected)
|
|
460
|
+
return;
|
|
461
|
+
const providerName = entries[labels.indexOf(selected)][0];
|
|
462
|
+
const confirmed = await ctx.ui.confirm("Confirm removal", `Remove provider "${providerName}"?`);
|
|
463
|
+
if (!confirmed)
|
|
464
|
+
return;
|
|
465
|
+
const existing = readRawConfig();
|
|
466
|
+
const providers = existing.providers ?? {};
|
|
467
|
+
delete providers[providerName];
|
|
468
|
+
// Clear default if it pointed to the removed provider
|
|
469
|
+
if (existing.default?.provider === providerName) {
|
|
470
|
+
delete existing.default;
|
|
471
|
+
}
|
|
472
|
+
writeRawConfig({ ...existing, providers });
|
|
473
|
+
ctx.ui.notify(`Provider "${providerName}" removed. Restart session to take effect.`);
|
|
474
|
+
}
|
|
475
|
+
// ---------------------------------------------------------------------------
|
|
476
|
+
// Configure provider flow
|
|
477
|
+
// ---------------------------------------------------------------------------
|
|
478
|
+
async function handleModelProvider(ctx) {
|
|
479
|
+
const presetLabel = await ctx.ui.select("Provider", PRESETS.map((p) => p.label));
|
|
480
|
+
if (!presetLabel)
|
|
481
|
+
return;
|
|
482
|
+
const preset = PRESETS.find((p) => p.label === presetLabel);
|
|
483
|
+
if (!preset)
|
|
484
|
+
return;
|
|
485
|
+
// Provider name — auto-derive for known presets, only ask for Compatible
|
|
486
|
+
let providerName;
|
|
487
|
+
if (preset.needsBaseUrl) {
|
|
488
|
+
const entered = await ctx.ui.input("Provider name");
|
|
489
|
+
if (!entered)
|
|
490
|
+
return;
|
|
491
|
+
providerName = entered.trim();
|
|
492
|
+
}
|
|
493
|
+
else {
|
|
494
|
+
providerName = preset.name;
|
|
495
|
+
}
|
|
496
|
+
// API Key
|
|
497
|
+
const apiKey = await ctx.ui.input("API Key");
|
|
498
|
+
if (!apiKey) {
|
|
499
|
+
ctx.ui.notify("API key is required", "warning");
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
// Base URL
|
|
503
|
+
let baseUrl = preset.baseUrl;
|
|
504
|
+
if (preset.needsBaseUrl) {
|
|
505
|
+
const entered = await ctx.ui.input("Base URL");
|
|
506
|
+
if (!entered) {
|
|
507
|
+
ctx.ui.notify("Base URL is required", "warning");
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
baseUrl = entered.trim();
|
|
511
|
+
}
|
|
512
|
+
else {
|
|
513
|
+
const entered = await ctx.ui.input("Base URL (Enter to keep default)", preset.baseUrl);
|
|
514
|
+
if (entered)
|
|
515
|
+
baseUrl = entered.trim();
|
|
516
|
+
}
|
|
517
|
+
// Model ID (for compatible APIs)
|
|
518
|
+
let models = preset.models;
|
|
519
|
+
if (preset.needsBaseUrl) {
|
|
520
|
+
const modelId = await ctx.ui.input("Model ID", "default");
|
|
521
|
+
if (modelId && modelId !== "default") {
|
|
522
|
+
const trimmed = modelId.trim();
|
|
523
|
+
models = [{ ...models[0], id: trimmed, name: trimmed }];
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
// Write config
|
|
527
|
+
const provider = {
|
|
528
|
+
baseUrl,
|
|
529
|
+
apiKey,
|
|
530
|
+
api: preset.api,
|
|
531
|
+
authHeader: true,
|
|
532
|
+
models,
|
|
533
|
+
};
|
|
534
|
+
saveProviderToConfig(providerName, provider);
|
|
535
|
+
const modelName = models[0].name || models[0].id;
|
|
536
|
+
ctx.ui.notify(`Provider "${providerName}" saved | Model: ${modelName}\nRestart session to activate.`);
|
|
537
|
+
}
|
|
538
|
+
// ---------------------------------------------------------------------------
|
|
539
|
+
// Add model to existing provider
|
|
540
|
+
// ---------------------------------------------------------------------------
|
|
541
|
+
async function handleAddModel(ctx) {
|
|
542
|
+
const config = loadConfig();
|
|
543
|
+
const entries = Object.entries(config.providers);
|
|
544
|
+
if (entries.length === 0) {
|
|
545
|
+
ctx.ui.notify("No providers configured. Use Add provider first.", "warning");
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
// Select provider
|
|
549
|
+
const addLabels = entries.map(([name, p]) => `${name} (${p.baseUrl || "no URL"})`);
|
|
550
|
+
const providerLabel = await ctx.ui.select("Add model to", addLabels);
|
|
551
|
+
if (!providerLabel)
|
|
552
|
+
return;
|
|
553
|
+
const providerName = entries[addLabels.indexOf(providerLabel)][0];
|
|
554
|
+
// Model details
|
|
555
|
+
const rawModelId = await ctx.ui.input("Model ID");
|
|
556
|
+
if (!rawModelId)
|
|
557
|
+
return;
|
|
558
|
+
const modelId = rawModelId.trim();
|
|
559
|
+
const rawModelName = await ctx.ui.input("Model name", modelId);
|
|
560
|
+
const modelName = rawModelName ? rawModelName.trim() : modelId;
|
|
561
|
+
const ctxWindowStr = await ctx.ui.input("Context window", "128000");
|
|
562
|
+
const contextWindow = parseInt(ctxWindowStr || "128000", 10);
|
|
563
|
+
const maxTokensStr = await ctx.ui.input("Max output tokens", "8192");
|
|
564
|
+
const maxTokens = parseInt(maxTokensStr || "8192", 10);
|
|
565
|
+
// Read existing provider to inherit api type and compat defaults
|
|
566
|
+
const existingProvider = entries.find(([n]) => n === providerName)?.[1];
|
|
567
|
+
if (!existingProvider)
|
|
568
|
+
return;
|
|
569
|
+
const isAnthropic = existingProvider.api === "anthropic";
|
|
570
|
+
const newModel = {
|
|
571
|
+
id: modelId,
|
|
572
|
+
name: modelName || modelId,
|
|
573
|
+
reasoning: false,
|
|
574
|
+
input: ["text"],
|
|
575
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
576
|
+
contextWindow,
|
|
577
|
+
maxTokens,
|
|
578
|
+
compat: {
|
|
579
|
+
supportsDeveloperRole: !isAnthropic,
|
|
580
|
+
supportsUsageInStreaming: true,
|
|
581
|
+
maxTokensField: "max_tokens",
|
|
582
|
+
},
|
|
583
|
+
};
|
|
584
|
+
// Write to config
|
|
585
|
+
const existing = readRawConfig();
|
|
586
|
+
const providers = existing.providers ?? {};
|
|
587
|
+
if (!providers[providerName]) {
|
|
588
|
+
ctx.ui.notify(`Provider "${providerName}" not found`, "error");
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
providers[providerName].models.push(newModel);
|
|
592
|
+
writeRawConfig({ ...existing, providers });
|
|
593
|
+
const allModels = providers[providerName].models.map((m) => m.name || m.id).join(", ");
|
|
594
|
+
ctx.ui.notify(`Model "${modelName || modelId}" added to "${providerName}".\nModels: ${allModels}\nRestart session to activate.`);
|
|
595
|
+
}
|
|
596
|
+
// ---------------------------------------------------------------------------
|
|
597
|
+
// Remove model from existing provider
|
|
598
|
+
// ---------------------------------------------------------------------------
|
|
599
|
+
async function handleRemoveModel(ctx) {
|
|
600
|
+
const config = loadConfig();
|
|
601
|
+
const entries = Object.entries(config.providers);
|
|
602
|
+
if (entries.length === 0) {
|
|
603
|
+
ctx.ui.notify("No providers configured.", "warning");
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
// Select provider
|
|
607
|
+
const rmProvLabels = entries.map(([name, p]) => `${name} (${p.models.length} models)`);
|
|
608
|
+
const providerLabel = await ctx.ui.select("Remove model from", rmProvLabels);
|
|
609
|
+
if (!providerLabel)
|
|
610
|
+
return;
|
|
611
|
+
const provIdx = rmProvLabels.indexOf(providerLabel);
|
|
612
|
+
const providerName = entries[provIdx][0];
|
|
613
|
+
const provider = entries[provIdx][1];
|
|
614
|
+
if (provider.models.length <= 1) {
|
|
615
|
+
ctx.ui.notify(`Provider "${providerName}" has only one model. Remove the provider instead.`, "warning");
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
// Select model
|
|
619
|
+
const modelLabels = provider.models.map((m) => `${m.name || m.id} (${m.id})`);
|
|
620
|
+
const modelLabel = await ctx.ui.select("Remove model", modelLabels);
|
|
621
|
+
if (!modelLabel)
|
|
622
|
+
return;
|
|
623
|
+
const modelIdx = modelLabels.indexOf(modelLabel);
|
|
624
|
+
const modelId = provider.models[modelIdx].id;
|
|
625
|
+
const confirmed = await ctx.ui.confirm("Confirm removal", `Remove model "${modelId}" from provider "${providerName}"?`);
|
|
626
|
+
if (!confirmed)
|
|
627
|
+
return;
|
|
628
|
+
// Write to config
|
|
629
|
+
const existing = readRawConfig();
|
|
630
|
+
const providers = existing.providers ?? {};
|
|
631
|
+
if (!providers[providerName]) {
|
|
632
|
+
ctx.ui.notify(`Provider "${providerName}" not found`, "error");
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
providers[providerName].models = providers[providerName].models.filter((m) => m.id !== modelId);
|
|
636
|
+
writeRawConfig({ ...existing, providers });
|
|
637
|
+
const remaining = providers[providerName].models.map((m) => m.name || m.id).join(", ");
|
|
638
|
+
ctx.ui.notify(`Model "${modelId}" removed from "${providerName}".\nRemaining: ${remaining}\nRestart session to activate.`);
|
|
639
|
+
}
|
|
640
|
+
// ---------------------------------------------------------------------------
|
|
641
|
+
// Status bar helper
|
|
642
|
+
// ---------------------------------------------------------------------------
|
|
643
|
+
function updateSetupStatus(ctx, credentialsDir) {
|
|
644
|
+
const config = loadConfig();
|
|
645
|
+
// Count credentials
|
|
646
|
+
let credCount = 0;
|
|
647
|
+
try {
|
|
648
|
+
const manifestPath = path.join(credentialsDir, "manifest.json");
|
|
649
|
+
if (fs.existsSync(manifestPath)) {
|
|
650
|
+
credCount = JSON.parse(fs.readFileSync(manifestPath, "utf-8")).length;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
catch { /* ignore */ }
|
|
654
|
+
// Count providers
|
|
655
|
+
const providerCount = Object.keys(config.providers).length;
|
|
656
|
+
// Build status parts
|
|
657
|
+
const missing = [];
|
|
658
|
+
if (providerCount === 0)
|
|
659
|
+
missing.push("model");
|
|
660
|
+
if (credCount === 0)
|
|
661
|
+
missing.push("credentials");
|
|
662
|
+
if (missing.length > 0) {
|
|
663
|
+
ctx.ui.setStatus("setup", `/setup: ${missing.join(" + ")} not configured`);
|
|
664
|
+
}
|
|
665
|
+
else {
|
|
666
|
+
ctx.ui.setStatus("setup", undefined);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
//# sourceMappingURL=setup.js.map
|