@howlil/ez-agents 3.0.0 → 3.4.1
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/README.md +295 -714
- package/bin/install.js +446 -78
- package/commands/ez/auth.md +87 -0
- package/commands/ez/join-discord.md +1 -1
- package/ez-agents/bin/ez-tools.cjs +120 -2
- package/ez-agents/bin/lib/assistant-adapter.cjs +62 -3
- package/ez-agents/bin/lib/audit-exec.cjs +20 -8
- package/ez-agents/bin/lib/auth.cjs +2 -1
- package/ez-agents/bin/lib/circuit-breaker.cjs +1 -1
- package/ez-agents/bin/lib/commands.cjs +42 -23
- package/ez-agents/bin/lib/config.cjs +18 -11
- package/ez-agents/bin/lib/core.cjs +42 -25
- package/ez-agents/bin/lib/file-lock.cjs +3 -3
- package/ez-agents/bin/lib/fs-utils.cjs +1 -1
- package/ez-agents/bin/lib/git-utils.cjs +1 -1
- package/ez-agents/bin/lib/health-check.cjs +2 -3
- package/ez-agents/bin/lib/index.cjs +1 -1
- package/ez-agents/bin/lib/init.cjs +70 -23
- package/ez-agents/bin/lib/logger.cjs +11 -4
- package/ez-agents/bin/lib/model-provider.cjs +124 -29
- package/ez-agents/bin/lib/phase.cjs +39 -22
- package/ez-agents/bin/lib/planning-write.cjs +107 -0
- package/ez-agents/bin/lib/retry.cjs +1 -1
- package/ez-agents/bin/lib/roadmap.cjs +3 -2
- package/ez-agents/bin/lib/safe-exec.cjs +1 -1
- package/ez-agents/bin/lib/safe-path.cjs +1 -1
- package/ez-agents/bin/lib/state.cjs +24 -9
- package/ez-agents/bin/lib/temp-file.cjs +1 -1
- package/ez-agents/bin/lib/template.cjs +2 -1
- package/ez-agents/bin/lib/test-file-lock.cjs +1 -1
- package/ez-agents/bin/lib/test-graceful.cjs +2 -2
- package/ez-agents/bin/lib/test-logger.cjs +2 -2
- package/ez-agents/bin/lib/test-temp-file.cjs +1 -1
- package/ez-agents/bin/lib/timeout-exec.cjs +4 -3
- package/ez-agents/bin/lib/verify.cjs +54 -25
- package/ez-agents/references/continuation-format.md +1 -1
- package/ez-agents/workflows/add-tests.md +2 -2
- package/ez-agents/workflows/add-todo.md +1 -1
- package/ez-agents/workflows/autonomous.md +15 -15
- package/ez-agents/workflows/diagnose-issues.md +1 -1
- package/ez-agents/workflows/discuss-phase.md +3 -3
- package/ez-agents/workflows/execute-phase.md +2 -2
- package/ez-agents/workflows/health.md +1 -1
- package/ez-agents/workflows/help.md +2 -2
- package/ez-agents/workflows/map-codebase.md +1 -1
- package/ez-agents/workflows/new-milestone.md +5 -5
- package/ez-agents/workflows/new-project.md +12 -10
- package/ez-agents/workflows/plan-phase.md +8 -8
- package/ez-agents/workflows/progress.md +1 -1
- package/ez-agents/workflows/set-profile.md +1 -1
- package/ez-agents/workflows/settings.md +9 -9
- package/ez-agents/workflows/stats.md +1 -1
- package/ez-agents/workflows/ui-phase.md +3 -3
- package/ez-agents/workflows/ui-review.md +2 -2
- package/ez-agents/workflows/update.md +1 -1
- package/ez-agents/workflows/validate-phase.md +3 -3
- package/ez-agents/workflows/verify-work.md +3 -3
- package/package.json +1 -1
- package/scripts/build-hooks.js +1 -1
- package/scripts/fix-qwen-installation.js +144 -0
- 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');
|
|
@@ -299,7 +333,7 @@ if (hasUninstall) {
|
|
|
299
333
|
|
|
300
334
|
// Show help if requested
|
|
301
335
|
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
|
|
336
|
+
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}--qwen${reset} Install for Qwen Code only\\n ${cyan}--kimi${reset} Install for Kimi Code 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 Qwen Code globally${reset}\\n npx ez-agents --qwen --global\\n\\n ${dim}# Install for Kimi Code globally${reset}\\n npx ez-agents --kimi --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 / QWEN_CONFIG_DIR / KIMI_CONFIG_DIR environment variables.\\n\\n ${yellow}Model Providers:${reset}\\n OpenAI and Anthropic are model providers configured in settings.json,\\n not separate CLI runtimes. See README.md for model configuration.\\n`);
|
|
303
337
|
process.exit(0);
|
|
304
338
|
}
|
|
305
339
|
|
|
@@ -717,6 +751,52 @@ function convertClaudeCommandToCodexSkill(content, skillName) {
|
|
|
717
751
|
return `---\nname: ${yamlQuote(skillName)}\ndescription: ${yamlQuote(description)}\nmetadata:\n short-description: ${yamlQuote(shortDescription)}\n---\n\n${adapter}\n\n${body.trimStart()}`;
|
|
718
752
|
}
|
|
719
753
|
|
|
754
|
+
function convertClaudeToQwenAgent(content) {
|
|
755
|
+
const { frontmatter, body } = extractFrontmatterAndBody(content);
|
|
756
|
+
if (!frontmatter) return content;
|
|
757
|
+
|
|
758
|
+
const name = extractFrontmatterField(frontmatter, 'name') || 'unknown';
|
|
759
|
+
const description = extractFrontmatterField(frontmatter, 'description') || '';
|
|
760
|
+
|
|
761
|
+
// Qwen Code uses simple markdown. We'll put name and description in the body.
|
|
762
|
+
return `# Agent: ${name}\n\n${description}\n\n${body.trimStart()}`;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
function convertClaudeToKimiSkill(content, skillName) {
|
|
766
|
+
const { frontmatter, body } = extractFrontmatterAndBody(content);
|
|
767
|
+
if (!frontmatter) return content;
|
|
768
|
+
|
|
769
|
+
const description = extractFrontmatterField(frontmatter, 'description') || '';
|
|
770
|
+
|
|
771
|
+
// Reconstruct as simple Markdown
|
|
772
|
+
return `# Skill: ${skillName}\n\n${description}\n\n${body.trimStart()}`;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
function convertClaudeToKimiAgent(content) {
|
|
776
|
+
const { frontmatter, body } = extractFrontmatterAndBody(content);
|
|
777
|
+
if (!frontmatter) return content;
|
|
778
|
+
|
|
779
|
+
const name = extractFrontmatterField(frontmatter, 'name') || 'unknown';
|
|
780
|
+
const description = extractFrontmatterField(frontmatter, 'description') || '';
|
|
781
|
+
|
|
782
|
+
// Kimi Code uses simple markdown
|
|
783
|
+
return `# Agent: ${name}\n\n${description}\n\n${body.trimStart()}`;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
/**
|
|
787
|
+
* Convert a Claude command (.md) to a Qwen skill (SKILL.md).
|
|
788
|
+
* Strips frontmatter and formats as simple Markdown.
|
|
789
|
+
*/
|
|
790
|
+
function convertClaudeToQwenSkill(content, skillName) {
|
|
791
|
+
const { frontmatter, body } = extractFrontmatterAndBody(content);
|
|
792
|
+
if (!frontmatter) return content;
|
|
793
|
+
|
|
794
|
+
const description = extractFrontmatterField(frontmatter, 'description') || '';
|
|
795
|
+
|
|
796
|
+
// Reconstruct as simple Markdown
|
|
797
|
+
return `# Skill: ${skillName}\n\n${description}\n\n${body.trimStart()}`;
|
|
798
|
+
}
|
|
799
|
+
|
|
720
800
|
/**
|
|
721
801
|
* Convert Claude Code agent markdown to Codex agent format.
|
|
722
802
|
* Applies base markdown conversions, then adds a <codex_agent_role> header
|
|
@@ -860,6 +940,23 @@ function mergeCodexConfig(configPath, EZ_AgentsBlock) {
|
|
|
860
940
|
fs.writeFileSync(configPath, content);
|
|
861
941
|
}
|
|
862
942
|
|
|
943
|
+
function findCopilotInstructionsMarkerPair(content) {
|
|
944
|
+
const markerPairs = [
|
|
945
|
+
[EZ_COPILOT_INSTRUCTIONS_MARKER, EZ_COPILOT_INSTRUCTIONS_CLOSE_MARKER],
|
|
946
|
+
[LEGACY_EZ_COPILOT_INSTRUCTIONS_MARKER, LEGACY_EZ_COPILOT_INSTRUCTIONS_CLOSE_MARKER],
|
|
947
|
+
];
|
|
948
|
+
|
|
949
|
+
for (const [openMarker, closeMarker] of markerPairs) {
|
|
950
|
+
const openIndex = content.indexOf(openMarker);
|
|
951
|
+
const closeIndex = content.indexOf(closeMarker);
|
|
952
|
+
if (openIndex !== -1 && closeIndex !== -1 && closeIndex >= openIndex) {
|
|
953
|
+
return { openIndex, closeIndex, closeMarker };
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
return null;
|
|
958
|
+
}
|
|
959
|
+
|
|
863
960
|
/**
|
|
864
961
|
* Merge EZ_Agents instructions into copilot-instructions.md.
|
|
865
962
|
* Three cases: new file, existing with markers, existing without markers.
|
|
@@ -878,13 +975,12 @@ function mergeCopilotInstructions(filePath, EZ_AgentsContent) {
|
|
|
878
975
|
}
|
|
879
976
|
|
|
880
977
|
const existing = fs.readFileSync(filePath, 'utf8');
|
|
881
|
-
const
|
|
882
|
-
const closeIndex = existing.indexOf(EZ_COPILOT_INSTRUCTIONS_CLOSE_MARKER);
|
|
978
|
+
const markerMatch = findCopilotInstructionsMarkerPair(existing);
|
|
883
979
|
|
|
884
980
|
// 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 +
|
|
981
|
+
if (markerMatch) {
|
|
982
|
+
const before = existing.substring(0, markerMatch.openIndex).trimEnd();
|
|
983
|
+
const after = existing.substring(markerMatch.closeIndex + markerMatch.closeMarker.length).trimStart();
|
|
888
984
|
let newContent = '';
|
|
889
985
|
if (before) newContent += before + '\n\n';
|
|
890
986
|
newContent += EZ_AgentsBlock;
|
|
@@ -906,12 +1002,11 @@ function mergeCopilotInstructions(filePath, EZ_AgentsContent) {
|
|
|
906
1002
|
* @returns {string|null} - Cleaned content or null if empty
|
|
907
1003
|
*/
|
|
908
1004
|
function stripEzAgentsFromCopilotInstructions(content) {
|
|
909
|
-
const
|
|
910
|
-
const closeIndex = content.indexOf(EZ_COPILOT_INSTRUCTIONS_CLOSE_MARKER);
|
|
1005
|
+
const markerMatch = findCopilotInstructionsMarkerPair(content);
|
|
911
1006
|
|
|
912
|
-
if (
|
|
913
|
-
const before = content.substring(0, openIndex).trimEnd();
|
|
914
|
-
const after = content.substring(closeIndex +
|
|
1007
|
+
if (markerMatch) {
|
|
1008
|
+
const before = content.substring(0, markerMatch.openIndex).trimEnd();
|
|
1009
|
+
const after = content.substring(markerMatch.closeIndex + markerMatch.closeMarker.length).trimStart();
|
|
915
1010
|
const cleaned = (before + (before && after ? '\n\n' : '') + after).trim();
|
|
916
1011
|
if (!cleaned) return null;
|
|
917
1012
|
return cleaned + '\n';
|
|
@@ -930,7 +1025,7 @@ function installCodexConfig(targetDir, agentsSrc) {
|
|
|
930
1025
|
const agentsTomlDir = path.join(targetDir, 'agents');
|
|
931
1026
|
fs.mkdirSync(agentsTomlDir, { recursive: true });
|
|
932
1027
|
|
|
933
|
-
const agentEntries = fs.readdirSync(agentsSrc).filter(f => f.startsWith('
|
|
1028
|
+
const agentEntries = fs.readdirSync(agentsSrc).filter(f => f.startsWith('ez-') && f.endsWith('.md'));
|
|
934
1029
|
const agents = [];
|
|
935
1030
|
|
|
936
1031
|
// Compute the Codex pathPrefix for replacing .claude paths
|
|
@@ -1389,6 +1484,176 @@ function copyCommandsAsCopilotSkills(srcDir, skillsDir, prefix, isGlobal = false
|
|
|
1389
1484
|
recurse(srcDir, prefix);
|
|
1390
1485
|
}
|
|
1391
1486
|
|
|
1487
|
+
/**
|
|
1488
|
+
* Copy Claude commands as Kimi Code skills — one folder per skill with SKILL.md.
|
|
1489
|
+
*/
|
|
1490
|
+
function copyCommandsAsKimiSkills(srcDir, skillsDir, prefix, pathPrefix, runtime) {
|
|
1491
|
+
if (!fs.existsSync(srcDir)) {
|
|
1492
|
+
return;
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
fs.mkdirSync(skillsDir, { recursive: true });
|
|
1496
|
+
|
|
1497
|
+
// Remove previous EZ Kimi skills
|
|
1498
|
+
const existing = fs.readdirSync(skillsDir, { withFileTypes: true });
|
|
1499
|
+
for (const entry of existing) {
|
|
1500
|
+
if (entry.isDirectory() && entry.name.startsWith(`${prefix}-`)) {
|
|
1501
|
+
fs.rmSync(path.join(skillsDir, entry.name), { recursive: true });
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
function recurse(currentSrcDir, currentPrefix) {
|
|
1506
|
+
const entries = fs.readdirSync(currentSrcDir, { withFileTypes: true });
|
|
1507
|
+
|
|
1508
|
+
for (const entry of entries) {
|
|
1509
|
+
const srcPath = path.join(currentSrcDir, entry.name);
|
|
1510
|
+
if (entry.isDirectory()) {
|
|
1511
|
+
recurse(srcPath, `${currentPrefix}-${entry.name}`);
|
|
1512
|
+
continue;
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
if (!entry.name.endsWith('.md')) {
|
|
1516
|
+
continue;
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
const baseName = entry.name.replace('.md', '');
|
|
1520
|
+
const skillName = `${currentPrefix}-${baseName}`;
|
|
1521
|
+
const skillDir = path.join(skillsDir, skillName);
|
|
1522
|
+
fs.mkdirSync(skillDir, { recursive: true });
|
|
1523
|
+
|
|
1524
|
+
let content = fs.readFileSync(srcPath, 'utf8');
|
|
1525
|
+
const globalClaudeRegex = /~\/\.claude\//g;
|
|
1526
|
+
const globalClaudeHomeRegex = /\$HOME\/\.claude\//g;
|
|
1527
|
+
const localClaudeRegex = /\.\/\.claude\//g;
|
|
1528
|
+
const kimiDirRegex = /~\/\.kimi\//g;
|
|
1529
|
+
content = content.replace(globalClaudeRegex, pathPrefix);
|
|
1530
|
+
content = content.replace(globalClaudeHomeRegex, toHomePrefix(pathPrefix));
|
|
1531
|
+
content = content.replace(localClaudeRegex, `./${getDirName(runtime)}/`);
|
|
1532
|
+
content = content.replace(kimiDirRegex, pathPrefix);
|
|
1533
|
+
content = processAttribution(content, getCommitAttribution(runtime));
|
|
1534
|
+
content = convertClaudeToKimiSkill(content, skillName);
|
|
1535
|
+
fs.writeFileSync(path.join(skillDir, 'SKILL.md'), content);
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
recurse(srcDir, prefix);
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
/**
|
|
1543
|
+
* Copy Claude commands as Qwen Code commands — flat structure in commands/ez/.
|
|
1544
|
+
* Qwen Code uses ~/.qwen/commands/ez/*.md (like Claude Code ~/.claude/commands/ez/)
|
|
1545
|
+
*/
|
|
1546
|
+
function copyCommandsAsQwenCommands(srcDir, commandsDir, prefix, pathPrefix, runtime) {
|
|
1547
|
+
if (!fs.existsSync(srcDir)) {
|
|
1548
|
+
return;
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
fs.mkdirSync(commandsDir, { recursive: true });
|
|
1552
|
+
|
|
1553
|
+
// Remove previous EZ Qwen commands
|
|
1554
|
+
const existing = fs.readdirSync(commandsDir, { withFileTypes: true });
|
|
1555
|
+
for (const entry of existing) {
|
|
1556
|
+
if (entry.isFile() && entry.name.startsWith(`${prefix}-`) && entry.name.endsWith('.md')) {
|
|
1557
|
+
fs.rmSync(path.join(commandsDir, entry.name));
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
function recurse(currentSrcDir, currentPrefix) {
|
|
1562
|
+
const entries = fs.readdirSync(currentSrcDir, { withFileTypes: true });
|
|
1563
|
+
|
|
1564
|
+
for (const entry of entries) {
|
|
1565
|
+
const srcPath = path.join(currentSrcDir, entry.name);
|
|
1566
|
+
if (entry.isDirectory()) {
|
|
1567
|
+
recurse(srcPath, `${currentPrefix}-${entry.name}`);
|
|
1568
|
+
continue;
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
if (!entry.name.endsWith('.md')) {
|
|
1572
|
+
continue;
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
const baseName = entry.name.replace('.md', '');
|
|
1576
|
+
const commandName = `${currentPrefix}-${baseName}`;
|
|
1577
|
+
const commandFile = path.join(commandsDir, `${commandName}.md`);
|
|
1578
|
+
|
|
1579
|
+
let content = fs.readFileSync(srcPath, 'utf8');
|
|
1580
|
+
const globalClaudeRegex = /~\/\.claude\//g;
|
|
1581
|
+
const globalClaudeHomeRegex = /\$HOME\/\.claude\//g;
|
|
1582
|
+
const localClaudeRegex = /\.\/\.claude\//g;
|
|
1583
|
+
const qwenDirRegex = /~\/\.qwen\//g;
|
|
1584
|
+
content = content.replace(globalClaudeRegex, pathPrefix);
|
|
1585
|
+
content = content.replace(globalClaudeHomeRegex, toHomePrefix(pathPrefix));
|
|
1586
|
+
content = content.replace(localClaudeRegex, `./${getDirName(runtime)}/`);
|
|
1587
|
+
content = content.replace(qwenDirRegex, pathPrefix);
|
|
1588
|
+
content = processAttribution(content, getCommitAttribution(runtime));
|
|
1589
|
+
|
|
1590
|
+
// Qwen Code uses simple markdown commands (no SKILL.md wrapper)
|
|
1591
|
+
// Just copy the content as-is with path replacements
|
|
1592
|
+
fs.writeFileSync(commandFile, content);
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
recurse(srcDir, prefix);
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
/**
|
|
1600
|
+
* Copy Claude commands as Qwen Code skills — one folder per skill with SKILL.md.
|
|
1601
|
+
* Qwen Code uses the same skills/ structure as Codex but with simpler format (no adapters needed).
|
|
1602
|
+
* @deprecated Use copyCommandsAsQwenCommands instead - Qwen Code uses commands/ez/ not skills/
|
|
1603
|
+
*/
|
|
1604
|
+
function copyCommandsAsQwenSkills(srcDir, skillsDir, prefix, pathPrefix, runtime) {
|
|
1605
|
+
if (!fs.existsSync(srcDir)) {
|
|
1606
|
+
return;
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
fs.mkdirSync(skillsDir, { recursive: true });
|
|
1610
|
+
|
|
1611
|
+
// Remove previous EZ Qwen skills
|
|
1612
|
+
const existing = fs.readdirSync(skillsDir, { withFileTypes: true });
|
|
1613
|
+
for (const entry of existing) {
|
|
1614
|
+
if (entry.isDirectory() && entry.name.startsWith(`${prefix}-`)) {
|
|
1615
|
+
fs.rmSync(path.join(skillsDir, entry.name), { recursive: true });
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
function recurse(currentSrcDir, currentPrefix) {
|
|
1620
|
+
const entries = fs.readdirSync(currentSrcDir, { withFileTypes: true });
|
|
1621
|
+
|
|
1622
|
+
for (const entry of entries) {
|
|
1623
|
+
const srcPath = path.join(currentSrcDir, entry.name);
|
|
1624
|
+
if (entry.isDirectory()) {
|
|
1625
|
+
recurse(srcPath, `${currentPrefix}-${entry.name}`);
|
|
1626
|
+
continue;
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
if (!entry.name.endsWith('.md')) {
|
|
1630
|
+
continue;
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
const baseName = entry.name.replace('.md', '');
|
|
1634
|
+
const skillName = `${currentPrefix}-${baseName}`;
|
|
1635
|
+
const skillDir = path.join(skillsDir, skillName);
|
|
1636
|
+
fs.mkdirSync(skillDir, { recursive: true });
|
|
1637
|
+
|
|
1638
|
+
let content = fs.readFileSync(srcPath, 'utf8');
|
|
1639
|
+
const globalClaudeRegex = /~\/\.claude\//g;
|
|
1640
|
+
const globalClaudeHomeRegex = /\$HOME\/\.claude\//g;
|
|
1641
|
+
const localClaudeRegex = /\.\/\.claude\//g;
|
|
1642
|
+
const qwenDirRegex = /~\/\.qwen\//g;
|
|
1643
|
+
content = content.replace(globalClaudeRegex, pathPrefix);
|
|
1644
|
+
content = content.replace(globalClaudeHomeRegex, toHomePrefix(pathPrefix));
|
|
1645
|
+
content = content.replace(localClaudeRegex, `./${getDirName(runtime)}/`);
|
|
1646
|
+
content = content.replace(qwenDirRegex, pathPrefix);
|
|
1647
|
+
content = processAttribution(content, getCommitAttribution(runtime));
|
|
1648
|
+
// Qwen Code uses simple markdown skills without frontmatter adapters
|
|
1649
|
+
content = convertClaudeToQwenSkill(content, skillName);
|
|
1650
|
+
fs.writeFileSync(path.join(skillDir, 'SKILL.md'), content);
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
recurse(srcDir, prefix);
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1392
1657
|
/**
|
|
1393
1658
|
* Recursively copy directory, replacing paths in .md files
|
|
1394
1659
|
* Deletes existing destDir first to remove orphaned files from previous versions
|
|
@@ -1552,6 +1817,8 @@ function uninstall(isGlobal, runtime = 'claude') {
|
|
|
1552
1817
|
const isOpencode = runtime === 'opencode';
|
|
1553
1818
|
const isCodex = runtime === 'codex';
|
|
1554
1819
|
const isCopilot = runtime === 'copilot';
|
|
1820
|
+
const isQwen = runtime === 'qwen';
|
|
1821
|
+
const isKimi = runtime === 'kimi';
|
|
1555
1822
|
const dirName = getDirName(runtime);
|
|
1556
1823
|
|
|
1557
1824
|
// Get the target directory based on runtime and install type
|
|
@@ -1568,6 +1835,7 @@ function uninstall(isGlobal, runtime = 'claude') {
|
|
|
1568
1835
|
if (runtime === 'gemini') runtimeLabel = 'Gemini';
|
|
1569
1836
|
if (runtime === 'codex') runtimeLabel = 'Codex';
|
|
1570
1837
|
if (runtime === 'copilot') runtimeLabel = 'Copilot';
|
|
1838
|
+
if (runtime === 'qwen') runtimeLabel = 'Qwen Code';
|
|
1571
1839
|
|
|
1572
1840
|
console.log(` Uninstalling EZ_Agents from ${cyan}${runtimeLabel}${reset} at ${cyan}${locationLabel}${reset}\n`);
|
|
1573
1841
|
|
|
@@ -1594,8 +1862,8 @@ function uninstall(isGlobal, runtime = 'claude') {
|
|
|
1594
1862
|
}
|
|
1595
1863
|
console.log(` ${green}✓${reset} Removed EZ_Agents commands from command/`);
|
|
1596
1864
|
}
|
|
1597
|
-
} else if (isCodex) {
|
|
1598
|
-
// Codex: remove skills/ez-*/SKILL.md skill directories
|
|
1865
|
+
} else if (isCodex || isQwen || isKimi) {
|
|
1866
|
+
// Codex/Qwen/Kimi: remove skills/ez-*/SKILL.md skill directories
|
|
1599
1867
|
const skillsDir = path.join(targetDir, 'skills');
|
|
1600
1868
|
if (fs.existsSync(skillsDir)) {
|
|
1601
1869
|
let skillCount = 0;
|
|
@@ -1608,41 +1876,46 @@ function uninstall(isGlobal, runtime = 'claude') {
|
|
|
1608
1876
|
}
|
|
1609
1877
|
if (skillCount > 0) {
|
|
1610
1878
|
removedCount++;
|
|
1611
|
-
|
|
1879
|
+
let runtimeLabel = 'Codex';
|
|
1880
|
+
if (isQwen) runtimeLabel = 'Qwen Code';
|
|
1881
|
+
if (isKimi) runtimeLabel = 'Kimi Code';
|
|
1882
|
+
console.log(` ${green}✓${reset} Removed ${skillCount} ${runtimeLabel} skills`);
|
|
1612
1883
|
}
|
|
1613
1884
|
}
|
|
1614
1885
|
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1886
|
+
if (isCodex) {
|
|
1887
|
+
// Codex: remove EZ agent .toml config files
|
|
1888
|
+
const codexAgentsDir = path.join(targetDir, 'agents');
|
|
1889
|
+
if (fs.existsSync(codexAgentsDir)) {
|
|
1890
|
+
const tomlFiles = fs.readdirSync(codexAgentsDir);
|
|
1891
|
+
let tomlCount = 0;
|
|
1892
|
+
for (const file of tomlFiles) {
|
|
1893
|
+
if (file.startsWith('ez-') && file.endsWith('.toml')) {
|
|
1894
|
+
fs.unlinkSync(path.join(codexAgentsDir, file));
|
|
1895
|
+
tomlCount++;
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
if (tomlCount > 0) {
|
|
1899
|
+
removedCount++;
|
|
1900
|
+
console.log(` ${green}✓${reset} Removed ${tomlCount} agent .toml configs`);
|
|
1624
1901
|
}
|
|
1625
1902
|
}
|
|
1626
|
-
if (tomlCount > 0) {
|
|
1627
|
-
removedCount++;
|
|
1628
|
-
console.log(` ${green}✓${reset} Removed ${tomlCount} agent .toml configs`);
|
|
1629
|
-
}
|
|
1630
|
-
}
|
|
1631
1903
|
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1904
|
+
// Codex: clean EZ sections from config.toml
|
|
1905
|
+
const configPath = path.join(targetDir, 'config.toml');
|
|
1906
|
+
if (fs.existsSync(configPath)) {
|
|
1907
|
+
const content = fs.readFileSync(configPath, 'utf8');
|
|
1908
|
+
const cleaned = stripEzAgentsFromCodexConfig(content);
|
|
1909
|
+
if (cleaned === null) {
|
|
1910
|
+
// File is empty after stripping — delete it
|
|
1911
|
+
fs.unlinkSync(configPath);
|
|
1912
|
+
removedCount++;
|
|
1913
|
+
console.log(` ${green}✓${reset} Removed config.toml (was EZ-only)`);
|
|
1914
|
+
} else if (cleaned !== content) {
|
|
1915
|
+
fs.writeFileSync(configPath, cleaned);
|
|
1916
|
+
removedCount++;
|
|
1917
|
+
console.log(` ${green}✓${reset} Cleaned EZ_Agents sections from config.toml`);
|
|
1918
|
+
}
|
|
1646
1919
|
}
|
|
1647
1920
|
}
|
|
1648
1921
|
} else if (isCopilot) {
|
|
@@ -2208,6 +2481,8 @@ function install(isGlobal, runtime = 'claude') {
|
|
|
2208
2481
|
const isGemini = runtime === 'gemini';
|
|
2209
2482
|
const isCodex = runtime === 'codex';
|
|
2210
2483
|
const isCopilot = runtime === 'copilot';
|
|
2484
|
+
const isQwen = runtime === 'qwen';
|
|
2485
|
+
const isKimi = runtime === 'kimi';
|
|
2211
2486
|
const dirName = getDirName(runtime);
|
|
2212
2487
|
const src = path.join(__dirname, '..');
|
|
2213
2488
|
|
|
@@ -2233,6 +2508,8 @@ function install(isGlobal, runtime = 'claude') {
|
|
|
2233
2508
|
if (isGemini) runtimeLabel = 'Gemini';
|
|
2234
2509
|
if (isCodex) runtimeLabel = 'Codex';
|
|
2235
2510
|
if (isCopilot) runtimeLabel = 'Copilot';
|
|
2511
|
+
if (isQwen) runtimeLabel = 'Qwen Code';
|
|
2512
|
+
if (isKimi) runtimeLabel = 'Kimi Code';
|
|
2236
2513
|
|
|
2237
2514
|
console.log(` Installing for ${cyan}${runtimeLabel}${reset} to ${cyan}${locationLabel}${reset}\n`);
|
|
2238
2515
|
|
|
@@ -2245,7 +2522,7 @@ function install(isGlobal, runtime = 'claude') {
|
|
|
2245
2522
|
// Clean up orphaned files from previous versions
|
|
2246
2523
|
cleanupOrphanedFiles(targetDir);
|
|
2247
2524
|
|
|
2248
|
-
// OpenCode uses command/ (flat), Codex
|
|
2525
|
+
// OpenCode uses command/ (flat), Codex/Copilot/Qwen use skills/, Claude/Gemini use commands/ez/
|
|
2249
2526
|
if (isOpencode) {
|
|
2250
2527
|
// OpenCode: flat structure in command/ directory
|
|
2251
2528
|
const commandDir = path.join(targetDir, 'command');
|
|
@@ -2260,16 +2537,41 @@ function install(isGlobal, runtime = 'claude') {
|
|
|
2260
2537
|
} else {
|
|
2261
2538
|
failures.push('command/ez-*');
|
|
2262
2539
|
}
|
|
2263
|
-
} else if (isCodex) {
|
|
2540
|
+
} else if (isCodex || isKimi) {
|
|
2541
|
+
// Codex, Kimi: skills structure in skills/ directory
|
|
2264
2542
|
const skillsDir = path.join(targetDir, 'skills');
|
|
2265
2543
|
const ezSrc = path.join(src, 'commands', 'ez');
|
|
2266
|
-
|
|
2544
|
+
if (isKimi) {
|
|
2545
|
+
copyCommandsAsKimiSkills(ezSrc, skillsDir, 'ez', pathPrefix, runtime);
|
|
2546
|
+
} else {
|
|
2547
|
+
copyCommandsAsCodexSkills(ezSrc, skillsDir, 'ez', pathPrefix, runtime);
|
|
2548
|
+
}
|
|
2267
2549
|
const installedSkillNames = listCodexSkillNames(skillsDir);
|
|
2268
2550
|
if (installedSkillNames.length > 0) {
|
|
2269
2551
|
console.log(` ${green}✓${reset} Installed ${installedSkillNames.length} skills to skills/`);
|
|
2270
2552
|
} else {
|
|
2271
2553
|
failures.push('skills/ez-*');
|
|
2272
2554
|
}
|
|
2555
|
+
} else if (isQwen) {
|
|
2556
|
+
// Qwen Code: commands structure in commands/ez/ directory (like Claude Code/Gemini)
|
|
2557
|
+
// Qwen Code uses ~/.qwen/commands/ez/*.md, NOT ~/.qwen/skills/
|
|
2558
|
+
const commandsDir = path.join(targetDir, 'commands');
|
|
2559
|
+
const ezCommandsDir = path.join(commandsDir, 'ez');
|
|
2560
|
+
fs.mkdirSync(ezCommandsDir, { recursive: true });
|
|
2561
|
+
|
|
2562
|
+
const ezSrc = path.join(src, 'commands', 'ez');
|
|
2563
|
+
copyCommandsAsQwenCommands(ezSrc, ezCommandsDir, 'ez', pathPrefix, runtime);
|
|
2564
|
+
|
|
2565
|
+
if (fs.existsSync(ezCommandsDir)) {
|
|
2566
|
+
const count = fs.readdirSync(ezCommandsDir).filter(f => f.endsWith('.md')).length;
|
|
2567
|
+
if (count > 0) {
|
|
2568
|
+
console.log(` ${green}✓${reset} Installed ${count} commands to commands/ez/`);
|
|
2569
|
+
} else {
|
|
2570
|
+
failures.push('commands/ez/*.md');
|
|
2571
|
+
}
|
|
2572
|
+
} else {
|
|
2573
|
+
failures.push('commands/ez/*.md');
|
|
2574
|
+
}
|
|
2273
2575
|
} else if (isCopilot) {
|
|
2274
2576
|
const skillsDir = path.join(targetDir, 'skills');
|
|
2275
2577
|
const ezSrc = path.join(src, 'commands', 'ez');
|
|
@@ -2356,6 +2658,10 @@ function install(isGlobal, runtime = 'claude') {
|
|
|
2356
2658
|
content = convertClaudeAgentToCodexAgent(content);
|
|
2357
2659
|
} else if (isCopilot) {
|
|
2358
2660
|
content = convertClaudeAgentToCopilotAgent(content, isGlobal);
|
|
2661
|
+
} else if (isQwen) {
|
|
2662
|
+
content = convertClaudeToQwenAgent(content);
|
|
2663
|
+
} else if (isKimi) {
|
|
2664
|
+
content = convertClaudeToKimiAgent(content);
|
|
2359
2665
|
}
|
|
2360
2666
|
const destName = isCopilot ? entry.name.replace('.md', '.agent.md') : entry.name;
|
|
2361
2667
|
fs.writeFileSync(path.join(agentsDest, destName), content);
|
|
@@ -2389,7 +2695,7 @@ function install(isGlobal, runtime = 'claude') {
|
|
|
2389
2695
|
failures.push('VERSION');
|
|
2390
2696
|
}
|
|
2391
2697
|
|
|
2392
|
-
if (!isCodex && !isCopilot) {
|
|
2698
|
+
if (!isCodex && !isCopilot && !isQwen && !isKimi) {
|
|
2393
2699
|
// Write package.json to force CommonJS mode for EZ scripts
|
|
2394
2700
|
// Prevents "require is not defined" errors when project has "type": "module"
|
|
2395
2701
|
// Node.js walks up looking for package.json - this stops inheritance from project
|
|
@@ -2493,6 +2799,12 @@ function install(isGlobal, runtime = 'claude') {
|
|
|
2493
2799
|
return { settingsPath: null, settings: null, statuslineCommand: null, runtime };
|
|
2494
2800
|
}
|
|
2495
2801
|
|
|
2802
|
+
if (isQwen || isKimi) {
|
|
2803
|
+
// Qwen/Kimi: no settings.json hooks, no statusline (like Codex/Copilot)
|
|
2804
|
+
// Skills are installed directly in skills/ez-*/SKILL.md
|
|
2805
|
+
return { settingsPath: null, settings: null, statuslineCommand: null, runtime };
|
|
2806
|
+
}
|
|
2807
|
+
|
|
2496
2808
|
// Configure statusline and hooks in settings.json
|
|
2497
2809
|
// Gemini uses AfterTool instead of PostToolUse for post-tool hooks
|
|
2498
2810
|
const postToolEvent = runtime === 'gemini' ? 'AfterTool' : 'PostToolUse';
|
|
@@ -2576,8 +2888,10 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
|
|
|
2576
2888
|
const isOpencode = runtime === 'opencode';
|
|
2577
2889
|
const isCodex = runtime === 'codex';
|
|
2578
2890
|
const isCopilot = runtime === 'copilot';
|
|
2891
|
+
const isQwen = runtime === 'qwen';
|
|
2892
|
+
const isKimi = runtime === 'kimi';
|
|
2579
2893
|
|
|
2580
|
-
if (shouldInstallStatusline && !isOpencode && !isCodex && !isCopilot) {
|
|
2894
|
+
if (shouldInstallStatusline && !isOpencode && !isCodex && !isCopilot && !isQwen && !isKimi) {
|
|
2581
2895
|
settings.statusLine = {
|
|
2582
2896
|
type: 'command',
|
|
2583
2897
|
command: statuslineCommand
|
|
@@ -2586,7 +2900,7 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
|
|
|
2586
2900
|
}
|
|
2587
2901
|
|
|
2588
2902
|
// Write settings when runtime supports settings.json
|
|
2589
|
-
if (!isCodex && !isCopilot) {
|
|
2903
|
+
if (!isCodex && !isCopilot && !isQwen && !isKimi) {
|
|
2590
2904
|
writeSettings(settingsPath, settings);
|
|
2591
2905
|
}
|
|
2592
2906
|
|
|
@@ -2600,11 +2914,12 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
|
|
|
2600
2914
|
if (runtime === 'gemini') program = 'Gemini';
|
|
2601
2915
|
if (runtime === 'codex') program = 'Codex';
|
|
2602
2916
|
if (runtime === 'copilot') program = 'Copilot';
|
|
2917
|
+
if (runtime === 'qwen') program = 'Qwen Code';
|
|
2918
|
+
if (runtime === 'kimi') program = 'Kimi Code';
|
|
2603
2919
|
|
|
2604
2920
|
let command = '/ez:new-project';
|
|
2605
|
-
if (runtime === 'opencode') command = '/ez-new-project';
|
|
2921
|
+
if (runtime === 'opencode' || runtime === 'gemini' || runtime === 'copilot' || runtime === 'qwen' || runtime === 'kimi') command = '/ez-new-project';
|
|
2606
2922
|
if (runtime === 'codex') command = '$ez-new-project';
|
|
2607
|
-
if (runtime === 'copilot') command = '/ez-new-project';
|
|
2608
2923
|
console.log(`
|
|
2609
2924
|
${green}Done!${reset} Open a blank directory in ${program} and run ${cyan}${command}${reset}.
|
|
2610
2925
|
|
|
@@ -2687,15 +3002,24 @@ function promptRuntime(callback) {
|
|
|
2687
3002
|
${cyan}3${reset}) Gemini ${dim}(~/.gemini)${reset}
|
|
2688
3003
|
${cyan}4${reset}) Codex ${dim}(~/.codex)${reset}
|
|
2689
3004
|
${cyan}5${reset}) Copilot ${dim}(~/.copilot)${reset}
|
|
2690
|
-
${cyan}6${reset})
|
|
3005
|
+
${cyan}6${reset}) Qwen Code ${dim}(~/.qwen)${reset} - Alibaba Qwen official CLI
|
|
3006
|
+
${cyan}7${reset}) Kimi Code ${dim}(~/.kimi)${reset} - Moonshot Kimi official CLI
|
|
3007
|
+
${cyan}8${reset}) All ${dim}(all 7 runtimes above)${reset}
|
|
3008
|
+
|
|
3009
|
+
${dim}Note: OpenAI and Anthropic are model providers configured within each runtime's settings,
|
|
3010
|
+
not separate CLI runtimes. See README.md for model configuration.${reset}
|
|
2691
3011
|
`);
|
|
2692
3012
|
|
|
2693
3013
|
rl.question(` Choice ${dim}[1]${reset}: `, (answer) => {
|
|
2694
3014
|
answered = true;
|
|
2695
3015
|
rl.close();
|
|
2696
3016
|
const choice = answer.trim() || '1';
|
|
2697
|
-
if (choice === '
|
|
2698
|
-
callback(['claude', 'opencode', 'gemini', 'codex', 'copilot']);
|
|
3017
|
+
if (choice === '8') {
|
|
3018
|
+
callback(['claude', 'opencode', 'gemini', 'codex', 'copilot', 'qwen', 'kimi']);
|
|
3019
|
+
} else if (choice === '7') {
|
|
3020
|
+
callback(['kimi']);
|
|
3021
|
+
} else if (choice === '6') {
|
|
3022
|
+
callback(['qwen']);
|
|
2699
3023
|
} else if (choice === '5') {
|
|
2700
3024
|
callback(['copilot']);
|
|
2701
3025
|
} else if (choice === '4') {
|
|
@@ -2767,26 +3091,66 @@ function installAllRuntimes(runtimes, isGlobal, isInteractive) {
|
|
|
2767
3091
|
}
|
|
2768
3092
|
|
|
2769
3093
|
const statuslineRuntimes = ['claude', 'gemini'];
|
|
2770
|
-
const
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
3094
|
+
const statuslineResults = results.filter(r => statuslineRuntimes.includes(r.runtime));
|
|
3095
|
+
|
|
3096
|
+
// Handle statusline per-runtime to avoid conflicts
|
|
3097
|
+
// Each runtime with settings.json gets its own statusline prompt
|
|
3098
|
+
const finalizePerRuntime = (runtimeResult, shouldInstallStatusline) => {
|
|
3099
|
+
const useStatusline = statuslineRuntimes.includes(runtimeResult.runtime) && shouldInstallStatusline;
|
|
3100
|
+
finishInstall(
|
|
3101
|
+
runtimeResult.settingsPath,
|
|
3102
|
+
runtimeResult.settings,
|
|
3103
|
+
runtimeResult.statuslineCommand,
|
|
3104
|
+
useStatusline,
|
|
3105
|
+
runtimeResult.runtime,
|
|
3106
|
+
isGlobal
|
|
3107
|
+
);
|
|
2784
3108
|
};
|
|
2785
3109
|
|
|
2786
|
-
if (
|
|
2787
|
-
|
|
3110
|
+
if (statuslineResults.length === 0) {
|
|
3111
|
+
// No runtimes support statusline - finalize all without statusline
|
|
3112
|
+
for (const result of results) {
|
|
3113
|
+
finalizePerRuntime(result, false);
|
|
3114
|
+
}
|
|
3115
|
+
} else if (statuslineResults.length === 1) {
|
|
3116
|
+
// Single runtime with statusline support - prompt once
|
|
3117
|
+
const singleResult = statuslineResults[0];
|
|
3118
|
+
handleStatusline(singleResult.settings, isInteractive, (shouldInstall) => {
|
|
3119
|
+
finalizePerRuntime(singleResult, shouldInstall);
|
|
3120
|
+
// Finalize other runtimes without statusline
|
|
3121
|
+
for (const result of results) {
|
|
3122
|
+
if (!statuslineRuntimes.includes(result.runtime)) {
|
|
3123
|
+
finalizePerRuntime(result, false);
|
|
3124
|
+
}
|
|
3125
|
+
}
|
|
3126
|
+
});
|
|
2788
3127
|
} else {
|
|
2789
|
-
|
|
3128
|
+
// Multiple runtimes with statusline support - prompt for each
|
|
3129
|
+
let currentIndex = 0;
|
|
3130
|
+
const statuslineChoices = new Array(statuslineResults.length).fill(false);
|
|
3131
|
+
|
|
3132
|
+
const promptNextStatusline = () => {
|
|
3133
|
+
if (currentIndex >= statuslineResults.length) {
|
|
3134
|
+
// All statusline prompts done - finalize all runtimes
|
|
3135
|
+
for (let i = 0; i < results.length; i++) {
|
|
3136
|
+
const result = results[i];
|
|
3137
|
+
const statuslineIndex = statuslineResults.findIndex(r => r.runtime === result.runtime);
|
|
3138
|
+
const shouldInstall = statuslineIndex !== -1 ? statuslineChoices[statuslineIndex] : false;
|
|
3139
|
+
finalizePerRuntime(result, shouldInstall);
|
|
3140
|
+
}
|
|
3141
|
+
return;
|
|
3142
|
+
}
|
|
3143
|
+
|
|
3144
|
+
const currentResult = statuslineResults[currentIndex];
|
|
3145
|
+
const currentRuntimeIndex = currentIndex;
|
|
3146
|
+
handleStatusline(currentResult.settings, isInteractive, (shouldInstall) => {
|
|
3147
|
+
statuslineChoices[currentRuntimeIndex] = shouldInstall;
|
|
3148
|
+
currentIndex++;
|
|
3149
|
+
promptNextStatusline();
|
|
3150
|
+
});
|
|
3151
|
+
};
|
|
3152
|
+
|
|
3153
|
+
promptNextStatusline();
|
|
2790
3154
|
}
|
|
2791
3155
|
}
|
|
2792
3156
|
|
|
@@ -2819,6 +3183,10 @@ if (process.env.EZ_AGENTS_TEST_MODE) {
|
|
|
2819
3183
|
stripEzAgentsFromCopilotInstructions,
|
|
2820
3184
|
writeManifest,
|
|
2821
3185
|
reportLocalPatches,
|
|
3186
|
+
convertClaudeToQwenSkill,
|
|
3187
|
+
convertClaudeToQwenAgent,
|
|
3188
|
+
convertClaudeToKimiSkill,
|
|
3189
|
+
convertClaudeToKimiAgent,
|
|
2822
3190
|
};
|
|
2823
3191
|
} else {
|
|
2824
3192
|
|