gwqadd 0.5.0 → 0.5.2

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,16 @@ 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 --skip-git-repo-check (if Claude fails) → opencode run → gemini -p
143
143
  ```
144
144
 
145
+ Automatic detection tries Claude first and falls back to
146
+ `codex exec --skip-git-repo-check` when it fails or returns no usable branch
147
+ name, then continues through the other available CLIs. The flag is required
148
+ because the AI runs in an empty directory rather than the user's repository.
149
+ An explicit `--ai '<cmd>'` or `GWQADD_AI='<cmd>'` selects one command and keeps
150
+ the existing manual fallback if that command fails.
151
+
145
152
  No API key to obtain, no account to create — it uses what you already have.
146
153
  Expect 6–8 seconds, almost all of it the CLI's own start-up; an elapsed counter
147
154
  runs while it works.
package/bin/gwqadd.mjs CHANGED
@@ -76,11 +76,12 @@ 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
83
- prompt for an ASCII name.
79
+ Automatic detection tries claude -p first, then codex exec
80
+ --skip-git-repo-check if Claude fails or returns no usable name, followed by
81
+ opencode run and gemini -p. Nothing is sent anywhere until you answer the
82
+ question, and nothing is created until you confirm. Override with --ai '<cmd>'
83
+ or GWQADD_AI='<cmd>' to use one command explicitly; disable with --no-ai or
84
+ GWQADD_AI=off, which leaves a plain prompt for an ASCII name.
84
85
 
85
86
  --no-random (or GWQADD_RANDOM=off) starts at the description prompt instead.
86
87
  --random goes the other way and never asks; it is the only naming path that
@@ -1228,7 +1229,9 @@ function namingPrompt(ctx, description, rejected) {
1228
1229
  // answers a bare prompt headlessly.
1229
1230
  const AI_CLIS = [
1230
1231
  { bin: 'claude', args: ['-p'] },
1231
- { bin: 'codex', args: ['exec'] },
1232
+ // runAi() deliberately uses an empty, non-Git directory; Codex requires this
1233
+ // opt-out of its repository check before it will accept that working root.
1234
+ { bin: 'codex', args: ['exec', '--skip-git-repo-check'] },
1232
1235
  { bin: 'opencode', args: ['run'] },
1233
1236
  { bin: 'gemini', args: ['-p'] },
1234
1237
  ];
@@ -1243,9 +1246,15 @@ function detectAi() {
1243
1246
  if (['off', '0', 'false', 'none'].includes(override)) return null;
1244
1247
  // Split on whitespace only: quoting rules would be a shell of our own.
1245
1248
  const parts = override.split(/\s+/).filter(Boolean);
1246
- return { bin: parts[0], args: parts.slice(1) };
1249
+ return {
1250
+ candidates: [{ bin: parts[0], args: parts.slice(1) }],
1251
+ automatic: false,
1252
+ };
1247
1253
  }
1248
- return AI_CLIS.find((c) => commandExists(c.bin)) ?? null;
1254
+ return {
1255
+ candidates: AI_CLIS.filter((c) => commandExists(c.bin)),
1256
+ automatic: true,
1257
+ };
1249
1258
  }
1250
1259
 
1251
1260
  // These CLIs are agents, not text transformers: run one inside a repository and
@@ -1433,11 +1442,14 @@ async function composeBranchName(dir, repo, base) {
1433
1442
  if (chosen) return chosen;
1434
1443
  }
1435
1444
 
1436
- const ai = detectAi();
1437
- if (!ai) return { branch: await typeItYourself(), named: 'manual' };
1445
+ const selection = detectAi();
1446
+ if (!selection || selection.candidates.length === 0) {
1447
+ return { branch: await typeItYourself(), named: 'manual' };
1448
+ }
1438
1449
 
1439
1450
  const ctx = repoContext(dir, repo, base);
1440
1451
  const rejected = [];
1452
+ let aiIndex = 0;
1441
1453
 
1442
1454
  for (;;) {
1443
1455
  log(`${dim('│')}`);
@@ -1447,17 +1459,23 @@ async function composeBranchName(dir, repo, base) {
1447
1459
  // An empty answer is the escape hatch out of the AI entirely.
1448
1460
  if (!description) return { branch: await typeItYourself(), named: 'manual' };
1449
1461
 
1450
- const res = await askAi(ai, namingPrompt(ctx, description, rejected));
1451
- if (!res.ok) {
1452
- warn(`${aiLabel(ai)} failed — name it yourself instead`);
1462
+ let ai;
1463
+ let candidates;
1464
+ for (;;) {
1465
+ ai = selection.candidates[aiIndex];
1466
+ const res = await askAi(ai, namingPrompt(ctx, description, rejected));
1467
+ candidates = res.ok ? parseCandidates(res.out) : [];
1468
+ if (res.ok && candidates.length > 0) break;
1469
+
1470
+ const reason = res.ok ? 'returned nothing usable' : 'failed';
1471
+ const next = selection.automatic
1472
+ ? selection.candidates[aiIndex + 1]
1473
+ : null;
1474
+ warn(`${aiLabel(ai)} ${reason} — ${next ? `trying ${aiLabel(next)} instead` : 'name it yourself instead'}`);
1453
1475
  const first = (res.err || '').trim().split('\n')[0];
1454
1476
  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' };
1477
+ if (!next) return { branch: await typeItYourself(), named: 'manual' };
1478
+ aiIndex++;
1461
1479
  }
1462
1480
 
1463
1481
  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.2",
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": {