umbrella-context 0.1.2 → 0.1.20
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/commands/catalog.d.ts +33 -0
- package/dist/commands/catalog.js +211 -0
- package/dist/commands/connect.js +14 -14
- package/dist/commands/connectors.d.ts +15 -0
- package/dist/commands/connectors.js +620 -0
- package/dist/commands/curate.d.ts +1 -0
- package/dist/commands/curate.js +39 -3
- package/dist/commands/debug.d.ts +2 -0
- package/dist/commands/debug.js +55 -0
- package/dist/commands/hub.d.ts +20 -0
- package/dist/commands/hub.js +429 -0
- package/dist/commands/interactive.d.ts +2 -0
- package/dist/commands/interactive.js +918 -62
- package/dist/commands/locations.d.ts +1 -0
- package/dist/commands/locations.js +15 -12
- package/dist/commands/logout.d.ts +2 -0
- package/dist/commands/logout.js +34 -0
- package/dist/commands/model.d.ts +11 -0
- package/dist/commands/model.js +211 -0
- package/dist/commands/providers.d.ts +17 -0
- package/dist/commands/providers.js +344 -0
- package/dist/commands/pull.js +10 -1
- package/dist/commands/push.js +18 -1
- package/dist/commands/restart.d.ts +2 -0
- package/dist/commands/restart.js +21 -0
- package/dist/commands/search.js +19 -1
- package/dist/commands/session.d.ts +2 -0
- package/dist/commands/session.js +128 -0
- package/dist/commands/space.d.ts +1 -0
- package/dist/commands/space.js +81 -63
- package/dist/commands/status.d.ts +25 -0
- package/dist/commands/status.js +104 -16
- package/dist/config.d.ts +23 -0
- package/dist/config.js +69 -0
- package/dist/index.js +26 -4
- package/dist/repo-state.d.ts +84 -0
- package/dist/repo-state.js +419 -3
- package/package.json +1 -1
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import { configManager } from "../config.js";
|
|
3
|
+
export async function locationsCommandAction() {
|
|
4
|
+
const locations = configManager.locations;
|
|
5
|
+
if (locations.length === 0) {
|
|
6
|
+
console.log(chalk.yellow("\n No repo locations saved yet."));
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
console.log(chalk.bold("\n Saved Context Locations\n"));
|
|
10
|
+
locations.forEach((location, index) => {
|
|
11
|
+
console.log(chalk.cyan(` ${index + 1}. ${location.companyName} / ${location.projectName}`));
|
|
12
|
+
console.log(chalk.gray(` ${location.repoRoot}`));
|
|
13
|
+
console.log(chalk.gray(` Updated: ${new Date(location.updatedAt).toLocaleString()}`));
|
|
14
|
+
});
|
|
15
|
+
}
|
|
3
16
|
export function locationsCommand(cli) {
|
|
4
|
-
cli.command("locations", "List repo folders that have been connected on this machine").action(() => {
|
|
5
|
-
|
|
6
|
-
if (locations.length === 0) {
|
|
7
|
-
console.log(chalk.yellow("\n No repo locations saved yet."));
|
|
8
|
-
return;
|
|
9
|
-
}
|
|
10
|
-
console.log(chalk.bold("\n Saved Context Locations\n"));
|
|
11
|
-
locations.forEach((location, index) => {
|
|
12
|
-
console.log(chalk.cyan(` ${index + 1}. ${location.companyName} / ${location.projectName}`));
|
|
13
|
-
console.log(chalk.gray(` ${location.repoRoot}`));
|
|
14
|
-
console.log(chalk.gray(` Updated: ${new Date(location.updatedAt).toLocaleString()}`));
|
|
15
|
-
});
|
|
17
|
+
cli.command("locations", "List repo folders that have been connected on this machine").action(async () => {
|
|
18
|
+
await locationsCommandAction();
|
|
16
19
|
});
|
|
17
20
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import prompts from "prompts";
|
|
3
|
+
import { configManager } from "../config.js";
|
|
4
|
+
export async function logoutCommandAction(force = false) {
|
|
5
|
+
const current = configManager.config;
|
|
6
|
+
if (!current) {
|
|
7
|
+
console.log(chalk.yellow("No Umbrella session is currently saved on this device."));
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
if (!force) {
|
|
11
|
+
const answer = await prompts({
|
|
12
|
+
type: "confirm",
|
|
13
|
+
name: "value",
|
|
14
|
+
message: `Disconnect this device from ${current.companyName} / ${current.projectName}?`,
|
|
15
|
+
initial: false,
|
|
16
|
+
});
|
|
17
|
+
if (!answer.value) {
|
|
18
|
+
console.log(chalk.yellow("\n Logout cancelled."));
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
configManager.clearUmbrellaSession();
|
|
23
|
+
console.log(chalk.green("\n This device is now signed out of Umbrella Context."));
|
|
24
|
+
console.log(chalk.gray(' Your local providers and hub registries were kept on this device.'));
|
|
25
|
+
console.log(chalk.gray(' Run "umbrella-context setup" to connect again.'));
|
|
26
|
+
}
|
|
27
|
+
export function logoutCommand(cli) {
|
|
28
|
+
cli
|
|
29
|
+
.command("logout", "Disconnect this device from Umbrella Context and clear saved auth")
|
|
30
|
+
.option("--yes", "Skip confirmation")
|
|
31
|
+
.action(async (opts) => {
|
|
32
|
+
await logoutCommandAction(Boolean(opts?.yes));
|
|
33
|
+
});
|
|
34
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare function getModelsForActiveProvider(): string[];
|
|
2
|
+
export type ModelCommandResult = {
|
|
3
|
+
action: "list" | "switch" | "inspect" | "ready";
|
|
4
|
+
changed: boolean;
|
|
5
|
+
activeModel: string | null;
|
|
6
|
+
providerName: string | null;
|
|
7
|
+
availableModels: string[];
|
|
8
|
+
inspectedModel?: string | null;
|
|
9
|
+
};
|
|
10
|
+
export declare function modelCommandAction(action: string, target?: string): Promise<ModelCommandResult | void>;
|
|
11
|
+
export declare function modelCommand(cli: any): void;
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import prompts from "prompts";
|
|
3
|
+
import { configManager } from "../config.js";
|
|
4
|
+
import { getSessionState, recordSessionEvent, recordSessionModel, setSessionPanel } from "../repo-state.js";
|
|
5
|
+
const MODELS_BY_PROVIDER = {
|
|
6
|
+
anthropic: ["claude-3-7-sonnet", "claude-3-5-sonnet", "claude-3-5-haiku"],
|
|
7
|
+
openai: ["gpt-5.4", "gpt-5.4-mini", "gpt-4.1"],
|
|
8
|
+
google: ["gemini-2.5-pro", "gemini-2.5-flash"],
|
|
9
|
+
openrouter: ["openai/gpt-4.1", "anthropic/claude-3.7-sonnet", "google/gemini-2.5-pro"],
|
|
10
|
+
"openai-compatible": ["custom-model"],
|
|
11
|
+
};
|
|
12
|
+
function getActiveProvider() {
|
|
13
|
+
const config = configManager.config;
|
|
14
|
+
const providers = configManager.providers;
|
|
15
|
+
return providers.find((entry) => entry.id === config?.activeProvider) ?? null;
|
|
16
|
+
}
|
|
17
|
+
export function getModelsForActiveProvider() {
|
|
18
|
+
const provider = getActiveProvider();
|
|
19
|
+
if (!provider)
|
|
20
|
+
return [];
|
|
21
|
+
return MODELS_BY_PROVIDER[provider.kind] ?? ["custom-model"];
|
|
22
|
+
}
|
|
23
|
+
export async function modelCommandAction(action, target) {
|
|
24
|
+
const normalized = action.toLowerCase();
|
|
25
|
+
const config = configManager.config;
|
|
26
|
+
if (!config) {
|
|
27
|
+
console.log(chalk.red("Not configured. Run: umbrella-context setup"));
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const provider = getActiveProvider();
|
|
31
|
+
if (!provider) {
|
|
32
|
+
console.log(chalk.yellow(" No active provider. Run: umbrella-context providers connect"));
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const models = getModelsForActiveProvider();
|
|
36
|
+
await setSessionPanel("model", config.activeModel ?? provider.id);
|
|
37
|
+
if (normalized === "list") {
|
|
38
|
+
await setSessionPanel("model", config.activeModel ?? provider.id);
|
|
39
|
+
console.log(chalk.bold(`\n Models for ${provider.name}\n`));
|
|
40
|
+
models.forEach((model, index) => {
|
|
41
|
+
const active = config.activeModel === model ? " (active)" : "";
|
|
42
|
+
console.log(` ${index + 1}. ${model}${active}`);
|
|
43
|
+
});
|
|
44
|
+
return {
|
|
45
|
+
action: "list",
|
|
46
|
+
changed: false,
|
|
47
|
+
activeModel: config.activeModel ?? null,
|
|
48
|
+
providerName: provider.name,
|
|
49
|
+
availableModels: models,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
if (normalized === "ready") {
|
|
53
|
+
const ready = Boolean(config.activeModel);
|
|
54
|
+
await setSessionPanel("model", config.activeModel ?? provider.id);
|
|
55
|
+
await recordSessionEvent({
|
|
56
|
+
kind: "model",
|
|
57
|
+
title: ready ? `Model runtime ready for ${provider.name}` : `Model missing for ${provider.name}`,
|
|
58
|
+
detail: ready
|
|
59
|
+
? `${config.activeModel} is ready for terminal work.`
|
|
60
|
+
: "Choose an active model before running provider-backed queries.",
|
|
61
|
+
panel: "model",
|
|
62
|
+
focus: config.activeModel ?? provider.id,
|
|
63
|
+
status: ready ? "success" : "warning",
|
|
64
|
+
});
|
|
65
|
+
console.log(chalk.bold(`\n Model readiness for ${provider.name}\n`));
|
|
66
|
+
console.log(` Active model: ${config.activeModel ?? "Not selected"}`);
|
|
67
|
+
console.log(` Available models: ${models.length}`);
|
|
68
|
+
console.log(` Runtime ready: ${ready ? "Yes" : "No"}`);
|
|
69
|
+
console.log("");
|
|
70
|
+
console.log(chalk.cyan(" Recommended Next Step"));
|
|
71
|
+
console.log(ready
|
|
72
|
+
? " - Runtime is ready. Ask a plain question or run umbrella-context query."
|
|
73
|
+
: " - Run umbrella-context model switch to choose the active model.");
|
|
74
|
+
return {
|
|
75
|
+
action: "ready",
|
|
76
|
+
changed: false,
|
|
77
|
+
activeModel: config.activeModel ?? null,
|
|
78
|
+
providerName: provider.name,
|
|
79
|
+
availableModels: models,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
if (normalized === "inspect") {
|
|
83
|
+
if (models.length === 0) {
|
|
84
|
+
console.log(chalk.yellow(" No models available for the active provider."));
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
let selectedModel = target;
|
|
88
|
+
if (!selectedModel) {
|
|
89
|
+
const answer = await prompts({
|
|
90
|
+
type: "select",
|
|
91
|
+
name: "value",
|
|
92
|
+
message: `Choose a model to inspect for ${provider.name}`,
|
|
93
|
+
choices: models.map((model) => ({
|
|
94
|
+
title: model,
|
|
95
|
+
value: model,
|
|
96
|
+
})),
|
|
97
|
+
});
|
|
98
|
+
if (!answer.value) {
|
|
99
|
+
console.log(chalk.yellow("\n No model selected."));
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
selectedModel = answer.value;
|
|
103
|
+
}
|
|
104
|
+
const inspectedModel = selectedModel;
|
|
105
|
+
await setSessionPanel("model", inspectedModel);
|
|
106
|
+
await recordSessionModel(inspectedModel);
|
|
107
|
+
await recordSessionEvent({
|
|
108
|
+
kind: "model",
|
|
109
|
+
title: `Inspected model ${inspectedModel}`,
|
|
110
|
+
detail: `${inspectedModel} belongs to ${provider.name} and is ${config.activeModel === inspectedModel ? "already active" : "available to activate"}.`,
|
|
111
|
+
panel: "model",
|
|
112
|
+
focus: inspectedModel,
|
|
113
|
+
status: "info",
|
|
114
|
+
});
|
|
115
|
+
console.log(chalk.bold(`\n ${inspectedModel}\n`));
|
|
116
|
+
console.log(` Provider: ${provider.name}`);
|
|
117
|
+
console.log(` Active: ${config.activeModel === inspectedModel ? "Yes" : "No"}`);
|
|
118
|
+
console.log(` Family: ${provider.kind}`);
|
|
119
|
+
console.log("");
|
|
120
|
+
console.log(chalk.cyan(" Recommended Next Step"));
|
|
121
|
+
if (config.activeModel !== inspectedModel) {
|
|
122
|
+
console.log(` - Run umbrella-context model switch and choose ${inspectedModel} to make it active.`);
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
console.log(" - Model is active. Ask a plain question or run umbrella-context query.");
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
action: "inspect",
|
|
129
|
+
changed: false,
|
|
130
|
+
activeModel: config.activeModel ?? null,
|
|
131
|
+
providerName: provider.name,
|
|
132
|
+
availableModels: models,
|
|
133
|
+
inspectedModel,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
if (normalized === "recent") {
|
|
137
|
+
const session = await getSessionState();
|
|
138
|
+
const recentModel = target ?? session?.recentModels?.[0] ?? config.activeModel ?? null;
|
|
139
|
+
if (!recentModel) {
|
|
140
|
+
console.log(chalk.yellow("\n No recent model in this session yet."));
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
await setSessionPanel("model", recentModel);
|
|
144
|
+
await recordSessionModel(recentModel);
|
|
145
|
+
await recordSessionEvent({
|
|
146
|
+
kind: "model",
|
|
147
|
+
title: `Re-opened recent model ${recentModel}`,
|
|
148
|
+
detail: `Returned to the most recent model touched for ${provider.name}.`,
|
|
149
|
+
panel: "model",
|
|
150
|
+
focus: recentModel,
|
|
151
|
+
status: "info",
|
|
152
|
+
});
|
|
153
|
+
console.log(chalk.bold(`\n ${recentModel}\n`));
|
|
154
|
+
console.log(` Provider: ${provider.name}`);
|
|
155
|
+
console.log(` Active: ${config.activeModel === recentModel ? "Yes" : "No"}`);
|
|
156
|
+
console.log(` Family: ${provider.kind}`);
|
|
157
|
+
console.log("");
|
|
158
|
+
console.log(chalk.cyan(" Recommended Next Step"));
|
|
159
|
+
if (config.activeModel !== recentModel) {
|
|
160
|
+
console.log(` - Run umbrella-context model switch and choose ${recentModel} to make it active.`);
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
console.log(" - Model is active. Ask a plain question or run umbrella-context query.");
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
action: "inspect",
|
|
167
|
+
changed: false,
|
|
168
|
+
activeModel: config.activeModel ?? null,
|
|
169
|
+
providerName: provider.name,
|
|
170
|
+
availableModels: models,
|
|
171
|
+
inspectedModel: recentModel,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
if (normalized === "switch") {
|
|
175
|
+
const answer = await prompts({
|
|
176
|
+
type: "select",
|
|
177
|
+
name: "value",
|
|
178
|
+
message: `Choose the active model for ${provider.name}`,
|
|
179
|
+
choices: models.map((model) => ({ title: model, value: model })),
|
|
180
|
+
});
|
|
181
|
+
if (!answer.value) {
|
|
182
|
+
console.log(chalk.yellow("\n No model selected."));
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
configManager.set({ activeModel: answer.value });
|
|
186
|
+
await setSessionPanel("model", answer.value);
|
|
187
|
+
await recordSessionModel(answer.value);
|
|
188
|
+
await recordSessionEvent({
|
|
189
|
+
kind: "model",
|
|
190
|
+
title: `Switched active model to ${answer.value}`,
|
|
191
|
+
detail: `${provider.name} is now paired with ${answer.value} for terminal work.`,
|
|
192
|
+
panel: "model",
|
|
193
|
+
focus: answer.value,
|
|
194
|
+
status: "success",
|
|
195
|
+
});
|
|
196
|
+
console.log(chalk.green(`\n Active model: ${answer.value}`));
|
|
197
|
+
return {
|
|
198
|
+
action: "switch",
|
|
199
|
+
changed: true,
|
|
200
|
+
activeModel: answer.value,
|
|
201
|
+
providerName: provider.name,
|
|
202
|
+
availableModels: models,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
console.log(chalk.red("Use one of: model list, model inspect, model recent, model ready, model switch"));
|
|
206
|
+
}
|
|
207
|
+
export function modelCommand(cli) {
|
|
208
|
+
cli.command("model <action> [target]", "List or switch the active model for the current provider").action(async (action, target) => {
|
|
209
|
+
await modelCommandAction(action, target);
|
|
210
|
+
});
|
|
211
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type SavedProvider } from "../config.js";
|
|
2
|
+
export type ProvidersCommandResult = {
|
|
3
|
+
action: "list" | "connect" | "switch" | "disconnect" | "inspect" | "test";
|
|
4
|
+
changed: boolean;
|
|
5
|
+
activeProvider: SavedProvider | null;
|
|
6
|
+
needsModelSelection: boolean;
|
|
7
|
+
inspectedProvider?: SavedProvider | null;
|
|
8
|
+
};
|
|
9
|
+
export declare function getActiveProvider(): SavedProvider | null;
|
|
10
|
+
export declare function getProviderReadinessSummary(): {
|
|
11
|
+
activeProvider: SavedProvider | null;
|
|
12
|
+
activeModel: string | null;
|
|
13
|
+
providerReady: boolean;
|
|
14
|
+
modelReady: boolean;
|
|
15
|
+
};
|
|
16
|
+
export declare function providersCommandAction(action: string, target?: string): Promise<ProvidersCommandResult | void>;
|
|
17
|
+
export declare function providersCommand(cli: any): void;
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
import { randomUUID } from "crypto";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import prompts from "prompts";
|
|
4
|
+
import { configManager } from "../config.js";
|
|
5
|
+
import { recordSessionEvent, recordSessionProvider, setSessionPanel } from "../repo-state.js";
|
|
6
|
+
const PROVIDER_CHOICES = [
|
|
7
|
+
{ kind: "anthropic", name: "Anthropic" },
|
|
8
|
+
{ kind: "openai", name: "OpenAI" },
|
|
9
|
+
{ kind: "google", name: "Google" },
|
|
10
|
+
{ kind: "openrouter", name: "OpenRouter" },
|
|
11
|
+
{ kind: "openai-compatible", name: "OpenAI-Compatible" },
|
|
12
|
+
];
|
|
13
|
+
function printProviders(providers, activeProvider) {
|
|
14
|
+
console.log(chalk.bold("\n Connected Providers\n"));
|
|
15
|
+
if (providers.length === 0) {
|
|
16
|
+
console.log(chalk.yellow(" No providers connected yet. Run: umbrella-context providers connect"));
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
providers.forEach((provider, index) => {
|
|
20
|
+
const active = provider.id === activeProvider ? " (active)" : "";
|
|
21
|
+
const base = provider.baseUrl ? ` -> ${provider.baseUrl}` : "";
|
|
22
|
+
console.log(` ${index + 1}. ${provider.name} [${provider.kind}]${active}${base}`);
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
export function getActiveProvider() {
|
|
26
|
+
const config = configManager.config;
|
|
27
|
+
const providers = configManager.providers;
|
|
28
|
+
return providers.find((entry) => entry.id === config?.activeProvider) ?? null;
|
|
29
|
+
}
|
|
30
|
+
export function getProviderReadinessSummary() {
|
|
31
|
+
const activeProvider = getActiveProvider();
|
|
32
|
+
const activeModel = configManager.config?.activeModel ?? null;
|
|
33
|
+
return {
|
|
34
|
+
activeProvider,
|
|
35
|
+
activeModel,
|
|
36
|
+
providerReady: Boolean(activeProvider),
|
|
37
|
+
modelReady: Boolean(activeProvider && activeModel),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
export async function providersCommandAction(action, target) {
|
|
41
|
+
const normalized = action.toLowerCase();
|
|
42
|
+
const providers = configManager.providers;
|
|
43
|
+
const activeProvider = configManager.config?.activeProvider;
|
|
44
|
+
await setSessionPanel("providers", null);
|
|
45
|
+
if (normalized === "list") {
|
|
46
|
+
await setSessionPanel("providers", "list");
|
|
47
|
+
printProviders(providers, activeProvider);
|
|
48
|
+
const current = getActiveProvider();
|
|
49
|
+
return {
|
|
50
|
+
action: "list",
|
|
51
|
+
changed: false,
|
|
52
|
+
activeProvider: current,
|
|
53
|
+
needsModelSelection: Boolean(current && !configManager.config?.activeModel),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
if (normalized === "test") {
|
|
57
|
+
const current = getActiveProvider();
|
|
58
|
+
if (!current) {
|
|
59
|
+
console.log(chalk.yellow("\n No active provider. Run: umbrella-context providers connect"));
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
await setSessionPanel("providers", current.id);
|
|
63
|
+
await recordSessionProvider(current.id);
|
|
64
|
+
const runtimeReady = Boolean(current.apiKey && configManager.config?.activeModel);
|
|
65
|
+
await recordSessionEvent({
|
|
66
|
+
kind: "provider",
|
|
67
|
+
title: runtimeReady ? `Runtime ready for ${current.name}` : `Runtime incomplete for ${current.name}`,
|
|
68
|
+
detail: runtimeReady
|
|
69
|
+
? `${current.name} has both an API key and an active model.`
|
|
70
|
+
: !configManager.config?.activeModel
|
|
71
|
+
? `${current.name} is connected, but you still need to choose a model.`
|
|
72
|
+
: `${current.name} is missing the configuration needed to run.`,
|
|
73
|
+
panel: "providers",
|
|
74
|
+
focus: current.id,
|
|
75
|
+
status: runtimeReady ? "success" : "warning",
|
|
76
|
+
});
|
|
77
|
+
console.log(chalk.bold(`\n Runtime check for ${current.name}\n`));
|
|
78
|
+
console.log(` Kind: ${current.kind}`);
|
|
79
|
+
console.log(` API key saved: ${current.apiKey ? "Yes" : "No"}`);
|
|
80
|
+
console.log(` Active model: ${configManager.config?.activeModel ?? "Not selected"}`);
|
|
81
|
+
console.log(` Runtime ready: ${runtimeReady ? "Yes" : "No"}`);
|
|
82
|
+
console.log("");
|
|
83
|
+
console.log(chalk.cyan(" Recommended Next Step"));
|
|
84
|
+
console.log(runtimeReady
|
|
85
|
+
? " - Runtime is ready. Ask a plain question or run umbrella-context query."
|
|
86
|
+
: ' - Run umbrella-context model switch to finish the runtime setup.');
|
|
87
|
+
return {
|
|
88
|
+
action: "test",
|
|
89
|
+
changed: false,
|
|
90
|
+
activeProvider: current,
|
|
91
|
+
needsModelSelection: !configManager.config?.activeModel,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
if (normalized === "inspect") {
|
|
95
|
+
if (providers.length === 0) {
|
|
96
|
+
console.log(chalk.yellow(" No connected providers yet. Run: umbrella-context providers connect"));
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
let selectedId = target;
|
|
100
|
+
if (!selectedId) {
|
|
101
|
+
const answer = await prompts({
|
|
102
|
+
type: "select",
|
|
103
|
+
name: "value",
|
|
104
|
+
message: "Choose a provider to inspect",
|
|
105
|
+
choices: providers.map((provider) => ({
|
|
106
|
+
title: `${provider.name} (${provider.kind})`,
|
|
107
|
+
value: provider.id,
|
|
108
|
+
})),
|
|
109
|
+
});
|
|
110
|
+
if (!answer.value) {
|
|
111
|
+
console.log(chalk.yellow("\n No provider selected."));
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
selectedId = answer.value;
|
|
115
|
+
}
|
|
116
|
+
const provider = providers.find((entry) => entry.id === selectedId || entry.name.toLowerCase() === selectedId?.toLowerCase()) ?? null;
|
|
117
|
+
if (!provider)
|
|
118
|
+
return;
|
|
119
|
+
await setSessionPanel("providers", provider.id);
|
|
120
|
+
await recordSessionProvider(provider.id);
|
|
121
|
+
await recordSessionEvent({
|
|
122
|
+
kind: "provider",
|
|
123
|
+
title: `Inspected provider ${provider.name}`,
|
|
124
|
+
detail: `${provider.name} is ${provider.id === activeProvider ? "the active provider" : "saved on this device but not active"}.`,
|
|
125
|
+
panel: "providers",
|
|
126
|
+
focus: provider.id,
|
|
127
|
+
status: "info",
|
|
128
|
+
});
|
|
129
|
+
console.log(chalk.bold(`\n ${provider.name}\n`));
|
|
130
|
+
console.log(` Kind: ${provider.kind}`);
|
|
131
|
+
console.log(` Active: ${provider.id === activeProvider ? "Yes" : "No"}`);
|
|
132
|
+
console.log(` Base URL: ${provider.baseUrl ?? "Default"}`);
|
|
133
|
+
console.log(` Updated: ${new Date(provider.updatedAt).toLocaleString()}`);
|
|
134
|
+
console.log("");
|
|
135
|
+
console.log(chalk.cyan(" Recommended Next Step"));
|
|
136
|
+
if (provider.id !== activeProvider) {
|
|
137
|
+
console.log(" - Run umbrella-context providers switch to make this the active runtime.");
|
|
138
|
+
}
|
|
139
|
+
else if (!configManager.config?.activeModel) {
|
|
140
|
+
console.log(" - Run umbrella-context model switch to choose a model for this provider.");
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
console.log(" - Runtime is ready. Ask a question or run umbrella-context query.");
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
action: "inspect",
|
|
147
|
+
changed: false,
|
|
148
|
+
activeProvider: getActiveProvider(),
|
|
149
|
+
needsModelSelection: Boolean(provider.id === activeProvider && !configManager.config?.activeModel),
|
|
150
|
+
inspectedProvider: provider,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
if (normalized === "recent") {
|
|
154
|
+
const session = await import("../repo-state.js").then((mod) => mod.getSessionState());
|
|
155
|
+
const recentId = target ?? session?.recentProviderIds?.[0];
|
|
156
|
+
if (!recentId) {
|
|
157
|
+
console.log(chalk.yellow("\n No recent provider in this session yet."));
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
const provider = providers.find((entry) => entry.id === recentId) ?? null;
|
|
161
|
+
if (!provider) {
|
|
162
|
+
console.log(chalk.yellow("\n The most recent provider is no longer saved on this device."));
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
await setSessionPanel("providers", provider.id);
|
|
166
|
+
await recordSessionProvider(provider.id);
|
|
167
|
+
await recordSessionEvent({
|
|
168
|
+
kind: "provider",
|
|
169
|
+
title: `Re-opened recent provider ${provider.name}`,
|
|
170
|
+
detail: "Returned to the provider you touched most recently in this session.",
|
|
171
|
+
panel: "providers",
|
|
172
|
+
focus: provider.id,
|
|
173
|
+
status: "info",
|
|
174
|
+
});
|
|
175
|
+
console.log(chalk.bold(`\n ${provider.name}\n`));
|
|
176
|
+
console.log(` Kind: ${provider.kind}`);
|
|
177
|
+
console.log(` Active: ${provider.id === activeProvider ? "Yes" : "No"}`);
|
|
178
|
+
console.log(` Base URL: ${provider.baseUrl ?? "Default"}`);
|
|
179
|
+
console.log(` Updated: ${new Date(provider.updatedAt).toLocaleString()}`);
|
|
180
|
+
console.log("");
|
|
181
|
+
console.log(chalk.cyan(" Recommended Next Step"));
|
|
182
|
+
if (provider.id !== activeProvider) {
|
|
183
|
+
console.log(" - Run umbrella-context providers switch to make this the active runtime.");
|
|
184
|
+
}
|
|
185
|
+
else if (!configManager.config?.activeModel) {
|
|
186
|
+
console.log(" - Run umbrella-context model switch to choose a model for this provider.");
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
console.log(" - Runtime is ready. Ask a question or run umbrella-context query.");
|
|
190
|
+
}
|
|
191
|
+
return {
|
|
192
|
+
action: "inspect",
|
|
193
|
+
changed: false,
|
|
194
|
+
activeProvider: getActiveProvider(),
|
|
195
|
+
needsModelSelection: Boolean(provider.id === activeProvider && !configManager.config?.activeModel),
|
|
196
|
+
inspectedProvider: provider,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
if (normalized === "connect") {
|
|
200
|
+
const answer = await prompts([
|
|
201
|
+
{
|
|
202
|
+
type: "select",
|
|
203
|
+
name: "kind",
|
|
204
|
+
message: "Choose a provider",
|
|
205
|
+
choices: PROVIDER_CHOICES.map((entry) => ({ title: entry.name, value: entry.kind })),
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
type: "text",
|
|
209
|
+
name: "label",
|
|
210
|
+
message: "What label should this provider use on this device?",
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
type: "password",
|
|
214
|
+
name: "apiKey",
|
|
215
|
+
message: "Paste the provider API key (stored only on this device)",
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
type: (prev) => (prev === "openai-compatible" ? "text" : null),
|
|
219
|
+
name: "baseUrl",
|
|
220
|
+
message: "Base URL for the compatible API",
|
|
221
|
+
},
|
|
222
|
+
]);
|
|
223
|
+
if (!answer.kind || !answer.label || !answer.apiKey) {
|
|
224
|
+
console.log(chalk.yellow("\n Provider connection cancelled."));
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
const provider = {
|
|
228
|
+
id: randomUUID(),
|
|
229
|
+
name: answer.label,
|
|
230
|
+
kind: answer.kind,
|
|
231
|
+
apiKey: answer.apiKey,
|
|
232
|
+
baseUrl: answer.baseUrl || undefined,
|
|
233
|
+
updatedAt: new Date().toISOString(),
|
|
234
|
+
};
|
|
235
|
+
configManager.upsertProvider(provider);
|
|
236
|
+
configManager.set({
|
|
237
|
+
activeProvider: provider.id,
|
|
238
|
+
activeModel: undefined,
|
|
239
|
+
});
|
|
240
|
+
await setSessionPanel("providers", provider.id);
|
|
241
|
+
await recordSessionProvider(provider.id);
|
|
242
|
+
await recordSessionEvent({
|
|
243
|
+
kind: "provider",
|
|
244
|
+
title: `Connected provider ${provider.name}`,
|
|
245
|
+
detail: `${provider.name} is now the active provider for this device and needs a model next.`,
|
|
246
|
+
panel: "providers",
|
|
247
|
+
focus: provider.id,
|
|
248
|
+
status: "success",
|
|
249
|
+
});
|
|
250
|
+
console.log(chalk.green(`\n Connected ${provider.name}.`));
|
|
251
|
+
console.log(chalk.gray(" Use 'umbrella-context model switch' to choose a model next."));
|
|
252
|
+
return {
|
|
253
|
+
action: "connect",
|
|
254
|
+
changed: true,
|
|
255
|
+
activeProvider: provider,
|
|
256
|
+
needsModelSelection: true,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
if (normalized === "switch") {
|
|
260
|
+
if (providers.length === 0) {
|
|
261
|
+
console.log(chalk.yellow(" No connected providers yet. Run: umbrella-context providers connect"));
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
const answer = await prompts({
|
|
265
|
+
type: "select",
|
|
266
|
+
name: "value",
|
|
267
|
+
message: "Choose the active provider",
|
|
268
|
+
choices: providers.map((provider) => ({
|
|
269
|
+
title: `${provider.name} (${provider.kind})`,
|
|
270
|
+
value: provider.id,
|
|
271
|
+
})),
|
|
272
|
+
});
|
|
273
|
+
if (!answer.value) {
|
|
274
|
+
console.log(chalk.yellow("\n No provider selected."));
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
configManager.set({ activeProvider: answer.value, activeModel: undefined });
|
|
278
|
+
const provider = providers.find((entry) => entry.id === answer.value);
|
|
279
|
+
if (provider) {
|
|
280
|
+
await setSessionPanel("providers", provider.id);
|
|
281
|
+
await recordSessionProvider(provider.id);
|
|
282
|
+
await recordSessionEvent({
|
|
283
|
+
kind: "provider",
|
|
284
|
+
title: `Switched active provider to ${provider.name}`,
|
|
285
|
+
detail: "The active model was cleared so you can choose one for this provider.",
|
|
286
|
+
panel: "providers",
|
|
287
|
+
focus: provider.id,
|
|
288
|
+
status: "success",
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
console.log(chalk.green(`\n Active provider: ${provider?.name ?? "Updated"}`));
|
|
292
|
+
return {
|
|
293
|
+
action: "switch",
|
|
294
|
+
changed: true,
|
|
295
|
+
activeProvider: provider ?? null,
|
|
296
|
+
needsModelSelection: true,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
if (normalized === "disconnect") {
|
|
300
|
+
if (providers.length === 0) {
|
|
301
|
+
console.log(chalk.yellow(" No connected providers yet."));
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
const answer = await prompts({
|
|
305
|
+
type: "select",
|
|
306
|
+
name: "value",
|
|
307
|
+
message: "Choose a provider to disconnect",
|
|
308
|
+
choices: providers.map((provider) => ({
|
|
309
|
+
title: `${provider.name} (${provider.kind})`,
|
|
310
|
+
value: provider.id,
|
|
311
|
+
})),
|
|
312
|
+
});
|
|
313
|
+
if (!answer.value) {
|
|
314
|
+
console.log(chalk.yellow("\n No provider selected."));
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
const provider = providers.find((entry) => entry.id === answer.value);
|
|
318
|
+
configManager.removeProvider(answer.value);
|
|
319
|
+
await setSessionPanel("providers", provider?.id ?? null);
|
|
320
|
+
await recordSessionEvent({
|
|
321
|
+
kind: "provider",
|
|
322
|
+
title: `Disconnected ${provider?.name ?? "a provider"}`,
|
|
323
|
+
detail: getActiveProvider()
|
|
324
|
+
? `${getActiveProvider().name} is still available as the active provider.`
|
|
325
|
+
: "No active provider is left on this device.",
|
|
326
|
+
panel: "providers",
|
|
327
|
+
focus: provider?.id ?? null,
|
|
328
|
+
status: "warning",
|
|
329
|
+
});
|
|
330
|
+
console.log(chalk.green(`\n Disconnected ${provider?.name ?? "provider"}.`));
|
|
331
|
+
return {
|
|
332
|
+
action: "disconnect",
|
|
333
|
+
changed: true,
|
|
334
|
+
activeProvider: getActiveProvider(),
|
|
335
|
+
needsModelSelection: false,
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
console.log(chalk.red("Use one of: providers list, providers inspect, providers recent, providers test, providers connect, providers switch, providers disconnect"));
|
|
339
|
+
}
|
|
340
|
+
export function providersCommand(cli) {
|
|
341
|
+
cli.command("providers <action> [target]", "Manage local LLM providers for the interactive Context terminal").action(async (action, target) => {
|
|
342
|
+
await providersCommandAction(action, target);
|
|
343
|
+
});
|
|
344
|
+
}
|