@michaelhartmayer/agentctl 1.1.3 → 1.2.0

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 CHANGED
@@ -90,14 +90,31 @@ Teach your AI agent how to use `agentctl` by installing a skill file.
90
90
  agentctl ctl --install-skill cursor
91
91
  ```
92
92
 
93
- ### Headless Execution
94
- Run agentic workflows directly from the CLI.
93
+ ### Provide the proper help copy
94
+ When creating commands, make sure the `manifest.json` properly scopes its information:
95
+ - `description`: A short summary of the command, displayed when viewing the list of available commands.
96
+ - `help`: A longer set of instructions/usage string. For `group` types, this is displayed when calling the group without a subcommand. For `scaffold` types, this is currently for reference only; your command script must manually handle an `--help` flag for detailed usage.
97
+
98
+ #### Scaffold Schema
99
+ ```json
100
+ {
101
+ "name": "<command_folder_name>",
102
+ "description": "<insert command summary here>",
103
+ "help": "<insert longer usage/help instructions here>",
104
+ "type": "scaffold",
105
+ "run": "./command.cmd"
106
+ }
107
+ ```
95
108
 
96
- ```bash
97
- # Run a headless Gemini session in the current directory
98
- agentctl agent headless-gemini . "refactor the formatting in src/"
109
+ #### Group Schema
110
+ ```json
111
+ {
112
+ "name": "<group_folder_name>",
113
+ "description": "<insert group summary here>",
114
+ "help": "<insert longer group description/instructions here>",
115
+ "type": "group"
116
+ }
99
117
  ```
100
- *(Requires `gemini-cli` installed and authenticated)*
101
118
 
102
119
  ---
103
120
 
@@ -105,17 +122,119 @@ agentctl agent headless-gemini . "refactor the formatting in src/"
105
122
 
106
123
  The `ctl` subcommand is the meta-layer for managing `agentctl` itself.
107
124
 
108
- | Command | Description |
109
- |---|---|
110
- | `scaffold <path>` | Create a new script-based command |
111
- | `alias <path> <target>` | Create a command that runs another command |
112
- | `group <path>` | Create a new namespace |
113
- | `rm <path>` | Remove a command or group |
114
- | `mv <src> <dest>` | and/or rename a command |
115
- | `list` | Show all commands (local + global) |
116
- | `inspect <path>` | JSON dump of command manifest |
117
- | `global <path>` | Promote local command to global |
118
- | `local <path>` | Pull global command to local |
125
+ ### `agentctl ctl scaffold <path>`
126
+ Create a new script-based command. This generates a folder containing a `manifest.json` and a starter script (`command.cmd`/`command.sh`) for your logic.
127
+ - **Why it's great:** Automates the boilerplate of creating a new CLI tool.
128
+ - **When to use it:** When you have a complex script or an arbitrary executable (Node, Python, Go) you want to expose easily via the CLI.
129
+ ```bash
130
+ agentctl ctl scaffold build:front
131
+ agentctl ctl scaffold "build server" # Creates group 'build' and subcommand 'server'
132
+ ```
133
+
134
+ ### `agentctl ctl group <path>`
135
+ Create a new namespace to organize subcommands.
136
+ - **Why it's great:** Keeps your CLI clean by grouping related tools without forcing you to write a parent CLI router.
137
+ - **When to use it:** When you have a collection of similar commands (e.g., `db migrate`, `db reset`) and want an organized help menu grouping them under `db`.
138
+ ```bash
139
+ agentctl ctl group data # Creates 'data' group
140
+ agentctl ctl group "cloud aws" # Creates nested 'cloud/aws' groups
141
+ ```
142
+
143
+ ### `agentctl ctl alias <path> <target>`
144
+ Create a command that simply runs an existing binary, string, or target.
145
+ - **Why it's great:** Replaces bash aliases with structured, documentable commands that work locally or globally across environments and OSes.
146
+ - **When to use it:** When you have a long, frequently used string (`docker compose run --rm node npm install`) and want a short name (`agentctl npm install`) without needing a whole generated shell script.
147
+ ```bash
148
+ agentctl ctl alias "tools gh" gh
149
+ agentctl ctl alias list-files "ls -la"
150
+ ```
151
+
152
+ ### `agentctl ctl rm [options] <path>`
153
+ Permanently remove a command or entire group from your workspace.
154
+ - **Why it's great:** Quickly clean up old tooling right from the CLI without needing to manually delete `.agentctl` directories.
155
+ - **When to use it:** When retiring a script or cleaning up an obsolete group.
156
+
157
+ **Options:**
158
+ - `-g, --global`: Remove from global scope instead of local
159
+
160
+ ```bash
161
+ agentctl ctl rm build:front # Removes local 'build front' command
162
+ agentctl ctl rm mytool --global # Removes global 'mytool' command
163
+ ```
164
+
165
+ ### `agentctl ctl mv [options] <src> <dest>`
166
+ Rename a command or move it to a new group/namespace.
167
+ - **Why it's great:** Refactor your CLI commands like you'd refactor code, moving logic around namespaces without breaking the underlying executed scripts.
168
+ - **When to use it:** When restructuring your toolbelt from a flat list to a nested categorization.
169
+
170
+ **Options:**
171
+ - `-g, --global`: Operate in global scope
172
+
173
+ ```bash
174
+ agentctl ctl mv mytool my-new-tool
175
+ agentctl ctl mv "tools ping" "network ping"
176
+ agentctl ctl mv "tools ping" "network ping" --global # Moves in global scope
177
+ ```
178
+
179
+ ### `agentctl ctl list [options]`
180
+ List all currently available commands across both your local and global scopes.
181
+ - **Why it's great:** Generates an easy-to-read JSON summary of everything your agent can do.
182
+ - **When to use it:** When programming an agent or exploring what tooling is available in a scoped project.
183
+
184
+ ```bash
185
+ agentctl ctl list
186
+ ```
187
+
188
+ ### `agentctl ctl inspect [options] <path>`
189
+ Dump the resolved manifest and location of a given command path.
190
+ - **Why it's great:** Eliminates the "where did this command come from?" problem when debugging complex workspaces.
191
+ - **When to use it:** When you have local/global conflicts or want to quickly see the JSON source manifest for a command.
192
+ ```bash
193
+ agentctl ctl inspect dev:start
194
+ ```
195
+
196
+ ### `agentctl ctl global [options] <path>`
197
+ Promote a local command or group to your globally available toolbelt.
198
+ - **Why it's great:** Eject highly useful, generic project abstractions directly into your personal universal toolbelt.
199
+ - **When to use it:** You wrote a script specific to a project and realized "I want this CLI tool in every repo I touch."
200
+
201
+ **Options:**
202
+ - `-c, --copy`: Copy the command (keep local version, default)
203
+ - `-m, --move`: Move the command (delete local after copying)
204
+
205
+ ```bash
206
+ agentctl ctl global "dev toolkit" # Copies the command to global scope
207
+ agentctl ctl global "dev toolkit" --move # Moves it instead of copy
208
+ ```
209
+
210
+ ### `agentctl ctl local [options] <path>`
211
+ Bring a global command down into the current local project environment.
212
+ - **Why it's great:** Promotes collaboration seamlessly. You can take your personal script, localize it, and commit it to git for your entire team.
213
+ - **When to use it:** You have a generic tool that is suddenly very relevant to a specific repository, and you want CI or your coworkers to access it.
214
+
215
+ **Options:**
216
+ - `-c, --copy`: Copy the command (keep global version, default)
217
+ - `-m, --move`: Move the command (delete global after pulling)
218
+
219
+ ```bash
220
+ agentctl ctl local "my global-tool"
221
+ agentctl ctl local "my global-tool" --move
222
+ ```
223
+
224
+ ### `agentctl ctl install [options] <repo-url> [path...]`
225
+ Install commands or groups directly from a remote Git repository's `.agentctl` folder into your local or global scope.
226
+ - **Why it's great:** Provides dependency-free tool sharing! Distribute entire suites of scripts/commands without using a package manager.
227
+ - **When to use it:** When you want to pull down shared utility commands that your team maintains in a central repository, or distribute your own tools to others.
228
+
229
+ **Options:**
230
+ - `-g, --global`: Install globally instead of locally
231
+ - `--allow-collisions`: Allow overwriting existing commands or merging into groups
232
+
233
+ ```bash
234
+ agentctl ctl install https://github.com/my-org/tools
235
+ agentctl ctl install https://github.com/my-org/tools --global
236
+ agentctl ctl install https://github.com/my-org/tools deploy --allow-collisions
237
+ ```
119
238
 
120
239
  ---
121
240
 
package/dist/ctl.js CHANGED
@@ -181,7 +181,8 @@ async function install(repoUrl, pathArgs, options = {}) {
181
181
  pathParts: pathArgs,
182
182
  global: !!options.global,
183
183
  allowCollisions: !!options.allowCollisions,
184
- localRoot: ctx.localRoot,
184
+ localRoot: ctx.localRoot || ctx.cwd,
185
+ isNewLocalRoot: !options.global && !ctx.localRoot,
185
186
  globalRoot: ctx.globalRoot,
186
187
  osTmpdir: os_1.default.tmpdir()
187
188
  };
@@ -238,9 +239,11 @@ async function list(options = {}) {
238
239
  else if (manifest.type)
239
240
  type = manifest.type;
240
241
  }
241
- const item = { path: cmdPath, type, scope, description: manifest?.description || '' };
242
242
  if (!commands.has(cmdPath)) {
243
- commands.set(cmdPath, item);
243
+ if (manifest) {
244
+ const item = { path: cmdPath, type, scope, description: manifest.description || '' };
245
+ commands.set(cmdPath, item);
246
+ }
244
247
  const effectiveManifest = manifest || { name: file, type: 'group' };
245
248
  if (!(0, manifest_1.isCappedManifest)(effectiveManifest))
246
249
  await walk(filePath, cmdPathParts, scope);
package/dist/index.js CHANGED
@@ -15,13 +15,47 @@ const path_1 = __importDefault(require("path"));
15
15
  const pkgPath = path_1.default.join(__dirname, '../package.json');
16
16
  const pkg = JSON.parse(fs_1.default.readFileSync(pkgPath, 'utf8'));
17
17
  const program = new commander_1.Command();
18
+ program.addHelpCommand(false);
18
19
  program
19
20
  .name('agentctl')
20
21
  .description('Agent Controller CLI - Unified control plane for humans and agents')
21
22
  .version(pkg.version);
22
23
  // --- Subcommand: ctl ---
23
24
  const ctl = program.command('ctl')
24
- .description('Agent Controller Management - Create, organize, and manage commands');
25
+ .description('Agent Controller Management - Create, organize, and manage commands')
26
+ .addHelpCommand(false)
27
+ .configureHelp({ visibleCommands: () => [] })
28
+ .action((opts, command) => {
29
+ command.help();
30
+ })
31
+ .addHelpText('after', `
32
+ ${chalk_1.default.bold('Agentctl Paradigm:')}
33
+ Agentctl acts as a unified control plane allowing both Humans and AI Agents
34
+ to create, discover, and execute local shell commands. By running \`agentctl ctl scaffold <name>\`
35
+ you create a directory in the \`.agentctl\` folder containing a \`manifest.json\`
36
+ and a run script. This dynamically creates a new \`agentctl <name>\` command that
37
+ is easily callable by agents and globally executable on your machine.
38
+
39
+ Commands:
40
+
41
+ ${chalk_1.default.bold('Creation')}
42
+ scaffold [path...] create a new command
43
+ alias [args...] create a shell alias
44
+ group [path...] create a namespace group
45
+
46
+ ${chalk_1.default.bold('Organize & Scope')}
47
+ rm [options] [path...] delete a command
48
+ mv [options] [src] [dest] rename/move a command
49
+ global [options] [path...] make a command global
50
+ local [options] [path...] make a command local
51
+
52
+ ${chalk_1.default.bold('Information')}
53
+ list list all commands
54
+ inspect [path...] inspect command details
55
+
56
+ ${chalk_1.default.bold('Integration')}
57
+ install [options] [repoUrl] [pathParts...] install remote command group
58
+ `);
25
59
  const withErrorHandling = (fn) => {
26
60
  return async (...args) => {
27
61
  try {
@@ -38,6 +72,7 @@ const withErrorHandling = (fn) => {
38
72
  }
39
73
  };
40
74
  };
75
+ const normalizePath = (parts) => parts.flatMap(p => p.split(/[\s/\\:]+/).filter(Boolean));
41
76
  ctl.command('scaffold')
42
77
  .description('Scaffold a new command directory with a manifest and starter script.')
43
78
  .argument('[path...]', 'Hierarchical path for the new command (e.g., "dev start" or "utils/cleanup")')
@@ -45,20 +80,34 @@ ctl.command('scaffold')
45
80
  .addHelpText('after', `
46
81
  Additional Info:
47
82
  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.
83
+ Inside this newly created folder, it generates:
84
+ - manifest.json: Metadata config to edit for your command.
85
+ - command.sh/cmd: A starter script to edit for your actual logic.
86
+
87
+ Manifest Schema (manifest.json) to edit:
88
+ {
89
+ "name": "<command_folder_name>",
90
+ "description": "<insert command summary here>",
91
+ "help": "<insert longer usage/help instructions here>",
92
+ "type": "scaffold", // do not change!
93
+ "run": "./command.cmd" // points to the script to execute
94
+ }
95
+
96
+ Note:
97
+ - The "description" is displayed when you view this command in a list.
98
+ - Commands must provide their own help implementation (e.g. by handling --help inside your script).
51
99
 
52
100
  Examples:
53
- $ agentctl ctl scaffold build front
54
- $ agentctl ctl scaffold utils/backup
101
+ $ agentctl ctl scaffold build:front
102
+ $ agentctl ctl scaffold "build front" # Creates group 'build' and subcommand 'front'
55
103
  `)
56
104
  .action(withErrorHandling(async (pathParts, opts, command) => {
57
- if (!pathParts || pathParts.length === 0) {
105
+ const normalized = normalizePath(pathParts);
106
+ if (normalized.length === 0) {
58
107
  command.help();
59
108
  return;
60
109
  }
61
- await (0, ctl_1.scaffold)(pathParts);
110
+ await (0, ctl_1.scaffold)(normalized);
62
111
  }));
63
112
  ctl.command('alias')
64
113
  .description('Create a command that executes a raw shell string.')
@@ -75,7 +124,11 @@ Examples:
75
124
  return;
76
125
  }
77
126
  const target = args.pop();
78
- const name = args;
127
+ const name = normalizePath(args);
128
+ if (name.length === 0) {
129
+ command.help();
130
+ return;
131
+ }
79
132
  await (0, ctl_1.alias)(name, target);
80
133
  }));
81
134
  ctl.command('group')
@@ -86,17 +139,33 @@ ctl.command('group')
86
139
  Additional Info:
87
140
  Groups allow you to categorize commands. Running a group command without
88
141
  subcommands will list all direct subcommands within that group.
142
+
143
+ This command creates a folder in your local .agentctl directory.
144
+ Inside this newly created folder, it generates a manifest.json.
145
+
146
+ Group Schema (manifest.json) to edit:
147
+ {
148
+ "name": "<group_folder_name>",
149
+ "description": "<insert group summary here>",
150
+ "help": "<insert longer group description/instructions here>",
151
+ "type": "group" // do not change!
152
+ }
153
+
154
+ Note:
155
+ - The "description" is displayed when you view this group in a list.
156
+ - The "help" is displayed when you call this group without a subcommand.
89
157
 
90
158
  Examples:
91
159
  $ agentctl ctl group dev
92
- $ agentctl ctl group data/pipelines
160
+ $ agentctl ctl group "data pipelines" # Creates group 'data' and subgroup 'pipelines'
93
161
  `)
94
162
  .action(withErrorHandling(async (parts, opts, command) => {
95
- if (!parts || parts.length === 0) {
163
+ const normalized = normalizePath(parts);
164
+ if (normalized.length === 0) {
96
165
  command.help();
97
166
  return;
98
167
  }
99
- await (0, ctl_1.group)(parts);
168
+ await (0, ctl_1.group)(normalized);
100
169
  }));
101
170
  ctl.command('rm')
102
171
  .description('Permanently remove a command or group.')
@@ -105,15 +174,16 @@ ctl.command('rm')
105
174
  .summary('delete a command')
106
175
  .addHelpText('after', `
107
176
  Examples:
108
- $ agentctl ctl rm dev start
109
- $ agentctl ctl rm utils --global
110
- `)
177
+ $ agentctl ctl rm dev start
178
+ $ agentctl ctl rm utils--global
179
+ `)
111
180
  .action(withErrorHandling(async (parts, opts, command) => {
112
- if (!parts || parts.length === 0) {
181
+ const normalized = normalizePath(parts);
182
+ if (normalized.length === 0) {
113
183
  command.help();
114
184
  return;
115
185
  }
116
- await (0, ctl_1.rm)(parts, { global: opts.global });
186
+ await (0, ctl_1.rm)(normalized, { global: opts.global });
117
187
  }));
118
188
  ctl.command('mv')
119
189
  .description('Move or rename a command/group within its current scope.')
@@ -123,26 +193,28 @@ ctl.command('mv')
123
193
  .summary('rename/move a command')
124
194
  .addHelpText('after', `
125
195
  Examples:
126
- $ agentctl ctl mv "dev start" "dev begin"
127
- $ agentctl ctl mv utils scripts --global
128
- `)
196
+ $ agentctl ctl mv "dev start" "dev begin"
197
+ $ agentctl ctl mv utils scripts--global
198
+ `)
129
199
  .action(withErrorHandling(async (src, dest, opts, command) => {
130
- if (!src || !dest) {
200
+ const normalizedSrc = normalizePath(src ? [src] : []);
201
+ const normalizedDest = normalizePath(dest ? [dest] : []);
202
+ if (normalizedSrc.length === 0 || normalizedDest.length === 0) {
131
203
  command.help();
132
204
  return;
133
205
  }
134
- await (0, ctl_1.mv)(src.split(' '), dest.split(' '), { global: opts.global });
206
+ await (0, ctl_1.mv)(normalizedSrc, normalizedDest, { global: opts.global });
135
207
  }));
136
208
  ctl.command('list')
137
209
  .description('List all available commands across local and global scopes.')
138
210
  .summary('list all commands')
139
211
  .addHelpText('after', `
140
212
  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
213
+ TYPE - scaffold, alias, or group
214
+ SCOPE - local(project - specific) or global(user - wide)
215
+ COMMAND - The path used to invoke the command
144
216
  DESCRIPTION - Brief text from the command's manifest
145
- `)
217
+ `)
146
218
  .action(withErrorHandling(async () => {
147
219
  const items = await (0, ctl_1.list)();
148
220
  console.log(chalk_1.default.bold('TYPE SCOPE COMMAND DESCRIPTION'));
@@ -158,14 +230,15 @@ ctl.command('inspect')
158
230
  .summary('inspect command details')
159
231
  .addHelpText('after', `
160
232
  Examples:
161
- $ agentctl ctl inspect dev start
162
- `)
233
+ $ agentctl ctl inspect dev start
234
+ `)
163
235
  .action(withErrorHandling(async (parts, opts, command) => {
164
- if (!parts || parts.length === 0) {
236
+ const normalized = normalizePath(parts);
237
+ if (normalized.length === 0) {
165
238
  command.help();
166
239
  return;
167
240
  }
168
- const info = await (0, ctl_1.inspect)(parts);
241
+ const info = await (0, ctl_1.inspect)(normalized);
169
242
  if (info) {
170
243
  console.log(JSON.stringify(info, null, 2));
171
244
  }
@@ -182,18 +255,19 @@ ctl.command('global')
182
255
  .summary('make a command global')
183
256
  .addHelpText('after', `
184
257
  Additional Info:
185
- Global commands are stored in your home directory and are available in any project.
258
+ Global commands are stored in your home directory and are available in any project.
186
259
 
187
- Examples:
188
- $ agentctl ctl global utils/cleanup
189
- $ agentctl ctl global dev/deploy --move
190
- `)
260
+ Examples:
261
+ $ agentctl ctl global utils / cleanup
262
+ $ agentctl ctl global dev / deploy--move
263
+ `)
191
264
  .action(withErrorHandling(async (parts, opts, command) => {
192
- if (!parts || parts.length === 0) {
265
+ const normalized = normalizePath(parts);
266
+ if (normalized.length === 0) {
193
267
  command.help();
194
268
  return;
195
269
  }
196
- await (0, ctl_1.pushGlobal)(parts, { move: opts.move, copy: opts.copy || !opts.move });
270
+ await (0, ctl_1.pushGlobal)(normalized, { move: opts.move, copy: opts.copy || !opts.move });
197
271
  }));
198
272
  ctl.command('local')
199
273
  .description('Pull a global command into the current local project.')
@@ -203,38 +277,16 @@ ctl.command('local')
203
277
  .summary('make a command local')
204
278
  .addHelpText('after', `
205
279
  Examples:
206
- $ agentctl ctl local utils/shared
207
- $ agentctl ctl local snippets/js --move
208
- `)
280
+ $ agentctl ctl local utils / shared
281
+ $ agentctl ctl local snippets / js--move
282
+ `)
209
283
  .action(withErrorHandling(async (parts, opts, command) => {
210
- if (!parts || parts.length === 0) {
284
+ const normalized = normalizePath(parts);
285
+ if (normalized.length === 0) {
211
286
  command.help();
212
287
  return;
213
288
  }
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')
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
-
228
- Examples:
229
- $ agentctl ctl install-skill cursor
230
- $ agentctl ctl install-skill antigravity --global
231
- `)
232
- .action(withErrorHandling(async (agent, opts, command) => {
233
- if (!agent) {
234
- command.help();
235
- return;
236
- }
237
- await (0, ctl_1.installSkill)(agent, { global: opts.global });
289
+ await (0, ctl_1.pullLocal)(normalized, { move: opts.move, copy: opts.copy || !opts.move });
238
290
  }));
239
291
  ctl.command('install')
240
292
  .description('Install a command group from a remote Git repository.')
@@ -245,19 +297,20 @@ ctl.command('install')
245
297
  .summary('install remote command group')
246
298
  .addHelpText('after', `
247
299
  Additional Info:
248
- Fetches the .agentctl folder from the remote repository and installs it into
300
+ Fetches the.agentctl folder from the remote repository and installs it into
249
301
  your local or global agentctl environment.
250
302
 
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
- `)
303
+ Examples:
304
+ $ agentctl ctl install https://github.com/org/repo-tools
305
+ $ agentctl ctl install https://github.com/org/deploy-scripts deploy --global
306
+ `)
255
307
  .action(withErrorHandling(async (repoUrl, pathParts, opts, command) => {
256
308
  if (!repoUrl) {
257
309
  command.help();
258
310
  return;
259
311
  }
260
- await (0, ctl_1.install)(repoUrl, pathParts, { global: opts.global, allowCollisions: opts.allowCollisions });
312
+ const normalized = normalizePath(pathParts || []);
313
+ await (0, ctl_1.install)(repoUrl, normalized, { global: opts.global, allowCollisions: opts.allowCollisions });
261
314
  }));
262
315
  // --- Dynamic Command Logic ---
263
316
  async function handleDynamicCommand(args) {
@@ -283,7 +336,7 @@ async function handleDynamicCommand(args) {
283
336
  return e;
284
337
  if (e.message.startsWith(' '))
285
338
  return e;
286
- if (e.message === 'No description')
339
+ if (e.message === 'Command group containing subcommands')
287
340
  return { ...e, message: chalk_1.default.dim(e.message) };
288
341
  }
289
342
  return e;
@@ -309,7 +362,8 @@ async function handleDynamicCommand(args) {
309
362
  const lines = [''];
310
363
  lines.push(chalk_1.default.bold('User Commands:'));
311
364
  for (const cmd of topLevel) {
312
- lines.push(` ${chalk_1.default.yellow(cmd.path.padEnd(27))}${cmd.description}`);
365
+ const desc = cmd.description || 'Command group containing subcommands';
366
+ lines.push(` ${chalk_1.default.yellow(cmd.path.padEnd(27))}${desc}`);
313
367
  }
314
368
  lines.push('');
315
369
  program.addHelpText('after', lines.join('\n'));
package/dist/logic/ctl.js CHANGED
@@ -31,7 +31,8 @@ exports.Logic = {
31
31
  const effects = [{ type: 'mkdir', path: targetDir }];
32
32
  const manifest = {
33
33
  name,
34
- description: '',
34
+ description: '<insert summary>',
35
+ help: '<insert usage/help instructions>',
35
36
  type,
36
37
  };
37
38
  if (type === 'scaffold') {
@@ -11,6 +11,15 @@ exports.AppLogic = {
11
11
  return [{ type: 'log', message: `Command '${args.join(' ')}' not found. Run 'agentctl list' to see available commands.` }];
12
12
  }
13
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
+ }
14
23
  if (manifest.run) {
15
24
  const cmdDir = path_1.default.dirname(manifestPath);
16
25
  let runCmd = manifest.run;
@@ -19,33 +28,38 @@ exports.AppLogic = {
19
28
  }
20
29
  runCmd = runCmd.replace(/{{DIR}}/g, cmdDir);
21
30
  const fullCommand = `${runCmd} ${remainingArgs.join(' ')}`;
22
- return [
23
- { type: 'log', message: `[${scope}] Running: ${fullCommand}` },
24
- {
25
- type: 'spawn',
26
- command: fullCommand,
27
- options: {
28
- cwd: process.cwd(),
29
- shell: true,
30
- stdio: 'inherit',
31
- env: { ...process.env, AGENTCTL_SCOPE: scope }
32
- },
33
- onExit: (code) => {
34
- process.exit(code || 0);
35
- }
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);
36
42
  }
37
- ];
43
+ });
44
+ return effects;
38
45
  }
39
46
  else {
40
- return exports.AppLogic.planGroupList(manifest, cmdPath, []); // Shell will call this again with actual children
47
+ return [...effects, ...exports.AppLogic.planGroupList(manifest, cmdPath, [], manifestPath)];
41
48
  }
42
49
  },
43
- planGroupList(manifest, cmdPath, allCommands) {
44
- const effects = [
45
- { type: 'log', message: manifest.name },
46
- { type: 'log', message: manifest.description || 'No description' },
47
- { type: 'log', message: '\nSubcommands:' }
48
- ];
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:' });
49
63
  const prefix = cmdPath + ' ';
50
64
  const depth = cmdPath.split(' ').length;
51
65
  const children = allCommands.filter(c => c.path.startsWith(prefix) && c.path !== cmdPath);
@@ -56,7 +70,8 @@ exports.AppLogic = {
56
70
  else {
57
71
  for (const child of direct) {
58
72
  const name = child.path.split(' ').pop();
59
- effects.push({ type: 'log', message: ` ${name}\t${child.description}` });
73
+ const desc = child.description || 'Command group containing subcommands';
74
+ effects.push({ type: 'log', message: ` ${name}\t${desc}` });
60
75
  }
61
76
  }
62
77
  return effects;
@@ -43,6 +43,9 @@ function planInstallCopy(ctx, deps) {
43
43
  }
44
44
  // Ensure target path exists
45
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
+ }
46
49
  // Copy contents
47
50
  effects.push({
48
51
  type: 'copy',
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "access": "public"
5
5
  },
6
6
  "description": "Agent Controller - A unified interface for humans and AI agents",
7
- "version": "1.1.3",
7
+ "version": "1.2.0",
8
8
  "main": "dist/index.js",
9
9
  "bin": {
10
10
  "agentctl": "./dist/index.js"