@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.
- package/LICENSE +21 -21
- package/README.md +288 -718
- package/bin/install.js +438 -71
- package/commands/ez/auth.md +87 -0
- package/commands/ez/join-discord.md +18 -18
- package/ez-agents/bin/ez-tools.cjs +120 -2
- package/ez-agents/bin/lib/assistant-adapter.cjs +264 -205
- package/ez-agents/bin/lib/audit-exec.cjs +26 -9
- package/ez-agents/bin/lib/auth.cjs +2 -1
- package/ez-agents/bin/lib/circuit-breaker.cjs +118 -118
- package/ez-agents/bin/lib/commands.cjs +42 -23
- package/ez-agents/bin/lib/config.cjs +190 -183
- package/ez-agents/bin/lib/core.cjs +42 -25
- package/ez-agents/bin/lib/file-lock.cjs +236 -236
- package/ez-agents/bin/lib/frontmatter.cjs +299 -299
- package/ez-agents/bin/lib/fs-utils.cjs +153 -153
- package/ez-agents/bin/lib/git-utils.cjs +203 -203
- package/ez-agents/bin/lib/health-check.cjs +2 -3
- package/ez-agents/bin/lib/index.cjs +113 -113
- package/ez-agents/bin/lib/init.cjs +757 -710
- package/ez-agents/bin/lib/logger.cjs +52 -15
- package/ez-agents/bin/lib/milestone.cjs +241 -241
- package/ez-agents/bin/lib/model-provider.cjs +241 -146
- package/ez-agents/bin/lib/phase.cjs +925 -908
- package/ez-agents/bin/lib/planning-write.cjs +107 -0
- package/ez-agents/bin/lib/retry.cjs +119 -119
- package/ez-agents/bin/lib/roadmap.cjs +306 -305
- package/ez-agents/bin/lib/safe-exec.cjs +91 -5
- package/ez-agents/bin/lib/safe-path.cjs +130 -130
- package/ez-agents/bin/lib/state.cjs +736 -721
- package/ez-agents/bin/lib/temp-file.cjs +239 -239
- package/ez-agents/bin/lib/template.cjs +223 -222
- package/ez-agents/bin/lib/test-file-lock.cjs +112 -112
- package/ez-agents/bin/lib/test-graceful.cjs +93 -93
- package/ez-agents/bin/lib/test-logger.cjs +60 -60
- package/ez-agents/bin/lib/test-safe-exec.cjs +38 -38
- package/ez-agents/bin/lib/test-safe-path.cjs +33 -33
- package/ez-agents/bin/lib/test-temp-file.cjs +125 -125
- package/ez-agents/bin/lib/timeout-exec.cjs +63 -62
- package/ez-agents/bin/lib/verify.cjs +69 -26
- package/ez-agents/references/checkpoints.md +776 -776
- package/ez-agents/references/continuation-format.md +249 -249
- package/ez-agents/references/questioning.md +162 -162
- package/ez-agents/references/tdd.md +263 -263
- package/ez-agents/templates/codebase/concerns.md +310 -310
- package/ez-agents/templates/codebase/conventions.md +307 -307
- package/ez-agents/templates/codebase/integrations.md +280 -280
- package/ez-agents/templates/codebase/stack.md +186 -186
- package/ez-agents/templates/codebase/testing.md +480 -480
- package/ez-agents/templates/config.json +37 -37
- package/ez-agents/templates/continue-here.md +78 -78
- package/ez-agents/templates/milestone-archive.md +123 -123
- package/ez-agents/templates/milestone.md +115 -115
- package/ez-agents/templates/requirements.md +231 -231
- package/ez-agents/templates/research-project/ARCHITECTURE.md +204 -204
- package/ez-agents/templates/research-project/FEATURES.md +147 -147
- package/ez-agents/templates/research-project/PITFALLS.md +200 -200
- package/ez-agents/templates/research-project/STACK.md +120 -120
- package/ez-agents/templates/research-project/SUMMARY.md +170 -170
- package/ez-agents/templates/retrospective.md +54 -54
- package/ez-agents/templates/roadmap.md +202 -202
- package/ez-agents/templates/summary-minimal.md +41 -41
- package/ez-agents/templates/summary-standard.md +48 -48
- package/ez-agents/templates/summary.md +248 -248
- package/ez-agents/templates/user-setup.md +311 -311
- package/ez-agents/templates/verification-report.md +322 -322
- package/ez-agents/workflows/add-phase.md +112 -112
- package/ez-agents/workflows/add-tests.md +351 -351
- package/ez-agents/workflows/add-todo.md +158 -158
- package/ez-agents/workflows/audit-milestone.md +332 -332
- package/ez-agents/workflows/autonomous.md +743 -743
- package/ez-agents/workflows/check-todos.md +177 -177
- package/ez-agents/workflows/cleanup.md +152 -152
- package/ez-agents/workflows/complete-milestone.md +766 -766
- package/ez-agents/workflows/diagnose-issues.md +219 -219
- package/ez-agents/workflows/discovery-phase.md +289 -289
- package/ez-agents/workflows/discuss-phase.md +762 -762
- package/ez-agents/workflows/execute-phase.md +468 -468
- package/ez-agents/workflows/execute-plan.md +483 -483
- package/ez-agents/workflows/health.md +159 -159
- package/ez-agents/workflows/help.md +492 -492
- package/ez-agents/workflows/insert-phase.md +130 -130
- package/ez-agents/workflows/list-phase-assumptions.md +178 -178
- package/ez-agents/workflows/map-codebase.md +316 -316
- package/ez-agents/workflows/new-milestone.md +384 -384
- package/ez-agents/workflows/new-project.md +1113 -1111
- package/ez-agents/workflows/node-repair.md +92 -92
- package/ez-agents/workflows/pause-work.md +122 -122
- package/ez-agents/workflows/plan-milestone-gaps.md +274 -274
- package/ez-agents/workflows/plan-phase.md +651 -651
- package/ez-agents/workflows/progress.md +382 -382
- package/ez-agents/workflows/quick.md +610 -610
- package/ez-agents/workflows/remove-phase.md +155 -155
- package/ez-agents/workflows/research-phase.md +74 -74
- package/ez-agents/workflows/resume-project.md +307 -307
- package/ez-agents/workflows/set-profile.md +81 -81
- package/ez-agents/workflows/settings.md +242 -242
- package/ez-agents/workflows/stats.md +57 -57
- package/ez-agents/workflows/transition.md +544 -544
- package/ez-agents/workflows/ui-phase.md +290 -290
- package/ez-agents/workflows/ui-review.md +157 -157
- package/ez-agents/workflows/update.md +320 -320
- package/ez-agents/workflows/validate-phase.md +167 -167
- package/ez-agents/workflows/verify-phase.md +243 -243
- package/ez-agents/workflows/verify-work.md +584 -584
- package/package.json +2 -3
- package/scripts/build-hooks.js +43 -43
- package/scripts/fix-qwen-installation.js +144 -0
- package/scripts/run-tests.cjs +29 -29
- 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 = '<!--
|
|
21
|
-
const EZ_COPILOT_INSTRUCTIONS_CLOSE_MARKER = '<!-- /
|
|
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 '
|
|
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
|
-
'
|
|
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
|
|
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]
|
|
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
|
|
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 (
|
|
886
|
-
const before = existing.substring(0, openIndex).trimEnd();
|
|
887
|
-
const after = existing.substring(closeIndex +
|
|
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
|
|
910
|
-
const closeIndex = content.indexOf(EZ_COPILOT_INSTRUCTIONS_CLOSE_MARKER);
|
|
1056
|
+
const markerMatch = findCopilotInstructionsMarkerPair(content);
|
|
911
1057
|
|
|
912
|
-
if (
|
|
913
|
-
const before = content.substring(0, openIndex).trimEnd();
|
|
914
|
-
const after = content.substring(closeIndex +
|
|
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('
|
|
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
|
-
|
|
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
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
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
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
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
|
|
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
|
-
|
|
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})
|
|
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:
|
|
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 === '
|
|
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
|
|