@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
package/dist/index.js
CHANGED
|
@@ -5,129 +5,54 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
};
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
7
|
const commander_1 = require("commander");
|
|
8
|
-
const
|
|
9
|
-
require("fs-extra");
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
10
9
|
const ctl_1 = require("./ctl");
|
|
11
10
|
const resolve_1 = require("./resolve");
|
|
12
|
-
const
|
|
13
|
-
const
|
|
11
|
+
const effects_1 = require("./effects");
|
|
12
|
+
const index_1 = require("./logic/index");
|
|
13
|
+
const fs_1 = __importDefault(require("fs"));
|
|
14
|
+
const path_1 = __importDefault(require("path"));
|
|
15
|
+
const pkgPath = path_1.default.join(__dirname, '../package.json');
|
|
16
|
+
const pkg = JSON.parse(fs_1.default.readFileSync(pkgPath, 'utf8'));
|
|
14
17
|
const program = new commander_1.Command();
|
|
15
|
-
|
|
18
|
+
program.addHelpCommand(false);
|
|
16
19
|
program
|
|
17
20
|
.name('agentctl')
|
|
18
21
|
.description('Agent Controller CLI - Unified control plane for humans and agents')
|
|
19
|
-
.version(pkg.version)
|
|
20
|
-
|
|
21
|
-
.helpOption(false) // Disable default help to allow pass-through
|
|
22
|
-
.argument('[command...]', 'Command to run')
|
|
23
|
-
.action(async (args, _options, _command) => {
|
|
24
|
-
// If no args, check for help flag or just show help
|
|
25
|
-
if (!args || args.length === 0) {
|
|
26
|
-
// If they passed --help or -h, show help. If no args at all, show help.
|
|
27
|
-
// Since we ate options, we check raw args or just treat empty args as help.
|
|
28
|
-
// command.opts() won't have help if we disabled it?
|
|
29
|
-
// Actually, if we disable helpOption, --help becomes an unknown option or arg.
|
|
30
|
-
// Let's check process.argv for -h or --help if args is empty?
|
|
31
|
-
// "agentctl --help" -> args=[], options might contain help if we didn't disable it?
|
|
32
|
-
// With helpOption(false), --help is just a flag in argv.
|
|
33
|
-
// If args is empty and we see help flag, show help.
|
|
34
|
-
if (process.argv.includes('--help') || process.argv.includes('-h') || process.argv.length <= 2) {
|
|
35
|
-
program.help();
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
// If args are present, we try to resolve.
|
|
40
|
-
// BUT, "agentctl --help" will result in args being empty if it's parsed as option?
|
|
41
|
-
// Wait, if helpOption(false), then --help is an unknown option.
|
|
42
|
-
// If allowUnknownOption is true, it might not be in 'args' if it looks like a flag.
|
|
43
|
-
// Let's rely on resolveCommand. passed args are variadic.
|
|
44
|
-
// However, "agentctl dev --help" -> args=["dev", "--help"]?
|
|
45
|
-
// My repro says yes: [ 'dev-tools', 'gh', '--help' ].
|
|
46
|
-
// So for "agentctl --help", args might be ["--help"].
|
|
47
|
-
if (args.length === 1 && (args[0] === '--help' || args[0] === '-h')) {
|
|
48
|
-
program.help();
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
// Bypass for ctl subcommand if it slipped through (shouldn't if registered)
|
|
52
|
-
if (args[0] === 'ctl')
|
|
53
|
-
return;
|
|
54
|
-
try {
|
|
55
|
-
// resolveCommand needs to handle flags in args if they are part of the path?
|
|
56
|
-
// No, flags usually come after. resolveCommand stops at first non-matching path part?
|
|
57
|
-
// resolveCommand logic: iterates args.
|
|
58
|
-
// "dev-tools gh --help" -> path "dev-tools gh", remaining "--help"
|
|
59
|
-
const result = await (0, resolve_1.resolveCommand)(args);
|
|
60
|
-
if (!result) {
|
|
61
|
-
// If not found, and they asked for help, show root help?
|
|
62
|
-
// Or if they just typed a wrong command.
|
|
63
|
-
if (args.includes('--help') || args.includes('-h')) {
|
|
64
|
-
// Try to show help for the partial command?
|
|
65
|
-
// For now, just show root list/help or error.
|
|
66
|
-
// If it's "agentctl dev --help" and "dev" is a group, resolveCommand SHOULD return the group.
|
|
67
|
-
}
|
|
68
|
-
console.error(chalk_1.default.red(`Command '${args.join(' ')}' not found.`));
|
|
69
|
-
console.log(`Run ${chalk_1.default.cyan('agentctl list')} to see available commands.`);
|
|
70
|
-
process.exit(1);
|
|
71
|
-
}
|
|
72
|
-
const { manifest, args: remainingArgs, scope } = result;
|
|
73
|
-
if (manifest.run) {
|
|
74
|
-
// ... run logic ...
|
|
75
|
-
// remainingArgs should contain --help if it was passed.
|
|
76
|
-
const cmdDir = path_1.default.dirname(result.manifestPath);
|
|
77
|
-
let runCmd = manifest.run;
|
|
78
|
-
// Resolve relative path
|
|
79
|
-
if (runCmd.startsWith('./') || runCmd.startsWith('.\\')) {
|
|
80
|
-
runCmd = path_1.default.resolve(cmdDir, runCmd);
|
|
81
|
-
}
|
|
82
|
-
// Interpolate {{DIR}}
|
|
83
|
-
runCmd = runCmd.replace(/{{DIR}}/g, cmdDir);
|
|
84
|
-
const fullCommand = `${runCmd} ${remainingArgs.join(' ')}`;
|
|
85
|
-
console.log(chalk_1.default.dim(`[${scope}] Running: ${fullCommand}`));
|
|
86
|
-
const child = (0, child_process_1.spawn)(fullCommand, {
|
|
87
|
-
cwd: process.cwd(), // Execute in CWD as discussed
|
|
88
|
-
shell: true,
|
|
89
|
-
stdio: 'inherit',
|
|
90
|
-
env: { ...process.env, AGENTCTL_SCOPE: scope }
|
|
91
|
-
});
|
|
92
|
-
child.on('exit', (code) => {
|
|
93
|
-
process.exit(code || 0);
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
else {
|
|
97
|
-
// Group
|
|
98
|
-
console.log(chalk_1.default.blue(chalk_1.default.bold(`${manifest.name}`)));
|
|
99
|
-
console.log(manifest.description || 'No description');
|
|
100
|
-
console.log('\nSubcommands:');
|
|
101
|
-
const all = await (0, ctl_1.list)();
|
|
102
|
-
const prefix = result.cmdPath + ' ';
|
|
103
|
-
// Filter logic roughly for direct children
|
|
104
|
-
const depth = result.cmdPath.split(' ').length;
|
|
105
|
-
const children = all.filter(c => c.path.startsWith(prefix) && c.path !== result.cmdPath);
|
|
106
|
-
const direct = children.filter(c => c.path.split(' ').length === depth + 1);
|
|
107
|
-
if (direct.length === 0 && children.length === 0) {
|
|
108
|
-
console.log(chalk_1.default.dim(' (No subcommands found)'));
|
|
109
|
-
}
|
|
110
|
-
for (const child of direct) {
|
|
111
|
-
console.log(` ${child.path.split(' ').pop()}\t${chalk_1.default.dim(child.description)}`);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
catch (e) {
|
|
116
|
-
if (e instanceof Error) {
|
|
117
|
-
console.error(chalk_1.default.red(e.message));
|
|
118
|
-
}
|
|
119
|
-
else {
|
|
120
|
-
console.error(chalk_1.default.red('An unknown error occurred'));
|
|
121
|
-
}
|
|
122
|
-
process.exit(1);
|
|
123
|
-
}
|
|
124
|
-
});
|
|
22
|
+
.version(pkg.version);
|
|
23
|
+
// --- Subcommand: ctl ---
|
|
125
24
|
const ctl = program.command('ctl')
|
|
126
|
-
.description('Agent Controller Management - Create, organize, and manage commands')
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
25
|
+
.description('Agent Controller Management - Create, organize, and manage commands')
|
|
26
|
+
.addHelpCommand(false)
|
|
27
|
+
.configureHelp({ visibleCommands: () => [] })
|
|
28
|
+
.addHelpText('after', `
|
|
29
|
+
${chalk_1.default.bold('Agentctl Paradigm:')}
|
|
30
|
+
Agentctl acts as a unified control plane allowing both Humans and AI Agents
|
|
31
|
+
to create, discover, and execute local shell commands. By running \`agentctl ctl scaffold <name>\`
|
|
32
|
+
you create a directory in the \`.agentctl\` folder containing a \`manifest.json\`
|
|
33
|
+
and a run script. This dynamically creates a new \`agentctl <name>\` command that
|
|
34
|
+
is easily callable by agents and globally executable on your machine.
|
|
35
|
+
|
|
36
|
+
Commands:
|
|
37
|
+
|
|
38
|
+
${chalk_1.default.bold('Creation')}
|
|
39
|
+
scaffold [path...] create a new command
|
|
40
|
+
alias [args...] create a shell alias
|
|
41
|
+
group [path...] create a namespace group
|
|
42
|
+
|
|
43
|
+
${chalk_1.default.bold('Organize & Scope')}
|
|
44
|
+
rm [options] [path...] delete a command
|
|
45
|
+
mv [options] [src] [dest] rename/move a command
|
|
46
|
+
global [options] [path...] make a command global
|
|
47
|
+
local [options] [path...] make a command local
|
|
48
|
+
|
|
49
|
+
${chalk_1.default.bold('Information')}
|
|
50
|
+
list list all commands
|
|
51
|
+
inspect [path...] inspect command details
|
|
52
|
+
|
|
53
|
+
${chalk_1.default.bold('Integration')}
|
|
54
|
+
install [options] [repoUrl] [pathParts...] install remote command group
|
|
55
|
+
`);
|
|
131
56
|
const withErrorHandling = (fn) => {
|
|
132
57
|
return async (...args) => {
|
|
133
58
|
try {
|
|
@@ -144,208 +69,322 @@ const withErrorHandling = (fn) => {
|
|
|
144
69
|
}
|
|
145
70
|
};
|
|
146
71
|
};
|
|
72
|
+
const normalizePath = (parts) => parts.flatMap(p => p.split(/[\s/\\:]+/).filter(Boolean));
|
|
147
73
|
ctl.command('scaffold')
|
|
148
|
-
.description('Scaffold a new command
|
|
149
|
-
.argument('[path...]', '
|
|
74
|
+
.description('Scaffold a new command directory with a manifest and starter script.')
|
|
75
|
+
.argument('[path...]', 'Hierarchical path for the new command (e.g., "dev start" or "utils/cleanup")')
|
|
76
|
+
.summary('create a new command')
|
|
150
77
|
.addHelpText('after', `
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
78
|
+
Additional Info:
|
|
79
|
+
This command creates a folder in your local .agentctl directory.
|
|
80
|
+
Inside this newly created folder, it generates:
|
|
81
|
+
- manifest.json: Metadata config to edit for your command.
|
|
82
|
+
- command.sh/cmd: A starter script to edit for your actual logic.
|
|
83
|
+
|
|
84
|
+
Manifest Schema (manifest.json) to edit:
|
|
85
|
+
{
|
|
86
|
+
"name": "<command_folder_name>",
|
|
87
|
+
"description": "<insert command summary here>",
|
|
88
|
+
"help": "<insert longer usage/help instructions here>",
|
|
89
|
+
"type": "scaffold", // do not change!
|
|
90
|
+
"run": "./command.cmd" // points to the script to execute
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
Note:
|
|
94
|
+
- The "description" is displayed when you view this command in a list.
|
|
95
|
+
- Commands must provide their own help implementation (e.g. by handling --help inside your script).
|
|
155
96
|
|
|
156
97
|
Examples:
|
|
157
|
-
$ agentctl ctl scaffold
|
|
158
|
-
$ agentctl ctl scaffold
|
|
98
|
+
$ agentctl ctl scaffold build:front
|
|
99
|
+
$ agentctl ctl scaffold "build front" # Creates group 'build' and subcommand 'front'
|
|
159
100
|
`)
|
|
160
|
-
.action(withErrorHandling(async (pathParts,
|
|
161
|
-
|
|
101
|
+
.action(withErrorHandling(async (pathParts, opts, command) => {
|
|
102
|
+
const normalized = normalizePath(pathParts);
|
|
103
|
+
if (normalized.length === 0) {
|
|
162
104
|
command.help();
|
|
163
105
|
return;
|
|
164
106
|
}
|
|
165
|
-
await (0, ctl_1.scaffold)(
|
|
107
|
+
await (0, ctl_1.scaffold)(normalized);
|
|
166
108
|
}));
|
|
167
109
|
ctl.command('alias')
|
|
168
|
-
.description('Create
|
|
169
|
-
.argument('[
|
|
170
|
-
.
|
|
110
|
+
.description('Create a command that executes a raw shell string.')
|
|
111
|
+
.argument('[args...]', 'Hierarchical path segments followed by the shell command target')
|
|
112
|
+
.summary('create a shell alias')
|
|
113
|
+
.addHelpText('after', `
|
|
114
|
+
Examples:
|
|
115
|
+
$ agentctl ctl alias dev logs "docker compose logs -f"
|
|
116
|
+
$ agentctl ctl alias list-files "ls -la"
|
|
117
|
+
`)
|
|
118
|
+
.action(withErrorHandling(async (args, opts, command) => {
|
|
171
119
|
if (!args || args.length < 2) {
|
|
172
120
|
command.help();
|
|
173
121
|
return;
|
|
174
122
|
}
|
|
175
123
|
const target = args.pop();
|
|
176
|
-
const name = args;
|
|
124
|
+
const name = normalizePath(args);
|
|
125
|
+
if (name.length === 0) {
|
|
126
|
+
command.help();
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
177
129
|
await (0, ctl_1.alias)(name, target);
|
|
178
|
-
}))
|
|
179
|
-
.addHelpText('after', `
|
|
180
|
-
How it works:
|
|
181
|
-
The last argument is always treated as the shell command to execute.
|
|
182
|
-
All preceding arguments form the hierarchical path.
|
|
183
|
-
|
|
184
|
-
If the shell command contains spaces, wrap it in quotes.
|
|
185
|
-
|
|
186
|
-
Examples:
|
|
187
|
-
$ agentctl ctl alias tools git-status "git status"
|
|
188
|
-
-> Creates 'agentctl tools git-status' which runs 'git status'.
|
|
189
|
-
|
|
190
|
-
$ agentctl ctl alias dev build "npm run build"
|
|
191
|
-
-> Creates 'agentctl dev build' which runs 'npm run build'.
|
|
192
|
-
`);
|
|
130
|
+
}));
|
|
193
131
|
ctl.command('group')
|
|
194
|
-
.description('Create a command group (namespace) to organize
|
|
195
|
-
.argument('[path...]', 'Hierarchical path for the group (e.g
|
|
132
|
+
.description('Create a command group (namespace) to organize subcommands.')
|
|
133
|
+
.argument('[path...]', 'Hierarchical path for the group (e.g., "dev" or "cloud/aws")')
|
|
134
|
+
.summary('create a namespace group')
|
|
196
135
|
.addHelpText('after', `
|
|
197
|
-
|
|
198
|
-
Groups
|
|
199
|
-
|
|
136
|
+
Additional Info:
|
|
137
|
+
Groups allow you to categorize commands. Running a group command without
|
|
138
|
+
subcommands will list all direct subcommands within that group.
|
|
139
|
+
|
|
140
|
+
This command creates a folder in your local .agentctl directory.
|
|
141
|
+
Inside this newly created folder, it generates a manifest.json.
|
|
142
|
+
|
|
143
|
+
Group Schema (manifest.json) to edit:
|
|
144
|
+
{
|
|
145
|
+
"name": "<group_folder_name>",
|
|
146
|
+
"description": "<insert group summary here>",
|
|
147
|
+
"help": "<insert longer group description/instructions here>",
|
|
148
|
+
"type": "group" // do not change!
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
Note:
|
|
152
|
+
- The "description" is displayed when you view this group in a list.
|
|
153
|
+
- The "help" is displayed when you call this group without a subcommand.
|
|
200
154
|
|
|
201
155
|
Examples:
|
|
202
156
|
$ agentctl ctl group dev
|
|
203
|
-
$ agentctl ctl group
|
|
157
|
+
$ agentctl ctl group "data pipelines" # Creates group 'data' and subgroup 'pipelines'
|
|
204
158
|
`)
|
|
205
|
-
.action(withErrorHandling(async (parts,
|
|
206
|
-
|
|
159
|
+
.action(withErrorHandling(async (parts, opts, command) => {
|
|
160
|
+
const normalized = normalizePath(parts);
|
|
161
|
+
if (normalized.length === 0) {
|
|
207
162
|
command.help();
|
|
208
163
|
return;
|
|
209
164
|
}
|
|
210
|
-
await (0, ctl_1.group)(
|
|
165
|
+
await (0, ctl_1.group)(normalized);
|
|
211
166
|
}));
|
|
212
167
|
ctl.command('rm')
|
|
213
|
-
.description('
|
|
168
|
+
.description('Permanently remove a command or group.')
|
|
214
169
|
.argument('[path...]', 'Command path to remove')
|
|
215
|
-
.option('--global', 'Remove from global scope')
|
|
170
|
+
.option('-g, --global', 'Remove from global scope instead of local')
|
|
171
|
+
.summary('delete a command')
|
|
216
172
|
.addHelpText('after', `
|
|
217
173
|
Examples:
|
|
218
|
-
|
|
219
|
-
$ agentctl ctl rm
|
|
220
|
-
`)
|
|
174
|
+
$ agentctl ctl rm dev start
|
|
175
|
+
$ agentctl ctl rm utils--global
|
|
176
|
+
`)
|
|
221
177
|
.action(withErrorHandling(async (parts, opts, command) => {
|
|
222
|
-
|
|
178
|
+
const normalized = normalizePath(parts);
|
|
179
|
+
if (normalized.length === 0) {
|
|
223
180
|
command.help();
|
|
224
181
|
return;
|
|
225
182
|
}
|
|
226
|
-
await (0, ctl_1.rm)(
|
|
183
|
+
await (0, ctl_1.rm)(normalized, { global: opts.global });
|
|
227
184
|
}));
|
|
228
185
|
ctl.command('mv')
|
|
229
|
-
.description('Move or rename a command
|
|
230
|
-
.argument('[src]', 'Current path
|
|
231
|
-
.argument('[dest]', 'New path
|
|
232
|
-
.option('--global', '
|
|
186
|
+
.description('Move or rename a command/group within its current scope.')
|
|
187
|
+
.argument('[src]', 'Current path (space-separated or quoted)')
|
|
188
|
+
.argument('[dest]', 'New path (space-separated or quoted)')
|
|
189
|
+
.option('-g, --global', 'Operate in global scope')
|
|
190
|
+
.summary('rename/move a command')
|
|
233
191
|
.addHelpText('after', `
|
|
234
192
|
Examples:
|
|
235
|
-
|
|
236
|
-
$ agentctl ctl mv
|
|
237
|
-
`)
|
|
193
|
+
$ agentctl ctl mv "dev start" "dev begin"
|
|
194
|
+
$ agentctl ctl mv utils scripts--global
|
|
195
|
+
`)
|
|
238
196
|
.action(withErrorHandling(async (src, dest, opts, command) => {
|
|
239
|
-
|
|
197
|
+
const normalizedSrc = normalizePath(src ? [src] : []);
|
|
198
|
+
const normalizedDest = normalizePath(dest ? [dest] : []);
|
|
199
|
+
if (normalizedSrc.length === 0 || normalizedDest.length === 0) {
|
|
240
200
|
command.help();
|
|
241
201
|
return;
|
|
242
202
|
}
|
|
243
|
-
await (0, ctl_1.mv)(
|
|
203
|
+
await (0, ctl_1.mv)(normalizedSrc, normalizedDest, { global: opts.global });
|
|
244
204
|
}));
|
|
245
|
-
// --- Introspection ---
|
|
246
205
|
ctl.command('list')
|
|
247
|
-
.description('List all available commands across local and global scopes')
|
|
206
|
+
.description('List all available commands across local and global scopes.')
|
|
207
|
+
.summary('list all commands')
|
|
208
|
+
.addHelpText('after', `
|
|
209
|
+
Output Columns:
|
|
210
|
+
TYPE - scaffold, alias, or group
|
|
211
|
+
SCOPE - local(project - specific) or global(user - wide)
|
|
212
|
+
COMMAND - The path used to invoke the command
|
|
213
|
+
DESCRIPTION - Brief text from the command's manifest
|
|
214
|
+
`)
|
|
248
215
|
.action(withErrorHandling(async () => {
|
|
249
216
|
const items = await (0, ctl_1.list)();
|
|
250
|
-
console.log('TYPE SCOPE COMMAND DESCRIPTION');
|
|
217
|
+
console.log(chalk_1.default.bold('TYPE SCOPE COMMAND DESCRIPTION'));
|
|
251
218
|
for (const item of items) {
|
|
252
|
-
|
|
219
|
+
const typePipe = item.type.padEnd(9);
|
|
220
|
+
const scopePipe = item.scope === 'local' ? chalk_1.default.cyan(item.scope.padEnd(9)) : chalk_1.default.magenta(item.scope.padEnd(9));
|
|
221
|
+
console.log(`${typePipe} ${scopePipe} ${chalk_1.default.yellow(item.path.padEnd(19))} ${item.description}`);
|
|
253
222
|
}
|
|
254
223
|
}));
|
|
255
224
|
ctl.command('inspect')
|
|
256
|
-
.description('
|
|
225
|
+
.description('Show the internal manifest and file system path of a command.')
|
|
257
226
|
.argument('[path...]', 'Command path to inspect')
|
|
258
|
-
.
|
|
259
|
-
|
|
227
|
+
.summary('inspect command details')
|
|
228
|
+
.addHelpText('after', `
|
|
229
|
+
Examples:
|
|
230
|
+
$ agentctl ctl inspect dev start
|
|
231
|
+
`)
|
|
232
|
+
.action(withErrorHandling(async (parts, opts, command) => {
|
|
233
|
+
const normalized = normalizePath(parts);
|
|
234
|
+
if (normalized.length === 0) {
|
|
260
235
|
command.help();
|
|
261
236
|
return;
|
|
262
237
|
}
|
|
263
|
-
const info = await (0, ctl_1.inspect)(
|
|
238
|
+
const info = await (0, ctl_1.inspect)(normalized);
|
|
264
239
|
if (info) {
|
|
265
240
|
console.log(JSON.stringify(info, null, 2));
|
|
266
241
|
}
|
|
267
242
|
else {
|
|
268
|
-
console.error('Command not found');
|
|
243
|
+
console.error(chalk_1.default.red('Command not found'));
|
|
269
244
|
process.exit(1);
|
|
270
245
|
}
|
|
271
246
|
}));
|
|
272
|
-
// --- Scoping ---
|
|
273
247
|
ctl.command('global')
|
|
274
|
-
.description('
|
|
275
|
-
.argument('[path...]', 'Local command path')
|
|
276
|
-
.option('--move', 'Move
|
|
277
|
-
.option('--copy', 'Copy (default)')
|
|
248
|
+
.description('Promote a local command to the global scope.')
|
|
249
|
+
.argument('[path...]', 'Local command path to promote')
|
|
250
|
+
.option('-m, --move', 'Move the command (delete local after copying)')
|
|
251
|
+
.option('-c, --copy', 'Copy the command (keep local version, default)')
|
|
252
|
+
.summary('make a command global')
|
|
278
253
|
.addHelpText('after', `
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
254
|
+
Additional Info:
|
|
255
|
+
Global commands are stored in your home directory and are available in any project.
|
|
256
|
+
|
|
257
|
+
Examples:
|
|
258
|
+
$ agentctl ctl global utils / cleanup
|
|
259
|
+
$ agentctl ctl global dev / deploy--move
|
|
260
|
+
`)
|
|
283
261
|
.action(withErrorHandling(async (parts, opts, command) => {
|
|
284
|
-
|
|
262
|
+
const normalized = normalizePath(parts);
|
|
263
|
+
if (normalized.length === 0) {
|
|
285
264
|
command.help();
|
|
286
265
|
return;
|
|
287
266
|
}
|
|
288
|
-
await (0, ctl_1.pushGlobal)(
|
|
267
|
+
await (0, ctl_1.pushGlobal)(normalized, { move: opts.move, copy: opts.copy || !opts.move });
|
|
289
268
|
}));
|
|
290
269
|
ctl.command('local')
|
|
291
|
-
.description('Pull a global command
|
|
292
|
-
.argument('[path...]', 'Global command path')
|
|
293
|
-
.option('--move', 'Move
|
|
294
|
-
.option('--copy', 'Copy (default)')
|
|
270
|
+
.description('Pull a global command into the current local project.')
|
|
271
|
+
.argument('[path...]', 'Global command path to pull')
|
|
272
|
+
.option('-m, --move', 'Move the command (delete global after pulling)')
|
|
273
|
+
.option('-c, --copy', 'Copy the command (keep global version, default)')
|
|
274
|
+
.summary('make a command local')
|
|
295
275
|
.addHelpText('after', `
|
|
296
276
|
Examples:
|
|
297
|
-
|
|
298
|
-
|
|
277
|
+
$ agentctl ctl local utils / shared
|
|
278
|
+
$ agentctl ctl local snippets / js--move
|
|
279
|
+
`)
|
|
299
280
|
.action(withErrorHandling(async (parts, opts, command) => {
|
|
300
|
-
|
|
281
|
+
const normalized = normalizePath(parts);
|
|
282
|
+
if (normalized.length === 0) {
|
|
301
283
|
command.help();
|
|
302
284
|
return;
|
|
303
285
|
}
|
|
304
|
-
await (0, ctl_1.pullLocal)(
|
|
286
|
+
await (0, ctl_1.pullLocal)(normalized, { move: opts.move, copy: opts.copy || !opts.move });
|
|
305
287
|
}));
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
.option('--
|
|
288
|
+
ctl.command('install')
|
|
289
|
+
.description('Install a command group from a remote Git repository.')
|
|
290
|
+
.argument('[repoUrl]', 'URL of the remote Git repository containing an .agentctl folder')
|
|
291
|
+
.argument('[pathParts...]', 'Optional local namespace/group to install into')
|
|
292
|
+
.option('-g, --global', 'Install globally instead of locally')
|
|
293
|
+
.option('--allow-collisions', 'Allow overwriting existing commands or merging into groups')
|
|
294
|
+
.summary('install remote command group')
|
|
312
295
|
.addHelpText('after', `
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
296
|
+
Additional Info:
|
|
297
|
+
Fetches the.agentctl folder from the remote repository and installs it into
|
|
298
|
+
your local or global agentctl environment.
|
|
299
|
+
|
|
300
|
+
Examples:
|
|
301
|
+
$ agentctl ctl install https://github.com/org/repo-tools
|
|
302
|
+
$ agentctl ctl install https://github.com/org/deploy-scripts deploy --global
|
|
303
|
+
`)
|
|
304
|
+
.action(withErrorHandling(async (repoUrl, pathParts, opts, command) => {
|
|
305
|
+
if (!repoUrl) {
|
|
306
|
+
command.help();
|
|
307
|
+
return;
|
|
322
308
|
}
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
309
|
+
const normalized = normalizePath(pathParts || []);
|
|
310
|
+
await (0, ctl_1.install)(repoUrl, normalized, { global: opts.global, allowCollisions: opts.allowCollisions });
|
|
311
|
+
}));
|
|
312
|
+
// --- Dynamic Command Logic ---
|
|
313
|
+
async function handleDynamicCommand(args) {
|
|
314
|
+
try {
|
|
315
|
+
const result = await (0, resolve_1.resolveCommand)(args);
|
|
316
|
+
if (!result) {
|
|
317
|
+
const effects = index_1.AppLogic.planApp(args, null);
|
|
318
|
+
await (0, effects_1.execute)(effects.map(e => e.type === 'log' ? { ...e, message: chalk_1.default.red(e.message) } : e));
|
|
319
|
+
process.exit(1);
|
|
320
|
+
}
|
|
321
|
+
if (result.manifest.run) {
|
|
322
|
+
const effects = index_1.AppLogic.planApp(args, result);
|
|
323
|
+
await (0, effects_1.execute)(effects);
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
326
|
+
const all = await (0, ctl_1.list)();
|
|
327
|
+
const effects = index_1.AppLogic.planGroupList(result.manifest, result.cmdPath, all);
|
|
328
|
+
await (0, effects_1.execute)(effects.map(e => {
|
|
329
|
+
if (e.type === 'log') {
|
|
330
|
+
if (e.message === result.manifest.name)
|
|
331
|
+
return { ...e, message: chalk_1.default.blue(chalk_1.default.bold(e.message)) };
|
|
332
|
+
if (e.message === '\nSubcommands:')
|
|
333
|
+
return e;
|
|
334
|
+
if (e.message.startsWith(' '))
|
|
335
|
+
return e;
|
|
336
|
+
if (e.message === 'Command group containing subcommands')
|
|
337
|
+
return { ...e, message: chalk_1.default.dim(e.message) };
|
|
338
|
+
}
|
|
339
|
+
return e;
|
|
340
|
+
}));
|
|
327
341
|
}
|
|
328
342
|
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
343
|
+
catch (e) {
|
|
344
|
+
if (e instanceof Error) {
|
|
345
|
+
console.error(chalk_1.default.red(e.message));
|
|
346
|
+
}
|
|
347
|
+
else {
|
|
348
|
+
console.error(chalk_1.default.red('An unknown error occurred'));
|
|
349
|
+
}
|
|
350
|
+
process.exit(1);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
332
353
|
(async () => {
|
|
354
|
+
// Add help text for user commands dynamically
|
|
333
355
|
try {
|
|
334
356
|
const allCommands = await (0, ctl_1.list)();
|
|
335
|
-
const topLevel = allCommands.filter(c => !c.path.includes(' '));
|
|
357
|
+
const topLevel = allCommands.filter(c => !c.path.includes(' '));
|
|
336
358
|
if (topLevel.length > 0) {
|
|
337
359
|
const lines = [''];
|
|
338
|
-
lines.push('User Commands:');
|
|
360
|
+
lines.push(chalk_1.default.bold('User Commands:'));
|
|
339
361
|
for (const cmd of topLevel) {
|
|
340
|
-
|
|
341
|
-
lines.push(` ${cmd.path.padEnd(27)}${
|
|
362
|
+
const desc = cmd.description || 'Command group containing subcommands';
|
|
363
|
+
lines.push(` ${chalk_1.default.yellow(cmd.path.padEnd(27))}${desc}`);
|
|
342
364
|
}
|
|
343
365
|
lines.push('');
|
|
344
366
|
program.addHelpText('after', lines.join('\n'));
|
|
345
367
|
}
|
|
346
368
|
}
|
|
347
369
|
catch {
|
|
348
|
-
// Ignore errors during help generation
|
|
370
|
+
// Ignore errors during help generation
|
|
371
|
+
}
|
|
372
|
+
// Process arguments
|
|
373
|
+
const args = process.argv.slice(2);
|
|
374
|
+
// If no args, show help
|
|
375
|
+
if (args.length === 0) {
|
|
376
|
+
program.help();
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
// Special case: if it starts with 'ctl', or is '--help', '--version', '-h', etc.,
|
|
380
|
+
// let commander handle it natively.
|
|
381
|
+
const firstArg = args[0];
|
|
382
|
+
const isStandardCommand = ['ctl', '--help', '-h', '--version', '-V'].includes(firstArg) || firstArg.startsWith('-');
|
|
383
|
+
if (isStandardCommand) {
|
|
384
|
+
program.parse(process.argv);
|
|
385
|
+
}
|
|
386
|
+
else {
|
|
387
|
+
// It's a dynamic command
|
|
388
|
+
await handleDynamicCommand(args);
|
|
349
389
|
}
|
|
350
|
-
program.parse(process.argv);
|
|
351
390
|
})();
|