summon-cli 0.2.0 → 0.3.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
@@ -6,9 +6,9 @@
6
6
 
7
7
  Terminal launcher for local AI CLIs. Run `summon`, pick a tool, launch it.
8
8
 
9
- Supported: Codex CLI, Claude Code, Antigravity CLI, Cursor CLI, GitHub Copilot CLI, opencode CLI. Missing tools show dimmed.
9
+ Built-in: Codex CLI, Claude Code, Antigravity CLI, Cursor CLI, GitHub Copilot CLI, opencode CLI. Missing tools show dimmed. Add your own with `--add`.
10
10
 
11
- > Pre-release (0.1.0).
11
+ > Pre-release (0.2.0).
12
12
 
13
13
  ## Install
14
14
 
@@ -24,10 +24,19 @@ Move with arrows or `j/k` or `1-9`. `Enter` launches, `Esc` quits.
24
24
  - `summon` open the picker
25
25
  - `summon reorder` set the order
26
26
  - `summon default <tool>` start the cursor on it (`off` clears, no arg = pick)
27
- - `summon alias <name>` add another command name (e.g. `summon alias cli`), or shortcut `summon --add <name>`
27
+ - `summon alias <name>` install a second shell command name for the launcher
28
28
  - `summon help`
29
29
 
30
- Flags: `--no-logo` / `--logo` toggle the side logo (remembered). Args after `--` go to the launched tool.
30
+ ## Flags
31
+
32
+ - `--add <cmd> [label]` add a custom CLI to the menu. Example:
33
+ ```sh
34
+ summon --add grok "Grok Build"
35
+ ```
36
+ Adds an entry that runs `grok` and shows as `Grok Build` with hint `Custom provider`.
37
+ - `--remove <id>` remove a custom CLI from the menu.
38
+ - `--no-logo` / `--logo` toggle the side logo (remembered).
39
+ - Args after `--` go to the launched tool.
31
40
 
32
41
  ## Config
33
42
 
package/bin/cli.mjs CHANGED
@@ -31,10 +31,10 @@ if (flags.has('--no-logo')) {
31
31
  logo = true;
32
32
  saveConfig({logo: true});
33
33
  }
34
- const items = orderTools(config.order);
34
+ const items = orderTools(config.order, config.customTools);
35
35
 
36
36
  if (process.env.CLI_LEVEL_SNAPSHOT === '1') {
37
- process.stdout.write(renderSnapshot(Number(process.env.CLI_LEVEL_ACTIVE || 0)));
37
+ process.stdout.write(renderSnapshot(Number(process.env.CLI_LEVEL_ACTIVE || 0), items));
38
38
  process.exit(0);
39
39
  }
40
40
 
@@ -44,7 +44,12 @@ if (flags.has('--help') || flags.has('-h') || command === 'help') {
44
44
  }
45
45
 
46
46
  if (flags.has('--add')) {
47
- runAlias(args[0]);
47
+ runAdd(args[0], args[1]);
48
+ process.exit(0);
49
+ }
50
+
51
+ if (flags.has('--remove') || flags.has('--rm')) {
52
+ runRemove(args[0]);
48
53
  process.exit(0);
49
54
  }
50
55
 
@@ -70,7 +75,7 @@ switch (command) {
70
75
 
71
76
  async function runMenu() {
72
77
  if (!canRenderTui) {
73
- process.stdout.write(renderSnapshot(0));
78
+ process.stdout.write(renderSnapshot(0, items));
74
79
  process.stderr.write(`${PROG}: interactive terminal required for selection.\n`);
75
80
  process.exit(2);
76
81
  }
@@ -91,6 +96,7 @@ async function runReorder() {
91
96
  const order = await new Promise(resolve => {
92
97
  let instance;
93
98
  instance = render(React.createElement(ReorderApp, {
99
+ items,
94
100
  onCancel: () => {
95
101
  instance.unmount();
96
102
  resolve(null);
@@ -140,9 +146,51 @@ async function runDefault(target) {
140
146
  process.stdout.write(`Default set to ${selected.label}. The menu now opens with the cursor on it.\n`);
141
147
  }
142
148
 
149
+ function runAdd(name, label) {
150
+ if (!name || !/^[a-zA-Z0-9._-]+$/.test(name)) {
151
+ process.stderr.write(`${PROG}: usage: ${PROG} --add <command> [label]\n`);
152
+ process.exit(2);
153
+ }
154
+
155
+ const id = name.toLowerCase();
156
+ if (tools.some(t => t.id === id)) {
157
+ process.stderr.write(`${PROG}: '${id}' is a built-in tool already.\n`);
158
+ process.exit(2);
159
+ }
160
+
161
+ const current = loadConfig().customTools || [];
162
+ if (current.some(t => t.id === id)) {
163
+ process.stderr.write(`${PROG}: '${id}' is already in your list.\n`);
164
+ process.exit(2);
165
+ }
166
+
167
+ const labelText = label || (name.charAt(0).toUpperCase() + name.slice(1));
168
+ const next = [...current, {id, label: labelText, command: name, hint: 'Custom provider'}];
169
+ saveConfig({customTools: next});
170
+ process.stdout.write(`Added '${labelText}' (command: ${name}) to the menu.\n`);
171
+ }
172
+
173
+ function runRemove(name) {
174
+ if (!name) {
175
+ process.stderr.write(`${PROG}: usage: ${PROG} --remove <id>\n`);
176
+ process.exit(2);
177
+ }
178
+
179
+ const id = name.toLowerCase();
180
+ const current = loadConfig().customTools || [];
181
+ const next = current.filter(t => t.id !== id);
182
+ if (next.length === current.length) {
183
+ process.stderr.write(`${PROG}: no custom tool '${id}'. List: ${current.map(t => t.id).join(', ') || '(none)'}.\n`);
184
+ process.exit(2);
185
+ }
186
+
187
+ saveConfig({customTools: next});
188
+ process.stdout.write(`Removed '${id}'.\n`);
189
+ }
190
+
143
191
  function runAlias(name) {
144
192
  if (!name || !/^[a-zA-Z0-9._-]+$/.test(name)) {
145
- process.stderr.write(`${PROG}: usage: ${PROG} alias <name> (or ${PROG} --add <name>)\n`);
193
+ process.stderr.write(`${PROG}: usage: ${PROG} alias <name>\n`);
146
194
  process.exit(2);
147
195
  }
148
196
 
@@ -243,12 +291,12 @@ Commands:
243
291
  (none) Open the picker
244
292
  reorder Set the order tools appear in
245
293
  default [tool] Start the cursor on <tool>; no tool = pick one; 'off' clears
246
- alias <name> Install a second command name for this launcher
247
- (shortcut: ${PROG} --add <name>)
294
+ alias <name> Install a second shell command name for this launcher
248
295
  help Show this help
249
296
 
250
297
  Options:
251
- --add <name> Install a second command name (same as 'alias')
298
+ --add <cmd> [lbl] Add a custom CLI to the menu (e.g. --add grok "Grok Build")
299
+ --remove <id> Remove a custom CLI from the menu
252
300
  --no-logo Hide the side logo and remember it
253
301
  --logo Show the side logo and remember it
254
302
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "summon-cli",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Summon your AI CLI. Terminal launcher for Codex CLI, Claude Code, Antigravity CLI, Cursor CLI, GitHub Copilot CLI, and opencode CLI.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/app.mjs CHANGED
@@ -249,15 +249,15 @@ export function App({items = tools, logo = false, initialId = null, onSelect, on
249
249
  );
250
250
  }
251
251
 
252
- export function ReorderApp({onDone, onCancel}) {
252
+ export function ReorderApp({items = tools, onDone, onCancel}) {
253
253
  const {exit} = useApp();
254
254
  const [order, setOrder] = useState([]);
255
255
  const [active, setActive] = useState(0);
256
256
 
257
257
  const seekUnpicked = (from, dir, taken) => {
258
- for (let step = 0; step <= tools.length; step += 1) {
259
- const index = (from + dir * step + tools.length * (step + 1)) % tools.length;
260
- if (!taken.has(tools[index].id)) {
258
+ for (let step = 0; step <= items.length; step += 1) {
259
+ const index = (from + dir * step + items.length * (step + 1)) % items.length;
260
+ if (!taken.has(items[index].id)) {
261
261
  return index;
262
262
  }
263
263
  }
@@ -290,13 +290,13 @@ export function ReorderApp({onDone, onCancel}) {
290
290
  }
291
291
 
292
292
  if (key.return) {
293
- const id = tools[active].id;
293
+ const id = items[active].id;
294
294
  if (taken.has(id)) {
295
295
  return;
296
296
  }
297
297
 
298
298
  const next = [...order, id];
299
- if (next.length === tools.length) {
299
+ if (next.length === items.length) {
300
300
  exit();
301
301
  onDone(next);
302
302
  return;
@@ -314,7 +314,7 @@ export function ReorderApp({onDone, onCancel}) {
314
314
  React.createElement(Text, {color: dimGray}, ' pick first to last')
315
315
  ),
316
316
  React.createElement(Box, {flexDirection: 'column', gap: 0},
317
- tools.map((tool, index) => {
317
+ items.map((tool, index) => {
318
318
  const pos = order.indexOf(tool.id);
319
319
  const picked = pos >= 0;
320
320
  const isActive = index === active && !picked;
@@ -509,9 +509,10 @@ function renderPlainLine(tool, index, activeIndex) {
509
509
  return `${arrow} ${label.padEnd(16)} ${tool.hint}`;
510
510
  }
511
511
 
512
- export function renderSnapshot(activeIndex = 0) {
513
- const bounded = Math.max(0, Math.min(tools.length - 1, activeIndex));
514
- return `${tools.map((tool, index) => renderPlainLine(tool, index, bounded)).join('\n')}\n`;
512
+ export function renderSnapshot(activeIndex = 0, items = null) {
513
+ const list = items || tools;
514
+ const bounded = Math.max(0, Math.min(list.length - 1, activeIndex));
515
+ return `${list.map((tool, index) => renderPlainLine(tool, index, bounded)).join('\n')}\n`;
515
516
  }
516
517
 
517
518
  function commandExists(command) {
@@ -545,8 +546,26 @@ function nextAvailable(current, availability, items) {
545
546
  }
546
547
 
547
548
  // Reorder canonical tools by an array of ids; unknown ids ignored, missing appended.
548
- export function orderTools(order = []) {
549
- const byId = new Map(tools.map(tool => [tool.id, tool]));
549
+ export function buildCustomTool({id, label, command, hint, palette} = {}) {
550
+ if (!id) return null;
551
+ return {
552
+ id,
553
+ label: label || id,
554
+ command: command || id,
555
+ hint: hint || 'Custom provider',
556
+ palette: palette && palette.length ? palette : ['#9aa0a6', '#cdcdcd']
557
+ };
558
+ }
559
+
560
+ export function orderTools(order = [], customTools = []) {
561
+ const all = [...tools];
562
+ for (const raw of customTools) {
563
+ const tool = buildCustomTool(raw);
564
+ if (!tool) continue;
565
+ if (all.some(t => t.id === tool.id)) continue;
566
+ all.push(tool);
567
+ }
568
+ const byId = new Map(all.map(tool => [tool.id, tool]));
550
569
  const result = [];
551
570
  for (const id of order) {
552
571
  if (byId.has(id)) {
@@ -554,7 +573,7 @@ export function orderTools(order = []) {
554
573
  byId.delete(id);
555
574
  }
556
575
  }
557
- for (const tool of tools) {
576
+ for (const tool of all) {
558
577
  if (byId.has(tool.id)) {
559
578
  result.push(tool);
560
579
  }
package/src/config.mjs CHANGED
@@ -8,7 +8,8 @@ const configPath = join(baseDir, 'summon-cli', 'config.json');
8
8
  const DEFAULTS = {
9
9
  order: [],
10
10
  logo: true,
11
- default: null
11
+ default: null,
12
+ customTools: []
12
13
  };
13
14
 
14
15
  export function loadConfig() {