gwqadd 0.5.0 → 0.5.1

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
@@ -139,9 +139,14 @@ One question, one confirmation:
139
139
  **The AI** is whichever of these is on your `PATH`:
140
140
 
141
141
  ```
142
- claude -p → codex exec → opencode run → gemini -p
142
+ claude -p → codex exec (if Claude fails) → opencode run → gemini -p
143
143
  ```
144
144
 
145
+ Automatic detection tries Claude first and falls back to `codex exec` when it
146
+ fails or returns no usable branch name, then continues through the other
147
+ available CLIs. An explicit `--ai '<cmd>'` or `GWQADD_AI='<cmd>'` selects one
148
+ command and keeps the existing manual fallback if that command fails.
149
+
145
150
  No API key to obtain, no account to create — it uses what you already have.
146
151
  Expect 6–8 seconds, almost all of it the CLI's own start-up; an elapsed counter
147
152
  runs while it works.
package/bin/gwqadd.mjs CHANGED
@@ -76,10 +76,11 @@ NAMING HELP
76
76
  modified, so the AI picks the prefix and the wording that match the repo. A
77
77
  rejected suggestion is passed back as an exclusion, so "n" does not return it.
78
78
 
79
- The AI is whichever of these is on PATH claude, codex, opencode, gemini —
80
- invoked headlessly. Nothing is sent anywhere until you answer the question,
81
- and nothing is created until you confirm. Override with --ai '<cmd>' or
82
- GWQADD_AI='<cmd>'; disable with --no-ai or GWQADD_AI=off, which leaves a plain
79
+ Automatic detection tries claude -p first, then codex exec if Claude fails or
80
+ returns no usable name, followed by opencode run and gemini -p. Nothing is
81
+ sent anywhere until you answer the question, and nothing is created until you
82
+ confirm. Override with --ai '<cmd>' or GWQADD_AI='<cmd>' to use one command
83
+ explicitly; disable with --no-ai or GWQADD_AI=off, which leaves a plain
83
84
  prompt for an ASCII name.
84
85
 
85
86
  --no-random (or GWQADD_RANDOM=off) starts at the description prompt instead.
@@ -1243,9 +1244,15 @@ function detectAi() {
1243
1244
  if (['off', '0', 'false', 'none'].includes(override)) return null;
1244
1245
  // Split on whitespace only: quoting rules would be a shell of our own.
1245
1246
  const parts = override.split(/\s+/).filter(Boolean);
1246
- return { bin: parts[0], args: parts.slice(1) };
1247
+ return {
1248
+ candidates: [{ bin: parts[0], args: parts.slice(1) }],
1249
+ automatic: false,
1250
+ };
1247
1251
  }
1248
- return AI_CLIS.find((c) => commandExists(c.bin)) ?? null;
1252
+ return {
1253
+ candidates: AI_CLIS.filter((c) => commandExists(c.bin)),
1254
+ automatic: true,
1255
+ };
1249
1256
  }
1250
1257
 
1251
1258
  // These CLIs are agents, not text transformers: run one inside a repository and
@@ -1433,11 +1440,14 @@ async function composeBranchName(dir, repo, base) {
1433
1440
  if (chosen) return chosen;
1434
1441
  }
1435
1442
 
1436
- const ai = detectAi();
1437
- if (!ai) return { branch: await typeItYourself(), named: 'manual' };
1443
+ const selection = detectAi();
1444
+ if (!selection || selection.candidates.length === 0) {
1445
+ return { branch: await typeItYourself(), named: 'manual' };
1446
+ }
1438
1447
 
1439
1448
  const ctx = repoContext(dir, repo, base);
1440
1449
  const rejected = [];
1450
+ let aiIndex = 0;
1441
1451
 
1442
1452
  for (;;) {
1443
1453
  log(`${dim('│')}`);
@@ -1447,17 +1457,23 @@ async function composeBranchName(dir, repo, base) {
1447
1457
  // An empty answer is the escape hatch out of the AI entirely.
1448
1458
  if (!description) return { branch: await typeItYourself(), named: 'manual' };
1449
1459
 
1450
- const res = await askAi(ai, namingPrompt(ctx, description, rejected));
1451
- if (!res.ok) {
1452
- warn(`${aiLabel(ai)} failed — name it yourself instead`);
1460
+ let ai;
1461
+ let candidates;
1462
+ for (;;) {
1463
+ ai = selection.candidates[aiIndex];
1464
+ const res = await askAi(ai, namingPrompt(ctx, description, rejected));
1465
+ candidates = res.ok ? parseCandidates(res.out) : [];
1466
+ if (res.ok && candidates.length > 0) break;
1467
+
1468
+ const reason = res.ok ? 'returned nothing usable' : 'failed';
1469
+ const next = selection.automatic
1470
+ ? selection.candidates[aiIndex + 1]
1471
+ : null;
1472
+ warn(`${aiLabel(ai)} ${reason} — ${next ? `trying ${aiLabel(next)} instead` : 'name it yourself instead'}`);
1453
1473
  const first = (res.err || '').trim().split('\n')[0];
1454
1474
  if (first) log(`${dim('│')} ${dim(first.slice(0, 120))}`);
1455
- return { branch: await typeItYourself(), named: 'manual' };
1456
- }
1457
- const candidates = parseCandidates(res.out);
1458
- if (candidates.length === 0) {
1459
- warn(`${aiLabel(ai)} returned nothing usable — name it yourself instead`);
1460
- return { branch: await typeItYourself(), named: 'manual' };
1475
+ if (!next) return { branch: await typeItYourself(), named: 'manual' };
1476
+ aiIndex++;
1461
1477
  }
1462
1478
 
1463
1479
  const choice = await confirmCreate(candidates[0], ctx.base);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gwqadd",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "Create a branch and its gwq worktree in the repository you are in, and cd there.",
5
5
  "type": "module",
6
6
  "bin": {