cto-agent-system 1.1.0 → 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.
@@ -9,7 +9,7 @@
9
9
  {
10
10
  "name": "cto-agent-system",
11
11
  "description": "An autonomous software company: CEO (you) + CTO/CPO/CMO leading 15 specialist agents. Run /cto and the CTO takes over — digests the project, fixes fires, improves the product, reports back with a roadmap.",
12
- "version": "1.1.0",
12
+ "version": "1.2.0",
13
13
  "source": "./",
14
14
  "author": {
15
15
  "name": "xenitV1",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "cto-agent-system",
3
3
  "description": "An autonomous software company: CEO (you) + CTO/CPO/CMO leading 15 specialist agents. Run /cto and the CTO takes over — digests the project, fixes fires, improves the product, reports back with a roadmap.",
4
- "version": "1.1.0",
4
+ "version": "1.2.0",
5
5
  "author": {
6
6
  "name": "xenitV1",
7
7
  "url": "https://github.com/xenitV1"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cto-agent-system",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "An autonomous software company: CEO (you) + CTO/CPO/CMO leading 15 specialist agents. Run /cto and the CTO takes over — digests the project, fixes fires, improves the product, reports back with a roadmap.",
5
5
  "author": {
6
6
  "name": "xenitV1",
@@ -2,7 +2,7 @@
2
2
  "name": "cto-agent-system",
3
3
  "displayName": "Software Company Agent System",
4
4
  "description": "An autonomous software company: CEO (you) + CTO/CPO/CMO leading 15 specialist agents. Run /cto and the CTO takes over.",
5
- "version": "1.1.0",
5
+ "version": "1.2.0",
6
6
  "author": {
7
7
  "name": "xenitV1",
8
8
  "url": "https://github.com/xenitV1"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "cto-agent-system",
3
3
  "description": "An autonomous software company: CEO (you) + CTO/CPO/CMO leading 15 specialist agents. Run /cto and the CTO takes over — digests the project, fixes fires, improves the product, reports back with a roadmap.",
4
- "version": "1.1.0",
4
+ "version": "1.2.0",
5
5
  "author": {
6
6
  "name": "xenitV1",
7
7
  "url": "https://github.com/xenitV1"
package/install.js CHANGED
@@ -22,6 +22,7 @@ import {
22
22
  import { dirname, join, resolve, isAbsolute } from "node:path";
23
23
  import { fileURLToPath } from "node:url";
24
24
  import { homedir, platform } from "node:os";
25
+ import { ReadStream } from "node:tty";
25
26
 
26
27
  const __dirname = dirname(fileURLToPath(import.meta.url));
27
28
  // install.js lives at the package root (next to AGENTS.md, src/, .claude/).
@@ -100,6 +101,174 @@ async function askYesNo(rl, question, defaultYes = true) {
100
101
  return answer === "y" || answer === "yes";
101
102
  }
102
103
 
104
+ // ---------------------------------------------------------------------------
105
+ // Interactive multi-select (arrow keys + space to toggle + enter to confirm)
106
+ // Zero-dependency: drives raw TTY mode directly. Falls back to a numbered
107
+ // prompt when stdin is not a TTY (piped/CI).
108
+ // ---------------------------------------------------------------------------
109
+
110
+ const ESC = "\x1b[";
111
+
112
+ function hideCursor() { stdout.write(`${ESC}?25l`); }
113
+ function showCursor() { stdout.write(`${ESC}?25h`); }
114
+ function clearMenu(lines) { stdout.write(`${ESC}${lines}A${ESC}J`); } // move up, clear down
115
+ function writeLine(s) { stdout.write(`${s}\r\n`); }
116
+
117
+ /**
118
+ * Render a multi-select menu.
119
+ * @param {string} title Prompt header.
120
+ * @param {{label:string, hint?:string, checked:boolean}[]} items
121
+ * @param {boolean} allowToggle If false, behaves like a single-select.
122
+ * @returns {Promise<number[]>} Indices of checked items (on confirm).
123
+ */
124
+ function checkboxMenu(title, items, { allowToggle = true } = {}) {
125
+ return new Promise((resolvePromise) => {
126
+ // Non-TTY fallback: numbered prompt.
127
+ const isTTY = stdin.isTTY && stdin instanceof ReadStream;
128
+ if (!isTTY) {
129
+ const rl = createInterface({ input: stdin, output: stdout });
130
+ const fallback = async () => {
131
+ console.log(title);
132
+ items.forEach((it, i) => {
133
+ const mark = it.checked ? "[x]" : "[ ]";
134
+ console.log(` ${i + 1}. ${mark} ${it.label}${it.hint ? ` — ${it.hint}` : ""}`);
135
+ });
136
+ const ans = (await rl.question(
137
+ `Enter numbers comma-separated (e.g. 1,3) or 'all': `
138
+ )).trim().toLowerCase();
139
+ rl.close();
140
+ if (ans === "all" || ans === "") return items.map((_, i) => i);
141
+ const picked = ans.split(/[,\s]+/).map((n) => parseInt(n, 10) - 1)
142
+ .filter((n) => n >= 0 && n < items.length);
143
+ return picked;
144
+ };
145
+ fallback().then(resolvePromise);
146
+ return;
147
+ }
148
+
149
+ let cursor = 0;
150
+ const state = items.map((it) => !!it.checked);
151
+ const headerLines = title.split("\n").length + 1; // title + blank
152
+
153
+ const render = () => {
154
+ writeLine(title);
155
+ writeLine(" (↑/↓ move, space toggle, a all, enter confirm)");
156
+ items.forEach((it, i) => {
157
+ const arrow = i === cursor ? "❯" : " ";
158
+ const box = state[i] ? "◉" : "◯";
159
+ const hint = it.hint ? ` ${it.hint}` : "";
160
+ writeLine(` ${arrow} ${box} ${it.label}${hint}`);
161
+ });
162
+ };
163
+
164
+ // Render once, then capture how many lines we wrote so we can clear on re-render.
165
+ let lastLines = 0;
166
+ const paint = () => {
167
+ if (lastLines > 0) clearMenu(lastLines);
168
+ const before = stdout.rows; // unused; we count lines instead
169
+ const lineCount = headerLines + 1 + items.length; // title(=headerLines) + hint + items
170
+ render();
171
+ lastLines = lineCount;
172
+ };
173
+
174
+ stdin.setRawMode(true);
175
+ stdin.resume();
176
+ stdin.setEncoding("utf8");
177
+ hideCursor();
178
+ paint();
179
+
180
+ const onData = (ch) => {
181
+ // Ctrl-C
182
+ if (ch === "\x03") { cleanup(); process.exit(0); }
183
+ // Enter
184
+ if (ch === "\r" || ch === "\n") {
185
+ cleanup();
186
+ const result = items.map((_, i) => i).filter((i) => state[i]);
187
+ resolvePromise(result);
188
+ return;
189
+ }
190
+ // 'a' = toggle all
191
+ if (ch === "a" || ch === "A") {
192
+ const allOn = state.every(Boolean);
193
+ for (let i = 0; i < state.length; i++) state[i] = !allOn;
194
+ paint();
195
+ return;
196
+ }
197
+ // space = toggle current
198
+ if (ch === " ") {
199
+ if (allowToggle) { state[cursor] = !state[cursor]; paint(); }
200
+ return;
201
+ }
202
+ // arrows
203
+ if (ch === `${ESC}A`) { cursor = (cursor - 1 + items.length) % items.length; paint(); return; } // up
204
+ if (ch === `${ESC}B`) { cursor = (cursor + 1) % items.length; paint(); return; } // down
205
+ };
206
+
207
+ const cleanup = () => {
208
+ stdin.removeListener("data", onData);
209
+ stdin.setRawMode(false);
210
+ stdin.pause();
211
+ showCursor();
212
+ };
213
+
214
+ stdin.on("data", onData);
215
+ });
216
+ }
217
+
218
+ /**
219
+ * Single-select menu (arrows + enter). Returns chosen index.
220
+ */
221
+ function selectMenu(title, items) {
222
+ return new Promise((resolvePromise) => {
223
+ const isTTY = stdin.isTTY && stdin instanceof ReadStream;
224
+ if (!isTTY) {
225
+ const rl = createInterface({ input: stdin, output: stdout });
226
+ const fallback = async () => {
227
+ console.log(title);
228
+ items.forEach((it, i) => console.log(` ${i + 1}. ${it.label}`));
229
+ const ans = parseInt((await rl.question("Choice (number): ")).trim(), 10);
230
+ rl.close();
231
+ return Number.isNaN(ans) ? 0 : Math.max(0, Math.min(items.length - 1, ans - 1));
232
+ };
233
+ fallback().then(resolvePromise);
234
+ return;
235
+ }
236
+
237
+ let cursor = 0;
238
+ let lastLines = 0;
239
+ const paint = () => {
240
+ if (lastLines > 0) clearMenu(lastLines);
241
+ writeLine(title);
242
+ writeLine(" (↑/↓ move, enter select)");
243
+ items.forEach((it, i) => {
244
+ const arrow = i === cursor ? "❯" : " ";
245
+ writeLine(` ${arrow} ${it.label}`);
246
+ });
247
+ lastLines = 1 + 1 + items.length;
248
+ };
249
+
250
+ stdin.setRawMode(true);
251
+ stdin.resume();
252
+ stdin.setEncoding("utf8");
253
+ hideCursor();
254
+ paint();
255
+
256
+ const onData = (ch) => {
257
+ if (ch === "\x03") { cleanup(); process.exit(0); }
258
+ if (ch === "\r" || ch === "\n") { cleanup(); resolvePromise(cursor); return; }
259
+ if (ch === `${ESC}A`) { cursor = (cursor - 1 + items.length) % items.length; paint(); return; }
260
+ if (ch === `${ESC}B`) { cursor = (cursor + 1) % items.length; paint(); return; }
261
+ };
262
+ const cleanup = () => {
263
+ stdin.removeListener("data", onData);
264
+ stdin.setRawMode(false);
265
+ stdin.pause();
266
+ showCursor();
267
+ };
268
+ stdin.on("data", onData);
269
+ });
270
+ }
271
+
103
272
  // ---------------------------------------------------------------------------
104
273
  // File helpers
105
274
  // ---------------------------------------------------------------------------
@@ -236,20 +405,25 @@ Examples:
236
405
  console.log("");
237
406
 
238
407
  const interactive = !yes && stdin.isTTY;
239
- const rl = interactive ? createInterface({ input: stdin, output: stdout }) : null;
240
408
 
241
- // 2. Scope
409
+ // 2. Scope — arrow-key single select (interactive) or flags/default.
242
410
  let scope;
243
411
  if (goGlobal) scope = "global";
244
412
  else if (goProject) scope = "project";
245
413
  else if (interactive) {
246
- if (installed.length > 0) {
247
- const g = await askYesNo(rl, "Install GLOBALLY for detected CLI(s) (user-wide)?", true);
248
- scope = g ? "global" : "project";
249
- } else scope = "project";
414
+ const scopeItems = installed.length > 0
415
+ ? [
416
+ { label: "Global — install into the user config of detected CLIs (user-wide)" },
417
+ { label: "Project install into a specific project directory" },
418
+ ]
419
+ : [
420
+ { label: "Project — install into a specific project directory" },
421
+ { label: "Global — install into the user config of detected CLIs" },
422
+ ];
423
+ const idx = await selectMenu("Where do you want to install?", scopeItems);
424
+ scope = scopeItems[idx].label.startsWith("Global") ? "global" : "project";
250
425
  } else {
251
- // Non-interactive default: project scope.
252
- scope = "project";
426
+ scope = "project"; // non-interactive default
253
427
  }
254
428
 
255
429
  // 3. Target
@@ -269,7 +443,7 @@ Examples:
269
443
  log("→", `Target: ${target}`);
270
444
  console.log("");
271
445
 
272
- // 4. Which adapters?
446
+ // 4. Which adapters — checkbox multi-select (interactive) or flags/default.
273
447
  let chosen;
274
448
  if (toolList) {
275
449
  const want = toolList.split(",").map((s) => s.trim()).filter(Boolean);
@@ -277,22 +451,16 @@ Examples:
277
451
  } else if (installAll) {
278
452
  chosen = scope === "global" && installed.length ? installed : ADAPTERS;
279
453
  } else if (interactive) {
280
- const all = await askYesNo(rl, "Install ALL adapters (claude/codex/opencode/cursor)?", true);
281
- if (all) {
282
- chosen = scope === "global" && installed.length ? installed : ADAPTERS;
283
- } else if (scope === "global" && installed.length) {
284
- chosen = [];
285
- for (const a of installed) {
286
- if (await askYesNo(rl, ` Install ${a.name}?`, true)) chosen.push(a);
287
- }
288
- } else {
289
- chosen = [];
290
- for (const a of ADAPTERS) {
291
- if (await askYesNo(rl, ` Install ${a.name} adapter?`, false)) chosen.push(a);
292
- }
293
- }
454
+ // Build checkbox items. Detected CLIs are pre-checked; others unchecked.
455
+ const detectedKeys = new Set(installed.map((a) => a.key));
456
+ const items = ADAPTERS.map((a) => ({
457
+ label: a.name,
458
+ hint: detectedKeys.has(a.key) ? "detected" : (existsSync(join(PKG_ROOT, a.dir)) ? "available" : "not shipped yet"),
459
+ checked: detectedKeys.has(a.key),
460
+ }));
461
+ const pickedIdx = await checkboxMenu("Select which CLI adapters to install:", items);
462
+ chosen = pickedIdx.map((i) => ADAPTERS[i]);
294
463
  } else {
295
- // Non-interactive default: all detected CLIs (global) or all adapters (project).
296
464
  chosen = scope === "global" && installed.length ? installed : ADAPTERS;
297
465
  }
298
466
 
@@ -301,12 +469,14 @@ Examples:
301
469
  if (scope !== "project") {
302
470
  initCto = false;
303
471
  } else if (interactive) {
304
- initCto = await askYesNo(rl, "Initialize .cto/ state files in the project?", true);
472
+ const idx = await selectMenu("Initialize .cto/ state files in the project?", [
473
+ { label: "Yes — create .cto/ state files" },
474
+ { label: "No — skip state init" },
475
+ ]);
476
+ initCto = idx === 0;
305
477
  } else {
306
- initCto = true; // non-interactive default for project scope
478
+ initCto = true;
307
479
  }
308
-
309
- if (rl) rl.close();
310
480
  console.log("");
311
481
  console.log(" ── Installing ──────────────────────────────────────────────");
312
482
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cto-agent-system",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "An autonomous software company: CEO (you) + CTO/CPO/CMO leading 15 specialist agents. Run /cto and the CTO takes over — digests the project, fixes fires, improves the product, reports back with a roadmap.",
5
5
  "license": "MIT",
6
6
  "author": {