@evref-bl/pharo-agent 0.15.0 → 0.17.0
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/.claude-plugin/plugin.json +1 -1
- package/README.md +9 -8
- package/package.json +3 -3
- package/plugin.json +1 -1
- package/skills/pharo-feedback/SKILL.md +1 -1
- package/skills/pharo-setup/SKILL.md +11 -27
- package/src/cli.js +65 -39
- package/src/gateway.js +33 -0
- package/src/help.js +25 -39
- package/src/hosts.js +3 -1
- package/src/images.js +3 -2
- package/src/init.js +68 -53
- package/src/live.js +127 -120
- package/src/mcpServer.js +23 -35
- package/src/mcpYaml.js +102 -0
- package/src/runtime.js +17 -1
- package/src/toolCommand.js +216 -0
package/README.md
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
`pharo-agent` gives a coding agent a Pharo Launcher of its own. It can see the
|
|
4
4
|
templates the launcher offers, create images from them, start and stop them,
|
|
5
5
|
and work inside the ones it started: classes, methods, senders, tests,
|
|
6
|
-
refactorings, critiques, and the rest of what a Pharo IDE does. On macOS
|
|
7
|
-
can also read and edit an image's source as files.
|
|
6
|
+
refactorings, critiques, and the rest of what a Pharo IDE does. On macOS and
|
|
7
|
+
Linux it can also read and edit an image's source as files.
|
|
8
8
|
|
|
9
9
|
It works with Claude Code, Codex and OpenCode.
|
|
10
10
|
|
|
@@ -32,10 +32,10 @@ tested together, and gives the agent one command surface over the lot.
|
|
|
32
32
|
- Node 22.12 or later.
|
|
33
33
|
- [Pharo Launcher](https://pharo-project.github.io/pharo-launcher/), which is
|
|
34
34
|
where images and templates come from.
|
|
35
|
-
- For editing source as files: macOS
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
rather than failing.
|
|
35
|
+
- For editing source as files: macOS, which needs nothing more, or Linux with
|
|
36
|
+
FUSE 3 (the `fuse3` package, installed by default on most distributions).
|
|
37
|
+
Its daemon is downloaded for you. Where a projection cannot mount, the image
|
|
38
|
+
starts with its Pharo tools and a warning saying why, rather than failing.
|
|
39
39
|
|
|
40
40
|
The image tools cover Pharo 12 to 15. An image outside that range still
|
|
41
41
|
starts, and says what it will not have.
|
|
@@ -76,10 +76,11 @@ its schemas stay out of the agent's context. The tools belong to the image, so
|
|
|
76
76
|
what exists depends on what that image has loaded. [Tools](docs/tools.md)
|
|
77
77
|
covers both, and serving a different MCP from an image.
|
|
78
78
|
|
|
79
|
-
**Source as files, on macOS.** Starting an image mounts its source below
|
|
79
|
+
**Source as files, on macOS and Linux.** Starting an image mounts its source below
|
|
80
80
|
`.pharo-agent/mounts`. A file edited there is compiled into the live image:
|
|
81
81
|
the files and the tools are two views of one image, not of an export and an
|
|
82
|
-
image.
|
|
82
|
+
image. Setting `PHARO_AGENT_PROJECTION=off` in the environment starts images
|
|
83
|
+
without mounting anything.
|
|
83
84
|
|
|
84
85
|
**Guidance for the agent.** Setting a project up writes a Pharo guide into it,
|
|
85
86
|
and a conditional line in the host's own guide pointing at that guide for
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evref-bl/pharo-agent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Terminal-first Pharo integration for coding agents.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
"test": "node --test"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@evref-bl/pharo-mcp": "3.
|
|
36
|
-
"@evref-bl/plexus": "0.
|
|
35
|
+
"@evref-bl/pharo-mcp": "3.6.0",
|
|
36
|
+
"@evref-bl/plexus": "0.15.1",
|
|
37
37
|
"@modelcontextprotocol/sdk": "1.32.2"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
package/plugin.json
CHANGED
|
@@ -63,7 +63,7 @@ instead of a new issue.
|
|
|
63
63
|
One issue per problem; findings that share a cause make one issue. Title it
|
|
64
64
|
with what goes wrong, in the tool's own terms. Keep the body short:
|
|
65
65
|
|
|
66
|
-
- **Tool:** its name; `pharo-agent 0.
|
|
66
|
+
- **Tool:** its name; `pharo-agent 0.17.0`; the image's Pharo version, which
|
|
67
67
|
`image_list` gives; the host you run in.
|
|
68
68
|
- **What happened:** what you asked for and what came back, the error quoted
|
|
69
69
|
and trimmed.
|
|
@@ -5,21 +5,20 @@ description: Enable or refresh Pharo image tooling in the current Pharo project.
|
|
|
5
5
|
|
|
6
6
|
Run this only when the user asks to enable or refresh Pharo tooling in this project.
|
|
7
7
|
|
|
8
|
-
From the project root, run the released CLI that matches this plugin
|
|
8
|
+
From the project root, run the released CLI that matches this plugin, naming
|
|
9
|
+
the host this chat runs in: `--host claude` in Claude Code, `--host codex` in
|
|
10
|
+
Codex, `--host opencode` in OpenCode.
|
|
9
11
|
|
|
10
12
|
```sh
|
|
11
|
-
npx --yes @evref-bl/pharo-agent@0.
|
|
13
|
+
npx --yes @evref-bl/pharo-agent@0.17.0 init --host claude
|
|
12
14
|
```
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
block and this project's own files, so Pharo guidance stays out of unrelated
|
|
18
|
-
work.
|
|
16
|
+
It writes the project's Pharo guide, skills and references, and a block in the
|
|
17
|
+
host's guide that points to them; no host configuration. Re-running it after
|
|
18
|
+
an upgrade replaces those files and keeps image declarations and image code.
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
`.claude/settings.json`:
|
|
20
|
+
In Claude Code, enable the plugin for this project in `.claude/settings.json`;
|
|
21
|
+
the next session gets the tools:
|
|
23
22
|
|
|
24
23
|
```json
|
|
25
24
|
{
|
|
@@ -35,26 +34,11 @@ this project rather than configuring a server by hand. Commit
|
|
|
35
34
|
}
|
|
36
35
|
```
|
|
37
36
|
|
|
38
|
-
|
|
39
|
-
one. Enabling it per project keeps every other project untouched.
|
|
40
|
-
|
|
41
|
-
After upgrading the plugin/CLI, re-run the setup command from the project root
|
|
42
|
-
with the same host to refresh the generated guide and copied skills/references.
|
|
43
|
-
Those files are overwritten; keep project-specific guidance outside the host's
|
|
44
|
-
managed block and outside generated/copied paths. Omit `--mount-root` to retain
|
|
45
|
-
existing settings. Image declarations, runtime state, and live image code are
|
|
46
|
-
preserved. This refresh does not install a newer package or upgrade image tools.
|
|
47
|
-
|
|
48
|
-
Setup initializes the current directory, creating `plexus.json` with
|
|
49
|
-
no image when it is absent. Register one before the first
|
|
50
|
-
`pharo-agent image start`:
|
|
37
|
+
Then declare the project's first image, its default:
|
|
51
38
|
|
|
52
39
|
```sh
|
|
53
40
|
pharo-agent image templates
|
|
54
41
|
pharo-agent image add dev --template '<launcher template name>'
|
|
55
42
|
```
|
|
56
43
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
After setup, read `pharo.AGENTS.md` in this chat before doing Pharo work. A
|
|
60
|
-
new chat picks up the conditional project guidance automatically.
|
|
44
|
+
Read `pharo.AGENTS.md` before doing Pharo work.
|
package/src/cli.js
CHANGED
|
@@ -2,11 +2,18 @@ import { hostChoices, hostList } from "./hosts.js";
|
|
|
2
2
|
import { usage } from "./help.js";
|
|
3
3
|
import { initialize } from "./init.js";
|
|
4
4
|
import { addImage, listImages, removeImage, useImage } from "./images.js";
|
|
5
|
-
import { callImageTool,
|
|
5
|
+
import { callImageTool, listAdvertisedTools, toolContract } from "./gateway.js";
|
|
6
6
|
import { serveImagesMcp } from "./mcpServer.js";
|
|
7
7
|
import { listImageTemplates, startAgentImage, stopAgentImage } from "./live.js";
|
|
8
8
|
import { findProjectRoot } from "./project.js";
|
|
9
9
|
import { readRunningImage } from "./runtime.js";
|
|
10
|
+
import {
|
|
11
|
+
isToolName,
|
|
12
|
+
resultText,
|
|
13
|
+
toolArguments,
|
|
14
|
+
toolListing,
|
|
15
|
+
toolUsage,
|
|
16
|
+
} from "./toolCommand.js";
|
|
10
17
|
|
|
11
18
|
function parseInit(args) {
|
|
12
19
|
let host;
|
|
@@ -97,20 +104,6 @@ function requireNoArguments(command, args) {
|
|
|
97
104
|
}
|
|
98
105
|
}
|
|
99
106
|
|
|
100
|
-
function parseToolArguments(value) {
|
|
101
|
-
if (value === undefined) return {};
|
|
102
|
-
let parsed;
|
|
103
|
-
try {
|
|
104
|
-
parsed = JSON.parse(value);
|
|
105
|
-
} catch {
|
|
106
|
-
throw new Error("Tool arguments must be a JSON object.");
|
|
107
|
-
}
|
|
108
|
-
if (!parsed || Array.isArray(parsed) || typeof parsed !== "object") {
|
|
109
|
-
throw new Error("Tool arguments must be a JSON object.");
|
|
110
|
-
}
|
|
111
|
-
return parsed;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
107
|
// A tool the image answered with an error is still an answer: the MCP result
|
|
115
108
|
// carries the Pharo exception, and a caller wants to read it. Print it as
|
|
116
109
|
// always, and report the failure in the exit status too, so `set -e` and
|
|
@@ -119,8 +112,61 @@ export function exitCodeForToolResult(result) {
|
|
|
119
112
|
return result?.isError === true ? 1 : 0;
|
|
120
113
|
}
|
|
121
114
|
|
|
115
|
+
function takeFlag(args, flag) {
|
|
116
|
+
return { present: args.includes(flag), rest: args.filter((value) => value !== flag) };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// `pharo-agent <tool> ...`: the answer's text for a reader, or the MCP result
|
|
120
|
+
// with --json. Its schema is fetched only when the words need reading.
|
|
121
|
+
// The MCP server's own tools manage images; from the shell they are the
|
|
122
|
+
// image commands, which an agent that knows the server may not have read.
|
|
123
|
+
const lifecycleTools = new Set(["image_templates", "image_add", "image_start", "image_list", "image_stop"]);
|
|
124
|
+
|
|
125
|
+
async function runTool([toolName, ...words]) {
|
|
126
|
+
if (lifecycleTools.has(toolName)) {
|
|
127
|
+
throw new Error(`${toolName} is pharo-agent image ${toolName.slice("image_".length)} here.`);
|
|
128
|
+
}
|
|
129
|
+
const { image, rest: withoutImage } = takeImageOption(words);
|
|
130
|
+
const json = takeFlag(withoutImage, "--json");
|
|
131
|
+
const help = takeFlag(json.rest, "--help");
|
|
132
|
+
if (help.present) {
|
|
133
|
+
// Help answers before a project or image exists; only the tool's own
|
|
134
|
+
// arguments need its image.
|
|
135
|
+
try {
|
|
136
|
+
const projectRoot = findProjectRoot();
|
|
137
|
+
console.log(toolUsage(await toolContract(projectRoot, readRunningImage(projectRoot, image), toolName)));
|
|
138
|
+
} catch (error) {
|
|
139
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
140
|
+
console.log(`${usage(["<tool>"])}\n\n${toolName}'s own arguments come from a running image: ${reason}`);
|
|
141
|
+
}
|
|
142
|
+
return 0;
|
|
143
|
+
}
|
|
144
|
+
const projectRoot = findProjectRoot();
|
|
145
|
+
const running = readRunningImage(projectRoot, image);
|
|
146
|
+
const argumentsValue = help.rest.length === 0 ? {}
|
|
147
|
+
: toolArguments(toolName, help.rest, (await toolContract(projectRoot, running, toolName)).inputSchema);
|
|
148
|
+
const result = await callImageTool(projectRoot, running, toolName, argumentsValue);
|
|
149
|
+
console.log(json.present ? JSON.stringify(result, null, 2) : resultText(result));
|
|
150
|
+
return exitCodeForToolResult(result);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function listTools(words) {
|
|
154
|
+
const { image, rest: withoutImage } = takeImageOption(words);
|
|
155
|
+
const json = takeFlag(withoutImage, "--json");
|
|
156
|
+
requireNoArguments("list", json.rest);
|
|
157
|
+
const projectRoot = findProjectRoot();
|
|
158
|
+
const tools = await listAdvertisedTools(projectRoot, readRunningImage(projectRoot, image));
|
|
159
|
+
console.log(json.present ? JSON.stringify(tools, null, 2) : toolListing(tools));
|
|
160
|
+
}
|
|
161
|
+
|
|
122
162
|
export async function main(args) {
|
|
123
163
|
const [command, ...rest] = args;
|
|
164
|
+
// A snake_case word is an image tool; its own --help reads the tool's
|
|
165
|
+
// schema, and the word help is a value it may take, not a request for
|
|
166
|
+
// the CLI's usage.
|
|
167
|
+
if (command && isToolName(command)) {
|
|
168
|
+
return runTool(args);
|
|
169
|
+
}
|
|
124
170
|
// A caller reaching for help is asking the same question wherever the word
|
|
125
171
|
// lands, and `image add --help` answering that it needs an image id is no
|
|
126
172
|
// answer at all.
|
|
@@ -143,6 +189,10 @@ export async function main(args) {
|
|
|
143
189
|
await serveImagesMcp(findProjectRoot, mcpImage);
|
|
144
190
|
return;
|
|
145
191
|
}
|
|
192
|
+
if (command === "list") {
|
|
193
|
+
await listTools(rest);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
146
196
|
const projectRoot = findProjectRoot();
|
|
147
197
|
if (command === "image") {
|
|
148
198
|
const [subcommand, ...imageArgs] = rest;
|
|
@@ -193,29 +243,5 @@ export async function main(args) {
|
|
|
193
243
|
}
|
|
194
244
|
throw new Error(`Unsupported image command: ${subcommand ?? ""}\n${usage()}`);
|
|
195
245
|
}
|
|
196
|
-
const { image, rest: commandArgs } = takeImageOption(rest);
|
|
197
|
-
if (command === "pharo") {
|
|
198
|
-
const [toolName, argumentsJson, ...extra] = commandArgs;
|
|
199
|
-
if (toolName === "list") {
|
|
200
|
-
if (argumentsJson || extra.length > 0) throw new Error("pharo list does not accept arguments");
|
|
201
|
-
console.log(JSON.stringify(
|
|
202
|
-
await listImageTools(projectRoot, readRunningImage(projectRoot, image)),
|
|
203
|
-
null,
|
|
204
|
-
2,
|
|
205
|
-
));
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
if (!toolName || extra.length > 0) {
|
|
209
|
-
throw new Error("pharo requires one tool name and an optional JSON object");
|
|
210
|
-
}
|
|
211
|
-
const result = await callImageTool(
|
|
212
|
-
projectRoot,
|
|
213
|
-
readRunningImage(projectRoot, image),
|
|
214
|
-
toolName,
|
|
215
|
-
parseToolArguments(argumentsJson),
|
|
216
|
-
);
|
|
217
|
-
console.log(JSON.stringify(result, null, 2));
|
|
218
|
-
return exitCodeForToolResult(result);
|
|
219
|
-
}
|
|
220
246
|
throw new Error(`Unsupported command: ${command}\n${usage()}`);
|
|
221
247
|
}
|
package/src/gateway.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
import { PlexusGateway } from "@evref-bl/plexus/gateway";
|
|
3
3
|
import { loadProjectState } from "@evref-bl/plexus";
|
|
4
|
+
import { toolContractFrom } from "./mcpYaml.js";
|
|
4
5
|
import {
|
|
5
6
|
selectRunningImage,
|
|
6
7
|
unappliedPharoMcpPin,
|
|
@@ -65,6 +66,38 @@ export async function listImageTools(projectRoot, running) {
|
|
|
65
66
|
return gateway.refreshPharoTools();
|
|
66
67
|
}
|
|
67
68
|
|
|
69
|
+
// PLexus adds its routing handle to every schema it lists; a caller never
|
|
70
|
+
// passes it, so the CLI neither offers nor describes it.
|
|
71
|
+
function withoutRouting(tool) {
|
|
72
|
+
const { required: listed = [], ...schema } = tool.inputSchema ?? {};
|
|
73
|
+
const properties = { ...(schema.properties ?? {}) };
|
|
74
|
+
delete properties.mcpServerId;
|
|
75
|
+
const required = listed.filter((name) => name !== "mcpServerId");
|
|
76
|
+
return { ...tool, inputSchema: { ...schema, properties, ...(required.length ? { required } : {}) } };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export async function listAdvertisedTools(projectRoot, running) {
|
|
80
|
+
return (await listImageTools(projectRoot, running)).map(withoutRouting);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* A tool's contract: from the advertised list when it is there, otherwise
|
|
85
|
+
* from tool_get, which reaches the rest of the catalogue.
|
|
86
|
+
*/
|
|
87
|
+
export async function toolContract(projectRoot, running, toolName) {
|
|
88
|
+
const advertised = (await listAdvertisedTools(projectRoot, running)).find((tool) => tool.name === toolName);
|
|
89
|
+
if (advertised) return advertised;
|
|
90
|
+
const answer = /** @type {{ isError?: boolean, content?: { text?: string }[] }} */ (
|
|
91
|
+
await callImageTool(projectRoot, running, "tool_get", { name: toolName })
|
|
92
|
+
);
|
|
93
|
+
const contract = answer?.isError ? undefined
|
|
94
|
+
: toolContractFrom((answer?.content ?? []).map((part) => part.text ?? "").join("\n"));
|
|
95
|
+
if (!contract) {
|
|
96
|
+
throw new Error(`Image ${running.imageId} has no tool ${toolName}. pharo-agent tool_search <words> finds one.`);
|
|
97
|
+
}
|
|
98
|
+
return withoutRouting(contract);
|
|
99
|
+
}
|
|
100
|
+
|
|
68
101
|
export function withImageSelector(tool, defaultImage) {
|
|
69
102
|
const inputSchema = tool.inputSchema ?? {};
|
|
70
103
|
const properties = { ...(inputSchema.properties ?? {}) };
|
package/src/help.js
CHANGED
|
@@ -3,81 +3,67 @@ import { hostChoices, hostIds } from "./hosts.js";
|
|
|
3
3
|
const commands = {
|
|
4
4
|
init: {
|
|
5
5
|
usage: `init --host <${hostChoices()}> [--mount-root <path>]`,
|
|
6
|
-
description: "
|
|
7
|
-
+ "
|
|
8
|
-
+ "The mount root defaults to .pharo-agent/mounts; omit --mount-root to keep an existing setting.",
|
|
6
|
+
description: "Write this project's Pharo guide, skills and references for the host. Re-run to refresh\n"
|
|
7
|
+
+ "them. --mount-root sets where sources mount, .pharo-agent/mounts by default.",
|
|
9
8
|
example: `init --host ${hostIds[0]}`,
|
|
10
9
|
},
|
|
11
10
|
"image list": {
|
|
12
11
|
usage: "image list",
|
|
13
|
-
description: "List the
|
|
14
|
-
+ "projection port of each image this project has running. An image this project\n"
|
|
15
|
-
+ "started that has since exited is listed as exited.",
|
|
12
|
+
description: "List the declared images, the default, and the mount and port of each running one.",
|
|
16
13
|
example: "image list",
|
|
17
14
|
},
|
|
18
15
|
"image templates": {
|
|
19
16
|
usage: "image templates",
|
|
20
|
-
description: "List
|
|
21
|
-
+ "needs --category when passed to image add.",
|
|
17
|
+
description: "List the templates images can be made from, with their categories.",
|
|
22
18
|
example: "image templates",
|
|
23
19
|
},
|
|
24
20
|
"image add": {
|
|
25
21
|
usage: "image add <id> --template <name> [--category <name>] [--profile <id>]",
|
|
26
|
-
description: "Declare an image
|
|
27
|
-
+ "
|
|
28
|
-
|
|
29
|
-
example: "image add dev --template '<template name>'",
|
|
22
|
+
description: "Declare an image; image start creates it. Name it after what it is for, like a branch.\n"
|
|
23
|
+
+ "The first declared image is the default. --category is for a name two categories offer.",
|
|
24
|
+
example: "image add pretty-print-formatter --template '<template name>'",
|
|
30
25
|
},
|
|
31
26
|
"image use": {
|
|
32
27
|
usage: "image use <id>",
|
|
33
|
-
description: "Make
|
|
28
|
+
description: "Make an image the default.",
|
|
34
29
|
example: "image use dev",
|
|
35
30
|
},
|
|
36
31
|
"image remove": {
|
|
37
32
|
usage: "image remove <id>",
|
|
38
|
-
description: "
|
|
39
|
-
+ "Removing the default image clears the default selection.",
|
|
33
|
+
description: "Drop an image's declaration. Stop it first; its files are kept.",
|
|
40
34
|
example: "image remove dev",
|
|
41
35
|
},
|
|
42
36
|
"image start": {
|
|
43
37
|
usage: "image start [<id>]",
|
|
44
|
-
description: "Create if needed and start an image
|
|
45
|
-
+ "
|
|
46
|
-
+ "mounted on macOS with fuse-t; elsewhere the image starts without one and says so.\n"
|
|
47
|
-
+ "Pharo versions outside the MCP support range start without tools or a projection.",
|
|
38
|
+
description: "Create if needed and start an image, the default one without an id. Others keep running.\n"
|
|
39
|
+
+ "On macOS and Linux its source mounts as files, unless PHARO_AGENT_PROJECTION=off.",
|
|
48
40
|
example: "image start dev",
|
|
49
41
|
},
|
|
50
42
|
"image stop": {
|
|
51
43
|
usage: "image stop [<id>] [--all]",
|
|
52
|
-
description: "Stop
|
|
53
|
-
+ "image when no id is given. Use --all to stop every image this project started.",
|
|
44
|
+
description: "Stop an image and its source mount, the default one without an id, or --all of them.",
|
|
54
45
|
example: "image stop dev",
|
|
55
46
|
},
|
|
47
|
+
list: {
|
|
48
|
+
usage: "list [--image <id>] [--json]",
|
|
49
|
+
description: "List a running image's advertised tools, one line each. tool_search finds the rest.",
|
|
50
|
+
example: "list",
|
|
51
|
+
},
|
|
52
|
+
"<tool>": {
|
|
53
|
+
usage: "<tool> [<value>] [--<argument> <value>]... [--image <id>] [--json]",
|
|
54
|
+
description: "Call an image tool. Its arguments are flags, - reads a value from stdin, and\n"
|
|
55
|
+
+ "<tool> --help lists them. Prints the answer's text, or the MCP result with --json.",
|
|
56
|
+
example: "class_get Point",
|
|
57
|
+
},
|
|
56
58
|
mcp: {
|
|
57
59
|
usage: "mcp [--image <id>]",
|
|
58
|
-
description: "Serve this project's images as a
|
|
59
|
-
+ "--image to use the project's default image. It starts whether or not an image\n"
|
|
60
|
-
+ "is running: image_start starts one and the image's own tools join the list. The\n"
|
|
61
|
-
+ "server exits with this command, and the plugin's .mcp.json starts it for a host.",
|
|
60
|
+
description: "Serve this project's images as a stdio MCP server, as the plugin does for a host.",
|
|
62
61
|
example: "mcp --image dev",
|
|
63
62
|
},
|
|
64
|
-
"pharo list": {
|
|
65
|
-
usage: "pharo list [--image <id>]",
|
|
66
|
-
description: "List a running image's advertised tools. Omit --image to use the project's default image.\n"
|
|
67
|
-
+ "Use tool_search and tool_get to discover and inspect the rest of its catalogue.",
|
|
68
|
-
example: "pharo list",
|
|
69
|
-
},
|
|
70
|
-
pharo: {
|
|
71
|
-
usage: "pharo <tool> [arguments-json] [--image <id>]",
|
|
72
|
-
description: "Call a tool in a running image. Arguments must be a JSON object and default to {}.\n"
|
|
73
|
-
+ "Omit --image to use the project's default image. Inspect the tool's schema with tool_get\n"
|
|
74
|
-
+ "before calling it; a failed tool result makes the command exit unsuccessfully.",
|
|
75
|
-
example: 'pharo tool_get \'{"name":"class_search"}\'',
|
|
76
|
-
},
|
|
77
63
|
};
|
|
78
64
|
|
|
79
65
|
export function usage(args = []) {
|
|
80
|
-
const key = ["image"
|
|
66
|
+
const key = args[0] === "image" && args[1]
|
|
81
67
|
? `${args[0]} ${args[1]}`
|
|
82
68
|
: args[0];
|
|
83
69
|
const command = Object.hasOwn(commands, key) ? commands[key]
|
package/src/hosts.js
CHANGED
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
export const supportedHosts = [
|
|
5
5
|
{ id: "claude", name: "Claude Code", guide: "CLAUDE.md", plugin: true },
|
|
6
6
|
{ id: "codex", name: "Codex", guide: "AGENTS.md", plugin: true },
|
|
7
|
-
|
|
7
|
+
// No plugin manifest to declare the MCP server in: init writes it into the
|
|
8
|
+
// project's own configuration, which OpenCode reads for that project only.
|
|
9
|
+
{ id: "opencode", name: "OpenCode", guide: "AGENTS.md", plugin: false, serverConfig: "opencode.json" },
|
|
8
10
|
];
|
|
9
11
|
|
|
10
12
|
export const hostIds = supportedHosts.map((host) => host.id);
|
package/src/images.js
CHANGED
|
@@ -2,6 +2,7 @@ import { declareProjectImage, removeProjectImage } from "@evref-bl/plexus";
|
|
|
2
2
|
import { configuredImages } from "./project.js";
|
|
3
3
|
import {
|
|
4
4
|
defaultImageId,
|
|
5
|
+
notConfigured,
|
|
5
6
|
readConfig,
|
|
6
7
|
readRunningImages,
|
|
7
8
|
recordedImageIsRunning,
|
|
@@ -93,7 +94,7 @@ export function addImage(projectRoot, request) {
|
|
|
93
94
|
|
|
94
95
|
export function useImage(projectRoot, id) {
|
|
95
96
|
if (!configuredImages(projectRoot).some((image) => image.id === id)) {
|
|
96
|
-
throw
|
|
97
|
+
throw notConfigured(projectRoot, id);
|
|
97
98
|
}
|
|
98
99
|
writeConfig(projectRoot, { ...readConfig(projectRoot), defaultImage: id });
|
|
99
100
|
return { defaultImage: id };
|
|
@@ -101,7 +102,7 @@ export function useImage(projectRoot, id) {
|
|
|
101
102
|
|
|
102
103
|
export function removeImage(projectRoot, id) {
|
|
103
104
|
if (!configuredImages(projectRoot).some((image) => image.id === id)) {
|
|
104
|
-
throw
|
|
105
|
+
throw notConfigured(projectRoot, id);
|
|
105
106
|
}
|
|
106
107
|
assertNotRunning(projectRoot, id, "removing it");
|
|
107
108
|
removeProjectImage(projectRoot, id);
|