@michaelhartmayer/agentctl 1.0.3 → 1.1.3

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/dist/fs-utils.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,30 +20,23 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
20
  exports.getGlobalRoot = getGlobalRoot;
7
21
  exports.getAntigravityGlobalRoot = getAntigravityGlobalRoot;
8
22
  exports.findLocalRoot = findLocalRoot;
9
- const path_1 = __importDefault(require("path"));
10
23
  const os_1 = __importDefault(require("os"));
11
24
  const fs_extra_1 = __importDefault(require("fs-extra"));
25
+ const utils_1 = require("./logic/utils");
26
+ __exportStar(require("./logic/utils"), exports);
27
+ function getRealContext() {
28
+ return {
29
+ platform: process.platform,
30
+ env: process.env,
31
+ homedir: process.env.HOME || process.env.USERPROFILE || os_1.default.homedir()
32
+ };
33
+ }
12
34
  function getGlobalRoot() {
13
- if (process.platform === 'win32') {
14
- return path_1.default.join(process.env.APPDATA || path_1.default.join(os_1.default.homedir(), 'AppData', 'Roaming'), 'agentctl');
15
- }
16
- return path_1.default.join(os_1.default.homedir(), '.config', 'agentctl');
35
+ return utils_1.UtilsLogic.getGlobalRoot(getRealContext());
17
36
  }
18
37
  function getAntigravityGlobalRoot() {
19
- return path_1.default.join(os_1.default.homedir(), '.gemini', 'antigravity');
38
+ return utils_1.UtilsLogic.getAntigravityGlobalRoot(getRealContext());
20
39
  }
21
40
  function findLocalRoot(cwd = process.cwd()) {
22
- let current = path_1.default.resolve(cwd);
23
- const root = path_1.default.parse(current).root;
24
- // Safety break and root check
25
- // Using for(;;) to avoid no-constant-condition
26
- for (;;) {
27
- if (fs_extra_1.default.existsSync(path_1.default.join(current, '.agentctl'))) {
28
- return current;
29
- }
30
- if (current === root) {
31
- return null;
32
- }
33
- current = path_1.default.dirname(current);
34
- }
41
+ return utils_1.UtilsLogic.findLocalRoot(cwd, fs_extra_1.default.existsSync);
35
42
  }
package/dist/index.js CHANGED
@@ -1,147 +1,31 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
4
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
5
- return new (P || (P = Promise))(function (resolve, reject) {
6
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
7
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
8
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
9
- step((generator = generator.apply(thisArg, _arguments || [])).next());
10
- });
11
- };
12
3
  var __importDefault = (this && this.__importDefault) || function (mod) {
13
4
  return (mod && mod.__esModule) ? mod : { "default": mod };
14
5
  };
15
6
  Object.defineProperty(exports, "__esModule", { value: true });
16
7
  const commander_1 = require("commander");
17
- const path_1 = __importDefault(require("path"));
18
- require("fs-extra");
8
+ const chalk_1 = __importDefault(require("chalk"));
19
9
  const ctl_1 = require("./ctl");
20
10
  const resolve_1 = require("./resolve");
21
- const child_process_1 = require("child_process");
22
- const chalk_1 = __importDefault(require("chalk"));
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'));
23
17
  const program = new commander_1.Command();
24
- // eslint-disable-next-line @typescript-eslint/no-var-requires
25
- const pkg = require('../package.json');
26
18
  program
27
19
  .name('agentctl')
28
20
  .description('Agent Controller CLI - Unified control plane for humans and agents')
29
- .version(pkg.version)
30
- .allowUnknownOption()
31
- .helpOption(false) // Disable default help to allow pass-through
32
- .argument('[command...]', 'Command to run')
33
- .action((args, _options, _command) => __awaiter(void 0, void 0, void 0, function* () {
34
- // If no args, check for help flag or just show help
35
- if (!args || args.length === 0) {
36
- // If they passed --help or -h, show help. If no args at all, show help.
37
- // Since we ate options, we check raw args or just treat empty args as help.
38
- // command.opts() won't have help if we disabled it?
39
- // Actually, if we disable helpOption, --help becomes an unknown option or arg.
40
- // Let's check process.argv for -h or --help if args is empty?
41
- // "agentctl --help" -> args=[], options might contain help if we didn't disable it?
42
- // With helpOption(false), --help is just a flag in argv.
43
- // If args is empty and we see help flag, show help.
44
- if (process.argv.includes('--help') || process.argv.includes('-h') || process.argv.length <= 2) {
45
- program.help();
46
- return;
47
- }
48
- }
49
- // If args are present, we try to resolve.
50
- // BUT, "agentctl --help" will result in args being empty if it's parsed as option?
51
- // Wait, if helpOption(false), then --help is an unknown option.
52
- // If allowUnknownOption is true, it might not be in 'args' if it looks like a flag.
53
- // Let's rely on resolveCommand. passed args are variadic.
54
- // However, "agentctl dev --help" -> args=["dev", "--help"]?
55
- // My repro says yes: [ 'dev-tools', 'gh', '--help' ].
56
- // So for "agentctl --help", args might be ["--help"].
57
- if (args.length === 1 && (args[0] === '--help' || args[0] === '-h')) {
58
- program.help();
59
- return;
60
- }
61
- // Bypass for ctl subcommand if it slipped through (shouldn't if registered)
62
- if (args[0] === 'ctl')
63
- return;
64
- try {
65
- // resolveCommand needs to handle flags in args if they are part of the path?
66
- // No, flags usually come after. resolveCommand stops at first non-matching path part?
67
- // resolveCommand logic: iterates args.
68
- // "dev-tools gh --help" -> path "dev-tools gh", remaining "--help"
69
- const result = yield (0, resolve_1.resolveCommand)(args);
70
- if (!result) {
71
- // If not found, and they asked for help, show root help?
72
- // Or if they just typed a wrong command.
73
- if (args.includes('--help') || args.includes('-h')) {
74
- // Try to show help for the partial command?
75
- // For now, just show root list/help or error.
76
- // If it's "agentctl dev --help" and "dev" is a group, resolveCommand SHOULD return the group.
77
- }
78
- console.error(chalk_1.default.red(`Command '${args.join(' ')}' not found.`));
79
- console.log(`Run ${chalk_1.default.cyan('agentctl list')} to see available commands.`);
80
- process.exit(1);
81
- }
82
- const { manifest, args: remainingArgs, scope } = result;
83
- if (manifest.run) {
84
- // ... run logic ...
85
- // remainingArgs should contain --help if it was passed.
86
- const cmdDir = path_1.default.dirname(result.manifestPath);
87
- let runCmd = manifest.run;
88
- // Resolve relative path
89
- if (runCmd.startsWith('./') || runCmd.startsWith('.\\')) {
90
- runCmd = path_1.default.resolve(cmdDir, runCmd);
91
- }
92
- // Interpolate {{DIR}}
93
- runCmd = runCmd.replace(/{{DIR}}/g, cmdDir);
94
- const fullCommand = `${runCmd} ${remainingArgs.join(' ')}`;
95
- console.log(chalk_1.default.dim(`[${scope}] Running: ${fullCommand}`));
96
- const child = (0, child_process_1.spawn)(fullCommand, {
97
- cwd: process.cwd(), // Execute in CWD as discussed
98
- shell: true,
99
- stdio: 'inherit',
100
- env: Object.assign(Object.assign({}, process.env), { AGENTCTL_SCOPE: scope })
101
- });
102
- child.on('exit', (code) => {
103
- process.exit(code || 0);
104
- });
105
- }
106
- else {
107
- // Group
108
- console.log(chalk_1.default.blue(chalk_1.default.bold(`${manifest.name}`)));
109
- console.log(manifest.description || 'No description');
110
- console.log('\nSubcommands:');
111
- const all = yield (0, ctl_1.list)();
112
- const prefix = result.cmdPath + ' ';
113
- // Filter logic roughly for direct children
114
- const depth = result.cmdPath.split(' ').length;
115
- const children = all.filter(c => c.path.startsWith(prefix) && c.path !== result.cmdPath);
116
- const direct = children.filter(c => c.path.split(' ').length === depth + 1);
117
- if (direct.length === 0 && children.length === 0) {
118
- console.log(chalk_1.default.dim(' (No subcommands found)'));
119
- }
120
- for (const child of direct) {
121
- console.log(` ${child.path.split(' ').pop()}\t${chalk_1.default.dim(child.description)}`);
122
- }
123
- }
124
- }
125
- catch (e) {
126
- if (e instanceof Error) {
127
- console.error(chalk_1.default.red(e.message));
128
- }
129
- else {
130
- console.error(chalk_1.default.red('An unknown error occurred'));
131
- }
132
- process.exit(1);
133
- }
134
- }));
21
+ .version(pkg.version);
22
+ // --- Subcommand: ctl ---
135
23
  const ctl = program.command('ctl')
136
- .description('Agent Controller Management - Create, organizing, and managing commands');
137
- // --- Lifecycle Commands ---
138
- // We'll stick to flat list but with good descriptions.
139
- // Helper for consistent error handling
140
- // Helper for consistent error handling
24
+ .description('Agent Controller Management - Create, organize, and manage commands');
141
25
  const withErrorHandling = (fn) => {
142
- return (...args) => __awaiter(void 0, void 0, void 0, function* () {
26
+ return async (...args) => {
143
27
  try {
144
- yield fn(...args);
28
+ await fn(...args);
145
29
  }
146
30
  catch (e) {
147
31
  if (e instanceof Error) {
@@ -152,164 +36,304 @@ const withErrorHandling = (fn) => {
152
36
  }
153
37
  process.exit(1);
154
38
  }
155
- });
39
+ };
156
40
  };
157
41
  ctl.command('scaffold')
158
- .description('Create a new capped command with a script file')
159
- .argument('<path...>', 'Command path segments (e.g., "dev start")')
42
+ .description('Scaffold a new command directory with a manifest and starter script.')
43
+ .argument('[path...]', 'Hierarchical path for the new command (e.g., "dev start" or "utils/cleanup")')
44
+ .summary('create a new command')
160
45
  .addHelpText('after', `
46
+ Additional Info:
47
+ This command creates a folder in your local .agentctl directory.
48
+ Inside, it generates:
49
+ - manifest.json: Metadata about the command.
50
+ - command.sh/cmd: A starter script for your logic.
51
+
161
52
  Examples:
162
- $ agentctl ctl scaffold dev start
163
- $ agentctl ctl scaffold sys backup
53
+ $ agentctl ctl scaffold build front
54
+ $ agentctl ctl scaffold utils/backup
164
55
  `)
165
- .action(withErrorHandling((pathParts) => __awaiter(void 0, void 0, void 0, function* () {
166
- yield (0, ctl_1.scaffold)(pathParts);
167
- })));
56
+ .action(withErrorHandling(async (pathParts, opts, command) => {
57
+ if (!pathParts || pathParts.length === 0) {
58
+ command.help();
59
+ return;
60
+ }
61
+ await (0, ctl_1.scaffold)(pathParts);
62
+ }));
168
63
  ctl.command('alias')
169
- .description('Create a new capped command that runs an inline shell command')
170
- .argument('<args...>', 'Name parts followed by target (e.g., "tools" "gh" "gh")')
171
- .action(withErrorHandling((args) => __awaiter(void 0, void 0, void 0, function* () {
172
- if (args.length < 2) {
173
- console.error('Usage: ctl alias <name...> <target>');
174
- process.exit(1);
64
+ .description('Create a command that executes a raw shell string.')
65
+ .argument('[args...]', 'Hierarchical path segments followed by the shell command target')
66
+ .summary('create a shell alias')
67
+ .addHelpText('after', `
68
+ Examples:
69
+ $ agentctl ctl alias dev logs "docker compose logs -f"
70
+ $ agentctl ctl alias list-files "ls -la"
71
+ `)
72
+ .action(withErrorHandling(async (args, opts, command) => {
73
+ if (!args || args.length < 2) {
74
+ command.help();
75
+ return;
175
76
  }
176
77
  const target = args.pop();
177
78
  const name = args;
178
- yield (0, ctl_1.alias)(name, target);
179
- })))
180
- .addHelpText('after', `
181
- Examples:
182
- $ agentctl ctl alias tools gh "gh"
183
- $ agentctl ctl alias dev build "npm run build"
184
- `);
79
+ await (0, ctl_1.alias)(name, target);
80
+ }));
185
81
  ctl.command('group')
186
- .description('Create a new command group (namespace)')
187
- .argument('<path...>', 'Group path (e.g., "dev")')
82
+ .description('Create a command group (namespace) to organize subcommands.')
83
+ .argument('[path...]', 'Hierarchical path for the group (e.g., "dev" or "cloud/aws")')
84
+ .summary('create a namespace group')
188
85
  .addHelpText('after', `
86
+ Additional Info:
87
+ Groups allow you to categorize commands. Running a group command without
88
+ subcommands will list all direct subcommands within that group.
89
+
189
90
  Examples:
190
91
  $ agentctl ctl group dev
191
- $ agentctl ctl group tools
92
+ $ agentctl ctl group data/pipelines
192
93
  `)
193
- .action(withErrorHandling((parts) => __awaiter(void 0, void 0, void 0, function* () {
194
- yield (0, ctl_1.group)(parts);
195
- })));
94
+ .action(withErrorHandling(async (parts, opts, command) => {
95
+ if (!parts || parts.length === 0) {
96
+ command.help();
97
+ return;
98
+ }
99
+ await (0, ctl_1.group)(parts);
100
+ }));
196
101
  ctl.command('rm')
197
- .description('Remove a command or group permanently')
198
- .argument('<path...>', 'Command path to remove')
199
- .option('--global', 'Remove from global scope')
102
+ .description('Permanently remove a command or group.')
103
+ .argument('[path...]', 'Command path to remove')
104
+ .option('-g, --global', 'Remove from global scope instead of local')
105
+ .summary('delete a command')
200
106
  .addHelpText('after', `
201
107
  Examples:
202
108
  $ agentctl ctl rm dev start
203
- $ agentctl ctl rm tools --global
109
+ $ agentctl ctl rm utils --global
204
110
  `)
205
- .action(withErrorHandling((parts, opts) => __awaiter(void 0, void 0, void 0, function* () {
206
- yield (0, ctl_1.rm)(parts, { global: opts.global });
207
- })));
111
+ .action(withErrorHandling(async (parts, opts, command) => {
112
+ if (!parts || parts.length === 0) {
113
+ command.help();
114
+ return;
115
+ }
116
+ await (0, ctl_1.rm)(parts, { global: opts.global });
117
+ }));
208
118
  ctl.command('mv')
209
- .description('Move a command or group to a new path')
210
- .argument('<src>', 'Source path (quoted string or single token)')
211
- .argument('<dest>', 'Destination path')
212
- .option('--global', 'Move command in global scope')
119
+ .description('Move or rename a command/group within its current scope.')
120
+ .argument('[src]', 'Current path (space-separated or quoted)')
121
+ .argument('[dest]', 'New path (space-separated or quoted)')
122
+ .option('-g, --global', 'Operate in global scope')
123
+ .summary('rename/move a command')
213
124
  .addHelpText('after', `
214
125
  Examples:
215
- $ agentctl ctl mv "dev start" "dev boot"
216
- $ agentctl ctl mv tools/gh tools/github --global
126
+ $ agentctl ctl mv "dev start" "dev begin"
127
+ $ agentctl ctl mv utils scripts --global
217
128
  `)
218
- .action(withErrorHandling((src, dest, opts) => __awaiter(void 0, void 0, void 0, function* () {
219
- yield (0, ctl_1.mv)(src.split(' '), dest.split(' '), { global: opts.global });
220
- })));
221
- // --- Introspection ---
129
+ .action(withErrorHandling(async (src, dest, opts, command) => {
130
+ if (!src || !dest) {
131
+ command.help();
132
+ return;
133
+ }
134
+ await (0, ctl_1.mv)(src.split(' '), dest.split(' '), { global: opts.global });
135
+ }));
222
136
  ctl.command('list')
223
- .description('List all available commands across local and global scopes')
224
- .action(withErrorHandling(() => __awaiter(void 0, void 0, void 0, function* () {
225
- const items = yield (0, ctl_1.list)();
226
- console.log('TYPE SCOPE COMMAND DESCRIPTION');
137
+ .description('List all available commands across local and global scopes.')
138
+ .summary('list all commands')
139
+ .addHelpText('after', `
140
+ Output Columns:
141
+ TYPE - scaffold, alias, or group
142
+ SCOPE - local (project-specific) or global (user-wide)
143
+ COMMAND - The path used to invoke the command
144
+ DESCRIPTION - Brief text from the command's manifest
145
+ `)
146
+ .action(withErrorHandling(async () => {
147
+ const items = await (0, ctl_1.list)();
148
+ console.log(chalk_1.default.bold('TYPE SCOPE COMMAND DESCRIPTION'));
227
149
  for (const item of items) {
228
- console.log(`${item.type.padEnd(9)} ${item.scope.padEnd(9)} ${item.path.padEnd(19)} ${item.description}`);
150
+ const typePipe = item.type.padEnd(9);
151
+ const scopePipe = item.scope === 'local' ? chalk_1.default.cyan(item.scope.padEnd(9)) : chalk_1.default.magenta(item.scope.padEnd(9));
152
+ console.log(`${typePipe} ${scopePipe} ${chalk_1.default.yellow(item.path.padEnd(19))} ${item.description}`);
229
153
  }
230
- })));
154
+ }));
231
155
  ctl.command('inspect')
232
- .description('Inspect the internal manifest and details of a command')
233
- .argument('<path...>', 'Command path to inspect')
234
- .action(withErrorHandling((parts) => __awaiter(void 0, void 0, void 0, function* () {
235
- const info = yield (0, ctl_1.inspect)(parts);
156
+ .description('Show the internal manifest and file system path of a command.')
157
+ .argument('[path...]', 'Command path to inspect')
158
+ .summary('inspect command details')
159
+ .addHelpText('after', `
160
+ Examples:
161
+ $ agentctl ctl inspect dev start
162
+ `)
163
+ .action(withErrorHandling(async (parts, opts, command) => {
164
+ if (!parts || parts.length === 0) {
165
+ command.help();
166
+ return;
167
+ }
168
+ const info = await (0, ctl_1.inspect)(parts);
236
169
  if (info) {
237
170
  console.log(JSON.stringify(info, null, 2));
238
171
  }
239
172
  else {
240
- console.error('Command not found');
173
+ console.error(chalk_1.default.red('Command not found'));
241
174
  process.exit(1);
242
175
  }
243
- })));
244
- // --- Scoping ---
176
+ }));
245
177
  ctl.command('global')
246
- .description('Push a local command to the global scope')
247
- .argument('<path...>', 'Local command path')
248
- .option('--move', 'Move instead of copy')
249
- .option('--copy', 'Copy (default)')
178
+ .description('Promote a local command to the global scope.')
179
+ .argument('[path...]', 'Local command path to promote')
180
+ .option('-m, --move', 'Move the command (delete local after copying)')
181
+ .option('-c, --copy', 'Copy the command (keep local version, default)')
182
+ .summary('make a command global')
250
183
  .addHelpText('after', `
184
+ Additional Info:
185
+ Global commands are stored in your home directory and are available in any project.
186
+
251
187
  Examples:
252
- $ agentctl ctl global sys --move
253
- $ agentctl ctl global tools --copy
188
+ $ agentctl ctl global utils/cleanup
189
+ $ agentctl ctl global dev/deploy --move
254
190
  `)
255
- .action(withErrorHandling((parts, opts) => __awaiter(void 0, void 0, void 0, function* () {
256
- yield (0, ctl_1.pushGlobal)(parts, { move: opts.move, copy: opts.copy || !opts.move });
257
- })));
191
+ .action(withErrorHandling(async (parts, opts, command) => {
192
+ if (!parts || parts.length === 0) {
193
+ command.help();
194
+ return;
195
+ }
196
+ await (0, ctl_1.pushGlobal)(parts, { move: opts.move, copy: opts.copy || !opts.move });
197
+ }));
258
198
  ctl.command('local')
259
- .description('Pull a global command to the local scope')
260
- .argument('<path...>', 'Global command path')
261
- .option('--move', 'Move instead of copy')
262
- .option('--copy', 'Copy (default)')
199
+ .description('Pull a global command into the current local project.')
200
+ .argument('[path...]', 'Global command path to pull')
201
+ .option('-m, --move', 'Move the command (delete global after pulling)')
202
+ .option('-c, --copy', 'Copy the command (keep global version, default)')
203
+ .summary('make a command local')
263
204
  .addHelpText('after', `
264
205
  Examples:
265
- $ agentctl ctl local tools --copy
206
+ $ agentctl ctl local utils/shared
207
+ $ agentctl ctl local snippets/js --move
266
208
  `)
267
- .action(withErrorHandling((parts, opts) => __awaiter(void 0, void 0, void 0, function* () {
268
- yield (0, ctl_1.pullLocal)(parts, { move: opts.move, copy: opts.copy || !opts.move });
269
- })));
270
- // --- Agent Integration ---
271
- // We attach this to the root `ctl` as options or a sub-command?
272
- // Original code had it as options on `ctl`. We can make it a command for better help.
273
- // But sticking to options maintains compatibility. We'll improve the option help.
274
- ctl.option('--install-skill <agent>', 'Install skill for agent (cursor, antigravity, agentsmd, gemini)')
275
- .option('--global', 'Install skill globally (for supported agents)')
209
+ .action(withErrorHandling(async (parts, opts, command) => {
210
+ if (!parts || parts.length === 0) {
211
+ command.help();
212
+ return;
213
+ }
214
+ await (0, ctl_1.pullLocal)(parts, { move: opts.move, copy: opts.copy || !opts.move });
215
+ }));
216
+ ctl.command('install-skill')
217
+ .description('Configure a supported AI agent (like Cursor or Gemini) to natively use Agentctl.')
218
+ .argument('[agent]', 'Agent name (cursor, antigravity, agentsmd, gemini)')
219
+ .option('-g, --global', 'Install globally for the agent (if supported)')
220
+ .summary('configure AI agent integration')
276
221
  .addHelpText('after', `
222
+ Supported Agents:
223
+ - cursor (Installs to .cursor/skills)
224
+ - antigravity (Installs to .agent/skills or ~/.gemini/antigravity)
225
+ - agentsmd (Installs to .agents/skills)
226
+ - gemini (Installs to .gemini/skills or ~/.gemini/skills)
227
+
277
228
  Examples:
278
- $ agentctl ctl --install-skill cursor
279
- $ agentctl ctl --install-skill antigravity --global
280
- $ agentctl ctl --install-skill gemini
229
+ $ agentctl ctl install-skill cursor
230
+ $ agentctl ctl install-skill antigravity --global
281
231
  `)
282
- .action(withErrorHandling((op, command) => __awaiter(void 0, void 0, void 0, function* () {
283
- const opts = ctl.opts();
284
- if (opts.installSkill) {
285
- yield (0, ctl_1.installSkill)(opts.installSkill, { global: opts.global });
232
+ .action(withErrorHandling(async (agent, opts, command) => {
233
+ if (!agent) {
234
+ command.help();
235
+ return;
286
236
  }
287
- else {
288
- // If no subcmd and no option, show help
289
- if (command.args.length === 0) {
290
- ctl.help();
237
+ await (0, ctl_1.installSkill)(agent, { global: opts.global });
238
+ }));
239
+ ctl.command('install')
240
+ .description('Install a command group from a remote Git repository.')
241
+ .argument('[repoUrl]', 'URL of the remote Git repository containing an .agentctl folder')
242
+ .argument('[pathParts...]', 'Optional local namespace/group to install into')
243
+ .option('-g, --global', 'Install globally instead of locally')
244
+ .option('--allow-collisions', 'Allow overwriting existing commands or merging into groups')
245
+ .summary('install remote command group')
246
+ .addHelpText('after', `
247
+ Additional Info:
248
+ Fetches the .agentctl folder from the remote repository and installs it into
249
+ your local or global agentctl environment.
250
+
251
+ Examples:
252
+ $ agentctl ctl install https://github.com/org/repo-tools
253
+ $ agentctl ctl install https://github.com/org/deploy-scripts deploy --global
254
+ `)
255
+ .action(withErrorHandling(async (repoUrl, pathParts, opts, command) => {
256
+ if (!repoUrl) {
257
+ command.help();
258
+ return;
259
+ }
260
+ await (0, ctl_1.install)(repoUrl, pathParts, { global: opts.global, allowCollisions: opts.allowCollisions });
261
+ }));
262
+ // --- Dynamic Command Logic ---
263
+ async function handleDynamicCommand(args) {
264
+ try {
265
+ const result = await (0, resolve_1.resolveCommand)(args);
266
+ if (!result) {
267
+ const effects = index_1.AppLogic.planApp(args, null);
268
+ await (0, effects_1.execute)(effects.map(e => e.type === 'log' ? { ...e, message: chalk_1.default.red(e.message) } : e));
269
+ process.exit(1);
270
+ }
271
+ if (result.manifest.run) {
272
+ const effects = index_1.AppLogic.planApp(args, result);
273
+ await (0, effects_1.execute)(effects);
274
+ }
275
+ else {
276
+ const all = await (0, ctl_1.list)();
277
+ const effects = index_1.AppLogic.planGroupList(result.manifest, result.cmdPath, all);
278
+ await (0, effects_1.execute)(effects.map(e => {
279
+ if (e.type === 'log') {
280
+ if (e.message === result.manifest.name)
281
+ return { ...e, message: chalk_1.default.blue(chalk_1.default.bold(e.message)) };
282
+ if (e.message === '\nSubcommands:')
283
+ return e;
284
+ if (e.message.startsWith(' '))
285
+ return e;
286
+ if (e.message === 'No description')
287
+ return { ...e, message: chalk_1.default.dim(e.message) };
288
+ }
289
+ return e;
290
+ }));
291
+ }
292
+ }
293
+ catch (e) {
294
+ if (e instanceof Error) {
295
+ console.error(chalk_1.default.red(e.message));
296
+ }
297
+ else {
298
+ console.error(chalk_1.default.red('An unknown error occurred'));
291
299
  }
300
+ process.exit(1);
292
301
  }
293
- })));
294
- // Inject dynamic commands into root help
295
- // We need to do this before parsing
296
- (() => __awaiter(void 0, void 0, void 0, function* () {
302
+ }
303
+ (async () => {
304
+ // Add help text for user commands dynamically
297
305
  try {
298
- const allCommands = yield (0, ctl_1.list)();
299
- const topLevel = allCommands.filter(c => !c.path.includes(' ')); // Only top level
306
+ const allCommands = await (0, ctl_1.list)();
307
+ const topLevel = allCommands.filter(c => !c.path.includes(' '));
300
308
  if (topLevel.length > 0) {
301
309
  const lines = [''];
302
- lines.push('User Commands:');
310
+ lines.push(chalk_1.default.bold('User Commands:'));
303
311
  for (const cmd of topLevel) {
304
- // simple padding
305
- lines.push(` ${cmd.path.padEnd(27)}${cmd.description}`);
312
+ lines.push(` ${chalk_1.default.yellow(cmd.path.padEnd(27))}${cmd.description}`);
306
313
  }
307
314
  lines.push('');
308
315
  program.addHelpText('after', lines.join('\n'));
309
316
  }
310
317
  }
311
- catch (_a) {
312
- // Ignore errors during help generation (e.g. if not initialized)
318
+ catch {
319
+ // Ignore errors during help generation
320
+ }
321
+ // Process arguments
322
+ const args = process.argv.slice(2);
323
+ // If no args, show help
324
+ if (args.length === 0) {
325
+ program.help();
326
+ return;
327
+ }
328
+ // Special case: if it starts with 'ctl', or is '--help', '--version', '-h', etc.,
329
+ // let commander handle it natively.
330
+ const firstArg = args[0];
331
+ const isStandardCommand = ['ctl', '--help', '-h', '--version', '-V'].includes(firstArg) || firstArg.startsWith('-');
332
+ if (isStandardCommand) {
333
+ program.parse(process.argv);
334
+ }
335
+ else {
336
+ // It's a dynamic command
337
+ await handleDynamicCommand(args);
313
338
  }
314
- program.parse(process.argv);
315
- }))();
339
+ })();