agent-ctrl-cli 0.1.3 → 0.1.7
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 +158 -36
- package/package.json +1 -1
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
|
};
|
|
@@ -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/);
|
|
@@ -8314,45 +8403,62 @@ class QwenAdapter {
|
|
|
8314
8403
|
// src/infrastructure/features/kilo/adapters/KiloAdapter.ts
|
|
8315
8404
|
import { homedir as homedir15 } from "node:os";
|
|
8316
8405
|
import { resolve as resolve26 } from "node:path";
|
|
8406
|
+
import { mkdir as mkdir8 } from "node:fs/promises";
|
|
8407
|
+
var KILO_VSCODE_DIR = ".kilo";
|
|
8408
|
+
var KILO_CLI_DIR = ".kilocode";
|
|
8409
|
+
function getKiloConfigRoots(basePath) {
|
|
8410
|
+
return [resolve26(basePath, KILO_VSCODE_DIR), resolve26(basePath, KILO_CLI_DIR)];
|
|
8411
|
+
}
|
|
8412
|
+
async function ensureDirExists(path) {
|
|
8413
|
+
await mkdir8(path, { recursive: true });
|
|
8414
|
+
}
|
|
8415
|
+
|
|
8317
8416
|
class KiloAdapter {
|
|
8318
8417
|
platformName = "kilo";
|
|
8319
8418
|
sourceLoader = new ApplySourceLoader;
|
|
8320
8419
|
async resolveTarget(projectPath, request) {
|
|
8321
8420
|
const scope = resolveApplyScope(request?.targetScope, "user", true);
|
|
8322
|
-
const
|
|
8421
|
+
const targetPath = scope === "project" ? projectPath : request?.userConfigRootPath ?? resolve26(homedir15());
|
|
8422
|
+
const [vscodeRoot, cliRoot] = getKiloConfigRoots(targetPath);
|
|
8423
|
+
await ensureDirExists(vscodeRoot);
|
|
8424
|
+
await ensureDirExists(cliRoot);
|
|
8323
8425
|
return {
|
|
8324
|
-
configPath:
|
|
8426
|
+
configPath: vscodeRoot,
|
|
8325
8427
|
scope,
|
|
8326
8428
|
surface: "rules-workflows-skills-agents-mcp"
|
|
8327
8429
|
};
|
|
8328
8430
|
}
|
|
8329
8431
|
async applyAppyIntegration(request) {
|
|
8330
|
-
const
|
|
8432
|
+
const scope = resolveApplyScope(request?.targetScope, "user", true);
|
|
8433
|
+
const targetPath = scope === "project" ? request.projectPath : request?.userConfigRootPath ?? resolve26(homedir15());
|
|
8434
|
+
const targetRoots = getKiloConfigRoots(targetPath);
|
|
8331
8435
|
const source = await this.sourceLoader.load(request.projectPath);
|
|
8332
8436
|
let changed = false;
|
|
8333
8437
|
const fileChanges = [];
|
|
8334
|
-
|
|
8335
|
-
|
|
8336
|
-
|
|
8337
|
-
|
|
8338
|
-
|
|
8339
|
-
|
|
8340
|
-
|
|
8341
|
-
|
|
8342
|
-
|
|
8343
|
-
|
|
8344
|
-
|
|
8345
|
-
|
|
8346
|
-
|
|
8347
|
-
|
|
8348
|
-
|
|
8438
|
+
for (const targetRoot of targetRoots) {
|
|
8439
|
+
const rulesResult = await syncRulesAsFiles(source.rules, resolve26(targetRoot, "rules"), (rule, content) => ({ relativePath: `${rule.id}.md`, content: content.trimEnd() }), Boolean(request.dryRun));
|
|
8440
|
+
changed = rulesResult.changed || changed;
|
|
8441
|
+
fileChanges.push(...rulesResult.paths);
|
|
8442
|
+
const workflowsResult = await syncCommandsAsMarkdown(source.commands, resolve26(targetRoot, "commands"), Boolean(request.dryRun), CommandRendererFactory.getRenderer("workflow"), ":");
|
|
8443
|
+
changed = workflowsResult.changed || changed;
|
|
8444
|
+
fileChanges.push(...workflowsResult.paths);
|
|
8445
|
+
const skillsResult = await syncSkills(source.skills, resolve26(targetRoot, "skills"), Boolean(request.dryRun));
|
|
8446
|
+
changed = skillsResult.changed || changed;
|
|
8447
|
+
fileChanges.push(...skillsResult.paths);
|
|
8448
|
+
const agentsResult = await syncAgentsAsMarkdown(source.agents, resolve26(targetRoot, "agents"), Boolean(request.dryRun), true);
|
|
8449
|
+
changed = agentsResult.changed || changed;
|
|
8450
|
+
fileChanges.push(...agentsResult.paths);
|
|
8451
|
+
const mcpResult = await mergeJsonObjectFile(resolve26(targetRoot, "kilo.json"), (existing) => renderOpencodeMcpConfig(existing, source.mcpServers), Boolean(request.dryRun));
|
|
8452
|
+
changed = mcpResult.changed || changed;
|
|
8453
|
+
fileChanges.push(...mcpResult.paths);
|
|
8454
|
+
}
|
|
8349
8455
|
return {
|
|
8350
8456
|
platform: this.platformName,
|
|
8351
|
-
configPath:
|
|
8352
|
-
scope
|
|
8353
|
-
surface:
|
|
8457
|
+
configPath: targetRoots.join(", "),
|
|
8458
|
+
scope,
|
|
8459
|
+
surface: "rules-workflows-skills-agents-mcp",
|
|
8354
8460
|
status: toStatus(changed),
|
|
8355
|
-
message: "Applied Kilo rules, workflows, skills, agents, and MCP servers.",
|
|
8461
|
+
message: "Applied Kilo rules, workflows, skills, agents, and MCP servers to both .kilo and .kilocode directories.",
|
|
8356
8462
|
fileChanges,
|
|
8357
8463
|
warnings: source.warnings
|
|
8358
8464
|
};
|
|
@@ -8441,14 +8547,13 @@ class CodexAdapter {
|
|
|
8441
8547
|
return {
|
|
8442
8548
|
configPath: scope === "project" ? resolve28(projectPath, "AGENTS.md") : resolve28(userRoot, "AGENTS.md"),
|
|
8443
8549
|
scope,
|
|
8444
|
-
surface: "agents-md-
|
|
8550
|
+
surface: "agents-md-skills-config-toml"
|
|
8445
8551
|
};
|
|
8446
8552
|
}
|
|
8447
8553
|
async applyAppyIntegration(request) {
|
|
8448
8554
|
const target = await this.resolveTarget(request.projectPath, request);
|
|
8449
8555
|
const userRoot = request.userConfigRootPath ? resolve28(request.userConfigRootPath) : resolve28(homedir17(), ".codex");
|
|
8450
8556
|
const skillRoot = target.scope === "project" ? resolve28(request.projectPath, ".agents", "skills") : resolve28(userRoot, "skills");
|
|
8451
|
-
const promptRoot = resolve28(userRoot, "prompts");
|
|
8452
8557
|
const configPath = target.scope === "project" ? resolve28(request.projectPath, ".codex", "config.toml") : resolve28(userRoot, "config.toml");
|
|
8453
8558
|
const source = await this.sourceLoader.load(request.projectPath);
|
|
8454
8559
|
let changed = false;
|
|
@@ -8460,9 +8565,15 @@ class CodexAdapter {
|
|
|
8460
8565
|
changed = skillsResult.changed || changed;
|
|
8461
8566
|
fileChanges.push(...skillsResult.paths);
|
|
8462
8567
|
if (target.scope === "user") {
|
|
8463
|
-
const
|
|
8464
|
-
changed =
|
|
8465
|
-
fileChanges.push(...
|
|
8568
|
+
const commandsAsSkillsResult = await syncCommandsAsSkills(source.commands, skillRoot, Boolean(request.dryRun));
|
|
8569
|
+
changed = commandsAsSkillsResult.changed || changed;
|
|
8570
|
+
fileChanges.push(...commandsAsSkillsResult.paths);
|
|
8571
|
+
}
|
|
8572
|
+
if (source.agents.length > 0) {
|
|
8573
|
+
const agentsDir = target.scope === "project" ? resolve28(request.projectPath, ".codex", "agents") : resolve28(userRoot, "agents");
|
|
8574
|
+
const agentsResult = await syncAgentsAsCodexToml(source.agents, agentsDir, Boolean(request.dryRun));
|
|
8575
|
+
changed = agentsResult.changed || changed;
|
|
8576
|
+
fileChanges.push(...agentsResult.paths);
|
|
8466
8577
|
}
|
|
8467
8578
|
if (source.mcpServers.length > 0) {
|
|
8468
8579
|
const mcpResult = await mergeManagedTomlSection(configPath, renderCodexMcpServers(source.mcpServers), CodexAdapter.mcpMarkers, Boolean(request.dryRun));
|
|
@@ -8475,11 +8586,11 @@ class CodexAdapter {
|
|
|
8475
8586
|
scope: target.scope,
|
|
8476
8587
|
surface: target.surface,
|
|
8477
8588
|
status: toStatus(changed),
|
|
8478
|
-
message: "Applied Codex guidance,
|
|
8589
|
+
message: "Applied Codex guidance, skills, agents, and MCP servers.",
|
|
8479
8590
|
fileChanges,
|
|
8480
8591
|
warnings: [
|
|
8481
8592
|
...source.warnings,
|
|
8482
|
-
...countUnsupportedArtifacts("Codex", source, target.scope === "project" ? ["commands"
|
|
8593
|
+
...countUnsupportedArtifacts("Codex", source, target.scope === "project" ? ["commands"] : [])
|
|
8483
8594
|
]
|
|
8484
8595
|
};
|
|
8485
8596
|
}
|
|
@@ -8783,12 +8894,23 @@ function createApplyCommand() {
|
|
|
8783
8894
|
}
|
|
8784
8895
|
console.log(`Duration: ${durationMs}ms`);
|
|
8785
8896
|
}
|
|
8786
|
-
|
|
8787
|
-
|
|
8788
|
-
|
|
8789
|
-
|
|
8897
|
+
const criticalWarnings = warnings.filter((w) => w.includes("does not have a documented apply target for"));
|
|
8898
|
+
const noiseWarnings = warnings.filter((w) => !w.includes("does not have a documented apply target for"));
|
|
8899
|
+
if (criticalWarnings.length > 0) {
|
|
8900
|
+
console.log(`
|
|
8790
8901
|
Warnings:`);
|
|
8791
|
-
|
|
8902
|
+
for (const warning of criticalWarnings) {
|
|
8903
|
+
console.log(` - ${warning}`);
|
|
8904
|
+
}
|
|
8905
|
+
}
|
|
8906
|
+
if (verbose && noiseWarnings.length > 0) {
|
|
8907
|
+
const filteredNoiseWarnings = noiseWarnings.filter((w) => !w.includes("Skipped .gitkeep") && !w.includes("invalid extension"));
|
|
8908
|
+
if (filteredNoiseWarnings.length > 0) {
|
|
8909
|
+
if (criticalWarnings.length === 0) {
|
|
8910
|
+
console.log(`
|
|
8911
|
+
Warnings:`);
|
|
8912
|
+
}
|
|
8913
|
+
for (const warning of filteredNoiseWarnings) {
|
|
8792
8914
|
console.log(` - ${warning}`);
|
|
8793
8915
|
}
|
|
8794
8916
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-ctrl-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
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",
|