umbrella-context 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 +41 -0
- package/dist/commands/connect.d.ts +1 -0
- package/dist/commands/connect.js +69 -0
- package/dist/commands/curate.d.ts +5 -0
- package/dist/commands/curate.js +35 -0
- package/dist/commands/fix.d.ts +1 -0
- package/dist/commands/fix.js +40 -0
- package/dist/commands/interactive.d.ts +1 -0
- package/dist/commands/interactive.js +145 -0
- package/dist/commands/locations.d.ts +1 -0
- package/dist/commands/locations.js +17 -0
- package/dist/commands/login.d.ts +1 -0
- package/dist/commands/login.js +7 -0
- package/dist/commands/mcp.d.ts +1 -0
- package/dist/commands/mcp.js +73 -0
- package/dist/commands/pull.d.ts +2 -0
- package/dist/commands/pull.js +58 -0
- package/dist/commands/push.d.ts +2 -0
- package/dist/commands/push.js +65 -0
- package/dist/commands/record.d.ts +1 -0
- package/dist/commands/record.js +37 -0
- package/dist/commands/search.d.ts +2 -0
- package/dist/commands/search.js +57 -0
- package/dist/commands/seed.d.ts +1 -0
- package/dist/commands/seed.js +117 -0
- package/dist/commands/setup.d.ts +1 -0
- package/dist/commands/setup.js +171 -0
- package/dist/commands/space.d.ts +1 -0
- package/dist/commands/space.js +75 -0
- package/dist/commands/status.d.ts +2 -0
- package/dist/commands/status.js +32 -0
- package/dist/config.d.ts +30 -0
- package/dist/config.js +63 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +42 -0
- package/dist/repo-state.d.ts +53 -0
- package/dist/repo-state.js +171 -0
- package/dist/umbrella.d.ts +45 -0
- package/dist/umbrella.js +108 -0
- package/package.json +41 -0
package/README.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Umbrella Context CLI
|
|
2
|
+
|
|
3
|
+
Use this CLI to connect a laptop or IDE to an Umbrella company context space.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g umbrella-context
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
For local testing from a tarball:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install -g ./umbrella-context-0.1.1.tgz
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## First-time setup
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
umbrella-context setup
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The setup flow will:
|
|
24
|
+
- connect to your Umbrella server
|
|
25
|
+
- sign you in when needed
|
|
26
|
+
- list your companies
|
|
27
|
+
- let you choose a company
|
|
28
|
+
- let you choose or create a context space
|
|
29
|
+
- save this device config locally
|
|
30
|
+
|
|
31
|
+
## Common commands
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
umbrella-context query "What do we already know?"
|
|
35
|
+
umbrella-context curate "We learned that..."
|
|
36
|
+
umbrella-context fix "ETIMEDOUT"
|
|
37
|
+
umbrella-context mcp
|
|
38
|
+
umbrella-context
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The old `agent-memory` command still works as a compatibility alias.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function connectCommand(cli: any): void;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { configManager } from "../config.js";
|
|
3
|
+
import { ensureRepoContext } from "../repo-state.js";
|
|
4
|
+
export function connectCommand(cli) {
|
|
5
|
+
cli
|
|
6
|
+
.command("connect", "Connect this device directly to a company context space without the interactive setup flow")
|
|
7
|
+
.option("--umbrella-url <url>", "Umbrella server URL for company and space metadata")
|
|
8
|
+
.option("--server-url <url>", "Agent Memory server URL")
|
|
9
|
+
.option("--company-id <id>", "Umbrella company ID")
|
|
10
|
+
.option("--company-name <name>", "Umbrella company name")
|
|
11
|
+
.option("--workspace-id <id>", "Underlying workspace ID")
|
|
12
|
+
.option("--workspace-name <name>", "Underlying workspace name")
|
|
13
|
+
.option("--space-id <id>", "Context space ID")
|
|
14
|
+
.option("--space-name <name>", "Context space name")
|
|
15
|
+
.option("--project-id <id>", "Deprecated alias for --space-id")
|
|
16
|
+
.option("--project-name <name>", "Deprecated alias for --space-name")
|
|
17
|
+
.option("--api-key <key>", "Context API key")
|
|
18
|
+
.action(async (options) => {
|
|
19
|
+
const serverUrl = options.serverUrl?.trim();
|
|
20
|
+
const workspaceId = options.workspaceId?.trim();
|
|
21
|
+
const workspaceName = options.workspaceName?.trim();
|
|
22
|
+
const companyId = options.companyId?.trim() ?? workspaceId;
|
|
23
|
+
const companyName = options.companyName?.trim() ?? workspaceName;
|
|
24
|
+
const projectId = options.spaceId?.trim() ?? options.projectId?.trim();
|
|
25
|
+
const projectName = options.spaceName?.trim() ?? options.projectName?.trim();
|
|
26
|
+
const apiKey = options.apiKey?.trim();
|
|
27
|
+
if (!serverUrl || !workspaceId || !workspaceName || !projectId || !projectName || !apiKey) {
|
|
28
|
+
console.log(chalk.red("\n Missing required connection details."));
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
const res = await fetch(`${serverUrl}/api/memories?limit=1`, {
|
|
33
|
+
headers: {
|
|
34
|
+
Authorization: `Bearer ${apiKey}`,
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
if (!res.ok) {
|
|
38
|
+
const body = await res.text();
|
|
39
|
+
console.log(chalk.red(`\n Connection failed: ${body || res.statusText}`));
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
configManager.set({
|
|
43
|
+
umbrellaUrl: options.umbrellaUrl?.trim(),
|
|
44
|
+
serverUrl,
|
|
45
|
+
companyId,
|
|
46
|
+
companyName,
|
|
47
|
+
workspaceId,
|
|
48
|
+
workspaceName,
|
|
49
|
+
projectId,
|
|
50
|
+
projectName,
|
|
51
|
+
apiKey,
|
|
52
|
+
});
|
|
53
|
+
configManager.upsertLocation({
|
|
54
|
+
repoRoot: process.cwd(),
|
|
55
|
+
companyId: companyId ?? workspaceId,
|
|
56
|
+
companyName: companyName ?? workspaceName,
|
|
57
|
+
projectId,
|
|
58
|
+
projectName,
|
|
59
|
+
updatedAt: new Date().toISOString(),
|
|
60
|
+
});
|
|
61
|
+
await ensureRepoContext(configManager.config);
|
|
62
|
+
console.log(chalk.green(`\n Connected: ${companyName} / ${projectName}`));
|
|
63
|
+
console.log(chalk.gray(` Config saved to ${configManager.configPath}`));
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
console.log(chalk.red(`\n Error: ${err.message}`));
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { configManager } from "../config.js";
|
|
3
|
+
import { addPendingMemory, ensureRepoContext, getRepoContext } from "../repo-state.js";
|
|
4
|
+
export async function curateCommandAction(content, opts = {}) {
|
|
5
|
+
const config = configManager.config;
|
|
6
|
+
if (!config) {
|
|
7
|
+
console.log(chalk.red("Not configured. Run: umbrella-context setup"));
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
if (!content?.trim()) {
|
|
11
|
+
console.log(chalk.red("\n Usage: umbrella-context curate \"What we learned\""));
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
await ensureRepoContext(config);
|
|
15
|
+
const entry = await addPendingMemory({
|
|
16
|
+
content: content.trim(),
|
|
17
|
+
tags: opts.tag ? (Array.isArray(opts.tag) ? opts.tag : [opts.tag]) : [],
|
|
18
|
+
source: "cli",
|
|
19
|
+
systemType: opts.type || "system1_knowledge",
|
|
20
|
+
});
|
|
21
|
+
const { umDir } = await getRepoContext();
|
|
22
|
+
console.log(chalk.green(`\n Saved locally to ${config.companyName} / ${config.projectName}`));
|
|
23
|
+
console.log(chalk.gray(` Pending entry: ${entry.id}`));
|
|
24
|
+
console.log(chalk.gray(` Repo context folder: ${umDir}`));
|
|
25
|
+
console.log(chalk.gray(' Run "umbrella-context push" when you want to sync this to the server.'));
|
|
26
|
+
}
|
|
27
|
+
export function curateCommand(cli) {
|
|
28
|
+
cli
|
|
29
|
+
.command("curate <content>", "Save context locally in this repo so it can be pushed later")
|
|
30
|
+
.option("--tag <tag>", "Add tags (repeatable)")
|
|
31
|
+
.option("--type <type>", "system1_knowledge or system2_reasoning")
|
|
32
|
+
.action(async (content, opts) => {
|
|
33
|
+
await curateCommandAction(content, opts);
|
|
34
|
+
});
|
|
35
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function fixCommand(cli: any): void;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { configManager } from "../config.js";
|
|
3
|
+
export function fixCommand(cli) {
|
|
4
|
+
cli.command("fix <error>", "Search for known fixes to an error").action(async (error) => {
|
|
5
|
+
const config = configManager.config;
|
|
6
|
+
if (!config) {
|
|
7
|
+
console.log(chalk.red("Not configured. Run: umbrella-context setup"));
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
try {
|
|
11
|
+
const res = await fetch(`${config.serverUrl}/api/evolutions/search?error=${encodeURIComponent(error)}`, {
|
|
12
|
+
headers: { Authorization: `Bearer ${config.apiKey}` },
|
|
13
|
+
});
|
|
14
|
+
if (!res.ok) {
|
|
15
|
+
const err = await res.json();
|
|
16
|
+
console.log(chalk.red(`\n Failed: ${err.error}`));
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const data = await res.json();
|
|
20
|
+
if (data.results.length === 0) {
|
|
21
|
+
console.log(chalk.yellow(`\n No known fixes for "${error}"`));
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
console.log(chalk.bold(`\n Found ${data.total} fixes for "${error}":\n`));
|
|
25
|
+
data.results.forEach((r, i) => {
|
|
26
|
+
const pct = Math.round(r.confidence * 100);
|
|
27
|
+
const color = pct >= 80 ? chalk.green : pct >= 60 ? chalk.yellow : chalk.red;
|
|
28
|
+
console.log(chalk.cyan(` ${i + 1}.`));
|
|
29
|
+
console.log(` Error: ${r.errorSignal}`);
|
|
30
|
+
console.log(` Fix: ${r.solution}`);
|
|
31
|
+
console.log(color(` Confidence: ${pct}% (${r.timesSucceeded}/${r.timesApplied} successes)`));
|
|
32
|
+
console.log(chalk.gray(` Last applied: ${r.lastApplied ? new Date(r.lastApplied).toLocaleDateString() : "never"}`));
|
|
33
|
+
console.log("");
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
console.log(chalk.red(`\n Error: ${err.message}`));
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function interactiveCommand(_args: string[]): Promise<void>;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import prompts from "prompts";
|
|
3
|
+
import { configManager } from "../config.js";
|
|
4
|
+
import { getCompanyContextSummary, toContextSpaces } from "../umbrella.js";
|
|
5
|
+
import { curateCommandAction } from "./curate.js";
|
|
6
|
+
import { pushCommandAction } from "./push.js";
|
|
7
|
+
import { pullCommandAction } from "./pull.js";
|
|
8
|
+
import { searchCommandAction } from "./search.js";
|
|
9
|
+
import { statusCommandAction } from "./status.js";
|
|
10
|
+
export async function interactiveCommand(_args) {
|
|
11
|
+
const config = configManager.config;
|
|
12
|
+
if (!config) {
|
|
13
|
+
console.log(chalk.red("Not configured. Run: umbrella-context setup"));
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
console.log(chalk.bold(`\n Umbrella Context - ${config.companyName} / ${config.projectName}\n`));
|
|
17
|
+
console.log(chalk.gray(" Commands: curate, query, push, pull, fix, record, spaces, status, exit\n"));
|
|
18
|
+
while (true) {
|
|
19
|
+
const input = await prompts({
|
|
20
|
+
type: "text",
|
|
21
|
+
name: "value",
|
|
22
|
+
message: ">",
|
|
23
|
+
});
|
|
24
|
+
if (!input.value)
|
|
25
|
+
continue;
|
|
26
|
+
const parts = input.value.trim().split(/\s+/);
|
|
27
|
+
const cmd = parts[0].toLowerCase();
|
|
28
|
+
const rest = parts.slice(1).join(" ");
|
|
29
|
+
if (cmd === "exit" || cmd === "quit")
|
|
30
|
+
break;
|
|
31
|
+
if (cmd === "curate") {
|
|
32
|
+
await curateCommandAction(rest);
|
|
33
|
+
}
|
|
34
|
+
else if (cmd === "push") {
|
|
35
|
+
await pushCommandAction();
|
|
36
|
+
}
|
|
37
|
+
else if (cmd === "pull") {
|
|
38
|
+
await pullCommandAction();
|
|
39
|
+
}
|
|
40
|
+
else if (cmd === "search" || cmd === "query") {
|
|
41
|
+
await searchCommandAction(rest);
|
|
42
|
+
}
|
|
43
|
+
else if (cmd === "fix") {
|
|
44
|
+
await handleFix(rest, config);
|
|
45
|
+
}
|
|
46
|
+
else if (cmd === "record") {
|
|
47
|
+
await handleRecord(parts.slice(1), config);
|
|
48
|
+
}
|
|
49
|
+
else if (cmd === "projects" || cmd === "spaces") {
|
|
50
|
+
await handleSpaces(config);
|
|
51
|
+
}
|
|
52
|
+
else if (cmd === "status") {
|
|
53
|
+
await statusCommandAction();
|
|
54
|
+
}
|
|
55
|
+
else if (cmd === "help") {
|
|
56
|
+
console.log(chalk.gray(`
|
|
57
|
+
curate <content> Save a local context note into .um
|
|
58
|
+
query <query> Search local and server context
|
|
59
|
+
push Sync local .um notes to the server
|
|
60
|
+
pull Refresh the local snapshot from the server
|
|
61
|
+
fix <error> Search known fixes
|
|
62
|
+
record <id> <outcome> Record success/failure
|
|
63
|
+
spaces Show the current company and active space
|
|
64
|
+
status Show current sync status
|
|
65
|
+
exit Quit
|
|
66
|
+
`));
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
console.log(chalk.yellow(`Unknown command: ${cmd}. Type 'help' for commands.`));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
async function handleFix(error, config) {
|
|
74
|
+
if (!error) {
|
|
75
|
+
console.log(chalk.red("Usage: fix <error>"));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
const res = await fetch(`${config.serverUrl}/api/evolutions/search?error=${encodeURIComponent(error)}`, { headers: { Authorization: `Bearer ${config.apiKey}` } });
|
|
80
|
+
if (!res.ok)
|
|
81
|
+
return;
|
|
82
|
+
const data = await res.json();
|
|
83
|
+
if (data.results.length === 0) {
|
|
84
|
+
console.log(chalk.yellow("No known fixes"));
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
data.results.forEach((result, index) => {
|
|
88
|
+
const pct = Math.round(result.confidence * 100);
|
|
89
|
+
console.log(chalk.cyan(` ${index + 1}. [${pct}%]`));
|
|
90
|
+
console.log(` ${result.solution}`);
|
|
91
|
+
console.log(chalk.gray(` ${result.timesSucceeded}/${result.timesApplied} successes | ID: ${result.id}`));
|
|
92
|
+
console.log("");
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
console.log(chalk.red(`Error: ${err.message}`));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
async function handleRecord(parts, config) {
|
|
100
|
+
const [id, outcome] = parts;
|
|
101
|
+
if (!id || !["success", "failure"].includes(outcome)) {
|
|
102
|
+
console.log(chalk.red("Usage: record <id> <success|failure>"));
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
const res = await fetch(`${config.serverUrl}/api/evolutions/${id}/record-outcome`, {
|
|
107
|
+
method: "POST",
|
|
108
|
+
headers: {
|
|
109
|
+
"Content-Type": "application/json",
|
|
110
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
111
|
+
},
|
|
112
|
+
body: JSON.stringify({ outcome }),
|
|
113
|
+
});
|
|
114
|
+
if (!res.ok)
|
|
115
|
+
return;
|
|
116
|
+
const data = await res.json();
|
|
117
|
+
const pct = Math.round(data.confidence * 100);
|
|
118
|
+
console.log(chalk.green(`Recorded ${outcome} - confidence: ${pct}%`));
|
|
119
|
+
}
|
|
120
|
+
catch (err) {
|
|
121
|
+
console.log(chalk.red(`Error: ${err.message}`));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
async function handleSpaces(config) {
|
|
125
|
+
console.log(chalk.cyan(` Current company: ${config.companyName}`));
|
|
126
|
+
console.log(chalk.cyan(` Current space: ${config.projectName}`));
|
|
127
|
+
if (!config.umbrellaUrl) {
|
|
128
|
+
console.log(chalk.gray(" Run setup again if you want this terminal to list all company spaces."));
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
const summary = await getCompanyContextSummary(config.umbrellaUrl, config.companyId);
|
|
133
|
+
const spaces = toContextSpaces(summary);
|
|
134
|
+
console.log("");
|
|
135
|
+
spaces.forEach((space, index) => {
|
|
136
|
+
const active = space.id === config.projectId ? " (active)" : "";
|
|
137
|
+
const core = space.isPrimary ? " [core]" : "";
|
|
138
|
+
console.log(chalk.gray(` ${index + 1}. ${space.name}${core}${active}`));
|
|
139
|
+
});
|
|
140
|
+
console.log(chalk.gray('\n To switch spaces, run "umbrella-context space switch".'));
|
|
141
|
+
}
|
|
142
|
+
catch (err) {
|
|
143
|
+
console.log(chalk.red(` Could not load spaces: ${err.message}`));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function locationsCommand(cli: any): void;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { configManager } from "../config.js";
|
|
3
|
+
export function locationsCommand(cli) {
|
|
4
|
+
cli.command("locations", "List repo folders that have been connected on this machine").action(() => {
|
|
5
|
+
const locations = configManager.locations;
|
|
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
|
+
});
|
|
16
|
+
});
|
|
17
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function loginCommand(cli: any): void;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
export function loginCommand(cli) {
|
|
3
|
+
cli.command("login", "Alias for `umbrella-context setup`").action(async () => {
|
|
4
|
+
console.log(chalk.gray("`umbrella-context login` now uses the Umbrella setup flow."));
|
|
5
|
+
console.log(chalk.gray("Run `umbrella-context setup` to sign in, choose a company, and choose a context space."));
|
|
6
|
+
});
|
|
7
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function mcpCommand(cli: any): void;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { configManager } from "../config.js";
|
|
3
|
+
export function mcpCommand(cli) {
|
|
4
|
+
cli.command("mcp", "Run as MCP server for IDE integration (Cursor, Claude Code, etc.)").action(async () => {
|
|
5
|
+
const config = configManager.config;
|
|
6
|
+
if (!config) {
|
|
7
|
+
console.log(chalk.red("Not configured. Run: umbrella-context setup"));
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
console.log(chalk.green(`\n MCP server starting - connected to ${config.serverUrl}`));
|
|
11
|
+
console.log(chalk.gray(` Space: ${config.projectName}\n`));
|
|
12
|
+
try {
|
|
13
|
+
const { McpServer } = await import("@modelcontextprotocol/sdk/server/mcp.js");
|
|
14
|
+
const { StdioServerTransport } = await import("@modelcontextprotocol/sdk/server/stdio.js");
|
|
15
|
+
const { z } = await import("zod");
|
|
16
|
+
const server = new McpServer({
|
|
17
|
+
name: "umbrella-context",
|
|
18
|
+
version: "0.1.1",
|
|
19
|
+
});
|
|
20
|
+
server.tool("memory_search", "Search stored memories for company context knowledge", { query: z.string().describe("Search query"), limit: z.number().optional().default(10) }, async ({ query, limit }) => {
|
|
21
|
+
const res = await fetch(`${config.serverUrl}/api/memories/search?query=${encodeURIComponent(query)}&limit=${limit}`, { headers: { Authorization: `Bearer ${config.apiKey}` } });
|
|
22
|
+
const data = await res.json();
|
|
23
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
24
|
+
});
|
|
25
|
+
server.tool("memory_push", "Save a memory or note to the current context space", { content: z.string().describe("The memory content"), tags: z.array(z.string()).optional() }, async ({ content, tags }) => {
|
|
26
|
+
const res = await fetch(`${config.serverUrl}/api/memories`, {
|
|
27
|
+
method: "POST",
|
|
28
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${config.apiKey}` },
|
|
29
|
+
body: JSON.stringify({ content, tags: tags || [], source: "mcp" }),
|
|
30
|
+
});
|
|
31
|
+
const data = await res.json();
|
|
32
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
33
|
+
});
|
|
34
|
+
server.tool("evolution_search", "Search for known fixes to an error signal", { error: z.string().describe("Error message or signal") }, async ({ error }) => {
|
|
35
|
+
const res = await fetch(`${config.serverUrl}/api/evolutions/search?error=${encodeURIComponent(error)}`, { headers: { Authorization: `Bearer ${config.apiKey}` } });
|
|
36
|
+
const data = await res.json();
|
|
37
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
38
|
+
});
|
|
39
|
+
server.tool("evolution_push", "Record an error fix for future reference", {
|
|
40
|
+
errorSignal: z.string().describe("The error message"),
|
|
41
|
+
solution: z.string().describe("How it was fixed"),
|
|
42
|
+
outcome: z.enum(["success", "failure", "partial"]).optional().default("success"),
|
|
43
|
+
}, async ({ errorSignal, solution, outcome }) => {
|
|
44
|
+
const res = await fetch(`${config.serverUrl}/api/evolutions`, {
|
|
45
|
+
method: "POST",
|
|
46
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${config.apiKey}` },
|
|
47
|
+
body: JSON.stringify({ errorSignal, solution, outcome }),
|
|
48
|
+
});
|
|
49
|
+
const data = await res.json();
|
|
50
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
51
|
+
});
|
|
52
|
+
server.tool("record_outcome", "Record whether a previously applied fix worked", {
|
|
53
|
+
id: z.string().describe("Evolution ID"),
|
|
54
|
+
outcome: z.enum(["success", "failure"]).describe("Whether the fix worked"),
|
|
55
|
+
}, async ({ id, outcome }) => {
|
|
56
|
+
const res = await fetch(`${config.serverUrl}/api/evolutions/${id}/record-outcome`, {
|
|
57
|
+
method: "POST",
|
|
58
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${config.apiKey}` },
|
|
59
|
+
body: JSON.stringify({ outcome }),
|
|
60
|
+
});
|
|
61
|
+
const data = await res.json();
|
|
62
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
63
|
+
});
|
|
64
|
+
const transport = new StdioServerTransport();
|
|
65
|
+
await server.connect(transport);
|
|
66
|
+
console.error("MCP server running on stdio");
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
console.log(chalk.red(`\n MCP server error: ${err.message}`));
|
|
70
|
+
console.log(chalk.gray("\n Make sure @modelcontextprotocol/sdk and zod are installed in the CLI package."));
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { configManager } from "../config.js";
|
|
3
|
+
import { ensureRepoContext, setPulledFixes, setPulledMemories } from "../repo-state.js";
|
|
4
|
+
export async function pullCommandAction() {
|
|
5
|
+
const config = configManager.config;
|
|
6
|
+
if (!config) {
|
|
7
|
+
console.log(chalk.red("Not configured. Run: umbrella-context setup"));
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
try {
|
|
11
|
+
await ensureRepoContext(config);
|
|
12
|
+
const [memoriesRes, fixesRes] = await Promise.all([
|
|
13
|
+
fetch(`${config.serverUrl}/api/memories?limit=50`, {
|
|
14
|
+
headers: { Authorization: `Bearer ${config.apiKey}` },
|
|
15
|
+
}),
|
|
16
|
+
fetch(`${config.serverUrl}/api/evolutions?limit=50`, {
|
|
17
|
+
headers: { Authorization: `Bearer ${config.apiKey}` },
|
|
18
|
+
}),
|
|
19
|
+
]);
|
|
20
|
+
if (!memoriesRes.ok) {
|
|
21
|
+
const err = await memoriesRes.text();
|
|
22
|
+
console.log(chalk.red(`\n Pull failed: ${err || memoriesRes.statusText}`));
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const memoriesJson = await memoriesRes.json();
|
|
26
|
+
const fixesJson = fixesRes.ok ? await fixesRes.json() : { results: [] };
|
|
27
|
+
await setPulledMemories((memoriesJson.results ?? []).map((entry) => ({
|
|
28
|
+
id: entry.id,
|
|
29
|
+
content: entry.content,
|
|
30
|
+
tags: entry.tags ?? [],
|
|
31
|
+
source: entry.source ?? "server",
|
|
32
|
+
systemType: entry.systemType ?? "system1_knowledge",
|
|
33
|
+
createdAt: entry.createdAt ?? new Date().toISOString(),
|
|
34
|
+
})));
|
|
35
|
+
await setPulledFixes((fixesJson.results ?? []).map((entry) => ({
|
|
36
|
+
id: entry.id,
|
|
37
|
+
errorSignal: entry.errorSignal,
|
|
38
|
+
solution: entry.solution,
|
|
39
|
+
confidence: entry.confidence ?? 0,
|
|
40
|
+
timesApplied: entry.timesApplied ?? 0,
|
|
41
|
+
timesSucceeded: entry.timesSucceeded ?? 0,
|
|
42
|
+
createdAt: entry.createdAt ?? new Date().toISOString(),
|
|
43
|
+
lastApplied: entry.lastApplied ?? null,
|
|
44
|
+
})));
|
|
45
|
+
console.log(chalk.green(`\n Pulled ${memoriesJson.results?.length ?? 0} context entries from the server.`));
|
|
46
|
+
console.log(chalk.gray(` Company: ${config.companyName}`));
|
|
47
|
+
console.log(chalk.gray(` Space: ${config.projectName}`));
|
|
48
|
+
console.log(chalk.gray(` Known fixes cached: ${fixesJson.results?.length ?? 0}`));
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
console.log(chalk.red(`\n Error: ${err.message}`));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
export function pullCommand(cli) {
|
|
55
|
+
cli.command("pull", "Refresh the local .um snapshot from the server").action(async () => {
|
|
56
|
+
await pullCommandAction();
|
|
57
|
+
});
|
|
58
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { configManager } from "../config.js";
|
|
3
|
+
import { clearPendingMemories, ensureRepoContext, getPendingMemories, getPulledMemories, markPushCompleted, setPulledMemories, } from "../repo-state.js";
|
|
4
|
+
export async function pushCommandAction() {
|
|
5
|
+
const config = configManager.config;
|
|
6
|
+
if (!config) {
|
|
7
|
+
console.log(chalk.red("Not configured. Run: umbrella-context setup"));
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
try {
|
|
11
|
+
await ensureRepoContext(config);
|
|
12
|
+
const pending = await getPendingMemories();
|
|
13
|
+
if (pending.length === 0) {
|
|
14
|
+
console.log(chalk.yellow("\n Nothing to push. Your local .um folder is already in sync."));
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const synced = [];
|
|
18
|
+
for (const entry of pending) {
|
|
19
|
+
const res = await fetch(`${config.serverUrl}/api/memories`, {
|
|
20
|
+
method: "POST",
|
|
21
|
+
headers: {
|
|
22
|
+
"Content-Type": "application/json",
|
|
23
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
24
|
+
},
|
|
25
|
+
body: JSON.stringify({
|
|
26
|
+
content: entry.content,
|
|
27
|
+
tags: entry.tags,
|
|
28
|
+
source: entry.source,
|
|
29
|
+
systemType: entry.systemType,
|
|
30
|
+
}),
|
|
31
|
+
});
|
|
32
|
+
if (!res.ok) {
|
|
33
|
+
const err = await res.text();
|
|
34
|
+
console.log(chalk.red(`\n Push failed: ${err || res.statusText}`));
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
synced.push(await res.json());
|
|
38
|
+
}
|
|
39
|
+
const existingPulled = await getPulledMemories();
|
|
40
|
+
await setPulledMemories([
|
|
41
|
+
...synced.map((entry) => ({
|
|
42
|
+
id: entry.id,
|
|
43
|
+
content: entry.content,
|
|
44
|
+
tags: entry.tags ?? [],
|
|
45
|
+
source: entry.source ?? "cli",
|
|
46
|
+
systemType: entry.systemType ?? "system1_knowledge",
|
|
47
|
+
createdAt: entry.createdAt ?? new Date().toISOString(),
|
|
48
|
+
})),
|
|
49
|
+
...existingPulled,
|
|
50
|
+
]);
|
|
51
|
+
await clearPendingMemories();
|
|
52
|
+
await markPushCompleted();
|
|
53
|
+
console.log(chalk.green(`\n Pushed ${synced.length} local context entr${synced.length === 1 ? "y" : "ies"}.`));
|
|
54
|
+
console.log(chalk.gray(` Company: ${config.companyName}`));
|
|
55
|
+
console.log(chalk.gray(` Space: ${config.projectName}`));
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
console.log(chalk.red(`\n Error: ${err.message}`));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
export function pushCommand(cli) {
|
|
62
|
+
cli.command("push", "Push pending local .um context to the server").action(async () => {
|
|
63
|
+
await pushCommandAction();
|
|
64
|
+
});
|
|
65
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function recordCommand(cli: any): void;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { configManager } from "../config.js";
|
|
3
|
+
export function recordCommand(cli) {
|
|
4
|
+
cli.command("record <id> <outcome>", "Record whether a fix worked or not").action(async (id, outcome) => {
|
|
5
|
+
const config = configManager.config;
|
|
6
|
+
if (!config) {
|
|
7
|
+
console.log(chalk.red("Not configured. Run: umbrella-context setup"));
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
if (!["success", "failure"].includes(outcome)) {
|
|
11
|
+
console.log(chalk.red("Outcome must be 'success' or 'failure'"));
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
const res = await fetch(`${config.serverUrl}/api/evolutions/${id}/record-outcome`, {
|
|
16
|
+
method: "POST",
|
|
17
|
+
headers: {
|
|
18
|
+
"Content-Type": "application/json",
|
|
19
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
20
|
+
},
|
|
21
|
+
body: JSON.stringify({ outcome }),
|
|
22
|
+
});
|
|
23
|
+
if (!res.ok) {
|
|
24
|
+
const err = await res.json();
|
|
25
|
+
console.log(chalk.red(`\n Failed: ${err.error}`));
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const data = await res.json();
|
|
29
|
+
const pct = Math.round(data.confidence * 100);
|
|
30
|
+
console.log(chalk.green(`\n Recorded ${outcome}`));
|
|
31
|
+
console.log(` New confidence: ${pct}% (${data.timesSucceeded}/${data.timesApplied})`);
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
console.log(chalk.red(`\n Error: ${err.message}`));
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|