march-cli 0.1.31 → 0.1.33
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/package.json +1 -1
- package/src/agent/output/binary-output-sink.mjs +17 -0
- package/src/agent/output/send-binary-tool.mjs +84 -0
- package/src/agent/tools.mjs +3 -0
- package/src/cli/args.mjs +3 -1
- package/src/cli/commands/help-command.mjs +1 -1
- package/src/cli/commands/mode-command.mjs +21 -0
- package/src/cli/repl-loop.mjs +1 -0
- package/src/cli/slash-commands.mjs +8 -0
- package/src/cli/startup/configured-command.mjs +17 -0
- package/src/cli/startup/gateway-daemon-command.mjs +21 -0
- package/src/cli/tui/markdown-renderer.mjs +37 -23
- package/src/config/loader.mjs +31 -0
- package/src/context/system-core/base.md +6 -2
- package/src/gateway/command-router.mjs +44 -0
- package/src/gateway/command.mjs +107 -0
- package/src/gateway/config.mjs +62 -0
- package/src/gateway/daemon.mjs +41 -0
- package/src/gateway/handler.mjs +29 -0
- package/src/gateway/message.mjs +37 -0
- package/src/gateway/platform-registry.mjs +38 -0
- package/src/gateway/platforms/telegram.mjs +241 -0
- package/src/gateway/runner-bridge.mjs +55 -0
- package/src/gateway/runtime/queue.mjs +46 -0
- package/src/gateway/session-store.mjs +46 -0
- package/src/gateway/setup/command.mjs +150 -0
- package/src/gateway/workspace-command.mjs +40 -0
- package/src/image-gen/tool.mjs +16 -9
- package/src/lsp/client.mjs +1 -0
- package/src/lsp/managed-node-server.mjs +1 -0
- package/src/lsp/server-definitions.mjs +8 -0
- package/src/main.mjs +6 -9
- package/src/memory/markdown/markdown-format.mjs +0 -9
- package/src/memory/markdown-store.mjs +3 -4
- package/src/memory/markdown-tools.mjs +1 -1
- package/src/platform/open-file.mjs +9 -10
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export function parseWorkspaceCommand(input) {
|
|
2
|
+
const trimmed = String(input ?? "").trim();
|
|
3
|
+
if (trimmed === "/workspace") return { type: "show" };
|
|
4
|
+
if (trimmed === "/workspaces") return { type: "list" };
|
|
5
|
+
const match = trimmed.match(/^\/workspace\s+set\s+(\S+)$/);
|
|
6
|
+
if (match) return { type: "set", alias: match[1] };
|
|
7
|
+
if (trimmed.startsWith("/workspace ")) return { type: "error", message: "Usage: /workspace, /workspaces, or /workspace set <alias>" };
|
|
8
|
+
return { type: "none" };
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function handleWorkspaceCommand(command, { session, sessionStore }) {
|
|
12
|
+
if (command.type === "error") return [`Error: ${command.message}`];
|
|
13
|
+
if (command.type === "show") return [formatCurrentWorkspace(session)];
|
|
14
|
+
if (command.type === "list") return formatWorkspaceList(sessionStore.listWorkspaces(), session.workspaceAlias);
|
|
15
|
+
if (command.type === "set") {
|
|
16
|
+
try {
|
|
17
|
+
sessionStore.setWorkspace(session, command.alias);
|
|
18
|
+
return [`Workspace: ${session.workspaceAlias} (${session.workspaceRoot})`];
|
|
19
|
+
} catch (err) {
|
|
20
|
+
return [`Error: ${err.message}`];
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function formatCurrentWorkspace(session) {
|
|
27
|
+
if (!session.workspaceAlias || !session.workspaceRoot) return "Workspace: not configured";
|
|
28
|
+
return `Workspace: ${session.workspaceAlias} (${session.workspaceRoot})`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function formatWorkspaceList(workspaces, currentAlias) {
|
|
32
|
+
if (workspaces.length === 0) return ["No gateway workspaces configured."];
|
|
33
|
+
return [
|
|
34
|
+
"Gateway workspaces:",
|
|
35
|
+
...workspaces.map((workspace) => {
|
|
36
|
+
const marker = workspace.alias === currentAlias ? "*" : " ";
|
|
37
|
+
return `${marker} ${workspace.alias}: ${workspace.root}`;
|
|
38
|
+
}),
|
|
39
|
+
];
|
|
40
|
+
}
|
package/src/image-gen/tool.mjs
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { defineTool } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { Type } from "typebox";
|
|
3
|
+
import { basename } from "node:path";
|
|
4
|
+
import { sendBinaryOutput } from "../agent/output/binary-output-sink.mjs";
|
|
3
5
|
import { toolText } from "../agent/tool-result.mjs";
|
|
4
|
-
import { openFileWithDefaultApp } from "../platform/open-file.mjs";
|
|
5
6
|
import { generateImage } from "./provider.mjs";
|
|
6
7
|
|
|
7
8
|
export function createImageGenTool({
|
|
8
9
|
authStorage,
|
|
9
10
|
projectMarchDir,
|
|
10
11
|
generateImageImpl = generateImage,
|
|
11
|
-
|
|
12
|
+
sendBinary = sendBinaryOutput,
|
|
12
13
|
}) {
|
|
13
14
|
return defineTool({
|
|
14
15
|
name: "image_generate",
|
|
@@ -48,7 +49,7 @@ export function createImageGenTool({
|
|
|
48
49
|
try {
|
|
49
50
|
const { prompt, quality = "medium", aspectRatio = "1:1", auto_open: autoOpen = true } = params;
|
|
50
51
|
const image = await generateImageImpl({ prompt, quality, aspectRatio, authStorage, projectMarchDir });
|
|
51
|
-
const
|
|
52
|
+
const outputResult = autoOpen ? await deliverGeneratedImage(image, sendBinary) : { opened: false, delivered: false };
|
|
52
53
|
return toolJson({
|
|
53
54
|
success: true,
|
|
54
55
|
image: image.marker,
|
|
@@ -57,8 +58,8 @@ export function createImageGenTool({
|
|
|
57
58
|
prompt,
|
|
58
59
|
aspectRatio,
|
|
59
60
|
quality,
|
|
60
|
-
...
|
|
61
|
-
}, { ...image, ...
|
|
61
|
+
...outputResult,
|
|
62
|
+
}, { ...image, ...outputResult });
|
|
62
63
|
} catch (err) {
|
|
63
64
|
return toolJson({
|
|
64
65
|
success: false,
|
|
@@ -70,12 +71,18 @@ export function createImageGenTool({
|
|
|
70
71
|
});
|
|
71
72
|
}
|
|
72
73
|
|
|
73
|
-
async function
|
|
74
|
+
async function deliverGeneratedImage(image, sendBinary) {
|
|
75
|
+
const binary = {
|
|
76
|
+
type: "image",
|
|
77
|
+
path: image.filePath,
|
|
78
|
+
filename: basename(image.filePath),
|
|
79
|
+
mimeType: image.mimeType,
|
|
80
|
+
};
|
|
74
81
|
try {
|
|
75
|
-
await
|
|
76
|
-
return { opened: true };
|
|
82
|
+
const sink = await sendBinary(binary);
|
|
83
|
+
return { opened: sink?.opened === true, delivered: true, sink };
|
|
77
84
|
} catch (err) {
|
|
78
|
-
return { opened: false, openError: err.message };
|
|
85
|
+
return { opened: false, delivered: false, openError: err.message };
|
|
79
86
|
}
|
|
80
87
|
}
|
|
81
88
|
|
package/src/lsp/client.mjs
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
81
|
-
|
|
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;
|
|
@@ -55,15 +55,6 @@ export function generateMemoryId() {
|
|
|
55
55
|
return `mem_${randomUUID().replace(/-/g, "").slice(0, 16)}`;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
export function slugify(value) {
|
|
59
|
-
return String(value ?? "memory")
|
|
60
|
-
.trim()
|
|
61
|
-
.toLowerCase()
|
|
62
|
-
.replace(/[^a-z0-9\u4e00-\u9fff]+/g, "-")
|
|
63
|
-
.replace(/^-+|-+$/g, "")
|
|
64
|
-
.slice(0, 80) || "memory";
|
|
65
|
-
}
|
|
66
|
-
|
|
67
58
|
export function walkMarkdownFiles(root) {
|
|
68
59
|
const out = [];
|
|
69
60
|
if (!existsSync(root)) return out;
|
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
normalizeText,
|
|
9
9
|
parseMemoryMarkdown,
|
|
10
10
|
quoteFtsTerm,
|
|
11
|
-
slugify,
|
|
12
11
|
walkMarkdownFiles,
|
|
13
12
|
} from "./markdown/markdown-format.mjs";
|
|
14
13
|
import { scoreEntry, toHint } from "./markdown/markdown-recall.mjs";
|
|
@@ -176,7 +175,7 @@ export class MarkdownMemoryStore {
|
|
|
176
175
|
if (!nextDescription) throw new Error("description is required");
|
|
177
176
|
if (!existing && body == null) throw new Error("body is required");
|
|
178
177
|
const nextBody = body ?? (existing ? parseMemoryMarkdown(readFileSync(existing.path, "utf8")).body : "");
|
|
179
|
-
const nextPath = existing?.path ?? this.#newMemoryPath(now,
|
|
178
|
+
const nextPath = existing?.path ?? this.#newMemoryPath(now, nextId);
|
|
180
179
|
mkdirSync(dirname(nextPath), { recursive: true });
|
|
181
180
|
const content = formatMemoryMarkdown({
|
|
182
181
|
frontmatter: {
|
|
@@ -248,11 +247,11 @@ export class MarkdownMemoryStore {
|
|
|
248
247
|
}
|
|
249
248
|
}
|
|
250
249
|
|
|
251
|
-
#newMemoryPath(isoDate,
|
|
250
|
+
#newMemoryPath(isoDate, id) {
|
|
252
251
|
const date = isoDate.slice(0, 10);
|
|
253
252
|
const [year, month, day] = date.split("-");
|
|
254
253
|
const week = `week${Math.ceil(Number(day) / 7)}`;
|
|
255
|
-
return join(this.root, year, month, week, `${date}-${
|
|
254
|
+
return join(this.root, year, month, week, `${date}-${id}.md`);
|
|
256
255
|
}
|
|
257
256
|
|
|
258
257
|
#resolveMemoryPath(raw) {
|
|
@@ -70,7 +70,7 @@ export function createMarkdownMemoryTools(store, { remoteSources = [] } = {}) {
|
|
|
70
70
|
name: "memory_save",
|
|
71
71
|
label: "Memory Save",
|
|
72
72
|
description:
|
|
73
|
-
"Create a Markdown memory or update whole fields on an existing memory. For targeted edits to an existing memory body or frontmatter, use memory_open to get the path, then edit_file. Before creating a new memory, merge related updates into an existing memory when they share the same topic or decision thread. New memories require name, description, body, and at least one tag because recall hints only use tags. When updating by id, omitted fields keep their existing values; passing tags replaces the full tag list.",
|
|
73
|
+
"Create a Markdown memory or update whole fields on an existing memory. Local memory filenames are id-based storage paths; frontmatter name is the user-visible title. For targeted edits to an existing memory body or frontmatter, use memory_open to get the path, then edit_file. Before creating a new memory, merge related updates into an existing memory when they share the same topic or decision thread. New memories require name, description, body, and at least one tag because recall hints only use tags. When updating by id, omitted fields keep their existing values; passing tags replaces the full tag list.",
|
|
74
74
|
parameters: Type.Object({
|
|
75
75
|
id: Type.Optional(Type.String({ description: "Existing memory id to update. Omit to create a new memory." })),
|
|
76
76
|
name: Type.Optional(Type.String({ description: "Memory name. Required when creating." })),
|
|
@@ -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 =
|
|
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 (
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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 (
|
|
22
|
+
if (platform === "darwin") {
|
|
24
23
|
return { command: "open", args: [filePath] };
|
|
25
24
|
}
|
|
26
25
|
|