agent-ctrl-cli 0.1.3 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +25 -17
  2. package/dist/index.js +121 -16
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -2,14 +2,34 @@
2
2
 
3
3
  # agent-ctrl
4
4
 
5
- [![Bun](https://img.shields.io/badge/Bun-000000?style=flat&logo=bun&logoColor=white)](https://bun.sh)
6
- [![TypeScript](https://img.shields.io/badge/TypeScript-3178C6?style=flat&logo=typescript&logoColor=white)](https://www.typescriptlang.org)
7
- [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
5
+ [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](./LICENSE)
6
+ [![NPM Downloads](https://img.shields.io/npm/dm/agent-ctrl-cli)](https://www.npmjs.com/package/agent-ctrl-cli)
8
7
  [![GitHub stars](https://img.shields.io/github/stars/ahmet-cetinkaya/agent-ctrl?style=social)](https://github.com/ahmet-cetinkaya/agent-ctrl/stargazers)
8
+ [![GitHub forks](https://img.shields.io/github/forks/ahmet-cetinkaya/agent-ctrl?style=social)](https://github.com/ahmet-cetinkaya/agent-ctrl/network/members)
9
+ [![GitHub contributors](https://img.shields.io/github/contributors/ahmet-cetinkaya/agent-ctrl)](https://github.com/ahmet-cetinkaya/agent-ctrl/graphs/contributors)
10
+ [![GitHub issues](https://img.shields.io/github/issues/ahmet-cetinkaya/agent-ctrl)](https://github.com/ahmet-cetinkaya/agent-ctrl/issues)
11
+ [![Buy Me A Coffee](https://img.shields.io/badge/Buy%20Me%20a%20Coffee-ffdd00?logo=buy-me-a-coffee&logoColor=black&style=flat)](https://ahmetcetinkaya.me/donate)
9
12
 
10
13
  A centralized CLI tool for managing AI agent configurations using a **standard directory-based configuration pattern**. Define agent behavior through rules, skills, agents, commands, and MCP servers in a structured, shareable way that works across multiple AI platforms.
11
14
 
12
- **Status:** 🚧 Early Development phase.
15
+ **Download:**
16
+ [![NPM Release](https://img.shields.io/npm/v/agent-ctrl-cli?color=cb3837)](https://www.npmjs.com/package/agent-ctrl-cli)
17
+ [![GitHub release](https://img.shields.io/github/v/release/ahmet-cetinkaya/agent-ctrl)](https://github.com/ahmet-cetinkaya/agent-ctrl/releases)
18
+
19
+ **Supported platforms:**
20
+ ![Antigravity](https://img.shields.io/badge/Antigravity-black)
21
+ ![Claude Code](https://img.shields.io/badge/Claude%20Code-D49A5A)
22
+ ![Codex](https://img.shields.io/badge/Codex-black)
23
+ ![Cursor](https://img.shields.io/badge/Cursor-black)
24
+ ![Gemini](https://img.shields.io/badge/Gemini-4285F4)
25
+ ![KiloCode](https://img.shields.io/badge/KiloCode-F8F674)
26
+ ![OpenCode](https://img.shields.io/badge/OpenCode-black)
27
+ ![QwenCode](https://img.shields.io/badge/QwenCode-6C63F5)
28
+ ![Windsurf](https://img.shields.io/badge/Windsurf-007ACC)
29
+
30
+ **Core Techs:**
31
+ [![Bun](https://img.shields.io/badge/Bun-000000?style=flat&logo=bun&logoColor=white)](https://bun.sh)
32
+ [![TypeScript](https://img.shields.io/badge/TypeScript-3178C6?style=flat&logo=typescript&logoColor=white)](https://www.typescriptlang.org)
13
33
 
14
34
  ---
15
35
 
@@ -50,18 +70,6 @@ agent-ctrl apply claude
50
70
  - `init [path]` - Initialize the global configuration structure (default: `~/.agent-ctrl`).
51
71
  - `apply <platform>` - Sync local artifacts to a platform's native configuration.
52
72
 
53
- **Supported platforms:**
54
-
55
- ![Antigravity](https://img.shields.io/badge/Antigravity-black)
56
- ![Claude Code](https://img.shields.io/badge/Claude%20Code-D49A5A)
57
- ![Codex](https://img.shields.io/badge/Codex-black)
58
- ![Cursor](https://img.shields.io/badge/Cursor-black)
59
- ![Gemini](https://img.shields.io/badge/Gemini-4285F4)
60
- ![KiloCode](https://img.shields.io/badge/KiloCode-F8F674)
61
- ![OpenCode](https://img.shields.io/badge/OpenCode-black)
62
- ![QwenCode](https://img.shields.io/badge/QwenCode-6C63F5)
63
- ![Windsurf](https://img.shields.io/badge/Windsurf-007ACC)
64
-
65
73
  ### Artifact Management
66
74
 
67
75
  #### Rules (`rules/`)
@@ -83,7 +91,7 @@ Capabilities following the `SKILL.md` standard.
83
91
 
84
92
  #### Commands (`commands/`)
85
93
 
86
- Grouped command prompts or scripts.
94
+ Grouped command prompts or scripts (mapped to skills for Codex).
87
95
 
88
96
  - `agent-ctrl command ls` - List available commands.
89
97
 
package/dist/index.js CHANGED
@@ -3471,6 +3471,28 @@ function createMissingApiKeyError(registryName, envVarName) {
3471
3471
  }
3472
3472
  return new Error(`${registryName} API key is missing. Configure ${envVarName} in .agent-ctrl/.env or pass --api-key.`);
3473
3473
  }
3474
+ function createSkillNotFoundError(skillRef, cause) {
3475
+ const baseMessage = `Skill "${skillRef}" could not be found.`;
3476
+ const hint = `Try: agent-ctrl skill search <query> to find available skills.`;
3477
+ const causeSuffix = cause ? `
3478
+ Cause: ${cause}` : "";
3479
+ return new Error(`${baseMessage}${causeSuffix}
3480
+
3481
+ ${hint}`);
3482
+ }
3483
+ function createSkillAccessBlockedError(skillRef) {
3484
+ return new Error(`The SkillsMP service is blocking requests for "${skillRef}".
3485
+
3486
+ ` + `Hint: The service may be restricting access from this environment.
3487
+ ` + `Try: agent-ctrl skill search <query> to find available skills locally.`);
3488
+ }
3489
+ function createSkillRepositoryNotAccessibleError(skillRef, repositoryUrl) {
3490
+ return new Error(`The repository for "${skillRef}" could not be accessed.
3491
+
3492
+ ` + `Expected repository: ${repositoryUrl}
3493
+
3494
+ ` + `Hint: Verify the repository exists and is publicly accessible, or search ` + `for an alternative skill using: agent-ctrl skill search <query>`);
3495
+ }
3474
3496
 
3475
3497
  // src/infrastructure/features/catalog/clients/SkillsMpClient.ts
3476
3498
  class SkillsMpClient {
@@ -3589,14 +3611,21 @@ class SkillsMpClient {
3589
3611
  }
3590
3612
  });
3591
3613
  }
3614
+ const repositoryUrl = slugFallbackRecord.metadata?.repository;
3615
+ if (typeof repositoryUrl === "string") {
3616
+ return err(createSkillRepositoryNotAccessibleError(skillId, repositoryUrl));
3617
+ }
3592
3618
  }
3593
3619
  const detailUrl = exact?.sourceUrl ?? `${this.baseUrl}/skills/${skillId}`;
3594
3620
  const detailResponse = await this.fetchText(detailUrl, apiKey);
3595
3621
  if (!detailResponse.success) {
3622
+ if (detailResponse.error.message.includes("403") || detailResponse.error.message.includes("blocked")) {
3623
+ return err(createSkillAccessBlockedError(skillId));
3624
+ }
3596
3625
  if (exact) {
3597
3626
  return ok({ ...exact, installation: exact.metadata?.installation });
3598
3627
  }
3599
- return detailResponse;
3628
+ return err(createSkillNotFoundError(skillId, detailResponse.error.message));
3600
3629
  }
3601
3630
  if (this.isCloudflareBlockPage(detailResponse.data) && exact) {
3602
3631
  return ok({ ...exact, installation: exact.metadata?.installation });
@@ -3810,7 +3839,8 @@ class SkillsMpClient {
3810
3839
  repository: guessedUrl,
3811
3840
  raw: {
3812
3841
  resolver: "slug-repository-fallback",
3813
- sourceSkillId: skillId
3842
+ sourceSkillId: skillId,
3843
+ warning: `This skill was resolved from a GitHub path format. Verify the repository exists at: ${guessedUrl}`
3814
3844
  }
3815
3845
  }
3816
3846
  };
@@ -7556,6 +7586,29 @@ async function syncCommandsAsToml(commands, targetRoot, dryRun, renderer) {
7556
7586
  }));
7557
7587
  return syncRenderedFiles(targetRoot, rendered, dryRun);
7558
7588
  }
7589
+ async function syncCommandsAsSkills(commands, targetRoot, dryRun) {
7590
+ let changed = false;
7591
+ const paths = [];
7592
+ for (const command of commands) {
7593
+ const source = await readFile5(command.path, "utf-8");
7594
+ const skillName = command.id.replaceAll("/", "-");
7595
+ const parsed = parseMarkdownPrompt(source, command.id);
7596
+ const skillMd = ["---", `name: ${skillName}`, `description: ${parsed.description}`, "---", "", source.trim()].join(`
7597
+ `);
7598
+ const skillDir = resolve20(targetRoot, skillName);
7599
+ const targetPath = resolve20(skillDir, "SKILL.md");
7600
+ const existing = await readTextFileOrNull(targetPath);
7601
+ if (normalizeText(existing ?? "") === normalizeText(skillMd)) {
7602
+ continue;
7603
+ }
7604
+ changed = true;
7605
+ paths.push(targetPath);
7606
+ if (!dryRun) {
7607
+ await writeTextFile(targetPath, skillMd);
7608
+ }
7609
+ }
7610
+ return { changed, paths };
7611
+ }
7559
7612
  async function syncCommandsAsWorkflows(commands, targetRoot, dryRun, renderer) {
7560
7613
  const commandRenderer = renderer ?? CommandRendererFactory.getRenderer("workflow");
7561
7614
  const rendered = await Promise.all(commands.map(async (command) => {
@@ -7578,6 +7631,27 @@ async function syncSkills(skills, targetRoot, dryRun, compatibility, renderer) {
7578
7631
  }
7579
7632
  return { changed, paths };
7580
7633
  }
7634
+ async function syncAgentsAsCodexToml(agents, targetRoot, dryRun) {
7635
+ let changed = false;
7636
+ const paths = [];
7637
+ for (const agent of agents) {
7638
+ const source = await readFile5(agent.path, "utf-8");
7639
+ const parsed = parseMarkdownPrompt(source, agent.id);
7640
+ const agentName = agent.id.replaceAll("/", "-");
7641
+ const tomlContent = buildCodexAgentToml(agentName, parsed);
7642
+ const targetPath = resolve20(targetRoot, `${agentName}.toml`);
7643
+ const existing = await readTextFileOrNull(targetPath);
7644
+ if (normalizeText(existing ?? "") === normalizeText(tomlContent)) {
7645
+ continue;
7646
+ }
7647
+ changed = true;
7648
+ paths.push(targetPath);
7649
+ if (!dryRun) {
7650
+ await writeTextFile(targetPath, tomlContent);
7651
+ }
7652
+ }
7653
+ return { changed, paths };
7654
+ }
7581
7655
  async function syncAgentsAsMarkdown(agents, targetRoot, dryRun, withFrontmatter, renderer) {
7582
7656
  const commandRenderer = renderer ?? CommandRendererFactory.getRenderer("opencode");
7583
7657
  const rendered = await Promise.all(agents.map(async (agent) => {
@@ -7717,6 +7791,21 @@ function renderSkillMarkdown(source, skillName, compatibility, renderer) {
7717
7791
  return lines.join(`
7718
7792
  `);
7719
7793
  }
7794
+ function buildCodexAgentToml(name, parsed) {
7795
+ const lines = [
7796
+ `name = "${escapeTomlString(name)}"`,
7797
+ `description = "${escapeTomlString(parsed.description)}"`,
7798
+ `developer_instructions = """`,
7799
+ parsed.body || parsed.title,
7800
+ `"""`,
7801
+ ""
7802
+ ];
7803
+ return lines.join(`
7804
+ `);
7805
+ }
7806
+ function escapeTomlString(value) {
7807
+ return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\n/g, "\\n");
7808
+ }
7720
7809
  function parseMarkdownPrompt(source, id) {
7721
7810
  const trimmed = source.trim();
7722
7811
  const lines = trimmed.split(/\r?\n/);
@@ -8334,7 +8423,7 @@ class KiloAdapter {
8334
8423
  const rulesResult = await syncRulesAsFiles(source.rules, resolve26(target.configPath, "rules"), (rule, content) => ({ relativePath: `${rule.id}.md`, content: content.trimEnd() }), Boolean(request.dryRun));
8335
8424
  changed = rulesResult.changed || changed;
8336
8425
  fileChanges.push(...rulesResult.paths);
8337
- const workflowsResult = await syncCommandsAsWorkflows(source.commands, resolve26(target.configPath, "workflows"), Boolean(request.dryRun));
8426
+ const workflowsResult = await syncCommandsAsMarkdown(source.commands, resolve26(target.configPath, "workflows"), Boolean(request.dryRun), CommandRendererFactory.getRenderer("workflow"), "-");
8338
8427
  changed = workflowsResult.changed || changed;
8339
8428
  fileChanges.push(...workflowsResult.paths);
8340
8429
  const skillsResult = await syncSkills(source.skills, resolve26(target.configPath, "skills"), Boolean(request.dryRun));
@@ -8343,7 +8432,7 @@ class KiloAdapter {
8343
8432
  const agentsResult = await syncAgentsAsMarkdown(source.agents, resolve26(target.configPath, "agents"), Boolean(request.dryRun), true);
8344
8433
  changed = agentsResult.changed || changed;
8345
8434
  fileChanges.push(...agentsResult.paths);
8346
- const mcpResult = await mergeJsonObjectFile(resolve26(target.configPath, "opencode.json"), (existing) => renderOpencodeMcpConfig(existing, source.mcpServers), Boolean(request.dryRun));
8435
+ const mcpResult = await mergeJsonObjectFile(resolve26(target.configPath, "kilo.json"), (existing) => renderOpencodeMcpConfig(existing, source.mcpServers), Boolean(request.dryRun));
8347
8436
  changed = mcpResult.changed || changed;
8348
8437
  fileChanges.push(...mcpResult.paths);
8349
8438
  return {
@@ -8441,14 +8530,13 @@ class CodexAdapter {
8441
8530
  return {
8442
8531
  configPath: scope === "project" ? resolve28(projectPath, "AGENTS.md") : resolve28(userRoot, "AGENTS.md"),
8443
8532
  scope,
8444
- surface: "agents-md-prompts-skills-config-toml"
8533
+ surface: "agents-md-skills-config-toml"
8445
8534
  };
8446
8535
  }
8447
8536
  async applyAppyIntegration(request) {
8448
8537
  const target = await this.resolveTarget(request.projectPath, request);
8449
8538
  const userRoot = request.userConfigRootPath ? resolve28(request.userConfigRootPath) : resolve28(homedir17(), ".codex");
8450
8539
  const skillRoot = target.scope === "project" ? resolve28(request.projectPath, ".agents", "skills") : resolve28(userRoot, "skills");
8451
- const promptRoot = resolve28(userRoot, "prompts");
8452
8540
  const configPath = target.scope === "project" ? resolve28(request.projectPath, ".codex", "config.toml") : resolve28(userRoot, "config.toml");
8453
8541
  const source = await this.sourceLoader.load(request.projectPath);
8454
8542
  let changed = false;
@@ -8460,9 +8548,15 @@ class CodexAdapter {
8460
8548
  changed = skillsResult.changed || changed;
8461
8549
  fileChanges.push(...skillsResult.paths);
8462
8550
  if (target.scope === "user") {
8463
- const promptsResult = await syncCommandsAsMarkdown(source.commands, promptRoot, Boolean(request.dryRun), undefined, ":");
8464
- changed = promptsResult.changed || changed;
8465
- fileChanges.push(...promptsResult.paths);
8551
+ const commandsAsSkillsResult = await syncCommandsAsSkills(source.commands, skillRoot, Boolean(request.dryRun));
8552
+ changed = commandsAsSkillsResult.changed || changed;
8553
+ fileChanges.push(...commandsAsSkillsResult.paths);
8554
+ }
8555
+ if (source.agents.length > 0) {
8556
+ const agentsDir = target.scope === "project" ? resolve28(request.projectPath, ".codex", "agents") : resolve28(userRoot, "agents");
8557
+ const agentsResult = await syncAgentsAsCodexToml(source.agents, agentsDir, Boolean(request.dryRun));
8558
+ changed = agentsResult.changed || changed;
8559
+ fileChanges.push(...agentsResult.paths);
8466
8560
  }
8467
8561
  if (source.mcpServers.length > 0) {
8468
8562
  const mcpResult = await mergeManagedTomlSection(configPath, renderCodexMcpServers(source.mcpServers), CodexAdapter.mcpMarkers, Boolean(request.dryRun));
@@ -8475,11 +8569,11 @@ class CodexAdapter {
8475
8569
  scope: target.scope,
8476
8570
  surface: target.surface,
8477
8571
  status: toStatus(changed),
8478
- message: "Applied Codex guidance, prompts, skills, and MCP servers.",
8572
+ message: "Applied Codex guidance, skills, agents, and MCP servers.",
8479
8573
  fileChanges,
8480
8574
  warnings: [
8481
8575
  ...source.warnings,
8482
- ...countUnsupportedArtifacts("Codex", source, target.scope === "project" ? ["commands", "agents"] : ["agents"])
8576
+ ...countUnsupportedArtifacts("Codex", source, target.scope === "project" ? ["commands"] : [])
8483
8577
  ]
8484
8578
  };
8485
8579
  }
@@ -8783,12 +8877,23 @@ function createApplyCommand() {
8783
8877
  }
8784
8878
  console.log(`Duration: ${durationMs}ms`);
8785
8879
  }
8786
- if (verbose && warnings.length > 0) {
8787
- const filteredWarnings = warnings.filter((w) => !w.includes("Skipped .gitkeep") && !w.includes("invalid extension"));
8788
- if (filteredWarnings.length > 0) {
8789
- console.log(`
8880
+ const criticalWarnings = warnings.filter((w) => w.includes("does not have a documented apply target for"));
8881
+ const noiseWarnings = warnings.filter((w) => !w.includes("does not have a documented apply target for"));
8882
+ if (criticalWarnings.length > 0) {
8883
+ console.log(`
8884
+ Warnings:`);
8885
+ for (const warning of criticalWarnings) {
8886
+ console.log(` - ${warning}`);
8887
+ }
8888
+ }
8889
+ if (verbose && noiseWarnings.length > 0) {
8890
+ const filteredNoiseWarnings = noiseWarnings.filter((w) => !w.includes("Skipped .gitkeep") && !w.includes("invalid extension"));
8891
+ if (filteredNoiseWarnings.length > 0) {
8892
+ if (criticalWarnings.length === 0) {
8893
+ console.log(`
8790
8894
  Warnings:`);
8791
- for (const warning of filteredWarnings) {
8895
+ }
8896
+ for (const warning of filteredNoiseWarnings) {
8792
8897
  console.log(` - ${warning}`);
8793
8898
  }
8794
8899
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-ctrl-cli",
3
- "version": "0.1.3",
3
+ "version": "0.1.6",
4
4
  "description": "A centralized CLI tool for managing AI agent configurations with standard directory-based patterns.",
5
5
  "author": "Ahmet Cetinkaya <ahmetcetinkaya@tutamail.com> (https://github.com/ahmet-cetinkaya)",
6
6
  "license": "GPL-3.0",