@michaelhartmayer/agentctl 1.1.3 → 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 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,44 @@ 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
+ .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
+ `);
25
56
  const withErrorHandling = (fn) => {
26
57
  return async (...args) => {
27
58
  try {
@@ -38,6 +69,7 @@ const withErrorHandling = (fn) => {
38
69
  }
39
70
  };
40
71
  };
72
+ const normalizePath = (parts) => parts.flatMap(p => p.split(/[\s/\\:]+/).filter(Boolean));
41
73
  ctl.command('scaffold')
42
74
  .description('Scaffold a new command directory with a manifest and starter script.')
43
75
  .argument('[path...]', 'Hierarchical path for the new command (e.g., "dev start" or "utils/cleanup")')
@@ -45,20 +77,34 @@ ctl.command('scaffold')
45
77
  .addHelpText('after', `
46
78
  Additional Info:
47
79
  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.
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).
51
96
 
52
97
  Examples:
53
- $ agentctl ctl scaffold build front
54
- $ agentctl ctl scaffold utils/backup
98
+ $ agentctl ctl scaffold build:front
99
+ $ agentctl ctl scaffold "build front" # Creates group 'build' and subcommand 'front'
55
100
  `)
56
101
  .action(withErrorHandling(async (pathParts, opts, command) => {
57
- if (!pathParts || pathParts.length === 0) {
102
+ const normalized = normalizePath(pathParts);
103
+ if (normalized.length === 0) {
58
104
  command.help();
59
105
  return;
60
106
  }
61
- await (0, ctl_1.scaffold)(pathParts);
107
+ await (0, ctl_1.scaffold)(normalized);
62
108
  }));
63
109
  ctl.command('alias')
64
110
  .description('Create a command that executes a raw shell string.')
@@ -75,7 +121,11 @@ Examples:
75
121
  return;
76
122
  }
77
123
  const target = args.pop();
78
- const name = args;
124
+ const name = normalizePath(args);
125
+ if (name.length === 0) {
126
+ command.help();
127
+ return;
128
+ }
79
129
  await (0, ctl_1.alias)(name, target);
80
130
  }));
81
131
  ctl.command('group')
@@ -86,17 +136,33 @@ ctl.command('group')
86
136
  Additional Info:
87
137
  Groups allow you to categorize commands. Running a group command without
88
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.
89
154
 
90
155
  Examples:
91
156
  $ agentctl ctl group dev
92
- $ agentctl ctl group data/pipelines
157
+ $ agentctl ctl group "data pipelines" # Creates group 'data' and subgroup 'pipelines'
93
158
  `)
94
159
  .action(withErrorHandling(async (parts, opts, command) => {
95
- if (!parts || parts.length === 0) {
160
+ const normalized = normalizePath(parts);
161
+ if (normalized.length === 0) {
96
162
  command.help();
97
163
  return;
98
164
  }
99
- await (0, ctl_1.group)(parts);
165
+ await (0, ctl_1.group)(normalized);
100
166
  }));
101
167
  ctl.command('rm')
102
168
  .description('Permanently remove a command or group.')
@@ -105,15 +171,16 @@ ctl.command('rm')
105
171
  .summary('delete a command')
106
172
  .addHelpText('after', `
107
173
  Examples:
108
- $ agentctl ctl rm dev start
109
- $ agentctl ctl rm utils --global
110
- `)
174
+ $ agentctl ctl rm dev start
175
+ $ agentctl ctl rm utils--global
176
+ `)
111
177
  .action(withErrorHandling(async (parts, opts, command) => {
112
- if (!parts || parts.length === 0) {
178
+ const normalized = normalizePath(parts);
179
+ if (normalized.length === 0) {
113
180
  command.help();
114
181
  return;
115
182
  }
116
- await (0, ctl_1.rm)(parts, { global: opts.global });
183
+ await (0, ctl_1.rm)(normalized, { global: opts.global });
117
184
  }));
118
185
  ctl.command('mv')
119
186
  .description('Move or rename a command/group within its current scope.')
@@ -123,26 +190,28 @@ ctl.command('mv')
123
190
  .summary('rename/move a command')
124
191
  .addHelpText('after', `
125
192
  Examples:
126
- $ agentctl ctl mv "dev start" "dev begin"
127
- $ agentctl ctl mv utils scripts --global
128
- `)
193
+ $ agentctl ctl mv "dev start" "dev begin"
194
+ $ agentctl ctl mv utils scripts--global
195
+ `)
129
196
  .action(withErrorHandling(async (src, dest, opts, command) => {
130
- if (!src || !dest) {
197
+ const normalizedSrc = normalizePath(src ? [src] : []);
198
+ const normalizedDest = normalizePath(dest ? [dest] : []);
199
+ if (normalizedSrc.length === 0 || normalizedDest.length === 0) {
131
200
  command.help();
132
201
  return;
133
202
  }
134
- await (0, ctl_1.mv)(src.split(' '), dest.split(' '), { global: opts.global });
203
+ await (0, ctl_1.mv)(normalizedSrc, normalizedDest, { global: opts.global });
135
204
  }));
136
205
  ctl.command('list')
137
206
  .description('List all available commands across local and global scopes.')
138
207
  .summary('list all commands')
139
208
  .addHelpText('after', `
140
209
  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
210
+ TYPE - scaffold, alias, or group
211
+ SCOPE - local(project - specific) or global(user - wide)
212
+ COMMAND - The path used to invoke the command
144
213
  DESCRIPTION - Brief text from the command's manifest
145
- `)
214
+ `)
146
215
  .action(withErrorHandling(async () => {
147
216
  const items = await (0, ctl_1.list)();
148
217
  console.log(chalk_1.default.bold('TYPE SCOPE COMMAND DESCRIPTION'));
@@ -158,14 +227,15 @@ ctl.command('inspect')
158
227
  .summary('inspect command details')
159
228
  .addHelpText('after', `
160
229
  Examples:
161
- $ agentctl ctl inspect dev start
162
- `)
230
+ $ agentctl ctl inspect dev start
231
+ `)
163
232
  .action(withErrorHandling(async (parts, opts, command) => {
164
- if (!parts || parts.length === 0) {
233
+ const normalized = normalizePath(parts);
234
+ if (normalized.length === 0) {
165
235
  command.help();
166
236
  return;
167
237
  }
168
- const info = await (0, ctl_1.inspect)(parts);
238
+ const info = await (0, ctl_1.inspect)(normalized);
169
239
  if (info) {
170
240
  console.log(JSON.stringify(info, null, 2));
171
241
  }
@@ -182,18 +252,19 @@ ctl.command('global')
182
252
  .summary('make a command global')
183
253
  .addHelpText('after', `
184
254
  Additional Info:
185
- Global commands are stored in your home directory and are available in any project.
255
+ Global commands are stored in your home directory and are available in any project.
186
256
 
187
- Examples:
188
- $ agentctl ctl global utils/cleanup
189
- $ agentctl ctl global dev/deploy --move
190
- `)
257
+ Examples:
258
+ $ agentctl ctl global utils / cleanup
259
+ $ agentctl ctl global dev / deploy--move
260
+ `)
191
261
  .action(withErrorHandling(async (parts, opts, command) => {
192
- if (!parts || parts.length === 0) {
262
+ const normalized = normalizePath(parts);
263
+ if (normalized.length === 0) {
193
264
  command.help();
194
265
  return;
195
266
  }
196
- await (0, ctl_1.pushGlobal)(parts, { move: opts.move, copy: opts.copy || !opts.move });
267
+ await (0, ctl_1.pushGlobal)(normalized, { move: opts.move, copy: opts.copy || !opts.move });
197
268
  }));
198
269
  ctl.command('local')
199
270
  .description('Pull a global command into the current local project.')
@@ -203,38 +274,16 @@ ctl.command('local')
203
274
  .summary('make a command local')
204
275
  .addHelpText('after', `
205
276
  Examples:
206
- $ agentctl ctl local utils/shared
207
- $ agentctl ctl local snippets/js --move
208
- `)
277
+ $ agentctl ctl local utils / shared
278
+ $ agentctl ctl local snippets / js--move
279
+ `)
209
280
  .action(withErrorHandling(async (parts, opts, command) => {
210
- if (!parts || parts.length === 0) {
281
+ const normalized = normalizePath(parts);
282
+ if (normalized.length === 0) {
211
283
  command.help();
212
284
  return;
213
285
  }
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 });
286
+ await (0, ctl_1.pullLocal)(normalized, { move: opts.move, copy: opts.copy || !opts.move });
238
287
  }));
239
288
  ctl.command('install')
240
289
  .description('Install a command group from a remote Git repository.')
@@ -245,19 +294,20 @@ ctl.command('install')
245
294
  .summary('install remote command group')
246
295
  .addHelpText('after', `
247
296
  Additional Info:
248
- Fetches the .agentctl folder from the remote repository and installs it into
297
+ Fetches the.agentctl folder from the remote repository and installs it into
249
298
  your local or global agentctl environment.
250
299
 
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
- `)
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
+ `)
255
304
  .action(withErrorHandling(async (repoUrl, pathParts, opts, command) => {
256
305
  if (!repoUrl) {
257
306
  command.help();
258
307
  return;
259
308
  }
260
- await (0, ctl_1.install)(repoUrl, pathParts, { global: opts.global, allowCollisions: opts.allowCollisions });
309
+ const normalized = normalizePath(pathParts || []);
310
+ await (0, ctl_1.install)(repoUrl, normalized, { global: opts.global, allowCollisions: opts.allowCollisions });
261
311
  }));
262
312
  // --- Dynamic Command Logic ---
263
313
  async function handleDynamicCommand(args) {
@@ -283,7 +333,7 @@ async function handleDynamicCommand(args) {
283
333
  return e;
284
334
  if (e.message.startsWith(' '))
285
335
  return e;
286
- if (e.message === 'No description')
336
+ if (e.message === 'Command group containing subcommands')
287
337
  return { ...e, message: chalk_1.default.dim(e.message) };
288
338
  }
289
339
  return e;
@@ -309,7 +359,8 @@ async function handleDynamicCommand(args) {
309
359
  const lines = [''];
310
360
  lines.push(chalk_1.default.bold('User Commands:'));
311
361
  for (const cmd of topLevel) {
312
- lines.push(` ${chalk_1.default.yellow(cmd.path.padEnd(27))}${cmd.description}`);
362
+ const desc = cmd.description || 'Command group containing subcommands';
363
+ lines.push(` ${chalk_1.default.yellow(cmd.path.padEnd(27))}${desc}`);
313
364
  }
314
365
  lines.push('');
315
366
  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.1.4",
8
8
  "main": "dist/index.js",
9
9
  "bin": {
10
10
  "agentctl": "./dist/index.js"