opencode-manifold 0.4.7 → 0.4.9
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
|
@@ -71,8 +71,10 @@ Open Manifold requires:
|
|
|
71
71
|
|
|
72
72
|
1. **Install** using one of the methods above
|
|
73
73
|
2. **Run `/manifold-init`** in the opencode TUI to set up agents, skills, and the Manifold directory
|
|
74
|
-
3. **
|
|
75
|
-
4. **
|
|
74
|
+
3. **(Optional) Run `/manifold-models`** to configure which models each sub-agent uses
|
|
75
|
+
4. **(Optional) Run `/manifold-update`** to clear plugin cache before updating (requires path configuration)
|
|
76
|
+
5. **Create a plan** — any format (markdown, TODO list, email, meeting notes)
|
|
77
|
+
6. **Point the Lead Dev agent** at your plan file and tell it to execute
|
|
76
78
|
|
|
77
79
|
The Lead Dev will:
|
|
78
80
|
- Extract tasks from your plan
|
|
@@ -180,7 +182,13 @@ rm -rf .opencode/agents/ .opencode/skills/ Manifold/
|
|
|
180
182
|
|
|
181
183
|
### Assigning models to agents
|
|
182
184
|
|
|
183
|
-
|
|
185
|
+
**Interactive (Recommended):** Run `/manifold-models` in the TUI. You'll be prompted to:
|
|
186
|
+
1. Select a sub-agent (clerk, senior-dev, junior-dev, or debug)
|
|
187
|
+
2. Choose from all available models in your configured providers
|
|
188
|
+
|
|
189
|
+
This updates the agent's frontmatter automatically.
|
|
190
|
+
|
|
191
|
+
**Manual:** Add a `model` field to the agent's frontmatter or your `opencode.json`:
|
|
184
192
|
|
|
185
193
|
```json
|
|
186
194
|
{
|
|
@@ -203,8 +211,8 @@ See the [Example Configurations](#example-configurations) section for model pair
|
|
|
203
211
|
|
|
204
212
|
| Budget | Senior Dev | Junior Dev | Debug |
|
|
205
213
|
|--------|-----------|-----------|-------|
|
|
206
|
-
|
|
|
207
|
-
|
|
|
214
|
+
| Self Host | Qwen3.5 27B | Gemma 4 31B | GLM 4.7 Flash |
|
|
215
|
+
| Cost-effective | Qwen3.5 397B A17B | MiniMax-2.7 | GLM 5.1 |
|
|
208
216
|
| Premium | Claude Opus | GPT 5.4 | Gemini 3.1 pro |
|
|
209
217
|
|
|
210
218
|
Models change frequently. These examples prioritize sr/debug diversity and cost gradient.
|
|
@@ -245,6 +253,7 @@ Located at `Manifold/settings.json`:
|
|
|
245
253
|
| `clerkRetryEnabled` | true | Clerk gets second pass after Debug fails |
|
|
246
254
|
| `timeout` | 300 | Max seconds per agent call |
|
|
247
255
|
| `testCommand` | null | Default test command |
|
|
256
|
+
| `updateCachePaths` | `[]` | Paths to clear when running `/manifold-update` (see [Plugin Updates](#plugin-updates)) |
|
|
248
257
|
|
|
249
258
|
---
|
|
250
259
|
|
|
@@ -288,6 +297,62 @@ A: The system is designed around the Clerk's ability to research context via sem
|
|
|
288
297
|
|
|
289
298
|
---
|
|
290
299
|
|
|
300
|
+
## Plugin Updates
|
|
301
|
+
|
|
302
|
+
Opencode caches plugins to improve startup performance. When a new version of `opencode-manifold` is published, you may need to clear the cache to receive the update.
|
|
303
|
+
|
|
304
|
+
### Using `/manifold-update`
|
|
305
|
+
|
|
306
|
+
1. **Configure cache paths** in `Manifold/settings.json`:
|
|
307
|
+
```json
|
|
308
|
+
{
|
|
309
|
+
"updateCachePaths": [
|
|
310
|
+
"~/.cache/opencode/packages/opencode-manifold@latest",
|
|
311
|
+
"~/node_modules/opencode-manifold"
|
|
312
|
+
]
|
|
313
|
+
}
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
2. **Run `/manifold-update`** in the TUI
|
|
317
|
+
3. **Confirm** the paths to clear
|
|
318
|
+
4. **Restart opencode** to pull the fresh version
|
|
319
|
+
|
|
320
|
+
### Safety Features
|
|
321
|
+
|
|
322
|
+
The command includes built-in protections:
|
|
323
|
+
- **Wildcards blocked:** Paths with `*` are rejected
|
|
324
|
+
- **System paths protected:** `/`, `/System`, `/Applications`, home directory, etc. cannot be deleted
|
|
325
|
+
- **Project detection:** Directories containing `.git` + `opencode.json` or `package.json` are treated as projects and blocked
|
|
326
|
+
- **Explicit confirmation:** You must type "yes" to proceed
|
|
327
|
+
|
|
328
|
+
### Platform-Specific Paths
|
|
329
|
+
|
|
330
|
+
**macOS:**
|
|
331
|
+
```json
|
|
332
|
+
[
|
|
333
|
+
"~/.cache/opencode/packages/opencode-manifold@latest",
|
|
334
|
+
"~/node_modules/opencode-manifold"
|
|
335
|
+
]
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
**Linux:**
|
|
339
|
+
```json
|
|
340
|
+
[
|
|
341
|
+
"~/.cache/opencode/packages/opencode-manifold@latest"
|
|
342
|
+
]
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
**Windows:**
|
|
346
|
+
```json
|
|
347
|
+
[
|
|
348
|
+
"%LOCALAPPDATA%/opencode/packages/opencode-manifold@latest"
|
|
349
|
+
]
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
If your cache is stored elsewhere, add the full path to `updateCachePaths`. The command is intentionally conservative — it's safer to manually clear cache than to accidentally delete project files.
|
|
353
|
+
|
|
354
|
+
---
|
|
355
|
+
|
|
291
356
|
## Uninstall
|
|
292
357
|
|
|
293
358
|
To remove Open Manifold from a project:
|
|
@@ -307,26 +372,20 @@ rm -rf ~/.config/opencode/manifold/ ~/.config/opencode/commands/manifold-init.md
|
|
|
307
372
|
To fully uninstall the plugin, also remove `"opencode-manifold"` from the `plugin` array in your global or project `opencode.json`.
|
|
308
373
|
|
|
309
374
|
### Complete Clean Uninstall
|
|
310
|
-
|
|
311
|
-
|
|
375
|
+
|
|
376
|
+
For a full clean reinstall, use `/manifold-update` (see [Plugin Updates](#plugin-updates)) to clear caches, then:
|
|
312
377
|
|
|
313
378
|
```bash
|
|
314
|
-
# Remove
|
|
315
|
-
rm -rf
|
|
316
|
-
rm -rf ~/.cache/opencode/packages/opencode-manifold@latest
|
|
317
|
-
rm -rf ~/.config/opencode/manifold/
|
|
318
|
-
rm -rf ~/.config/opencode/commands/manifold-init.md
|
|
379
|
+
# Remove project files
|
|
380
|
+
rm -rf .opencode/agents/ .opencode/skills/ Manifold/
|
|
319
381
|
|
|
320
|
-
#
|
|
382
|
+
# Remove global templates
|
|
383
|
+
rm -rf ~/.config/opencode/manifold/ ~/.config/opencode/commands/manifold-init.md
|
|
321
384
|
```
|
|
322
385
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
# Win:
|
|
326
|
-
I have no clue. Use linux.
|
|
386
|
+
Then remove `"opencode-manifold"` from your `opencode.json` plugin array.
|
|
327
387
|
|
|
328
|
-
|
|
329
|
-
Still have no clue but I know you can handle it.
|
|
388
|
+
---
|
|
330
389
|
|
|
331
390
|
---
|
|
332
391
|
|
package/dist/index.js
CHANGED
|
@@ -4074,7 +4074,8 @@ async function readSettings(directory) {
|
|
|
4074
4074
|
recentTaskCount: 3,
|
|
4075
4075
|
clerkRetryEnabled: true,
|
|
4076
4076
|
timeout: 300,
|
|
4077
|
-
testCommand: null
|
|
4077
|
+
testCommand: null,
|
|
4078
|
+
updateCachePaths: []
|
|
4078
4079
|
};
|
|
4079
4080
|
}
|
|
4080
4081
|
async function updatePlansRegistry(directory, planFile) {
|
|
@@ -4191,6 +4192,484 @@ ${testResult.result.output}`,
|
|
|
4191
4192
|
}
|
|
4192
4193
|
});
|
|
4193
4194
|
|
|
4195
|
+
// src/tools/set-subagent-model.ts
|
|
4196
|
+
import { readFile as readFile5, writeFile as writeFile5, readdir as readdir3 } from "fs/promises";
|
|
4197
|
+
import { existsSync as existsSync6 } from "fs";
|
|
4198
|
+
import { join as join6 } from "path";
|
|
4199
|
+
import { homedir as homedir2 } from "os";
|
|
4200
|
+
var MANIFOLD_AGENTS = ["clerk", "senior-dev", "junior-dev", "debug"];
|
|
4201
|
+
async function getManifoldAgents(directory) {
|
|
4202
|
+
const agentsDir = join6(directory, ".opencode", "agents");
|
|
4203
|
+
if (!existsSync6(agentsDir)) {
|
|
4204
|
+
return [];
|
|
4205
|
+
}
|
|
4206
|
+
const files = await readdir3(agentsDir);
|
|
4207
|
+
const agents = [];
|
|
4208
|
+
for (const file of files) {
|
|
4209
|
+
if (!file.endsWith(".md"))
|
|
4210
|
+
continue;
|
|
4211
|
+
const name = file.replace(".md", "");
|
|
4212
|
+
if (MANIFOLD_AGENTS.includes(name)) {
|
|
4213
|
+
agents.push(name);
|
|
4214
|
+
}
|
|
4215
|
+
}
|
|
4216
|
+
return agents;
|
|
4217
|
+
}
|
|
4218
|
+
async function getAvailableModels(client) {
|
|
4219
|
+
try {
|
|
4220
|
+
const result = await client.config.providers();
|
|
4221
|
+
const providers = result.data?.providers || [];
|
|
4222
|
+
const models = [];
|
|
4223
|
+
for (const provider of providers) {
|
|
4224
|
+
if (provider.models) {
|
|
4225
|
+
for (const [modelId, model] of Object.entries(provider.models)) {
|
|
4226
|
+
models.push({
|
|
4227
|
+
id: `${provider.id}/${modelId}`,
|
|
4228
|
+
name: model.name || modelId,
|
|
4229
|
+
providerID: provider.id
|
|
4230
|
+
});
|
|
4231
|
+
}
|
|
4232
|
+
}
|
|
4233
|
+
}
|
|
4234
|
+
return models.sort((a, b) => a.name.localeCompare(b.name));
|
|
4235
|
+
} catch (error) {
|
|
4236
|
+
await client.app.log({
|
|
4237
|
+
body: {
|
|
4238
|
+
service: "opencode-manifold",
|
|
4239
|
+
level: "error",
|
|
4240
|
+
message: `Error fetching models: ${error}`
|
|
4241
|
+
}
|
|
4242
|
+
});
|
|
4243
|
+
return [];
|
|
4244
|
+
}
|
|
4245
|
+
}
|
|
4246
|
+
async function readAgentFile(agentName, directory) {
|
|
4247
|
+
const agentPath = join6(directory, ".opencode", "agents", `${agentName}.md`);
|
|
4248
|
+
if (!existsSync6(agentPath)) {
|
|
4249
|
+
const globalPath = join6(homedir2(), ".config", "opencode", "manifold", "agents", `${agentName}.md`);
|
|
4250
|
+
if (existsSync6(globalPath)) {
|
|
4251
|
+
const content2 = await readFile5(globalPath, "utf-8");
|
|
4252
|
+
const { frontmatter: frontmatter2, body: body2 } = parseFrontmatter(content2);
|
|
4253
|
+
return { content: content2, frontmatter: frontmatter2, body: body2 };
|
|
4254
|
+
}
|
|
4255
|
+
throw new Error(`Agent file not found for ${agentName}`);
|
|
4256
|
+
}
|
|
4257
|
+
const content = await readFile5(agentPath, "utf-8");
|
|
4258
|
+
const { frontmatter, body } = parseFrontmatter(content);
|
|
4259
|
+
return { content, frontmatter, body };
|
|
4260
|
+
}
|
|
4261
|
+
function parseFrontmatter(content) {
|
|
4262
|
+
const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
4263
|
+
if (!match) {
|
|
4264
|
+
return { frontmatter: {}, body: content };
|
|
4265
|
+
}
|
|
4266
|
+
const frontmatterYaml = match[1];
|
|
4267
|
+
const body = match[2];
|
|
4268
|
+
const frontmatter = jsYaml.load(frontmatterYaml) || {};
|
|
4269
|
+
return { frontmatter, body };
|
|
4270
|
+
}
|
|
4271
|
+
function buildAgentFile(frontmatter, body) {
|
|
4272
|
+
const yamlContent = jsYaml.dump(frontmatter, {
|
|
4273
|
+
lineWidth: -1,
|
|
4274
|
+
noCompatMode: true
|
|
4275
|
+
});
|
|
4276
|
+
return `---
|
|
4277
|
+
${yamlContent}---
|
|
4278
|
+
${body}`;
|
|
4279
|
+
}
|
|
4280
|
+
async function updateAgentModel(client, agentName, modelId, directory) {
|
|
4281
|
+
const { frontmatter, body } = await readAgentFile(agentName, directory);
|
|
4282
|
+
frontmatter.model = modelId;
|
|
4283
|
+
const newContent = buildAgentFile(frontmatter, body);
|
|
4284
|
+
const agentPath = join6(directory, ".opencode", "agents", `${agentName}.md`);
|
|
4285
|
+
await writeFile5(agentPath, newContent);
|
|
4286
|
+
await client.app.log({
|
|
4287
|
+
body: {
|
|
4288
|
+
service: "opencode-manifold",
|
|
4289
|
+
level: "info",
|
|
4290
|
+
message: `Updated ${agentName} model to ${modelId}`
|
|
4291
|
+
}
|
|
4292
|
+
});
|
|
4293
|
+
}
|
|
4294
|
+
async function handleSubModelCommand(client, directory) {
|
|
4295
|
+
await client.app.log({
|
|
4296
|
+
body: {
|
|
4297
|
+
service: "opencode-manifold",
|
|
4298
|
+
level: "info",
|
|
4299
|
+
message: "Starting /sub-model command"
|
|
4300
|
+
}
|
|
4301
|
+
});
|
|
4302
|
+
const agents = await getManifoldAgents(directory);
|
|
4303
|
+
if (agents.length === 0) {
|
|
4304
|
+
await client.tui.showToast({
|
|
4305
|
+
body: {
|
|
4306
|
+
type: "error",
|
|
4307
|
+
message: "No Manifold agents found. Run /manifold-init first."
|
|
4308
|
+
}
|
|
4309
|
+
});
|
|
4310
|
+
return;
|
|
4311
|
+
}
|
|
4312
|
+
const models = await getAvailableModels(client);
|
|
4313
|
+
if (models.length === 0) {
|
|
4314
|
+
await client.tui.showToast({
|
|
4315
|
+
body: {
|
|
4316
|
+
type: "error",
|
|
4317
|
+
message: "No models available. Configure providers first."
|
|
4318
|
+
}
|
|
4319
|
+
});
|
|
4320
|
+
return;
|
|
4321
|
+
}
|
|
4322
|
+
const agentList = agents.map((a) => `${a} (sub-agent)`).join(`
|
|
4323
|
+
`);
|
|
4324
|
+
await client.tui.appendPrompt({
|
|
4325
|
+
body: {
|
|
4326
|
+
type: "text",
|
|
4327
|
+
text: `\uD83D\uDCCB Select a Manifold sub-agent to configure:
|
|
4328
|
+
|
|
4329
|
+
${agentList}`
|
|
4330
|
+
}
|
|
4331
|
+
});
|
|
4332
|
+
await client.tui.publish({
|
|
4333
|
+
body: {
|
|
4334
|
+
type: "tui.prompt_append",
|
|
4335
|
+
data: {
|
|
4336
|
+
type: "text",
|
|
4337
|
+
text: `
|
|
4338
|
+
|
|
4339
|
+
Type the agent name (clerk, senior-dev, junior-dev, or debug): `
|
|
4340
|
+
}
|
|
4341
|
+
}
|
|
4342
|
+
});
|
|
4343
|
+
const agentResponse = await client.tui.control.next();
|
|
4344
|
+
const selectedAgent = agentResponse.data?.input?.trim().toLowerCase();
|
|
4345
|
+
if (!selectedAgent || !MANIFOLD_AGENTS.includes(selectedAgent)) {
|
|
4346
|
+
await client.tui.showToast({
|
|
4347
|
+
body: {
|
|
4348
|
+
type: "error",
|
|
4349
|
+
message: `Invalid agent selection. Must be one of: ${MANIFOLD_AGENTS.join(", ")}`
|
|
4350
|
+
}
|
|
4351
|
+
});
|
|
4352
|
+
return;
|
|
4353
|
+
}
|
|
4354
|
+
if (!agents.includes(selectedAgent)) {
|
|
4355
|
+
await client.tui.showToast({
|
|
4356
|
+
body: {
|
|
4357
|
+
type: "error",
|
|
4358
|
+
message: `Agent ${selectedAgent} not found in .opencode/agents/`
|
|
4359
|
+
}
|
|
4360
|
+
});
|
|
4361
|
+
return;
|
|
4362
|
+
}
|
|
4363
|
+
const currentModel = await readAgentFile(selectedAgent, directory).then((f) => f.frontmatter.model || "not set");
|
|
4364
|
+
const modelList = models.map((m, i2) => `${i2 + 1}. ${m.name} (${m.id})`).join(`
|
|
4365
|
+
`);
|
|
4366
|
+
await client.tui.appendPrompt({
|
|
4367
|
+
body: {
|
|
4368
|
+
type: "text",
|
|
4369
|
+
text: `
|
|
4370
|
+
\uD83D\uDCE6 Available models for ${selectedAgent} (current: ${currentModel}):
|
|
4371
|
+
|
|
4372
|
+
${modelList}`
|
|
4373
|
+
}
|
|
4374
|
+
});
|
|
4375
|
+
await client.tui.publish({
|
|
4376
|
+
body: {
|
|
4377
|
+
type: "tui.prompt_append",
|
|
4378
|
+
data: {
|
|
4379
|
+
type: "text",
|
|
4380
|
+
text: `
|
|
4381
|
+
|
|
4382
|
+
Type the model number or full model ID (e.g., anthropic/claude-sonnet-4-20250514): `
|
|
4383
|
+
}
|
|
4384
|
+
}
|
|
4385
|
+
});
|
|
4386
|
+
const modelResponse = await client.tui.control.next();
|
|
4387
|
+
const selectedModelInput = modelResponse.data?.input?.trim();
|
|
4388
|
+
if (!selectedModelInput) {
|
|
4389
|
+
await client.tui.showToast({
|
|
4390
|
+
body: {
|
|
4391
|
+
type: "error",
|
|
4392
|
+
message: "No model selected"
|
|
4393
|
+
}
|
|
4394
|
+
});
|
|
4395
|
+
return;
|
|
4396
|
+
}
|
|
4397
|
+
let selectedModelId;
|
|
4398
|
+
const modelIndex = parseInt(selectedModelInput, 10) - 1;
|
|
4399
|
+
if (!isNaN(modelIndex) && modelIndex >= 0 && modelIndex < models.length) {
|
|
4400
|
+
selectedModelId = models[modelIndex].id;
|
|
4401
|
+
} else {
|
|
4402
|
+
const found = models.find((m) => m.id.toLowerCase() === selectedModelInput.toLowerCase());
|
|
4403
|
+
if (found) {
|
|
4404
|
+
selectedModelId = found.id;
|
|
4405
|
+
}
|
|
4406
|
+
}
|
|
4407
|
+
if (!selectedModelId) {
|
|
4408
|
+
await client.tui.showToast({
|
|
4409
|
+
body: {
|
|
4410
|
+
type: "error",
|
|
4411
|
+
message: `Invalid model selection: ${selectedModelInput}`
|
|
4412
|
+
}
|
|
4413
|
+
});
|
|
4414
|
+
return;
|
|
4415
|
+
}
|
|
4416
|
+
await updateAgentModel(client, selectedAgent, selectedModelId, directory);
|
|
4417
|
+
await client.tui.showToast({
|
|
4418
|
+
body: {
|
|
4419
|
+
type: "success",
|
|
4420
|
+
message: `✅ Set ${selectedAgent} to ${selectedModelId}`
|
|
4421
|
+
}
|
|
4422
|
+
});
|
|
4423
|
+
await client.tui.appendPrompt({
|
|
4424
|
+
body: {
|
|
4425
|
+
type: "text",
|
|
4426
|
+
text: `
|
|
4427
|
+
✅ Model updated for ${selectedAgent}: ${selectedModelId}
|
|
4428
|
+
`
|
|
4429
|
+
}
|
|
4430
|
+
});
|
|
4431
|
+
}
|
|
4432
|
+
|
|
4433
|
+
// src/tools/clear-cache.ts
|
|
4434
|
+
import { rm } from "fs/promises";
|
|
4435
|
+
import { existsSync as existsSync7 } from "fs";
|
|
4436
|
+
import { join as join7, isAbsolute } from "path";
|
|
4437
|
+
import { homedir as homedir3 } from "os";
|
|
4438
|
+
var PROTECTED_PATHS = [
|
|
4439
|
+
"/",
|
|
4440
|
+
"/System",
|
|
4441
|
+
"/Applications",
|
|
4442
|
+
"/Users",
|
|
4443
|
+
"/etc",
|
|
4444
|
+
"/bin",
|
|
4445
|
+
"/sbin",
|
|
4446
|
+
"/usr"
|
|
4447
|
+
];
|
|
4448
|
+
function resolvePath(pathStr) {
|
|
4449
|
+
const expanded = pathStr.startsWith("~") ? join7(homedir3(), pathStr.slice(1)) : pathStr;
|
|
4450
|
+
return isAbsolute(expanded) ? expanded : join7(process.cwd(), expanded);
|
|
4451
|
+
}
|
|
4452
|
+
function isPathSafe(pathStr) {
|
|
4453
|
+
const normalized = pathStr.toLowerCase();
|
|
4454
|
+
if (normalized.includes("*")) {
|
|
4455
|
+
return { safe: false, reason: "Wildcards are not allowed for safety" };
|
|
4456
|
+
}
|
|
4457
|
+
for (const protectedPath of PROTECTED_PATHS) {
|
|
4458
|
+
if (normalized === protectedPath.toLowerCase() || normalized.startsWith(protectedPath.toLowerCase() + "/") || normalized.startsWith(protectedPath.toLowerCase() + "\\")) {
|
|
4459
|
+
return { safe: false, reason: `Cannot delete protected system path: ${protectedPath}` };
|
|
4460
|
+
}
|
|
4461
|
+
}
|
|
4462
|
+
if (normalized === homedir3().toLowerCase() || normalized.startsWith(homedir3().toLowerCase() + "/") && !normalized.includes("cache") && !normalized.includes("node_modules")) {
|
|
4463
|
+
const parts = normalized.replace(homedir3().toLowerCase(), "").split(/[\/\\]/).filter(Boolean);
|
|
4464
|
+
if (parts.length === 1) {
|
|
4465
|
+
return { safe: false, reason: "Cannot delete directories directly in home folder" };
|
|
4466
|
+
}
|
|
4467
|
+
}
|
|
4468
|
+
return { safe: true };
|
|
4469
|
+
}
|
|
4470
|
+
async function isProjectDirectory(pathStr) {
|
|
4471
|
+
try {
|
|
4472
|
+
const hasGit = existsSync7(join7(pathStr, ".git"));
|
|
4473
|
+
const hasOpencodeConfig = existsSync7(join7(pathStr, "opencode.json"));
|
|
4474
|
+
const hasPackageJson = existsSync7(join7(pathStr, "package.json"));
|
|
4475
|
+
if (hasGit && (hasOpencodeConfig || hasPackageJson)) {
|
|
4476
|
+
return true;
|
|
4477
|
+
}
|
|
4478
|
+
const entries = await Promise.all([
|
|
4479
|
+
existsSync7(join7(pathStr, ".git")) ? "git" : null,
|
|
4480
|
+
existsSync7(join7(pathStr, "opencode.json")) ? "opencode" : null
|
|
4481
|
+
].filter(Boolean));
|
|
4482
|
+
return entries.length >= 2;
|
|
4483
|
+
} catch {
|
|
4484
|
+
return false;
|
|
4485
|
+
}
|
|
4486
|
+
}
|
|
4487
|
+
async function clearCachePaths(paths) {
|
|
4488
|
+
const cleared = [];
|
|
4489
|
+
const skipped = [];
|
|
4490
|
+
const blocked = [];
|
|
4491
|
+
for (const pathStr of paths) {
|
|
4492
|
+
const resolved = resolvePath(pathStr);
|
|
4493
|
+
const safetyCheck = isPathSafe(resolved);
|
|
4494
|
+
if (!safetyCheck.safe) {
|
|
4495
|
+
blocked.push({ path: resolved, reason: safetyCheck.reason || "Failed safety check" });
|
|
4496
|
+
continue;
|
|
4497
|
+
}
|
|
4498
|
+
if (!existsSync7(resolved)) {
|
|
4499
|
+
skipped.push(resolved);
|
|
4500
|
+
continue;
|
|
4501
|
+
}
|
|
4502
|
+
const isProject = await isProjectDirectory(resolved);
|
|
4503
|
+
if (isProject) {
|
|
4504
|
+
blocked.push({ path: resolved, reason: "Appears to be a project directory (contains .git + config files)" });
|
|
4505
|
+
continue;
|
|
4506
|
+
}
|
|
4507
|
+
try {
|
|
4508
|
+
await rm(resolved, { recursive: true, force: true });
|
|
4509
|
+
cleared.push(resolved);
|
|
4510
|
+
} catch (error) {
|
|
4511
|
+
blocked.push({ path: resolved, reason: `Failed to delete: ${error}` });
|
|
4512
|
+
}
|
|
4513
|
+
}
|
|
4514
|
+
return { cleared, skipped, blocked };
|
|
4515
|
+
}
|
|
4516
|
+
async function handleUpdateCommand(client, directory) {
|
|
4517
|
+
await client.app.log({
|
|
4518
|
+
body: {
|
|
4519
|
+
service: "opencode-manifold",
|
|
4520
|
+
level: "info",
|
|
4521
|
+
message: "Starting /manifold-update command"
|
|
4522
|
+
}
|
|
4523
|
+
});
|
|
4524
|
+
const settings = await readSettings(directory);
|
|
4525
|
+
const configuredPaths = settings.updateCachePaths || [];
|
|
4526
|
+
if (configuredPaths.length === 0) {
|
|
4527
|
+
const examplePaths = [
|
|
4528
|
+
"~/.cache/opencode/packages/opencode-manifold@latest",
|
|
4529
|
+
"~/node_modules/opencode-manifold"
|
|
4530
|
+
];
|
|
4531
|
+
await client.tui.appendPrompt({
|
|
4532
|
+
body: {
|
|
4533
|
+
type: "text",
|
|
4534
|
+
text: `\uD83D\uDD04 **Manifold Plugin Update**
|
|
4535
|
+
|
|
4536
|
+
No cache paths configured in \`Manifold/settings.json\`.
|
|
4537
|
+
|
|
4538
|
+
To enable this feature, add cache paths to your settings:
|
|
4539
|
+
|
|
4540
|
+
\`\`\`json
|
|
4541
|
+
{
|
|
4542
|
+
"updateCachePaths": [
|
|
4543
|
+
"~/.cache/opencode/packages/opencode-manifold@latest",
|
|
4544
|
+
"~/node_modules/opencode-manifold"
|
|
4545
|
+
]
|
|
4546
|
+
}
|
|
4547
|
+
\`\`\`
|
|
4548
|
+
|
|
4549
|
+
**Safety features:**
|
|
4550
|
+
• Wildcards are blocked
|
|
4551
|
+
• System directories are protected
|
|
4552
|
+
• Project directories (with .git + config) are protected
|
|
4553
|
+
|
|
4554
|
+
**macOS users:** The paths above are typical cache locations.
|
|
4555
|
+
**Linux/Windows users:** Add your opencode cache paths manually.
|
|
4556
|
+
`
|
|
4557
|
+
}
|
|
4558
|
+
});
|
|
4559
|
+
await client.tui.showToast({
|
|
4560
|
+
body: {
|
|
4561
|
+
type: "info",
|
|
4562
|
+
message: "Configure updateCachePaths in Manifold/settings.json"
|
|
4563
|
+
}
|
|
4564
|
+
});
|
|
4565
|
+
return;
|
|
4566
|
+
}
|
|
4567
|
+
const resolvedPaths = configuredPaths.map(resolvePath);
|
|
4568
|
+
await client.tui.appendPrompt({
|
|
4569
|
+
body: {
|
|
4570
|
+
type: "text",
|
|
4571
|
+
text: `\uD83D\uDD04 **Manifold Plugin Update**
|
|
4572
|
+
|
|
4573
|
+
The following paths will be cleared:
|
|
4574
|
+
|
|
4575
|
+
${resolvedPaths.map((p) => `• ${p}`).join(`
|
|
4576
|
+
`)}
|
|
4577
|
+
|
|
4578
|
+
⚠️ **This action is irreversible.** These paths are read from \`Manifold/settings.json\`.
|
|
4579
|
+
`
|
|
4580
|
+
}
|
|
4581
|
+
});
|
|
4582
|
+
await client.tui.publish({
|
|
4583
|
+
body: {
|
|
4584
|
+
type: "tui.prompt_append",
|
|
4585
|
+
data: {
|
|
4586
|
+
type: "text",
|
|
4587
|
+
text: `
|
|
4588
|
+
|
|
4589
|
+
Type 'yes' to confirm and clear cache: `
|
|
4590
|
+
}
|
|
4591
|
+
}
|
|
4592
|
+
});
|
|
4593
|
+
const response = await client.tui.control.next();
|
|
4594
|
+
const answer = response.data?.input?.trim().toLowerCase();
|
|
4595
|
+
if (answer !== "yes" && answer !== "y") {
|
|
4596
|
+
await client.tui.appendPrompt({
|
|
4597
|
+
body: {
|
|
4598
|
+
type: "text",
|
|
4599
|
+
text: `
|
|
4600
|
+
❌ Cancelled. No cache cleared.
|
|
4601
|
+
`
|
|
4602
|
+
}
|
|
4603
|
+
});
|
|
4604
|
+
return;
|
|
4605
|
+
}
|
|
4606
|
+
const { cleared, skipped, blocked } = await clearCachePaths(configuredPaths);
|
|
4607
|
+
let message = `
|
|
4608
|
+
`;
|
|
4609
|
+
if (cleared.length > 0) {
|
|
4610
|
+
message += `✅ **Cleared:**
|
|
4611
|
+
${cleared.map((p) => `• ${p}`).join(`
|
|
4612
|
+
`)}
|
|
4613
|
+
|
|
4614
|
+
`;
|
|
4615
|
+
}
|
|
4616
|
+
if (skipped.length > 0) {
|
|
4617
|
+
message += `⏭️ **Already clean / skipped:**
|
|
4618
|
+
${skipped.map((p) => `• ${p}`).join(`
|
|
4619
|
+
`)}
|
|
4620
|
+
|
|
4621
|
+
`;
|
|
4622
|
+
}
|
|
4623
|
+
if (blocked.length > 0) {
|
|
4624
|
+
message += `\uD83D\uDEAB **Blocked (safety check):**
|
|
4625
|
+
`;
|
|
4626
|
+
for (const item of blocked) {
|
|
4627
|
+
message += `• ${item.path}
|
|
4628
|
+
Reason: ${item.reason}
|
|
4629
|
+
`;
|
|
4630
|
+
}
|
|
4631
|
+
message += `
|
|
4632
|
+
`;
|
|
4633
|
+
}
|
|
4634
|
+
if (cleared.length > 0) {
|
|
4635
|
+
message += `**Next step:** Restart opencode to pull the latest plugin version.
|
|
4636
|
+
|
|
4637
|
+
Your project files (.opencode/, Manifold/) are untouched — only the configured cache paths were cleared.
|
|
4638
|
+
`;
|
|
4639
|
+
} else if (blocked.length > 0) {
|
|
4640
|
+
message += `⚠️ No paths were cleared. Review the blocked paths above and adjust your settings if needed.
|
|
4641
|
+
`;
|
|
4642
|
+
}
|
|
4643
|
+
await client.tui.appendPrompt({
|
|
4644
|
+
body: {
|
|
4645
|
+
type: "text",
|
|
4646
|
+
text: message
|
|
4647
|
+
}
|
|
4648
|
+
});
|
|
4649
|
+
if (cleared.length > 0) {
|
|
4650
|
+
await client.tui.showToast({
|
|
4651
|
+
body: {
|
|
4652
|
+
type: "success",
|
|
4653
|
+
message: `Cache cleared (${cleared.length} paths). Restart opencode to update.`
|
|
4654
|
+
}
|
|
4655
|
+
});
|
|
4656
|
+
} else if (blocked.length > 0) {
|
|
4657
|
+
await client.tui.showToast({
|
|
4658
|
+
body: {
|
|
4659
|
+
type: "error",
|
|
4660
|
+
message: `Cache update blocked (${blocked.length} paths failed safety check)`
|
|
4661
|
+
}
|
|
4662
|
+
});
|
|
4663
|
+
}
|
|
4664
|
+
await client.app.log({
|
|
4665
|
+
body: {
|
|
4666
|
+
service: "opencode-manifold",
|
|
4667
|
+
level: "info",
|
|
4668
|
+
message: `/manifold-update complete: cleared ${cleared.length}, skipped ${skipped.length}, blocked ${blocked.length}`
|
|
4669
|
+
}
|
|
4670
|
+
});
|
|
4671
|
+
}
|
|
4672
|
+
|
|
4194
4673
|
// src/index.ts
|
|
4195
4674
|
var ManifoldPlugin = async (ctx) => {
|
|
4196
4675
|
setPluginContext(ctx.client);
|
|
@@ -4224,6 +4703,22 @@ var ManifoldPlugin = async (ctx) => {
|
|
|
4224
4703
|
}
|
|
4225
4704
|
];
|
|
4226
4705
|
}
|
|
4706
|
+
} else if (input.command === "manifold-models") {
|
|
4707
|
+
await handleSubModelCommand(ctx.client, ctx.directory);
|
|
4708
|
+
output.parts = [
|
|
4709
|
+
{
|
|
4710
|
+
type: "text",
|
|
4711
|
+
text: "Sub-agent model configuration complete."
|
|
4712
|
+
}
|
|
4713
|
+
];
|
|
4714
|
+
} else if (input.command === "manifold-update") {
|
|
4715
|
+
await handleUpdateCommand(ctx.client, ctx.directory);
|
|
4716
|
+
output.parts = [
|
|
4717
|
+
{
|
|
4718
|
+
type: "text",
|
|
4719
|
+
text: "Plugin cache update complete."
|
|
4720
|
+
}
|
|
4721
|
+
];
|
|
4227
4722
|
}
|
|
4228
4723
|
}
|
|
4229
4724
|
};
|
package/package.json
CHANGED