winter-super-cli 2026.6.24 → 2026.6.27

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.
Files changed (123) hide show
  1. package/CHANGELOG.md +28 -5
  2. package/README.md +85 -0
  3. package/package.json +5 -1
  4. package/resources/local/gsap-skills/.claude-plugin/marketplace.json +20 -0
  5. package/resources/local/gsap-skills/.claude-plugin/plugin.json +6 -0
  6. package/resources/local/gsap-skills/.cursor-plugin/marketplace.json +13 -0
  7. package/resources/local/gsap-skills/.cursor-plugin/plugin.json +22 -0
  8. package/resources/local/gsap-skills/.github/copilot-instructions.md +17 -0
  9. package/resources/local/gsap-skills/.github/instructions/react.instructions.md +15 -0
  10. package/resources/local/gsap-skills/.github/instructions/scrolltrigger.instructions.md +18 -0
  11. package/resources/local/gsap-skills/AGENTS.md +27 -0
  12. package/resources/local/gsap-skills/CLAUDE.md +1 -0
  13. package/resources/local/gsap-skills/GEMINI.md +1 -0
  14. package/resources/local/gsap-skills/LICENSE +21 -0
  15. package/resources/local/gsap-skills/README.md +163 -0
  16. package/resources/local/gsap-skills/assets/gsap-green.svg +7 -0
  17. package/resources/local/gsap-skills/assets/gsap-icon-inverted.svg +15 -0
  18. package/resources/local/gsap-skills/assets/gsap-icon-square.svg +1 -0
  19. package/resources/local/gsap-skills/assets/gsap-white.svg +7 -0
  20. package/resources/local/gsap-skills/examples/README.md +29 -0
  21. package/resources/local/gsap-skills/examples/nuxt/app/app.vue +3 -0
  22. package/resources/local/gsap-skills/examples/nuxt/app/composables/useGSAP.ts +91 -0
  23. package/resources/local/gsap-skills/examples/nuxt/app/pages/index.vue +55 -0
  24. package/resources/local/gsap-skills/examples/nuxt/nuxt.config.ts +4 -0
  25. package/resources/local/gsap-skills/examples/nuxt/package.json +18 -0
  26. package/resources/local/gsap-skills/examples/react/App.jsx +46 -0
  27. package/resources/local/gsap-skills/examples/react/index.html +12 -0
  28. package/resources/local/gsap-skills/examples/react/main.jsx +9 -0
  29. package/resources/local/gsap-skills/examples/react/package.json +21 -0
  30. package/resources/local/gsap-skills/examples/react/vite.config.js +7 -0
  31. package/resources/local/gsap-skills/examples/vanilla/index.html +33 -0
  32. package/resources/local/gsap-skills/examples/vanilla/main.js +36 -0
  33. package/resources/local/gsap-skills/examples/vue/app.vue +47 -0
  34. package/resources/local/gsap-skills/examples/vue/index.html +15 -0
  35. package/resources/local/gsap-skills/examples/vue/main.js +9 -0
  36. package/resources/local/gsap-skills/examples/vue/package.json +19 -0
  37. package/resources/local/gsap-skills/examples/vue/vite.config.js +7 -0
  38. package/resources/local/gsap-skills/skills/gsap-core/SKILL.md +254 -0
  39. package/resources/local/gsap-skills/skills/gsap-frameworks/SKILL.md +266 -0
  40. package/resources/local/gsap-skills/skills/gsap-performance/SKILL.md +79 -0
  41. package/resources/local/gsap-skills/skills/gsap-plugins/SKILL.md +433 -0
  42. package/resources/local/gsap-skills/skills/gsap-react/SKILL.md +136 -0
  43. package/resources/local/gsap-skills/skills/gsap-scrolltrigger/SKILL.md +296 -0
  44. package/resources/local/gsap-skills/skills/gsap-timeline/SKILL.md +107 -0
  45. package/resources/local/gsap-skills/skills/gsap-utils/SKILL.md +284 -0
  46. package/resources/local/gsap-skills/skills/llms.txt +39 -0
  47. package/resources/local/hermes-agent-core/AGENTS.md +1132 -0
  48. package/resources/local/hermes-agent-core/LICENSE +21 -0
  49. package/resources/local/hermes-agent-core/README.md +215 -0
  50. package/resources/local/hermes-agent-core/docs/2026-05-07-s6-overlay-dynamic-subagent-gateways.md +434 -0
  51. package/resources/local/hermes-agent-core/hermes-already-has-routines.md +160 -0
  52. package/resources/local/hermes-agent-core/skills/autonomous-ai-agents/DESCRIPTION.md +3 -0
  53. package/resources/local/hermes-agent-core/skills/autonomous-ai-agents/claude-code/SKILL.md +745 -0
  54. package/resources/local/hermes-agent-core/skills/autonomous-ai-agents/codex/SKILL.md +130 -0
  55. package/resources/local/hermes-agent-core/skills/autonomous-ai-agents/hermes-agent/SKILL.md +1021 -0
  56. package/resources/local/hermes-agent-core/skills/autonomous-ai-agents/kanban-codex-lane/SKILL.md +277 -0
  57. package/resources/local/hermes-agent-core/skills/autonomous-ai-agents/kanban-codex-lane/templates/pmb-codex-lane-prompt.md +57 -0
  58. package/resources/local/hermes-agent-core/skills/autonomous-ai-agents/opencode/SKILL.md +219 -0
  59. package/resources/local/hermes-agent-core/skills/github/DESCRIPTION.md +3 -0
  60. package/resources/local/hermes-agent-core/skills/github/codebase-inspection/SKILL.md +116 -0
  61. package/resources/local/hermes-agent-core/skills/github/github-auth/SKILL.md +247 -0
  62. package/resources/local/hermes-agent-core/skills/github/github-auth/scripts/gh-env.sh +66 -0
  63. package/resources/local/hermes-agent-core/skills/github/github-code-review/SKILL.md +481 -0
  64. package/resources/local/hermes-agent-core/skills/github/github-code-review/references/review-output-template.md +74 -0
  65. package/resources/local/hermes-agent-core/skills/github/github-issues/SKILL.md +370 -0
  66. package/resources/local/hermes-agent-core/skills/github/github-issues/templates/bug-report.md +35 -0
  67. package/resources/local/hermes-agent-core/skills/github/github-issues/templates/feature-request.md +31 -0
  68. package/resources/local/hermes-agent-core/skills/github/github-pr-workflow/SKILL.md +367 -0
  69. package/resources/local/hermes-agent-core/skills/github/github-pr-workflow/references/ci-troubleshooting.md +183 -0
  70. package/resources/local/hermes-agent-core/skills/github/github-pr-workflow/references/conventional-commits.md +71 -0
  71. package/resources/local/hermes-agent-core/skills/github/github-pr-workflow/templates/pr-body-bugfix.md +35 -0
  72. package/resources/local/hermes-agent-core/skills/github/github-pr-workflow/templates/pr-body-feature.md +33 -0
  73. package/resources/local/hermes-agent-core/skills/github/github-repo-management/SKILL.md +516 -0
  74. package/resources/local/hermes-agent-core/skills/github/github-repo-management/references/github-api-cheatsheet.md +161 -0
  75. package/resources/local/hermes-agent-core/skills/mcp/DESCRIPTION.md +3 -0
  76. package/resources/local/hermes-agent-core/skills/mcp/native-mcp/SKILL.md +357 -0
  77. package/resources/local/hermes-agent-core/skills/software-development/debugging-hermes-tui-commands/SKILL.md +152 -0
  78. package/resources/local/hermes-agent-core/skills/software-development/hermes-agent-skill-authoring/SKILL.md +165 -0
  79. package/resources/local/hermes-agent-core/skills/software-development/hermes-s6-container-supervision/SKILL.md +176 -0
  80. package/resources/local/hermes-agent-core/skills/software-development/node-inspect-debugger/SKILL.md +319 -0
  81. package/resources/local/hermes-agent-core/skills/software-development/plan/SKILL.md +58 -0
  82. package/resources/local/hermes-agent-core/skills/software-development/python-debugpy/SKILL.md +375 -0
  83. package/resources/local/hermes-agent-core/skills/software-development/requesting-code-review/SKILL.md +280 -0
  84. package/resources/local/hermes-agent-core/skills/software-development/spike/SKILL.md +197 -0
  85. package/resources/local/hermes-agent-core/skills/software-development/subagent-driven-development/SKILL.md +352 -0
  86. package/resources/local/hermes-agent-core/skills/software-development/subagent-driven-development/references/context-budget-discipline.md +53 -0
  87. package/resources/local/hermes-agent-core/skills/software-development/subagent-driven-development/references/gates-taxonomy.md +93 -0
  88. package/resources/local/hermes-agent-core/skills/software-development/systematic-debugging/SKILL.md +367 -0
  89. package/resources/local/hermes-agent-core/skills/software-development/test-driven-development/SKILL.md +343 -0
  90. package/resources/local/hermes-agent-core/skills/software-development/writing-plans/SKILL.md +297 -0
  91. package/resources/local/manifest.json +12 -0
  92. package/rule.md +2 -0
  93. package/scripts/audit-pack.js +5 -0
  94. package/scripts/smoke-browser.js +53 -0
  95. package/scripts/smoke-package.js +38 -4
  96. package/skill.md +36 -4
  97. package/skills/gsap.md +26 -0
  98. package/skills/hermes-agent.md +17 -0
  99. package/src/agent/agent-definitions.js +4 -4
  100. package/src/agent/runtime.js +179 -5
  101. package/src/agent/subagent-child.js +44 -0
  102. package/src/ai/capability-scorecard.js +193 -14
  103. package/src/ai/hermes-core.js +77 -0
  104. package/src/ai/model-capabilities.js +42 -2
  105. package/src/ai/prompts/system-prompt.js +18 -2
  106. package/src/ai/small-model-amplifier.js +35 -7
  107. package/src/ai/workflow-selector.js +22 -1
  108. package/src/cli/commands.js +46 -2
  109. package/src/cli/config.js +45 -6
  110. package/src/cli/context-loader.js +253 -9
  111. package/src/cli/conversation-format.js +5 -0
  112. package/src/cli/input-controller.js +79 -10
  113. package/src/cli/prompt-builder.js +47 -8
  114. package/src/cli/repl-commands.js +115 -0
  115. package/src/cli/repl.js +343 -85
  116. package/src/cli/slash-commands.js +4 -2
  117. package/src/cli/tui.js +133 -37
  118. package/src/mcp/client.js +54 -11
  119. package/src/mcp/presets.js +114 -0
  120. package/src/tools/agent.js +316 -25
  121. package/src/tools/executor.js +412 -12
  122. package/src/tools/permission.js +20 -17
  123. package/winter.d.ts +112 -10
package/src/cli/repl.js CHANGED
@@ -22,6 +22,8 @@ import { SessionManager } from '../session/manager.js';
22
22
  import { AIProviderManager } from '../ai/providers.js';
23
23
  import { ConfigLoader } from './config.js';
24
24
  import { PermissionManager } from '../tools/permission.js';
25
+ import { MCPClient } from '../mcp/client.js';
26
+ import { getMcpPreset, upsertMcpServer } from '../mcp/presets.js';
25
27
  import { compressConversation } from '../context/compress.js';
26
28
  import { getToolUsageSummary } from '../tools/analytics.js';
27
29
  import { SweAgent } from '../agent/swe-agent.js';
@@ -425,6 +427,7 @@ export class WinterREPL {
425
427
  const shouldDrop = (entry) => {
426
428
  const text = typeof entry === 'string' ? entry : entry?.text || '';
427
429
  return text.startsWith('[Required local resources]')
430
+ || text.startsWith('[Auto-loaded resource application profile]')
428
431
  || text.startsWith('[Auto-applied skills]')
429
432
  || text.startsWith('[Project rule file')
430
433
  || text.startsWith('[Startup local resource index]')
@@ -440,7 +443,11 @@ export class WinterREPL {
440
443
  `awesome-design-md: ${resourcePaths.designs}`,
441
444
  `karpathy-tools: ${resourcePaths.karpathy}`,
442
445
  `page-agent: ${resourcePaths.pageAgent}`,
446
+ `hermes-agent-core: ${resourcePaths.hermesAgentCore}`,
447
+ `gsap-skills: ${resourcePaths.gsapSkills}`,
443
448
  `ecc: ${resourcePaths.ecc}`,
449
+ `codex: ${resourcePaths.codex.root}`,
450
+ `claude: ${resourcePaths.claude.root}`,
444
451
  ];
445
452
  await this.session.replaceMemory(
446
453
  '[Startup local resource index]',
@@ -669,8 +676,8 @@ export class WinterREPL {
669
676
  }
670
677
  }
671
678
 
672
- await this.bootstrapProjectCapabilities();
673
679
  await this.compactStartupMemories({ projectInstructionFiles, autoCreateDocs });
680
+ await this.bootstrapProjectCapabilities();
674
681
 
675
682
  // Codebase Index is intentionally lazy/on-demand. Top-tier coding agents do
676
683
  // not make first-run chat pay for a repo-wide scan, and this also prevents
@@ -716,6 +723,7 @@ export class WinterREPL {
716
723
  this._pasteTimer = null;
717
724
  this._isPasteChunk = false;
718
725
  this._pasteChunkTimer = null;
726
+ this._pasteCounter = 0;
719
727
  const PASTE_DELAY = 80;
720
728
 
721
729
  process.stdin.on('data', (chunk) => {
@@ -734,7 +742,7 @@ export class WinterREPL {
734
742
  }
735
743
  });
736
744
 
737
- const flushPasteBuffer = () => {
745
+ const flushPasteBuffer = async () => {
738
746
  this._pasteTimer = null;
739
747
  if (this._pasteBuffer.length === 0) return;
740
748
 
@@ -770,6 +778,19 @@ export class WinterREPL {
770
778
  this._multilineBuffer.push(...this._pasteBuffer);
771
779
  this._pasteBuffer = [];
772
780
 
781
+ if (this.shouldAutoPersistBufferedPaste(this._multilineBuffer, { isPasteChunk: this._isPasteChunk })) {
782
+ const pastedText = this._multilineBuffer.join('\n').trimEnd();
783
+ if (this.shouldPersistPastedText(pastedText)) {
784
+ const paste = await this.persistPastedText(pastedText);
785
+ this._multilineBuffer = [];
786
+ this._isPasteChunk = false;
787
+ const reference = this.formatPastedTextReference(paste);
788
+ console.log(`${colors.cyan}│ ${colors.dim}${reference}${colors.reset}`);
789
+ this.submitInputQueue(reference);
790
+ return;
791
+ }
792
+ }
793
+
773
794
  // If they pressed Enter on an empty line, submit the multiline buffer!
774
795
  if (isJustEmptyEnter && this._multilineBuffer.length > 1) {
775
796
  // Remove the trailing empty line
@@ -786,6 +807,14 @@ export class WinterREPL {
786
807
  return;
787
808
  }
788
809
 
810
+ if (this.shouldPersistPastedText(combined)) {
811
+ const paste = await this.persistPastedText(combined);
812
+ const reference = this.formatPastedTextReference(paste);
813
+ console.log(`${colors.cyan}│ ${colors.dim}${reference}${colors.reset}`);
814
+ this.submitInputQueue(reference);
815
+ return;
816
+ }
817
+
789
818
  this.submitInputQueue(combined);
790
819
  return;
791
820
  }
@@ -814,7 +843,14 @@ export class WinterREPL {
814
843
  this.rl.on('line', (line) => {
815
844
  this._pasteBuffer.push(line);
816
845
  if (this._pasteTimer) clearTimeout(this._pasteTimer);
817
- this._pasteTimer = setTimeout(flushPasteBuffer, PASTE_DELAY);
846
+ this._pasteTimer = setTimeout(() => {
847
+ void flushPasteBuffer().catch((error) => {
848
+ this._pasteBuffer = [];
849
+ this._multilineBuffer = [];
850
+ console.log(`\n${colors.red}? Paste error: ${error.message}${colors.reset}\n`);
851
+ if (this.running && !this.readlineClosed) this.showInputPrompt();
852
+ });
853
+ }, PASTE_DELAY);
818
854
  });
819
855
 
820
856
  this.rl.on('close', async () => {
@@ -861,6 +897,55 @@ export class WinterREPL {
861
897
  return this.inputController.handleDirectClipboardPaste();
862
898
  }
863
899
 
900
+ getPasteStorageDir() {
901
+ return path.join(this.config?.winterDir || path.join(homedir(), '.winter'), 'pastes');
902
+ }
903
+
904
+ normalizePastedText(text = '') {
905
+ return String(text || '')
906
+ .replace(/\x1b\[200~/g, '')
907
+ .replace(/\x1b\[201~/g, '')
908
+ .replace(/\r\n/g, '\n')
909
+ .replace(/\r/g, '\n');
910
+ }
911
+
912
+ getPastedTextLineCount(text = '') {
913
+ const value = this.normalizePastedText(text);
914
+ if (!value) return 0;
915
+ return value.split(/\r\n|\r|\n/).length;
916
+ }
917
+
918
+ shouldPersistPastedText(text = '', { minLines = 8, minChars = 4000 } = {}) {
919
+ const value = this.normalizePastedText(text);
920
+ return this.getPastedTextLineCount(value) >= minLines || value.length >= minChars;
921
+ }
922
+
923
+ shouldAutoPersistBufferedPaste(buffer = [], { isPasteChunk = false } = {}) {
924
+ if (!Array.isArray(buffer) || buffer.length === 0) return false;
925
+ const manualMultiline = buffer[0] === '';
926
+ const text = this.normalizePastedText(buffer.join('\n')).trimEnd();
927
+ return Boolean(text) && this.shouldPersistPastedText(text) && (isPasteChunk || !manualMultiline);
928
+ }
929
+
930
+ async persistPastedText(text = '') {
931
+ const content = this.normalizePastedText(text);
932
+ const dir = this.getPasteStorageDir();
933
+ await fs.mkdir(dir, { recursive: true });
934
+ this._pasteCounter = Number(this._pasteCounter || 0) + 1;
935
+ const filePath = path.join(dir, `paste_${this._pasteCounter}_${Date.now()}.txt`);
936
+ await fs.writeFile(filePath, content, 'utf8');
937
+ return {
938
+ index: this._pasteCounter,
939
+ path: filePath,
940
+ lines: this.getPastedTextLineCount(content),
941
+ chars: content.length,
942
+ };
943
+ }
944
+
945
+ formatPastedTextReference(paste = {}) {
946
+ return `[Pasted text #${paste.index || 1}: ${paste.lines || 0} lines -> ${paste.path}]`;
947
+ }
948
+
864
949
  buildInputPanel() {
865
950
  return this.inputController.buildInputPanel();
866
951
  }
@@ -1123,6 +1208,19 @@ export class WinterREPL {
1123
1208
  return;
1124
1209
  }
1125
1210
 
1211
+ if (!input.startsWith('/')) {
1212
+ const browserShortcut = this.resolveBrowserShortcut(input);
1213
+ if (browserShortcut) {
1214
+ await this.handleOpenBrowserIntent(input, browserShortcut);
1215
+ return;
1216
+ }
1217
+ }
1218
+
1219
+ if (!input.startsWith('/') && this.isOpenBrowserIntent(input)) {
1220
+ await this.handleOpenBrowserIntent(input);
1221
+ return;
1222
+ }
1223
+
1126
1224
  // Parse @-symbols for non-command input
1127
1225
  if (!input.startsWith('/')) {
1128
1226
  const canUseHeavyContext = await this.shouldUseHeavyProjectContext();
@@ -1196,6 +1294,84 @@ export class WinterREPL {
1196
1294
  }
1197
1295
  }
1198
1296
 
1297
+ isOpenBrowserIntent(input = '') {
1298
+ const raw = String(input || '').trim();
1299
+ if (!raw) return false;
1300
+ const text = `${raw.toLowerCase()}\n${this.normalizeIntentText(raw).toLowerCase()}`;
1301
+ return /\b(mo|open|launch|start)\b.*\b(chrome|browser|trinh duyet|google chrome)\b/i.test(text)
1302
+ || /\b(chrome|browser|trinh duyet|google chrome)\b.*\b(mo|open|launch|start)\b/i.test(text);
1303
+ }
1304
+
1305
+ resolveBrowserShortcut(input = '') {
1306
+ const raw = String(input || '').trim();
1307
+ if (!raw) return null;
1308
+ const normalized = this.normalizeIntentText(raw).toLowerCase();
1309
+ const text = `${raw.toLowerCase()}\n${normalized}`;
1310
+
1311
+ const url = this.extractUrlFromText(raw);
1312
+ if (url && /\b(mo|open|launch|start|browse)\b/i.test(normalized)) {
1313
+ return { url, label: url };
1314
+ }
1315
+
1316
+ const knownSites = [
1317
+ { pattern: /\b(youtube music|yt music|music youtube)\b/i, url: 'https://music.youtube.com', label: 'YouTube Music' },
1318
+ { pattern: /\b(youtube|you tube)\b/i, url: 'https://www.youtube.com', label: 'YouTube' },
1319
+ { pattern: /\b(spotify)\b/i, url: 'https://open.spotify.com', label: 'Spotify' },
1320
+ { pattern: /\b(google)\b/i, url: 'https://www.google.com', label: 'Google' },
1321
+ ];
1322
+
1323
+ if (/\b(mo|open|launch|start)\b/i.test(normalized)) {
1324
+ const site = knownSites.find(item => item.pattern.test(text));
1325
+ if (site) return site;
1326
+ }
1327
+
1328
+ if (/\b(tim|search|kiem)\b/i.test(normalized) && /\b(chrome|google|browser|trinh duyet)\b/i.test(normalized)) {
1329
+ const query = this.extractBrowserSearchQuery(raw);
1330
+ if (query) {
1331
+ return {
1332
+ url: `https://www.google.com/search?${new URLSearchParams({ q: query }).toString()}`,
1333
+ label: `Google search: ${query}`,
1334
+ };
1335
+ }
1336
+ }
1337
+
1338
+ return null;
1339
+ }
1340
+
1341
+ extractBrowserSearchQuery(input = '') {
1342
+ let query = String(input || '').trim();
1343
+ query = query.replace(/^\s*(tìm|tim|search|kiếm|kiem)\s+/i, '');
1344
+ query = query.replace(/\s+(trên|tren|on)\s+(chrome|google|browser|trình duyệt|trinh duyet)\b.*$/i, '');
1345
+ query = query.replace(/\s+(đi|di)\s*$/i, '');
1346
+ query = query.trim();
1347
+ return query || null;
1348
+ }
1349
+
1350
+ extractUrlFromText(input = '') {
1351
+ const match = String(input || '').match(/https?:\/\/[^\s]+/i);
1352
+ return match ? match[0].replace(/[),.;]+$/g, '') : null;
1353
+ }
1354
+
1355
+ async handleOpenBrowserIntent(input = '', options = {}) {
1356
+ const url = options.url || this.extractUrlFromText(input) || 'about:blank';
1357
+ const result = await this.tools.execute('OpenBrowser', { browser: 'chrome', url }, { cwd: this.projectPath });
1358
+ await this.session?.addToHistory?.({ role: 'user', content: input });
1359
+
1360
+ if (result?.success === false) {
1361
+ const message = `Không mở được Chrome: ${result.error || 'unknown error'}`;
1362
+ console.log(`${colors.red}${message}${colors.reset}`);
1363
+ if (result.recovery) console.log(`${colors.dim}${result.recovery}${colors.reset}`);
1364
+ await this.session?.addToHistory?.({ role: 'assistant', content: message });
1365
+ return result;
1366
+ }
1367
+
1368
+ const label = options.label || url;
1369
+ const message = `Đã mở Chrome${url && url !== 'about:blank' ? `: ${label}` : '.'}`;
1370
+ console.log(`${colors.green}${message}${colors.reset}`);
1371
+ await this.session?.addToHistory?.({ role: 'assistant', content: message });
1372
+ return result;
1373
+ }
1374
+
1199
1375
  showSmartTip(input = '') {
1200
1376
  const text = input.toLowerCase();
1201
1377
  let tip = null;
@@ -1395,7 +1571,7 @@ export class WinterREPL {
1395
1571
 
1396
1572
  CRITICAL DEBUG/AGENT RULES:
1397
1573
  1. Inspect the project before changing anything. Read the failing file, related caller, config, and logs.
1398
- 2. Reproduce or locate the first hard failure. For frontend/runtime UI issues, use BrowserDebug when a URL/dev server is available.
1574
+ 2. Reproduce or locate the first hard failure. For frontend/runtime UI issues, use chrome-devtools MCP in visible Chrome when a URL/dev server is available; use BrowserDebug only as a headless fallback.
1399
1575
  3. Patch the smallest root cause with Write/Edit.
1400
1576
  4. Run the closest verification command(s): ${verifyCommands.join(' && ')}.
1401
1577
  5. If verification fails, read the new error, patch again, and run verification again.
@@ -1651,14 +1827,14 @@ ${colors.reset}
1651
1827
  case 'review':
1652
1828
  return byName(['Read', 'Grep', 'Glob', 'Bash', 'WebFetch']);
1653
1829
  case 'debug':
1654
- return byName(['Read', 'Write', 'Edit', 'Bash', 'Grep', 'Glob', 'BrowserDebug', 'WebFetch', 'Parallel']);
1830
+ return byName(['Read', 'Write', 'Edit', 'Bash', 'Grep', 'Glob', 'OpenBrowser', 'VisibleBrowser', 'BrowserDebug', 'WebFetch', 'MCP', 'Parallel']);
1655
1831
  case 'research':
1656
1832
  return byName(['Read', 'Grep', 'Glob', 'WebFetch', 'WebSearch', 'Parallel']);
1657
1833
  case 'design':
1658
1834
  case 'ui':
1659
- return byName(['Read', 'Write', 'Edit', 'Bash', 'Grep', 'Glob', 'BrowserDebug', 'WebFetch']);
1835
+ return byName(['Read', 'Write', 'Edit', 'Bash', 'Grep', 'Glob', 'OpenBrowser', 'VisibleBrowser', 'BrowserDebug', 'WebFetch', 'MCP']);
1660
1836
  default:
1661
- return byName(['Read', 'Write', 'Edit', 'Bash', 'Glob', 'Grep', 'BrowserDebug', 'WebFetch', 'WebSearch', 'Parallel', 'Agent']);
1837
+ return byName(['Read', 'Write', 'Edit', 'Bash', 'Glob', 'Grep', 'OpenBrowser', 'VisibleBrowser', 'BrowserDebug', 'WebFetch', 'WebSearch', 'MCP', 'Parallel', 'Agent', 'DelegateTask', 'ParallelAgent']);
1662
1838
  }
1663
1839
  }
1664
1840
 
@@ -1783,6 +1959,8 @@ ${colors.reset}
1783
1959
  const pureQuestionPattern = /^(what|why|how|when|where|is|are|can|could|should|would|explain|describe|tell me|compare|giải thích|mô tả|so sánh|tai sao|vi sao|la gi|co nen|co phai|tại sao|vì sao|là gì|có nên|có phải|nhu the nao|như thế nào|khi nào)\b/i;
1784
1960
 
1785
1961
  if (pureQuestionPattern.test(text) && !actionPattern.test(text)) return false;
1962
+
1963
+ if (this.isBrowserInteractionRequest(rawText)) return true;
1786
1964
 
1787
1965
  // Even without explicit target, some verbs are strong enough on their own
1788
1966
  const strongActionAlone = /\b(fix|debug|deploy|build|test|commit|install|run|refactor|sửa|chạy|cài|triển khai|xây dựng)\b/i;
@@ -1812,6 +1990,15 @@ ${colors.reset}
1812
1990
  return true;
1813
1991
  }
1814
1992
 
1993
+ isBrowserInteractionRequest(text = '') {
1994
+ const raw = String(text || '');
1995
+ const normalized = this.normalizeIntentText(raw);
1996
+ const combined = `${raw}\n${normalized}`;
1997
+ const action = /\b(click|fill|submit|press|select|navigate|bam|dien|chon|nhan|vao|bấm|điền|chọn|nhấn|vào)\b/i;
1998
+ const target = /\b(url|http|https|site|website|web|page|form|button|link|chrome|browser|trang|nut|nút|dang ky|dang nhap|khach hang|khách hàng|đăng ký|đăng nhập)\b/i;
1999
+ return action.test(combined) && target.test(combined);
2000
+ }
2001
+
1815
2002
  detectFakeCompletion(content = '') {
1816
2003
  const text = String(content || '').toLowerCase();
1817
2004
  if (!text.trim()) return false;
@@ -1820,6 +2007,9 @@ ${colors.reset}
1820
2007
  const fakeCompletionClaims = /(?:đã (?:sửa|tạo|viết|xóa|cập nhật|thêm|chỉnh|xong|hoàn thành|fix|update|edit|write|create|delete|remove|modify|change|apply|deploy|push)|i(?:'ve| have) (?:fixed|created|written|updated|added|modified|changed|edited|applied|deployed|deleted|removed|patched|implemented|refactored)|done!|xong rồi|hoàn thành|đã hoàn tất|hoàn tất|the (?:fix|change|update|edit|modification) (?:has been|is) (?:applied|done|completed|made)|here(?:'s| is) the (?:fix|update|change|solution|implementation|code)|file (?:has been|was) (?:updated|created|modified|written|changed)|changes? (?:have been|has been|were) (?:made|applied|saved)|successfully (?:updated|created|modified|fixed|applied|changed|written))/i;
1821
2008
  if (fakeCompletionClaims.test(text)) return true;
1822
2009
 
2010
+ const fakeBrowserClaims = /(?:đã|da|i(?:'ve| have))\s+(?:bấm|bam|click(?:ed)?|mở|mo|open(?:ed)?|điền|dien|fill(?:ed)?|chọn|chon|select(?:ed)?|submit(?:ted)?|vào|vao|navigate(?:d)?)/i;
2011
+ if (fakeBrowserClaims.test(text)) return true;
2012
+
1823
2013
  // Detect code blocks that pretend to show "changes" without tool use
1824
2014
  const codeBlockWithFilePath = /```[\s\S]*?(?:[\/\\][\w.-]+\.(?:js|ts|py|css|html|json|md|jsx|tsx|vue|go|rs|java|c|cpp|rb|sh))[\s\S]*?```/i;
1825
2015
  const claimsFileChange = /(?:here(?:'s| is)|below|sau đây|dưới đây|như sau|updated|modified|changed|new|fixed)/i;
@@ -1830,6 +2020,19 @@ ${colors.reset}
1830
2020
 
1831
2021
  buildToolEvidenceCorrection(messages = []) {
1832
2022
  const request = this.getLatestUserText(messages);
2023
+ if (this.isBrowserInteractionRequest(request)) {
2024
+ return [
2025
+ 'RUNTIME ENFORCEMENT: Your previous response was BLOCKED because you claimed a browser/web action without real browser tool evidence.',
2026
+ '',
2027
+ 'For browser interaction tasks you MUST use tools, not prose:',
2028
+ '1. If chrome-devtools MCP is configured, call MCP {"server":"chrome-devtools","tool":"list"} first if needed.',
2029
+ '2. Use chrome-devtools MCP tools such as new_page/navigate_page, take_snapshot, click, fill/fill_form, evaluate_script, list_network_requests, and take_screenshot in visible Chrome.',
2030
+ '3. Use WebFetch only for static text extraction. WebFetch cannot click buttons, fill forms, or preserve page state. Use VisibleBrowser when MCP is unavailable and the user needs visible browser control. BrowserDebug is headless and should be fallback only.',
2031
+ '4. Do not say "đã bấm", "đã điền", "đã mở", or "đã kiểm tra" until a browser/MCP tool result proves it.',
2032
+ '',
2033
+ `Original user request: ${request}`,
2034
+ ].join('\n');
2035
+ }
1833
2036
  return [
1834
2037
  '⚠️ RUNTIME ENFORCEMENT: Your previous response was BLOCKED because you did not use any tool.',
1835
2038
  '',
@@ -1842,7 +2045,7 @@ ${colors.reset}
1842
2045
  'DO NOT say "I have updated/created/fixed" without a tool call proving it.',
1843
2046
  'DO NOT describe what you would do. Actually DO IT with tool calls.',
1844
2047
  '',
1845
- 'Available tools: Read, Write, Edit, Bash, Glob, Grep, BrowserDebug, WebFetch, WebSearch.',
2048
+ 'Available tools: Read, Write, Edit, Bash, Glob, Grep, OpenBrowser, VisibleBrowser, BrowserDebug, WebFetch, WebSearch, MCP.',
1846
2049
  '',
1847
2050
  'If native tool calls are not supported, output exactly one fallback tool call:',
1848
2051
  '<invoke name="Read"><parameter name="path">README.md</parameter></invoke>',
@@ -1864,6 +2067,10 @@ ${colors.reset}
1864
2067
 
1865
2068
  const hints = [];
1866
2069
 
2070
+ if (/\b(click|fill|submit|press|select|navigate|bam|dien|chon|nhan|vao|bấm|điền|chọn|nhấn|vào)\b/i.test(text) && /\b(web|website|page|url|http|chrome|browser|form|button|link|trang|nut|nút|dang ky|dang nhap|khach hang|đăng ký|đăng nhập|khách hàng)\b/i.test(text)) {
2071
+ hints.push('TOOL HINT: This is a live browser interaction. Do NOT use WebFetch alone. Prefer visible Chrome via MCP {"server":"chrome-devtools","tool":"list"} then chrome-devtools tools new_page/navigate_page, take_snapshot, click, fill/fill_form, evaluate_script, list_network_requests, and take_screenshot. If MCP is unavailable, call VisibleBrowser for real visible Puppeteer control. Only use BrowserDebug as headless fallback. Only claim click/fill/navigation after MCP, VisibleBrowser, or BrowserDebug evidence.');
2072
+ }
2073
+
1867
2074
  // Detect file reading requests
1868
2075
  const hasPath = /[A-Za-z]:[\\/][\w.\\/\\-]+/i.test(text) || /(?:^|\s)[.~]?\/[\w.\/-]+/i.test(text);
1869
2076
  const readVerbs = /\b(đọc|doc|read|xem|view|mở|open|show|hiện|hiển thị|cat|type)\b/i;
@@ -1902,7 +2109,11 @@ ${colors.reset}
1902
2109
 
1903
2110
  // Detect URL/web requests
1904
2111
  if (/\b(https?:\/\/[^\s]+|url|website|trang web|web page)\b/i.test(text)) {
1905
- hints.push('TOOL HINT: To fetch a URL, call tool WebFetch with {"url": "<url>"}. For browser debugging, use BrowserDebug.');
2112
+ hints.push('TOOL HINT: To fetch static URL text, call WebFetch. For live visible browser debugging/control, prefer MCP server chrome-devtools with new_page/navigate_page, take_snapshot, click, fill/fill_form, evaluate_script, list_console_messages, list_network_requests, or performance trace tools; use BrowserDebug only as headless fallback.');
2113
+ }
2114
+
2115
+ if (/\b(chrome|devtools|browser debug|debug browser|screenshot|console|network|lcp|performance|perf|web vitals|localhost|127\.0\.0\.1)\b/i.test(text)) {
2116
+ hints.push('TOOL HINT: For "open Chrome" / "mở chrome", call OpenBrowser {"browser":"chrome","url":"about:blank"}. Do NOT call Bash/Get-Command/Start-Process. If configured, use MCP {"server":"chrome-devtools","tool":"list"} for page state, console, network, screenshots, and performance.');
1906
2117
  }
1907
2118
 
1908
2119
  if (hints.length === 0) return null;
@@ -1980,6 +2191,9 @@ ${colors.reset}
1980
2191
  if ((options?.requireToolEvidence && this.responseNeedsToolEvidence(assistantMsg.content)) || isFakeCompletion) {
1981
2192
  return { assistantMsg, toolCalls, finalContent: '', finishReason: 'tool_evidence_required' };
1982
2193
  }
2194
+ if (options?.deferFinalContent) {
2195
+ return { assistantMsg, toolCalls, finalContent: assistantMsg.content, finishReason };
2196
+ }
1983
2197
  this.printAssistantAnswer(assistantMsg.content, startedAt, totalUsage);
1984
2198
  return { assistantMsg, toolCalls, finalContent: assistantMsg.content, finishReason };
1985
2199
  }
@@ -1994,7 +2208,7 @@ ${colors.reset}
1994
2208
  const toolCallParts = [];
1995
2209
  let finishReason = null;
1996
2210
  let printed = false;
1997
- const bufferToolModeContent = options?.enableTools === true;
2211
+ const bufferToolModeContent = false;
1998
2212
 
1999
2213
  for await (const chunk of this.ai.streamRequest(messages, options)) {
2000
2214
  if (chunk.usage) this.addUsage(totalUsage, chunk.usage);
@@ -2111,6 +2325,14 @@ ${colors.reset}
2111
2325
  finishReason: 'tool_evidence_required',
2112
2326
  };
2113
2327
  }
2328
+ if (options?.deferFinalContent) {
2329
+ return {
2330
+ assistantMsg: { content: visibleContent },
2331
+ toolCalls,
2332
+ finalContent: visibleContent,
2333
+ finishReason,
2334
+ };
2335
+ }
2114
2336
  this.printAssistantAnswer(visibleContent, startedAt, totalUsage);
2115
2337
  return {
2116
2338
  assistantMsg: { content: visibleContent },
@@ -2147,7 +2369,7 @@ ${colors.reset}
2147
2369
  '/provider', '/model', '/models', '/providers',
2148
2370
  '/theme:toggle', '/tui',
2149
2371
  '/auto', '/debug', '/doctor', '/context', '/scorecard', '/swe',
2150
- '/read', '/write', '/glob', '/grep', '/bash',
2372
+ '/read', '/write', '/glob', '/grep', '/bash', '/browser', '/paste',
2151
2373
  '/codex', '/claude', '/karpathy', '/agents',
2152
2374
  '/resources', '/designs', '/skills',
2153
2375
  '/ecc',
@@ -2244,6 +2466,9 @@ ${colors.reset}
2244
2466
 
2245
2467
  async requestFinalAnswer(messages, toolSummaries, startedAt, totalUsage) {
2246
2468
  const executionProfile = this.selectExecutionProfile(messages, { enableTools: false });
2469
+ const latestUserText = this.getLatestUserText(messages);
2470
+ const browserInteraction = this.isBrowserInteractionRequest(latestUserText);
2471
+ const hasBrowserEvidence = toolSummaries.some(summary => /^(MCP|VisibleBrowser|BrowserDebug|OpenBrowser):/i.test(summary));
2247
2472
  const finalMessages = [
2248
2473
  ...messages,
2249
2474
  {
@@ -2256,6 +2481,7 @@ ${colors.reset}
2256
2481
  'Start with the actual outcome, then mention only the most relevant files/commands. Avoid broad generic advice.',
2257
2482
  'If a tool failed, explain the concrete failure briefly and answer with the available evidence.',
2258
2483
  'Do not repeat the plan. Do not re-summarize unrelated project context. Do not claim memory/tool state that is not visible in the transcript.',
2484
+ browserInteraction && !hasBrowserEvidence ? 'Important: The user asked for browser interaction, but no MCP/VisibleBrowser/BrowserDebug/OpenBrowser result is available. You must say the browser action was NOT performed; do not claim you clicked, filled, navigated, or inspected pages.' : '',
2259
2485
  toolSummaries.length ? `Tool summary:\n${toolSummaries.join('\n')}` : '',
2260
2486
  ].filter(Boolean).join('\n'),
2261
2487
  },
@@ -2268,7 +2494,7 @@ ${colors.reset}
2268
2494
  }
2269
2495
 
2270
2496
  if (typeof this.ai.streamRequest === 'function') {
2271
- return await this.streamFinalAnswer(finalMessages, startedAt, totalUsage, executionProfile);
2497
+ return await this.streamFinalAnswer(finalMessages, startedAt, totalUsage, executionProfile, { browserInteraction, hasBrowserEvidence });
2272
2498
  }
2273
2499
 
2274
2500
  const response = await this.ai.sendRequest(finalMessages, {
@@ -2278,7 +2504,10 @@ ${colors.reset}
2278
2504
  signal: this.currentAbortController?.signal,
2279
2505
  });
2280
2506
  this.addUsage(totalUsage, response.usage);
2281
- const content = response.choices?.[0]?.message?.content || '';
2507
+ let content = response.choices?.[0]?.message?.content || '';
2508
+ if (browserInteraction && !hasBrowserEvidence && this.detectFakeCompletion(content)) {
2509
+ content = 'Chưa thực hiện được thao tác trên trình duyệt: lượt này không có bằng chứng từ MCP/VisibleBrowser/BrowserDebug/OpenBrowser, nên Winter chặn câu trả lời để tránh báo sai. Hãy bật chrome-devtools MCP hoặc dùng VisibleBrowser để Winter gọi đúng browser tool.';
2510
+ }
2282
2511
 
2283
2512
  if (this.spinner) this.spinner.stop();
2284
2513
 
@@ -2295,7 +2524,7 @@ ${colors.reset}
2295
2524
  }
2296
2525
  }
2297
2526
 
2298
- async streamFinalAnswer(messages, startedAt, totalUsage, executionProfile = null) {
2527
+ async streamFinalAnswer(messages, startedAt, totalUsage, executionProfile = null, validation = {}) {
2299
2528
  let content = '';
2300
2529
  const profile = executionProfile || this.selectExecutionProfile(messages, { enableTools: false });
2301
2530
 
@@ -2321,6 +2550,10 @@ ${colors.reset}
2321
2550
 
2322
2551
  if (this.spinner) this.spinner.stop();
2323
2552
 
2553
+ if (validation.browserInteraction && !validation.hasBrowserEvidence && this.detectFakeCompletion(content)) {
2554
+ content = 'Chưa thực hiện được thao tác trên trình duyệt: lượt này không có bằng chứng từ MCP/VisibleBrowser/BrowserDebug/OpenBrowser, nên Winter chặn câu trả lời để tránh báo sai. Hãy bật chrome-devtools MCP hoặc dùng VisibleBrowser để Winter gọi đúng browser tool.';
2555
+ }
2556
+
2324
2557
  if (content) {
2325
2558
  this.printAssistantAnswer(content, startedAt, totalUsage);
2326
2559
  return content;
@@ -2341,6 +2574,9 @@ ${colors.reset}
2341
2574
  });
2342
2575
  this.addUsage(totalUsage, response.usage);
2343
2576
  content = response.choices?.[0]?.message?.content || '';
2577
+ if (validation.browserInteraction && !validation.hasBrowserEvidence && this.detectFakeCompletion(content)) {
2578
+ content = 'Chưa thực hiện được thao tác trên trình duyệt: lượt này không có bằng chứng từ MCP/VisibleBrowser/BrowserDebug/OpenBrowser, nên Winter chặn câu trả lời để tránh báo sai. Hãy bật chrome-devtools MCP hoặc dùng VisibleBrowser để Winter gọi đúng browser tool.';
2579
+ }
2344
2580
  if (content) {
2345
2581
  this.printAssistantAnswer(content, startedAt, totalUsage);
2346
2582
  }
@@ -2747,7 +2983,7 @@ ${colors.reset}
2747
2983
  messages.push({ role: 'system', content: toolHint });
2748
2984
  }
2749
2985
 
2750
- const { finalContent, usedMutatingTools } = await this.runConversation(messages, 'Thinking', tools);
2986
+ const { finalContent, usedMutatingTools, autoVerified } = await this.runConversation(messages, 'Thinking', tools);
2751
2987
 
2752
2988
  const allToolCalls = [];
2753
2989
  for (const msg of messages) {
@@ -2772,7 +3008,7 @@ ${colors.reset}
2772
3008
  });
2773
3009
 
2774
3010
  // Tự động verify: nếu AI đã dùng tools (sửa code), chạy test/build.
2775
- if (finalContent && this.shouldAutoVerifyAfterTools(message, usedMutatingTools)) {
3011
+ if (!autoVerified && finalContent && this.shouldAutoVerifyAfterTools(message, usedMutatingTools)) {
2776
3012
  const sessionContext = this.session?.getContext?.() || {};
2777
3013
  const profile = String(sessionContext.workflowProfile || 'general');
2778
3014
  const amplifier = sessionContext.smallModelAmplifier || {};
@@ -2823,7 +3059,7 @@ ${colors.reset}
2823
3059
  ].filter(Boolean).join('\n');
2824
3060
 
2825
3061
  const amplifier = buildSmallModelAmplification({
2826
- modelTier: this.ai?._modelTier || 'medium',
3062
+ modelTier: this.getActiveModelTier(),
2827
3063
  workflowProfile: workflow.profile,
2828
3064
  depth: workflow.depth,
2829
3065
  });
@@ -2973,35 +3209,10 @@ Do NOT stop until all errors are resolved.`;
2973
3209
  }
2974
3210
 
2975
3211
  async runAgent(role, task) {
2976
- const agentDefinition = await this.agentRegistry.get(role || 'general');
2977
- const context = await this.getProjectContext(task);
2978
- const messages = [
2979
- { role: 'system', content: this.getAgentDefinitionSystemPrompt(agentDefinition, context) }
2980
- ];
2981
-
2982
- const promptHistory = this.getCompressedPromptHistory({
2983
- limit: 40,
2984
- keepRecent: 16,
2985
- maxTotalChars: 16000,
2986
- });
2987
- if (promptHistory.summary) {
2988
- messages.push({ role: 'system', content: `Compressed prior conversation:\n${promptHistory.summary}` });
2989
- }
2990
- for (const entry of promptHistory.entries) {
2991
- messages.push({ role: entry.role, content: entry.content });
2992
- }
2993
-
2994
- messages.push({ role: 'user', content: `Task: ${task}` });
2995
-
2996
- const agentTools = this.getAgentToolsForDefinition(agentDefinition);
2997
- const { finalContent, usedMutatingTools } = await this.runConversation(messages, `Subagent [${agentDefinition.id}]`, agentTools);
2998
-
2999
- await this.session.addToHistory({ role: 'user', content: `[subagent:${agentDefinition.id}] ${task}` });
3000
- await this.session.addToHistory({ role: 'assistant', content: finalContent });
3001
-
3002
- if (finalContent && this.shouldAutoVerifyAfterTools(task, usedMutatingTools)) {
3003
- await this.verifyAndHeal(messages, agentTools, 2);
3004
- }
3212
+ const result = await this.tools.agentTool.run(task, { role: role || 'general' });
3213
+ await this.session.addToHistory({ role: 'user', content: `[subagent:${result.role || role || 'general'}] ${task}` });
3214
+ await this.session.addToHistory({ role: 'assistant', content: result.finalContent || result.summary || result.error || '' });
3215
+ return result;
3005
3216
  }
3006
3217
 
3007
3218
  async listAgentDefinitions() {
@@ -3278,6 +3489,14 @@ Light mode enabled for safety. Heavy codebase, graph, and git context are skippe
3278
3489
  await this.session.replaceMemory('[Required local resources]', requiredLocalResources, 'resource');
3279
3490
  }
3280
3491
 
3492
+ const projectInstructionFiles = await this.readProjectInstructionFiles();
3493
+ const resourceApplicationProfile = await this.contextLoader.getResourceApplicationProfile({ projectInstructionFiles });
3494
+ if (resourceApplicationProfile) {
3495
+ await this.session.updateContext('resourceApplicationProfile', resourceApplicationProfile);
3496
+ await this.session.replaceMemory('[Auto-loaded resource application profile]', resourceApplicationProfile, 'resource');
3497
+ this.startupNotice('resource profile loaded');
3498
+ }
3499
+
3281
3500
  const skillSnapshot = await this.inferStartupSkills();
3282
3501
  await this.session.updateContext('availableSkillCatalog', skillSnapshot.availableSkills);
3283
3502
  await this.session.updateContext('activeSkills', skillSnapshot.activeSkills);
@@ -3289,43 +3508,7 @@ Light mode enabled for safety. Heavy codebase, graph, and git context are skippe
3289
3508
  }
3290
3509
 
3291
3510
  async inferStartupSkills() {
3292
- const catalog = await this.getStartupSkillCatalog();
3293
- const signals = await this.getProjectSignals();
3294
- const normalizedSignals = new Set(signals.map(value => value.toLowerCase()));
3295
-
3296
- const hasAny = (...items) => items.some(item => normalizedSignals.has(item));
3297
- const activeSkills = new Set([
3298
- 'coding',
3299
- 'debug',
3300
- 'refactor',
3301
- 'test',
3302
- ]);
3303
-
3304
- if (hasAny('react', 'next', 'nextjs', 'tsx', 'jsx', 'vue', 'svelte', 'vite')) {
3305
- ['vercel-react-best-practices', 'web-design-guidelines', 'frontend-design', 'design'].forEach(skill => activeSkills.add(skill));
3306
- }
3307
-
3308
- if (hasAny('design', 'ui', 'ux', 'css', 'tailwind', 'styled-components', 'scss', 'style', 'component')) {
3309
- ['web-design-guidelines', 'frontend-design', 'design'].forEach(skill => activeSkills.add(skill));
3310
- }
3311
-
3312
- if (hasAny('claude', 'agent', 'mcp', 'plugin', 'skill', 'automation', 'workflow')) {
3313
- ['skill-creator', 'claude-automation-recommender', 'claude-md-improver', 'agent-development', 'hook-development', 'command-development', 'plugin-dev'].forEach(skill => activeSkills.add(skill));
3314
- }
3315
-
3316
- if (hasAny('docs', 'markdown', 'md', 'readme', 'documentation')) {
3317
- ['claude-md-improver', 'docs', 'writing-rules'].forEach(skill => activeSkills.add(skill));
3318
- }
3319
-
3320
- if (hasAny('figma', 'design-md', 'brand', 'brand-guidelines', 'style-guide')) {
3321
- ['vibefigma', 'web-design-guidelines'].forEach(skill => activeSkills.add(skill));
3322
- }
3323
-
3324
- const filtered = [...activeSkills].filter(skill => catalog.has(skill));
3325
- return {
3326
- availableSkills: [...catalog],
3327
- activeSkills: filtered,
3328
- };
3511
+ return this.contextLoader.inferStartupSkills();
3329
3512
  }
3330
3513
 
3331
3514
  async getStartupSkillCatalog() {
@@ -3378,6 +3561,8 @@ Light mode enabled for safety. Heavy codebase, graph, and git context are skippe
3378
3561
  const [action, ...rest] = args;
3379
3562
  const config = await this.config.load();
3380
3563
  config.mcp = config.mcp || { servers: [] };
3564
+ config.permissions = config.permissions || { allowlist: {} };
3565
+ config.permissions.allowlist = config.permissions.allowlist || { tools: [], commands: [], mcpServers: [] };
3381
3566
 
3382
3567
  switch (action) {
3383
3568
  case undefined:
@@ -3410,10 +3595,33 @@ Light mode enabled for safety. Heavy codebase, graph, and git context are skippe
3410
3595
  }
3411
3596
  config.mcp.servers = (config.mcp.servers || []).filter(server => server.name !== name);
3412
3597
  config.mcp.servers.push({ name, command, args: parsedArgs, enabled: true });
3598
+ config.permissions.allowlist.mcpServers = [...new Set([...(config.permissions.allowlist.mcpServers || []), name])];
3413
3599
  await this.config.save(config);
3414
3600
  console.log(`${colors.green}✓ Added MCP server: ${name}${colors.reset}`);
3415
3601
  break;
3416
3602
  }
3603
+ case 'preset':
3604
+ case 'install': {
3605
+ const [presetName, ...presetOptions] = rest;
3606
+ if (!presetName) {
3607
+ console.log(`${colors.yellow}Usage: /mcp preset <chrome-devtools> [--isolated] [--headless] [--browser-url <url>]${colors.reset}`);
3608
+ break;
3609
+ }
3610
+ try {
3611
+ const server = getMcpPreset(presetName, presetOptions);
3612
+ upsertMcpServer(config, server);
3613
+ await this.config.save(config);
3614
+ console.log(`${colors.green}OK Installed MCP preset: ${server.name}${colors.reset}`);
3615
+ console.log(` ${colors.dim}${server.command} ${server.args.join(' ')}${colors.reset}`);
3616
+ if (!server.args.includes('--headless')) {
3617
+ console.log(` ${colors.dim}Visible Chrome mode: enabled. Use --headless only for background browser runs.${colors.reset}`);
3618
+ }
3619
+ console.log(` ${colors.dim}Inspect tools with: /mcp tools ${server.name}${colors.reset}`);
3620
+ } catch (error) {
3621
+ console.log(`${colors.red}${error.message}${colors.reset}`);
3622
+ }
3623
+ break;
3624
+ }
3417
3625
  case 'remove': {
3418
3626
  const name = rest[0];
3419
3627
  if (!name) {
@@ -3421,6 +3629,7 @@ Light mode enabled for safety. Heavy codebase, graph, and git context are skippe
3421
3629
  break;
3422
3630
  }
3423
3631
  config.mcp.servers = (config.mcp.servers || []).filter(server => server.name !== name);
3632
+ config.permissions.allowlist.mcpServers = (config.permissions.allowlist.mcpServers || []).filter(server => server !== name);
3424
3633
  await this.config.save(config);
3425
3634
  console.log(`${colors.green}✓ Removed MCP server: ${name}${colors.reset}`);
3426
3635
  break;
@@ -3435,8 +3644,37 @@ Light mode enabled for safety. Heavy codebase, graph, and git context are skippe
3435
3644
  console.log(`${colors.green}✓ MCP server allowed: ${name}${colors.reset}`);
3436
3645
  break;
3437
3646
  }
3647
+ case 'tools': {
3648
+ const name = rest[0];
3649
+ if (!name) {
3650
+ console.log(`${colors.yellow}Usage: /mcp tools <name>${colors.reset}`);
3651
+ break;
3652
+ }
3653
+ const server = (config.mcp.servers || []).find(item => item.name === name && item.enabled !== false);
3654
+ if (!server) {
3655
+ console.log(`${colors.red}MCP server not configured or disabled: ${name}${colors.reset}`);
3656
+ break;
3657
+ }
3658
+ const client = new MCPClient(server);
3659
+ try {
3660
+ const tools = await client.listTools();
3661
+ console.log(`${colors.cyan}MCP Tools: ${name}${colors.reset}`);
3662
+ if (!tools.length) {
3663
+ console.log(` ${colors.dim}No tools reported.${colors.reset}`);
3664
+ }
3665
+ tools.forEach(tool => {
3666
+ const description = tool.description ? ` - ${tool.description}` : '';
3667
+ console.log(` ${colors.green}${tool.name}${colors.reset}${description}`);
3668
+ });
3669
+ } catch (error) {
3670
+ console.log(`${colors.red}Failed to list MCP tools: ${error.message}${colors.reset}`);
3671
+ } finally {
3672
+ await client.close();
3673
+ }
3674
+ break;
3675
+ }
3438
3676
  default:
3439
- console.log(`${colors.yellow}Usage: /mcp <list|add|remove|allow>${colors.reset}`);
3677
+ console.log(`${colors.yellow}Usage: /mcp <list|add|preset|install|remove|allow|tools>${colors.reset}`);
3440
3678
  }
3441
3679
  }
3442
3680
 
@@ -3450,6 +3688,9 @@ Light mode enabled for safety. Heavy codebase, graph, and git context are skippe
3450
3688
  case undefined:
3451
3689
  case 'list':
3452
3690
  console.log(`${colors.cyan}Permission Allowlist:${colors.reset}`);
3691
+ console.log(` Full access: ${config.sandbox?.enabled === false ? 'on' : 'off'}`);
3692
+ console.log(` Sandbox enabled: ${config.sandbox?.enabled !== false}`);
3693
+ console.log(` Restrict workspace: ${config.sandbox?.restrictToWorkspace !== false}`);
3453
3694
  console.log(` Tools: ${(config.permissions.allowlist.tools || []).join(', ') || 'none'}`);
3454
3695
  console.log(` Commands: ${(config.permissions.allowlist.commands || []).join(', ') || 'none'}`);
3455
3696
  console.log(` MCP Servers: ${(config.permissions.allowlist.mcpServers || []).join(', ') || 'none'}`);
@@ -3476,8 +3717,25 @@ Light mode enabled for safety. Heavy codebase, graph, and git context are skippe
3476
3717
  console.log(`${colors.green}✓ Updated prompt policy${colors.reset}`);
3477
3718
  break;
3478
3719
  }
3720
+ case 'full': {
3721
+ const value = String(rest[0] || 'on').toLowerCase();
3722
+ const enabled = !(value === 'off' || value === 'false' || value === '0' || value === 'no');
3723
+ if (typeof this.config.setFullAccess === 'function') {
3724
+ await this.config.setFullAccess(enabled);
3725
+ } else {
3726
+ config.sandbox = {
3727
+ ...(config.sandbox || {}),
3728
+ enabled: !enabled,
3729
+ restrictToWorkspace: !enabled,
3730
+ };
3731
+ config.permissions.promptByDefault = !enabled;
3732
+ await this.config.save(config);
3733
+ }
3734
+ console.log(`${colors.green}Full access ${enabled ? 'enabled' : 'disabled'}${colors.reset}`);
3735
+ break;
3736
+ }
3479
3737
  default:
3480
- console.log(`${colors.yellow}Usage: /permissions <list|allow|prompt>${colors.reset}`);
3738
+ console.log(`${colors.yellow}Usage: /permissions <list|allow|prompt|full>${colors.reset}`);
3481
3739
  }
3482
3740
  }
3483
3741