march-cli 0.1.32 → 0.1.34

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.
@@ -12,6 +12,7 @@ const MANAGED_PACKAGES = {
12
12
  "vscode-json-language-server": ["vscode-langservers-extracted"],
13
13
  "vscode-html-language-server": ["vscode-langservers-extracted"],
14
14
  "vscode-css-language-server": ["vscode-langservers-extracted"],
15
+ "sql-language-server": ["sql-language-server"],
15
16
  "docker-langserver": ["dockerfile-language-server-nodejs"],
16
17
  };
17
18
 
@@ -88,6 +88,14 @@ export function createLspServerDefinitions({ resolveTypeScriptProjectRoot, resol
88
88
  managedCommand: "vscode-json-language-server",
89
89
  args: ["--stdio"],
90
90
  },
91
+ {
92
+ id: "sql",
93
+ extensions: [".sql"],
94
+ rootMarkers: [".sqllsrc.json", ".sqllsrc", "package.json", ".git"],
95
+ command: ["sql-language-server"],
96
+ managedCommand: "sql-language-server",
97
+ args: ["up", "--method", "stdio"],
98
+ },
91
99
  {
92
100
  id: "html",
93
101
  extensions: [".html", ".htm"],
package/src/main.mjs CHANGED
@@ -38,7 +38,8 @@ import { installNetworkEnvironment } from "./network/environment.mjs";
38
38
  import { runMemoryCommand } from "./memory/command.mjs";
39
39
  import { normalizeRemoteMemorySources } from "./memory/remote/config.mjs";
40
40
  import { resolveMemoryRoot } from "./memory/root.mjs";
41
- import { runBrowserCommand } from "./browser/cli/command.mjs";
41
+ import { runConfiguredCliCommand } from "./cli/startup/configured-command.mjs";
42
+ import { maybeRunGatewayDaemonCommand } from "./cli/startup/gateway-daemon-command.mjs";
42
43
  import { ensureBrowserDaemon } from "./browser/client/lifecycle.mjs";
43
44
  export async function run(argv) {
44
45
  const cwd = process.cwd();
@@ -77,14 +78,8 @@ export async function run(argv) {
77
78
  args.memoryRoot = resolveMemoryRoot(config.memoryRoot, stateRoot);
78
79
  return await runMemoryCommand(args, { homeDir: homedir() });
79
80
  }
80
- if (args.command?.name === "browser") {
81
- try {
82
- return await runBrowserCommand(args, { stateRoot });
83
- } catch (err) {
84
- process.stderr.write(`Error: ${err.message}\n`);
85
- return 1;
86
- }
87
- }
81
+ const configuredCommand = await runConfiguredCliCommand(args, { config, cwd, stateRoot });
82
+ if (configuredCommand.handled) return configuredCommand.code;
88
83
  if (!existsSync(stateRoot)) mkdirSync(stateRoot, { recursive: true });
89
84
  await ensureBrowserDaemon({ stateRoot }).catch(() => {});
90
85
  const logger = createLogger({ logDir: join(stateRoot, "logs") });
@@ -243,6 +238,8 @@ export async function run(argv) {
243
238
  ui,
244
239
  });
245
240
  refreshStatusBar();
241
+ const gatewayDaemonCommand = await maybeRunGatewayDaemonCommand(args, { config, cwd, runner, currentProject, memoryStore, ui, logger });
242
+ if (gatewayDaemonCommand.handled) return gatewayDaemonCommand.code;
246
243
 
247
244
  if (args.prompt) {
248
245
  turnRunning = true;
@@ -0,0 +1,36 @@
1
+ export function isMemoryIdLike(value) {
2
+ return /^mem_[a-z0-9_-]+$/i.test(String(value ?? ""));
3
+ }
4
+
5
+ export function isSingleEditAway(left, right) {
6
+ if (left === right) return true;
7
+ if (Math.abs(left.length - right.length) > 1) return false;
8
+ if (left.length === right.length) return hasSingleSubstitution(left, right);
9
+ return hasSingleInsertionOrDeletion(left, right);
10
+ }
11
+
12
+ function hasSingleSubstitution(left, right) {
13
+ let mismatches = 0;
14
+ for (let i = 0; i < left.length; i += 1) {
15
+ if (left[i] !== right[i]) mismatches += 1;
16
+ if (mismatches > 1) return false;
17
+ }
18
+ return mismatches === 1;
19
+ }
20
+
21
+ function hasSingleInsertionOrDeletion(left, right) {
22
+ const shorter = left.length < right.length ? left : right;
23
+ const longer = left.length < right.length ? right : left;
24
+ let edits = 0;
25
+ for (let i = 0, j = 0; i < shorter.length && j < longer.length; ) {
26
+ if (shorter[i] === longer[j]) {
27
+ i += 1;
28
+ j += 1;
29
+ } else {
30
+ edits += 1;
31
+ j += 1;
32
+ if (edits > 1) return false;
33
+ }
34
+ }
35
+ return true;
36
+ }
@@ -13,6 +13,7 @@ import {
13
13
  import { scoreEntry, toHint } from "./markdown/markdown-recall.mjs";
14
14
  import { clearMarkdownMemoryIndex, loadMarkdownMemoryIndex, openMarkdownMemoryIndex, queryMarkdownMemoryIndex, replaceMarkdownMemoryIndex } from "./markdown/sqlite-index.mjs";
15
15
  import { softDeleteMemoryFile } from "./markdown/markdown-delete.mjs";
16
+ import { isMemoryIdLike, isSingleEditAway } from "./markdown/memory-id.mjs";
16
17
  import { openMarkdownRoot, searchMarkdownRoot } from "./search.mjs";
17
18
 
18
19
  export { formatRecallHints } from "./markdown/markdown-recall.mjs";
@@ -154,10 +155,9 @@ export class MarkdownMemoryStore {
154
155
  this.ensureFresh();
155
156
  const raw = String(identifier ?? "").trim();
156
157
  if (!raw) throw new Error("memory id or path is required");
157
- const entry = this.entries.get(raw);
158
- const path = entry ? entry.path : this.#resolveMemoryPath(raw);
159
- const opened = openMarkdownRoot({ root: this.root, path, ...options });
160
- return { ...opened, entry: entry ?? null };
158
+ const resolved = this.#resolveOpenTarget(raw);
159
+ const opened = openMarkdownRoot({ root: this.root, path: resolved.path, ...options });
160
+ return { ...opened, entry: resolved.entry, requestedId: resolved.requestedId };
161
161
  }
162
162
 
163
163
  save({ id = null, name = null, description = null, body = null, tags = null } = {}) {
@@ -261,11 +261,22 @@ export class MarkdownMemoryStore {
261
261
  return path;
262
262
  }
263
263
 
264
+ #resolveOpenTarget(raw) {
265
+ const exact = this.entries.get(raw);
266
+ if (exact) return { path: exact.path, entry: exact, requestedId: null };
267
+ if (!isMemoryIdLike(raw)) return { path: this.#resolveMemoryPath(raw), entry: null, requestedId: null };
268
+
269
+ const candidates = [...this.entries.values()].filter((entry) => isSingleEditAway(raw, entry.id));
270
+ if (candidates.length === 1) return { path: candidates[0].path, entry: candidates[0], requestedId: raw };
271
+ if (candidates.length > 1) {
272
+ throw new Error(`memory id is ambiguous: ${raw}; candidates: ${candidates.map((entry) => entry.id).join(", ")}`);
273
+ }
274
+ throw new Error(`memory not found: ${raw}`);
275
+ }
276
+
264
277
  #activeMemoryPaths() {
265
278
  return [...this.entries.values()]
266
279
  .filter((entry) => entry.status === "active")
267
280
  .map((entry) => entry.path);
268
281
  }
269
-
270
-
271
282
  }
@@ -146,12 +146,13 @@ function formatMemorySearchResults(results, requestedSource) {
146
146
 
147
147
  function formatLocalOpen(opened) {
148
148
  const range = opened.startLine && opened.endLine ? `lines: ${opened.startLine}-${opened.endLine}\n` : "";
149
- return `path: ${opened.path}\n${range}Use edit_file with this path for targeted edits.\n\n---\n${opened.content}`;
149
+ const correction = opened.requestedId && opened.entry?.id ? `matched id: ${opened.entry.id} (requested: ${opened.requestedId})\n` : "";
150
+ return `path: ${opened.path}\n${correction}${range}Use edit_file with this path for targeted edits.\n\ncontent:\n${opened.content}`;
150
151
  }
151
152
 
152
153
  function formatRemoteOpen(opened) {
153
154
  const range = opened.startLine && opened.endLine ? `lines: ${opened.startLine}-${opened.endLine}\n` : "";
154
- return `source: ${opened.source}\npath: ${opened.path}\n${range}Remote memory is read-only.\n\n---\n${opened.content}`;
155
+ return `source: ${opened.source}\npath: ${opened.path}\n${range}Remote memory is read-only.\n\ncontent:\n${opened.content}`;
155
156
  }
156
157
 
157
158
  function formatMemorySearchMiss(query, source) {
@@ -1,9 +1,9 @@
1
1
  import { spawn } from "node:child_process";
2
2
 
3
- export function openFileWithDefaultApp(filePath) {
3
+ export function openFileWithDefaultApp(filePath, { spawnFn = spawn } = {}) {
4
4
  return new Promise((resolve, reject) => {
5
- const { command, args } = openCommand(filePath);
6
- const child = spawn(command, args, { detached: true, stdio: "ignore" });
5
+ const { command, args, options } = openCommand(filePath);
6
+ const child = spawnFn(command, args, { ...options, detached: true, stdio: "ignore" });
7
7
  child.once("error", reject);
8
8
  child.once("spawn", () => {
9
9
  child.unref();
@@ -12,15 +12,14 @@ export function openFileWithDefaultApp(filePath) {
12
12
  });
13
13
  }
14
14
 
15
- function openCommand(filePath) {
16
- if (process.platform === "win32") {
17
- return {
18
- command: "powershell.exe",
19
- args: ["-NoProfile", "-Command", "Start-Process -LiteralPath $args[0]", filePath],
20
- };
15
+ export function openCommand(filePath, { platform = process.platform } = {}) {
16
+ if (platform === "win32") {
17
+ // cmd.exe start delegates to the user's shell association more reliably than
18
+ // PowerShell Start-Process for media files on Windows.
19
+ return { command: "cmd.exe", args: ["/c", "start", "", filePath] };
21
20
  }
22
21
 
23
- if (process.platform === "darwin") {
22
+ if (platform === "darwin") {
24
23
  return { command: "open", args: [filePath] };
25
24
  }
26
25