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 +6 -1
- package/bin/gwqadd.mjs +33 -17
- package/package.json +1 -1
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
|
-
|
|
80
|
-
|
|
81
|
-
and nothing is created until you
|
|
82
|
-
|
|
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 {
|
|
1247
|
+
return {
|
|
1248
|
+
candidates: [{ bin: parts[0], args: parts.slice(1) }],
|
|
1249
|
+
automatic: false,
|
|
1250
|
+
};
|
|
1247
1251
|
}
|
|
1248
|
-
return
|
|
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
|
|
1437
|
-
if (!
|
|
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
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
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);
|