@retasc/cli 1.16.0 → 1.17.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.
- package/CHANGELOG.md +45 -0
- package/dist/commands/import.js +78 -15
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,51 @@ release commits and the issues they reference.
|
|
|
6
6
|
|
|
7
7
|
Dates are the npm publish date. Each entry names the RTSC issue behind it.
|
|
8
8
|
|
|
9
|
+
## 1.17.0 (2026-08-02)
|
|
10
|
+
|
|
11
|
+
- **RTSC-529** — the column mapping is a numbered picker, like every other prompt.
|
|
12
|
+
|
|
13
|
+
It was the only one that made you type a word. Picking an org, a project, a source, a
|
|
14
|
+
list, a reviewer, a sign-in door or whether to install globally are all numbered lists
|
|
15
|
+
reading `Choose a number` — and the reviewer picker appears two lines below this one, so a
|
|
16
|
+
single import taught two input styles within four lines of output.
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
to do
|
|
20
|
+
1) todo (suggested)
|
|
21
|
+
2) doing
|
|
22
|
+
3) done
|
|
23
|
+
4) canceled
|
|
24
|
+
Choose a number [1]:
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Enter still takes the suggestion, now marked in the list rather than described beside it.
|
|
28
|
+
Typing the word still works for anyone who has learned the vocabulary; it just isn't what
|
|
29
|
+
the prompt advertises.
|
|
30
|
+
|
|
31
|
+
## 1.16.1 (2026-08-02)
|
|
32
|
+
|
|
33
|
+
- **RTSC-528** — two things the first real `retasc import` run turned up.
|
|
34
|
+
|
|
35
|
+
**The progress bar never appeared.** It only started drawing once it had seen the run
|
|
36
|
+
reported as running, and a 15-issue import finishes before that is ever observed, so the
|
|
37
|
+
output went straight from `Importing…` to `✓ Imported.` with a silent gap. It now draws
|
|
38
|
+
the moment the run starts, sweeping while it waits for counts and switching to the real
|
|
39
|
+
bar once they arrive. Still silent without a terminal and under `NO_COLOR`.
|
|
40
|
+
|
|
41
|
+
**The column prompt didn't say what to do.** It read
|
|
42
|
+
`to do [todo] (todo / doing / done / canceled):`, where nothing is a verb, so the first
|
|
43
|
+
person to run it had to guess that you type one of the words. Now:
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
to do
|
|
47
|
+
Enter to keep todo, or type: doing, done, canceled
|
|
48
|
+
>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The suggestion is no longer repeated among the alternatives, which is what made the old
|
|
52
|
+
line read as four equal options behind a mysterious bracket.
|
|
53
|
+
|
|
9
54
|
## 1.16.0 (2026-08-02)
|
|
10
55
|
|
|
11
56
|
- **RTSC-526** — `retasc import` catches up with the Dash on four things.
|
package/dist/commands/import.js
CHANGED
|
@@ -72,6 +72,19 @@ export function summaryLines(summary, label) {
|
|
|
72
72
|
const LIME = "\x1b[38;5;154m";
|
|
73
73
|
const DIM = "\x1b[38;5;240m";
|
|
74
74
|
const RESET = "\x1b[0m";
|
|
75
|
+
/**
|
|
76
|
+
* An indeterminate sweep, for the stretch before the server reports counts (RTSC-528).
|
|
77
|
+
*
|
|
78
|
+
* Pure and frame-indexed rather than time-based, so a test can pin every frame. A block
|
|
79
|
+
* that travels is the honest shape when the total is unknown: a 0% bar reads as stalled,
|
|
80
|
+
* which is the very impression this is here to prevent.
|
|
81
|
+
*/
|
|
82
|
+
export function sweep(frame, width = 24, color = true) {
|
|
83
|
+
const pos = frame % (width * 2 - 2);
|
|
84
|
+
const at = pos < width ? pos : width * 2 - 2 - pos; // bounce, so it never jumps
|
|
85
|
+
const cells = Array.from({ length: width }, (_, i) => Math.abs(i - at) <= 1 ? "\u2588" : "\u2591").join("");
|
|
86
|
+
return ` ${color ? LIME : ""}${cells}${color ? RESET : ""} working…`;
|
|
87
|
+
}
|
|
75
88
|
/**
|
|
76
89
|
* One line of progress bar.
|
|
77
90
|
*
|
|
@@ -167,7 +180,8 @@ export async function mapStatuses(statuses, reviewers, askFn) {
|
|
|
167
180
|
const statusMap = {};
|
|
168
181
|
const reviewerByStatus = {};
|
|
169
182
|
console.log(`\nWhat does each column mean? ${statuses.length} to confirm.\n` +
|
|
170
|
-
" Enter
|
|
183
|
+
" Press Enter to take the suggested answer, or pick another number.\n" +
|
|
184
|
+
" The suggestion comes from the column's type in your tool, not its name.");
|
|
171
185
|
// Grouped under the source's own section names, in the source's own order within each
|
|
172
186
|
// (RTSC-436/526). On a twenty-column Jira board a flat list is a wall; the sections are
|
|
173
187
|
// the shape the human already has in front of them in their tool.
|
|
@@ -182,23 +196,44 @@ export async function mapStatuses(statuses, reviewers, askFn) {
|
|
|
182
196
|
const suggested = allowed.includes(s.suggested)
|
|
183
197
|
? s.suggested
|
|
184
198
|
: "todo";
|
|
199
|
+
// RTSC-529 — NUMBERED, like every other prompt in the CLI.
|
|
200
|
+
//
|
|
201
|
+
// This was the only one that asked you to type a word. Org, project, source, list,
|
|
202
|
+
// reviewer, sign-in door (RTSC-508) and install choice (RTSC-523) are all numbered
|
|
203
|
+
// pickers reading "Choose a number" — and the reviewer picker appears two lines below
|
|
204
|
+
// THIS one, so a single run taught two input styles within four lines of output.
|
|
205
|
+
//
|
|
206
|
+
// RTSC-528 tried to fix that by making the typing instruction explicit. That treated
|
|
207
|
+
// the symptom: a prompt that has to teach you its own input method is usually the
|
|
208
|
+
// wrong input method.
|
|
209
|
+
//
|
|
210
|
+
// The default is marked in the list rather than described outside it, and the words
|
|
211
|
+
// are still accepted — someone ten columns in knows the vocabulary, and typing `doing`
|
|
212
|
+
// beats re-reading a list. That is a shortcut, not the advertised path, so it is not
|
|
213
|
+
// in the instruction.
|
|
214
|
+
console.log(`\n ${clean(s.name)}`);
|
|
215
|
+
allowed.forEach((m, i) => console.log(` ${i + 1}) ${m}${m === suggested ? " (suggested)" : ""}`));
|
|
216
|
+
const defaultSlot = allowed.indexOf(suggested) + 1;
|
|
185
217
|
for (let attempt = 0;; attempt++) {
|
|
186
|
-
const answer = (await askFn(
|
|
187
|
-
|
|
188
|
-
|
|
218
|
+
const answer = (await askFn(` Choose a number [${defaultSlot}]: `)).trim().toLowerCase();
|
|
219
|
+
// Enter takes the suggestion; a digit picks a row; a word still works.
|
|
220
|
+
const byNumber = /^\d+$/.test(answer) ? allowed[Number(answer) - 1] : undefined;
|
|
221
|
+
const choice = answer === "" ? suggested : (byNumber ?? answer);
|
|
222
|
+
if (choice && allowed.includes(choice)) {
|
|
189
223
|
statusMap[s.id] = choice;
|
|
190
224
|
break;
|
|
191
225
|
}
|
|
192
|
-
|
|
193
|
-
|
|
226
|
+
// Say WHY when the answer names something real but not permitted here. The server
|
|
227
|
+
// would reject it anyway, and repeating the list explains nothing.
|
|
228
|
+
if (answer === "review" && !reviewAllowed(s.group)) {
|
|
194
229
|
console.log(" Not for this column: review means finished and awaiting acceptance,");
|
|
195
230
|
console.log(" and this one holds work nobody has started.");
|
|
196
231
|
}
|
|
197
|
-
else if (
|
|
232
|
+
else if (answer === "review") {
|
|
198
233
|
console.log(" No one in this org can be a reviewer yet, so review isn't available.");
|
|
199
234
|
}
|
|
200
235
|
else {
|
|
201
|
-
console.log(`
|
|
236
|
+
console.log(` Enter a number between 1 and ${allowed.length}.`);
|
|
202
237
|
}
|
|
203
238
|
if (attempt >= 2)
|
|
204
239
|
throw new Error("no valid choice — aborting");
|
|
@@ -224,28 +259,56 @@ export async function mapStatuses(statuses, reviewers, askFn) {
|
|
|
224
259
|
* import failure.
|
|
225
260
|
*/
|
|
226
261
|
function followProgress(orgId) {
|
|
227
|
-
const tty = Boolean(stdout.isTTY) && !process.env.NO_COLOR;
|
|
228
262
|
if (!stdout.isTTY)
|
|
229
263
|
return () => { };
|
|
264
|
+
const color = !process.env.NO_COLOR;
|
|
230
265
|
let stopped = false;
|
|
231
|
-
|
|
266
|
+
let frame = 0;
|
|
267
|
+
// Declared before the paint timer that reads it. Safe either way (the callback fires
|
|
268
|
+
// after this line runs), but reading a variable above its declaration is a trap to leave
|
|
269
|
+
// for the next person.
|
|
270
|
+
let latest = null;
|
|
271
|
+
// RTSC-528 — DRAW IMMEDIATELY, and keep drawing, rather than waiting to observe
|
|
272
|
+
// `status === "running"`.
|
|
273
|
+
//
|
|
274
|
+
// The first live run showed nothing at all: a 15-issue import is over in a couple of
|
|
275
|
+
// seconds, so the first poll landed before `runImport` had created the row and the second
|
|
276
|
+
// landed after it finished. `running` was never seen, so nothing was ever drawn — and the
|
|
277
|
+
// silent gap this exists to remove was exactly what the human got. A bar that only works
|
|
278
|
+
// on slow imports is a bar nobody sees while testing.
|
|
279
|
+
//
|
|
280
|
+
// So the render loop is independent of the data: it ticks on its own, showing an
|
|
281
|
+
// indeterminate sweep until counts arrive and the real bar once they do.
|
|
282
|
+
const draw = (done, total) => {
|
|
283
|
+
stdout.write(`\r\x1b[2K${done === null ? sweep(frame++, 24, color) : progressBar(done, total, 24, color)}`);
|
|
284
|
+
};
|
|
285
|
+
draw(null, null);
|
|
286
|
+
const paint = setInterval(() => {
|
|
287
|
+
if (!stopped && latest === null)
|
|
288
|
+
draw(null, null);
|
|
289
|
+
}, 120);
|
|
290
|
+
// Counts, whenever the server has them. Every failure is swallowed: this is decoration
|
|
291
|
+
// on a run happening regardless, and must never be what surfaces as an import failure.
|
|
292
|
+
const poll = async () => {
|
|
232
293
|
while (!stopped) {
|
|
233
294
|
try {
|
|
234
295
|
const p = (await api.latestImport({ orgId }));
|
|
235
296
|
if (!stopped && p && p.status === "running") {
|
|
236
|
-
|
|
297
|
+
latest = { done: p.issuesDone ?? 0, total: p.issuesTotal ?? null };
|
|
298
|
+
draw(latest.done, latest.total);
|
|
237
299
|
}
|
|
238
300
|
}
|
|
239
301
|
catch {
|
|
240
|
-
/* progress
|
|
302
|
+
/* progress never speaks for the run */
|
|
241
303
|
}
|
|
242
|
-
await new Promise((r) => setTimeout(r,
|
|
304
|
+
await new Promise((r) => setTimeout(r, 700));
|
|
243
305
|
}
|
|
244
306
|
};
|
|
245
|
-
void
|
|
307
|
+
void poll();
|
|
246
308
|
return () => {
|
|
247
309
|
stopped = true;
|
|
248
|
-
|
|
310
|
+
clearInterval(paint);
|
|
311
|
+
// Clear the line so the summary never lands on a half-drawn bar.
|
|
249
312
|
stdout.write("\r\x1b[2K");
|
|
250
313
|
};
|
|
251
314
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@retasc/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.17.0",
|
|
4
4
|
"description": "Retasc CLI \u2014 the issue tracker AI agents pull work from. Sign in with GitHub or Google, create projects, mint agent API keys, and wire your agent to the Retasc MCP server in one command.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|