umbrella-context 0.1.39 → 0.1.41
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 +80 -0
- package/dist/adapters/byterover-runtime-bridge.js +47 -35
- package/dist/adapters/umbrella-provider-runtime.d.ts +9 -2
- package/dist/adapters/umbrella-provider-runtime.js +86 -7
- package/dist/adaptive/runtime.d.ts +27 -0
- package/dist/adaptive/runtime.js +154 -0
- package/dist/commands/adaptive.d.ts +7 -0
- package/dist/commands/adaptive.js +92 -0
- package/dist/commands/connectors.js +96 -1
- package/dist/commands/curate.js +2 -0
- package/dist/commands/logout.js +1 -0
- package/dist/commands/model.js +30 -5
- package/dist/commands/providers.js +9 -3
- package/dist/commands/search.d.ts +1 -0
- package/dist/commands/search.js +51 -12
- package/dist/commands/setup.js +1 -1
- package/dist/commands/source.d.ts +11 -0
- package/dist/commands/source.js +152 -0
- package/dist/commands/status.d.ts +5 -0
- package/dist/commands/status.js +18 -3
- package/dist/commands/swarm.d.ts +16 -0
- package/dist/commands/swarm.js +211 -0
- package/dist/commands/tui.js +223 -52
- package/dist/commands/worktree.d.ts +12 -0
- package/dist/commands/worktree.js +141 -0
- package/dist/index.js +8 -0
- package/dist/repo-state.d.ts +71 -0
- package/dist/repo-state.js +260 -7
- package/dist/swarm/runtime.d.ts +502 -0
- package/dist/swarm/runtime.js +957 -0
- package/package.json +1 -1
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { addKnowledgeSource, listKnowledgeSources, recordSessionEvent, removeKnowledgeSource, setSessionPanel, } from "../repo-state.js";
|
|
3
|
+
import { ensureSwarmConfigForLinkedSources } from "../swarm/runtime.js";
|
|
4
|
+
function asOutputFormat(value) {
|
|
5
|
+
return value === "json" ? "json" : "text";
|
|
6
|
+
}
|
|
7
|
+
function normalizeCliPathInput(value) {
|
|
8
|
+
const trimmed = value.trim();
|
|
9
|
+
if ((trimmed.startsWith('"') && trimmed.endsWith('"')) ||
|
|
10
|
+
(trimmed.startsWith("'") && trimmed.endsWith("'"))) {
|
|
11
|
+
return trimmed.slice(1, -1).trim();
|
|
12
|
+
}
|
|
13
|
+
return trimmed;
|
|
14
|
+
}
|
|
15
|
+
export async function sourceAddCommandAction(targetPath, opts = {}) {
|
|
16
|
+
const format = asOutputFormat(opts.format);
|
|
17
|
+
const normalizedTargetPath = normalizeCliPathInput(targetPath ?? "");
|
|
18
|
+
if (!normalizedTargetPath) {
|
|
19
|
+
const message = 'Type a repo path after "umbrella-context source add".';
|
|
20
|
+
if (format === "json") {
|
|
21
|
+
console.log(JSON.stringify({ ok: false, action: "source.add", error: message }, null, 2));
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
console.log(chalk.red(message));
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
await setSessionPanel("session", "source-add");
|
|
29
|
+
const result = await addKnowledgeSource(normalizedTargetPath, { alias: opts.alias });
|
|
30
|
+
const swarmBootstrap = await ensureSwarmConfigForLinkedSources();
|
|
31
|
+
await recordSessionEvent({
|
|
32
|
+
kind: "session",
|
|
33
|
+
title: "Added a knowledge source",
|
|
34
|
+
detail: swarmBootstrap.created
|
|
35
|
+
? `${result.message} Swarm was also bootstrapped so cross-repo search is live right away.`
|
|
36
|
+
: result.message,
|
|
37
|
+
panel: "session",
|
|
38
|
+
focus: "source-add",
|
|
39
|
+
status: "success",
|
|
40
|
+
});
|
|
41
|
+
if (format === "json") {
|
|
42
|
+
console.log(JSON.stringify({ ok: true, action: "source.add", ...result, swarmBootstrap }, null, 2));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
console.log(chalk.green(`\n ${result.message}`));
|
|
46
|
+
if (swarmBootstrap.created) {
|
|
47
|
+
console.log(chalk.gray(" Swarm is now ready too, so this source can show up in cross-repo search immediately."));
|
|
48
|
+
}
|
|
49
|
+
else if (swarmBootstrap.ready) {
|
|
50
|
+
console.log(chalk.gray(" Swarm was already ready, so this source can be searched right away."));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
55
|
+
if (format === "json") {
|
|
56
|
+
console.log(JSON.stringify({ ok: false, action: "source.add", error: message }, null, 2));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
console.log(chalk.red(`\n ${message}`));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
export async function sourceListCommandAction(opts = {}) {
|
|
63
|
+
const format = asOutputFormat(opts.format);
|
|
64
|
+
try {
|
|
65
|
+
await setSessionPanel("session", "source-list");
|
|
66
|
+
const sources = await listKnowledgeSources();
|
|
67
|
+
if (format === "json") {
|
|
68
|
+
console.log(JSON.stringify({ ok: true, action: "source.list", sources }, null, 2));
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
console.log(chalk.bold("\n Knowledge Sources\n"));
|
|
72
|
+
if (sources.length === 0) {
|
|
73
|
+
console.log(chalk.yellow("No read-only knowledge sources are linked yet."));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
sources.forEach((source) => {
|
|
77
|
+
console.log(`- ${source.alias} -> ${source.projectRoot} ${source.valid ? chalk.green("(valid)") : chalk.red("(broken)")}`);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
82
|
+
if (format === "json") {
|
|
83
|
+
console.log(JSON.stringify({ ok: false, action: "source.list", error: message }, null, 2));
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
console.log(chalk.red(`\n ${message}`));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
export async function sourceRemoveCommandAction(aliasOrPath, opts = {}) {
|
|
90
|
+
const format = asOutputFormat(opts.format);
|
|
91
|
+
const normalizedAliasOrPath = normalizeCliPathInput(aliasOrPath ?? "");
|
|
92
|
+
if (!normalizedAliasOrPath) {
|
|
93
|
+
const message = 'Type a source alias or path after "umbrella-context source remove".';
|
|
94
|
+
if (format === "json") {
|
|
95
|
+
console.log(JSON.stringify({ ok: false, action: "source.remove", error: message }, null, 2));
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
console.log(chalk.red(message));
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
await setSessionPanel("session", "source-remove");
|
|
103
|
+
const result = await removeKnowledgeSource(normalizedAliasOrPath);
|
|
104
|
+
await recordSessionEvent({
|
|
105
|
+
kind: "session",
|
|
106
|
+
title: "Removed a knowledge source",
|
|
107
|
+
detail: result.message,
|
|
108
|
+
panel: "session",
|
|
109
|
+
focus: "source-remove",
|
|
110
|
+
status: "success",
|
|
111
|
+
});
|
|
112
|
+
if (format === "json") {
|
|
113
|
+
console.log(JSON.stringify({ ok: true, action: "source.remove", ...result }, null, 2));
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
console.log(chalk.green(`\n ${result.message}`));
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
120
|
+
if (format === "json") {
|
|
121
|
+
console.log(JSON.stringify({ ok: false, action: "source.remove", error: message }, null, 2));
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
console.log(chalk.red(`\n ${message}`));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
export function sourceCommand(cli) {
|
|
128
|
+
cli
|
|
129
|
+
.command("source <action> [...value]", "Manage read-only knowledge sources from other Umbrella repos")
|
|
130
|
+
.example('source add "C:\\Shared Repo" --alias shared')
|
|
131
|
+
.example("source list")
|
|
132
|
+
.example("source remove shared")
|
|
133
|
+
.option("--alias <alias>", "Friendly source alias")
|
|
134
|
+
.option("--format <format>", "Output format (text or json)")
|
|
135
|
+
.action(async (action, value = [], opts) => {
|
|
136
|
+
const normalized = action.trim().toLowerCase();
|
|
137
|
+
const joinedValue = value.join(" ").trim();
|
|
138
|
+
if (normalized === "add") {
|
|
139
|
+
await sourceAddCommandAction(joinedValue, opts);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (normalized === "list") {
|
|
143
|
+
await sourceListCommandAction(opts);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (normalized === "remove") {
|
|
147
|
+
await sourceRemoveCommandAction(joinedValue, opts);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
console.log(chalk.red("Use one of: source add <path>, source list, source remove <alias-or-path>"));
|
|
151
|
+
});
|
|
152
|
+
}
|
|
@@ -29,6 +29,11 @@ export type StatusSnapshot = {
|
|
|
29
29
|
transportQueue: number;
|
|
30
30
|
transportActiveTask: string;
|
|
31
31
|
contextTreeSummary: string;
|
|
32
|
+
worktreeMode: "direct" | "linked";
|
|
33
|
+
worktreeCount: number;
|
|
34
|
+
sourceCount: number;
|
|
35
|
+
adaptiveEntryCount: number;
|
|
36
|
+
adaptiveTopTitle: string;
|
|
32
37
|
warnings: string[];
|
|
33
38
|
nextSteps: string[];
|
|
34
39
|
};
|
package/dist/commands/status.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import { promises as fs } from "fs";
|
|
3
3
|
import path from "path";
|
|
4
|
+
import { getAdaptiveKnowledgeSummary } from "../adaptive/runtime.js";
|
|
4
5
|
import { formatUmbrellaAuthStatus } from "../auth-state.js";
|
|
5
6
|
import { getStoredUmbrellaAuthSnapshot } from "../adapters/umbrella-auth-runtime.js";
|
|
6
7
|
import { configManager } from "../config.js";
|
|
7
|
-
import { getConnectorRuns, getContextTreeState, getInstalledConnectors, getInstalledHubEntries, getPendingMemories, getPulledFixes, getPulledMemories, getRepoContext, ensureSessionState, getTransportState, } from "../repo-state.js";
|
|
8
|
+
import { getConnectorRuns, getContextTreeState, getInstalledConnectors, getInstalledHubEntries, getRepoResolution, getPendingMemories, getPulledFixes, getPulledMemories, getRepoContext, listKnowledgeSources, listRegisteredWorktrees, ensureSessionState, getTransportState, } from "../repo-state.js";
|
|
8
9
|
export function buildConnectionPresentation(config, authSnapshot = getStoredUmbrellaAuthSnapshot()) {
|
|
9
10
|
const warnings = [];
|
|
10
11
|
const connectionMode = authSnapshot.status === "authorized"
|
|
@@ -27,7 +28,7 @@ export function buildConnectionPresentation(config, authSnapshot = getStoredUmbr
|
|
|
27
28
|
const backend = new URL(config.serverUrl);
|
|
28
29
|
const umbrellaIsLoopback = ["127.0.0.1", "localhost"].includes(umbrella.hostname.toLowerCase());
|
|
29
30
|
const backendIsLoopback = ["127.0.0.1", "localhost"].includes(backend.hostname.toLowerCase());
|
|
30
|
-
if (umbrella.hostname !== backend.hostname
|
|
31
|
+
if (umbrella.hostname !== backend.hostname) {
|
|
31
32
|
warnings.push(`The saved Umbrella app URL (${config.umbrellaUrl}) and Context backend URL (${config.serverUrl}) point to different places.`);
|
|
32
33
|
}
|
|
33
34
|
if (umbrellaIsLoopback !== backendIsLoopback) {
|
|
@@ -53,7 +54,7 @@ export async function getStatusSnapshot() {
|
|
|
53
54
|
const { repoRoot, state, umDir } = await getRepoContext();
|
|
54
55
|
const session = await ensureSessionState();
|
|
55
56
|
const latestEvent = session.events?.[0] ?? null;
|
|
56
|
-
const [pending, pulledMemories, pulledFixes, connectors, hubEntries, connectorRuns, transport, contextTree] = await Promise.all([
|
|
57
|
+
const [pending, pulledMemories, pulledFixes, connectors, hubEntries, connectorRuns, transport, contextTree, repoResolution, worktrees, sources, adaptive] = await Promise.all([
|
|
57
58
|
getPendingMemories(),
|
|
58
59
|
getPulledMemories(),
|
|
59
60
|
getPulledFixes(),
|
|
@@ -62,6 +63,10 @@ export async function getStatusSnapshot() {
|
|
|
62
63
|
getConnectorRuns(),
|
|
63
64
|
getTransportState(),
|
|
64
65
|
getContextTreeState(),
|
|
66
|
+
getRepoResolution(),
|
|
67
|
+
listRegisteredWorktrees(),
|
|
68
|
+
listKnowledgeSources(),
|
|
69
|
+
getAdaptiveKnowledgeSummary(),
|
|
65
70
|
]);
|
|
66
71
|
const activeProvider = configManager.providers.find((entry) => entry.id === config.activeProvider) ?? null;
|
|
67
72
|
const latestConnectorRun = connectorRuns[0] ?? null;
|
|
@@ -86,6 +91,8 @@ export async function getStatusSnapshot() {
|
|
|
86
91
|
nextSteps.push('Add a first repo connector with "umbrella-context connectors install".');
|
|
87
92
|
if (hubEntries.length === 0)
|
|
88
93
|
nextSteps.push('Browse reusable bundles with "umbrella-context hub list".');
|
|
94
|
+
if (sources.length === 0)
|
|
95
|
+
nextSteps.push('Link another Umbrella repo with "umbrella-context source add <path>" if you want cross-project knowledge.');
|
|
89
96
|
const connection = buildConnectionPresentation(config);
|
|
90
97
|
if (connection.connectionMode === "saved_local_config") {
|
|
91
98
|
nextSteps.unshift('Re-run "umbrella-context setup" to verify the live Umbrella connection before trusting the saved company and space.');
|
|
@@ -121,6 +128,11 @@ export async function getStatusSnapshot() {
|
|
|
121
128
|
transportQueue: transport?.queue.length ?? 0,
|
|
122
129
|
transportActiveTask: transport?.activeTaskId ?? "None",
|
|
123
130
|
contextTreeSummary: contextTree?.summaryHandle ?? "No context tree summary yet",
|
|
131
|
+
worktreeMode: repoResolution.mode,
|
|
132
|
+
worktreeCount: worktrees.length,
|
|
133
|
+
sourceCount: sources.length,
|
|
134
|
+
adaptiveEntryCount: adaptive.entryCount,
|
|
135
|
+
adaptiveTopTitle: adaptive.hottestEntries[0]?.title ?? "None yet",
|
|
124
136
|
warnings: connection.warnings,
|
|
125
137
|
nextSteps,
|
|
126
138
|
};
|
|
@@ -173,6 +185,9 @@ export async function statusCommandAction(opts = {}) {
|
|
|
173
185
|
`${snapshot.pendingCount} pending draft${snapshot.pendingCount === 1 ? "" : "s"}`,
|
|
174
186
|
`${snapshot.pulledContextCount} pulled note${snapshot.pulledContextCount === 1 ? "" : "s"}`,
|
|
175
187
|
`${snapshot.pulledFixCount} pulled known fix${snapshot.pulledFixCount === 1 ? "" : "es"}`,
|
|
188
|
+
`${snapshot.worktreeCount} registered worktree${snapshot.worktreeCount === 1 ? "" : "s"} (${snapshot.worktreeMode})`,
|
|
189
|
+
`${snapshot.sourceCount} linked knowledge source${snapshot.sourceCount === 1 ? "" : "s"}`,
|
|
190
|
+
`${snapshot.adaptiveEntryCount} adaptive abstract${snapshot.adaptiveEntryCount === 1 ? "" : "s"} (top: ${snapshot.adaptiveTopTitle})`,
|
|
176
191
|
`Last push: ${snapshot.lastPushAt}`,
|
|
177
192
|
`Last pull: ${snapshot.lastPullAt}`,
|
|
178
193
|
]);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
type OutputFormat = "json" | "text";
|
|
2
|
+
export declare function swarmOnboardCommandAction(format?: OutputFormat): Promise<void>;
|
|
3
|
+
export declare function swarmStatusCommandAction(format?: OutputFormat): Promise<void>;
|
|
4
|
+
export declare function swarmQueryCommandAction(query: string, opts?: {
|
|
5
|
+
explain?: boolean;
|
|
6
|
+
format?: string;
|
|
7
|
+
maxResults?: number;
|
|
8
|
+
provider?: string;
|
|
9
|
+
timeout?: number;
|
|
10
|
+
}): Promise<void>;
|
|
11
|
+
export declare function swarmCurateCommandAction(content: string, opts?: {
|
|
12
|
+
format?: string;
|
|
13
|
+
provider?: string;
|
|
14
|
+
}): Promise<void>;
|
|
15
|
+
export declare function swarmCommand(cli: any): void;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { executeSwarmCurate, executeSwarmQuery, formatSwarmQueryText, getSwarmStatus, runSwarmOnboardWizard, } from "../swarm/runtime.js";
|
|
3
|
+
import { recordSessionEvent, setSessionPanel } from "../repo-state.js";
|
|
4
|
+
function asOutputFormat(value) {
|
|
5
|
+
return value === "json" ? "json" : "text";
|
|
6
|
+
}
|
|
7
|
+
export async function swarmOnboardCommandAction(format = "text") {
|
|
8
|
+
try {
|
|
9
|
+
await setSessionPanel("session", "swarm-onboard");
|
|
10
|
+
const result = await runSwarmOnboardWizard();
|
|
11
|
+
await recordSessionEvent({
|
|
12
|
+
kind: "session",
|
|
13
|
+
title: "Configured swarm",
|
|
14
|
+
detail: `Swarm config was written to ${result.configPath}.`,
|
|
15
|
+
panel: "session",
|
|
16
|
+
focus: "swarm-onboard",
|
|
17
|
+
status: "success",
|
|
18
|
+
});
|
|
19
|
+
if (format === "json") {
|
|
20
|
+
console.log(JSON.stringify({ ok: true, action: "swarm.onboard", ...result }, null, 2));
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
console.log(chalk.green("\n Swarm is configured for this repo."));
|
|
24
|
+
console.log(chalk.gray(` Config file: ${result.configPath}`));
|
|
25
|
+
console.log(chalk.gray(' Next step: run "umbrella-context swarm status" to verify provider health.'));
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
29
|
+
if (format === "json") {
|
|
30
|
+
console.log(JSON.stringify({ ok: false, action: "swarm.onboard", error: message }, null, 2));
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
console.log(chalk.red(`\n ${message}`));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export async function swarmStatusCommandAction(format = "text") {
|
|
37
|
+
try {
|
|
38
|
+
await setSessionPanel("session", "swarm-status");
|
|
39
|
+
const result = await getSwarmStatus();
|
|
40
|
+
if (format === "json") {
|
|
41
|
+
console.log(JSON.stringify({ ok: true, action: "swarm.status", ...result }, null, 2));
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
console.log(chalk.bold("\n Swarm Status\n"));
|
|
45
|
+
console.log(` Configured: ${result.configured ? "Yes" : "No"}`);
|
|
46
|
+
console.log(` Config file: ${result.configPath}`);
|
|
47
|
+
console.log("");
|
|
48
|
+
if (!result.configured) {
|
|
49
|
+
result.suggestions.forEach((suggestion) => console.log(chalk.yellow(` - ${suggestion}`)));
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
console.log(chalk.bold(" Providers"));
|
|
53
|
+
result.providers.forEach((provider) => {
|
|
54
|
+
const state = provider.available ? chalk.green("healthy") : chalk.red("missing");
|
|
55
|
+
const write = provider.writable ? "write" : "read-only";
|
|
56
|
+
console.log(` - ${provider.label} [${provider.type}] -> ${state} -> ${write}`);
|
|
57
|
+
console.log(chalk.gray(` ${provider.detail}`));
|
|
58
|
+
});
|
|
59
|
+
if (result.enrichmentEdges.length > 0) {
|
|
60
|
+
console.log("");
|
|
61
|
+
console.log(chalk.bold(" Enrichment"));
|
|
62
|
+
result.enrichmentEdges.forEach((edge) => console.log(` - ${edge.from} -> ${edge.to}`));
|
|
63
|
+
}
|
|
64
|
+
if (result.suggestions.length > 0) {
|
|
65
|
+
console.log("");
|
|
66
|
+
console.log(chalk.bold(" Suggestions"));
|
|
67
|
+
result.suggestions.forEach((suggestion) => console.log(chalk.yellow(` - ${suggestion}`)));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
72
|
+
if (format === "json") {
|
|
73
|
+
console.log(JSON.stringify({ ok: false, action: "swarm.status", error: message }, null, 2));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
console.log(chalk.red(`\n ${message}`));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
export async function swarmQueryCommandAction(query, opts = {}) {
|
|
80
|
+
const format = asOutputFormat(opts.format);
|
|
81
|
+
if (!query.trim()) {
|
|
82
|
+
if (format === "json") {
|
|
83
|
+
console.log(JSON.stringify({ ok: false, action: "swarm.query", error: "Type a query after swarm query." }, null, 2));
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
console.log(chalk.red('Type a question after "umbrella-context swarm query".'));
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
await setSessionPanel("session", `swarm-query:${query}`);
|
|
91
|
+
const queryPromise = executeSwarmQuery(query, {
|
|
92
|
+
explain: Boolean(opts.explain),
|
|
93
|
+
format,
|
|
94
|
+
maxResults: typeof opts.maxResults === "number" ? opts.maxResults : undefined,
|
|
95
|
+
provider: opts.provider,
|
|
96
|
+
});
|
|
97
|
+
const timeoutSeconds = Math.max(1, Math.min(3600, Number(opts.timeout ?? 300)));
|
|
98
|
+
const result = await Promise.race([
|
|
99
|
+
queryPromise,
|
|
100
|
+
new Promise((_, reject) => {
|
|
101
|
+
setTimeout(() => reject(new Error(`Swarm query timed out after ${timeoutSeconds} seconds.`)), timeoutSeconds * 1000);
|
|
102
|
+
}),
|
|
103
|
+
]);
|
|
104
|
+
await recordSessionEvent({
|
|
105
|
+
kind: "query",
|
|
106
|
+
title: `Ran swarm query "${query}"`,
|
|
107
|
+
detail: `Swarm returned ${result.results.length} merged result${result.results.length === 1 ? "" : "s"}.`,
|
|
108
|
+
panel: "session",
|
|
109
|
+
focus: "swarm-query",
|
|
110
|
+
status: "success",
|
|
111
|
+
});
|
|
112
|
+
if (format === "json") {
|
|
113
|
+
console.log(JSON.stringify({ ok: true, action: "swarm.query", ...result }, null, 2));
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
console.log(formatSwarmQueryText(result, { explain: Boolean(opts.explain) }));
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
120
|
+
if (format === "json") {
|
|
121
|
+
console.log(JSON.stringify({ ok: false, action: "swarm.query", error: message }, null, 2));
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
console.log(chalk.red(`\n ${message}`));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
export async function swarmCurateCommandAction(content, opts = {}) {
|
|
128
|
+
const format = asOutputFormat(opts.format);
|
|
129
|
+
if (!content.trim()) {
|
|
130
|
+
if (format === "json") {
|
|
131
|
+
console.log(JSON.stringify({ ok: false, action: "swarm.curate", error: "Type content after swarm curate." }, null, 2));
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
console.log(chalk.red('Type content after "umbrella-context swarm curate".'));
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
try {
|
|
138
|
+
await setSessionPanel("session", "swarm-curate");
|
|
139
|
+
const result = await executeSwarmCurate(content, { provider: opts.provider });
|
|
140
|
+
await recordSessionEvent({
|
|
141
|
+
kind: "curate",
|
|
142
|
+
title: `Stored content through swarm`,
|
|
143
|
+
detail: `Swarm stored content in ${result.provider}${result.fallback ? " using fallback routing" : ""}.`,
|
|
144
|
+
panel: "session",
|
|
145
|
+
focus: "swarm-curate",
|
|
146
|
+
status: "success",
|
|
147
|
+
});
|
|
148
|
+
if (format === "json") {
|
|
149
|
+
console.log(JSON.stringify({ ok: true, action: "swarm.curate", ...result }, null, 2));
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
console.log(chalk.green(`\n Stored through swarm in ${result.provider}.`));
|
|
153
|
+
console.log(chalk.gray(` Target type: ${result.targetType}`));
|
|
154
|
+
if (result.id) {
|
|
155
|
+
console.log(chalk.gray(` Saved as: ${result.id}`));
|
|
156
|
+
}
|
|
157
|
+
if (result.fallback) {
|
|
158
|
+
console.log(chalk.yellow(" Swarm fell back to Umbrella Context because no other writable provider was ready."));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
163
|
+
if (format === "json") {
|
|
164
|
+
console.log(JSON.stringify({ ok: false, action: "swarm.curate", error: message }, null, 2));
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
console.log(chalk.red(`\n ${message}`));
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
export function swarmCommand(cli) {
|
|
171
|
+
cli
|
|
172
|
+
.command("swarm <action> [...rest]", "Swarm commands for searching and saving across multiple knowledge tools")
|
|
173
|
+
.example("swarm onboard")
|
|
174
|
+
.example("swarm status")
|
|
175
|
+
.example('swarm query "How do auth cookies refresh?" --explain')
|
|
176
|
+
.example('swarm curate "JWT refresh token notes"')
|
|
177
|
+
.option("--format <format>", "Output format (text or json)")
|
|
178
|
+
.option("--explain", "Show provider selection and enrichment details for swarm query")
|
|
179
|
+
.option("--max-results <n>", "Maximum number of merged hits to return for swarm query")
|
|
180
|
+
.option("--timeout <seconds>", "Maximum seconds to wait before a swarm query times out")
|
|
181
|
+
.option("--provider <provider>", "Force a specific swarm provider")
|
|
182
|
+
.action(async (action, rest = [], opts) => {
|
|
183
|
+
const normalized = action.trim().toLowerCase();
|
|
184
|
+
if (normalized === "onboard") {
|
|
185
|
+
await swarmOnboardCommandAction(asOutputFormat(opts.format));
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
if (normalized === "status") {
|
|
189
|
+
await swarmStatusCommandAction(asOutputFormat(opts.format));
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
if (normalized === "query") {
|
|
193
|
+
await swarmQueryCommandAction(rest.join(" "), {
|
|
194
|
+
explain: Boolean(opts.explain),
|
|
195
|
+
format: opts.format,
|
|
196
|
+
maxResults: opts.maxResults ? Number(opts.maxResults) : undefined,
|
|
197
|
+
provider: opts.provider,
|
|
198
|
+
timeout: opts.timeout ? Number(opts.timeout) : undefined,
|
|
199
|
+
});
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
if (normalized === "curate") {
|
|
203
|
+
await swarmCurateCommandAction(rest.join(" "), {
|
|
204
|
+
format: opts.format,
|
|
205
|
+
provider: opts.provider,
|
|
206
|
+
});
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
console.log(chalk.red('Use one of: swarm onboard, swarm status, swarm query "<question>", swarm curate "<content>"'));
|
|
210
|
+
});
|
|
211
|
+
}
|