feat(03-02): install Kimi global agent artifacts
- stage generated Kimi root and subagent YAML/prompt files under agents/ - copy and remove only GSD-owned Kimi agent files while preserving user files - print explicit kimi --agent-file launch hint
This commit is contained in:
@@ -6686,6 +6686,7 @@ function _applyRuntimeRewrites(content, runtime, pathPrefix) {
|
||||
* encodes the GSD namespace as its last segment (e.g. `commands/gsd`), in
|
||||
* which case write as `${stem}.md` (directory IS the namespace).
|
||||
* - agents: write as-is (files already carry their own `gsd-` prefix).
|
||||
* For kimi-agents kind: recursively copy generated YAML/prompt files.
|
||||
*/
|
||||
function _copyStaged(stagedDir, destDir, kind) {
|
||||
if (!fs.existsSync(stagedDir)) return;
|
||||
@@ -6702,6 +6703,11 @@ function _copyStaged(stagedDir, destDir, kind) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (kind.kind === 'kimi-agents') {
|
||||
fs.cpSync(stagedDir, destDir, { recursive: true });
|
||||
return;
|
||||
}
|
||||
|
||||
// commands or agents
|
||||
const entries = fs.readdirSync(stagedDir, { withFileTypes: true });
|
||||
// For commands: apply prefix unless the destSubpath's last segment already
|
||||
@@ -6738,6 +6744,21 @@ function _copyStaged(stagedDir, destDir, kind) {
|
||||
*/
|
||||
function _removeGsdEntries(destDir, kind) {
|
||||
if (!fs.existsSync(destDir)) return;
|
||||
if (kind.kind === 'kimi-agents') {
|
||||
for (const fileName of ['gsd.yaml', 'gsd.md']) {
|
||||
fs.rmSync(path.join(destDir, fileName), { force: true });
|
||||
}
|
||||
const subagentsDir = path.join(destDir, 'subagents');
|
||||
if (fs.existsSync(subagentsDir)) {
|
||||
for (const entry of fs.readdirSync(subagentsDir, { withFileTypes: true })) {
|
||||
if (!entry.isFile()) continue;
|
||||
if (!entry.name.startsWith('gsd-')) continue;
|
||||
if (!entry.name.endsWith('.yaml') && !entry.name.endsWith('.md')) continue;
|
||||
fs.rmSync(path.join(subagentsDir, entry.name), { force: true });
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (kind.prefix === '') {
|
||||
// Whole-namespace removal (Hermes nested case — destSubpath is skills/gsd)
|
||||
// The directory itself is the GSD namespace, so remove it entirely.
|
||||
@@ -8779,6 +8800,7 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
||||
if (isKimi && isGlobal) {
|
||||
installRuntimeArtifacts(runtime, targetDir, 'global', _resolvedProfile);
|
||||
const skillsDir = path.join(targetDir, 'skills');
|
||||
const rootAgentPath = path.join(targetDir, 'agents', 'gsd.yaml');
|
||||
const count = fs.existsSync(skillsDir)
|
||||
? fs.readdirSync(skillsDir, { withFileTypes: true })
|
||||
.filter(e => e.isDirectory() && e.name.startsWith('gsd-')).length
|
||||
@@ -8788,11 +8810,18 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
||||
} else {
|
||||
throw new Error('Kimi global install produced no skills/gsd-* entries');
|
||||
}
|
||||
if (fs.existsSync(rootAgentPath)) {
|
||||
console.log(` ${green}✓${reset} Generated Kimi root agent: ${rootAgentPath}`);
|
||||
console.log(` Launch with: kimi --agent-file ${rootAgentPath}`);
|
||||
} else {
|
||||
throw new Error('Kimi global install produced no agents/gsd.yaml');
|
||||
}
|
||||
return {
|
||||
runtime,
|
||||
skipped: true,
|
||||
reason: 'kimi_global_skills_only',
|
||||
reason: 'kimi_global_skills_and_agents',
|
||||
configDir: targetDir,
|
||||
agentPath: rootAgentPath,
|
||||
settingsPath: null,
|
||||
settings: null,
|
||||
statuslineCommand: null,
|
||||
|
||||
@@ -64,6 +64,7 @@ function getInstallExports(): InstallExports {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
type ArtifactKindName = 'commands' | 'agents' | 'skills';
|
||||
type KimiArtifactKindName = ArtifactKindName | 'kimi-agents';
|
||||
|
||||
// Mirrors the (unexported) ResolvedProfile in install-profiles.cts.
|
||||
// Must stay in sync if that shape changes.
|
||||
@@ -74,7 +75,7 @@ interface ResolvedProfile {
|
||||
}
|
||||
|
||||
interface ArtifactKind {
|
||||
kind: ArtifactKindName;
|
||||
kind: KimiArtifactKindName;
|
||||
destSubpath: string;
|
||||
prefix: string;
|
||||
stage: (resolvedProfile: ResolvedProfile) => string;
|
||||
@@ -192,6 +193,50 @@ function agentsKind(destSubpath: string, prefix: string, configDir: string): Art
|
||||
};
|
||||
}
|
||||
|
||||
function kimiAgentsKind(destSubpath: string, prefix: string, configDir: string): ArtifactKind {
|
||||
return {
|
||||
kind: 'kimi-agents',
|
||||
destSubpath,
|
||||
prefix,
|
||||
stage: (resolved) => {
|
||||
const installExports = getInstallExports();
|
||||
const buildKimiAgentArtifacts = installExports['buildKimiAgentArtifacts'] as (opts: {
|
||||
rootAgent?: string;
|
||||
subagents?: Array<{ path: string; content: string }>;
|
||||
}) => {
|
||||
root: { yaml: string; prompt: string };
|
||||
subagents: Array<{ name: string; yaml: string; prompt: string }>;
|
||||
};
|
||||
const stagedAgents = stageAgentsForProfile(findAgentsSourceRoot(configDir), resolved);
|
||||
const subagents: Array<{ path: string; content: string }> = [];
|
||||
if (fs.existsSync(stagedAgents)) {
|
||||
for (const entry of fs.readdirSync(stagedAgents, { withFileTypes: true })) {
|
||||
if (!entry.isFile() || !entry.name.endsWith('.md')) continue;
|
||||
const agentPath = path.join(stagedAgents, entry.name);
|
||||
subagents.push({
|
||||
path: path.join('agents', entry.name).replace(/\\/g, '/'),
|
||||
content: fs.readFileSync(agentPath, 'utf8'),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const rootAgent = `---\nname: gsd\ndescription: Run GSD workflows in Kimi CLI.\ntools: Agent\n---\n\n# GSD for Kimi CLI\n\nCoordinate installed /skill:gsd-* workflows and route work to generated GSD subagents when a workflow requires an agent handoff.\n`;
|
||||
const artifacts = buildKimiAgentArtifacts({ rootAgent, subagents });
|
||||
const stageDir = fs.mkdtempSync(path.join(require('node:os').tmpdir(), 'gsd-kimi-agents-'));
|
||||
fs.writeFileSync(path.join(stageDir, 'gsd.yaml'), artifacts.root.yaml);
|
||||
fs.writeFileSync(path.join(stageDir, 'gsd.md'), artifacts.root.prompt);
|
||||
const subagentsDir = path.join(stageDir, 'subagents');
|
||||
fs.mkdirSync(subagentsDir, { recursive: true });
|
||||
for (const artifact of artifacts.subagents) {
|
||||
fs.writeFileSync(path.join(subagentsDir, `${artifact.name}.yaml`), artifact.yaml);
|
||||
fs.writeFileSync(path.join(subagentsDir, `${artifact.name}.md`), artifact.prompt);
|
||||
}
|
||||
installProfiles.STAGED_DIRS.add(stageDir);
|
||||
return stageDir;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a skills kind descriptor.
|
||||
*
|
||||
@@ -306,7 +351,10 @@ function resolveRuntimeArtifactLayout(runtime: string, configDir: string, scope:
|
||||
|
||||
case 'kimi':
|
||||
kinds = scope === 'global'
|
||||
? [skillsKind('skills', 'gsd-', 'convertClaudeCommandToKimiSkill', 'kimi', configDir)]
|
||||
? [
|
||||
skillsKind('skills', 'gsd-', 'convertClaudeCommandToKimiSkill', 'kimi', configDir),
|
||||
kimiAgentsKind('agents', 'gsd', configDir),
|
||||
]
|
||||
: [];
|
||||
break;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user