umbrella-context 0.1.2 → 0.1.32
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/bin/um.js +2 -0
- package/dist/adapters/byterover-context-runtime-store.d.ts +15 -0
- package/dist/adapters/byterover-context-runtime-store.js +57 -0
- package/dist/adapters/byterover-runtime-bridge.d.ts +218 -0
- package/dist/adapters/byterover-runtime-bridge.js +343 -0
- package/dist/adapters/byterover-transport-task-store.d.ts +13 -0
- package/dist/adapters/byterover-transport-task-store.js +50 -0
- package/dist/adapters/umbrella-onboarding.d.ts +27 -0
- package/dist/adapters/umbrella-onboarding.js +79 -0
- package/dist/adapters/umbrella-provider-runtime.d.ts +38 -0
- package/dist/adapters/umbrella-provider-runtime.js +199 -0
- package/dist/adapters/vendor-byterover.d.ts +4 -0
- package/dist/adapters/vendor-byterover.js +19 -0
- package/dist/commands/activity.d.ts +2 -0
- package/dist/commands/activity.js +82 -0
- package/dist/commands/bridge.d.ts +2 -0
- package/dist/commands/bridge.js +40 -0
- package/dist/commands/catalog.d.ts +34 -0
- package/dist/commands/catalog.js +234 -0
- package/dist/commands/connect.js +14 -14
- package/dist/commands/connectors.d.ts +24 -0
- package/dist/commands/connectors.js +626 -0
- package/dist/commands/curate.d.ts +1 -0
- package/dist/commands/curate.js +48 -3
- package/dist/commands/debug.d.ts +2 -0
- package/dist/commands/debug.js +55 -0
- package/dist/commands/fix.js +54 -0
- package/dist/commands/hub.d.ts +22 -0
- package/dist/commands/hub.js +487 -0
- package/dist/commands/interactive.d.ts +2 -0
- package/dist/commands/interactive.js +970 -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 +225 -0
- package/dist/commands/providers.d.ts +17 -0
- package/dist/commands/providers.js +379 -0
- package/dist/commands/pull.js +60 -1
- package/dist/commands/push.js +62 -2
- package/dist/commands/reset.d.ts +2 -0
- package/dist/commands/reset.js +35 -0
- package/dist/commands/restart.d.ts +2 -0
- package/dist/commands/restart.js +21 -0
- package/dist/commands/search.js +65 -1
- package/dist/commands/session.d.ts +2 -0
- package/dist/commands/session.js +241 -0
- package/dist/commands/setup.js +58 -56
- package/dist/commands/space.d.ts +12 -0
- package/dist/commands/space.js +138 -42
- package/dist/commands/status.d.ts +29 -0
- package/dist/commands/status.js +120 -19
- package/dist/commands/tasks.d.ts +2 -0
- package/dist/commands/tasks.js +95 -0
- package/dist/commands/transport.d.ts +2 -0
- package/dist/commands/transport.js +88 -0
- package/dist/commands/tree.d.ts +2 -0
- package/dist/commands/tree.js +98 -0
- package/dist/commands/tui.d.ts +2 -0
- package/dist/commands/tui.js +1273 -0
- package/dist/config.d.ts +23 -0
- package/dist/config.js +69 -0
- package/dist/index.js +41 -5
- package/dist/repo-state.d.ts +227 -1
- package/dist/repo-state.js +920 -4
- package/dist/umbrella.js +29 -5
- package/package.json +11 -3
|
@@ -0,0 +1,626 @@
|
|
|
1
|
+
import { promises as fs } from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { randomUUID } from "crypto";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import prompts from "prompts";
|
|
6
|
+
import { addPendingMemory, addConnectorRun, getRepoContext, getInstalledConnectors, getSessionState, recordSessionEvent, recordSessionConnector, setInstalledConnectors, setSessionPanel, writeConnectorAsset, writeConnectorAssetFiles, writeConnectorRunReport, } from "../repo-state.js";
|
|
7
|
+
const CONNECTOR_TEMPLATES = [
|
|
8
|
+
{
|
|
9
|
+
key: "codex-mcp",
|
|
10
|
+
name: "Codex MCP Connector",
|
|
11
|
+
type: "mcp",
|
|
12
|
+
description: "Connect IDE agents to Umbrella Context through the local MCP server.",
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
key: "github-review-rule",
|
|
16
|
+
name: "GitHub Review Rule",
|
|
17
|
+
type: "rule",
|
|
18
|
+
description: "Push review takeaways and known fixes back into Context after PR work completes.",
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
key: "slack-growth-hook",
|
|
22
|
+
name: "Slack Growth Hook",
|
|
23
|
+
type: "hook",
|
|
24
|
+
description: "Capture useful campaign learnings from Slack threads into the current company space.",
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
key: "seo-skill-bridge",
|
|
28
|
+
name: "SEO Skill Bridge",
|
|
29
|
+
type: "skill",
|
|
30
|
+
description: "Attach OpenSEO-backed operator skills to the current repo workflow.",
|
|
31
|
+
},
|
|
32
|
+
];
|
|
33
|
+
export function listConnectorTemplates() {
|
|
34
|
+
return [...CONNECTOR_TEMPLATES];
|
|
35
|
+
}
|
|
36
|
+
function describeConnectorRun(type, name) {
|
|
37
|
+
if (type === "mcp") {
|
|
38
|
+
return `${name} validated the local MCP bridge and confirmed this repo can expose Context tools to IDE agents.`;
|
|
39
|
+
}
|
|
40
|
+
if (type === "rule") {
|
|
41
|
+
return `${name} checked the repo workflow and prepared a rule for saving review takeaways back into Context.`;
|
|
42
|
+
}
|
|
43
|
+
if (type === "hook") {
|
|
44
|
+
return `${name} captured a reusable signal from an external collaboration source and staged it for Context sync.`;
|
|
45
|
+
}
|
|
46
|
+
return `${name} attached a reusable skill bridge to this repo so future runs can reuse the same workflow.`;
|
|
47
|
+
}
|
|
48
|
+
function buildConnectorAsset(entry) {
|
|
49
|
+
return {
|
|
50
|
+
name: entry.name,
|
|
51
|
+
type: entry.type,
|
|
52
|
+
source: entry.key,
|
|
53
|
+
enabled: true,
|
|
54
|
+
summary: entry.description,
|
|
55
|
+
steps: [
|
|
56
|
+
"Review the connector purpose before enabling it in a workflow.",
|
|
57
|
+
"Use umbrella-context status to confirm the repo ecosystem state.",
|
|
58
|
+
"Run umbrella-context connectors run <key> to log a first execution.",
|
|
59
|
+
],
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
async function runConnectorCheck(entry, cwd = process.cwd()) {
|
|
63
|
+
const { repoRoot } = await getRepoContext(cwd);
|
|
64
|
+
if (entry.source === "codex-mcp") {
|
|
65
|
+
const filePath = path.join(repoRoot, ".mcp.json");
|
|
66
|
+
try {
|
|
67
|
+
const parsed = JSON.parse(await fs.readFile(filePath, "utf8"));
|
|
68
|
+
const configured = Boolean(parsed?.mcpServers?.["umbrella-context"]);
|
|
69
|
+
return {
|
|
70
|
+
status: configured ? "success" : "failure",
|
|
71
|
+
summary: configured
|
|
72
|
+
? `${entry.name} verified that .mcp.json exposes the umbrella-context MCP server for this repo.`
|
|
73
|
+
: `${entry.name} could not find an umbrella-context MCP entry in .mcp.json.`,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return {
|
|
78
|
+
status: "failure",
|
|
79
|
+
summary: `${entry.name} could not read the repo .mcp.json file.`,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (entry.source === "github-review-rule") {
|
|
84
|
+
return {
|
|
85
|
+
status: "success",
|
|
86
|
+
summary: `${entry.name} prepared a repo-local review rule pack so PR lessons can be curated back into Context.`,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
if (entry.source === "slack-growth-hook") {
|
|
90
|
+
return {
|
|
91
|
+
status: "success",
|
|
92
|
+
summary: `${entry.name} prepared a Slack capture template for turning campaign conversations into shared Context notes.`,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
status: "success",
|
|
97
|
+
summary: `${entry.name} attached a reusable skill bridge to this repo so future runs can reuse the same workflow.`,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
async function applyConnectorRunEffects(entry, cwd = process.cwd()) {
|
|
101
|
+
if (entry.source === "github-review-rule") {
|
|
102
|
+
const now = new Date().toISOString();
|
|
103
|
+
const filePaths = await writeConnectorAssetFiles(entry.source, [
|
|
104
|
+
{
|
|
105
|
+
name: "review-capture-template.md",
|
|
106
|
+
content: [
|
|
107
|
+
"# Review Capture Template",
|
|
108
|
+
"",
|
|
109
|
+
"Use this after a pull request or code review to turn what happened into reusable Context.",
|
|
110
|
+
"",
|
|
111
|
+
"```text",
|
|
112
|
+
"PR or Issue: ...",
|
|
113
|
+
"Bug or risk: ...",
|
|
114
|
+
"What changed: ...",
|
|
115
|
+
"Known fix: ...",
|
|
116
|
+
"What to remember next time: ...",
|
|
117
|
+
"```",
|
|
118
|
+
"",
|
|
119
|
+
`Last refreshed: ${now}`,
|
|
120
|
+
"",
|
|
121
|
+
].join("\n"),
|
|
122
|
+
},
|
|
123
|
+
], cwd);
|
|
124
|
+
return {
|
|
125
|
+
filePaths,
|
|
126
|
+
stagedMemory: null,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
if (entry.source === "slack-growth-hook") {
|
|
130
|
+
const staged = await addPendingMemory({
|
|
131
|
+
content: "Slack growth hook captured a reusable campaign note. Replace this placeholder with the real signal before pushing.",
|
|
132
|
+
tags: ["slack", "growth", "draft"],
|
|
133
|
+
systemType: "connector_capture",
|
|
134
|
+
source: "slack-growth-hook",
|
|
135
|
+
}, cwd);
|
|
136
|
+
const filePaths = await writeConnectorAssetFiles(entry.source, [
|
|
137
|
+
{
|
|
138
|
+
name: "last-capture.md",
|
|
139
|
+
content: [
|
|
140
|
+
"# Slack Capture Draft",
|
|
141
|
+
"",
|
|
142
|
+
"A new local Context draft was staged from the Slack growth hook.",
|
|
143
|
+
"",
|
|
144
|
+
`Draft ID: ${staged.id}`,
|
|
145
|
+
"",
|
|
146
|
+
"Edit the local draft with:",
|
|
147
|
+
"",
|
|
148
|
+
"```bash",
|
|
149
|
+
"umbrella-context status",
|
|
150
|
+
"umbrella-context push",
|
|
151
|
+
"```",
|
|
152
|
+
"",
|
|
153
|
+
].join("\n"),
|
|
154
|
+
},
|
|
155
|
+
], cwd);
|
|
156
|
+
return {
|
|
157
|
+
filePaths,
|
|
158
|
+
stagedMemory: staged.id,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
if (entry.source === "seo-skill-bridge") {
|
|
162
|
+
const filePaths = await writeConnectorAssetFiles(entry.source, [
|
|
163
|
+
{
|
|
164
|
+
name: "operator-brief.md",
|
|
165
|
+
content: [
|
|
166
|
+
"# SEO Operator Brief",
|
|
167
|
+
"",
|
|
168
|
+
"This bridge prepares the repo for SEO-focused Context work.",
|
|
169
|
+
"",
|
|
170
|
+
"Suggested next steps:",
|
|
171
|
+
"- Run `umbrella-context query \"What do we already know about this domain?\"`",
|
|
172
|
+
"- Use `$umbrella-seo-operator` for OpenSEO-backed workflows",
|
|
173
|
+
"- Save the best takeaways with `umbrella-context curate`",
|
|
174
|
+
"",
|
|
175
|
+
].join("\n"),
|
|
176
|
+
},
|
|
177
|
+
], cwd);
|
|
178
|
+
return {
|
|
179
|
+
filePaths,
|
|
180
|
+
stagedMemory: null,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
return {
|
|
184
|
+
filePaths: [],
|
|
185
|
+
stagedMemory: null,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function buildConnectorFiles(entry) {
|
|
189
|
+
if (entry.key === "codex-mcp") {
|
|
190
|
+
return [
|
|
191
|
+
{
|
|
192
|
+
name: "README.md",
|
|
193
|
+
content: [
|
|
194
|
+
"# Codex MCP Connector",
|
|
195
|
+
"",
|
|
196
|
+
"This connector makes the current repo expose `umbrella-context mcp` to IDE agents.",
|
|
197
|
+
"",
|
|
198
|
+
"## What it should change",
|
|
199
|
+
"",
|
|
200
|
+
"- Add an `umbrella-context` server entry to the repo `.mcp.json` file",
|
|
201
|
+
"- Keep the local `.um` folder as the repo-native context store",
|
|
202
|
+
"",
|
|
203
|
+
"## Verify it",
|
|
204
|
+
"",
|
|
205
|
+
"```bash",
|
|
206
|
+
"umbrella-context connectors run codex-mcp",
|
|
207
|
+
"umbrella-context status",
|
|
208
|
+
"```",
|
|
209
|
+
"",
|
|
210
|
+
].join("\n"),
|
|
211
|
+
},
|
|
212
|
+
];
|
|
213
|
+
}
|
|
214
|
+
if (entry.key === "github-review-rule") {
|
|
215
|
+
return [
|
|
216
|
+
{
|
|
217
|
+
name: "github-review-rule.md",
|
|
218
|
+
content: [
|
|
219
|
+
"# GitHub Review Rule",
|
|
220
|
+
"",
|
|
221
|
+
"After review work finishes, save the useful outcome back into Context.",
|
|
222
|
+
"",
|
|
223
|
+
"## Checklist",
|
|
224
|
+
"",
|
|
225
|
+
"1. Capture the bug, regression, or lesson.",
|
|
226
|
+
"2. Curate it into Context.",
|
|
227
|
+
"3. Record a known fix if one exists.",
|
|
228
|
+
"",
|
|
229
|
+
].join("\n"),
|
|
230
|
+
},
|
|
231
|
+
];
|
|
232
|
+
}
|
|
233
|
+
if (entry.key === "slack-growth-hook") {
|
|
234
|
+
return [
|
|
235
|
+
{
|
|
236
|
+
name: "slack-capture-template.md",
|
|
237
|
+
content: [
|
|
238
|
+
"# Slack Growth Capture Template",
|
|
239
|
+
"",
|
|
240
|
+
"Use this when Slack reveals a reusable campaign lesson.",
|
|
241
|
+
"",
|
|
242
|
+
"```text",
|
|
243
|
+
"Signal from Slack: ...",
|
|
244
|
+
"Why it matters: ...",
|
|
245
|
+
"Next action: ...",
|
|
246
|
+
"```",
|
|
247
|
+
"",
|
|
248
|
+
].join("\n"),
|
|
249
|
+
},
|
|
250
|
+
];
|
|
251
|
+
}
|
|
252
|
+
if (entry.key === "seo-skill-bridge") {
|
|
253
|
+
return [
|
|
254
|
+
{
|
|
255
|
+
name: "seo-skill-bridge.md",
|
|
256
|
+
content: [
|
|
257
|
+
"# SEO Skill Bridge",
|
|
258
|
+
"",
|
|
259
|
+
"Use the OpenSEO operator flow when this repo needs domain, keyword, or audit work.",
|
|
260
|
+
"",
|
|
261
|
+
"Suggested skill:",
|
|
262
|
+
"- `$umbrella-seo-operator`",
|
|
263
|
+
"",
|
|
264
|
+
].join("\n"),
|
|
265
|
+
},
|
|
266
|
+
];
|
|
267
|
+
}
|
|
268
|
+
return [];
|
|
269
|
+
}
|
|
270
|
+
async function installUmbrellaMcpIntoRepo(cwd = process.cwd()) {
|
|
271
|
+
const { repoRoot } = await getRepoContext(cwd);
|
|
272
|
+
const filePath = path.join(repoRoot, ".mcp.json");
|
|
273
|
+
let current = { mcpServers: {} };
|
|
274
|
+
try {
|
|
275
|
+
current = JSON.parse(await fs.readFile(filePath, "utf8"));
|
|
276
|
+
}
|
|
277
|
+
catch {
|
|
278
|
+
current = { mcpServers: {} };
|
|
279
|
+
}
|
|
280
|
+
const next = {
|
|
281
|
+
...current,
|
|
282
|
+
mcpServers: {
|
|
283
|
+
...(current.mcpServers ?? {}),
|
|
284
|
+
"umbrella-context": {
|
|
285
|
+
description: "Repo-local Context bridge for query, curate, push, pull, and known-fix workflows.",
|
|
286
|
+
command: "umbrella-context",
|
|
287
|
+
args: ["mcp"],
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
};
|
|
291
|
+
await fs.writeFile(filePath, `${JSON.stringify(next, null, 2)}\n`, "utf8");
|
|
292
|
+
return filePath;
|
|
293
|
+
}
|
|
294
|
+
async function getConnectorOutputSummary(source, cwd = process.cwd()) {
|
|
295
|
+
const { repoRoot } = await getRepoContext(cwd);
|
|
296
|
+
const connectorDir = path.join(repoRoot, ".um", "connectors", source);
|
|
297
|
+
let files = [];
|
|
298
|
+
try {
|
|
299
|
+
files = (await fs.readdir(connectorDir)).map((name) => path.join(connectorDir, name));
|
|
300
|
+
}
|
|
301
|
+
catch {
|
|
302
|
+
files = [];
|
|
303
|
+
}
|
|
304
|
+
const runs = await import("../repo-state.js").then((mod) => mod.getConnectorRuns(cwd));
|
|
305
|
+
const latestRun = runs.find((entry) => entry.connectorName && entry.connectorName.toLowerCase().includes(source.replace(/-/g, " "))) ?? runs[0] ?? null;
|
|
306
|
+
return { files, latestRun };
|
|
307
|
+
}
|
|
308
|
+
export async function connectorsCommandAction(action, target) {
|
|
309
|
+
const normalized = action.toLowerCase();
|
|
310
|
+
await setSessionPanel("connectors", target ?? normalized);
|
|
311
|
+
if (normalized === "list") {
|
|
312
|
+
await setSessionPanel("connectors", "list");
|
|
313
|
+
const installed = await getInstalledConnectors();
|
|
314
|
+
console.log(chalk.bold("\n Repo Connectors\n"));
|
|
315
|
+
if (installed.length === 0) {
|
|
316
|
+
console.log(chalk.yellow(" No connectors installed in this repo yet."));
|
|
317
|
+
console.log(chalk.gray(" Run: umbrella-context connectors install"));
|
|
318
|
+
return { action: "list", changed: false };
|
|
319
|
+
}
|
|
320
|
+
installed.forEach((entry, index) => {
|
|
321
|
+
console.log(` ${index + 1}. ${entry.name} [${entry.type}]`);
|
|
322
|
+
console.log(chalk.gray(` ${entry.description}`));
|
|
323
|
+
console.log(chalk.gray(` Source: ${entry.source}`));
|
|
324
|
+
});
|
|
325
|
+
return { action: "list", changed: false };
|
|
326
|
+
}
|
|
327
|
+
if (normalized === "inspect") {
|
|
328
|
+
const installed = await getInstalledConnectors();
|
|
329
|
+
if (installed.length === 0) {
|
|
330
|
+
console.log(chalk.yellow("\n No connectors installed in this repo yet."));
|
|
331
|
+
return { action: "inspect", changed: false };
|
|
332
|
+
}
|
|
333
|
+
let selected = target
|
|
334
|
+
? installed.find((entry) => entry.id === target || entry.source === target || entry.name.toLowerCase() === target.toLowerCase())
|
|
335
|
+
: undefined;
|
|
336
|
+
if (!selected) {
|
|
337
|
+
const answer = await prompts({
|
|
338
|
+
type: "select",
|
|
339
|
+
name: "value",
|
|
340
|
+
message: "Choose a connector to inspect",
|
|
341
|
+
choices: installed.map((entry) => ({
|
|
342
|
+
title: `${entry.name} (${entry.type})`,
|
|
343
|
+
value: entry.id,
|
|
344
|
+
})),
|
|
345
|
+
});
|
|
346
|
+
selected = installed.find((entry) => entry.id === answer.value);
|
|
347
|
+
}
|
|
348
|
+
if (!selected) {
|
|
349
|
+
console.log(chalk.yellow("\n No connector selected."));
|
|
350
|
+
return { action: "inspect", changed: false };
|
|
351
|
+
}
|
|
352
|
+
await setSessionPanel("connectors", selected.source);
|
|
353
|
+
await recordSessionConnector(selected.source);
|
|
354
|
+
await recordSessionEvent({
|
|
355
|
+
kind: "connector",
|
|
356
|
+
title: `Inspected connector ${selected.name}`,
|
|
357
|
+
detail: `Viewed the repo wiring and next steps for ${selected.source}.`,
|
|
358
|
+
panel: "connectors",
|
|
359
|
+
focus: selected.source,
|
|
360
|
+
status: "info",
|
|
361
|
+
});
|
|
362
|
+
console.log(chalk.bold(`\n ${selected.name}\n`));
|
|
363
|
+
console.log(` Type: ${selected.type}`);
|
|
364
|
+
console.log(` Source: ${selected.source}`);
|
|
365
|
+
console.log(` Installed: ${new Date(selected.installedAt).toLocaleString()}`);
|
|
366
|
+
console.log("");
|
|
367
|
+
console.log(chalk.cyan(" Recommended Next Step"));
|
|
368
|
+
console.log(` - Run umbrella-context connectors run ${selected.source}`);
|
|
369
|
+
if (selected.source === "codex-mcp") {
|
|
370
|
+
console.log(" - Then run umbrella-context status to verify MCP wiring.");
|
|
371
|
+
}
|
|
372
|
+
else if (selected.source === "slack-growth-hook") {
|
|
373
|
+
console.log(' - Then inspect ".um/pending-memories.json" or run umbrella-context push.');
|
|
374
|
+
}
|
|
375
|
+
return {
|
|
376
|
+
action: "inspect",
|
|
377
|
+
changed: false,
|
|
378
|
+
connectorName: selected.name,
|
|
379
|
+
inspectedConnector: selected.source,
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
if (normalized === "recent") {
|
|
383
|
+
const session = await getSessionState();
|
|
384
|
+
const recent = session?.recentConnectorSources?.[0];
|
|
385
|
+
if (!recent) {
|
|
386
|
+
console.log(chalk.yellow("\n No recent connector in this session yet."));
|
|
387
|
+
return { action: "inspect", changed: false };
|
|
388
|
+
}
|
|
389
|
+
return connectorsCommandAction("inspect", recent);
|
|
390
|
+
}
|
|
391
|
+
if (normalized === "outputs") {
|
|
392
|
+
const installed = await getInstalledConnectors();
|
|
393
|
+
let selected = target
|
|
394
|
+
? installed.find((entry) => entry.id === target || entry.source === target || entry.name.toLowerCase() === target.toLowerCase())
|
|
395
|
+
: installed[0];
|
|
396
|
+
if (!selected) {
|
|
397
|
+
console.log(chalk.yellow("\n No connectors installed in this repo yet."));
|
|
398
|
+
return { action: "outputs", changed: false };
|
|
399
|
+
}
|
|
400
|
+
const outputs = await getConnectorOutputSummary(selected.source);
|
|
401
|
+
await setSessionPanel("connectors", selected.source);
|
|
402
|
+
await recordSessionConnector(selected.source);
|
|
403
|
+
await recordSessionEvent({
|
|
404
|
+
kind: "connector",
|
|
405
|
+
title: `Viewed outputs for ${selected.name}`,
|
|
406
|
+
detail: `${outputs.files.length} connector file${outputs.files.length === 1 ? "" : "s"} currently exist for this repo.`,
|
|
407
|
+
panel: "connectors",
|
|
408
|
+
focus: selected.source,
|
|
409
|
+
status: "info",
|
|
410
|
+
});
|
|
411
|
+
console.log(chalk.bold(`\n Outputs for ${selected.name}\n`));
|
|
412
|
+
if (outputs.files.length === 0) {
|
|
413
|
+
console.log(chalk.yellow(" No connector files have been written yet."));
|
|
414
|
+
}
|
|
415
|
+
else {
|
|
416
|
+
outputs.files.forEach((filePath, index) => {
|
|
417
|
+
console.log(` ${index + 1}. ${filePath}`);
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
console.log("");
|
|
421
|
+
console.log(chalk.cyan(" Latest Run"));
|
|
422
|
+
if (!outputs.latestRun) {
|
|
423
|
+
console.log(" No connector run has been recorded yet.");
|
|
424
|
+
}
|
|
425
|
+
else {
|
|
426
|
+
console.log(` ${outputs.latestRun.connectorName} (${outputs.latestRun.status})`);
|
|
427
|
+
console.log(chalk.gray(` ${outputs.latestRun.summary}`));
|
|
428
|
+
console.log(chalk.gray(` ${new Date(outputs.latestRun.ranAt).toLocaleString()}`));
|
|
429
|
+
}
|
|
430
|
+
return {
|
|
431
|
+
action: "outputs",
|
|
432
|
+
changed: false,
|
|
433
|
+
connectorName: selected.name,
|
|
434
|
+
inspectedConnector: selected.source,
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
if (normalized === "install") {
|
|
438
|
+
const installed = await getInstalledConnectors();
|
|
439
|
+
let selected = target ? CONNECTOR_TEMPLATES.find((entry) => entry.key === target) : undefined;
|
|
440
|
+
if (!selected) {
|
|
441
|
+
const answer = await prompts({
|
|
442
|
+
type: "select",
|
|
443
|
+
name: "value",
|
|
444
|
+
message: "Choose a connector to install into this repo",
|
|
445
|
+
choices: CONNECTOR_TEMPLATES.map((entry) => ({
|
|
446
|
+
title: `${entry.name} (${entry.type})`,
|
|
447
|
+
description: entry.description,
|
|
448
|
+
value: entry.key,
|
|
449
|
+
})),
|
|
450
|
+
});
|
|
451
|
+
selected = CONNECTOR_TEMPLATES.find((entry) => entry.key === answer.value);
|
|
452
|
+
}
|
|
453
|
+
if (!selected) {
|
|
454
|
+
console.log(chalk.yellow("\n No connector selected."));
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
await setSessionPanel("connectors", selected.key);
|
|
458
|
+
await recordSessionConnector(selected.key);
|
|
459
|
+
const existing = installed.find((entry) => entry.source === selected.key);
|
|
460
|
+
if (existing) {
|
|
461
|
+
const filePaths = await writeConnectorAssetFiles(selected.key, buildConnectorFiles(selected));
|
|
462
|
+
let repoConfigPath = null;
|
|
463
|
+
if (selected.key === "codex-mcp") {
|
|
464
|
+
repoConfigPath = await installUmbrellaMcpIntoRepo();
|
|
465
|
+
}
|
|
466
|
+
await recordSessionEvent({
|
|
467
|
+
kind: "connector",
|
|
468
|
+
title: `${selected.name} was already installed`,
|
|
469
|
+
detail: "The existing install stayed in place, but the repo wiring and connector files were refreshed.",
|
|
470
|
+
panel: "connectors",
|
|
471
|
+
focus: selected.key,
|
|
472
|
+
status: "info",
|
|
473
|
+
});
|
|
474
|
+
console.log(chalk.yellow(`\n ${selected.name} is already installed in this repo.`));
|
|
475
|
+
if (filePaths.length > 0) {
|
|
476
|
+
filePaths.forEach((filePath) => {
|
|
477
|
+
console.log(chalk.gray(` Refreshed connector file ${filePath}`));
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
if (repoConfigPath) {
|
|
481
|
+
console.log(chalk.gray(` Refreshed repo MCP config at ${repoConfigPath}`));
|
|
482
|
+
}
|
|
483
|
+
console.log(chalk.gray(" Existing installs are kept, but repo wiring was refreshed."));
|
|
484
|
+
return {
|
|
485
|
+
action: "install",
|
|
486
|
+
changed: false,
|
|
487
|
+
connectorName: selected.name,
|
|
488
|
+
filePaths,
|
|
489
|
+
repoConfigPath,
|
|
490
|
+
inspectedConnector: selected.key,
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
await setInstalledConnectors([
|
|
494
|
+
...installed,
|
|
495
|
+
{
|
|
496
|
+
id: randomUUID(),
|
|
497
|
+
name: selected.name,
|
|
498
|
+
type: selected.type,
|
|
499
|
+
description: selected.description,
|
|
500
|
+
installedAt: new Date().toISOString(),
|
|
501
|
+
source: selected.key,
|
|
502
|
+
},
|
|
503
|
+
]);
|
|
504
|
+
const assetPath = await writeConnectorAsset(selected.key, buildConnectorAsset(selected));
|
|
505
|
+
const filePaths = await writeConnectorAssetFiles(selected.key, buildConnectorFiles(selected));
|
|
506
|
+
let repoConfigPath = null;
|
|
507
|
+
if (selected.key === "codex-mcp") {
|
|
508
|
+
repoConfigPath = await installUmbrellaMcpIntoRepo();
|
|
509
|
+
}
|
|
510
|
+
await recordSessionEvent({
|
|
511
|
+
kind: "connector",
|
|
512
|
+
title: `Installed connector ${selected.name}`,
|
|
513
|
+
detail: `${filePaths.length} repo file${filePaths.length === 1 ? "" : "s"} and connector metadata were added or refreshed.`,
|
|
514
|
+
panel: "connectors",
|
|
515
|
+
focus: selected.key,
|
|
516
|
+
status: "success",
|
|
517
|
+
});
|
|
518
|
+
console.log(chalk.green(`\n Installed ${selected.name} into this repo.`));
|
|
519
|
+
console.log(chalk.gray(` Asset written to ${assetPath}`));
|
|
520
|
+
filePaths.forEach((filePath) => {
|
|
521
|
+
console.log(chalk.gray(` Added connector file ${filePath}`));
|
|
522
|
+
});
|
|
523
|
+
if (repoConfigPath) {
|
|
524
|
+
console.log(chalk.gray(` Updated repo MCP config at ${repoConfigPath}`));
|
|
525
|
+
}
|
|
526
|
+
return {
|
|
527
|
+
action: "install",
|
|
528
|
+
changed: true,
|
|
529
|
+
connectorName: selected.name,
|
|
530
|
+
assetPath,
|
|
531
|
+
filePaths,
|
|
532
|
+
repoConfigPath,
|
|
533
|
+
inspectedConnector: selected.key,
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
if (normalized === "run") {
|
|
537
|
+
const installed = await getInstalledConnectors();
|
|
538
|
+
if (installed.length === 0) {
|
|
539
|
+
console.log(chalk.yellow("\n No connectors installed in this repo yet."));
|
|
540
|
+
console.log(chalk.gray(" Run: umbrella-context connectors install"));
|
|
541
|
+
return { action: "run", changed: false };
|
|
542
|
+
}
|
|
543
|
+
let selected = target
|
|
544
|
+
? installed.find((entry) => entry.id === target || entry.source === target || entry.name.toLowerCase() === target.toLowerCase())
|
|
545
|
+
: undefined;
|
|
546
|
+
if (!selected) {
|
|
547
|
+
const answer = await prompts({
|
|
548
|
+
type: "select",
|
|
549
|
+
name: "value",
|
|
550
|
+
message: "Choose a connector to run",
|
|
551
|
+
choices: installed.map((entry) => ({
|
|
552
|
+
title: `${entry.name} (${entry.type})`,
|
|
553
|
+
description: entry.description,
|
|
554
|
+
value: entry.id,
|
|
555
|
+
})),
|
|
556
|
+
});
|
|
557
|
+
selected = installed.find((entry) => entry.id === answer.value);
|
|
558
|
+
}
|
|
559
|
+
if (!selected) {
|
|
560
|
+
console.log(chalk.yellow("\n No connector selected."));
|
|
561
|
+
return { action: "run", changed: false };
|
|
562
|
+
}
|
|
563
|
+
await setSessionPanel("connectors", selected.source);
|
|
564
|
+
await recordSessionConnector(selected.source);
|
|
565
|
+
const execution = await runConnectorCheck({
|
|
566
|
+
source: selected.source,
|
|
567
|
+
name: selected.name,
|
|
568
|
+
type: selected.type,
|
|
569
|
+
});
|
|
570
|
+
const effects = execution.status === "success"
|
|
571
|
+
? await applyConnectorRunEffects({
|
|
572
|
+
source: selected.source,
|
|
573
|
+
name: selected.name,
|
|
574
|
+
type: selected.type,
|
|
575
|
+
})
|
|
576
|
+
: { filePaths: [], stagedMemory: null };
|
|
577
|
+
const summary = execution.summary || describeConnectorRun(selected.type, selected.name);
|
|
578
|
+
const run = await addConnectorRun({
|
|
579
|
+
connectorId: selected.id,
|
|
580
|
+
connectorName: selected.name,
|
|
581
|
+
status: execution.status,
|
|
582
|
+
summary,
|
|
583
|
+
});
|
|
584
|
+
const reportPath = await writeConnectorRunReport(run);
|
|
585
|
+
await recordSessionEvent({
|
|
586
|
+
kind: "connector",
|
|
587
|
+
title: `Ran connector ${selected.name}`,
|
|
588
|
+
detail: summary,
|
|
589
|
+
panel: "connectors",
|
|
590
|
+
focus: selected.source,
|
|
591
|
+
status: execution.status,
|
|
592
|
+
});
|
|
593
|
+
if (execution.status === "success") {
|
|
594
|
+
console.log(chalk.green(`\n Ran ${selected.name}.`));
|
|
595
|
+
}
|
|
596
|
+
else {
|
|
597
|
+
console.log(chalk.red(`\n ${selected.name} failed its repo check.`));
|
|
598
|
+
}
|
|
599
|
+
console.log(chalk.gray(` ${summary}`));
|
|
600
|
+
effects.filePaths.forEach((filePath) => {
|
|
601
|
+
console.log(chalk.gray(` Wrote connector output ${filePath}`));
|
|
602
|
+
});
|
|
603
|
+
if (effects.stagedMemory) {
|
|
604
|
+
console.log(chalk.gray(` Staged local Context draft ${effects.stagedMemory} in .um pending memories.`));
|
|
605
|
+
}
|
|
606
|
+
console.log(chalk.gray(` Saved in .um as connector run ${run.id}.`));
|
|
607
|
+
console.log(chalk.gray(` Report written to ${reportPath}`));
|
|
608
|
+
return {
|
|
609
|
+
action: "run",
|
|
610
|
+
changed: true,
|
|
611
|
+
connectorName: selected.name,
|
|
612
|
+
status: execution.status,
|
|
613
|
+
summary,
|
|
614
|
+
filePaths: effects.filePaths,
|
|
615
|
+
stagedMemoryId: effects.stagedMemory,
|
|
616
|
+
reportPath,
|
|
617
|
+
inspectedConnector: selected.source,
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
console.log(chalk.red("Use: connectors list | connectors inspect [key|name|id] | connectors recent | connectors outputs [key|name|id] | connectors install [key] | connectors run [key|name|id]"));
|
|
621
|
+
}
|
|
622
|
+
export function connectorsCommand(cli) {
|
|
623
|
+
cli.command("connectors <action> [target]", "Manage repo connectors for rules, hooks, MCP, and skills").action(async (action, target) => {
|
|
624
|
+
await connectorsCommandAction(action, target);
|
|
625
|
+
});
|
|
626
|
+
}
|