opencode-manifold 0.4.21 → 0.5.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/README.md +32 -69
- package/dist/index.js +11 -492
- package/dist/tui.js +9 -2706
- package/package.json +1 -1
- package/src/templates/agents/clerk.md +1 -1
- package/src/templates/agents/debug.md +1 -0
- package/src/templates/agents/junior-dev.md +1 -0
- package/src/templates/agents/manifold.md +1 -0
- package/src/templates/agents/senior-dev.md +1 -0
- package/src/templates/commands/manifold-models.md +0 -5
- package/src/templates/commands/manifold-update.md +0 -5
package/README.md
CHANGED
|
@@ -71,10 +71,8 @@ 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. **
|
|
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
|
|
74
|
+
3. **Create a plan** — any format (markdown, TODO list, email, meeting notes)
|
|
75
|
+
4. **Point the Lead Dev agent** at your plan file and tell it to execute
|
|
78
76
|
|
|
79
77
|
The Lead Dev will:
|
|
80
78
|
- Extract tasks from your plan
|
|
@@ -182,11 +180,14 @@ rm -rf .opencode/agents/ .opencode/skills/ Manifold/
|
|
|
182
180
|
|
|
183
181
|
### Assigning models to agents
|
|
184
182
|
|
|
185
|
-
**
|
|
186
|
-
1. Select a sub-agent (clerk, senior-dev, junior-dev, or debug)
|
|
187
|
-
2. Choose from all available models in your configured providers
|
|
183
|
+
**Using the get-model-path tool:** Say "call the get-model-path tool" to get the current model path (e.g., `google/gemini-3-flash`). Then manually edit the agent's frontmatter:
|
|
188
184
|
|
|
189
|
-
|
|
185
|
+
```yaml
|
|
186
|
+
---
|
|
187
|
+
description: Implementation specialist
|
|
188
|
+
model: google/gemini-3-flash
|
|
189
|
+
---
|
|
190
|
+
```
|
|
190
191
|
|
|
191
192
|
**Manual:** Add a `model` field to the agent's frontmatter or your `opencode.json`:
|
|
192
193
|
|
|
@@ -253,7 +254,7 @@ Located at `Manifold/settings.json`:
|
|
|
253
254
|
| `clerkRetryEnabled` | true | Clerk gets second pass after Debug fails |
|
|
254
255
|
| `timeout` | 300 | Max seconds per agent call |
|
|
255
256
|
| `testCommand` | null | Default test command |
|
|
256
|
-
| `updateCachePaths` | `[]` |
|
|
257
|
+
| `updateCachePaths` | `[]` | Legacy setting, no longer used |
|
|
257
258
|
|
|
258
259
|
---
|
|
259
260
|
|
|
@@ -299,57 +300,21 @@ A: The system is designed around the Clerk's ability to research context via sem
|
|
|
299
300
|
|
|
300
301
|
## Plugin Updates
|
|
301
302
|
|
|
302
|
-
Opencode caches plugins to improve startup performance. When a new version of `opencode-manifold` is published,
|
|
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
|
|
303
|
+
Opencode caches plugins to improve startup performance. When a new version of `opencode-manifold` is published, clear the cache to receive the update.
|
|
321
304
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
-
|
|
325
|
-
|
|
326
|
-
-
|
|
327
|
-
|
|
328
|
-
### Platform-Specific Paths
|
|
329
|
-
|
|
330
|
-
**macOS:**
|
|
331
|
-
```json
|
|
332
|
-
[
|
|
333
|
-
"~/.cache/opencode/packages/opencode-manifold@latest",
|
|
334
|
-
"~/node_modules/opencode-manifold"
|
|
335
|
-
]
|
|
305
|
+
### macOS/Linux
|
|
306
|
+
```bash
|
|
307
|
+
rm -rf ~/.cache/opencode/packages/opencode-manifold@latest
|
|
308
|
+
# If installed via npm globally:
|
|
309
|
+
rm -rf ~/node_modules/opencode-manifold
|
|
336
310
|
```
|
|
337
311
|
|
|
338
|
-
|
|
339
|
-
```
|
|
340
|
-
|
|
341
|
-
"~/.cache/opencode/packages/opencode-manifold@latest"
|
|
342
|
-
]
|
|
312
|
+
### Windows
|
|
313
|
+
```cmd
|
|
314
|
+
rmdir /s "%LOCALAPPDATA%\opencode\packages\opencode-manifold@latest"
|
|
343
315
|
```
|
|
344
316
|
|
|
345
|
-
|
|
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.
|
|
317
|
+
Then restart opencode to pull the fresh version.
|
|
353
318
|
|
|
354
319
|
---
|
|
355
320
|
|
|
@@ -361,30 +326,28 @@ To remove Open Manifold from a project:
|
|
|
361
326
|
rm -rf .opencode/agents/ .opencode/skills/ Manifold/
|
|
362
327
|
```
|
|
363
328
|
|
|
364
|
-
Then remove
|
|
329
|
+
Then remove `"opencode-manifold"` from the `plugin` array in your `opencode.json`.
|
|
330
|
+
|
|
331
|
+
### Clear Plugin Cache (Optional)
|
|
365
332
|
|
|
366
|
-
|
|
333
|
+
If you want to fully remove cached plugin files:
|
|
367
334
|
|
|
335
|
+
**macOS/Linux:**
|
|
368
336
|
```bash
|
|
369
|
-
rm -rf ~/.
|
|
337
|
+
rm -rf ~/.cache/opencode/packages/opencode-manifold@latest
|
|
370
338
|
```
|
|
371
339
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
340
|
+
**Windows:**
|
|
341
|
+
```cmd
|
|
342
|
+
rmdir /s "%LOCALAPPDATA%\opencode\packages\opencode-manifold@latest"
|
|
343
|
+
```
|
|
375
344
|
|
|
376
|
-
|
|
345
|
+
### Remove Global Templates
|
|
377
346
|
|
|
378
347
|
```bash
|
|
379
|
-
|
|
380
|
-
rm -rf .opencode/agents/ .opencode/skills/ Manifold/
|
|
381
|
-
|
|
382
|
-
# Remove global templates
|
|
383
|
-
rm -rf ~/.config/opencode/manifold/ ~/.config/opencode/commands/manifold-init.md
|
|
348
|
+
rm -rf ~/.config/opencode/manifold/
|
|
384
349
|
```
|
|
385
350
|
|
|
386
|
-
Then remove `"opencode-manifold"` from your `opencode.json` plugin array.
|
|
387
|
-
|
|
388
351
|
---
|
|
389
352
|
|
|
390
353
|
---
|
package/dist/index.js
CHANGED
|
@@ -30,9 +30,15 @@ async function getModelPath(client, sessionID) {
|
|
|
30
30
|
});
|
|
31
31
|
if (messagesResponse.data && messagesResponse.data.length > 0) {
|
|
32
32
|
const lastMessage = messagesResponse.data[messagesResponse.data.length - 1];
|
|
33
|
-
if (lastMessage.info
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
if (lastMessage.info) {
|
|
34
|
+
if (lastMessage.info.role === "assistant") {
|
|
35
|
+
const assistantMsg = lastMessage.info;
|
|
36
|
+
return `${assistantMsg.providerID}/${assistantMsg.modelID}`;
|
|
37
|
+
}
|
|
38
|
+
if (lastMessage.info.model) {
|
|
39
|
+
const { providerID, modelID } = lastMessage.info.model;
|
|
40
|
+
return `${providerID}/${modelID}`;
|
|
41
|
+
}
|
|
36
42
|
}
|
|
37
43
|
}
|
|
38
44
|
} catch (error) {
|
|
@@ -232,7 +238,7 @@ async function initProject(directory, client) {
|
|
|
232
238
|
initialized.push(`Manifold/ (${manifoldCopied.join(", ")})`);
|
|
233
239
|
}
|
|
234
240
|
const projectCommandsDir = join(directory, ".opencode", "commands");
|
|
235
|
-
const commandsToCopy = [
|
|
241
|
+
const commandsToCopy = [];
|
|
236
242
|
const commandsInitialized = [];
|
|
237
243
|
for (const cmd of commandsToCopy) {
|
|
238
244
|
const src = join(globalTemplatesDir, "commands", cmd);
|
|
@@ -4328,486 +4334,6 @@ ${testResult.result.output}`,
|
|
|
4328
4334
|
|
|
4329
4335
|
// src/index.ts
|
|
4330
4336
|
init_get_model_path();
|
|
4331
|
-
|
|
4332
|
-
// src/tools/set-subagent-model.ts
|
|
4333
|
-
import { readFile as readFile5, writeFile as writeFile5, readdir as readdir3 } from "fs/promises";
|
|
4334
|
-
import { existsSync as existsSync6 } from "fs";
|
|
4335
|
-
import { join as join6 } from "path";
|
|
4336
|
-
import { homedir as homedir2 } from "os";
|
|
4337
|
-
var MANIFOLD_AGENTS = ["clerk", "senior-dev", "junior-dev", "debug"];
|
|
4338
|
-
async function getManifoldAgents(directory) {
|
|
4339
|
-
const agentsDir = join6(directory, ".opencode", "agents");
|
|
4340
|
-
if (!existsSync6(agentsDir)) {
|
|
4341
|
-
return [];
|
|
4342
|
-
}
|
|
4343
|
-
const files = await readdir3(agentsDir);
|
|
4344
|
-
const agents = [];
|
|
4345
|
-
for (const file of files) {
|
|
4346
|
-
if (!file.endsWith(".md"))
|
|
4347
|
-
continue;
|
|
4348
|
-
const name = file.replace(".md", "");
|
|
4349
|
-
if (MANIFOLD_AGENTS.includes(name)) {
|
|
4350
|
-
agents.push(name);
|
|
4351
|
-
}
|
|
4352
|
-
}
|
|
4353
|
-
return agents;
|
|
4354
|
-
}
|
|
4355
|
-
async function getAvailableModels(client) {
|
|
4356
|
-
try {
|
|
4357
|
-
const result = await client.config.providers();
|
|
4358
|
-
const providers = result.data?.providers || [];
|
|
4359
|
-
const models = [];
|
|
4360
|
-
for (const provider of providers) {
|
|
4361
|
-
if (provider.models) {
|
|
4362
|
-
for (const [modelId, model] of Object.entries(provider.models)) {
|
|
4363
|
-
models.push({
|
|
4364
|
-
id: `${provider.id}/${modelId}`,
|
|
4365
|
-
name: model.name || modelId,
|
|
4366
|
-
providerID: provider.id
|
|
4367
|
-
});
|
|
4368
|
-
}
|
|
4369
|
-
}
|
|
4370
|
-
}
|
|
4371
|
-
return models.sort((a, b) => a.name.localeCompare(b.name));
|
|
4372
|
-
} catch (error) {
|
|
4373
|
-
await client.app.log({
|
|
4374
|
-
body: {
|
|
4375
|
-
service: "opencode-manifold",
|
|
4376
|
-
level: "error",
|
|
4377
|
-
message: `Error fetching models: ${error}`
|
|
4378
|
-
}
|
|
4379
|
-
});
|
|
4380
|
-
return [];
|
|
4381
|
-
}
|
|
4382
|
-
}
|
|
4383
|
-
async function readAgentFile(agentName, directory) {
|
|
4384
|
-
const agentPath = join6(directory, ".opencode", "agents", `${agentName}.md`);
|
|
4385
|
-
if (!existsSync6(agentPath)) {
|
|
4386
|
-
const globalPath = join6(homedir2(), ".config", "opencode", "manifold", "agents", `${agentName}.md`);
|
|
4387
|
-
if (existsSync6(globalPath)) {
|
|
4388
|
-
const content2 = await readFile5(globalPath, "utf-8");
|
|
4389
|
-
const { frontmatter: frontmatter2, body: body2 } = parseFrontmatter(content2);
|
|
4390
|
-
return { content: content2, frontmatter: frontmatter2, body: body2 };
|
|
4391
|
-
}
|
|
4392
|
-
throw new Error(`Agent file not found for ${agentName}`);
|
|
4393
|
-
}
|
|
4394
|
-
const content = await readFile5(agentPath, "utf-8");
|
|
4395
|
-
const { frontmatter, body } = parseFrontmatter(content);
|
|
4396
|
-
return { content, frontmatter, body };
|
|
4397
|
-
}
|
|
4398
|
-
function parseFrontmatter(content) {
|
|
4399
|
-
const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
4400
|
-
if (!match) {
|
|
4401
|
-
return { frontmatter: {}, body: content };
|
|
4402
|
-
}
|
|
4403
|
-
const frontmatterYaml = match[1];
|
|
4404
|
-
const body = match[2];
|
|
4405
|
-
const frontmatter = jsYaml.load(frontmatterYaml) || {};
|
|
4406
|
-
return { frontmatter, body };
|
|
4407
|
-
}
|
|
4408
|
-
function buildAgentFile(frontmatter, body) {
|
|
4409
|
-
const yamlContent = jsYaml.dump(frontmatter, {
|
|
4410
|
-
lineWidth: -1,
|
|
4411
|
-
noCompatMode: true
|
|
4412
|
-
});
|
|
4413
|
-
return `---
|
|
4414
|
-
${yamlContent}---
|
|
4415
|
-
${body}`;
|
|
4416
|
-
}
|
|
4417
|
-
async function updateAgentModel(client, agentName, modelId, directory) {
|
|
4418
|
-
const { frontmatter, body } = await readAgentFile(agentName, directory);
|
|
4419
|
-
frontmatter.model = modelId;
|
|
4420
|
-
const newContent = buildAgentFile(frontmatter, body);
|
|
4421
|
-
const agentPath = join6(directory, ".opencode", "agents", `${agentName}.md`);
|
|
4422
|
-
await writeFile5(agentPath, newContent);
|
|
4423
|
-
await client.app.log({
|
|
4424
|
-
body: {
|
|
4425
|
-
service: "opencode-manifold",
|
|
4426
|
-
level: "info",
|
|
4427
|
-
message: `Updated ${agentName} model to ${modelId}`
|
|
4428
|
-
}
|
|
4429
|
-
});
|
|
4430
|
-
}
|
|
4431
|
-
async function handleSubModelCommand(client, directory) {
|
|
4432
|
-
await client.app.log({
|
|
4433
|
-
body: {
|
|
4434
|
-
service: "opencode-manifold",
|
|
4435
|
-
level: "info",
|
|
4436
|
-
message: "Starting /sub-model command"
|
|
4437
|
-
}
|
|
4438
|
-
});
|
|
4439
|
-
const agents = await getManifoldAgents(directory);
|
|
4440
|
-
if (agents.length === 0) {
|
|
4441
|
-
await client.tui.showToast({
|
|
4442
|
-
body: {
|
|
4443
|
-
type: "error",
|
|
4444
|
-
message: "No Manifold agents found. Run /manifold-init first."
|
|
4445
|
-
}
|
|
4446
|
-
});
|
|
4447
|
-
return;
|
|
4448
|
-
}
|
|
4449
|
-
const models = await getAvailableModels(client);
|
|
4450
|
-
if (models.length === 0) {
|
|
4451
|
-
await client.tui.showToast({
|
|
4452
|
-
body: {
|
|
4453
|
-
type: "error",
|
|
4454
|
-
message: "No models available. Configure providers first."
|
|
4455
|
-
}
|
|
4456
|
-
});
|
|
4457
|
-
return;
|
|
4458
|
-
}
|
|
4459
|
-
const agentList = agents.map((a) => `${a} (sub-agent)`).join(`
|
|
4460
|
-
`);
|
|
4461
|
-
await client.tui.appendPrompt({
|
|
4462
|
-
body: {
|
|
4463
|
-
type: "text",
|
|
4464
|
-
text: `\uD83D\uDCCB Select a Manifold sub-agent to configure:
|
|
4465
|
-
|
|
4466
|
-
${agentList}`
|
|
4467
|
-
}
|
|
4468
|
-
});
|
|
4469
|
-
await client.tui.publish({
|
|
4470
|
-
body: {
|
|
4471
|
-
type: "tui.prompt_append",
|
|
4472
|
-
data: {
|
|
4473
|
-
type: "text",
|
|
4474
|
-
text: `
|
|
4475
|
-
|
|
4476
|
-
Type the agent name (clerk, senior-dev, junior-dev, or debug): `
|
|
4477
|
-
}
|
|
4478
|
-
}
|
|
4479
|
-
});
|
|
4480
|
-
const agentResponse = await client.tui.control.next();
|
|
4481
|
-
const selectedAgent = agentResponse.data?.input?.trim().toLowerCase();
|
|
4482
|
-
if (!selectedAgent || !MANIFOLD_AGENTS.includes(selectedAgent)) {
|
|
4483
|
-
await client.tui.showToast({
|
|
4484
|
-
body: {
|
|
4485
|
-
type: "error",
|
|
4486
|
-
message: `Invalid agent selection. Must be one of: ${MANIFOLD_AGENTS.join(", ")}`
|
|
4487
|
-
}
|
|
4488
|
-
});
|
|
4489
|
-
return;
|
|
4490
|
-
}
|
|
4491
|
-
if (!agents.includes(selectedAgent)) {
|
|
4492
|
-
await client.tui.showToast({
|
|
4493
|
-
body: {
|
|
4494
|
-
type: "error",
|
|
4495
|
-
message: `Agent ${selectedAgent} not found in .opencode/agents/`
|
|
4496
|
-
}
|
|
4497
|
-
});
|
|
4498
|
-
return;
|
|
4499
|
-
}
|
|
4500
|
-
const currentModel = await readAgentFile(selectedAgent, directory).then((f) => f.frontmatter.model || "not set");
|
|
4501
|
-
const modelList = models.map((m, i2) => `${i2 + 1}. ${m.name} (${m.id})`).join(`
|
|
4502
|
-
`);
|
|
4503
|
-
await client.tui.appendPrompt({
|
|
4504
|
-
body: {
|
|
4505
|
-
type: "text",
|
|
4506
|
-
text: `
|
|
4507
|
-
\uD83D\uDCE6 Available models for ${selectedAgent} (current: ${currentModel}):
|
|
4508
|
-
|
|
4509
|
-
${modelList}`
|
|
4510
|
-
}
|
|
4511
|
-
});
|
|
4512
|
-
await client.tui.publish({
|
|
4513
|
-
body: {
|
|
4514
|
-
type: "tui.prompt_append",
|
|
4515
|
-
data: {
|
|
4516
|
-
type: "text",
|
|
4517
|
-
text: `
|
|
4518
|
-
|
|
4519
|
-
Type the model number or full model ID (e.g., anthropic/claude-sonnet-4-20250514): `
|
|
4520
|
-
}
|
|
4521
|
-
}
|
|
4522
|
-
});
|
|
4523
|
-
const modelResponse = await client.tui.control.next();
|
|
4524
|
-
const selectedModelInput = modelResponse.data?.input?.trim();
|
|
4525
|
-
if (!selectedModelInput) {
|
|
4526
|
-
await client.tui.showToast({
|
|
4527
|
-
body: {
|
|
4528
|
-
type: "error",
|
|
4529
|
-
message: "No model selected"
|
|
4530
|
-
}
|
|
4531
|
-
});
|
|
4532
|
-
return;
|
|
4533
|
-
}
|
|
4534
|
-
let selectedModelId;
|
|
4535
|
-
const modelIndex = parseInt(selectedModelInput, 10) - 1;
|
|
4536
|
-
if (!isNaN(modelIndex) && modelIndex >= 0 && modelIndex < models.length) {
|
|
4537
|
-
selectedModelId = models[modelIndex].id;
|
|
4538
|
-
} else {
|
|
4539
|
-
const found = models.find((m) => m.id.toLowerCase() === selectedModelInput.toLowerCase());
|
|
4540
|
-
if (found) {
|
|
4541
|
-
selectedModelId = found.id;
|
|
4542
|
-
}
|
|
4543
|
-
}
|
|
4544
|
-
if (!selectedModelId) {
|
|
4545
|
-
await client.tui.showToast({
|
|
4546
|
-
body: {
|
|
4547
|
-
type: "error",
|
|
4548
|
-
message: `Invalid model selection: ${selectedModelInput}`
|
|
4549
|
-
}
|
|
4550
|
-
});
|
|
4551
|
-
return;
|
|
4552
|
-
}
|
|
4553
|
-
await updateAgentModel(client, selectedAgent, selectedModelId, directory);
|
|
4554
|
-
await client.tui.showToast({
|
|
4555
|
-
body: {
|
|
4556
|
-
type: "success",
|
|
4557
|
-
message: `✅ Set ${selectedAgent} to ${selectedModelId}`
|
|
4558
|
-
}
|
|
4559
|
-
});
|
|
4560
|
-
await client.tui.appendPrompt({
|
|
4561
|
-
body: {
|
|
4562
|
-
type: "text",
|
|
4563
|
-
text: `
|
|
4564
|
-
✅ Model updated for ${selectedAgent}: ${selectedModelId}
|
|
4565
|
-
`
|
|
4566
|
-
}
|
|
4567
|
-
});
|
|
4568
|
-
}
|
|
4569
|
-
|
|
4570
|
-
// src/tools/clear-cache.ts
|
|
4571
|
-
import { rm } from "fs/promises";
|
|
4572
|
-
import { existsSync as existsSync7 } from "fs";
|
|
4573
|
-
import { join as join7, isAbsolute } from "path";
|
|
4574
|
-
import { homedir as homedir3 } from "os";
|
|
4575
|
-
var PROTECTED_PATHS = [
|
|
4576
|
-
"/",
|
|
4577
|
-
"/System",
|
|
4578
|
-
"/Applications",
|
|
4579
|
-
"/Users",
|
|
4580
|
-
"/etc",
|
|
4581
|
-
"/bin",
|
|
4582
|
-
"/sbin",
|
|
4583
|
-
"/usr"
|
|
4584
|
-
];
|
|
4585
|
-
function resolvePath(pathStr) {
|
|
4586
|
-
const expanded = pathStr.startsWith("~") ? join7(homedir3(), pathStr.slice(1)) : pathStr;
|
|
4587
|
-
return isAbsolute(expanded) ? expanded : join7(process.cwd(), expanded);
|
|
4588
|
-
}
|
|
4589
|
-
function isPathSafe(pathStr) {
|
|
4590
|
-
const normalized = pathStr.toLowerCase();
|
|
4591
|
-
if (normalized.includes("*")) {
|
|
4592
|
-
return { safe: false, reason: "Wildcards are not allowed for safety" };
|
|
4593
|
-
}
|
|
4594
|
-
for (const protectedPath of PROTECTED_PATHS) {
|
|
4595
|
-
if (normalized === protectedPath.toLowerCase() || normalized.startsWith(protectedPath.toLowerCase() + "/") || normalized.startsWith(protectedPath.toLowerCase() + "\\")) {
|
|
4596
|
-
return { safe: false, reason: `Cannot delete protected system path: ${protectedPath}` };
|
|
4597
|
-
}
|
|
4598
|
-
}
|
|
4599
|
-
if (normalized === homedir3().toLowerCase() || normalized.startsWith(homedir3().toLowerCase() + "/") && !normalized.includes("cache") && !normalized.includes("node_modules")) {
|
|
4600
|
-
const parts = normalized.replace(homedir3().toLowerCase(), "").split(/[\/\\]/).filter(Boolean);
|
|
4601
|
-
if (parts.length === 1) {
|
|
4602
|
-
return { safe: false, reason: "Cannot delete directories directly in home folder" };
|
|
4603
|
-
}
|
|
4604
|
-
}
|
|
4605
|
-
return { safe: true };
|
|
4606
|
-
}
|
|
4607
|
-
async function isProjectDirectory(pathStr) {
|
|
4608
|
-
try {
|
|
4609
|
-
const hasGit = existsSync7(join7(pathStr, ".git"));
|
|
4610
|
-
const hasOpencodeConfig = existsSync7(join7(pathStr, "opencode.json"));
|
|
4611
|
-
const hasPackageJson = existsSync7(join7(pathStr, "package.json"));
|
|
4612
|
-
if (hasGit && (hasOpencodeConfig || hasPackageJson)) {
|
|
4613
|
-
return true;
|
|
4614
|
-
}
|
|
4615
|
-
const entries = await Promise.all([
|
|
4616
|
-
existsSync7(join7(pathStr, ".git")) ? "git" : null,
|
|
4617
|
-
existsSync7(join7(pathStr, "opencode.json")) ? "opencode" : null
|
|
4618
|
-
].filter(Boolean));
|
|
4619
|
-
return entries.length >= 2;
|
|
4620
|
-
} catch {
|
|
4621
|
-
return false;
|
|
4622
|
-
}
|
|
4623
|
-
}
|
|
4624
|
-
async function clearCachePaths(paths) {
|
|
4625
|
-
const cleared = [];
|
|
4626
|
-
const skipped = [];
|
|
4627
|
-
const blocked = [];
|
|
4628
|
-
for (const pathStr of paths) {
|
|
4629
|
-
const resolved = resolvePath(pathStr);
|
|
4630
|
-
const safetyCheck = isPathSafe(resolved);
|
|
4631
|
-
if (!safetyCheck.safe) {
|
|
4632
|
-
blocked.push({ path: resolved, reason: safetyCheck.reason || "Failed safety check" });
|
|
4633
|
-
continue;
|
|
4634
|
-
}
|
|
4635
|
-
if (!existsSync7(resolved)) {
|
|
4636
|
-
skipped.push(resolved);
|
|
4637
|
-
continue;
|
|
4638
|
-
}
|
|
4639
|
-
const isProject = await isProjectDirectory(resolved);
|
|
4640
|
-
if (isProject) {
|
|
4641
|
-
blocked.push({ path: resolved, reason: "Appears to be a project directory (contains .git + config files)" });
|
|
4642
|
-
continue;
|
|
4643
|
-
}
|
|
4644
|
-
try {
|
|
4645
|
-
await rm(resolved, { recursive: true, force: true });
|
|
4646
|
-
cleared.push(resolved);
|
|
4647
|
-
} catch (error) {
|
|
4648
|
-
blocked.push({ path: resolved, reason: `Failed to delete: ${error}` });
|
|
4649
|
-
}
|
|
4650
|
-
}
|
|
4651
|
-
return { cleared, skipped, blocked };
|
|
4652
|
-
}
|
|
4653
|
-
async function handleUpdateCommand(client, directory) {
|
|
4654
|
-
await client.app.log({
|
|
4655
|
-
body: {
|
|
4656
|
-
service: "opencode-manifold",
|
|
4657
|
-
level: "info",
|
|
4658
|
-
message: "Starting /manifold-update command"
|
|
4659
|
-
}
|
|
4660
|
-
});
|
|
4661
|
-
const settings = await readSettings(directory);
|
|
4662
|
-
const configuredPaths = settings.updateCachePaths || [];
|
|
4663
|
-
if (configuredPaths.length === 0) {
|
|
4664
|
-
const examplePaths = [
|
|
4665
|
-
"~/.cache/opencode/packages/opencode-manifold@latest",
|
|
4666
|
-
"~/node_modules/opencode-manifold"
|
|
4667
|
-
];
|
|
4668
|
-
await client.tui.appendPrompt({
|
|
4669
|
-
body: {
|
|
4670
|
-
type: "text",
|
|
4671
|
-
text: `\uD83D\uDD04 **Manifold Plugin Update**
|
|
4672
|
-
|
|
4673
|
-
No cache paths configured in \`Manifold/settings.json\`.
|
|
4674
|
-
|
|
4675
|
-
To enable this feature, add cache paths to your settings:
|
|
4676
|
-
|
|
4677
|
-
\`\`\`json
|
|
4678
|
-
{
|
|
4679
|
-
"updateCachePaths": [
|
|
4680
|
-
"~/.cache/opencode/packages/opencode-manifold@latest",
|
|
4681
|
-
"~/node_modules/opencode-manifold"
|
|
4682
|
-
]
|
|
4683
|
-
}
|
|
4684
|
-
\`\`\`
|
|
4685
|
-
|
|
4686
|
-
**Safety features:**
|
|
4687
|
-
• Wildcards are blocked
|
|
4688
|
-
• System directories are protected
|
|
4689
|
-
• Project directories (with .git + config) are protected
|
|
4690
|
-
|
|
4691
|
-
**macOS users:** The paths above are typical cache locations.
|
|
4692
|
-
**Linux/Windows users:** Add your opencode cache paths manually.
|
|
4693
|
-
`
|
|
4694
|
-
}
|
|
4695
|
-
});
|
|
4696
|
-
await client.tui.showToast({
|
|
4697
|
-
body: {
|
|
4698
|
-
type: "info",
|
|
4699
|
-
message: "Configure updateCachePaths in Manifold/settings.json"
|
|
4700
|
-
}
|
|
4701
|
-
});
|
|
4702
|
-
return;
|
|
4703
|
-
}
|
|
4704
|
-
const resolvedPaths = configuredPaths.map(resolvePath);
|
|
4705
|
-
await client.tui.appendPrompt({
|
|
4706
|
-
body: {
|
|
4707
|
-
type: "text",
|
|
4708
|
-
text: `\uD83D\uDD04 **Manifold Plugin Update**
|
|
4709
|
-
|
|
4710
|
-
The following paths will be cleared:
|
|
4711
|
-
|
|
4712
|
-
${resolvedPaths.map((p) => `• ${p}`).join(`
|
|
4713
|
-
`)}
|
|
4714
|
-
|
|
4715
|
-
⚠️ **This action is irreversible.** These paths are read from \`Manifold/settings.json\`.
|
|
4716
|
-
`
|
|
4717
|
-
}
|
|
4718
|
-
});
|
|
4719
|
-
await client.tui.publish({
|
|
4720
|
-
body: {
|
|
4721
|
-
type: "tui.prompt_append",
|
|
4722
|
-
data: {
|
|
4723
|
-
type: "text",
|
|
4724
|
-
text: `
|
|
4725
|
-
|
|
4726
|
-
Type 'yes' to confirm and clear cache: `
|
|
4727
|
-
}
|
|
4728
|
-
}
|
|
4729
|
-
});
|
|
4730
|
-
const response = await client.tui.control.next();
|
|
4731
|
-
const answer = response.data?.input?.trim().toLowerCase();
|
|
4732
|
-
if (answer !== "yes" && answer !== "y") {
|
|
4733
|
-
await client.tui.appendPrompt({
|
|
4734
|
-
body: {
|
|
4735
|
-
type: "text",
|
|
4736
|
-
text: `
|
|
4737
|
-
❌ Cancelled. No cache cleared.
|
|
4738
|
-
`
|
|
4739
|
-
}
|
|
4740
|
-
});
|
|
4741
|
-
return;
|
|
4742
|
-
}
|
|
4743
|
-
const { cleared, skipped, blocked } = await clearCachePaths(configuredPaths);
|
|
4744
|
-
let message = `
|
|
4745
|
-
`;
|
|
4746
|
-
if (cleared.length > 0) {
|
|
4747
|
-
message += `✅ **Cleared:**
|
|
4748
|
-
${cleared.map((p) => `• ${p}`).join(`
|
|
4749
|
-
`)}
|
|
4750
|
-
|
|
4751
|
-
`;
|
|
4752
|
-
}
|
|
4753
|
-
if (skipped.length > 0) {
|
|
4754
|
-
message += `⏭️ **Already clean / skipped:**
|
|
4755
|
-
${skipped.map((p) => `• ${p}`).join(`
|
|
4756
|
-
`)}
|
|
4757
|
-
|
|
4758
|
-
`;
|
|
4759
|
-
}
|
|
4760
|
-
if (blocked.length > 0) {
|
|
4761
|
-
message += `\uD83D\uDEAB **Blocked (safety check):**
|
|
4762
|
-
`;
|
|
4763
|
-
for (const item of blocked) {
|
|
4764
|
-
message += `• ${item.path}
|
|
4765
|
-
Reason: ${item.reason}
|
|
4766
|
-
`;
|
|
4767
|
-
}
|
|
4768
|
-
message += `
|
|
4769
|
-
`;
|
|
4770
|
-
}
|
|
4771
|
-
if (cleared.length > 0) {
|
|
4772
|
-
message += `**Next step:** Restart opencode to pull the latest plugin version.
|
|
4773
|
-
|
|
4774
|
-
Your project files (.opencode/, Manifold/) are untouched — only the configured cache paths were cleared.
|
|
4775
|
-
`;
|
|
4776
|
-
} else if (blocked.length > 0) {
|
|
4777
|
-
message += `⚠️ No paths were cleared. Review the blocked paths above and adjust your settings if needed.
|
|
4778
|
-
`;
|
|
4779
|
-
}
|
|
4780
|
-
await client.tui.appendPrompt({
|
|
4781
|
-
body: {
|
|
4782
|
-
type: "text",
|
|
4783
|
-
text: message
|
|
4784
|
-
}
|
|
4785
|
-
});
|
|
4786
|
-
if (cleared.length > 0) {
|
|
4787
|
-
await client.tui.showToast({
|
|
4788
|
-
body: {
|
|
4789
|
-
type: "success",
|
|
4790
|
-
message: `Cache cleared (${cleared.length} paths). Restart opencode to update.`
|
|
4791
|
-
}
|
|
4792
|
-
});
|
|
4793
|
-
} else if (blocked.length > 0) {
|
|
4794
|
-
await client.tui.showToast({
|
|
4795
|
-
body: {
|
|
4796
|
-
type: "error",
|
|
4797
|
-
message: `Cache update blocked (${blocked.length} paths failed safety check)`
|
|
4798
|
-
}
|
|
4799
|
-
});
|
|
4800
|
-
}
|
|
4801
|
-
await client.app.log({
|
|
4802
|
-
body: {
|
|
4803
|
-
service: "opencode-manifold",
|
|
4804
|
-
level: "info",
|
|
4805
|
-
message: `/manifold-update complete: cleared ${cleared.length}, skipped ${skipped.length}, blocked ${blocked.length}`
|
|
4806
|
-
}
|
|
4807
|
-
});
|
|
4808
|
-
}
|
|
4809
|
-
|
|
4810
|
-
// src/index.ts
|
|
4811
4337
|
var ManifoldPlugin = async (ctx) => {
|
|
4812
4338
|
setPluginContext(ctx.client);
|
|
4813
4339
|
setPluginContext2(ctx.client);
|
|
@@ -4842,15 +4368,8 @@ var ManifoldPlugin = async (ctx) => {
|
|
|
4842
4368
|
}
|
|
4843
4369
|
];
|
|
4844
4370
|
}
|
|
4845
|
-
} else if (input.command === "manifold-models") {
|
|
4846
|
-
await handleSubModelCommand(ctx.client, ctx.directory);
|
|
4847
|
-
output.parts = [{ type: "text", text: "Manifold sub-agent configuration started." }];
|
|
4848
|
-
} else if (input.command === "manifold-update") {
|
|
4849
|
-
await handleUpdateCommand(ctx.client, ctx.directory);
|
|
4850
|
-
output.parts = [{ type: "text", text: "Manifold update process started." }];
|
|
4851
4371
|
} else if (input.command === "manifold-model-path") {
|
|
4852
|
-
const
|
|
4853
|
-
const modelPath = await getModelPath2(ctx.client, input.sessionID || undefined);
|
|
4372
|
+
const modelPath = await getModelPath(ctx.client, input.sessionID || undefined);
|
|
4854
4373
|
output.parts = [{ type: "text", text: modelPath }];
|
|
4855
4374
|
}
|
|
4856
4375
|
}
|