umbrella-context 0.1.37 → 0.1.39

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.
@@ -1,9 +1,70 @@
1
1
  import chalk from "chalk";
2
2
  import { configManager } from "../config.js";
3
3
  import { addTaskReasoningContent, addTaskToolCall, completeTask, createTask, ensureRepoContext, recordSessionEvent, setPulledFixes, setPulledMemories, setSessionPanel, } from "../repo-state.js";
4
- export async function pullCommandAction() {
4
+ function printJson(payload) {
5
+ console.log(JSON.stringify(payload, null, 2));
6
+ }
7
+ function buildPullPreview(memories, fixes) {
8
+ const categories = {};
9
+ const accessLevels = {};
10
+ const tagCounts = new Map();
11
+ for (const entry of memories) {
12
+ const category = entry.category?.trim() || "uncategorized";
13
+ categories[category] = (categories[category] ?? 0) + 1;
14
+ const accessLevel = entry.accessLevel?.trim() || "space";
15
+ accessLevels[accessLevel] = (accessLevels[accessLevel] ?? 0) + 1;
16
+ for (const tag of entry.tags ?? []) {
17
+ const normalized = String(tag).trim().toLowerCase();
18
+ if (!normalized)
19
+ continue;
20
+ tagCounts.set(normalized, (tagCounts.get(normalized) ?? 0) + 1);
21
+ }
22
+ }
23
+ return {
24
+ remoteMemories: memories.length,
25
+ remoteFixes: fixes.length,
26
+ categories,
27
+ accessLevels,
28
+ topTags: [...tagCounts.entries()]
29
+ .sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
30
+ .slice(0, 5)
31
+ .map(([tag, count]) => ({ tag, count })),
32
+ };
33
+ }
34
+ function printPullPreviewText(preview, companyName, spaceName) {
35
+ console.log(chalk.bold(`\n Pull preview for ${companyName} / ${spaceName}`));
36
+ console.log(chalk.gray(` Shared memories: ${preview.remoteMemories}`));
37
+ console.log(chalk.gray(` Known fixes: ${preview.remoteFixes}`));
38
+ const accessLevels = Object.entries(preview.accessLevels);
39
+ if (accessLevels.length > 0) {
40
+ console.log(chalk.cyan("\n Access levels:"));
41
+ for (const [accessLevel, count] of accessLevels.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))) {
42
+ console.log(chalk.gray(` - ${accessLevel}: ${count}`));
43
+ }
44
+ }
45
+ const categories = Object.entries(preview.categories);
46
+ if (categories.length > 0) {
47
+ console.log(chalk.cyan("\n Categories:"));
48
+ for (const [category, count] of categories.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))) {
49
+ console.log(chalk.gray(` - ${category}: ${count}`));
50
+ }
51
+ }
52
+ if (preview.topTags.length > 0) {
53
+ console.log(chalk.cyan("\n Top tags:"));
54
+ for (const entry of preview.topTags) {
55
+ console.log(chalk.gray(` - ${entry.tag}: ${entry.count}`));
56
+ }
57
+ }
58
+ }
59
+ export async function pullCommandAction(opts = {}) {
5
60
  const config = configManager.config;
61
+ const format = opts.format === "json" ? "json" : "text";
62
+ const dryRun = Boolean(opts.dryRun);
6
63
  if (!config) {
64
+ if (format === "json") {
65
+ printJson({ ok: false, error: "Not configured. Run: umbrella-context setup" });
66
+ return;
67
+ }
7
68
  console.log(chalk.red("Not configured. Run: umbrella-context setup"));
8
69
  return;
9
70
  }
@@ -46,11 +107,16 @@ export async function pullCommandAction() {
46
107
  error: err || memoriesRes.statusText,
47
108
  });
48
109
  await completeTask(task.id, "failed", err || memoriesRes.statusText);
110
+ if (format === "json") {
111
+ printJson({ ok: false, action: "pull", error: err || memoriesRes.statusText });
112
+ return;
113
+ }
49
114
  console.log(chalk.red(`\n Pull failed: ${err || memoriesRes.statusText}`));
50
115
  return;
51
116
  }
52
117
  const memoriesJson = await memoriesRes.json();
53
118
  const fixesJson = fixesRes.ok ? await fixesRes.json() : { results: [] };
119
+ const preview = buildPullPreview(memoriesJson.results ?? [], fixesJson.results ?? []);
54
120
  await addTaskToolCall(task.id, {
55
121
  toolName: "server.memory.list",
56
122
  status: "completed",
@@ -65,10 +131,38 @@ export async function pullCommandAction() {
65
131
  error: fixesRes.ok ? undefined : fixesRes.statusText,
66
132
  });
67
133
  await addTaskReasoningContent(task.id, `Pulled ${memoriesJson.results?.length ?? 0} context entr${(memoriesJson.results?.length ?? 0) === 1 ? "y" : "ies"} and ${fixesJson.results?.length ?? 0} known fix${(fixesJson.results?.length ?? 0) === 1 ? "" : "es"}.`);
134
+ if (dryRun) {
135
+ await completeTask(task.id, "completed", "Previewed the shared server snapshot without changing the local .um files.");
136
+ await recordSessionEvent({
137
+ kind: "pull",
138
+ title: "Previewed shared Context snapshot",
139
+ detail: `Looked at ${preview.remoteMemories} shared memor${preview.remoteMemories === 1 ? "y" : "ies"} and ${preview.remoteFixes} known fix${preview.remoteFixes === 1 ? "" : "es"} for ${config.companyName} / ${config.projectName}.`,
140
+ panel: "pull",
141
+ focus: config.projectName,
142
+ status: "info",
143
+ });
144
+ if (format === "json") {
145
+ printJson({
146
+ ok: true,
147
+ action: "pull",
148
+ mode: "preview",
149
+ companyName: config.companyName,
150
+ spaceName: config.projectName,
151
+ preview,
152
+ });
153
+ return;
154
+ }
155
+ printPullPreviewText(preview, config.companyName, config.projectName);
156
+ console.log(chalk.gray("\n Run `umbrella-context pull` when you are ready to refresh your local .um snapshot."));
157
+ return;
158
+ }
68
159
  await setPulledMemories((memoriesJson.results ?? []).map((entry) => ({
69
160
  id: entry.id,
70
161
  content: entry.content,
71
162
  tags: entry.tags ?? [],
163
+ keywords: entry.keywords ?? [],
164
+ category: entry.category ?? null,
165
+ accessLevel: entry.accessLevel ?? "space",
72
166
  source: entry.source ?? "server",
73
167
  systemType: entry.systemType ?? "system1_knowledge",
74
168
  createdAt: entry.createdAt ?? new Date().toISOString(),
@@ -92,6 +186,18 @@ export async function pullCommandAction() {
92
186
  status: "success",
93
187
  });
94
188
  await completeTask(task.id, "completed", `Pulled ${memoriesJson.results?.length ?? 0} context entr${(memoriesJson.results?.length ?? 0) === 1 ? "y" : "ies"} and ${fixesJson.results?.length ?? 0} known fix${(fixesJson.results?.length ?? 0) === 1 ? "" : "es"}.`);
189
+ if (format === "json") {
190
+ printJson({
191
+ ok: true,
192
+ action: "pull",
193
+ companyName: config.companyName,
194
+ spaceName: config.projectName,
195
+ pulledMemories: memoriesJson.results?.length ?? 0,
196
+ pulledFixes: fixesJson.results?.length ?? 0,
197
+ preview,
198
+ });
199
+ return;
200
+ }
95
201
  console.log(chalk.green(`\n Pulled ${memoriesJson.results?.length ?? 0} context entries from the server.`));
96
202
  console.log(chalk.gray(` Company: ${config.companyName}`));
97
203
  console.log(chalk.gray(` Space: ${config.projectName}`));
@@ -107,11 +213,21 @@ export async function pullCommandAction() {
107
213
  focus: config.projectName,
108
214
  });
109
215
  await completeTask(failedTask.id, "failed", err.message);
216
+ if (format === "json") {
217
+ printJson({ ok: false, action: "pull", error: err.message });
218
+ return;
219
+ }
110
220
  console.log(chalk.red(`\n Error: ${err.message}`));
111
221
  }
112
222
  }
113
223
  export function pullCommand(cli) {
114
- cli.command("pull", "Refresh the local .um snapshot from the server").action(async () => {
115
- await pullCommandAction();
224
+ cli
225
+ .command("pull", "Refresh the local .um snapshot from the server")
226
+ .option("--format <format>", "Output format (text or json)")
227
+ .option("--dry-run", "Preview what would be pulled without changing local files")
228
+ .example("pull --dry-run")
229
+ .example("pull --format json")
230
+ .action(async (opts) => {
231
+ await pullCommandAction(opts);
116
232
  });
117
233
  }
@@ -1,2 +1,7 @@
1
- export declare function pushCommandAction(): Promise<void>;
1
+ type OutputFormat = "text" | "json";
2
+ export declare function pushCommandAction(opts?: {
3
+ format?: OutputFormat;
4
+ dryRun?: boolean;
5
+ }): Promise<void>;
2
6
  export declare function pushCommand(cli: any): void;
7
+ export {};
@@ -1,9 +1,83 @@
1
1
  import chalk from "chalk";
2
2
  import { configManager } from "../config.js";
3
3
  import { addTaskReasoningContent, addTaskToolCall, clearPendingMemories, completeTask, createTask, ensureRepoContext, getPendingMemories, getPulledMemories, markPushCompleted, recordSessionEvent, setSessionPanel, setPulledMemories, } from "../repo-state.js";
4
- export async function pushCommandAction() {
4
+ function printJson(payload) {
5
+ console.log(JSON.stringify(payload, null, 2));
6
+ }
7
+ function buildPushPreview(entries) {
8
+ const categoryCounts = {};
9
+ const accessLevelCounts = { space: 0, company: 0 };
10
+ const tagCounts = new Map();
11
+ const keywordCounts = new Map();
12
+ for (const entry of entries) {
13
+ const category = entry.category?.trim() || "uncategorized";
14
+ categoryCounts[category] = (categoryCounts[category] ?? 0) + 1;
15
+ accessLevelCounts[entry.accessLevel] += 1;
16
+ for (const tag of entry.tags) {
17
+ const normalized = tag.trim().toLowerCase();
18
+ if (!normalized)
19
+ continue;
20
+ tagCounts.set(normalized, (tagCounts.get(normalized) ?? 0) + 1);
21
+ }
22
+ for (const keyword of entry.keywords) {
23
+ const normalized = keyword.trim().toLowerCase();
24
+ if (!normalized)
25
+ continue;
26
+ keywordCounts.set(normalized, (keywordCounts.get(normalized) ?? 0) + 1);
27
+ }
28
+ }
29
+ const sortCounts = (counts) => [...counts.entries()]
30
+ .sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
31
+ .slice(0, 5)
32
+ .map(([value, count]) => ({ tag: value, count }));
33
+ return {
34
+ pendingCount: entries.length,
35
+ categoryCounts,
36
+ accessLevelCounts,
37
+ topTags: sortCounts(tagCounts),
38
+ topKeywords: [...keywordCounts.entries()]
39
+ .sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
40
+ .slice(0, 5)
41
+ .map(([keyword, count]) => ({ keyword, count })),
42
+ latestDraftAt: entries.length > 0 ? entries.map((entry) => entry.createdAt).sort().at(-1) ?? null : null,
43
+ };
44
+ }
45
+ function printPushPreviewText(preview, companyName, spaceName) {
46
+ console.log(chalk.bold(`\n Push preview for ${companyName} / ${spaceName}`));
47
+ console.log(chalk.gray(` Pending drafts: ${preview.pendingCount}`));
48
+ console.log(chalk.gray(` Access: ${preview.accessLevelCounts.space} space, ${preview.accessLevelCounts.company} company`));
49
+ const categories = Object.entries(preview.categoryCounts);
50
+ if (categories.length > 0) {
51
+ console.log(chalk.cyan("\n Categories:"));
52
+ for (const [category, count] of categories.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))) {
53
+ console.log(chalk.gray(` - ${category}: ${count}`));
54
+ }
55
+ }
56
+ if (preview.topTags.length > 0) {
57
+ console.log(chalk.cyan("\n Top tags:"));
58
+ for (const entry of preview.topTags) {
59
+ console.log(chalk.gray(` - ${entry.tag}: ${entry.count}`));
60
+ }
61
+ }
62
+ if (preview.topKeywords.length > 0) {
63
+ console.log(chalk.cyan("\n Top keywords:"));
64
+ for (const entry of preview.topKeywords) {
65
+ console.log(chalk.gray(` - ${entry.keyword}: ${entry.count}`));
66
+ }
67
+ }
68
+ if (preview.latestDraftAt) {
69
+ console.log(chalk.gray(`\n Latest draft: ${new Date(preview.latestDraftAt).toLocaleString()}`));
70
+ }
71
+ }
72
+ export async function pushCommandAction(opts = {}) {
5
73
  const config = configManager.config;
74
+ const format = opts.format === "json" ? "json" : "text";
75
+ const dryRun = Boolean(opts.dryRun);
6
76
  if (!config) {
77
+ if (format === "json") {
78
+ printJson({ ok: false, error: "Not configured. Run: umbrella-context setup" });
79
+ return;
80
+ }
7
81
  console.log(chalk.red("Not configured. Run: umbrella-context setup"));
8
82
  return;
9
83
  }
@@ -19,6 +93,7 @@ export async function pushCommandAction() {
19
93
  focus: config.projectName,
20
94
  });
21
95
  const pending = await getPendingMemories();
96
+ const preview = buildPushPreview(pending);
22
97
  await addTaskReasoningContent(task.id, `Found ${pending.length} pending local draft${pending.length === 1 ? "" : "s"} ready for upload.`);
23
98
  if (pending.length === 0) {
24
99
  await completeTask(task.id, "completed", "Nothing was pending, so no push was needed.");
@@ -30,9 +105,46 @@ export async function pushCommandAction() {
30
105
  focus: config.projectName,
31
106
  status: "info",
32
107
  });
108
+ if (format === "json") {
109
+ printJson({
110
+ ok: true,
111
+ action: "push",
112
+ companyName: config.companyName,
113
+ spaceName: config.projectName,
114
+ pushedCount: 0,
115
+ pendingCount: 0,
116
+ preview,
117
+ });
118
+ return;
119
+ }
33
120
  console.log(chalk.yellow("\n Nothing to push. Your local .um folder is already in sync."));
34
121
  return;
35
122
  }
123
+ if (dryRun) {
124
+ await completeTask(task.id, "completed", "Previewed pending local drafts without uploading them.");
125
+ await recordSessionEvent({
126
+ kind: "push",
127
+ title: "Previewed pending Context drafts",
128
+ detail: `Looked at ${pending.length} pending draft${pending.length === 1 ? "" : "s"} for ${config.companyName} / ${config.projectName} without pushing them.`,
129
+ panel: "push",
130
+ focus: config.projectName,
131
+ status: "info",
132
+ });
133
+ if (format === "json") {
134
+ printJson({
135
+ ok: true,
136
+ action: "push",
137
+ mode: "preview",
138
+ companyName: config.companyName,
139
+ spaceName: config.projectName,
140
+ preview,
141
+ });
142
+ return;
143
+ }
144
+ printPushPreviewText(preview, config.companyName, config.projectName);
145
+ console.log(chalk.gray("\n Run `umbrella-context push` when you are ready to upload these drafts."));
146
+ return;
147
+ }
36
148
  const synced = [];
37
149
  for (const entry of pending) {
38
150
  await addTaskToolCall(task.id, {
@@ -53,6 +165,9 @@ export async function pushCommandAction() {
53
165
  body: JSON.stringify({
54
166
  content: entry.content,
55
167
  tags: entry.tags,
168
+ keywords: entry.keywords,
169
+ category: entry.category,
170
+ accessLevel: entry.accessLevel,
56
171
  source: entry.source,
57
172
  systemType: entry.systemType,
58
173
  }),
@@ -66,6 +181,10 @@ export async function pushCommandAction() {
66
181
  error: err || res.statusText,
67
182
  });
68
183
  await completeTask(task.id, "failed", err || res.statusText);
184
+ if (format === "json") {
185
+ printJson({ ok: false, action: "push", error: err || res.statusText });
186
+ return;
187
+ }
69
188
  console.log(chalk.red(`\n Push failed: ${err || res.statusText}`));
70
189
  return;
71
190
  }
@@ -84,6 +203,9 @@ export async function pushCommandAction() {
84
203
  id: entry.id,
85
204
  content: entry.content,
86
205
  tags: entry.tags ?? [],
206
+ keywords: entry.keywords ?? [],
207
+ category: entry.category ?? null,
208
+ accessLevel: entry.accessLevel ?? "space",
87
209
  source: entry.source ?? "cli",
88
210
  systemType: entry.systemType ?? "system1_knowledge",
89
211
  createdAt: entry.createdAt ?? new Date().toISOString(),
@@ -101,6 +223,18 @@ export async function pushCommandAction() {
101
223
  status: "success",
102
224
  });
103
225
  await completeTask(task.id, "completed", `Pushed ${synced.length} entr${synced.length === 1 ? "y" : "ies"} to the server.`);
226
+ if (format === "json") {
227
+ printJson({
228
+ ok: true,
229
+ action: "push",
230
+ companyName: config.companyName,
231
+ spaceName: config.projectName,
232
+ pushedCount: synced.length,
233
+ preview,
234
+ ids: synced.map((entry) => entry.id),
235
+ });
236
+ return;
237
+ }
104
238
  console.log(chalk.green(`\n Pushed ${synced.length} local context entr${synced.length === 1 ? "y" : "ies"}.`));
105
239
  console.log(chalk.gray(` Company: ${config.companyName}`));
106
240
  console.log(chalk.gray(` Space: ${config.projectName}`));
@@ -115,11 +249,21 @@ export async function pushCommandAction() {
115
249
  focus: config.projectName,
116
250
  });
117
251
  await completeTask(failedTask.id, "failed", err.message);
252
+ if (format === "json") {
253
+ printJson({ ok: false, action: "push", error: err.message });
254
+ return;
255
+ }
118
256
  console.log(chalk.red(`\n Error: ${err.message}`));
119
257
  }
120
258
  }
121
259
  export function pushCommand(cli) {
122
- cli.command("push", "Push pending local .um context to the server").action(async () => {
123
- await pushCommandAction();
260
+ cli
261
+ .command("push", "Push pending local .um context to the server")
262
+ .option("--format <format>", "Output format (text or json)")
263
+ .option("--dry-run", "Preview what would be pushed without uploading anything")
264
+ .example("push --dry-run")
265
+ .example("push --format json")
266
+ .action(async (opts) => {
267
+ await pushCommandAction(opts);
124
268
  });
125
269
  }
@@ -15,7 +15,12 @@ export async function restartCommandAction() {
15
15
  console.log(chalk.gray(" Local .um context files were kept. Only the live shell session was refreshed."));
16
16
  }
17
17
  export function restartCommand(cli) {
18
- cli.command("restart", "Refresh the local repo session without deleting .um data").action(async () => {
18
+ cli
19
+ .command("restart", `Refresh the local repo session without deleting .um data
20
+
21
+ Use this when the Context terminal feels stuck, panel state looks wrong, or you want a clean local session without losing saved notes in .um.`)
22
+ .example("restart")
23
+ .action(async () => {
19
24
  await restartCommandAction();
20
25
  });
21
26
  }
@@ -1,2 +1,6 @@
1
- export declare function searchCommandAction(query: string): Promise<void>;
1
+ type OutputFormat = "text" | "json";
2
+ export declare function searchCommandAction(query: string, opts?: {
3
+ format?: OutputFormat;
4
+ }): Promise<void>;
2
5
  export declare function searchCommand(cli: any): void;
6
+ export {};