opencode-manifold 0.4.17 → 0.4.19
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/dist/index.js +486 -5
- package/dist/tui.js +2 -211
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
1
|
-
import { createRequire } from "node:module";
|
|
2
|
-
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
3
|
-
|
|
4
1
|
// src/init.ts
|
|
5
2
|
import { readFile, writeFile, mkdir, readdir } from "fs/promises";
|
|
6
3
|
import { existsSync } from "fs";
|
|
7
4
|
import { join, dirname } from "path";
|
|
8
5
|
import { fileURLToPath } from "url";
|
|
9
6
|
import { homedir } from "os";
|
|
10
|
-
import { createRequire
|
|
7
|
+
import { createRequire } from "module";
|
|
11
8
|
var __dirname2 = dirname(fileURLToPath(import.meta.url));
|
|
12
|
-
var require2 =
|
|
9
|
+
var require2 = createRequire(import.meta.url);
|
|
13
10
|
var bundledTemplatesDir = join(__dirname2, "..", "src", "templates");
|
|
14
11
|
var globalTemplatesDir = join(homedir(), ".config", "opencode", "manifold");
|
|
15
12
|
async function getPluginVersion() {
|
|
@@ -4216,6 +4213,484 @@ ${testResult.result.output}`,
|
|
|
4216
4213
|
}
|
|
4217
4214
|
});
|
|
4218
4215
|
|
|
4216
|
+
// src/tools/set-subagent-model.ts
|
|
4217
|
+
import { readFile as readFile5, writeFile as writeFile5, readdir as readdir3 } from "fs/promises";
|
|
4218
|
+
import { existsSync as existsSync6 } from "fs";
|
|
4219
|
+
import { join as join6 } from "path";
|
|
4220
|
+
import { homedir as homedir2 } from "os";
|
|
4221
|
+
var MANIFOLD_AGENTS = ["clerk", "senior-dev", "junior-dev", "debug"];
|
|
4222
|
+
async function getManifoldAgents(directory) {
|
|
4223
|
+
const agentsDir = join6(directory, ".opencode", "agents");
|
|
4224
|
+
if (!existsSync6(agentsDir)) {
|
|
4225
|
+
return [];
|
|
4226
|
+
}
|
|
4227
|
+
const files = await readdir3(agentsDir);
|
|
4228
|
+
const agents = [];
|
|
4229
|
+
for (const file of files) {
|
|
4230
|
+
if (!file.endsWith(".md"))
|
|
4231
|
+
continue;
|
|
4232
|
+
const name = file.replace(".md", "");
|
|
4233
|
+
if (MANIFOLD_AGENTS.includes(name)) {
|
|
4234
|
+
agents.push(name);
|
|
4235
|
+
}
|
|
4236
|
+
}
|
|
4237
|
+
return agents;
|
|
4238
|
+
}
|
|
4239
|
+
async function getAvailableModels(client) {
|
|
4240
|
+
try {
|
|
4241
|
+
const result = await client.config.providers();
|
|
4242
|
+
const providers = result.data?.providers || [];
|
|
4243
|
+
const models = [];
|
|
4244
|
+
for (const provider of providers) {
|
|
4245
|
+
if (provider.models) {
|
|
4246
|
+
for (const [modelId, model] of Object.entries(provider.models)) {
|
|
4247
|
+
models.push({
|
|
4248
|
+
id: `${provider.id}/${modelId}`,
|
|
4249
|
+
name: model.name || modelId,
|
|
4250
|
+
providerID: provider.id
|
|
4251
|
+
});
|
|
4252
|
+
}
|
|
4253
|
+
}
|
|
4254
|
+
}
|
|
4255
|
+
return models.sort((a, b) => a.name.localeCompare(b.name));
|
|
4256
|
+
} catch (error) {
|
|
4257
|
+
await client.app.log({
|
|
4258
|
+
body: {
|
|
4259
|
+
service: "opencode-manifold",
|
|
4260
|
+
level: "error",
|
|
4261
|
+
message: `Error fetching models: ${error}`
|
|
4262
|
+
}
|
|
4263
|
+
});
|
|
4264
|
+
return [];
|
|
4265
|
+
}
|
|
4266
|
+
}
|
|
4267
|
+
async function readAgentFile(agentName, directory) {
|
|
4268
|
+
const agentPath = join6(directory, ".opencode", "agents", `${agentName}.md`);
|
|
4269
|
+
if (!existsSync6(agentPath)) {
|
|
4270
|
+
const globalPath = join6(homedir2(), ".config", "opencode", "manifold", "agents", `${agentName}.md`);
|
|
4271
|
+
if (existsSync6(globalPath)) {
|
|
4272
|
+
const content2 = await readFile5(globalPath, "utf-8");
|
|
4273
|
+
const { frontmatter: frontmatter2, body: body2 } = parseFrontmatter(content2);
|
|
4274
|
+
return { content: content2, frontmatter: frontmatter2, body: body2 };
|
|
4275
|
+
}
|
|
4276
|
+
throw new Error(`Agent file not found for ${agentName}`);
|
|
4277
|
+
}
|
|
4278
|
+
const content = await readFile5(agentPath, "utf-8");
|
|
4279
|
+
const { frontmatter, body } = parseFrontmatter(content);
|
|
4280
|
+
return { content, frontmatter, body };
|
|
4281
|
+
}
|
|
4282
|
+
function parseFrontmatter(content) {
|
|
4283
|
+
const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
4284
|
+
if (!match) {
|
|
4285
|
+
return { frontmatter: {}, body: content };
|
|
4286
|
+
}
|
|
4287
|
+
const frontmatterYaml = match[1];
|
|
4288
|
+
const body = match[2];
|
|
4289
|
+
const frontmatter = jsYaml.load(frontmatterYaml) || {};
|
|
4290
|
+
return { frontmatter, body };
|
|
4291
|
+
}
|
|
4292
|
+
function buildAgentFile(frontmatter, body) {
|
|
4293
|
+
const yamlContent = jsYaml.dump(frontmatter, {
|
|
4294
|
+
lineWidth: -1,
|
|
4295
|
+
noCompatMode: true
|
|
4296
|
+
});
|
|
4297
|
+
return `---
|
|
4298
|
+
${yamlContent}---
|
|
4299
|
+
${body}`;
|
|
4300
|
+
}
|
|
4301
|
+
async function updateAgentModel(client, agentName, modelId, directory) {
|
|
4302
|
+
const { frontmatter, body } = await readAgentFile(agentName, directory);
|
|
4303
|
+
frontmatter.model = modelId;
|
|
4304
|
+
const newContent = buildAgentFile(frontmatter, body);
|
|
4305
|
+
const agentPath = join6(directory, ".opencode", "agents", `${agentName}.md`);
|
|
4306
|
+
await writeFile5(agentPath, newContent);
|
|
4307
|
+
await client.app.log({
|
|
4308
|
+
body: {
|
|
4309
|
+
service: "opencode-manifold",
|
|
4310
|
+
level: "info",
|
|
4311
|
+
message: `Updated ${agentName} model to ${modelId}`
|
|
4312
|
+
}
|
|
4313
|
+
});
|
|
4314
|
+
}
|
|
4315
|
+
async function handleSubModelCommand(client, directory) {
|
|
4316
|
+
await client.app.log({
|
|
4317
|
+
body: {
|
|
4318
|
+
service: "opencode-manifold",
|
|
4319
|
+
level: "info",
|
|
4320
|
+
message: "Starting /sub-model command"
|
|
4321
|
+
}
|
|
4322
|
+
});
|
|
4323
|
+
const agents = await getManifoldAgents(directory);
|
|
4324
|
+
if (agents.length === 0) {
|
|
4325
|
+
await client.tui.showToast({
|
|
4326
|
+
body: {
|
|
4327
|
+
type: "error",
|
|
4328
|
+
message: "No Manifold agents found. Run /manifold-init first."
|
|
4329
|
+
}
|
|
4330
|
+
});
|
|
4331
|
+
return;
|
|
4332
|
+
}
|
|
4333
|
+
const models = await getAvailableModels(client);
|
|
4334
|
+
if (models.length === 0) {
|
|
4335
|
+
await client.tui.showToast({
|
|
4336
|
+
body: {
|
|
4337
|
+
type: "error",
|
|
4338
|
+
message: "No models available. Configure providers first."
|
|
4339
|
+
}
|
|
4340
|
+
});
|
|
4341
|
+
return;
|
|
4342
|
+
}
|
|
4343
|
+
const agentList = agents.map((a) => `${a} (sub-agent)`).join(`
|
|
4344
|
+
`);
|
|
4345
|
+
await client.tui.appendPrompt({
|
|
4346
|
+
body: {
|
|
4347
|
+
type: "text",
|
|
4348
|
+
text: `\uD83D\uDCCB Select a Manifold sub-agent to configure:
|
|
4349
|
+
|
|
4350
|
+
${agentList}`
|
|
4351
|
+
}
|
|
4352
|
+
});
|
|
4353
|
+
await client.tui.publish({
|
|
4354
|
+
body: {
|
|
4355
|
+
type: "tui.prompt_append",
|
|
4356
|
+
data: {
|
|
4357
|
+
type: "text",
|
|
4358
|
+
text: `
|
|
4359
|
+
|
|
4360
|
+
Type the agent name (clerk, senior-dev, junior-dev, or debug): `
|
|
4361
|
+
}
|
|
4362
|
+
}
|
|
4363
|
+
});
|
|
4364
|
+
const agentResponse = await client.tui.control.next();
|
|
4365
|
+
const selectedAgent = agentResponse.data?.input?.trim().toLowerCase();
|
|
4366
|
+
if (!selectedAgent || !MANIFOLD_AGENTS.includes(selectedAgent)) {
|
|
4367
|
+
await client.tui.showToast({
|
|
4368
|
+
body: {
|
|
4369
|
+
type: "error",
|
|
4370
|
+
message: `Invalid agent selection. Must be one of: ${MANIFOLD_AGENTS.join(", ")}`
|
|
4371
|
+
}
|
|
4372
|
+
});
|
|
4373
|
+
return;
|
|
4374
|
+
}
|
|
4375
|
+
if (!agents.includes(selectedAgent)) {
|
|
4376
|
+
await client.tui.showToast({
|
|
4377
|
+
body: {
|
|
4378
|
+
type: "error",
|
|
4379
|
+
message: `Agent ${selectedAgent} not found in .opencode/agents/`
|
|
4380
|
+
}
|
|
4381
|
+
});
|
|
4382
|
+
return;
|
|
4383
|
+
}
|
|
4384
|
+
const currentModel = await readAgentFile(selectedAgent, directory).then((f) => f.frontmatter.model || "not set");
|
|
4385
|
+
const modelList = models.map((m, i2) => `${i2 + 1}. ${m.name} (${m.id})`).join(`
|
|
4386
|
+
`);
|
|
4387
|
+
await client.tui.appendPrompt({
|
|
4388
|
+
body: {
|
|
4389
|
+
type: "text",
|
|
4390
|
+
text: `
|
|
4391
|
+
\uD83D\uDCE6 Available models for ${selectedAgent} (current: ${currentModel}):
|
|
4392
|
+
|
|
4393
|
+
${modelList}`
|
|
4394
|
+
}
|
|
4395
|
+
});
|
|
4396
|
+
await client.tui.publish({
|
|
4397
|
+
body: {
|
|
4398
|
+
type: "tui.prompt_append",
|
|
4399
|
+
data: {
|
|
4400
|
+
type: "text",
|
|
4401
|
+
text: `
|
|
4402
|
+
|
|
4403
|
+
Type the model number or full model ID (e.g., anthropic/claude-sonnet-4-20250514): `
|
|
4404
|
+
}
|
|
4405
|
+
}
|
|
4406
|
+
});
|
|
4407
|
+
const modelResponse = await client.tui.control.next();
|
|
4408
|
+
const selectedModelInput = modelResponse.data?.input?.trim();
|
|
4409
|
+
if (!selectedModelInput) {
|
|
4410
|
+
await client.tui.showToast({
|
|
4411
|
+
body: {
|
|
4412
|
+
type: "error",
|
|
4413
|
+
message: "No model selected"
|
|
4414
|
+
}
|
|
4415
|
+
});
|
|
4416
|
+
return;
|
|
4417
|
+
}
|
|
4418
|
+
let selectedModelId;
|
|
4419
|
+
const modelIndex = parseInt(selectedModelInput, 10) - 1;
|
|
4420
|
+
if (!isNaN(modelIndex) && modelIndex >= 0 && modelIndex < models.length) {
|
|
4421
|
+
selectedModelId = models[modelIndex].id;
|
|
4422
|
+
} else {
|
|
4423
|
+
const found = models.find((m) => m.id.toLowerCase() === selectedModelInput.toLowerCase());
|
|
4424
|
+
if (found) {
|
|
4425
|
+
selectedModelId = found.id;
|
|
4426
|
+
}
|
|
4427
|
+
}
|
|
4428
|
+
if (!selectedModelId) {
|
|
4429
|
+
await client.tui.showToast({
|
|
4430
|
+
body: {
|
|
4431
|
+
type: "error",
|
|
4432
|
+
message: `Invalid model selection: ${selectedModelInput}`
|
|
4433
|
+
}
|
|
4434
|
+
});
|
|
4435
|
+
return;
|
|
4436
|
+
}
|
|
4437
|
+
await updateAgentModel(client, selectedAgent, selectedModelId, directory);
|
|
4438
|
+
await client.tui.showToast({
|
|
4439
|
+
body: {
|
|
4440
|
+
type: "success",
|
|
4441
|
+
message: `✅ Set ${selectedAgent} to ${selectedModelId}`
|
|
4442
|
+
}
|
|
4443
|
+
});
|
|
4444
|
+
await client.tui.appendPrompt({
|
|
4445
|
+
body: {
|
|
4446
|
+
type: "text",
|
|
4447
|
+
text: `
|
|
4448
|
+
✅ Model updated for ${selectedAgent}: ${selectedModelId}
|
|
4449
|
+
`
|
|
4450
|
+
}
|
|
4451
|
+
});
|
|
4452
|
+
}
|
|
4453
|
+
|
|
4454
|
+
// src/tools/clear-cache.ts
|
|
4455
|
+
import { rm } from "fs/promises";
|
|
4456
|
+
import { existsSync as existsSync7 } from "fs";
|
|
4457
|
+
import { join as join7, isAbsolute } from "path";
|
|
4458
|
+
import { homedir as homedir3 } from "os";
|
|
4459
|
+
var PROTECTED_PATHS = [
|
|
4460
|
+
"/",
|
|
4461
|
+
"/System",
|
|
4462
|
+
"/Applications",
|
|
4463
|
+
"/Users",
|
|
4464
|
+
"/etc",
|
|
4465
|
+
"/bin",
|
|
4466
|
+
"/sbin",
|
|
4467
|
+
"/usr"
|
|
4468
|
+
];
|
|
4469
|
+
function resolvePath(pathStr) {
|
|
4470
|
+
const expanded = pathStr.startsWith("~") ? join7(homedir3(), pathStr.slice(1)) : pathStr;
|
|
4471
|
+
return isAbsolute(expanded) ? expanded : join7(process.cwd(), expanded);
|
|
4472
|
+
}
|
|
4473
|
+
function isPathSafe(pathStr) {
|
|
4474
|
+
const normalized = pathStr.toLowerCase();
|
|
4475
|
+
if (normalized.includes("*")) {
|
|
4476
|
+
return { safe: false, reason: "Wildcards are not allowed for safety" };
|
|
4477
|
+
}
|
|
4478
|
+
for (const protectedPath of PROTECTED_PATHS) {
|
|
4479
|
+
if (normalized === protectedPath.toLowerCase() || normalized.startsWith(protectedPath.toLowerCase() + "/") || normalized.startsWith(protectedPath.toLowerCase() + "\\")) {
|
|
4480
|
+
return { safe: false, reason: `Cannot delete protected system path: ${protectedPath}` };
|
|
4481
|
+
}
|
|
4482
|
+
}
|
|
4483
|
+
if (normalized === homedir3().toLowerCase() || normalized.startsWith(homedir3().toLowerCase() + "/") && !normalized.includes("cache") && !normalized.includes("node_modules")) {
|
|
4484
|
+
const parts = normalized.replace(homedir3().toLowerCase(), "").split(/[\/\\]/).filter(Boolean);
|
|
4485
|
+
if (parts.length === 1) {
|
|
4486
|
+
return { safe: false, reason: "Cannot delete directories directly in home folder" };
|
|
4487
|
+
}
|
|
4488
|
+
}
|
|
4489
|
+
return { safe: true };
|
|
4490
|
+
}
|
|
4491
|
+
async function isProjectDirectory(pathStr) {
|
|
4492
|
+
try {
|
|
4493
|
+
const hasGit = existsSync7(join7(pathStr, ".git"));
|
|
4494
|
+
const hasOpencodeConfig = existsSync7(join7(pathStr, "opencode.json"));
|
|
4495
|
+
const hasPackageJson = existsSync7(join7(pathStr, "package.json"));
|
|
4496
|
+
if (hasGit && (hasOpencodeConfig || hasPackageJson)) {
|
|
4497
|
+
return true;
|
|
4498
|
+
}
|
|
4499
|
+
const entries = await Promise.all([
|
|
4500
|
+
existsSync7(join7(pathStr, ".git")) ? "git" : null,
|
|
4501
|
+
existsSync7(join7(pathStr, "opencode.json")) ? "opencode" : null
|
|
4502
|
+
].filter(Boolean));
|
|
4503
|
+
return entries.length >= 2;
|
|
4504
|
+
} catch {
|
|
4505
|
+
return false;
|
|
4506
|
+
}
|
|
4507
|
+
}
|
|
4508
|
+
async function clearCachePaths(paths) {
|
|
4509
|
+
const cleared = [];
|
|
4510
|
+
const skipped = [];
|
|
4511
|
+
const blocked = [];
|
|
4512
|
+
for (const pathStr of paths) {
|
|
4513
|
+
const resolved = resolvePath(pathStr);
|
|
4514
|
+
const safetyCheck = isPathSafe(resolved);
|
|
4515
|
+
if (!safetyCheck.safe) {
|
|
4516
|
+
blocked.push({ path: resolved, reason: safetyCheck.reason || "Failed safety check" });
|
|
4517
|
+
continue;
|
|
4518
|
+
}
|
|
4519
|
+
if (!existsSync7(resolved)) {
|
|
4520
|
+
skipped.push(resolved);
|
|
4521
|
+
continue;
|
|
4522
|
+
}
|
|
4523
|
+
const isProject = await isProjectDirectory(resolved);
|
|
4524
|
+
if (isProject) {
|
|
4525
|
+
blocked.push({ path: resolved, reason: "Appears to be a project directory (contains .git + config files)" });
|
|
4526
|
+
continue;
|
|
4527
|
+
}
|
|
4528
|
+
try {
|
|
4529
|
+
await rm(resolved, { recursive: true, force: true });
|
|
4530
|
+
cleared.push(resolved);
|
|
4531
|
+
} catch (error) {
|
|
4532
|
+
blocked.push({ path: resolved, reason: `Failed to delete: ${error}` });
|
|
4533
|
+
}
|
|
4534
|
+
}
|
|
4535
|
+
return { cleared, skipped, blocked };
|
|
4536
|
+
}
|
|
4537
|
+
async function handleUpdateCommand(client, directory) {
|
|
4538
|
+
await client.app.log({
|
|
4539
|
+
body: {
|
|
4540
|
+
service: "opencode-manifold",
|
|
4541
|
+
level: "info",
|
|
4542
|
+
message: "Starting /manifold-update command"
|
|
4543
|
+
}
|
|
4544
|
+
});
|
|
4545
|
+
const settings = await readSettings(directory);
|
|
4546
|
+
const configuredPaths = settings.updateCachePaths || [];
|
|
4547
|
+
if (configuredPaths.length === 0) {
|
|
4548
|
+
const examplePaths = [
|
|
4549
|
+
"~/.cache/opencode/packages/opencode-manifold@latest",
|
|
4550
|
+
"~/node_modules/opencode-manifold"
|
|
4551
|
+
];
|
|
4552
|
+
await client.tui.appendPrompt({
|
|
4553
|
+
body: {
|
|
4554
|
+
type: "text",
|
|
4555
|
+
text: `\uD83D\uDD04 **Manifold Plugin Update**
|
|
4556
|
+
|
|
4557
|
+
No cache paths configured in \`Manifold/settings.json\`.
|
|
4558
|
+
|
|
4559
|
+
To enable this feature, add cache paths to your settings:
|
|
4560
|
+
|
|
4561
|
+
\`\`\`json
|
|
4562
|
+
{
|
|
4563
|
+
"updateCachePaths": [
|
|
4564
|
+
"~/.cache/opencode/packages/opencode-manifold@latest",
|
|
4565
|
+
"~/node_modules/opencode-manifold"
|
|
4566
|
+
]
|
|
4567
|
+
}
|
|
4568
|
+
\`\`\`
|
|
4569
|
+
|
|
4570
|
+
**Safety features:**
|
|
4571
|
+
• Wildcards are blocked
|
|
4572
|
+
• System directories are protected
|
|
4573
|
+
• Project directories (with .git + config) are protected
|
|
4574
|
+
|
|
4575
|
+
**macOS users:** The paths above are typical cache locations.
|
|
4576
|
+
**Linux/Windows users:** Add your opencode cache paths manually.
|
|
4577
|
+
`
|
|
4578
|
+
}
|
|
4579
|
+
});
|
|
4580
|
+
await client.tui.showToast({
|
|
4581
|
+
body: {
|
|
4582
|
+
type: "info",
|
|
4583
|
+
message: "Configure updateCachePaths in Manifold/settings.json"
|
|
4584
|
+
}
|
|
4585
|
+
});
|
|
4586
|
+
return;
|
|
4587
|
+
}
|
|
4588
|
+
const resolvedPaths = configuredPaths.map(resolvePath);
|
|
4589
|
+
await client.tui.appendPrompt({
|
|
4590
|
+
body: {
|
|
4591
|
+
type: "text",
|
|
4592
|
+
text: `\uD83D\uDD04 **Manifold Plugin Update**
|
|
4593
|
+
|
|
4594
|
+
The following paths will be cleared:
|
|
4595
|
+
|
|
4596
|
+
${resolvedPaths.map((p) => `• ${p}`).join(`
|
|
4597
|
+
`)}
|
|
4598
|
+
|
|
4599
|
+
⚠️ **This action is irreversible.** These paths are read from \`Manifold/settings.json\`.
|
|
4600
|
+
`
|
|
4601
|
+
}
|
|
4602
|
+
});
|
|
4603
|
+
await client.tui.publish({
|
|
4604
|
+
body: {
|
|
4605
|
+
type: "tui.prompt_append",
|
|
4606
|
+
data: {
|
|
4607
|
+
type: "text",
|
|
4608
|
+
text: `
|
|
4609
|
+
|
|
4610
|
+
Type 'yes' to confirm and clear cache: `
|
|
4611
|
+
}
|
|
4612
|
+
}
|
|
4613
|
+
});
|
|
4614
|
+
const response = await client.tui.control.next();
|
|
4615
|
+
const answer = response.data?.input?.trim().toLowerCase();
|
|
4616
|
+
if (answer !== "yes" && answer !== "y") {
|
|
4617
|
+
await client.tui.appendPrompt({
|
|
4618
|
+
body: {
|
|
4619
|
+
type: "text",
|
|
4620
|
+
text: `
|
|
4621
|
+
❌ Cancelled. No cache cleared.
|
|
4622
|
+
`
|
|
4623
|
+
}
|
|
4624
|
+
});
|
|
4625
|
+
return;
|
|
4626
|
+
}
|
|
4627
|
+
const { cleared, skipped, blocked } = await clearCachePaths(configuredPaths);
|
|
4628
|
+
let message = `
|
|
4629
|
+
`;
|
|
4630
|
+
if (cleared.length > 0) {
|
|
4631
|
+
message += `✅ **Cleared:**
|
|
4632
|
+
${cleared.map((p) => `• ${p}`).join(`
|
|
4633
|
+
`)}
|
|
4634
|
+
|
|
4635
|
+
`;
|
|
4636
|
+
}
|
|
4637
|
+
if (skipped.length > 0) {
|
|
4638
|
+
message += `⏭️ **Already clean / skipped:**
|
|
4639
|
+
${skipped.map((p) => `• ${p}`).join(`
|
|
4640
|
+
`)}
|
|
4641
|
+
|
|
4642
|
+
`;
|
|
4643
|
+
}
|
|
4644
|
+
if (blocked.length > 0) {
|
|
4645
|
+
message += `\uD83D\uDEAB **Blocked (safety check):**
|
|
4646
|
+
`;
|
|
4647
|
+
for (const item of blocked) {
|
|
4648
|
+
message += `• ${item.path}
|
|
4649
|
+
Reason: ${item.reason}
|
|
4650
|
+
`;
|
|
4651
|
+
}
|
|
4652
|
+
message += `
|
|
4653
|
+
`;
|
|
4654
|
+
}
|
|
4655
|
+
if (cleared.length > 0) {
|
|
4656
|
+
message += `**Next step:** Restart opencode to pull the latest plugin version.
|
|
4657
|
+
|
|
4658
|
+
Your project files (.opencode/, Manifold/) are untouched — only the configured cache paths were cleared.
|
|
4659
|
+
`;
|
|
4660
|
+
} else if (blocked.length > 0) {
|
|
4661
|
+
message += `⚠️ No paths were cleared. Review the blocked paths above and adjust your settings if needed.
|
|
4662
|
+
`;
|
|
4663
|
+
}
|
|
4664
|
+
await client.tui.appendPrompt({
|
|
4665
|
+
body: {
|
|
4666
|
+
type: "text",
|
|
4667
|
+
text: message
|
|
4668
|
+
}
|
|
4669
|
+
});
|
|
4670
|
+
if (cleared.length > 0) {
|
|
4671
|
+
await client.tui.showToast({
|
|
4672
|
+
body: {
|
|
4673
|
+
type: "success",
|
|
4674
|
+
message: `Cache cleared (${cleared.length} paths). Restart opencode to update.`
|
|
4675
|
+
}
|
|
4676
|
+
});
|
|
4677
|
+
} else if (blocked.length > 0) {
|
|
4678
|
+
await client.tui.showToast({
|
|
4679
|
+
body: {
|
|
4680
|
+
type: "error",
|
|
4681
|
+
message: `Cache update blocked (${blocked.length} paths failed safety check)`
|
|
4682
|
+
}
|
|
4683
|
+
});
|
|
4684
|
+
}
|
|
4685
|
+
await client.app.log({
|
|
4686
|
+
body: {
|
|
4687
|
+
service: "opencode-manifold",
|
|
4688
|
+
level: "info",
|
|
4689
|
+
message: `/manifold-update complete: cleared ${cleared.length}, skipped ${skipped.length}, blocked ${blocked.length}`
|
|
4690
|
+
}
|
|
4691
|
+
});
|
|
4692
|
+
}
|
|
4693
|
+
|
|
4219
4694
|
// src/index.ts
|
|
4220
4695
|
var ManifoldPlugin = async (ctx) => {
|
|
4221
4696
|
setPluginContext(ctx.client);
|
|
@@ -4249,6 +4724,12 @@ var ManifoldPlugin = async (ctx) => {
|
|
|
4249
4724
|
}
|
|
4250
4725
|
];
|
|
4251
4726
|
}
|
|
4727
|
+
} else if (input.command === "manifold-models") {
|
|
4728
|
+
await handleSubModelCommand(ctx.client, ctx.directory);
|
|
4729
|
+
output.parts = [{ type: "text", text: "Manifold sub-agent configuration started." }];
|
|
4730
|
+
} else if (input.command === "manifold-update") {
|
|
4731
|
+
await handleUpdateCommand(ctx.client, ctx.directory);
|
|
4732
|
+
output.parts = [{ type: "text", text: "Manifold update process started." }];
|
|
4252
4733
|
}
|
|
4253
4734
|
}
|
|
4254
4735
|
};
|
package/dist/tui.js
CHANGED
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
import { createRequire } from "node:module";
|
|
2
|
-
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
3
|
-
|
|
4
|
-
// src/tui.ts
|
|
5
|
-
import { existsSync } from "fs";
|
|
6
|
-
import { join } from "path";
|
|
7
|
-
import { homedir } from "os";
|
|
8
|
-
import { readFile, writeFile, readdir } from "fs/promises";
|
|
9
|
-
|
|
10
1
|
// node_modules/js-yaml/dist/js-yaml.mjs
|
|
11
2
|
/*! js-yaml 4.1.1 https://github.com/nodeca/js-yaml @license MIT */
|
|
12
3
|
function isNothing(subject) {
|
|
@@ -2693,65 +2684,6 @@ var jsYaml = {
|
|
|
2693
2684
|
};
|
|
2694
2685
|
|
|
2695
2686
|
// src/tui.ts
|
|
2696
|
-
var MANIFOLD_AGENTS = ["clerk", "senior-dev", "junior-dev", "debug"];
|
|
2697
|
-
async function getManifoldAgents(directory) {
|
|
2698
|
-
const agentsDir = join(directory, ".opencode", "agents");
|
|
2699
|
-
if (!existsSync(agentsDir)) {
|
|
2700
|
-
return [];
|
|
2701
|
-
}
|
|
2702
|
-
const files = await readdir(agentsDir);
|
|
2703
|
-
const agents = [];
|
|
2704
|
-
for (const file of files) {
|
|
2705
|
-
if (!file.endsWith(".md"))
|
|
2706
|
-
continue;
|
|
2707
|
-
const name = file.replace(".md", "");
|
|
2708
|
-
if (MANIFOLD_AGENTS.includes(name)) {
|
|
2709
|
-
agents.push(name);
|
|
2710
|
-
}
|
|
2711
|
-
}
|
|
2712
|
-
return agents;
|
|
2713
|
-
}
|
|
2714
|
-
async function readAgentFile(agentName, directory) {
|
|
2715
|
-
const agentPath = join(directory, ".opencode", "agents", `${agentName}.md`);
|
|
2716
|
-
if (!existsSync(agentPath)) {
|
|
2717
|
-
const globalPath = join(homedir(), ".config", "opencode", "manifold", "agents", `${agentName}.md`);
|
|
2718
|
-
if (existsSync(globalPath)) {
|
|
2719
|
-
const content2 = await readFile(globalPath, "utf-8");
|
|
2720
|
-
const { frontmatter: frontmatter2, body: body2 } = parseFrontmatter(content2);
|
|
2721
|
-
return { content: content2, frontmatter: frontmatter2, body: body2 };
|
|
2722
|
-
}
|
|
2723
|
-
throw new Error(`Agent file not found for ${agentName}`);
|
|
2724
|
-
}
|
|
2725
|
-
const content = await readFile(agentPath, "utf-8");
|
|
2726
|
-
const { frontmatter, body } = parseFrontmatter(content);
|
|
2727
|
-
return { content, frontmatter, body };
|
|
2728
|
-
}
|
|
2729
|
-
function parseFrontmatter(content) {
|
|
2730
|
-
const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
2731
|
-
if (!match) {
|
|
2732
|
-
return { frontmatter: {}, body: content };
|
|
2733
|
-
}
|
|
2734
|
-
const frontmatterYaml = match[1];
|
|
2735
|
-
const body = match[2];
|
|
2736
|
-
const frontmatter = jsYaml.load(frontmatterYaml) || {};
|
|
2737
|
-
return { frontmatter, body };
|
|
2738
|
-
}
|
|
2739
|
-
function buildAgentFile(frontmatter, body) {
|
|
2740
|
-
const yamlContent = jsYaml.dump(frontmatter, {
|
|
2741
|
-
lineWidth: -1,
|
|
2742
|
-
noCompatMode: true
|
|
2743
|
-
});
|
|
2744
|
-
return `---
|
|
2745
|
-
${yamlContent}---
|
|
2746
|
-
${body}`;
|
|
2747
|
-
}
|
|
2748
|
-
async function updateAgentModel(agentName, modelId, directory) {
|
|
2749
|
-
const { frontmatter, body } = await readAgentFile(agentName, directory);
|
|
2750
|
-
frontmatter.model = modelId;
|
|
2751
|
-
const newContent = buildAgentFile(frontmatter, body);
|
|
2752
|
-
const agentPath = join(directory, ".opencode", "agents", `${agentName}.md`);
|
|
2753
|
-
await writeFile(agentPath, newContent);
|
|
2754
|
-
}
|
|
2755
2687
|
var tui = async (api) => {
|
|
2756
2688
|
api.command.register(() => [
|
|
2757
2689
|
{
|
|
@@ -2770,8 +2702,7 @@ var tui = async (api) => {
|
|
|
2770
2702
|
category: "Manifold",
|
|
2771
2703
|
slash: {
|
|
2772
2704
|
name: "manifold-models"
|
|
2773
|
-
}
|
|
2774
|
-
onSelect: () => handleManifoldModels(api)
|
|
2705
|
+
}
|
|
2775
2706
|
},
|
|
2776
2707
|
{
|
|
2777
2708
|
title: "Manifold Update",
|
|
@@ -2780,150 +2711,10 @@ var tui = async (api) => {
|
|
|
2780
2711
|
category: "Manifold",
|
|
2781
2712
|
slash: {
|
|
2782
2713
|
name: "manifold-update"
|
|
2783
|
-
}
|
|
2784
|
-
onSelect: () => handleManifoldUpdate(api)
|
|
2714
|
+
}
|
|
2785
2715
|
}
|
|
2786
2716
|
]);
|
|
2787
2717
|
};
|
|
2788
|
-
function handleManifoldModels(api) {
|
|
2789
|
-
const directory = api.state.path.directory;
|
|
2790
|
-
getManifoldAgents(directory).then((agents) => {
|
|
2791
|
-
if (agents.length === 0) {
|
|
2792
|
-
api.ui.toast({
|
|
2793
|
-
variant: "error",
|
|
2794
|
-
message: "No Manifold agents found. Run /manifold-init first."
|
|
2795
|
-
});
|
|
2796
|
-
return;
|
|
2797
|
-
}
|
|
2798
|
-
const providers = api.state.provider;
|
|
2799
|
-
const models = [];
|
|
2800
|
-
for (const provider of providers) {
|
|
2801
|
-
if (provider.models) {
|
|
2802
|
-
for (const [modelId, model] of Object.entries(provider.models)) {
|
|
2803
|
-
models.push({
|
|
2804
|
-
id: `${provider.id}/${modelId}`,
|
|
2805
|
-
name: model.name || modelId,
|
|
2806
|
-
providerID: provider.id
|
|
2807
|
-
});
|
|
2808
|
-
}
|
|
2809
|
-
}
|
|
2810
|
-
}
|
|
2811
|
-
if (models.length === 0) {
|
|
2812
|
-
api.ui.toast({
|
|
2813
|
-
variant: "error",
|
|
2814
|
-
message: "No models available. Configure providers first."
|
|
2815
|
-
});
|
|
2816
|
-
return;
|
|
2817
|
-
}
|
|
2818
|
-
models.sort((a, b) => a.name.localeCompare(b.name));
|
|
2819
|
-
const agentOptions = agents.map((agent) => ({
|
|
2820
|
-
title: `${agent} (sub-agent)`,
|
|
2821
|
-
value: agent,
|
|
2822
|
-
description: `Configure model for ${agent}`
|
|
2823
|
-
}));
|
|
2824
|
-
api.ui.dialog.replace(() => api.ui.DialogSelect({
|
|
2825
|
-
title: "Select Manifold Sub-Agent",
|
|
2826
|
-
options: agentOptions,
|
|
2827
|
-
onSelect: (option) => {
|
|
2828
|
-
const selectedAgent = option.value;
|
|
2829
|
-
readAgentFile(selectedAgent, directory).then(({ frontmatter }) => {
|
|
2830
|
-
const currentModel = frontmatter.model || "not set";
|
|
2831
|
-
const modelOptions = models.map((model) => ({
|
|
2832
|
-
title: `${model.name}`,
|
|
2833
|
-
value: model.id,
|
|
2834
|
-
description: model.id,
|
|
2835
|
-
footer: model.id === currentModel ? "✓ Current" : undefined
|
|
2836
|
-
}));
|
|
2837
|
-
api.ui.dialog.replace(() => api.ui.DialogSelect({
|
|
2838
|
-
title: `Select Model for ${selectedAgent}`,
|
|
2839
|
-
options: modelOptions,
|
|
2840
|
-
current: currentModel !== "not set" ? currentModel : undefined,
|
|
2841
|
-
onSelect: (modelOption) => {
|
|
2842
|
-
const selectedModelId = modelOption.value;
|
|
2843
|
-
updateAgentModel(selectedAgent, selectedModelId, directory).then(() => {
|
|
2844
|
-
api.ui.toast({
|
|
2845
|
-
variant: "success",
|
|
2846
|
-
message: `Set ${selectedAgent} to ${selectedModelId}`
|
|
2847
|
-
});
|
|
2848
|
-
api.ui.dialog.clear();
|
|
2849
|
-
});
|
|
2850
|
-
}
|
|
2851
|
-
}));
|
|
2852
|
-
}).catch(() => {
|
|
2853
|
-
api.ui.toast({
|
|
2854
|
-
variant: "error",
|
|
2855
|
-
message: `Error reading agent file for ${selectedAgent}`
|
|
2856
|
-
});
|
|
2857
|
-
});
|
|
2858
|
-
}
|
|
2859
|
-
}));
|
|
2860
|
-
});
|
|
2861
|
-
}
|
|
2862
|
-
function handleManifoldUpdate(api) {
|
|
2863
|
-
const directory = api.state.path.directory;
|
|
2864
|
-
const settingsPath = join(directory, "Manifold", "settings.json");
|
|
2865
|
-
const proceed = (settings) => {
|
|
2866
|
-
const globalConfigDir = join(homedir(), ".config", "opencode", "manifold");
|
|
2867
|
-
const configuredPaths = settings.updateCachePaths || [];
|
|
2868
|
-
const pathsToClear = [...new Set([...configuredPaths, globalConfigDir])];
|
|
2869
|
-
const resolvedPaths = pathsToClear.map((p) => {
|
|
2870
|
-
const expanded = p.startsWith("~") ? join(homedir(), p.slice(1)) : p;
|
|
2871
|
-
return expanded;
|
|
2872
|
-
});
|
|
2873
|
-
api.ui.dialog.replace(() => api.ui.DialogSelect({
|
|
2874
|
-
title: "Manifold Plugin Update",
|
|
2875
|
-
options: [
|
|
2876
|
-
{
|
|
2877
|
-
title: "Clear Plugin & Template Cache",
|
|
2878
|
-
value: "confirm",
|
|
2879
|
-
description: `Will clear ${resolvedPaths.length} path(s) including global templates`,
|
|
2880
|
-
footer: resolvedPaths.map((p) => `• ${p}`).join(`
|
|
2881
|
-
`)
|
|
2882
|
-
},
|
|
2883
|
-
{
|
|
2884
|
-
title: "Cancel",
|
|
2885
|
-
value: "cancel",
|
|
2886
|
-
description: "Do not clear cache"
|
|
2887
|
-
}
|
|
2888
|
-
],
|
|
2889
|
-
onSelect: (option) => {
|
|
2890
|
-
if (option.value === "cancel") {
|
|
2891
|
-
api.ui.dialog.clear();
|
|
2892
|
-
return;
|
|
2893
|
-
}
|
|
2894
|
-
import("fs/promises").then(({ rm }) => {
|
|
2895
|
-
const cleared = [];
|
|
2896
|
-
const promises = resolvedPaths.map((pathStr) => {
|
|
2897
|
-
if (existsSync(pathStr)) {
|
|
2898
|
-
return rm(pathStr, { recursive: true, force: true }).then(() => {
|
|
2899
|
-
cleared.push(pathStr);
|
|
2900
|
-
}).catch(() => {});
|
|
2901
|
-
}
|
|
2902
|
-
return Promise.resolve();
|
|
2903
|
-
});
|
|
2904
|
-
Promise.all(promises).then(() => {
|
|
2905
|
-
api.ui.toast({
|
|
2906
|
-
variant: cleared.length > 0 ? "success" : "error",
|
|
2907
|
-
message: cleared.length > 0 ? `Cache cleared. Restart opencode to update.` : `No paths cleared or update blocked`
|
|
2908
|
-
});
|
|
2909
|
-
api.ui.dialog.clear();
|
|
2910
|
-
});
|
|
2911
|
-
});
|
|
2912
|
-
}
|
|
2913
|
-
}));
|
|
2914
|
-
};
|
|
2915
|
-
if (existsSync(settingsPath)) {
|
|
2916
|
-
readFile(settingsPath, "utf-8").then((content) => {
|
|
2917
|
-
try {
|
|
2918
|
-
proceed(JSON.parse(content));
|
|
2919
|
-
} catch {
|
|
2920
|
-
proceed({});
|
|
2921
|
-
}
|
|
2922
|
-
}).catch(() => proceed({}));
|
|
2923
|
-
} else {
|
|
2924
|
-
proceed({});
|
|
2925
|
-
}
|
|
2926
|
-
}
|
|
2927
2718
|
export {
|
|
2928
2719
|
tui
|
|
2929
2720
|
};
|