@kimbho/kimbho-cli 0.1.18 → 0.1.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.cjs CHANGED
@@ -10612,7 +10612,7 @@ var {
10612
10612
  // package.json
10613
10613
  var package_default = {
10614
10614
  name: "@kimbho/kimbho-cli",
10615
- version: "0.1.18",
10615
+ version: "0.1.19",
10616
10616
  description: "Kimbho CLI is a terminal-native coding agent for planning, execution, and verification.",
10617
10617
  type: "module",
10618
10618
  engines: {
@@ -16124,6 +16124,10 @@ async function loadCustomAgents(cwd) {
16124
16124
  ];
16125
16125
  });
16126
16126
  }
16127
+ async function loadCustomAgentById(cwd, id) {
16128
+ const agents = await loadCustomAgents(cwd);
16129
+ return agents.find((agent) => agent.id === id) ?? null;
16130
+ }
16127
16131
  async function loadAgentTeams(cwd) {
16128
16132
  const records = await loadMarkdownRecords(resolveAgentTeamDir(cwd));
16129
16133
  return records.map(({ filePath, source }) => {
@@ -16142,6 +16146,10 @@ async function loadAgentTeams(cwd) {
16142
16146
  };
16143
16147
  });
16144
16148
  }
16149
+ async function loadAgentTeamById(cwd, id) {
16150
+ const teams = await loadAgentTeams(cwd);
16151
+ return teams.find((team) => team.id === id) ?? null;
16152
+ }
16145
16153
  async function createCustomAgentFile(cwd, id, baseRole, options = {}) {
16146
16154
  const directory = resolveCustomAgentDir(cwd);
16147
16155
  const filePath = import_node_path5.default.join(directory, `${id}.md`);
@@ -16176,6 +16184,11 @@ async function createCustomAgentFile(cwd, id, baseRole, options = {}) {
16176
16184
  `, "utf8");
16177
16185
  return filePath;
16178
16186
  }
16187
+ async function deleteCustomAgentFile(cwd, id) {
16188
+ const filePath = import_node_path5.default.join(resolveCustomAgentDir(cwd), `${id}.md`);
16189
+ await (0, import_promises5.rm)(filePath);
16190
+ return filePath;
16191
+ }
16179
16192
  async function createAgentTeamFile(cwd, id, agentIds, label) {
16180
16193
  const directory = resolveAgentTeamDir(cwd);
16181
16194
  const filePath = import_node_path5.default.join(directory, `${id}.md`);
@@ -16199,6 +16212,11 @@ async function createAgentTeamFile(cwd, id, agentIds, label) {
16199
16212
  `, "utf8");
16200
16213
  return filePath;
16201
16214
  }
16215
+ async function deleteAgentTeamFile(cwd, id) {
16216
+ const filePath = import_node_path5.default.join(resolveAgentTeamDir(cwd), `${id}.md`);
16217
+ await (0, import_promises5.rm)(filePath);
16218
+ return filePath;
16219
+ }
16202
16220
  function scoreCustomAgentMatch(definition, task, request) {
16203
16221
  if (definition.baseRole !== task.agentRole) {
16204
16222
  return -1;
@@ -18290,6 +18308,46 @@ var KNOWN_SCAFFOLD_PRESETS = [
18290
18308
  "static-landing",
18291
18309
  "kimbho-cli-monorepo"
18292
18310
  ];
18311
+ function inferVisualAdjustment(goal) {
18312
+ const normalized = goal.toLowerCase();
18313
+ if (!normalized.includes("background") && !normalized.includes("gradient") && !normalized.includes("theme")) {
18314
+ return null;
18315
+ }
18316
+ if (normalized.includes("green")) {
18317
+ return {
18318
+ summaryLabel: "green background",
18319
+ background: "linear-gradient(180deg, #f4fff6 0%, #d8f3dc 100%)"
18320
+ };
18321
+ }
18322
+ if (normalized.includes("amber") || normalized.includes("gold") || normalized.includes("orange")) {
18323
+ return {
18324
+ summaryLabel: "amber background",
18325
+ background: "linear-gradient(180deg, #fff8e7 0%, #f7d08a 100%)"
18326
+ };
18327
+ }
18328
+ if (normalized.includes("blue")) {
18329
+ return {
18330
+ summaryLabel: "blue background",
18331
+ background: "linear-gradient(180deg, #eef6ff 0%, #cfe3ff 100%)"
18332
+ };
18333
+ }
18334
+ if (normalized.includes("pink") || normalized.includes("rose")) {
18335
+ return {
18336
+ summaryLabel: "rose background",
18337
+ background: "linear-gradient(180deg, #fff3f8 0%, #ffd6e7 100%)"
18338
+ };
18339
+ }
18340
+ if (normalized.includes("purple") || normalized.includes("violet")) {
18341
+ return {
18342
+ summaryLabel: "violet background",
18343
+ background: "linear-gradient(180deg, #f6f2ff 0%, #ddd1ff 100%)"
18344
+ };
18345
+ }
18346
+ return null;
18347
+ }
18348
+ function looksLikeVisualAdjustmentGoal(value) {
18349
+ return inferVisualAdjustment(value) !== null;
18350
+ }
18293
18351
  function looksLikeStaticLandingGoal(value) {
18294
18352
  const normalized = value.toLowerCase();
18295
18353
  const backendSignals = [
@@ -18348,6 +18406,9 @@ function looksLikeStaticLandingGoal(value) {
18348
18406
  if (normalized.includes("blog") && (normalized.includes("make") || normalized.includes("improve") || normalized.includes("enhance") || normalized.includes("redesign")) && !backendSignals.some((signal) => normalized.includes(signal))) {
18349
18407
  return true;
18350
18408
  }
18409
+ if (looksLikeVisualAdjustmentGoal(normalized) && !backendSignals.some((signal) => normalized.includes(signal))) {
18410
+ return true;
18411
+ }
18351
18412
  return false;
18352
18413
  }
18353
18414
  function sanitizeName(value) {
@@ -18575,7 +18636,76 @@ async function detectExistingStaticTarget(cwd) {
18575
18636
  }
18576
18637
  return null;
18577
18638
  }
18639
+ function rewriteBackgroundInSource(source, background) {
18640
+ const inlinePatterns = [
18641
+ [/background:\s*'[^']*'/, `background: '${background}'`],
18642
+ [/background:\s*"[^"]*"/, `background: "${background}"`],
18643
+ [/background:\s*`[^`]*`/, `background: \`${background}\``]
18644
+ ];
18645
+ for (const [pattern, replacement] of inlinePatterns) {
18646
+ if (pattern.test(source)) {
18647
+ return source.replace(pattern, replacement);
18648
+ }
18649
+ }
18650
+ const cssPatterns = [
18651
+ [/background:\s*[^;]+;/, `background: ${background};`],
18652
+ [/background-color:\s*[^;]+;/, `background: ${background};`]
18653
+ ];
18654
+ for (const [pattern, replacement] of cssPatterns) {
18655
+ if (pattern.test(source)) {
18656
+ return source.replace(pattern, replacement);
18657
+ }
18658
+ }
18659
+ return source;
18660
+ }
18661
+ async function applyExistingStaticVisualAdjustment(cwd, goal, target) {
18662
+ const adjustment = inferVisualAdjustment(goal);
18663
+ if (!adjustment) {
18664
+ return null;
18665
+ }
18666
+ const candidatePaths = [
18667
+ target.pagePath,
18668
+ target.kind === "next-app" ? "src/app/globals.css" : null,
18669
+ target.kind === "next-pages" ? "styles.css" : null,
18670
+ target.kind === "static-html" ? "styles.css" : null
18671
+ ].filter((value) => Boolean(value));
18672
+ for (const relativePath of candidatePaths) {
18673
+ const absolutePath = import_node_path10.default.join(cwd, relativePath);
18674
+ if (!await pathExists(absolutePath)) {
18675
+ continue;
18676
+ }
18677
+ const currentSource = await (0, import_promises10.readFile)(absolutePath, "utf8");
18678
+ if (currentSource.includes(adjustment.background)) {
18679
+ return {
18680
+ preset: "static-landing",
18681
+ projectName: inferProjectName(goal, cwd),
18682
+ summary: `${relativePath} already uses the requested ${adjustment.summaryLabel}.`,
18683
+ artifacts: [
18684
+ absolutePath
18685
+ ]
18686
+ };
18687
+ }
18688
+ const nextSource = rewriteBackgroundInSource(currentSource, adjustment.background);
18689
+ if (nextSource === currentSource) {
18690
+ continue;
18691
+ }
18692
+ const artifacts = await writeFiles(cwd, {
18693
+ [relativePath]: nextSource
18694
+ });
18695
+ return {
18696
+ preset: "static-landing",
18697
+ projectName: inferProjectName(goal, cwd),
18698
+ summary: `Updated ${relativePath} with a ${adjustment.summaryLabel}.`,
18699
+ artifacts
18700
+ };
18701
+ }
18702
+ return null;
18703
+ }
18578
18704
  async function adaptExistingStaticLanding(cwd, projectName, goal, target) {
18705
+ const adjustedResult = await applyExistingStaticVisualAdjustment(cwd, goal, target);
18706
+ if (adjustedResult) {
18707
+ return adjustedResult;
18708
+ }
18579
18709
  const title = inferLandingTitle(goal, projectName);
18580
18710
  const content = buildLandingContent(title, inferLandingKind(goal));
18581
18711
  const files = {};
@@ -19364,6 +19494,9 @@ function isVerificationShellCommand(command) {
19364
19494
  const normalized = command.trim().toLowerCase();
19365
19495
  return VERIFICATION_SHELL_PREFIXES.some((prefix) => normalized === prefix || normalized.startsWith(`${prefix} `));
19366
19496
  }
19497
+ function isLowRiskScaffoldRequest(input) {
19498
+ return input.preset === "static-landing" && typeof input.goal === "string" && looksLikeVisualAdjustmentGoal(input.goal);
19499
+ }
19367
19500
  function isDestructiveShellCommand(command) {
19368
19501
  return DESTRUCTIVE_SHELL_PATTERNS.some((pattern) => pattern.test(command));
19369
19502
  }
@@ -19432,8 +19565,9 @@ function enforceToolPolicy(toolId, input, descriptor, context) {
19432
19565
  const sandboxMode = context.sandboxMode ?? "workspace-write";
19433
19566
  const approvalMode = context.approvalMode ?? "manual";
19434
19567
  const command = typeof input.command === "string" ? input.command : "";
19568
+ const isVerificationCommand2 = isVerificationShellCommand(command);
19435
19569
  if (sandboxMode === "read-only") {
19436
- if (toolId === "shell.exec" && (isReadOnlyShellCommand(command) || isVerificationShellCommand(command))) {
19570
+ if ((toolId === "shell.exec" || toolId === "tests.run") && (isReadOnlyShellCommand(command) || isVerificationCommand2)) {
19437
19571
  return null;
19438
19572
  }
19439
19573
  if (descriptor.permission !== "safe") {
@@ -19445,7 +19579,10 @@ function enforceToolPolicy(toolId, input, descriptor, context) {
19445
19579
  return new ToolApprovalRequiredError(createApprovalRequest(toolId, input, descriptor, context, `Approval required for destructive shell command: ${command}`));
19446
19580
  }
19447
19581
  }
19448
- if (toolId === "shell.exec" && isVerificationShellCommand(command)) {
19582
+ if ((toolId === "shell.exec" || toolId === "tests.run") && isVerificationCommand2) {
19583
+ return null;
19584
+ }
19585
+ if (toolId === "scaffold.generate" && isLowRiskScaffoldRequest(input)) {
19449
19586
  return null;
19450
19587
  }
19451
19588
  if ((toolId === "file.write" || toolId === "file.patch") && sandboxMode === "workspace-write") {
@@ -27626,6 +27763,37 @@ var MCP_INVENTORY_TIMEOUT_MS = 8e3;
27626
27763
  function toMcpToolId(serverName, toolName) {
27627
27764
  return `mcp.${serverName}.${toolName}`;
27628
27765
  }
27766
+ function normalizeMcpCommandSegment(value) {
27767
+ return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
27768
+ }
27769
+ async function resolveMcpServerInventoryByReference(cwd, serverReference) {
27770
+ const inventory = await loadMcpServerInventory(cwd);
27771
+ const normalizedReference = normalizeMcpCommandSegment(serverReference);
27772
+ const server = inventory.find((candidate) => candidate.name === serverReference || normalizeMcpCommandSegment(candidate.name) === normalizedReference);
27773
+ if (!server) {
27774
+ throw new Error(`MCP server ${serverReference} is not configured or did not respond.`);
27775
+ }
27776
+ if (server.warning) {
27777
+ throw new Error(`MCP server ${server.name} is unavailable: ${server.warning}`);
27778
+ }
27779
+ return server;
27780
+ }
27781
+ async function resolveMcpPromptReference(cwd, serverReference, promptReference) {
27782
+ const server = await resolveMcpServerInventoryByReference(cwd, serverReference);
27783
+ const normalizedPromptReference = normalizeMcpCommandSegment(promptReference);
27784
+ const prompt = server.prompts.find((candidate) => candidate.name === promptReference || normalizeMcpCommandSegment(candidate.name) === normalizedPromptReference);
27785
+ if (!prompt) {
27786
+ throw new Error(`MCP prompt ${promptReference} was not found on server ${server.name}.`);
27787
+ }
27788
+ return {
27789
+ serverName: server.name,
27790
+ promptName: prompt.name
27791
+ };
27792
+ }
27793
+ async function resolveMcpServerName(cwd, serverReference) {
27794
+ const server = await resolveMcpServerInventoryByReference(cwd, serverReference);
27795
+ return server.name;
27796
+ }
27629
27797
  function permissionFromAnnotations(annotations) {
27630
27798
  if (annotations?.destructiveHint) {
27631
27799
  return "destructive";
@@ -27785,23 +27953,24 @@ async function discoverMcpTools(cwd) {
27785
27953
  }
27786
27954
  async function callMcpTool(cwd, serverName, toolName, input) {
27787
27955
  const config2 = await loadMcpConfig(cwd);
27788
- const server = config2.mcpServers[serverName];
27956
+ const resolvedServerName = await resolveMcpServerName(cwd, serverName).catch(() => serverName);
27957
+ const server = config2.mcpServers[resolvedServerName];
27789
27958
  if (!server || server.enabled === false) {
27790
27959
  return {
27791
- toolId: toMcpToolId(serverName, toolName),
27960
+ toolId: toMcpToolId(resolvedServerName, toolName),
27792
27961
  success: false,
27793
- summary: `MCP server ${serverName} is not configured or is disabled.`,
27962
+ summary: `MCP server ${resolvedServerName} is not configured or is disabled.`,
27794
27963
  artifacts: []
27795
27964
  };
27796
27965
  }
27797
- const result = await withMcpClient(cwd, serverName, server, async (client) => client.callTool({
27966
+ const result = await withMcpClient(cwd, resolvedServerName, server, async (client) => client.callTool({
27798
27967
  name: toolName,
27799
27968
  arguments: Object.fromEntries(Object.entries(input).filter(([, value]) => value !== void 0))
27800
27969
  }));
27801
27970
  return {
27802
- toolId: toMcpToolId(serverName, toolName),
27971
+ toolId: toMcpToolId(resolvedServerName, toolName),
27803
27972
  success: !result.isError,
27804
- summary: result.isError ? `MCP tool ${serverName}/${toolName} reported an error.` : `Ran MCP tool ${serverName}/${toolName}.`,
27973
+ summary: result.isError ? `MCP tool ${resolvedServerName}/${toolName} reported an error.` : `Ran MCP tool ${resolvedServerName}/${toolName}.`,
27805
27974
  stdout: renderMcpContent(result),
27806
27975
  artifacts: []
27807
27976
  };
@@ -27823,18 +27992,19 @@ ${"text" in resource ? resource.text : `[binary ${resource.mimeType ?? "resource
27823
27992
  }).join("\n\n");
27824
27993
  }
27825
27994
  async function invokeMcpPrompt(cwd, serverName, promptName, args) {
27995
+ const { serverName: resolvedServerName, promptName: resolvedPromptName } = await resolveMcpPromptReference(cwd, serverName, promptName);
27826
27996
  const config2 = await loadMcpConfig(cwd);
27827
- const server = config2.mcpServers[serverName];
27997
+ const server = config2.mcpServers[resolvedServerName];
27828
27998
  if (!server || server.enabled === false) {
27829
- throw new Error(`MCP server ${serverName} is not configured or is disabled.`);
27999
+ throw new Error(`MCP server ${resolvedServerName} is not configured or is disabled.`);
27830
28000
  }
27831
- const result = await withMcpClient(cwd, serverName, server, async (client) => client.getPrompt({
27832
- name: promptName,
28001
+ const result = await withMcpClient(cwd, resolvedServerName, server, async (client) => client.getPrompt({
28002
+ name: resolvedPromptName,
27833
28003
  arguments: args
27834
28004
  }));
27835
28005
  return {
27836
- serverName,
27837
- promptName,
28006
+ serverName: resolvedServerName,
28007
+ promptName: resolvedPromptName,
27838
28008
  ...result.description ? {
27839
28009
  description: result.description
27840
28010
  } : {},
@@ -27842,19 +28012,20 @@ async function invokeMcpPrompt(cwd, serverName, promptName, args) {
27842
28012
  };
27843
28013
  }
27844
28014
  async function readMcpResource(cwd, serverName, uri) {
28015
+ const resolvedServerName = await resolveMcpServerName(cwd, serverName);
27845
28016
  const config2 = await loadMcpConfig(cwd);
27846
- const server = config2.mcpServers[serverName];
28017
+ const server = config2.mcpServers[resolvedServerName];
27847
28018
  if (!server || server.enabled === false) {
27848
- throw new Error(`MCP server ${serverName} is not configured or is disabled.`);
28019
+ throw new Error(`MCP server ${resolvedServerName} is not configured or is disabled.`);
27849
28020
  }
27850
- const result = await withMcpClient(cwd, serverName, server, async (client) => client.readResource({
28021
+ const result = await withMcpClient(cwd, resolvedServerName, server, async (client) => client.readResource({
27851
28022
  uri
27852
28023
  }));
27853
28024
  const content = result.contents.map((item) => "text" in item ? `resource: ${item.uri}
27854
28025
  ${item.text}` : `resource: ${item.uri}
27855
28026
  [binary ${item.mimeType ?? "resource"}]`).join("\n\n");
27856
28027
  return {
27857
- serverName,
28028
+ serverName: resolvedServerName,
27858
28029
  uri,
27859
28030
  content
27860
28031
  };
@@ -29177,6 +29348,66 @@ var import_node_path14 = __toESM(require("node:path"), 1);
29177
29348
  function normalizeGoal(goal) {
29178
29349
  return goal.trim().replace(/\s+/g, " ");
29179
29350
  }
29351
+ function looksLikeVisualAdjustmentGoal2(goal) {
29352
+ const lower = goal.toLowerCase();
29353
+ const backendSignals = [
29354
+ "api",
29355
+ "database",
29356
+ "backend",
29357
+ "server",
29358
+ "migration",
29359
+ "schema",
29360
+ "prisma",
29361
+ "postgres",
29362
+ "sqlite",
29363
+ "auth",
29364
+ "billing",
29365
+ "webhook"
29366
+ ];
29367
+ const editSignals = [
29368
+ "make",
29369
+ "change",
29370
+ "update",
29371
+ "turn",
29372
+ "switch",
29373
+ "set",
29374
+ "use",
29375
+ "refresh",
29376
+ "restyle",
29377
+ "redesign"
29378
+ ];
29379
+ const visualSignals = [
29380
+ "background",
29381
+ "color",
29382
+ "green",
29383
+ "amber",
29384
+ "blue",
29385
+ "pink",
29386
+ "purple",
29387
+ "gradient",
29388
+ "theme",
29389
+ "palette",
29390
+ "styling",
29391
+ "style",
29392
+ "look",
29393
+ "visual",
29394
+ "hero"
29395
+ ];
29396
+ const pageContextSignals = [
29397
+ "page",
29398
+ "landing",
29399
+ "lnding",
29400
+ "homepage",
29401
+ "home page",
29402
+ "site",
29403
+ "website",
29404
+ "blog"
29405
+ ];
29406
+ if (backendSignals.some((signal) => lower.includes(signal))) {
29407
+ return false;
29408
+ }
29409
+ return editSignals.some((signal) => lower.includes(signal)) && visualSignals.some((signal) => lower.includes(signal)) && (pageContextSignals.some((signal) => lower.includes(signal)) || lower.includes("background"));
29410
+ }
29180
29411
  function looksLikeStaticSiteGoal(goal) {
29181
29412
  const lower = goal.toLowerCase();
29182
29413
  const backendSignals = [
@@ -29235,6 +29466,9 @@ function looksLikeStaticSiteGoal(goal) {
29235
29466
  if (lower.includes("blog") && (lower.includes("make") || lower.includes("improve") || lower.includes("enhance") || lower.includes("redesign")) && !backendSignals.some((signal) => lower.includes(signal))) {
29236
29467
  return true;
29237
29468
  }
29469
+ if (looksLikeVisualAdjustmentGoal2(lower)) {
29470
+ return true;
29471
+ }
29238
29472
  return false;
29239
29473
  }
29240
29474
  function inferProjectShape(goal) {
@@ -31151,7 +31385,10 @@ function isStaticSiteTask(task, request) {
31151
31385
  }
31152
31386
  return task.filesLikelyTouched.some((filePath) => filePath === "src/app/page.tsx" || filePath === "src/app/layout.tsx" || filePath === "src/pages/index.tsx" || filePath === "index.html" || filePath === "styles.css");
31153
31387
  }
31154
- function shouldDelegateTask(task) {
31388
+ function shouldDelegateTask(task, request) {
31389
+ if (looksLikeVisualAdjustmentGoal(request.goal) && isStaticSiteTask(task, request)) {
31390
+ return false;
31391
+ }
31155
31392
  return (task.swarmDepth ?? 0) === 0 && task.type === "implementation" && ![
31156
31393
  "repo-analyst",
31157
31394
  "planner",
@@ -31168,7 +31405,7 @@ function terminalExpansionTaskIds(tasks) {
31168
31405
  return terminalIds.length > 0 ? terminalIds : tasks.map((task) => task.id);
31169
31406
  }
31170
31407
  function createDefaultDelegationPlan(task, request) {
31171
- if (!shouldDelegateTask(task)) {
31408
+ if (!shouldDelegateTask(task, request)) {
31172
31409
  return null;
31173
31410
  }
31174
31411
  if (task.agentRole === "frontend-specialist" || isStaticSiteTask(task, request)) {
@@ -31317,6 +31554,53 @@ function createDefaultDelegationPlan(task, request) {
31317
31554
  }
31318
31555
  return null;
31319
31556
  }
31557
+ async function detectAppliedVisualAdjustment(cwd, request) {
31558
+ const adjustment = inferVisualAdjustment(request.goal);
31559
+ if (!adjustment) {
31560
+ return null;
31561
+ }
31562
+ const candidates = [
31563
+ "src/app/page.tsx",
31564
+ "src/pages/index.tsx",
31565
+ "src/app/globals.css",
31566
+ "styles.css",
31567
+ "index.html"
31568
+ ];
31569
+ for (const relativePath of candidates) {
31570
+ const absolutePath = import_node_path14.default.join(cwd, relativePath);
31571
+ try {
31572
+ await (0, import_promises14.access)(absolutePath);
31573
+ const contents = await (0, import_promises14.readFile)(absolutePath, "utf8");
31574
+ if (contents.includes(adjustment.background)) {
31575
+ return absolutePath;
31576
+ }
31577
+ } catch {
31578
+ }
31579
+ }
31580
+ return null;
31581
+ }
31582
+ async function detectPreferredVerificationCommand(cwd) {
31583
+ const packagePath = import_node_path14.default.join(cwd, "package.json");
31584
+ try {
31585
+ await (0, import_promises14.access)(packagePath);
31586
+ const raw = await (0, import_promises14.readFile)(packagePath, "utf8");
31587
+ const parsed = JSON.parse(raw);
31588
+ const scripts = parsed.scripts ?? {};
31589
+ const packageManager = parsed.packageManager?.startsWith("pnpm") ? "pnpm" : parsed.packageManager?.startsWith("yarn") ? "yarn" : "npm";
31590
+ if (scripts.build) {
31591
+ return packageManager === "yarn" ? "yarn build" : `${packageManager} run build`;
31592
+ }
31593
+ if (scripts.lint) {
31594
+ return packageManager === "yarn" ? "yarn lint" : `${packageManager} run lint`;
31595
+ }
31596
+ if (scripts.test) {
31597
+ return packageManager === "yarn" ? "yarn test" : `${packageManager} run test`;
31598
+ }
31599
+ } catch {
31600
+ return null;
31601
+ }
31602
+ return null;
31603
+ }
31320
31604
  function materializeDelegatedTasks(plan, task, label, strategy, members) {
31321
31605
  const existingIds = new Set(flattenPlanTasks(plan).filter((candidate) => candidate.id !== task.id).map((candidate) => candidate.id));
31322
31606
  const teamMemberIds = members.map((member) => member.id);
@@ -31952,6 +32236,9 @@ var ExecutionOrchestrator = class {
31952
32236
  if (request.workspaceState === "existing" && isStaticSiteTask(task, request) && (task.type === "scaffold" || task.type === "implementation")) {
31953
32237
  return this.executeDeterministicStaticSiteTask(sessionId, task, request, resolvedApproval, options, emitProgress);
31954
32238
  }
32239
+ if (request.workspaceState === "existing" && looksLikeStaticLandingGoal(request.goal) && task.type === "verification") {
32240
+ return this.executeDeterministicVerificationTask(sessionId, task, request, emitProgress, options.signal);
32241
+ }
31955
32242
  if (task.type === "scaffold") {
31956
32243
  return this.executeScaffoldTask(sessionId, task, request, plan, resolvedApproval, options, emitProgress);
31957
32244
  }
@@ -32086,7 +32373,69 @@ var ExecutionOrchestrator = class {
32086
32373
  ]))
32087
32374
  };
32088
32375
  }
32376
+ async executeDeterministicVerificationTask(sessionId, task, request, emitProgress, signal) {
32377
+ const command = await detectPreferredVerificationCommand(request.cwd);
32378
+ if (!command) {
32379
+ return {
32380
+ status: "completed",
32381
+ summary: "No build, lint, or test script was found; verification was recorded as not applicable.",
32382
+ toolResults: [],
32383
+ artifacts: []
32384
+ };
32385
+ }
32386
+ const input = {
32387
+ command
32388
+ };
32389
+ if (emitProgress) {
32390
+ await emitProgress({
32391
+ type: "tool-started",
32392
+ sessionId,
32393
+ taskId: task.id,
32394
+ agentRole: task.agentRole,
32395
+ toolId: "tests.run",
32396
+ input
32397
+ });
32398
+ }
32399
+ const result = await this.safeRunTool("tests.run", input, {
32400
+ cwd: request.cwd,
32401
+ taskId: task.id,
32402
+ agentRole: task.agentRole,
32403
+ ...signal ? {
32404
+ signal
32405
+ } : {}
32406
+ });
32407
+ if (emitProgress) {
32408
+ await emitProgress({
32409
+ type: "tool-finished",
32410
+ sessionId,
32411
+ taskId: task.id,
32412
+ agentRole: task.agentRole,
32413
+ toolResult: result
32414
+ });
32415
+ }
32416
+ return {
32417
+ status: result.success ? "completed" : "blocked",
32418
+ summary: result.success ? `Verification passed via ${command}.` : `Verification failed via ${command}.`,
32419
+ toolResults: [
32420
+ result
32421
+ ],
32422
+ artifacts: result.artifacts
32423
+ };
32424
+ }
32089
32425
  async executeDeterministicStaticSiteTask(sessionId, task, request, resolvedApproval, options, emitProgress) {
32426
+ if (looksLikeVisualAdjustmentGoal(request.goal)) {
32427
+ const appliedArtifact = await detectAppliedVisualAdjustment(request.cwd, request);
32428
+ if (appliedArtifact) {
32429
+ return {
32430
+ status: "completed",
32431
+ summary: `Confirmed the requested visual adjustment in ${import_node_path14.default.relative(request.cwd, appliedArtifact) || appliedArtifact}.`,
32432
+ toolResults: [],
32433
+ artifacts: [
32434
+ appliedArtifact
32435
+ ]
32436
+ };
32437
+ }
32438
+ }
32090
32439
  const config2 = await loadConfig(request.cwd);
32091
32440
  const scaffoldInput = {
32092
32441
  goal: request.goal,
@@ -32482,7 +32831,7 @@ var ExecutionOrchestrator = class {
32482
32831
  }
32483
32832
  async maybeExpandReadyTaskGraph(sessionId, request, plan, events, emitProgress) {
32484
32833
  const envelope = this.buildEnvelope(request, plan, sessionId);
32485
- const candidate = envelope.readyTasks.find((task) => shouldDelegateTask(task));
32834
+ const candidate = envelope.readyTasks.find((task) => shouldDelegateTask(task, request));
32486
32835
  if (!candidate) {
32487
32836
  return {
32488
32837
  plan
@@ -32703,10 +33052,69 @@ function createAgentsCommand() {
32703
33052
  });
32704
33053
  console.log(`Created ${outputPath}`);
32705
33054
  });
33055
+ command.command("show").description("Show one custom agent or team.").argument("<id>", "Agent or team id").option("--team", "Show a team instead of an agent", false).action(async (id, options) => {
33056
+ if (options.team) {
33057
+ const team = await loadAgentTeamById(process.cwd(), id);
33058
+ if (!team) {
33059
+ throw new Error(`Unknown team "${id}".`);
33060
+ }
33061
+ console.log(team.id);
33062
+ console.log(` label: ${team.label}`);
33063
+ console.log(` agents: ${team.agents.join(", ") || "-"}`);
33064
+ console.log(` match: ${team.match.join(", ") || "-"}`);
33065
+ console.log(` strategy: ${team.strategy}`);
33066
+ console.log(` file: ${team.filePath}`);
33067
+ if (team.description) {
33068
+ console.log("");
33069
+ console.log(team.description);
33070
+ }
33071
+ return;
33072
+ }
33073
+ const agent = await loadCustomAgentById(process.cwd(), id);
33074
+ if (!agent) {
33075
+ throw new Error(`Unknown custom agent "${id}".`);
33076
+ }
33077
+ console.log(agent.id);
33078
+ console.log(` label: ${agent.label}`);
33079
+ console.log(` baseRole: ${agent.baseRole}`);
33080
+ console.log(` brainRole: ${agent.brainRole ?? "-"}`);
33081
+ console.log(` purpose: ${agent.purpose ?? "-"}`);
33082
+ console.log(` match: ${agent.match.join(", ") || "-"}`);
33083
+ console.log(` tools: ${agent.tools.join(", ") || "(inherits base role tools)"}`);
33084
+ console.log(` file: ${agent.filePath}`);
33085
+ if (agent.instructions.trim()) {
33086
+ console.log("");
33087
+ console.log(agent.instructions.trim());
33088
+ }
33089
+ });
33090
+ command.command("delete").description("Delete a custom agent or team markdown file.").argument("<id>", "Agent or team id").option("--team", "Delete a team instead of an agent", false).action(async (id, options) => {
33091
+ const outputPath = options.team ? await deleteAgentTeamFile(process.cwd(), id) : await deleteCustomAgentFile(process.cwd(), id);
33092
+ console.log(`Deleted ${outputPath}`);
33093
+ });
32706
33094
  command.command("team-create").description("Create a markdown-backed agent team definition.").argument("<id>", "Team id").argument("<agents...>", "Member agent ids").option("--label <label>", "Human-readable label").action(async (id, agents, options) => {
32707
33095
  const outputPath = await createAgentTeamFile(process.cwd(), id, agents, options.label);
32708
33096
  console.log(`Created ${outputPath}`);
32709
33097
  });
33098
+ command.command("team-show").description("Show one markdown-backed team definition.").argument("<id>", "Team id").action(async (id) => {
33099
+ const team = await loadAgentTeamById(process.cwd(), id);
33100
+ if (!team) {
33101
+ throw new Error(`Unknown team "${id}".`);
33102
+ }
33103
+ console.log(team.id);
33104
+ console.log(` label: ${team.label}`);
33105
+ console.log(` agents: ${team.agents.join(", ") || "-"}`);
33106
+ console.log(` match: ${team.match.join(", ") || "-"}`);
33107
+ console.log(` strategy: ${team.strategy}`);
33108
+ console.log(` file: ${team.filePath}`);
33109
+ if (team.description) {
33110
+ console.log("");
33111
+ console.log(team.description);
33112
+ }
33113
+ });
33114
+ command.command("team-delete").description("Delete one markdown-backed team definition.").argument("<id>", "Team id").action(async (id) => {
33115
+ const outputPath = await deleteAgentTeamFile(process.cwd(), id);
33116
+ console.log(`Deleted ${outputPath}`);
33117
+ });
32710
33118
  return command;
32711
33119
  }
32712
33120
 
@@ -33523,7 +33931,7 @@ async function renderMcpInventory(cwd, name) {
33523
33931
  ` prompts: ${server.prompts.length}`,
33524
33932
  ...server.prompts.slice(0, 10).flatMap((prompt) => [
33525
33933
  ` - ${prompt.name}${prompt.arguments.length > 0 ? ` (${prompt.arguments.map((argument) => `${argument.name}${argument.required ? "*" : ""}`).join(", ")})` : ""}`,
33526
- ` command: /mcp__${server.name}__${prompt.name}`
33934
+ ` command: /mcp__${normalizeMcpCommandSegment(server.name)}__${normalizeMcpCommandSegment(prompt.name)}`
33527
33935
  ]),
33528
33936
  ` resources: ${server.resources.length}`,
33529
33937
  ...server.resources.slice(0, 10).flatMap((resource) => [
@@ -33549,7 +33957,7 @@ async function renderMcpPromptList(cwd, name) {
33549
33957
  ] : [],
33550
33958
  ...server.prompts.length > 0 ? server.prompts.flatMap((prompt) => [
33551
33959
  ` - ${prompt.name}${prompt.arguments.length > 0 ? ` (${prompt.arguments.map((argument) => `${argument.name}${argument.required ? "*" : ""}`).join(", ")})` : ""}`,
33552
- ` command: /mcp__${server.name}__${prompt.name}`
33960
+ ` command: /mcp__${normalizeMcpCommandSegment(server.name)}__${normalizeMcpCommandSegment(prompt.name)}`
33553
33961
  ]) : [
33554
33962
  " - no prompts"
33555
33963
  ]
@@ -35490,53 +35898,65 @@ function renderBox(lines) {
35490
35898
  function renderHelp() {
35491
35899
  return [
35492
35900
  `${color(DIM, "Commands")}`,
35493
- "/status Show the active role, provider, and workspace state.",
35494
- "/ask <prompt> Send a prompt to the active model without running the agent.",
35495
- "/chat <prompt> Alias for /ask <prompt>.",
35496
- "/reset-chat Clear the active model conversation history.",
35497
- "/model Show current model, provider, and role state.",
35901
+ `${color(BOLD, "Core")}`,
35902
+ "/status Show active model, workspace, approvals, and sandbox state.",
35903
+ "/ask <prompt> Chat with the focused model without running the agent.",
35904
+ "/plan <goal> Create a structured plan only.",
35905
+ "/run <goal> Execute a goal immediately.",
35906
+ "/resume Continue the latest paused or awaiting-approval session.",
35907
+ "/review Review the current git diff and summarize risk.",
35908
+ "/clear Redraw the shell.",
35909
+ "/quit, /exit Leave the shell.",
35910
+ "",
35911
+ `${color(BOLD, "Model")}`,
35912
+ "/model Show current provider/model state.",
35498
35913
  "/model providers List configured providers.",
35499
35914
  "/model add <tpl> [id] Add a provider template.",
35500
35915
  "/model use <provider> [model] Switch provider/model for all roles.",
35501
35916
  "/model find [search] Discover models for the active provider.",
35502
- "/model select <n> Pick a model from the last numbered list.",
35503
- "/model focus <role> Change shell focus to planner, coder, reviewer, or fast.",
35504
- "/permissions Show approval, sandbox, and trusted directory settings.",
35917
+ "/model select <n> Pick from the last numbered model list.",
35918
+ "/model focus <role> Change focus to planner, coder, reviewer, or fast.",
35919
+ "",
35920
+ `${color(BOLD, "Permissions")}`,
35921
+ "/permissions Show layered approval, sandbox, and trust settings.",
35505
35922
  "/permissions auto|manual Change approval mode.",
35506
35923
  "/permissions sandbox <mode> Set read-only, workspace-write, or full.",
35507
35924
  "/permissions trust <path> Add a trusted directory.",
35508
35925
  "/permissions untrust <path> Remove a trusted directory.",
35509
- "/memory Show project, user, and agent memory paths.",
35510
- "/memory refresh Refresh kimbho_init.md and project memory.",
35926
+ "/approve [id] Approve a pending risky action and continue.",
35927
+ "/approve-all Approve every pending action in the current session.",
35928
+ "/deny [id] Deny a pending risky action.",
35929
+ "",
35930
+ `${color(BOLD, "Memory")}`,
35931
+ "/init [--memory-only] Build config and durable project understanding files.",
35932
+ "/memory Show init/project/user/agent memory paths.",
35933
+ "/memory refresh Re-scan the project and refresh markdown memory.",
35511
35934
  "/memory show <scope> Print init, project, user, or agent memory.",
35512
- "/memory add <scope> <text> Append a markdown memory note.",
35935
+ "/memory add <scope> ... Append a markdown memory note.",
35513
35936
  "/config Show config, memory, and MCP file locations.",
35937
+ "",
35938
+ `${color(BOLD, "MCP")}`,
35514
35939
  "/mcp List configured MCP servers.",
35940
+ "/mcp discover [server] Show MCP tools, prompts-as-commands, and resources.",
35941
+ "/mcp prompt <server> <prompt> [key=value ...] Render one MCP prompt.",
35942
+ "/mcp read <server> <uri> Read one MCP resource.",
35515
35943
  "/mcp add <name> --command <cmd> [--arg <value>] [--env KEY=VALUE]",
35516
35944
  " Add an MCP stdio server to .mcp.json.",
35517
- "/mcp tools [server] Discover tools/prompts/resources from MCP servers.",
35518
- "/mcp prompts [server] List MCP prompts.",
35519
- "/mcp resources [server] List MCP resources and templates.",
35520
- "/mcp prompt <server> <prompt> [key=value ...]",
35521
- " Render an MCP prompt.",
35522
- "/mcp read <server> <uri> Read one MCP resource.",
35945
+ "/mcp__server__prompt ... Invoke an MCP prompt directly from the shell.",
35946
+ "@server:uri Attach an MCP resource inside /ask, /plan, or /run prompts.",
35947
+ "",
35948
+ `${color(BOLD, "Agents")}`,
35523
35949
  "/agents List custom agents and teams.",
35950
+ "/agents show <id> Show one custom agent definition.",
35951
+ "/agents delete <id> Delete one custom agent definition.",
35524
35952
  "/agents create <id> --base <role> Create a markdown custom agent.",
35525
35953
  "/agents team create <id> <agent...> Create a markdown team file.",
35526
- "/init [--memory-only] Create or refresh config and markdown memory files.",
35527
- "/plan <goal> Create a structured implementation plan.",
35528
- "/run <goal> Start a Kimbho execution session for a goal.",
35529
- "/resume Show the latest saved session.",
35530
- "/approve [id] Approve a pending risky action and continue the session.",
35531
- "/approve-all Approve all pending actions in the current session.",
35532
- "/deny [id] Deny a pending risky action.",
35533
- "/review Review the current git diff and summarize risk.",
35534
- "/doctor Check local environment and config.",
35535
- "/clear Redraw the shell.",
35536
- "/quit, /exit Leave the shell.",
35954
+ "/agents team show <id> Show one team definition.",
35955
+ "/agents team delete <id> Delete one team definition.",
35537
35956
  "",
35538
35957
  `${color(DIM, "Tip")}`,
35539
35958
  "Type build/change requests directly to run the agent. Type plan/design prompts for planning. Use /ask when you want plain chat.",
35959
+ "MCP prompts behave like slash commands, and MCP resources can be attached inline with @server:uri.",
35540
35960
  "Legacy aliases like /providers, /models, /select, /use-model, /brain, and /approval still work.",
35541
35961
  "Press Ctrl+C during an active run to pause it and return to the shell, then use /resume to continue."
35542
35962
  ].join("\n");
@@ -36769,7 +37189,7 @@ async function handleMcpCommand(cwd, tokens) {
36769
37189
  }
36770
37190
  return;
36771
37191
  }
36772
- if (subcommand === "tools" || subcommand === "inspect") {
37192
+ if (subcommand === "tools" || subcommand === "inspect" || subcommand === "discover") {
36773
37193
  const name = tokens[2]?.trim();
36774
37194
  for (const line of await renderMcpInventory(cwd, name)) {
36775
37195
  console.log(line);
@@ -36805,10 +37225,12 @@ async function handleMcpCommand(cwd, tokens) {
36805
37225
  return;
36806
37226
  }
36807
37227
  if (subcommand === "read") {
36808
- const serverName = tokens[2]?.trim();
36809
- const uri = tokens.slice(3).join(" ").trim();
37228
+ const shorthand = tokens[2]?.trim();
37229
+ const shorthandMatch = shorthand?.match(/^@([^:\s]+):(.+)$/);
37230
+ const serverName = shorthandMatch ? shorthandMatch[1] : tokens[2]?.trim();
37231
+ const uri = shorthandMatch ? shorthandMatch[2] : tokens.slice(3).join(" ").trim();
36810
37232
  if (!serverName || !uri) {
36811
- throw new Error("Usage: /mcp read <server> <uri>");
37233
+ throw new Error("Usage: /mcp read <server> <uri> | /mcp read @server:uri");
36812
37234
  }
36813
37235
  for (const line of await renderMcpResourceRead(cwd, serverName, uri)) {
36814
37236
  console.log(line);
@@ -36878,7 +37300,7 @@ async function handleMcpCommand(cwd, tokens) {
36878
37300
  console.log(`Added MCP server ${name}`);
36879
37301
  return;
36880
37302
  }
36881
- throw new Error("Usage: /mcp [list|tools [server]|prompts [server]|resources [server]|prompt <server> <prompt>|read <server> <uri>|add <name> --command <cmd>|remove <name>|enable <name>|disable <name>]");
37303
+ throw new Error("Usage: /mcp [list|discover [server]|prompts [server]|resources [server]|prompt <server> <prompt>|read <server> <uri>|read @server:uri|add <name> --command <cmd>|remove <name>|enable <name>|disable <name>]");
36882
37304
  }
36883
37305
  async function handleAgentsCommand(cwd, tokens) {
36884
37306
  const subcommand = tokens[1]?.trim().toLowerCase();
@@ -36923,6 +37345,38 @@ async function handleAgentsCommand(cwd, tokens) {
36923
37345
  console.log(`Created ${outputPath}`);
36924
37346
  return;
36925
37347
  }
37348
+ if (subcommand === "show") {
37349
+ const id = tokens[2]?.trim();
37350
+ if (!id) {
37351
+ throw new Error("Usage: /agents show <id>");
37352
+ }
37353
+ const agent = await loadCustomAgentById(cwd, id);
37354
+ if (!agent) {
37355
+ throw new Error(`Unknown custom agent "${id}".`);
37356
+ }
37357
+ console.log(`${agent.id}`);
37358
+ console.log(` label: ${agent.label}`);
37359
+ console.log(` baseRole: ${agent.baseRole}`);
37360
+ console.log(` brainRole: ${agent.brainRole ?? "-"}`);
37361
+ console.log(` purpose: ${agent.purpose ?? "-"}`);
37362
+ console.log(` match: ${agent.match.join(", ") || "-"}`);
37363
+ console.log(` tools: ${agent.tools.join(", ") || "(inherits base role tools)"}`);
37364
+ console.log(` file: ${agent.filePath}`);
37365
+ if (agent.instructions.trim()) {
37366
+ console.log("");
37367
+ console.log(agent.instructions.trim());
37368
+ }
37369
+ return;
37370
+ }
37371
+ if (subcommand === "delete") {
37372
+ const id = tokens[2]?.trim();
37373
+ if (!id) {
37374
+ throw new Error("Usage: /agents delete <id>");
37375
+ }
37376
+ const outputPath = await deleteCustomAgentFile(cwd, id);
37377
+ console.log(`Deleted ${outputPath}`);
37378
+ return;
37379
+ }
36926
37380
  if (subcommand === "team" && tokens[2]?.trim().toLowerCase() === "create") {
36927
37381
  const id = tokens[3]?.trim();
36928
37382
  const agentIds = tokens.slice(4).filter((token) => !token.startsWith("--"));
@@ -36935,7 +37389,37 @@ async function handleAgentsCommand(cwd, tokens) {
36935
37389
  console.log(`Created ${outputPath}`);
36936
37390
  return;
36937
37391
  }
36938
- throw new Error("Usage: /agents [list|create <id> --base <role>|team create <id> <agent...>]");
37392
+ if (subcommand === "team" && tokens[2]?.trim().toLowerCase() === "show") {
37393
+ const id = tokens[3]?.trim();
37394
+ if (!id) {
37395
+ throw new Error("Usage: /agents team show <id>");
37396
+ }
37397
+ const team = await loadAgentTeamById(cwd, id);
37398
+ if (!team) {
37399
+ throw new Error(`Unknown team "${id}".`);
37400
+ }
37401
+ console.log(`${team.id}`);
37402
+ console.log(` label: ${team.label}`);
37403
+ console.log(` agents: ${team.agents.join(", ") || "-"}`);
37404
+ console.log(` match: ${team.match.join(", ") || "-"}`);
37405
+ console.log(` strategy: ${team.strategy}`);
37406
+ console.log(` file: ${team.filePath}`);
37407
+ if (team.description) {
37408
+ console.log("");
37409
+ console.log(team.description);
37410
+ }
37411
+ return;
37412
+ }
37413
+ if (subcommand === "team" && tokens[2]?.trim().toLowerCase() === "delete") {
37414
+ const id = tokens[3]?.trim();
37415
+ if (!id) {
37416
+ throw new Error("Usage: /agents team delete <id>");
37417
+ }
37418
+ const outputPath = await deleteAgentTeamFile(cwd, id);
37419
+ console.log(`Deleted ${outputPath}`);
37420
+ return;
37421
+ }
37422
+ throw new Error("Usage: /agents [list|show <id>|delete <id>|create <id> --base <role>|team create <id> <agent...>|team show <id>|team delete <id>]");
36939
37423
  }
36940
37424
  async function handleModelSurfaceCommand(cwd, tokens, runtime) {
36941
37425
  const subcommand = tokens[1]?.trim().toLowerCase();