cligr 1.0.7 → 1.0.9
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/.claude/worktrees/agent-ac25cfb2/.claude/settings.local.json +30 -0
- package/.claude/worktrees/agent-ac25cfb2/README.md +65 -0
- package/.claude/worktrees/agent-ac25cfb2/docs/plans/2026-02-13-named-params-support.md +391 -0
- package/.claude/worktrees/agent-ac25cfb2/docs/plans/2026-02-25-named-items-design.md +164 -0
- package/.claude/worktrees/agent-ac25cfb2/docs/plans/2026-02-25-named-items-implementation.md +460 -0
- package/.claude/worktrees/agent-ac25cfb2/package-lock.json +554 -0
- package/.claude/worktrees/agent-ac25cfb2/package.json +27 -0
- package/.claude/worktrees/agent-ac25cfb2/scripts/build.js +20 -0
- package/.claude/worktrees/agent-ac25cfb2/scripts/test.js +168 -0
- package/.claude/worktrees/agent-ac25cfb2/src/commands/config.ts +121 -0
- package/.claude/worktrees/agent-ac25cfb2/src/commands/groups.ts +68 -0
- package/.claude/worktrees/agent-ac25cfb2/src/commands/ls.ts +25 -0
- package/.claude/worktrees/agent-ac25cfb2/src/commands/up.ts +49 -0
- package/.claude/worktrees/agent-ac25cfb2/src/config/loader.ts +148 -0
- package/.claude/worktrees/agent-ac25cfb2/src/config/types.ts +26 -0
- package/.claude/worktrees/agent-ac25cfb2/src/index.ts +97 -0
- package/.claude/worktrees/agent-ac25cfb2/src/process/manager.ts +270 -0
- package/.claude/worktrees/agent-ac25cfb2/src/process/pid-store.ts +203 -0
- package/.claude/worktrees/agent-ac25cfb2/src/process/template.ts +87 -0
- package/.claude/worktrees/agent-ac25cfb2/tests/integration/blocking-processes-fixed.test.ts +255 -0
- package/.claude/worktrees/agent-ac25cfb2/tests/integration/blocking-processes.test.ts +497 -0
- package/.claude/worktrees/agent-ac25cfb2/tests/integration/commands.test.ts +648 -0
- package/.claude/worktrees/agent-ac25cfb2/tests/integration/config-loader.test.ts +426 -0
- package/.claude/worktrees/agent-ac25cfb2/tests/integration/process-manager.test.ts +394 -0
- package/.claude/worktrees/agent-ac25cfb2/tests/integration/template-expander.test.ts +454 -0
- package/.claude/worktrees/agent-ac25cfb2/tsconfig.json +15 -0
- package/.claude/worktrees/agent-ac25cfb2/usage.md +9 -0
- package/dist/index.js +103 -46
- package/docs/superpowers/specs/2026-04-13-improve-web-ui-console-design.md +38 -0
- package/package.json +1 -1
- package/src/commands/groups.ts +1 -1
- package/src/commands/ls.ts +1 -1
- package/src/commands/serve.ts +65 -8
- package/src/config/loader.ts +6 -2
- package/src/config/types.ts +1 -1
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
# Named Items Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
|
4
|
+
|
|
5
|
+
**Goal:** Replace array-based items with named object format for better service identification in logs, PID files, and ls output.
|
|
6
|
+
|
|
7
|
+
**Architecture:** Add `ItemEntry` type to normalize named items, update config loader to validate and normalize, pass ItemEntry through template expander to process manager.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** TypeScript, js-yaml, Node.js
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Task 1: Update Types
|
|
14
|
+
|
|
15
|
+
**Files:**
|
|
16
|
+
- Modify: `src/config/types.ts:5-10`
|
|
17
|
+
|
|
18
|
+
**Step 1: Add ItemEntry interface**
|
|
19
|
+
|
|
20
|
+
Add after `ToolConfig` interface (around line 4):
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
export interface ItemEntry {
|
|
24
|
+
name: string; // the key from config (e.g., "nginxService1")
|
|
25
|
+
value: string; // the value string (e.g., "nginx,8080")
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Step 2: Update GroupConfig items type**
|
|
30
|
+
|
|
31
|
+
Change line 9 from:
|
|
32
|
+
```typescript
|
|
33
|
+
items: string[];
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
To:
|
|
37
|
+
```typescript
|
|
38
|
+
items: Record<string, string>;
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Step 3: Verify types compile**
|
|
42
|
+
|
|
43
|
+
Run: `npm run typecheck`
|
|
44
|
+
Expected: No errors
|
|
45
|
+
|
|
46
|
+
**Step 4: Commit**
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
git add src/config/types.ts
|
|
50
|
+
git commit -m "feat(types): add ItemEntry and update GroupConfig items to named format"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Task 2: Update Config Loader - Validation
|
|
56
|
+
|
|
57
|
+
**Files:**
|
|
58
|
+
- Modify: `src/config/loader.ts:60-72`
|
|
59
|
+
|
|
60
|
+
**Step 1: Update validate method to validate items**
|
|
61
|
+
|
|
62
|
+
Replace the `validate` method (lines 60-72) with:
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
private validate(config: unknown): CliGrConfig {
|
|
66
|
+
if (!config || typeof config !== 'object') {
|
|
67
|
+
throw new ConfigError('Config must be an object');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const cfg = config as Record<string, unknown>;
|
|
71
|
+
|
|
72
|
+
if (!cfg.groups || typeof cfg.groups !== 'object') {
|
|
73
|
+
throw new ConfigError('Config must have a "groups" object');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Validate each group's items
|
|
77
|
+
for (const [groupName, group] of Object.entries(cfg.groups as Record<string, unknown>)) {
|
|
78
|
+
if (group && typeof group === 'object') {
|
|
79
|
+
const groupObj = group as Record<string, unknown>;
|
|
80
|
+
this.validateItems(groupObj.items, groupName);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return cfg as unknown as CliGrConfig;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private validateItems(items: unknown, groupName: string): void {
|
|
88
|
+
if (!items || typeof items !== 'object' || Array.isArray(items)) {
|
|
89
|
+
throw new ConfigError(
|
|
90
|
+
`Group "${groupName}": items must be an object with named entries, e.g.:\n` +
|
|
91
|
+
' items:\n' +
|
|
92
|
+
' serviceName: "value1,value2"'
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const seenNames = new Set<string>();
|
|
97
|
+
|
|
98
|
+
for (const [name, value] of Object.entries(items as Record<string, unknown>)) {
|
|
99
|
+
if (typeof value !== 'string') {
|
|
100
|
+
throw new ConfigError(`Group "${groupName}": item "${name}" must have a string value`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (seenNames.has(name)) {
|
|
104
|
+
throw new ConfigError(
|
|
105
|
+
`Group "${groupName}": duplicate item name "${name}". ` +
|
|
106
|
+
`Item names must be unique within a group.`
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
seenNames.add(name);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**Step 2: Add ItemEntry import**
|
|
115
|
+
|
|
116
|
+
Add `ItemEntry` to the import at line 5:
|
|
117
|
+
```typescript
|
|
118
|
+
import type { CliGrConfig, GroupConfig, ToolConfig, ItemEntry } from './types.js';
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**Step 3: Verify types compile**
|
|
122
|
+
|
|
123
|
+
Run: `npm run typecheck`
|
|
124
|
+
Expected: No errors
|
|
125
|
+
|
|
126
|
+
**Step 4: Commit**
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
git add src/config/loader.ts
|
|
130
|
+
git commit -m "feat(loader): add validation for named items format"
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Task 3: Update Config Loader - Normalization
|
|
136
|
+
|
|
137
|
+
**Files:**
|
|
138
|
+
- Modify: `src/config/loader.ts:74-100`
|
|
139
|
+
|
|
140
|
+
**Step 1: Add normalizeItems method**
|
|
141
|
+
|
|
142
|
+
Add after `validateItems` method:
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
private normalizeItems(items: Record<string, string>): ItemEntry[] {
|
|
146
|
+
return Object.entries(items).map(([name, value]) => ({
|
|
147
|
+
name,
|
|
148
|
+
value
|
|
149
|
+
}));
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
**Step 2: Update getGroup return type and implementation**
|
|
154
|
+
|
|
155
|
+
Replace the `getGroup` method (lines 74-100) with:
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
getGroup(name: string): { config: GroupConfig; items: ItemEntry[]; tool: string | null; toolTemplate: string | null; params: Record<string, string> } {
|
|
159
|
+
const config = this.load();
|
|
160
|
+
const group = config.groups[name];
|
|
161
|
+
|
|
162
|
+
if (!group) {
|
|
163
|
+
const available = Object.keys(config.groups).join(', ');
|
|
164
|
+
throw new ConfigError(`Unknown group: ${name}. Available: ${available}`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Normalize items to ItemEntry[]
|
|
168
|
+
const items = this.normalizeItems(group.items);
|
|
169
|
+
|
|
170
|
+
// Resolve tool
|
|
171
|
+
let toolTemplate: string | null = null;
|
|
172
|
+
let tool: string | null = null;
|
|
173
|
+
|
|
174
|
+
if (config.tools && config.tools[group.tool]) {
|
|
175
|
+
toolTemplate = config.tools[group.tool].cmd;
|
|
176
|
+
tool = group.tool;
|
|
177
|
+
} else {
|
|
178
|
+
tool = null;
|
|
179
|
+
toolTemplate = null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const params = group.params || {};
|
|
183
|
+
|
|
184
|
+
return { config: group, items, tool, toolTemplate, params };
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
**Step 3: Verify types compile**
|
|
189
|
+
|
|
190
|
+
Run: `npm run typecheck`
|
|
191
|
+
Expected: Errors in ls.ts and up.ts (they need updates)
|
|
192
|
+
|
|
193
|
+
**Step 4: Commit**
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
git add src/config/loader.ts
|
|
197
|
+
git commit -m "feat(loader): normalize items to ItemEntry[]"
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## Task 4: Update TemplateExpander
|
|
203
|
+
|
|
204
|
+
**Files:**
|
|
205
|
+
- Modify: `src/process/template.ts:26-44`
|
|
206
|
+
|
|
207
|
+
**Step 1: Add ItemEntry import**
|
|
208
|
+
|
|
209
|
+
Add at top of file:
|
|
210
|
+
```typescript
|
|
211
|
+
import type { ProcessItem, ItemEntry } from '../config/types.js';
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
**Step 2: Update expand method signature and implementation**
|
|
215
|
+
|
|
216
|
+
Replace the `expand` method (lines 26-44) with:
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
static expand(template: string, item: ItemEntry, index: number, params: Record<string, string> = {}): ProcessItem {
|
|
220
|
+
const args = item.value.split(',').map(s => s.trim());
|
|
221
|
+
|
|
222
|
+
// Use explicit name from ItemEntry
|
|
223
|
+
const name = item.name;
|
|
224
|
+
|
|
225
|
+
// Replace $1, $2, $3 etc. with args (positional params)
|
|
226
|
+
let fullCmd = template;
|
|
227
|
+
for (let i = args.length - 1; i >= 0; i--) {
|
|
228
|
+
const placeholder = `$${i + 1}`;
|
|
229
|
+
fullCmd = fullCmd.replaceAll(placeholder, args[i]);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Replace named params ($name, $env, etc.) AFTER positional params
|
|
233
|
+
fullCmd = this.expandNamedParams(fullCmd, params);
|
|
234
|
+
|
|
235
|
+
return { name, args, fullCmd };
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
**Step 3: Update parseItem method signature and implementation**
|
|
240
|
+
|
|
241
|
+
Replace the `parseItem` method (lines 54-97) with:
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
static parseItem(
|
|
245
|
+
tool: string | null,
|
|
246
|
+
toolTemplate: string | null,
|
|
247
|
+
item: ItemEntry,
|
|
248
|
+
index: number,
|
|
249
|
+
params: Record<string, string> = {}
|
|
250
|
+
): ProcessItem {
|
|
251
|
+
if (toolTemplate) {
|
|
252
|
+
// Use registered tool template
|
|
253
|
+
const result = this.expand(toolTemplate, item, index, params);
|
|
254
|
+
|
|
255
|
+
// If there are more args than placeholders in the template, append them
|
|
256
|
+
const placeholdersInTemplate = (toolTemplate.match(/\$\d+/g) || []);
|
|
257
|
+
let maxPlaceholder = 0;
|
|
258
|
+
for (const p of placeholdersInTemplate) {
|
|
259
|
+
const num = parseInt(p.substring(1), 10);
|
|
260
|
+
if (num > maxPlaceholder) maxPlaceholder = num;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (maxPlaceholder > 0 && result.args.length > maxPlaceholder) {
|
|
264
|
+
const remainingArgs = result.args.slice(maxPlaceholder);
|
|
265
|
+
result.fullCmd = `${result.fullCmd} ${remainingArgs.join(' ')}`;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return result;
|
|
269
|
+
} else {
|
|
270
|
+
// Direct executable - use tool as command prefix
|
|
271
|
+
const args = item.value.split(',').map(s => s.trim());
|
|
272
|
+
const name = item.name;
|
|
273
|
+
const fullCmd = tool ? `${tool} ${item.value}` : item.value;
|
|
274
|
+
return { name, args, fullCmd };
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
**Step 4: Verify types compile**
|
|
280
|
+
|
|
281
|
+
Run: `npm run typecheck`
|
|
282
|
+
Expected: Errors in up.ts only
|
|
283
|
+
|
|
284
|
+
**Step 5: Commit**
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
git add src/process/template.ts
|
|
288
|
+
git commit -m "feat(template): accept ItemEntry instead of raw string"
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
## Task 5: Update ls Command
|
|
294
|
+
|
|
295
|
+
**Files:**
|
|
296
|
+
- Modify: `src/commands/ls.ts:14-17`
|
|
297
|
+
|
|
298
|
+
**Step 1: Update ls to use normalized items**
|
|
299
|
+
|
|
300
|
+
Replace the `lsCommand` function with:
|
|
301
|
+
|
|
302
|
+
```typescript
|
|
303
|
+
export async function lsCommand(groupName: string): Promise<number> {
|
|
304
|
+
const loader = new ConfigLoader();
|
|
305
|
+
|
|
306
|
+
try {
|
|
307
|
+
const { config, items } = loader.getGroup(groupName);
|
|
308
|
+
|
|
309
|
+
console.log(`\nGroup: ${groupName}`);
|
|
310
|
+
console.log(`Tool: ${config.tool}`);
|
|
311
|
+
console.log(`Restart: ${config.restart}`);
|
|
312
|
+
console.log('\nItems:');
|
|
313
|
+
|
|
314
|
+
for (const item of items) {
|
|
315
|
+
console.log(` ${item.name}: ${item.value}`);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
console.log('');
|
|
319
|
+
|
|
320
|
+
return 0;
|
|
321
|
+
} catch (err) {
|
|
322
|
+
console.error((err as Error).message);
|
|
323
|
+
return 1;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
**Step 2: Verify types compile**
|
|
329
|
+
|
|
330
|
+
Run: `npm run typecheck`
|
|
331
|
+
Expected: No errors
|
|
332
|
+
|
|
333
|
+
**Step 3: Commit**
|
|
334
|
+
|
|
335
|
+
```bash
|
|
336
|
+
git add src/commands/ls.ts
|
|
337
|
+
git commit -m "feat(ls): display named items in name: value format"
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
---
|
|
341
|
+
|
|
342
|
+
## Task 6: Update up Command
|
|
343
|
+
|
|
344
|
+
**Files:**
|
|
345
|
+
- Modify: `src/commands/up.ts:19-21`
|
|
346
|
+
|
|
347
|
+
**Step 1: Update up to use normalized items**
|
|
348
|
+
|
|
349
|
+
Replace line 16 with:
|
|
350
|
+
```typescript
|
|
351
|
+
const { config, items, tool, toolTemplate, params } = loader.getGroup(groupName);
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
Replace lines 19-21 with:
|
|
355
|
+
```typescript
|
|
356
|
+
// Build process items
|
|
357
|
+
const processItems = items.map((item, index) =>
|
|
358
|
+
TemplateExpander.parseItem(tool, toolTemplate, item, index, params)
|
|
359
|
+
);
|
|
360
|
+
|
|
361
|
+
// Spawn all processes
|
|
362
|
+
manager.spawnGroup(groupName, processItems, config.restart);
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
**Step 2: Verify types compile**
|
|
366
|
+
|
|
367
|
+
Run: `npm run typecheck`
|
|
368
|
+
Expected: No errors
|
|
369
|
+
|
|
370
|
+
**Step 3: Commit**
|
|
371
|
+
|
|
372
|
+
```bash
|
|
373
|
+
git add src/commands/up.ts
|
|
374
|
+
git commit -m "feat(up): pass ItemEntry to template expander"
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
---
|
|
378
|
+
|
|
379
|
+
## Task 7: Update Tests
|
|
380
|
+
|
|
381
|
+
**Files:**
|
|
382
|
+
- Modify: `tests/integration/process-manager.test.ts`
|
|
383
|
+
|
|
384
|
+
**Step 1: Run existing tests to identify failures**
|
|
385
|
+
|
|
386
|
+
Run: `npm test`
|
|
387
|
+
Expected: Some tests may fail due to config format change
|
|
388
|
+
|
|
389
|
+
**Step 2: Check test file for config usage**
|
|
390
|
+
|
|
391
|
+
Read the test file and identify any inline configs that use array format for items.
|
|
392
|
+
|
|
393
|
+
**Step 3: Update test configs to named format**
|
|
394
|
+
|
|
395
|
+
Change any test configs from:
|
|
396
|
+
```yaml
|
|
397
|
+
items:
|
|
398
|
+
- "nginx,8080"
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
To:
|
|
402
|
+
```yaml
|
|
403
|
+
items:
|
|
404
|
+
nginxService: "nginx,8080"
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
**Step 4: Run tests to verify**
|
|
408
|
+
|
|
409
|
+
Run: `npm test`
|
|
410
|
+
Expected: All tests pass
|
|
411
|
+
|
|
412
|
+
**Step 5: Commit**
|
|
413
|
+
|
|
414
|
+
```bash
|
|
415
|
+
git add tests/integration/process-manager.test.ts
|
|
416
|
+
git commit -m "test: update tests to use named items format"
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
---
|
|
420
|
+
|
|
421
|
+
## Task 8: Final Verification
|
|
422
|
+
|
|
423
|
+
**Step 1: Run full typecheck**
|
|
424
|
+
|
|
425
|
+
Run: `npm run typecheck`
|
|
426
|
+
Expected: No errors
|
|
427
|
+
|
|
428
|
+
**Step 2: Run all tests**
|
|
429
|
+
|
|
430
|
+
Run: `npm test`
|
|
431
|
+
Expected: All tests pass
|
|
432
|
+
|
|
433
|
+
**Step 3: Manual smoke test**
|
|
434
|
+
|
|
435
|
+
Create a test config file with named items and verify:
|
|
436
|
+
- `cligr groups` lists groups
|
|
437
|
+
- `cligr ls <group>` shows named items correctly
|
|
438
|
+
- `cligr up <group>` starts processes with correct names in logs
|
|
439
|
+
|
|
440
|
+
**Step 4: Final commit (if any fixes needed)**
|
|
441
|
+
|
|
442
|
+
```bash
|
|
443
|
+
git add -A
|
|
444
|
+
git commit -m "fix: final cleanup for named items feature"
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
---
|
|
448
|
+
|
|
449
|
+
## Summary
|
|
450
|
+
|
|
451
|
+
| Task | Description | Files |
|
|
452
|
+
|------|-------------|-------|
|
|
453
|
+
| 1 | Update types | `src/config/types.ts` |
|
|
454
|
+
| 2 | Add validation | `src/config/loader.ts` |
|
|
455
|
+
| 3 | Add normalization | `src/config/loader.ts` |
|
|
456
|
+
| 4 | Update template expander | `src/process/template.ts` |
|
|
457
|
+
| 5 | Update ls command | `src/commands/ls.ts` |
|
|
458
|
+
| 6 | Update up command | `src/commands/up.ts` |
|
|
459
|
+
| 7 | Update tests | `tests/integration/process-manager.test.ts` |
|
|
460
|
+
| 8 | Final verification | - |
|