agent-ctrl-cli 0.1.1 → 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.
- package/README.md +25 -17
- package/dist/index.js +124 -18
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -2,14 +2,34 @@
|
|
|
2
2
|
|
|
3
3
|
# agent-ctrl
|
|
4
4
|
|
|
5
|
-
[](https://www.gnu.org/licenses/gpl-3.0)
|
|
5
|
+
[](./LICENSE)
|
|
6
|
+
[](https://www.npmjs.com/package/agent-ctrl-cli)
|
|
8
7
|
[](https://github.com/ahmet-cetinkaya/agent-ctrl/stargazers)
|
|
8
|
+
[](https://github.com/ahmet-cetinkaya/agent-ctrl/network/members)
|
|
9
|
+
[](https://github.com/ahmet-cetinkaya/agent-ctrl/graphs/contributors)
|
|
10
|
+
[](https://github.com/ahmet-cetinkaya/agent-ctrl/issues)
|
|
11
|
+
[](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
|
-
**
|
|
15
|
+
**Download:**
|
|
16
|
+
[](https://www.npmjs.com/package/agent-ctrl-cli)
|
|
17
|
+
[](https://github.com/ahmet-cetinkaya/agent-ctrl/releases)
|
|
18
|
+
|
|
19
|
+
**Supported platforms:**
|
|
20
|
+

|
|
21
|
+

|
|
22
|
+

|
|
23
|
+

|
|
24
|
+

|
|
25
|
+

|
|
26
|
+

|
|
27
|
+

|
|
28
|
+

|
|
29
|
+
|
|
30
|
+
**Core Techs:**
|
|
31
|
+
[](https://bun.sh)
|
|
32
|
+
[](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
|
-

|
|
56
|
-

|
|
57
|
-

|
|
58
|
-

|
|
59
|
-

|
|
60
|
-

|
|
61
|
-

|
|
62
|
-

|
|
63
|
-

|
|
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
|
};
|
|
@@ -7533,12 +7563,13 @@ async function syncRulesAsFiles(rules, targetRoot, renderer, dryRun) {
|
|
|
7533
7563
|
}));
|
|
7534
7564
|
return syncRenderedFiles(targetRoot, rendered, dryRun);
|
|
7535
7565
|
}
|
|
7536
|
-
async function syncCommandsAsMarkdown(commands, targetRoot, dryRun, renderer) {
|
|
7566
|
+
async function syncCommandsAsMarkdown(commands, targetRoot, dryRun, renderer, flattenSeparator) {
|
|
7537
7567
|
const commandRenderer = renderer ?? CommandRendererFactory.getRenderer("opencode");
|
|
7538
7568
|
const rendered = await Promise.all(commands.map(async (command) => {
|
|
7539
7569
|
const source = await readFile5(command.path, "utf-8");
|
|
7570
|
+
const relativePath = flattenSeparator ? `${command.id.replaceAll("/", flattenSeparator)}${commandRenderer.fileExtension}` : `${command.id}${commandRenderer.fileExtension}`;
|
|
7540
7571
|
return {
|
|
7541
|
-
relativePath
|
|
7572
|
+
relativePath,
|
|
7542
7573
|
content: commandRenderer.renderCommand(source, command.id)
|
|
7543
7574
|
};
|
|
7544
7575
|
}));
|
|
@@ -7555,6 +7586,29 @@ async function syncCommandsAsToml(commands, targetRoot, dryRun, renderer) {
|
|
|
7555
7586
|
}));
|
|
7556
7587
|
return syncRenderedFiles(targetRoot, rendered, dryRun);
|
|
7557
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
|
+
}
|
|
7558
7612
|
async function syncCommandsAsWorkflows(commands, targetRoot, dryRun, renderer) {
|
|
7559
7613
|
const commandRenderer = renderer ?? CommandRendererFactory.getRenderer("workflow");
|
|
7560
7614
|
const rendered = await Promise.all(commands.map(async (command) => {
|
|
@@ -7577,6 +7631,27 @@ async function syncSkills(skills, targetRoot, dryRun, compatibility, renderer) {
|
|
|
7577
7631
|
}
|
|
7578
7632
|
return { changed, paths };
|
|
7579
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
|
+
}
|
|
7580
7655
|
async function syncAgentsAsMarkdown(agents, targetRoot, dryRun, withFrontmatter, renderer) {
|
|
7581
7656
|
const commandRenderer = renderer ?? CommandRendererFactory.getRenderer("opencode");
|
|
7582
7657
|
const rendered = await Promise.all(agents.map(async (agent) => {
|
|
@@ -7716,6 +7791,21 @@ function renderSkillMarkdown(source, skillName, compatibility, renderer) {
|
|
|
7716
7791
|
return lines.join(`
|
|
7717
7792
|
`);
|
|
7718
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
|
+
}
|
|
7719
7809
|
function parseMarkdownPrompt(source, id) {
|
|
7720
7810
|
const trimmed = source.trim();
|
|
7721
7811
|
const lines = trimmed.split(/\r?\n/);
|
|
@@ -8333,7 +8423,7 @@ class KiloAdapter {
|
|
|
8333
8423
|
const rulesResult = await syncRulesAsFiles(source.rules, resolve26(target.configPath, "rules"), (rule, content) => ({ relativePath: `${rule.id}.md`, content: content.trimEnd() }), Boolean(request.dryRun));
|
|
8334
8424
|
changed = rulesResult.changed || changed;
|
|
8335
8425
|
fileChanges.push(...rulesResult.paths);
|
|
8336
|
-
const workflowsResult = await
|
|
8426
|
+
const workflowsResult = await syncCommandsAsMarkdown(source.commands, resolve26(target.configPath, "workflows"), Boolean(request.dryRun), CommandRendererFactory.getRenderer("workflow"), "-");
|
|
8337
8427
|
changed = workflowsResult.changed || changed;
|
|
8338
8428
|
fileChanges.push(...workflowsResult.paths);
|
|
8339
8429
|
const skillsResult = await syncSkills(source.skills, resolve26(target.configPath, "skills"), Boolean(request.dryRun));
|
|
@@ -8342,7 +8432,7 @@ class KiloAdapter {
|
|
|
8342
8432
|
const agentsResult = await syncAgentsAsMarkdown(source.agents, resolve26(target.configPath, "agents"), Boolean(request.dryRun), true);
|
|
8343
8433
|
changed = agentsResult.changed || changed;
|
|
8344
8434
|
fileChanges.push(...agentsResult.paths);
|
|
8345
|
-
const mcpResult = await mergeJsonObjectFile(resolve26(target.configPath, "
|
|
8435
|
+
const mcpResult = await mergeJsonObjectFile(resolve26(target.configPath, "kilo.json"), (existing) => renderOpencodeMcpConfig(existing, source.mcpServers), Boolean(request.dryRun));
|
|
8346
8436
|
changed = mcpResult.changed || changed;
|
|
8347
8437
|
fileChanges.push(...mcpResult.paths);
|
|
8348
8438
|
return {
|
|
@@ -8440,14 +8530,13 @@ class CodexAdapter {
|
|
|
8440
8530
|
return {
|
|
8441
8531
|
configPath: scope === "project" ? resolve28(projectPath, "AGENTS.md") : resolve28(userRoot, "AGENTS.md"),
|
|
8442
8532
|
scope,
|
|
8443
|
-
surface: "agents-md-
|
|
8533
|
+
surface: "agents-md-skills-config-toml"
|
|
8444
8534
|
};
|
|
8445
8535
|
}
|
|
8446
8536
|
async applyAppyIntegration(request) {
|
|
8447
8537
|
const target = await this.resolveTarget(request.projectPath, request);
|
|
8448
8538
|
const userRoot = request.userConfigRootPath ? resolve28(request.userConfigRootPath) : resolve28(homedir17(), ".codex");
|
|
8449
8539
|
const skillRoot = target.scope === "project" ? resolve28(request.projectPath, ".agents", "skills") : resolve28(userRoot, "skills");
|
|
8450
|
-
const promptRoot = resolve28(userRoot, "prompts");
|
|
8451
8540
|
const configPath = target.scope === "project" ? resolve28(request.projectPath, ".codex", "config.toml") : resolve28(userRoot, "config.toml");
|
|
8452
8541
|
const source = await this.sourceLoader.load(request.projectPath);
|
|
8453
8542
|
let changed = false;
|
|
@@ -8459,9 +8548,15 @@ class CodexAdapter {
|
|
|
8459
8548
|
changed = skillsResult.changed || changed;
|
|
8460
8549
|
fileChanges.push(...skillsResult.paths);
|
|
8461
8550
|
if (target.scope === "user") {
|
|
8462
|
-
const
|
|
8463
|
-
changed =
|
|
8464
|
-
fileChanges.push(...
|
|
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);
|
|
8465
8560
|
}
|
|
8466
8561
|
if (source.mcpServers.length > 0) {
|
|
8467
8562
|
const mcpResult = await mergeManagedTomlSection(configPath, renderCodexMcpServers(source.mcpServers), CodexAdapter.mcpMarkers, Boolean(request.dryRun));
|
|
@@ -8474,11 +8569,11 @@ class CodexAdapter {
|
|
|
8474
8569
|
scope: target.scope,
|
|
8475
8570
|
surface: target.surface,
|
|
8476
8571
|
status: toStatus(changed),
|
|
8477
|
-
message: "Applied Codex guidance,
|
|
8572
|
+
message: "Applied Codex guidance, skills, agents, and MCP servers.",
|
|
8478
8573
|
fileChanges,
|
|
8479
8574
|
warnings: [
|
|
8480
8575
|
...source.warnings,
|
|
8481
|
-
...countUnsupportedArtifacts("Codex", source, target.scope === "project" ? ["commands"
|
|
8576
|
+
...countUnsupportedArtifacts("Codex", source, target.scope === "project" ? ["commands"] : [])
|
|
8482
8577
|
]
|
|
8483
8578
|
};
|
|
8484
8579
|
}
|
|
@@ -8782,12 +8877,23 @@ function createApplyCommand() {
|
|
|
8782
8877
|
}
|
|
8783
8878
|
console.log(`Duration: ${durationMs}ms`);
|
|
8784
8879
|
}
|
|
8785
|
-
|
|
8786
|
-
|
|
8787
|
-
|
|
8788
|
-
|
|
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(`
|
|
8789
8894
|
Warnings:`);
|
|
8790
|
-
|
|
8895
|
+
}
|
|
8896
|
+
for (const warning of filteredNoiseWarnings) {
|
|
8791
8897
|
console.log(` - ${warning}`);
|
|
8792
8898
|
}
|
|
8793
8899
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-ctrl-cli",
|
|
3
|
-
"version": "0.1.
|
|
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",
|
|
@@ -23,7 +23,10 @@
|
|
|
23
23
|
],
|
|
24
24
|
"main": "./dist/index.js",
|
|
25
25
|
"types": "./dist/index.d.ts",
|
|
26
|
-
"bin":
|
|
26
|
+
"bin": {
|
|
27
|
+
"agent-ctrl-cli": "./dist/index.js",
|
|
28
|
+
"agent-ctrl": "./dist/index.js"
|
|
29
|
+
},
|
|
27
30
|
"files": [
|
|
28
31
|
"dist/",
|
|
29
32
|
"README.md",
|