@michaelhartmayer/agentctl 1.1.0 → 1.1.4
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 +136 -17
- package/dist/ctl.js +175 -228
- package/dist/effects.js +61 -0
- package/dist/fs-utils.js +26 -19
- package/dist/index.js +266 -227
- package/dist/logic/ctl.js +173 -0
- package/dist/logic/index.js +79 -0
- package/dist/logic/install.js +60 -0
- package/dist/logic/manifest.js +8 -0
- package/dist/logic/resolve.js +73 -0
- package/dist/logic/skills.js +20 -0
- package/dist/logic/utils.js +31 -0
- package/dist/manifest.js +17 -1
- package/dist/resolve.js +30 -76
- package/dist/skills.js +11 -23
- package/package.json +1 -1
- package/dist/package.json +0 -60
- package/dist/src/ctl.js +0 -316
- package/dist/src/fs-utils.js +0 -35
- package/dist/src/index.js +0 -351
- package/dist/src/manifest.js +0 -19
- package/dist/src/resolve.js +0 -112
- package/dist/src/skills.js +0 -39
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.Logic = void 0;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const utils_1 = require("./utils");
|
|
9
|
+
const skills_1 = require("../skills");
|
|
10
|
+
/**
|
|
11
|
+
* THE BRAIN: Pure functions that calculate "Plans" (Effect arrays).
|
|
12
|
+
* NO IMPURE IMPORTS (no fs, no os, no process).
|
|
13
|
+
*/
|
|
14
|
+
exports.Logic = {
|
|
15
|
+
planScaffold(args, ctx, options) {
|
|
16
|
+
if (args.length === 0)
|
|
17
|
+
throw new Error('No command path provided');
|
|
18
|
+
if (options.exists) {
|
|
19
|
+
throw new Error(`Command ${args.join(' ')} already exists`);
|
|
20
|
+
}
|
|
21
|
+
if (options.cappedAncestor) {
|
|
22
|
+
throw new Error(`Cannot nest command under capped command: ${options.cappedAncestor.relPath}`);
|
|
23
|
+
}
|
|
24
|
+
const localRoot = ctx.localRoot || ctx.cwd;
|
|
25
|
+
const agentctlDir = path_1.default.join(localRoot, '.agentctl');
|
|
26
|
+
const cmdPath = args.join(path_1.default.sep);
|
|
27
|
+
const targetDir = path_1.default.join(agentctlDir, cmdPath);
|
|
28
|
+
const name = args[args.length - 1];
|
|
29
|
+
const type = options.type || 'scaffold';
|
|
30
|
+
const isWin = ctx.platform === 'win32';
|
|
31
|
+
const effects = [{ type: 'mkdir', path: targetDir }];
|
|
32
|
+
const manifest = {
|
|
33
|
+
name,
|
|
34
|
+
description: '<insert summary>',
|
|
35
|
+
help: '<insert usage/help instructions>',
|
|
36
|
+
type,
|
|
37
|
+
};
|
|
38
|
+
if (type === 'scaffold') {
|
|
39
|
+
const scriptName = isWin ? 'command.cmd' : 'command.sh';
|
|
40
|
+
const scriptPath = path_1.default.join(targetDir, scriptName);
|
|
41
|
+
const scriptContent = isWin
|
|
42
|
+
? '@echo off\r\nREM Add your command logic here\r\necho Not implemented'
|
|
43
|
+
: '#!/usr/bin/env bash\n# Add your command logic here\necho "Not implemented"';
|
|
44
|
+
manifest.run = `./${scriptName}`;
|
|
45
|
+
effects.push({ type: 'writeFile', path: scriptPath, content: scriptContent });
|
|
46
|
+
if (!isWin)
|
|
47
|
+
effects.push({ type: 'chmod', path: scriptPath, mode: 0o755 });
|
|
48
|
+
}
|
|
49
|
+
else if (type === 'alias') {
|
|
50
|
+
manifest.run = options.target;
|
|
51
|
+
}
|
|
52
|
+
effects.push({ type: 'writeJson', path: path_1.default.join(targetDir, 'manifest.json'), content: manifest });
|
|
53
|
+
const logMsg = type === 'scaffold' ? `Scaffolded command: ${args.join(' ')}` :
|
|
54
|
+
type === 'alias' ? `Aliased command: ${args.join(' ')} -> ${options.target}` :
|
|
55
|
+
`Created group: ${args.join(' ')}`;
|
|
56
|
+
effects.push({ type: 'log', message: logMsg });
|
|
57
|
+
return { effects };
|
|
58
|
+
},
|
|
59
|
+
planPushGlobal(args, ctx, options) {
|
|
60
|
+
if (!ctx.localRoot)
|
|
61
|
+
throw new Error('Not in a local context');
|
|
62
|
+
if (!options.existsInLocal)
|
|
63
|
+
throw new Error(`Local command ${args.join(' ')} not found`);
|
|
64
|
+
if (options.existsInGlobal)
|
|
65
|
+
throw new Error(`Global command ${args.join(' ')} already exists`);
|
|
66
|
+
const cmdPathStr = args.join(path_1.default.sep);
|
|
67
|
+
const srcDir = path_1.default.join(ctx.localRoot, '.agentctl', cmdPathStr);
|
|
68
|
+
const destDir = path_1.default.join(ctx.globalRoot, cmdPathStr);
|
|
69
|
+
const effects = [
|
|
70
|
+
{ type: 'mkdir', path: path_1.default.dirname(destDir) }
|
|
71
|
+
];
|
|
72
|
+
if (options.move) {
|
|
73
|
+
effects.push({ type: 'move', src: srcDir, dest: destDir });
|
|
74
|
+
effects.push({ type: 'log', message: `Moved ${args.join(' ')} to global scope` });
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
effects.push({ type: 'copy', src: srcDir, dest: destDir });
|
|
78
|
+
effects.push({ type: 'log', message: `Copied ${args.join(' ')} to global scope` });
|
|
79
|
+
}
|
|
80
|
+
return effects;
|
|
81
|
+
},
|
|
82
|
+
planPullLocal(args, ctx, options) {
|
|
83
|
+
if (!ctx.localRoot)
|
|
84
|
+
throw new Error('Not in a local context');
|
|
85
|
+
if (!options.existsInGlobal)
|
|
86
|
+
throw new Error(`Global command ${args.join(' ')} not found`);
|
|
87
|
+
if (options.existsInLocal)
|
|
88
|
+
throw new Error(`Local command ${args.join(' ')} already exists`);
|
|
89
|
+
const cmdPathStr = args.join(path_1.default.sep);
|
|
90
|
+
const srcDir = path_1.default.join(ctx.globalRoot, cmdPathStr);
|
|
91
|
+
const destDir = path_1.default.join(ctx.localRoot, '.agentctl', cmdPathStr);
|
|
92
|
+
const effects = [
|
|
93
|
+
{ type: 'mkdir', path: path_1.default.dirname(destDir) }
|
|
94
|
+
];
|
|
95
|
+
if (options.move) {
|
|
96
|
+
effects.push({ type: 'move', src: srcDir, dest: destDir });
|
|
97
|
+
effects.push({ type: 'log', message: `Moved ${args.join(' ')} to local scope` });
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
effects.push({ type: 'copy', src: srcDir, dest: destDir });
|
|
101
|
+
effects.push({ type: 'log', message: `Copied ${args.join(' ')} to local scope` });
|
|
102
|
+
}
|
|
103
|
+
return effects;
|
|
104
|
+
},
|
|
105
|
+
planRemove(args, ctx, options) {
|
|
106
|
+
if (!options.resolvedPath) {
|
|
107
|
+
throw new Error(`Command ${args.join(' ')} not found${options.global ? ' in global scope' : ''}`);
|
|
108
|
+
}
|
|
109
|
+
const targetDir = path_1.default.dirname(options.resolvedPath);
|
|
110
|
+
return [
|
|
111
|
+
{ type: 'remove', path: targetDir },
|
|
112
|
+
{ type: 'log', message: `Removed ${options.scope} command: ${args.join(' ')}` }
|
|
113
|
+
];
|
|
114
|
+
},
|
|
115
|
+
planMove(srcArgs, destArgs, ctx, options) {
|
|
116
|
+
if (!options.resolvedSrc)
|
|
117
|
+
throw new Error(`Command ${srcArgs.join(' ')} not found`);
|
|
118
|
+
if (!options.rootDir || !options.agentctlDir)
|
|
119
|
+
throw new Error('Cannot determine root for move');
|
|
120
|
+
if (options.destExists)
|
|
121
|
+
throw new Error(`Destination ${destArgs.join(' ')} already exists`);
|
|
122
|
+
if (options.cappedAncestor)
|
|
123
|
+
throw new Error(`Cannot nest command under capped command: ${options.cappedAncestor.relPath}`);
|
|
124
|
+
const srcDir = path_1.default.dirname(options.resolvedSrc.manifestPath);
|
|
125
|
+
const destPathStr = destArgs.join(path_1.default.sep);
|
|
126
|
+
const destDir = path_1.default.join(options.agentctlDir, destPathStr);
|
|
127
|
+
const effects = [
|
|
128
|
+
{ type: 'move', src: srcDir, dest: destDir }
|
|
129
|
+
];
|
|
130
|
+
const updatedManifest = { ...options.resolvedSrc.manifest, name: destArgs[destArgs.length - 1] };
|
|
131
|
+
effects.push({ type: 'writeJson', path: path_1.default.join(destDir, 'manifest.json'), content: updatedManifest });
|
|
132
|
+
effects.push({ type: 'log', message: `Moved ${srcArgs.join(' ')} to ${destArgs.join(' ')}` });
|
|
133
|
+
return effects;
|
|
134
|
+
},
|
|
135
|
+
planInstallSkill(agent, ctx, options) {
|
|
136
|
+
if (!skills_1.SUPPORTED_AGENTS.includes(agent)) {
|
|
137
|
+
throw new Error(`Agent '${agent}' not supported. Supported agents: ${skills_1.SUPPORTED_AGENTS.join(', ')}`);
|
|
138
|
+
}
|
|
139
|
+
let targetDir;
|
|
140
|
+
if (agent === 'cursor') {
|
|
141
|
+
targetDir = path_1.default.join(ctx.cwd, '.cursor', 'skills');
|
|
142
|
+
}
|
|
143
|
+
else if (agent === 'antigravity') {
|
|
144
|
+
if (options.global) {
|
|
145
|
+
const globalRoot = options.antigravityGlobalDir || utils_1.UtilsLogic.getAntigravityGlobalRoot({
|
|
146
|
+
platform: ctx.platform,
|
|
147
|
+
env: {}, // UtilsLogic should probably take full ctx or we just use ctx.homedir
|
|
148
|
+
homedir: ctx.homedir
|
|
149
|
+
});
|
|
150
|
+
targetDir = path_1.default.join(globalRoot, 'skills', 'agentctl');
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
targetDir = path_1.default.join(ctx.cwd, '.agent', 'skills', 'agentctl');
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
else if (agent === 'agentsmd') {
|
|
157
|
+
targetDir = path_1.default.join(ctx.cwd, '.agents', 'skills', 'agentctl');
|
|
158
|
+
}
|
|
159
|
+
else if (agent === 'gemini') {
|
|
160
|
+
if (options.global) {
|
|
161
|
+
const globalRoot = options.geminiGlobalDir || path_1.default.join(ctx.homedir, '.gemini');
|
|
162
|
+
targetDir = path_1.default.join(globalRoot, 'skills', 'agentctl');
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
targetDir = path_1.default.join(ctx.cwd, '.gemini', 'skills', 'agentctl');
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
throw new Error(`Agent logic for '${agent}' not implemented.`);
|
|
170
|
+
}
|
|
171
|
+
return [{ type: 'installSkill', targetDir, agent }];
|
|
172
|
+
}
|
|
173
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.AppLogic = void 0;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
exports.AppLogic = {
|
|
9
|
+
planApp(args, result) {
|
|
10
|
+
if (!result) {
|
|
11
|
+
return [{ type: 'log', message: `Command '${args.join(' ')}' not found. Run 'agentctl list' to see available commands.` }];
|
|
12
|
+
}
|
|
13
|
+
const { manifest, args: remainingArgs, scope, manifestPath, cmdPath } = result;
|
|
14
|
+
const effects = [];
|
|
15
|
+
const allowedKeys = new Set(['name', 'description', 'help', 'type', 'run', 'flags']);
|
|
16
|
+
const unsupportedKeys = Object.keys(manifest).filter(k => !allowedKeys.has(k));
|
|
17
|
+
if (unsupportedKeys.length > 0) {
|
|
18
|
+
effects.push({
|
|
19
|
+
type: 'log',
|
|
20
|
+
message: `[WARNING] The manifest at ${manifestPath} contains unsupported keys: ${unsupportedKeys.join(', ')}. Agentctl will ignore them.`
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
if (manifest.run) {
|
|
24
|
+
const cmdDir = path_1.default.dirname(manifestPath);
|
|
25
|
+
let runCmd = manifest.run;
|
|
26
|
+
if (runCmd.startsWith('./') || runCmd.startsWith('.\\')) {
|
|
27
|
+
runCmd = path_1.default.resolve(cmdDir, runCmd);
|
|
28
|
+
}
|
|
29
|
+
runCmd = runCmd.replace(/{{DIR}}/g, cmdDir);
|
|
30
|
+
const fullCommand = `${runCmd} ${remainingArgs.join(' ')}`;
|
|
31
|
+
effects.push({ type: 'log', message: `[${scope}] Running: ${fullCommand}` }, {
|
|
32
|
+
type: 'spawn',
|
|
33
|
+
command: fullCommand,
|
|
34
|
+
options: {
|
|
35
|
+
cwd: process.cwd(),
|
|
36
|
+
shell: true,
|
|
37
|
+
stdio: 'inherit',
|
|
38
|
+
env: { ...process.env, AGENTCTL_SCOPE: scope }
|
|
39
|
+
},
|
|
40
|
+
onExit: (code) => {
|
|
41
|
+
process.exit(code || 0);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
return effects;
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
return [...effects, ...exports.AppLogic.planGroupList(manifest, cmdPath, [], manifestPath)];
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
planGroupList(manifest, cmdPath, allCommands, manifestPath) {
|
|
51
|
+
const effects = [];
|
|
52
|
+
if (manifestPath) {
|
|
53
|
+
const allowedKeys = new Set(['name', 'description', 'help', 'type', 'run', 'flags']);
|
|
54
|
+
const unsupportedKeys = Object.keys(manifest).filter(k => !allowedKeys.has(k));
|
|
55
|
+
if (unsupportedKeys.length > 0) {
|
|
56
|
+
effects.push({
|
|
57
|
+
type: 'log',
|
|
58
|
+
message: `[WARNING] The manifest at ${manifestPath} contains unsupported keys: ${unsupportedKeys.join(', ')}. Agentctl will ignore them.`
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
effects.push({ type: 'log', message: manifest.name }, { type: 'log', message: manifest.help || manifest.description || 'Command group containing subcommands' }, { type: 'log', message: '\nSubcommands:' });
|
|
63
|
+
const prefix = cmdPath + ' ';
|
|
64
|
+
const depth = cmdPath.split(' ').length;
|
|
65
|
+
const children = allCommands.filter(c => c.path.startsWith(prefix) && c.path !== cmdPath);
|
|
66
|
+
const direct = children.filter(c => c.path.split(' ').length === depth + 1);
|
|
67
|
+
if (direct.length === 0) {
|
|
68
|
+
effects.push({ type: 'log', message: ' (No subcommands found)' });
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
for (const child of direct) {
|
|
72
|
+
const name = child.path.split(' ').pop();
|
|
73
|
+
const desc = child.description || 'Command group containing subcommands';
|
|
74
|
+
effects.push({ type: 'log', message: ` ${name}\t${desc}` });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return effects;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.planInstallClone = planInstallClone;
|
|
7
|
+
exports.planInstallCopy = planInstallCopy;
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
function planInstallClone(ctx, deps) {
|
|
10
|
+
if (!ctx.global && !ctx.localRoot) {
|
|
11
|
+
throw new Error('Not in a local context. Run inside a project or use --global');
|
|
12
|
+
}
|
|
13
|
+
const tempDir = path_1.default.join(ctx.osTmpdir, deps.tempFolderName);
|
|
14
|
+
const effects = [];
|
|
15
|
+
// 1. Create a log message
|
|
16
|
+
effects.push({
|
|
17
|
+
type: 'log',
|
|
18
|
+
message: `Fetching ${ctx.repoUrl}...`
|
|
19
|
+
});
|
|
20
|
+
// 2. Clone the repository shallowly into a temp directory
|
|
21
|
+
effects.push({
|
|
22
|
+
type: 'gitClone',
|
|
23
|
+
url: ctx.repoUrl,
|
|
24
|
+
dest: tempDir
|
|
25
|
+
});
|
|
26
|
+
return { effects, tempDir };
|
|
27
|
+
}
|
|
28
|
+
function planInstallCopy(ctx, deps) {
|
|
29
|
+
if (!ctx.global && !ctx.localRoot) {
|
|
30
|
+
throw new Error('Not in a local context. Run inside a project or use --global');
|
|
31
|
+
}
|
|
32
|
+
const rootDir = ctx.global ? ctx.globalRoot : ctx.localRoot;
|
|
33
|
+
const agentctlDir = ctx.global ? rootDir : path_1.default.join(rootDir, '.agentctl');
|
|
34
|
+
const targetDir = path_1.default.join(agentctlDir, ...ctx.pathParts);
|
|
35
|
+
const effects = [];
|
|
36
|
+
if (!ctx.allowCollisions) {
|
|
37
|
+
// Simple collision detection based on paths relative to targetDir
|
|
38
|
+
const collisions = deps.existingItems.filter(item => deps.downloadedItems.includes(item));
|
|
39
|
+
if (collisions.length > 0) {
|
|
40
|
+
effects.push({ type: 'remove', path: path_1.default.dirname(deps.tempAgentctlDir) }); // cleanup
|
|
41
|
+
throw new Error(`Installation aborted due to collisions without --allow-collisions flag: \n${collisions.join('\n')}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// Ensure target path exists
|
|
45
|
+
effects.push({ type: 'mkdir', path: targetDir });
|
|
46
|
+
if (!ctx.global && ctx.isNewLocalRoot) {
|
|
47
|
+
effects.push({ type: 'log', message: `Initialized new .agentctl folder at ${agentctlDir}` });
|
|
48
|
+
}
|
|
49
|
+
// Copy contents
|
|
50
|
+
effects.push({
|
|
51
|
+
type: 'copy',
|
|
52
|
+
src: deps.tempAgentctlDir,
|
|
53
|
+
dest: targetDir,
|
|
54
|
+
options: { overwrite: ctx.allowCollisions }
|
|
55
|
+
});
|
|
56
|
+
// Clean up temp dir
|
|
57
|
+
effects.push({ type: 'remove', path: path_1.default.dirname(deps.tempAgentctlDir) });
|
|
58
|
+
effects.push({ type: 'log', message: `Successfully installed commands to ${targetDir}` });
|
|
59
|
+
return { effects };
|
|
60
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ResolveLogic = void 0;
|
|
4
|
+
exports.ResolveLogic = {
|
|
5
|
+
decide(local, global, isCapped) {
|
|
6
|
+
if (!local?.manifest && !global.manifest) {
|
|
7
|
+
return { currentMatch: null, finalResult: null, shouldStop: true };
|
|
8
|
+
}
|
|
9
|
+
const localManifest = local?.manifest;
|
|
10
|
+
const globalManifest = global.manifest;
|
|
11
|
+
// 1. Local Capped -> Return Match immediately.
|
|
12
|
+
if (localManifest && isCapped(localManifest)) {
|
|
13
|
+
return {
|
|
14
|
+
currentMatch: null,
|
|
15
|
+
finalResult: {
|
|
16
|
+
scope: 'local',
|
|
17
|
+
manifest: localManifest,
|
|
18
|
+
manifestPath: local.path
|
|
19
|
+
},
|
|
20
|
+
shouldStop: true
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
// 2. Global Capped
|
|
24
|
+
if (globalManifest && isCapped(globalManifest)) {
|
|
25
|
+
if (localManifest) {
|
|
26
|
+
// Local Group shadows Global Capped
|
|
27
|
+
return {
|
|
28
|
+
currentMatch: {
|
|
29
|
+
scope: 'local',
|
|
30
|
+
manifest: localManifest,
|
|
31
|
+
manifestPath: local.path
|
|
32
|
+
},
|
|
33
|
+
finalResult: null,
|
|
34
|
+
shouldStop: false
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
return {
|
|
39
|
+
currentMatch: null,
|
|
40
|
+
finalResult: {
|
|
41
|
+
scope: 'global',
|
|
42
|
+
manifest: globalManifest,
|
|
43
|
+
manifestPath: global.path
|
|
44
|
+
},
|
|
45
|
+
shouldStop: true
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// 3. Neither capped
|
|
50
|
+
if (localManifest) {
|
|
51
|
+
return {
|
|
52
|
+
currentMatch: {
|
|
53
|
+
scope: 'local',
|
|
54
|
+
manifest: localManifest,
|
|
55
|
+
manifestPath: local.path
|
|
56
|
+
},
|
|
57
|
+
finalResult: null,
|
|
58
|
+
shouldStop: false
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
return {
|
|
63
|
+
currentMatch: {
|
|
64
|
+
scope: 'global',
|
|
65
|
+
manifest: globalManifest,
|
|
66
|
+
manifestPath: global.path
|
|
67
|
+
},
|
|
68
|
+
finalResult: null,
|
|
69
|
+
shouldStop: false
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.SkillsLogic = void 0;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
exports.SkillsLogic = {
|
|
9
|
+
getSourcePaths(ctx) {
|
|
10
|
+
return [
|
|
11
|
+
path_1.default.resolve(ctx.dirname, '../../skills/agentctl/SKILL.md'),
|
|
12
|
+
path_1.default.resolve(ctx.dirname, '../skills/agentctl/SKILL.md'),
|
|
13
|
+
path_1.default.resolve(ctx.dirname, '../../../skills/agentctl/SKILL.md')
|
|
14
|
+
];
|
|
15
|
+
},
|
|
16
|
+
getTargetPath(targetDir, agent) {
|
|
17
|
+
const filename = agent === 'cursor' ? 'agentctl.md' : 'SKILL.md';
|
|
18
|
+
return path_1.default.join(targetDir, filename);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.UtilsLogic = void 0;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
exports.UtilsLogic = {
|
|
9
|
+
getGlobalRoot(ctx) {
|
|
10
|
+
if (ctx.platform === 'win32') {
|
|
11
|
+
return path_1.default.join(ctx.env.APPDATA || path_1.default.join(ctx.homedir, 'AppData', 'Roaming'), 'agentctl');
|
|
12
|
+
}
|
|
13
|
+
return path_1.default.join(ctx.homedir, '.config', 'agentctl');
|
|
14
|
+
},
|
|
15
|
+
getAntigravityGlobalRoot(ctx) {
|
|
16
|
+
return path_1.default.join(ctx.homedir, '.gemini', 'antigravity');
|
|
17
|
+
},
|
|
18
|
+
findLocalRoot(cwd, existsSync) {
|
|
19
|
+
let current = path_1.default.resolve(cwd);
|
|
20
|
+
const root = path_1.default.parse(current).root;
|
|
21
|
+
for (;;) {
|
|
22
|
+
if (existsSync(path_1.default.join(current, '.agentctl'))) {
|
|
23
|
+
return current;
|
|
24
|
+
}
|
|
25
|
+
if (current === root) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
current = path_1.default.dirname(current);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
};
|
package/dist/manifest.js
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
2
16
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
17
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
18
|
};
|
|
@@ -6,6 +20,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
20
|
exports.readManifest = readManifest;
|
|
7
21
|
exports.isCappedManifest = isCappedManifest;
|
|
8
22
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
23
|
+
const manifest_1 = require("./logic/manifest");
|
|
24
|
+
__exportStar(require("./logic/manifest"), exports);
|
|
9
25
|
async function readManifest(p) {
|
|
10
26
|
try {
|
|
11
27
|
return await fs_extra_1.default.readJson(p);
|
|
@@ -15,5 +31,5 @@ async function readManifest(p) {
|
|
|
15
31
|
}
|
|
16
32
|
}
|
|
17
33
|
function isCappedManifest(m) {
|
|
18
|
-
return
|
|
34
|
+
return manifest_1.ManifestLogic.isCappedManifest(m);
|
|
19
35
|
}
|
package/dist/resolve.js
CHANGED
|
@@ -8,104 +8,58 @@ const path_1 = __importDefault(require("path"));
|
|
|
8
8
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
9
9
|
const fs_utils_1 = require("./fs-utils");
|
|
10
10
|
const manifest_1 = require("./manifest");
|
|
11
|
+
const resolve_1 = require("./logic/resolve");
|
|
11
12
|
async function resolveCommand(args, options = {}) {
|
|
12
13
|
const cwd = options.cwd || process.cwd();
|
|
13
14
|
const localRoot = !options.global ? (0, fs_utils_1.findLocalRoot)(cwd) : null;
|
|
14
15
|
const globalRoot = options.globalDir || (0, fs_utils_1.getGlobalRoot)();
|
|
15
16
|
const localAgentctl = localRoot ? path_1.default.join(localRoot, '.agentctl') : null;
|
|
16
17
|
let currentMatch = null;
|
|
17
|
-
// Iterate through args to find longest match
|
|
18
18
|
for (let i = 0; i < args.length; i++) {
|
|
19
|
-
// Path corresponding to args[0..i]
|
|
20
19
|
const currentArgs = args.slice(0, i + 1);
|
|
21
20
|
const relPath = currentArgs.join(path_1.default.sep);
|
|
22
21
|
const cmdPath = currentArgs.join(' ');
|
|
23
22
|
const localPath = localAgentctl ? path_1.default.join(localAgentctl, relPath) : null;
|
|
24
23
|
const globalPath = path_1.default.join(globalRoot, relPath);
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
24
|
+
const getFacts = async (p) => {
|
|
25
|
+
if (!p || !(await fs_extra_1.default.pathExists(p)))
|
|
26
|
+
return null;
|
|
27
|
+
const mPath = path_1.default.join(p, 'manifest.json');
|
|
28
|
+
let manifest = null;
|
|
30
29
|
if (await fs_extra_1.default.pathExists(mPath)) {
|
|
31
|
-
|
|
30
|
+
manifest = await (0, manifest_1.readManifest)(mPath);
|
|
32
31
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
const stats = await fs_extra_1.default.stat(p);
|
|
33
|
+
const isDir = stats.isDirectory();
|
|
34
|
+
if (!manifest && isDir) {
|
|
35
|
+
manifest = { name: args[i], type: 'group' };
|
|
36
36
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
globalManifest = await (0, manifest_1.readManifest)(mPath);
|
|
43
|
-
}
|
|
44
|
-
if (!globalManifest && (await fs_extra_1.default.stat(globalPath)).isDirectory()) {
|
|
45
|
-
globalManifest = { name: args[i], type: 'group' };
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
if (!localManifest && !globalManifest) {
|
|
49
|
-
break;
|
|
50
|
-
}
|
|
37
|
+
return { exists: true, manifest, isDir, path: mPath };
|
|
38
|
+
};
|
|
39
|
+
const localFacts = await getFacts(localPath);
|
|
40
|
+
const globalFacts = (await getFacts(globalPath)) || { exists: false, manifest: null, isDir: false, path: path_1.default.join(globalPath, 'manifest.json') };
|
|
41
|
+
const decision = resolve_1.ResolveLogic.decide(localFacts, globalFacts, manifest_1.isCappedManifest);
|
|
51
42
|
const remainingArgs = args.slice(i + 1);
|
|
52
|
-
|
|
53
|
-
// 1. Local Capped -> Return Match immediately.
|
|
54
|
-
if (localManifest && (0, manifest_1.isCappedManifest)(localManifest)) {
|
|
43
|
+
if (decision.finalResult) {
|
|
55
44
|
return {
|
|
56
|
-
manifest:
|
|
57
|
-
manifestPath:
|
|
45
|
+
manifest: decision.finalResult.manifest,
|
|
46
|
+
manifestPath: decision.finalResult.manifestPath,
|
|
58
47
|
args: remainingArgs,
|
|
59
|
-
scope:
|
|
48
|
+
scope: decision.finalResult.scope,
|
|
60
49
|
cmdPath
|
|
61
50
|
};
|
|
62
51
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
manifestPath: path_1.default.join(localPath, 'manifest.json'),
|
|
72
|
-
args: remainingArgs,
|
|
73
|
-
scope: 'local',
|
|
74
|
-
cmdPath
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
else {
|
|
78
|
-
// Not shadowed. Global Capped wins. Return immediately.
|
|
79
|
-
return {
|
|
80
|
-
manifest: globalManifest,
|
|
81
|
-
manifestPath: path_1.default.join(globalPath, 'manifest.json'),
|
|
82
|
-
args: remainingArgs,
|
|
83
|
-
scope: 'global',
|
|
84
|
-
cmdPath
|
|
85
|
-
};
|
|
86
|
-
}
|
|
52
|
+
if (decision.currentMatch) {
|
|
53
|
+
currentMatch = {
|
|
54
|
+
manifest: decision.currentMatch.manifest,
|
|
55
|
+
manifestPath: decision.currentMatch.manifestPath,
|
|
56
|
+
args: remainingArgs,
|
|
57
|
+
scope: decision.currentMatch.scope,
|
|
58
|
+
cmdPath
|
|
59
|
+
};
|
|
87
60
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
// Local wins if exists.
|
|
91
|
-
if (localManifest) {
|
|
92
|
-
currentMatch = {
|
|
93
|
-
manifest: localManifest,
|
|
94
|
-
manifestPath: path_1.default.join(localPath, 'manifest.json'),
|
|
95
|
-
args: remainingArgs,
|
|
96
|
-
scope: 'local',
|
|
97
|
-
cmdPath
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
else {
|
|
101
|
-
currentMatch = {
|
|
102
|
-
manifest: globalManifest,
|
|
103
|
-
manifestPath: path_1.default.join(globalPath, 'manifest.json'),
|
|
104
|
-
args: remainingArgs,
|
|
105
|
-
scope: 'global',
|
|
106
|
-
cmdPath
|
|
107
|
-
};
|
|
108
|
-
}
|
|
61
|
+
if (decision.shouldStop) {
|
|
62
|
+
break;
|
|
109
63
|
}
|
|
110
64
|
}
|
|
111
65
|
return currentMatch;
|