@howlil/ez-agents 3.1.0 → 3.4.2

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 (110) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +288 -718
  3. package/bin/install.js +438 -71
  4. package/commands/ez/auth.md +87 -0
  5. package/commands/ez/join-discord.md +18 -18
  6. package/ez-agents/bin/ez-tools.cjs +120 -2
  7. package/ez-agents/bin/lib/assistant-adapter.cjs +264 -205
  8. package/ez-agents/bin/lib/audit-exec.cjs +26 -9
  9. package/ez-agents/bin/lib/auth.cjs +2 -1
  10. package/ez-agents/bin/lib/circuit-breaker.cjs +118 -118
  11. package/ez-agents/bin/lib/commands.cjs +42 -23
  12. package/ez-agents/bin/lib/config.cjs +190 -183
  13. package/ez-agents/bin/lib/core.cjs +42 -25
  14. package/ez-agents/bin/lib/file-lock.cjs +236 -236
  15. package/ez-agents/bin/lib/frontmatter.cjs +299 -299
  16. package/ez-agents/bin/lib/fs-utils.cjs +153 -153
  17. package/ez-agents/bin/lib/git-utils.cjs +203 -203
  18. package/ez-agents/bin/lib/health-check.cjs +2 -3
  19. package/ez-agents/bin/lib/index.cjs +113 -113
  20. package/ez-agents/bin/lib/init.cjs +757 -710
  21. package/ez-agents/bin/lib/logger.cjs +52 -15
  22. package/ez-agents/bin/lib/milestone.cjs +241 -241
  23. package/ez-agents/bin/lib/model-provider.cjs +241 -146
  24. package/ez-agents/bin/lib/phase.cjs +925 -908
  25. package/ez-agents/bin/lib/planning-write.cjs +107 -0
  26. package/ez-agents/bin/lib/retry.cjs +119 -119
  27. package/ez-agents/bin/lib/roadmap.cjs +306 -305
  28. package/ez-agents/bin/lib/safe-exec.cjs +91 -5
  29. package/ez-agents/bin/lib/safe-path.cjs +130 -130
  30. package/ez-agents/bin/lib/state.cjs +736 -721
  31. package/ez-agents/bin/lib/temp-file.cjs +239 -239
  32. package/ez-agents/bin/lib/template.cjs +223 -222
  33. package/ez-agents/bin/lib/test-file-lock.cjs +112 -112
  34. package/ez-agents/bin/lib/test-graceful.cjs +93 -93
  35. package/ez-agents/bin/lib/test-logger.cjs +60 -60
  36. package/ez-agents/bin/lib/test-safe-exec.cjs +38 -38
  37. package/ez-agents/bin/lib/test-safe-path.cjs +33 -33
  38. package/ez-agents/bin/lib/test-temp-file.cjs +125 -125
  39. package/ez-agents/bin/lib/timeout-exec.cjs +63 -62
  40. package/ez-agents/bin/lib/verify.cjs +69 -26
  41. package/ez-agents/references/checkpoints.md +776 -776
  42. package/ez-agents/references/continuation-format.md +249 -249
  43. package/ez-agents/references/questioning.md +162 -162
  44. package/ez-agents/references/tdd.md +263 -263
  45. package/ez-agents/templates/codebase/concerns.md +310 -310
  46. package/ez-agents/templates/codebase/conventions.md +307 -307
  47. package/ez-agents/templates/codebase/integrations.md +280 -280
  48. package/ez-agents/templates/codebase/stack.md +186 -186
  49. package/ez-agents/templates/codebase/testing.md +480 -480
  50. package/ez-agents/templates/config.json +37 -37
  51. package/ez-agents/templates/continue-here.md +78 -78
  52. package/ez-agents/templates/milestone-archive.md +123 -123
  53. package/ez-agents/templates/milestone.md +115 -115
  54. package/ez-agents/templates/requirements.md +231 -231
  55. package/ez-agents/templates/research-project/ARCHITECTURE.md +204 -204
  56. package/ez-agents/templates/research-project/FEATURES.md +147 -147
  57. package/ez-agents/templates/research-project/PITFALLS.md +200 -200
  58. package/ez-agents/templates/research-project/STACK.md +120 -120
  59. package/ez-agents/templates/research-project/SUMMARY.md +170 -170
  60. package/ez-agents/templates/retrospective.md +54 -54
  61. package/ez-agents/templates/roadmap.md +202 -202
  62. package/ez-agents/templates/summary-minimal.md +41 -41
  63. package/ez-agents/templates/summary-standard.md +48 -48
  64. package/ez-agents/templates/summary.md +248 -248
  65. package/ez-agents/templates/user-setup.md +311 -311
  66. package/ez-agents/templates/verification-report.md +322 -322
  67. package/ez-agents/workflows/add-phase.md +112 -112
  68. package/ez-agents/workflows/add-tests.md +351 -351
  69. package/ez-agents/workflows/add-todo.md +158 -158
  70. package/ez-agents/workflows/audit-milestone.md +332 -332
  71. package/ez-agents/workflows/autonomous.md +743 -743
  72. package/ez-agents/workflows/check-todos.md +177 -177
  73. package/ez-agents/workflows/cleanup.md +152 -152
  74. package/ez-agents/workflows/complete-milestone.md +766 -766
  75. package/ez-agents/workflows/diagnose-issues.md +219 -219
  76. package/ez-agents/workflows/discovery-phase.md +289 -289
  77. package/ez-agents/workflows/discuss-phase.md +762 -762
  78. package/ez-agents/workflows/execute-phase.md +468 -468
  79. package/ez-agents/workflows/execute-plan.md +483 -483
  80. package/ez-agents/workflows/health.md +159 -159
  81. package/ez-agents/workflows/help.md +492 -492
  82. package/ez-agents/workflows/insert-phase.md +130 -130
  83. package/ez-agents/workflows/list-phase-assumptions.md +178 -178
  84. package/ez-agents/workflows/map-codebase.md +316 -316
  85. package/ez-agents/workflows/new-milestone.md +384 -384
  86. package/ez-agents/workflows/new-project.md +1113 -1111
  87. package/ez-agents/workflows/node-repair.md +92 -92
  88. package/ez-agents/workflows/pause-work.md +122 -122
  89. package/ez-agents/workflows/plan-milestone-gaps.md +274 -274
  90. package/ez-agents/workflows/plan-phase.md +651 -651
  91. package/ez-agents/workflows/progress.md +382 -382
  92. package/ez-agents/workflows/quick.md +610 -610
  93. package/ez-agents/workflows/remove-phase.md +155 -155
  94. package/ez-agents/workflows/research-phase.md +74 -74
  95. package/ez-agents/workflows/resume-project.md +307 -307
  96. package/ez-agents/workflows/set-profile.md +81 -81
  97. package/ez-agents/workflows/settings.md +242 -242
  98. package/ez-agents/workflows/stats.md +57 -57
  99. package/ez-agents/workflows/transition.md +544 -544
  100. package/ez-agents/workflows/ui-phase.md +290 -290
  101. package/ez-agents/workflows/ui-review.md +157 -157
  102. package/ez-agents/workflows/update.md +320 -320
  103. package/ez-agents/workflows/validate-phase.md +167 -167
  104. package/ez-agents/workflows/verify-phase.md +243 -243
  105. package/ez-agents/workflows/verify-work.md +584 -584
  106. package/package.json +2 -3
  107. package/scripts/build-hooks.js +43 -43
  108. package/scripts/fix-qwen-installation.js +144 -0
  109. package/scripts/run-tests.cjs +29 -29
  110. package/README.zh-CN.md +0 -702
package/bin/install.js CHANGED
@@ -17,8 +17,11 @@ const reset = '\x1b[0m';
17
17
  const EZ_CODEX_MARKER = '# EZ_Agents Agent Configuration \u2014 managed by ez-agents installer';
18
18
 
19
19
  // Copilot instructions marker constants
20
- const EZ_COPILOT_INSTRUCTIONS_MARKER = '<!-- EZ_Agents Configuration \u2014 managed by ez-agents installer -->';
21
- const EZ_COPILOT_INSTRUCTIONS_CLOSE_MARKER = '<!-- /EZ_Agents Configuration -->';
20
+ const EZ_COPILOT_INSTRUCTIONS_MARKER = '<!-- EZ_AGENTS Configuration \u2014 managed by ez-agents installer -->';
21
+ const EZ_COPILOT_INSTRUCTIONS_CLOSE_MARKER = '<!-- /EZ_AGENTS Configuration -->';
22
+ // Legacy markers kept for backward compatibility with older installs.
23
+ const LEGACY_EZ_COPILOT_INSTRUCTIONS_MARKER = '<!-- EZ_Agents Configuration \u2014 managed by ez-agents installer -->';
24
+ const LEGACY_EZ_COPILOT_INSTRUCTIONS_CLOSE_MARKER = '<!-- /EZ_Agents Configuration -->';
22
25
 
23
26
  const CODEX_AGENT_SANDBOX = {
24
27
  'ez-executor': 'workspace-write',
@@ -63,6 +66,8 @@ const hasClaude = args.includes('--claude');
63
66
  const hasGemini = args.includes('--gemini');
64
67
  const hasCodex = args.includes('--codex');
65
68
  const hasCopilot = args.includes('--copilot');
69
+ const hasQwen = args.includes('--qwen');
70
+ const hasKimi = args.includes('--kimi');
66
71
  const hasBoth = args.includes('--both'); // Legacy flag, keeps working
67
72
  const hasAll = args.includes('--all');
68
73
  const hasUninstall = args.includes('--uninstall') || args.includes('-u');
@@ -70,7 +75,7 @@ const hasUninstall = args.includes('--uninstall') || args.includes('-u');
70
75
  // Runtime selection - can be set by flags or interactive prompt
71
76
  let selectedRuntimes = [];
72
77
  if (hasAll) {
73
- selectedRuntimes = ['claude', 'opencode', 'gemini', 'codex', 'copilot'];
78
+ selectedRuntimes = ['claude', 'opencode', 'gemini', 'codex', 'copilot', 'qwen', 'kimi'];
74
79
  } else if (hasBoth) {
75
80
  selectedRuntimes = ['claude', 'opencode'];
76
81
  } else {
@@ -79,6 +84,8 @@ if (hasAll) {
79
84
  if (hasGemini) selectedRuntimes.push('gemini');
80
85
  if (hasCodex) selectedRuntimes.push('codex');
81
86
  if (hasCopilot) selectedRuntimes.push('copilot');
87
+ if (hasQwen) selectedRuntimes.push('qwen');
88
+ if (hasKimi) selectedRuntimes.push('kimi');
82
89
  }
83
90
 
84
91
  // WSL + Windows Node.js detection
@@ -140,13 +147,15 @@ function getDirName(runtime) {
140
147
  if (runtime === 'opencode') return '.opencode';
141
148
  if (runtime === 'gemini') return '.gemini';
142
149
  if (runtime === 'codex') return '.codex';
150
+ if (runtime === 'qwen') return '.qwen';
151
+ if (runtime === 'kimi') return '.kimi';
143
152
  return '.claude';
144
153
  }
145
154
 
146
155
  /**
147
156
  * Get the config directory path relative to home directory for a runtime
148
157
  * Used for templating hooks that use path.join(homeDir, '<configDir>', ...)
149
- * @param {string} runtime - 'claude', 'opencode', 'gemini', 'codex', or 'copilot'
158
+ * @param {string} runtime - 'claude', 'opencode', 'gemini', 'codex', 'copilot', or 'qwen'
150
159
  * @param {boolean} isGlobal - Whether this is a global install
151
160
  */
152
161
  function getConfigDirFromHome(runtime, isGlobal) {
@@ -163,6 +172,8 @@ function getConfigDirFromHome(runtime, isGlobal) {
163
172
  }
164
173
  if (runtime === 'gemini') return "'.gemini'";
165
174
  if (runtime === 'codex') return "'.codex'";
175
+ if (runtime === 'qwen') return "'.qwen'";
176
+ if (runtime === 'kimi') return "'.kimi'";
166
177
  return "'.claude'";
167
178
  }
168
179
 
@@ -237,7 +248,29 @@ function getGlobalDir(runtime, explicitDir = null) {
237
248
  }
238
249
  return path.join(os.homedir(), '.copilot');
239
250
  }
240
-
251
+
252
+ if (runtime === 'qwen') {
253
+ // Qwen Code: --config-dir > QWEN_CONFIG_DIR > ~/.qwen
254
+ if (explicitDir) {
255
+ return expandTilde(explicitDir);
256
+ }
257
+ if (process.env.QWEN_CONFIG_DIR) {
258
+ return expandTilde(process.env.QWEN_CONFIG_DIR);
259
+ }
260
+ return path.join(os.homedir(), '.qwen');
261
+ }
262
+
263
+ if (runtime === 'kimi') {
264
+ // Kimi Code: --config-dir > KIMI_CONFIG_DIR > ~/.kimi
265
+ if (explicitDir) {
266
+ return expandTilde(explicitDir);
267
+ }
268
+ if (process.env.KIMI_CONFIG_DIR) {
269
+ return expandTilde(process.env.KIMI_CONFIG_DIR);
270
+ }
271
+ return path.join(os.homedir(), '.kimi');
272
+ }
273
+
241
274
  // Claude Code: --config-dir > CLAUDE_CONFIG_DIR > ~/.claude
242
275
  if (explicitDir) {
243
276
  return expandTilde(explicitDir);
@@ -257,12 +290,13 @@ const banner = '\n' +
257
290
  ' ╚══════╝╚═╝ ╚═╝' + reset + '\n' +
258
291
  '\n' +
259
292
  ' EZ_Agents ' + dim + 'v' + pkg.version + reset + '\n' +
260
- ' ' + dim + 'Multi-Model Edition' + reset + '\n' +
293
+ ' Multi-Model Edition' + reset + '\n' +
261
294
  ' A meta-prompting, context engineering and spec-driven\n' +
262
295
  ' development system for Claude Code, OpenCode, Gemini, Codex, Copilot,\n' +
263
- ' with multi-model support (Qwen, Kimi, OpenAI, Anthropic).\n' +
296
+ ' Qwen Code, Kimi Code, with multi-model support.\n' +
264
297
  ' Original by TÂCHES | Multi-Model Fork by @howlil.\n';
265
298
 
299
+
266
300
  // Parse --config-dir argument
267
301
  function parseConfigDirArg() {
268
302
  const configDirIndex = args.findIndex(arg => arg === '--config-dir' || arg === '-c');
@@ -289,6 +323,7 @@ function parseConfigDirArg() {
289
323
  }
290
324
  const explicitConfigDir = parseConfigDirArg();
291
325
  const hasHelp = args.includes('--help') || args.includes('-h');
326
+ const hasVersion = args.includes('--version') || args.includes('-v');
292
327
  const forceStatusline = args.includes('--force-statusline');
293
328
 
294
329
  console.log(banner);
@@ -297,9 +332,59 @@ if (hasUninstall) {
297
332
  console.log(' Mode: Uninstall\n');
298
333
  }
299
334
 
335
+ // Show version if requested
336
+ if (hasVersion) {
337
+ console.log(` v${pkg.version}\n`);
338
+ process.exit(0);
339
+ }
340
+
300
341
  // Show help if requested
301
342
  if (hasHelp) {
302
- console.log(` ${yellow}Usage:${reset} npx ez-agents [options]\n\n ${yellow}Options:${reset}\n ${cyan}-g, --global${reset} Install globally (to config directory)\n ${cyan}-l, --local${reset} Install locally (to current directory)\n ${cyan}--claude${reset} Install for Claude Code only\n ${cyan}--opencode${reset} Install for OpenCode only\n ${cyan}--gemini${reset} Install for Gemini only\n ${cyan}--codex${reset} Install for Codex only\n ${cyan}--copilot${reset} Install for Copilot only\n ${cyan}--all${reset} Install for all runtimes\n ${cyan}-u, --uninstall${reset} Uninstall EZ_Agents (remove all EZ_Agents files)\n ${cyan}-c, --config-dir <path>${reset} Specify custom config directory\n ${cyan}-h, --help${reset} Show this help message\n ${cyan}--force-statusline${reset} Replace existing statusline config\n\n ${yellow}Examples:${reset}\n ${dim}# Interactive install (prompts for runtime and location)${reset}\n npx ez-agents\n\n ${dim}# Install for Claude Code globally${reset}\n npx ez-agents --claude --global\n\n ${dim}# Install for all runtimes globally${reset}\n npx ez-agents --all --global\n\n ${dim}# Uninstall EZ_Agents globally${reset}\n npx ez-agents --all --global --uninstall\n\n ${yellow}Notes:${reset}\n The --config-dir option is useful when you have multiple configurations.\n It takes priority over CLAUDE_CONFIG_DIR / GEMINI_CONFIG_DIR / CODEX_HOME / COPILOT_CONFIG_DIR environment variables.\n\n ${yellow}Model Providers:${reset}\n Qwen, Kimi, OpenAI, and Anthropic are model providers configured in settings.json,\n not separate CLI runtimes. See README.md for model configuration.\n`);
343
+ console.log(` ${yellow}Usage:${reset} npx ez-agents [options]
344
+
345
+ ${yellow}Options:${reset}
346
+ ${cyan}-g, --global${reset} Install globally (to config directory)
347
+ ${cyan}-l, --local${reset} Install locally (to current directory)
348
+ ${cyan}--claude${reset} Install for Claude Code only
349
+ ${cyan}--opencode${reset} Install for OpenCode only
350
+ ${cyan}--gemini${reset} Install for Gemini only
351
+ ${cyan}--codex${reset} Install for Codex only
352
+ ${cyan}--copilot${reset} Install for Copilot only
353
+ ${cyan}--qwen${reset} Install for Qwen Code only
354
+ ${cyan}--kimi${reset} Install for Kimi Code only
355
+ ${cyan}--all${reset} Install for all runtimes
356
+ ${cyan}-u, --uninstall${reset} Uninstall EZ_Agents (remove all EZ_Agents files)
357
+ ${cyan}-c, --config-dir <path>${reset} Specify custom config directory
358
+ ${cyan}-h, --help${reset} Show this help message
359
+ ${cyan}--force-statusline${reset} Replace existing statusline config
360
+
361
+ ${yellow}Examples:${reset}
362
+ ${dim}# Interactive install (prompts for runtime and location)${reset}
363
+ npx ez-agents
364
+
365
+ ${dim}# Install for Claude Code globally${reset}
366
+ npx ez-agents --claude --global
367
+
368
+ ${dim}# Install for Qwen Code globally${reset}
369
+ npx ez-agents --qwen --global
370
+
371
+ ${dim}# Install for Kimi Code globally${reset}
372
+ npx ez-agents --kimi --global
373
+
374
+ ${dim}# Install for all runtimes globally${reset}
375
+ npx ez-agents --all --global
376
+
377
+ ${dim}# Uninstall EZ_Agents globally${reset}
378
+ npx ez-agents --all --global --uninstall
379
+
380
+ ${yellow}Notes:${reset}
381
+ The --config-dir option is useful when you have multiple configurations.
382
+ It takes priority over CLAUDE_CONFIG_DIR / GEMINI_CONFIG_DIR / CODEX_HOME / COPILOT_CONFIG_DIR / QWEN_CONFIG_DIR / KIMI_CONFIG_DIR environment variables.
383
+
384
+ ${yellow}Model Providers:${reset}
385
+ OpenAI and Anthropic are model providers configured in settings.json,
386
+ not separate CLI runtimes. See README.md for model configuration.
387
+ `);
303
388
  process.exit(0);
304
389
  }
305
390
 
@@ -717,6 +802,52 @@ function convertClaudeCommandToCodexSkill(content, skillName) {
717
802
  return `---\nname: ${yamlQuote(skillName)}\ndescription: ${yamlQuote(description)}\nmetadata:\n short-description: ${yamlQuote(shortDescription)}\n---\n\n${adapter}\n\n${body.trimStart()}`;
718
803
  }
719
804
 
805
+ function convertClaudeToQwenAgent(content) {
806
+ const { frontmatter, body } = extractFrontmatterAndBody(content);
807
+ if (!frontmatter) return content;
808
+
809
+ const name = extractFrontmatterField(frontmatter, 'name') || 'unknown';
810
+ const description = extractFrontmatterField(frontmatter, 'description') || '';
811
+
812
+ // Qwen Code uses simple markdown. We'll put name and description in the body.
813
+ return `# Agent: ${name}\n\n${description}\n\n${body.trimStart()}`;
814
+ }
815
+
816
+ function convertClaudeToKimiSkill(content, skillName) {
817
+ const { frontmatter, body } = extractFrontmatterAndBody(content);
818
+ if (!frontmatter) return content;
819
+
820
+ const description = extractFrontmatterField(frontmatter, 'description') || '';
821
+
822
+ // Reconstruct as simple Markdown
823
+ return `# Skill: ${skillName}\n\n${description}\n\n${body.trimStart()}`;
824
+ }
825
+
826
+ function convertClaudeToKimiAgent(content) {
827
+ const { frontmatter, body } = extractFrontmatterAndBody(content);
828
+ if (!frontmatter) return content;
829
+
830
+ const name = extractFrontmatterField(frontmatter, 'name') || 'unknown';
831
+ const description = extractFrontmatterField(frontmatter, 'description') || '';
832
+
833
+ // Kimi Code uses simple markdown
834
+ return `# Agent: ${name}\n\n${description}\n\n${body.trimStart()}`;
835
+ }
836
+
837
+ /**
838
+ * Convert a Claude command (.md) to a Qwen skill (SKILL.md).
839
+ * Strips frontmatter and formats as simple Markdown.
840
+ */
841
+ function convertClaudeToQwenSkill(content, skillName) {
842
+ const { frontmatter, body } = extractFrontmatterAndBody(content);
843
+ if (!frontmatter) return content;
844
+
845
+ const description = extractFrontmatterField(frontmatter, 'description') || '';
846
+
847
+ // Reconstruct as simple Markdown
848
+ return `# Skill: ${skillName}\n\n${description}\n\n${body.trimStart()}`;
849
+ }
850
+
720
851
  /**
721
852
  * Convert Claude Code agent markdown to Codex agent format.
722
853
  * Applies base markdown conversions, then adds a <codex_agent_role> header
@@ -860,6 +991,23 @@ function mergeCodexConfig(configPath, EZ_AgentsBlock) {
860
991
  fs.writeFileSync(configPath, content);
861
992
  }
862
993
 
994
+ function findCopilotInstructionsMarkerPair(content) {
995
+ const markerPairs = [
996
+ [EZ_COPILOT_INSTRUCTIONS_MARKER, EZ_COPILOT_INSTRUCTIONS_CLOSE_MARKER],
997
+ [LEGACY_EZ_COPILOT_INSTRUCTIONS_MARKER, LEGACY_EZ_COPILOT_INSTRUCTIONS_CLOSE_MARKER],
998
+ ];
999
+
1000
+ for (const [openMarker, closeMarker] of markerPairs) {
1001
+ const openIndex = content.indexOf(openMarker);
1002
+ const closeIndex = content.indexOf(closeMarker);
1003
+ if (openIndex !== -1 && closeIndex !== -1 && closeIndex >= openIndex) {
1004
+ return { openIndex, closeIndex, closeMarker };
1005
+ }
1006
+ }
1007
+
1008
+ return null;
1009
+ }
1010
+
863
1011
  /**
864
1012
  * Merge EZ_Agents instructions into copilot-instructions.md.
865
1013
  * Three cases: new file, existing with markers, existing without markers.
@@ -878,13 +1026,12 @@ function mergeCopilotInstructions(filePath, EZ_AgentsContent) {
878
1026
  }
879
1027
 
880
1028
  const existing = fs.readFileSync(filePath, 'utf8');
881
- const openIndex = existing.indexOf(EZ_COPILOT_INSTRUCTIONS_MARKER);
882
- const closeIndex = existing.indexOf(EZ_COPILOT_INSTRUCTIONS_CLOSE_MARKER);
1029
+ const markerMatch = findCopilotInstructionsMarkerPair(existing);
883
1030
 
884
1031
  // Case 2: Has EZ_Agents markers — replace between markers
885
- if (openIndex !== -1 && closeIndex !== -1) {
886
- const before = existing.substring(0, openIndex).trimEnd();
887
- const after = existing.substring(closeIndex + EZ_COPILOT_INSTRUCTIONS_CLOSE_MARKER.length).trimStart();
1032
+ if (markerMatch) {
1033
+ const before = existing.substring(0, markerMatch.openIndex).trimEnd();
1034
+ const after = existing.substring(markerMatch.closeIndex + markerMatch.closeMarker.length).trimStart();
888
1035
  let newContent = '';
889
1036
  if (before) newContent += before + '\n\n';
890
1037
  newContent += EZ_AgentsBlock;
@@ -906,12 +1053,11 @@ function mergeCopilotInstructions(filePath, EZ_AgentsContent) {
906
1053
  * @returns {string|null} - Cleaned content or null if empty
907
1054
  */
908
1055
  function stripEzAgentsFromCopilotInstructions(content) {
909
- const openIndex = content.indexOf(EZ_COPILOT_INSTRUCTIONS_MARKER);
910
- const closeIndex = content.indexOf(EZ_COPILOT_INSTRUCTIONS_CLOSE_MARKER);
1056
+ const markerMatch = findCopilotInstructionsMarkerPair(content);
911
1057
 
912
- if (openIndex !== -1 && closeIndex !== -1) {
913
- const before = content.substring(0, openIndex).trimEnd();
914
- const after = content.substring(closeIndex + EZ_COPILOT_INSTRUCTIONS_CLOSE_MARKER.length).trimStart();
1058
+ if (markerMatch) {
1059
+ const before = content.substring(0, markerMatch.openIndex).trimEnd();
1060
+ const after = content.substring(markerMatch.closeIndex + markerMatch.closeMarker.length).trimStart();
915
1061
  const cleaned = (before + (before && after ? '\n\n' : '') + after).trim();
916
1062
  if (!cleaned) return null;
917
1063
  return cleaned + '\n';
@@ -930,7 +1076,7 @@ function installCodexConfig(targetDir, agentsSrc) {
930
1076
  const agentsTomlDir = path.join(targetDir, 'agents');
931
1077
  fs.mkdirSync(agentsTomlDir, { recursive: true });
932
1078
 
933
- const agentEntries = fs.readdirSync(agentsSrc).filter(f => f.startsWith('EZ-') && f.endsWith('.md'));
1079
+ const agentEntries = fs.readdirSync(agentsSrc).filter(f => f.startsWith('ez-') && f.endsWith('.md'));
934
1080
  const agents = [];
935
1081
 
936
1082
  // Compute the Codex pathPrefix for replacing .claude paths
@@ -1389,6 +1535,176 @@ function copyCommandsAsCopilotSkills(srcDir, skillsDir, prefix, isGlobal = false
1389
1535
  recurse(srcDir, prefix);
1390
1536
  }
1391
1537
 
1538
+ /**
1539
+ * Copy Claude commands as Kimi Code skills — one folder per skill with SKILL.md.
1540
+ */
1541
+ function copyCommandsAsKimiSkills(srcDir, skillsDir, prefix, pathPrefix, runtime) {
1542
+ if (!fs.existsSync(srcDir)) {
1543
+ return;
1544
+ }
1545
+
1546
+ fs.mkdirSync(skillsDir, { recursive: true });
1547
+
1548
+ // Remove previous EZ Kimi skills
1549
+ const existing = fs.readdirSync(skillsDir, { withFileTypes: true });
1550
+ for (const entry of existing) {
1551
+ if (entry.isDirectory() && entry.name.startsWith(`${prefix}-`)) {
1552
+ fs.rmSync(path.join(skillsDir, entry.name), { recursive: true });
1553
+ }
1554
+ }
1555
+
1556
+ function recurse(currentSrcDir, currentPrefix) {
1557
+ const entries = fs.readdirSync(currentSrcDir, { withFileTypes: true });
1558
+
1559
+ for (const entry of entries) {
1560
+ const srcPath = path.join(currentSrcDir, entry.name);
1561
+ if (entry.isDirectory()) {
1562
+ recurse(srcPath, `${currentPrefix}-${entry.name}`);
1563
+ continue;
1564
+ }
1565
+
1566
+ if (!entry.name.endsWith('.md')) {
1567
+ continue;
1568
+ }
1569
+
1570
+ const baseName = entry.name.replace('.md', '');
1571
+ const skillName = `${currentPrefix}-${baseName}`;
1572
+ const skillDir = path.join(skillsDir, skillName);
1573
+ fs.mkdirSync(skillDir, { recursive: true });
1574
+
1575
+ let content = fs.readFileSync(srcPath, 'utf8');
1576
+ const globalClaudeRegex = /~\/\.claude\//g;
1577
+ const globalClaudeHomeRegex = /\$HOME\/\.claude\//g;
1578
+ const localClaudeRegex = /\.\/\.claude\//g;
1579
+ const kimiDirRegex = /~\/\.kimi\//g;
1580
+ content = content.replace(globalClaudeRegex, pathPrefix);
1581
+ content = content.replace(globalClaudeHomeRegex, toHomePrefix(pathPrefix));
1582
+ content = content.replace(localClaudeRegex, `./${getDirName(runtime)}/`);
1583
+ content = content.replace(kimiDirRegex, pathPrefix);
1584
+ content = processAttribution(content, getCommitAttribution(runtime));
1585
+ content = convertClaudeToKimiSkill(content, skillName);
1586
+ fs.writeFileSync(path.join(skillDir, 'SKILL.md'), content);
1587
+ }
1588
+ }
1589
+
1590
+ recurse(srcDir, prefix);
1591
+ }
1592
+
1593
+ /**
1594
+ * Copy Claude commands as Qwen Code commands — flat structure in commands/ez/.
1595
+ * Qwen Code uses ~/.qwen/commands/ez/*.md (like Claude Code ~/.claude/commands/ez/)
1596
+ */
1597
+ function copyCommandsAsQwenCommands(srcDir, commandsDir, prefix, pathPrefix, runtime) {
1598
+ if (!fs.existsSync(srcDir)) {
1599
+ return;
1600
+ }
1601
+
1602
+ fs.mkdirSync(commandsDir, { recursive: true });
1603
+
1604
+ // Remove previous EZ Qwen commands
1605
+ const existing = fs.readdirSync(commandsDir, { withFileTypes: true });
1606
+ for (const entry of existing) {
1607
+ if (entry.isFile() && entry.name.startsWith(`${prefix}-`) && entry.name.endsWith('.md')) {
1608
+ fs.rmSync(path.join(commandsDir, entry.name));
1609
+ }
1610
+ }
1611
+
1612
+ function recurse(currentSrcDir, currentPrefix) {
1613
+ const entries = fs.readdirSync(currentSrcDir, { withFileTypes: true });
1614
+
1615
+ for (const entry of entries) {
1616
+ const srcPath = path.join(currentSrcDir, entry.name);
1617
+ if (entry.isDirectory()) {
1618
+ recurse(srcPath, `${currentPrefix}-${entry.name}`);
1619
+ continue;
1620
+ }
1621
+
1622
+ if (!entry.name.endsWith('.md')) {
1623
+ continue;
1624
+ }
1625
+
1626
+ const baseName = entry.name.replace('.md', '');
1627
+ const commandName = `${currentPrefix}-${baseName}`;
1628
+ const commandFile = path.join(commandsDir, `${commandName}.md`);
1629
+
1630
+ let content = fs.readFileSync(srcPath, 'utf8');
1631
+ const globalClaudeRegex = /~\/\.claude\//g;
1632
+ const globalClaudeHomeRegex = /\$HOME\/\.claude\//g;
1633
+ const localClaudeRegex = /\.\/\.claude\//g;
1634
+ const qwenDirRegex = /~\/\.qwen\//g;
1635
+ content = content.replace(globalClaudeRegex, pathPrefix);
1636
+ content = content.replace(globalClaudeHomeRegex, toHomePrefix(pathPrefix));
1637
+ content = content.replace(localClaudeRegex, `./${getDirName(runtime)}/`);
1638
+ content = content.replace(qwenDirRegex, pathPrefix);
1639
+ content = processAttribution(content, getCommitAttribution(runtime));
1640
+
1641
+ // Qwen Code uses simple markdown commands (no SKILL.md wrapper)
1642
+ // Just copy the content as-is with path replacements
1643
+ fs.writeFileSync(commandFile, content);
1644
+ }
1645
+ }
1646
+
1647
+ recurse(srcDir, prefix);
1648
+ }
1649
+
1650
+ /**
1651
+ * Copy Claude commands as Qwen Code skills — one folder per skill with SKILL.md.
1652
+ * Qwen Code uses the same skills/ structure as Codex but with simpler format (no adapters needed).
1653
+ * @deprecated Use copyCommandsAsQwenCommands instead - Qwen Code uses commands/ez/ not skills/
1654
+ */
1655
+ function copyCommandsAsQwenSkills(srcDir, skillsDir, prefix, pathPrefix, runtime) {
1656
+ if (!fs.existsSync(srcDir)) {
1657
+ return;
1658
+ }
1659
+
1660
+ fs.mkdirSync(skillsDir, { recursive: true });
1661
+
1662
+ // Remove previous EZ Qwen skills
1663
+ const existing = fs.readdirSync(skillsDir, { withFileTypes: true });
1664
+ for (const entry of existing) {
1665
+ if (entry.isDirectory() && entry.name.startsWith(`${prefix}-`)) {
1666
+ fs.rmSync(path.join(skillsDir, entry.name), { recursive: true });
1667
+ }
1668
+ }
1669
+
1670
+ function recurse(currentSrcDir, currentPrefix) {
1671
+ const entries = fs.readdirSync(currentSrcDir, { withFileTypes: true });
1672
+
1673
+ for (const entry of entries) {
1674
+ const srcPath = path.join(currentSrcDir, entry.name);
1675
+ if (entry.isDirectory()) {
1676
+ recurse(srcPath, `${currentPrefix}-${entry.name}`);
1677
+ continue;
1678
+ }
1679
+
1680
+ if (!entry.name.endsWith('.md')) {
1681
+ continue;
1682
+ }
1683
+
1684
+ const baseName = entry.name.replace('.md', '');
1685
+ const skillName = `${currentPrefix}-${baseName}`;
1686
+ const skillDir = path.join(skillsDir, skillName);
1687
+ fs.mkdirSync(skillDir, { recursive: true });
1688
+
1689
+ let content = fs.readFileSync(srcPath, 'utf8');
1690
+ const globalClaudeRegex = /~\/\.claude\//g;
1691
+ const globalClaudeHomeRegex = /\$HOME\/\.claude\//g;
1692
+ const localClaudeRegex = /\.\/\.claude\//g;
1693
+ const qwenDirRegex = /~\/\.qwen\//g;
1694
+ content = content.replace(globalClaudeRegex, pathPrefix);
1695
+ content = content.replace(globalClaudeHomeRegex, toHomePrefix(pathPrefix));
1696
+ content = content.replace(localClaudeRegex, `./${getDirName(runtime)}/`);
1697
+ content = content.replace(qwenDirRegex, pathPrefix);
1698
+ content = processAttribution(content, getCommitAttribution(runtime));
1699
+ // Qwen Code uses simple markdown skills without frontmatter adapters
1700
+ content = convertClaudeToQwenSkill(content, skillName);
1701
+ fs.writeFileSync(path.join(skillDir, 'SKILL.md'), content);
1702
+ }
1703
+ }
1704
+
1705
+ recurse(srcDir, prefix);
1706
+ }
1707
+
1392
1708
  /**
1393
1709
  * Recursively copy directory, replacing paths in .md files
1394
1710
  * Deletes existing destDir first to remove orphaned files from previous versions
@@ -1552,6 +1868,8 @@ function uninstall(isGlobal, runtime = 'claude') {
1552
1868
  const isOpencode = runtime === 'opencode';
1553
1869
  const isCodex = runtime === 'codex';
1554
1870
  const isCopilot = runtime === 'copilot';
1871
+ const isQwen = runtime === 'qwen';
1872
+ const isKimi = runtime === 'kimi';
1555
1873
  const dirName = getDirName(runtime);
1556
1874
 
1557
1875
  // Get the target directory based on runtime and install type
@@ -1568,6 +1886,7 @@ function uninstall(isGlobal, runtime = 'claude') {
1568
1886
  if (runtime === 'gemini') runtimeLabel = 'Gemini';
1569
1887
  if (runtime === 'codex') runtimeLabel = 'Codex';
1570
1888
  if (runtime === 'copilot') runtimeLabel = 'Copilot';
1889
+ if (runtime === 'qwen') runtimeLabel = 'Qwen Code';
1571
1890
 
1572
1891
  console.log(` Uninstalling EZ_Agents from ${cyan}${runtimeLabel}${reset} at ${cyan}${locationLabel}${reset}\n`);
1573
1892
 
@@ -1594,8 +1913,8 @@ function uninstall(isGlobal, runtime = 'claude') {
1594
1913
  }
1595
1914
  console.log(` ${green}✓${reset} Removed EZ_Agents commands from command/`);
1596
1915
  }
1597
- } else if (isCodex) {
1598
- // Codex: remove skills/ez-*/SKILL.md skill directories
1916
+ } else if (isCodex || isQwen || isKimi) {
1917
+ // Codex/Qwen/Kimi: remove skills/ez-*/SKILL.md skill directories
1599
1918
  const skillsDir = path.join(targetDir, 'skills');
1600
1919
  if (fs.existsSync(skillsDir)) {
1601
1920
  let skillCount = 0;
@@ -1608,41 +1927,46 @@ function uninstall(isGlobal, runtime = 'claude') {
1608
1927
  }
1609
1928
  if (skillCount > 0) {
1610
1929
  removedCount++;
1611
- console.log(` ${green}✓${reset} Removed ${skillCount} Codex skills`);
1930
+ let runtimeLabel = 'Codex';
1931
+ if (isQwen) runtimeLabel = 'Qwen Code';
1932
+ if (isKimi) runtimeLabel = 'Kimi Code';
1933
+ console.log(` ${green}✓${reset} Removed ${skillCount} ${runtimeLabel} skills`);
1612
1934
  }
1613
1935
  }
1614
1936
 
1615
- // Codex: remove EZ agent .toml config files
1616
- const codexAgentsDir = path.join(targetDir, 'agents');
1617
- if (fs.existsSync(codexAgentsDir)) {
1618
- const tomlFiles = fs.readdirSync(codexAgentsDir);
1619
- let tomlCount = 0;
1620
- for (const file of tomlFiles) {
1621
- if (file.startsWith('ez-') && file.endsWith('.toml')) {
1622
- fs.unlinkSync(path.join(codexAgentsDir, file));
1623
- tomlCount++;
1937
+ if (isCodex) {
1938
+ // Codex: remove EZ agent .toml config files
1939
+ const codexAgentsDir = path.join(targetDir, 'agents');
1940
+ if (fs.existsSync(codexAgentsDir)) {
1941
+ const tomlFiles = fs.readdirSync(codexAgentsDir);
1942
+ let tomlCount = 0;
1943
+ for (const file of tomlFiles) {
1944
+ if (file.startsWith('ez-') && file.endsWith('.toml')) {
1945
+ fs.unlinkSync(path.join(codexAgentsDir, file));
1946
+ tomlCount++;
1947
+ }
1948
+ }
1949
+ if (tomlCount > 0) {
1950
+ removedCount++;
1951
+ console.log(` ${green}✓${reset} Removed ${tomlCount} agent .toml configs`);
1624
1952
  }
1625
1953
  }
1626
- if (tomlCount > 0) {
1627
- removedCount++;
1628
- console.log(` ${green}✓${reset} Removed ${tomlCount} agent .toml configs`);
1629
- }
1630
- }
1631
1954
 
1632
- // Codex: clean EZ sections from config.toml
1633
- const configPath = path.join(targetDir, 'config.toml');
1634
- if (fs.existsSync(configPath)) {
1635
- const content = fs.readFileSync(configPath, 'utf8');
1636
- const cleaned = stripEzAgentsFromCodexConfig(content);
1637
- if (cleaned === null) {
1638
- // File is empty after stripping — delete it
1639
- fs.unlinkSync(configPath);
1640
- removedCount++;
1641
- console.log(` ${green}✓${reset} Removed config.toml (was EZ-only)`);
1642
- } else if (cleaned !== content) {
1643
- fs.writeFileSync(configPath, cleaned);
1644
- removedCount++;
1645
- console.log(` ${green}✓${reset} Cleaned EZ_Agents sections from config.toml`);
1955
+ // Codex: clean EZ sections from config.toml
1956
+ const configPath = path.join(targetDir, 'config.toml');
1957
+ if (fs.existsSync(configPath)) {
1958
+ const content = fs.readFileSync(configPath, 'utf8');
1959
+ const cleaned = stripEzAgentsFromCodexConfig(content);
1960
+ if (cleaned === null) {
1961
+ // File is empty after stripping — delete it
1962
+ fs.unlinkSync(configPath);
1963
+ removedCount++;
1964
+ console.log(` ${green}✓${reset} Removed config.toml (was EZ-only)`);
1965
+ } else if (cleaned !== content) {
1966
+ fs.writeFileSync(configPath, cleaned);
1967
+ removedCount++;
1968
+ console.log(` ${green}✓${reset} Cleaned EZ_Agents sections from config.toml`);
1969
+ }
1646
1970
  }
1647
1971
  }
1648
1972
  } else if (isCopilot) {
@@ -2208,6 +2532,8 @@ function install(isGlobal, runtime = 'claude') {
2208
2532
  const isGemini = runtime === 'gemini';
2209
2533
  const isCodex = runtime === 'codex';
2210
2534
  const isCopilot = runtime === 'copilot';
2535
+ const isQwen = runtime === 'qwen';
2536
+ const isKimi = runtime === 'kimi';
2211
2537
  const dirName = getDirName(runtime);
2212
2538
  const src = path.join(__dirname, '..');
2213
2539
 
@@ -2233,6 +2559,8 @@ function install(isGlobal, runtime = 'claude') {
2233
2559
  if (isGemini) runtimeLabel = 'Gemini';
2234
2560
  if (isCodex) runtimeLabel = 'Codex';
2235
2561
  if (isCopilot) runtimeLabel = 'Copilot';
2562
+ if (isQwen) runtimeLabel = 'Qwen Code';
2563
+ if (isKimi) runtimeLabel = 'Kimi Code';
2236
2564
 
2237
2565
  console.log(` Installing for ${cyan}${runtimeLabel}${reset} to ${cyan}${locationLabel}${reset}\n`);
2238
2566
 
@@ -2245,7 +2573,7 @@ function install(isGlobal, runtime = 'claude') {
2245
2573
  // Clean up orphaned files from previous versions
2246
2574
  cleanupOrphanedFiles(targetDir);
2247
2575
 
2248
- // OpenCode uses command/ (flat), Codex uses skills/, Claude/Gemini use commands/ez/
2576
+ // OpenCode uses command/ (flat), Codex/Copilot/Qwen use skills/, Claude/Gemini use commands/ez/
2249
2577
  if (isOpencode) {
2250
2578
  // OpenCode: flat structure in command/ directory
2251
2579
  const commandDir = path.join(targetDir, 'command');
@@ -2260,16 +2588,41 @@ function install(isGlobal, runtime = 'claude') {
2260
2588
  } else {
2261
2589
  failures.push('command/ez-*');
2262
2590
  }
2263
- } else if (isCodex) {
2591
+ } else if (isCodex || isKimi) {
2592
+ // Codex, Kimi: skills structure in skills/ directory
2264
2593
  const skillsDir = path.join(targetDir, 'skills');
2265
2594
  const ezSrc = path.join(src, 'commands', 'ez');
2266
- copyCommandsAsCodexSkills(ezSrc, skillsDir, 'ez', pathPrefix, runtime);
2595
+ if (isKimi) {
2596
+ copyCommandsAsKimiSkills(ezSrc, skillsDir, 'ez', pathPrefix, runtime);
2597
+ } else {
2598
+ copyCommandsAsCodexSkills(ezSrc, skillsDir, 'ez', pathPrefix, runtime);
2599
+ }
2267
2600
  const installedSkillNames = listCodexSkillNames(skillsDir);
2268
2601
  if (installedSkillNames.length > 0) {
2269
2602
  console.log(` ${green}✓${reset} Installed ${installedSkillNames.length} skills to skills/`);
2270
2603
  } else {
2271
2604
  failures.push('skills/ez-*');
2272
2605
  }
2606
+ } else if (isQwen) {
2607
+ // Qwen Code: commands structure in commands/ez/ directory (like Claude Code/Gemini)
2608
+ // Qwen Code uses ~/.qwen/commands/ez/*.md, NOT ~/.qwen/skills/
2609
+ const commandsDir = path.join(targetDir, 'commands');
2610
+ const ezCommandsDir = path.join(commandsDir, 'ez');
2611
+ fs.mkdirSync(ezCommandsDir, { recursive: true });
2612
+
2613
+ const ezSrc = path.join(src, 'commands', 'ez');
2614
+ copyCommandsAsQwenCommands(ezSrc, ezCommandsDir, 'ez', pathPrefix, runtime);
2615
+
2616
+ if (fs.existsSync(ezCommandsDir)) {
2617
+ const count = fs.readdirSync(ezCommandsDir).filter(f => f.endsWith('.md')).length;
2618
+ if (count > 0) {
2619
+ console.log(` ${green}✓${reset} Installed ${count} commands to commands/ez/`);
2620
+ } else {
2621
+ failures.push('commands/ez/*.md');
2622
+ }
2623
+ } else {
2624
+ failures.push('commands/ez/*.md');
2625
+ }
2273
2626
  } else if (isCopilot) {
2274
2627
  const skillsDir = path.join(targetDir, 'skills');
2275
2628
  const ezSrc = path.join(src, 'commands', 'ez');
@@ -2300,15 +2653,6 @@ function install(isGlobal, runtime = 'claude') {
2300
2653
  }
2301
2654
  }
2302
2655
 
2303
- // Copy bin/update.js for ez-agents-update command
2304
- const updateSrc = path.join(src, 'bin', 'update.js');
2305
- if (fs.existsSync(updateSrc)) {
2306
- const updateDest = path.join(targetDir, 'bin', 'update.js');
2307
- fs.mkdirSync(path.dirname(updateDest), { recursive: true });
2308
- fs.copyFileSync(updateSrc, updateDest);
2309
- console.log(` ${green}✓${reset} Installed ez-agents-update command`);
2310
- }
2311
-
2312
2656
  // Copy ez-agents skill with path replacement
2313
2657
  const skillSrc = path.join(src, 'ez-agents');
2314
2658
  const skillDest = path.join(targetDir, 'ez-agents');
@@ -2356,6 +2700,10 @@ function install(isGlobal, runtime = 'claude') {
2356
2700
  content = convertClaudeAgentToCodexAgent(content);
2357
2701
  } else if (isCopilot) {
2358
2702
  content = convertClaudeAgentToCopilotAgent(content, isGlobal);
2703
+ } else if (isQwen) {
2704
+ content = convertClaudeToQwenAgent(content);
2705
+ } else if (isKimi) {
2706
+ content = convertClaudeToKimiAgent(content);
2359
2707
  }
2360
2708
  const destName = isCopilot ? entry.name.replace('.md', '.agent.md') : entry.name;
2361
2709
  fs.writeFileSync(path.join(agentsDest, destName), content);
@@ -2389,7 +2737,7 @@ function install(isGlobal, runtime = 'claude') {
2389
2737
  failures.push('VERSION');
2390
2738
  }
2391
2739
 
2392
- if (!isCodex && !isCopilot) {
2740
+ if (!isCodex && !isCopilot && !isQwen && !isKimi) {
2393
2741
  // Write package.json to force CommonJS mode for EZ scripts
2394
2742
  // Prevents "require is not defined" errors when project has "type": "module"
2395
2743
  // Node.js walks up looking for package.json - this stops inheritance from project
@@ -2493,6 +2841,12 @@ function install(isGlobal, runtime = 'claude') {
2493
2841
  return { settingsPath: null, settings: null, statuslineCommand: null, runtime };
2494
2842
  }
2495
2843
 
2844
+ if (isQwen || isKimi) {
2845
+ // Qwen/Kimi: no settings.json hooks, no statusline (like Codex/Copilot)
2846
+ // Skills are installed directly in skills/ez-*/SKILL.md
2847
+ return { settingsPath: null, settings: null, statuslineCommand: null, runtime };
2848
+ }
2849
+
2496
2850
  // Configure statusline and hooks in settings.json
2497
2851
  // Gemini uses AfterTool instead of PostToolUse for post-tool hooks
2498
2852
  const postToolEvent = runtime === 'gemini' ? 'AfterTool' : 'PostToolUse';
@@ -2576,8 +2930,10 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
2576
2930
  const isOpencode = runtime === 'opencode';
2577
2931
  const isCodex = runtime === 'codex';
2578
2932
  const isCopilot = runtime === 'copilot';
2933
+ const isQwen = runtime === 'qwen';
2934
+ const isKimi = runtime === 'kimi';
2579
2935
 
2580
- if (shouldInstallStatusline && !isOpencode && !isCodex && !isCopilot) {
2936
+ if (shouldInstallStatusline && !isOpencode && !isCodex && !isCopilot && !isQwen && !isKimi) {
2581
2937
  settings.statusLine = {
2582
2938
  type: 'command',
2583
2939
  command: statuslineCommand
@@ -2586,7 +2942,7 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
2586
2942
  }
2587
2943
 
2588
2944
  // Write settings when runtime supports settings.json
2589
- if (!isCodex && !isCopilot) {
2945
+ if (!isCodex && !isCopilot && !isQwen && !isKimi) {
2590
2946
  writeSettings(settingsPath, settings);
2591
2947
  }
2592
2948
 
@@ -2600,11 +2956,12 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
2600
2956
  if (runtime === 'gemini') program = 'Gemini';
2601
2957
  if (runtime === 'codex') program = 'Codex';
2602
2958
  if (runtime === 'copilot') program = 'Copilot';
2959
+ if (runtime === 'qwen') program = 'Qwen Code';
2960
+ if (runtime === 'kimi') program = 'Kimi Code';
2603
2961
 
2604
2962
  let command = '/ez:new-project';
2605
- if (runtime === 'opencode') command = '/ez-new-project';
2963
+ if (runtime === 'opencode' || runtime === 'gemini' || runtime === 'copilot' || runtime === 'qwen' || runtime === 'kimi') command = '/ez-new-project';
2606
2964
  if (runtime === 'codex') command = '$ez-new-project';
2607
- if (runtime === 'copilot') command = '/ez-new-project';
2608
2965
  console.log(`
2609
2966
  ${green}Done!${reset} Open a blank directory in ${program} and run ${cyan}${command}${reset}.
2610
2967
 
@@ -2687,9 +3044,11 @@ function promptRuntime(callback) {
2687
3044
  ${cyan}3${reset}) Gemini ${dim}(~/.gemini)${reset}
2688
3045
  ${cyan}4${reset}) Codex ${dim}(~/.codex)${reset}
2689
3046
  ${cyan}5${reset}) Copilot ${dim}(~/.copilot)${reset}
2690
- ${cyan}6${reset}) All ${dim}(all 5 runtimes above)${reset}
3047
+ ${cyan}6${reset}) Qwen Code ${dim}(~/.qwen)${reset} - Alibaba Qwen official CLI
3048
+ ${cyan}7${reset}) Kimi Code ${dim}(~/.kimi)${reset} - Moonshot Kimi official CLI
3049
+ ${cyan}8${reset}) All ${dim}(all 7 runtimes above)${reset}
2691
3050
 
2692
- ${dim}Note: Qwen, Kimi, and OpenAI are model providers configured within each runtime's settings,
3051
+ ${dim}Note: OpenAI and Anthropic are model providers configured within each runtime's settings,
2693
3052
  not separate CLI runtimes. See README.md for model configuration.${reset}
2694
3053
  `);
2695
3054
 
@@ -2697,8 +3056,12 @@ function promptRuntime(callback) {
2697
3056
  answered = true;
2698
3057
  rl.close();
2699
3058
  const choice = answer.trim() || '1';
2700
- if (choice === '6') {
2701
- callback(['claude', 'opencode', 'gemini', 'codex', 'copilot']);
3059
+ if (choice === '8') {
3060
+ callback(['claude', 'opencode', 'gemini', 'codex', 'copilot', 'qwen', 'kimi']);
3061
+ } else if (choice === '7') {
3062
+ callback(['kimi']);
3063
+ } else if (choice === '6') {
3064
+ callback(['qwen']);
2702
3065
  } else if (choice === '5') {
2703
3066
  callback(['copilot']);
2704
3067
  } else if (choice === '4') {
@@ -2862,6 +3225,10 @@ if (process.env.EZ_AGENTS_TEST_MODE) {
2862
3225
  stripEzAgentsFromCopilotInstructions,
2863
3226
  writeManifest,
2864
3227
  reportLocalPatches,
3228
+ convertClaudeToQwenSkill,
3229
+ convertClaudeToQwenAgent,
3230
+ convertClaudeToKimiSkill,
3231
+ convertClaudeToKimiAgent,
2865
3232
  };
2866
3233
  } else {
2867
3234