umbrella-context 0.1.37 → 0.1.38

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 CHANGED
@@ -14,10 +14,17 @@ Or install it inside the current project:
14
14
  npm i umbrella-context
15
15
  ```
16
16
 
17
- For local testing from a tarball:
17
+ For local testing from a tarball built from this repo:
18
18
 
19
19
  ```bash
20
- npm install -g ./umbrella-context-0.1.1.tgz
20
+ pnpm pack:umbrella-context:local
21
+ npm install -g ./.artifacts/umbrella-context-local/umbrella-context-<version>.tgz
22
+ ```
23
+
24
+ If you want to pack it and launch the packed copy immediately without publishing:
25
+
26
+ ```bash
27
+ pnpm run:umbrella-context:local
21
28
  ```
22
29
 
23
30
  ## First-time setup
@@ -1,5 +1,5 @@
1
1
  import { configManager } from "../config.js";
2
- import { ensureRepoContext } from "../repo-state.js";
2
+ import { ensureRepoContext, findRepoRoot } from "../repo-state.js";
3
3
  import { createContextSpace, createUmbrellaCompany, getCliSetup, getCompanyContextSummary, getUmbrellaHealth, listUmbrellaCompanies, signInToUmbrella, toContextSpaces, } from "../umbrella.js";
4
4
  export function normalizeUmbrellaServerUrl(value) {
5
5
  return value.trim().replace(/\/+$/, "");
@@ -50,6 +50,7 @@ export async function createUmbrellaOnboardingSpace(serverUrl, companyId, name,
50
50
  };
51
51
  }
52
52
  export async function saveUmbrellaDeviceLink(setup, umbrellaUrl, cwd = process.cwd()) {
53
+ const repoRoot = await findRepoRoot(cwd);
53
54
  configManager.set({
54
55
  umbrellaUrl,
55
56
  serverUrl: setup.baseUrl,
@@ -62,7 +63,7 @@ export async function saveUmbrellaDeviceLink(setup, umbrellaUrl, cwd = process.c
62
63
  apiKey: setup.apiKey,
63
64
  });
64
65
  configManager.upsertLocation({
65
- repoRoot: cwd,
66
+ repoRoot,
66
67
  companyId: setup.companyId,
67
68
  companyName: setup.companyName,
68
69
  projectId: setup.activeSpaceId,
@@ -1,6 +1,6 @@
1
1
  import chalk from "chalk";
2
2
  import { configManager } from "../config.js";
3
- import { ensureRepoContext } from "../repo-state.js";
3
+ import { ensureRepoContext, findRepoRoot } from "../repo-state.js";
4
4
  export function connectCommand(cli) {
5
5
  cli
6
6
  .command("connect", "Connect this device directly to a company context space without the interactive setup flow")
@@ -29,6 +29,7 @@ export function connectCommand(cli) {
29
29
  return;
30
30
  }
31
31
  try {
32
+ const repoRoot = await findRepoRoot(process.cwd());
32
33
  const res = await fetch(`${serverUrl}/api/memories?limit=1`, {
33
34
  headers: {
34
35
  Authorization: `Bearer ${apiKey}`,
@@ -51,7 +52,7 @@ export function connectCommand(cli) {
51
52
  apiKey,
52
53
  });
53
54
  configManager.upsertLocation({
54
- repoRoot: process.cwd(),
55
+ repoRoot,
55
56
  companyId,
56
57
  companyName,
57
58
  projectId: spaceId,
@@ -130,6 +130,9 @@ async function applyConnectorRunEffects(entry, cwd = process.cwd()) {
130
130
  const staged = await addPendingMemory({
131
131
  content: "Slack growth hook captured a reusable campaign note. Replace this placeholder with the real signal before pushing.",
132
132
  tags: ["slack", "growth", "draft"],
133
+ keywords: ["slack", "growth", "campaign"],
134
+ category: "team",
135
+ accessLevel: "space",
133
136
  systemType: "connector_capture",
134
137
  source: "slack-growth-hook",
135
138
  }, cwd);
@@ -1,6 +1,18 @@
1
+ type OutputFormat = "text" | "json";
2
+ type CurateViewStatus = "pending";
1
3
  export declare function curateCommandAction(content: string, opts?: {
4
+ format?: OutputFormat;
2
5
  tag?: string | string[];
3
6
  type?: string;
4
7
  }): Promise<void>;
5
- export declare function curateViewCommandAction(): Promise<void>;
8
+ export declare function curateViewCommandAction(opts?: {
9
+ before?: string;
10
+ detail?: boolean;
11
+ format?: OutputFormat;
12
+ id?: string;
13
+ limit?: number;
14
+ since?: string;
15
+ status?: CurateViewStatus | CurateViewStatus[];
16
+ }): Promise<void>;
6
17
  export declare function curateCommand(cli: any): void;
18
+ export {};
@@ -1,14 +1,78 @@
1
1
  import chalk from "chalk";
2
2
  import { configManager } from "../config.js";
3
3
  import { addPendingMemory, completeTask, createTask, ensureRepoContext, getPendingMemories, getRepoContext, recordSessionCuration, recordSessionEvent, setSessionPanel, } from "../repo-state.js";
4
+ const RELATIVE_TIME_PATTERN = /^(\d+)(m|h|d|w)$/;
5
+ function printCurateResult(format, payload) {
6
+ if (format === "json") {
7
+ console.log(JSON.stringify(payload, null, 2));
8
+ }
9
+ }
10
+ function parseTimeFilter(value) {
11
+ if (!value?.trim())
12
+ return null;
13
+ const relative = RELATIVE_TIME_PATTERN.exec(value.trim());
14
+ if (relative) {
15
+ const amount = Number(relative[1]);
16
+ const unit = relative[2];
17
+ const multipliers = {
18
+ m: 60_000,
19
+ h: 3_600_000,
20
+ d: 86_400_000,
21
+ w: 604_800_000,
22
+ };
23
+ return Date.now() - amount * multipliers[unit];
24
+ }
25
+ const timestamp = new Date(value).getTime();
26
+ return Number.isNaN(timestamp) ? null : timestamp;
27
+ }
28
+ function normalizeStatuses(status) {
29
+ if (!status)
30
+ return ["pending"];
31
+ return Array.isArray(status) ? status : [status];
32
+ }
33
+ function filterCurations(entries, opts) {
34
+ const after = parseTimeFilter(opts.since);
35
+ const before = parseTimeFilter(opts.before);
36
+ const statuses = normalizeStatuses(opts.status);
37
+ if ((opts.since && after === null) || (opts.before && before === null)) {
38
+ return {
39
+ error: 'Invalid time filter. Use ISO date (2026-04-07 or 2026-04-07T09:30:00Z) or relative time like "1h", "24h", "7d", or "2w".',
40
+ filtered: [],
41
+ };
42
+ }
43
+ let filtered = [...entries]
44
+ .filter(() => statuses.includes("pending"))
45
+ .filter((entry) => {
46
+ const createdAt = new Date(entry.createdAt).getTime();
47
+ if (after !== null && createdAt < after)
48
+ return false;
49
+ if (before !== null && createdAt > before)
50
+ return false;
51
+ return true;
52
+ });
53
+ if (opts.id) {
54
+ filtered = filtered.filter((entry) => entry.id === opts.id);
55
+ }
56
+ const limit = typeof opts.limit === "number" && Number.isFinite(opts.limit) && opts.limit > 0 ? opts.limit : 10;
57
+ return { error: null, filtered: filtered.slice(0, limit) };
58
+ }
4
59
  export async function curateCommandAction(content, opts = {}) {
5
60
  const config = configManager.config;
61
+ const format = opts.format === "json" ? "json" : "text";
6
62
  if (!config) {
63
+ if (format === "json") {
64
+ console.log(JSON.stringify({ ok: false, error: "Not configured. Run: umbrella-context setup" }, null, 2));
65
+ return;
66
+ }
7
67
  console.log(chalk.red("Not configured. Run: umbrella-context setup"));
8
68
  return;
9
69
  }
10
70
  if (!content?.trim()) {
11
- console.log(chalk.red("\n Usage: umbrella-context curate \"What we learned\""));
71
+ if (format === "json") {
72
+ console.log(JSON.stringify({ ok: false, error: 'Usage: umbrella-context curate "What we learned"' }, null, 2));
73
+ return;
74
+ }
75
+ console.log(chalk.red('\n Usage: umbrella-context curate "What we learned"'));
12
76
  return;
13
77
  }
14
78
  await ensureRepoContext(config);
@@ -21,9 +85,13 @@ export async function curateCommandAction(content, opts = {}) {
21
85
  panel: "curate",
22
86
  focus: content.trim(),
23
87
  });
88
+ const tags = opts.tag ? (Array.isArray(opts.tag) ? opts.tag : [opts.tag]) : [];
24
89
  const entry = await addPendingMemory({
25
90
  content: content.trim(),
26
- tags: opts.tag ? (Array.isArray(opts.tag) ? opts.tag : [opts.tag]) : [],
91
+ tags,
92
+ keywords: tags,
93
+ category: null,
94
+ accessLevel: "space",
27
95
  source: "cli",
28
96
  systemType: opts.type || "system1_knowledge",
29
97
  });
@@ -38,41 +106,109 @@ export async function curateCommandAction(content, opts = {}) {
38
106
  status: "success",
39
107
  });
40
108
  await completeTask(task.id, "completed", `Saved local draft ${entry.id} in .um.`);
109
+ printCurateResult(format, {
110
+ ok: true,
111
+ action: "curate",
112
+ companyName: config.companyName,
113
+ spaceName: config.projectName,
114
+ pendingEntryId: entry.id,
115
+ tags: entry.tags,
116
+ type: entry.systemType,
117
+ accessLevel: entry.accessLevel,
118
+ umDir,
119
+ });
120
+ if (format === "json") {
121
+ return;
122
+ }
41
123
  console.log(chalk.green(`\n Saved locally to ${config.companyName} / ${config.projectName}`));
42
124
  console.log(chalk.gray(` Pending entry: ${entry.id}`));
43
125
  console.log(chalk.gray(` Repo context folder: ${umDir}`));
44
126
  console.log(chalk.gray(' Run "umbrella-context push" when you want to sync this to the server.'));
45
127
  }
46
- export async function curateViewCommandAction() {
128
+ export async function curateViewCommandAction(opts = {}) {
47
129
  const config = configManager.config;
130
+ const format = opts.format === "json" ? "json" : "text";
48
131
  if (!config) {
132
+ if (format === "json") {
133
+ console.log(JSON.stringify({ ok: false, error: "Not configured. Run: umbrella-context setup" }, null, 2));
134
+ return;
135
+ }
49
136
  console.log(chalk.red("Not configured. Run: umbrella-context setup"));
50
137
  return;
51
138
  }
52
139
  await ensureRepoContext(config);
53
- await setSessionPanel("curate", "view");
140
+ await setSessionPanel("curate", opts.id ?? "view");
54
141
  const entries = await getPendingMemories();
142
+ const { error, filtered } = filterCurations(entries, opts);
143
+ if (error) {
144
+ if (format === "json") {
145
+ console.log(JSON.stringify({ ok: false, action: "curate.view", error }, null, 2));
146
+ return;
147
+ }
148
+ console.log(chalk.red(error));
149
+ return;
150
+ }
151
+ if (format === "json") {
152
+ console.log(JSON.stringify({
153
+ ok: true,
154
+ action: "curate.view",
155
+ companyName: config.companyName,
156
+ spaceName: config.projectName,
157
+ total: filtered.length,
158
+ statuses: normalizeStatuses(opts.status),
159
+ detail: Boolean(opts.detail || opts.id),
160
+ entries: filtered,
161
+ }, null, 2));
162
+ return;
163
+ }
55
164
  console.log(chalk.bold(`\n Local Curations for ${config.companyName} / ${config.projectName}\n`));
56
- if (entries.length === 0) {
57
- console.log(chalk.yellow(" No local curations staged yet."));
165
+ if (filtered.length === 0) {
166
+ console.log(chalk.yellow(" No local curations matched this view."));
58
167
  console.log(chalk.gray(' Try: umbrella-context curate "What you learned"'));
59
168
  return;
60
169
  }
61
- entries.forEach((entry, index) => {
170
+ filtered.forEach((entry, index) => {
62
171
  const tags = entry.tags.length > 0 ? ` [${entry.tags.join(", ")}]` : "";
63
172
  console.log(` ${index + 1}. ${entry.content}${tags}`);
64
- console.log(chalk.gray(` ${entry.systemType} • ${entry.createdAt} • ${entry.id}`));
173
+ console.log(chalk.gray(` pending • ${entry.systemType} • ${entry.createdAt} • ${entry.id}`));
174
+ if (opts.detail || opts.id) {
175
+ const keywords = entry.keywords.length > 0 ? entry.keywords.join(", ") : "none";
176
+ console.log(chalk.gray(` access: ${entry.accessLevel} • category: ${entry.category ?? "uncategorized"} • source: ${entry.source}`));
177
+ console.log(chalk.gray(` keywords: ${keywords}`));
178
+ }
65
179
  });
66
180
  }
67
181
  export function curateCommand(cli) {
68
182
  cli
69
- .command("curate [...content]", "Save context locally in this repo so it can be pushed later")
183
+ .command("curate [...content]", `Save context locally in this repo so it can be pushed later
184
+
185
+ Good examples:
186
+ - "Auth uses JWT with 24h expiry in httpOnly cookies via auth middleware"
187
+ - "Rate limit is 100 req/min per user using Redis sliding windows"
188
+ Bad examples:
189
+ - "Authentication"
190
+ - "Rate limiting"`)
70
191
  .option("--tag <tag>", "Add tags (repeatable)")
71
192
  .option("--type <type>", "system1_knowledge or system2_reasoning")
193
+ .option("--format <format>", "Output format (text or json)")
194
+ .option("--detail", "When used with curate view, show richer entry detail")
195
+ .option("--limit <limit>", "When used with curate view, limit the number of entries returned")
196
+ .option("--since <time>", "When used with curate view, show entries after a time like 1h, 24h, 7d, or an ISO date")
197
+ .option("--before <time>", "When used with curate view, show entries before a time like 1h, 24h, 7d, or an ISO date")
198
+ .option("--status <status>", "When used with curate view, filter by status (currently: pending)")
199
+ .example('curate "JWT auth uses httpOnly cookies"')
200
+ .example('curate "Auth middleware" --tag auth --tag cookies --format json')
201
+ .example("curate view")
202
+ .example("curate view --since 1h --detail")
203
+ .example("curate view 123e4567-e89b-12d3-a456-426614174000 --format json")
72
204
  .action(async (content, opts) => {
73
205
  const parts = Array.isArray(content) ? content : [content];
74
206
  if (parts.length === 1 && parts[0] === "view") {
75
- await curateViewCommandAction();
207
+ await curateViewCommandAction(opts);
208
+ return;
209
+ }
210
+ if (parts[0] === "view") {
211
+ await curateViewCommandAction({ ...opts, id: parts[1] });
76
212
  return;
77
213
  }
78
214
  await curateCommandAction(parts.join(" "), opts);
@@ -1,6 +1,6 @@
1
1
  import chalk from "chalk";
2
2
  import { configManager } from "../config.js";
3
- import { addTaskReasoningContent, addTaskToolCall, appendTaskStreamingContent, completeTask, createTask, patchTaskLifecycle, recordSessionEvent, setSessionPanel, } from "../repo-state.js";
3
+ import { addTaskReasoningContent, addTaskToolCall, appendTaskStreamingContent, completeTask, createTask, patchTaskLifecycle, removeTask, recordSessionEvent, setSessionPanel, } from "../repo-state.js";
4
4
  export function fixCommand(cli) {
5
5
  cli.command("fix <error>", "Search for known fixes to an error").action(async (error) => {
6
6
  const config = configManager.config;
@@ -54,15 +54,7 @@ export function fixCommand(cli) {
54
54
  });
55
55
  await addTaskReasoningContent(task.id, `Server returned ${data.results.length} known fix candidate${data.results.length === 1 ? "" : "s"}.`);
56
56
  if (data.results.length === 0) {
57
- await completeTask(task.id, "completed", `No known fixes found for "${error}".`);
58
- await recordSessionEvent({
59
- kind: "query",
60
- title: `No known fixes found for "${error}"`,
61
- detail: "The shared fixes store did not contain this error signal.",
62
- panel: "fix",
63
- focus: error,
64
- status: "warning",
65
- });
57
+ await removeTask(task.id);
66
58
  console.log(chalk.yellow(`\n No known fixes for "${error}"`));
67
59
  return;
68
60
  }
@@ -46,7 +46,7 @@ function printWelcomeHeader(providerLabel, modelLabel) {
46
46
  console.log(` ${chalk.yellow("/reset")} Reset this repo's local Context state`);
47
47
  console.log(` ${chalk.yellow("/")} Full command list`);
48
48
  console.log(divider());
49
- console.log(` ${chalk.gray("Shortcuts:")} type plain text to query, or start with ${chalk.yellow("+")} to curate quickly.`);
49
+ console.log(` ${chalk.gray("Shortcuts:")} start with ${chalk.yellow("?")} to query, or start with ${chalk.yellow("+")} to curate quickly.`);
50
50
  console.log("");
51
51
  }
52
52
  async function printRuntimeSnapshot() {
@@ -794,7 +794,7 @@ export async function interactiveCommand(_args) {
794
794
  await setSessionPanel("home", configManager.config.projectName);
795
795
  await printRuntimeSnapshot();
796
796
  await printHomeView(configManager.config);
797
- console.log(chalk.gray(" Type / to see commands. Use plain text to query or start with + to curate.\n"));
797
+ console.log(chalk.gray(" Type / to see commands. Use ? to query or + to curate.\n"));
798
798
  while (true) {
799
799
  const currentConfig = configManager.config;
800
800
  if (!currentConfig) {
@@ -837,21 +837,32 @@ export async function interactiveCommand(_args) {
837
837
  }
838
838
  continue;
839
839
  }
840
- const { command, args } = parseInteractiveInput(raw);
841
- const spec = resolveCommand(command);
842
- if (!raw.startsWith("/") && !spec) {
843
- commandHistory.push(`/query ${raw}`);
844
- await recordSessionCommand(`/query ${raw}`);
845
- await recordSessionQuery(raw);
846
- await setSessionSummary(`Queried Context: ${raw.length > 48 ? `${raw.slice(0, 48)}...` : raw}`);
840
+ if (raw.startsWith("?")) {
841
+ const query = raw.slice(1).trim();
842
+ if (!query) {
843
+ console.log(chalk.yellow("Use ? followed by a real question, for example ? what do we already know about auth timeouts"));
844
+ continue;
845
+ }
846
+ commandHistory.push(`/query ${query}`);
847
+ await recordSessionCommand(`/query ${query}`);
848
+ await recordSessionQuery(query);
849
+ await setSessionSummary(`Queried Context: ${query.length > 48 ? `${query.slice(0, 48)}...` : query}`);
847
850
  try {
848
- await searchCommandAction(raw);
851
+ await searchCommandAction(query);
849
852
  }
850
853
  catch (err) {
851
854
  console.log(chalk.red(`Command failed: ${err.message}`));
852
855
  }
853
856
  continue;
854
857
  }
858
+ const { command, args } = parseInteractiveInput(raw);
859
+ const spec = resolveCommand(command);
860
+ if (!raw.startsWith("/") && !spec) {
861
+ console.log(chalk.yellow("That text was not run automatically."));
862
+ console.log(chalk.gray("Use /query <question> or start with ? to search Context."));
863
+ console.log(chalk.gray("Use /curate <note> or start with + to save something locally."));
864
+ continue;
865
+ }
855
866
  if (!spec) {
856
867
  console.log(chalk.yellow(`Unknown command: ${raw}. Type / to see available commands.`));
857
868
  continue;
@@ -1,2 +1,7 @@
1
- export declare function locationsCommandAction(): Promise<void>;
1
+ type OutputFormat = "text" | "json";
2
+ export declare function locationsCommandAction(opts?: {
3
+ format?: OutputFormat;
4
+ detail?: boolean;
5
+ }): Promise<void>;
2
6
  export declare function locationsCommand(cli: any): void;
7
+ export {};
@@ -1,20 +1,106 @@
1
1
  import chalk from "chalk";
2
+ import { promises as fs } from "fs";
3
+ import path from "path";
2
4
  import { configManager } from "../config.js";
3
- export async function locationsCommandAction() {
4
- const locations = configManager.locations;
5
+ import { findRepoRoot, getRepoContext } from "../repo-state.js";
6
+ async function getCurrentRepoRoot() {
7
+ try {
8
+ const { repoRoot } = await getRepoContext();
9
+ return repoRoot;
10
+ }
11
+ catch {
12
+ return null;
13
+ }
14
+ }
15
+ async function summarizeSavedLocations() {
16
+ const currentRepoRoot = await getCurrentRepoRoot();
17
+ const storedLocations = configManager.locations;
18
+ const normalizedLocations = await Promise.all(storedLocations.map(async (location) => {
19
+ try {
20
+ const canonicalRepoRoot = await findRepoRoot(location.repoRoot);
21
+ return canonicalRepoRoot === location.repoRoot ? location : { ...location, repoRoot: canonicalRepoRoot };
22
+ }
23
+ catch {
24
+ return location;
25
+ }
26
+ }));
27
+ const dedupedLocations = Array.from(new Map(normalizedLocations.map((location) => [
28
+ location.repoRoot,
29
+ location,
30
+ ])).values());
31
+ const locationChanged = dedupedLocations.length !== storedLocations.length
32
+ || dedupedLocations.some((location, index) => {
33
+ const original = storedLocations[index];
34
+ return !original || original.repoRoot !== location.repoRoot;
35
+ });
36
+ if (locationChanged) {
37
+ configManager.replaceLocations(dedupedLocations);
38
+ }
39
+ const snapshots = await Promise.all(dedupedLocations.map(async (location) => {
40
+ const umDir = path.join(location.repoRoot, ".um");
41
+ const contextFile = path.join(umDir, "context.json");
42
+ const [umDirStat, contextStat] = await Promise.allSettled([fs.stat(umDir), fs.stat(contextFile)]);
43
+ const hasUmDir = umDirStat.status === "fulfilled" && umDirStat.value.isDirectory();
44
+ const hasContextFile = contextStat.status === "fulfilled" && contextStat.value.isFile();
45
+ return {
46
+ ...location,
47
+ isCurrentRepo: currentRepoRoot !== null && currentRepoRoot === location.repoRoot,
48
+ hasUmDir,
49
+ hasContextFile,
50
+ isLinked: hasUmDir && hasContextFile,
51
+ };
52
+ }));
53
+ return snapshots.sort((a, b) => a.repoRoot.localeCompare(b.repoRoot));
54
+ }
55
+ function printLocationsJson(locations) {
56
+ console.log(JSON.stringify({
57
+ ok: true,
58
+ action: "locations",
59
+ count: locations.length,
60
+ locations,
61
+ }, null, 2));
62
+ }
63
+ export async function locationsCommandAction(opts = {}) {
64
+ const locations = await summarizeSavedLocations();
5
65
  if (locations.length === 0) {
66
+ if (opts.format === "json") {
67
+ printLocationsJson([]);
68
+ return;
69
+ }
6
70
  console.log(chalk.yellow("\n No repo locations saved yet."));
7
71
  return;
8
72
  }
73
+ if (opts.format === "json") {
74
+ printLocationsJson(locations);
75
+ return;
76
+ }
9
77
  console.log(chalk.bold("\n Saved Context Locations\n"));
10
78
  locations.forEach((location, index) => {
11
- console.log(chalk.cyan(` ${index + 1}. ${location.companyName} / ${location.projectName}`));
79
+ const badges = [];
80
+ if (location.isCurrentRepo)
81
+ badges.push(chalk.green("CURRENT"));
82
+ if (location.isLinked)
83
+ badges.push(chalk.cyan("LINKED"));
84
+ else
85
+ badges.push(chalk.yellow("SAVED ONLY"));
86
+ console.log(chalk.cyan(` ${index + 1}. ${location.companyName} / ${location.projectName} ${badges.join(" ")}`));
12
87
  console.log(chalk.gray(` ${location.repoRoot}`));
13
88
  console.log(chalk.gray(` Updated: ${new Date(location.updatedAt).toLocaleString()}`));
89
+ if (opts.detail) {
90
+ console.log(chalk.gray(` .um folder: ${location.hasUmDir ? "Yes" : "No"}`));
91
+ console.log(chalk.gray(` .um/context.json: ${location.hasContextFile ? "Yes" : "No"}`));
92
+ }
14
93
  });
15
94
  }
16
95
  export function locationsCommand(cli) {
17
- cli.command("locations", "List repo folders that have been connected on this machine").action(async () => {
18
- await locationsCommandAction();
96
+ cli
97
+ .command("locations", "List repo folders that have been connected on this machine")
98
+ .option("--format <format>", "Output format (text or json)")
99
+ .option("--detail", "Show whether each saved repo still has a linked .um folder and context file")
100
+ .example("locations")
101
+ .example("locations --detail")
102
+ .example("locations --format json")
103
+ .action(async (opts) => {
104
+ await locationsCommandAction(opts);
19
105
  });
20
106
  }
@@ -22,11 +22,16 @@ export function mcpCommand(cli) {
22
22
  const data = await res.json();
23
23
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
24
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 }) => {
25
+ server.tool("memory_push", "Save a memory or note to the current context space", {
26
+ content: z.string().describe("The memory content"),
27
+ tags: z.array(z.string()).optional(),
28
+ category: z.enum(["project", "team", "convention", "environment", "preference", "incident", "other"]).optional(),
29
+ accessLevel: z.enum(["space", "company"]).optional(),
30
+ }, async ({ content, tags, category, accessLevel }) => {
26
31
  const res = await fetch(`${config.serverUrl}/api/memories`, {
27
32
  method: "POST",
28
33
  headers: { "Content-Type": "application/json", Authorization: `Bearer ${config.apiKey}` },
29
- body: JSON.stringify({ content, tags: tags || [], source: "mcp" }),
34
+ body: JSON.stringify({ content, tags: tags || [], category, accessLevel, source: "mcp" }),
30
35
  });
31
36
  const data = await res.json();
32
37
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
@@ -1,2 +1,7 @@
1
- export declare function pullCommandAction(): Promise<void>;
1
+ type OutputFormat = "text" | "json";
2
+ export declare function pullCommandAction(opts?: {
3
+ format?: OutputFormat;
4
+ dryRun?: boolean;
5
+ }): Promise<void>;
2
6
  export declare function pullCommand(cli: any): void;
7
+ export {};