@retasc/cli 1.16.0 → 1.16.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/CHANGELOG.md CHANGED
@@ -6,6 +6,29 @@ 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.16.1 (2026-08-02)
10
+
11
+ - **RTSC-528** — two things the first real `retasc import` run turned up.
12
+
13
+ **The progress bar never appeared.** It only started drawing once it had seen the run
14
+ reported as running, and a 15-issue import finishes before that is ever observed, so the
15
+ output went straight from `Importing…` to `✓ Imported.` with a silent gap. It now draws
16
+ the moment the run starts, sweeping while it waits for counts and switching to the real
17
+ bar once they arrive. Still silent without a terminal and under `NO_COLOR`.
18
+
19
+ **The column prompt didn't say what to do.** It read
20
+ `to do [todo] (todo / doing / done / canceled):`, where nothing is a verb, so the first
21
+ person to run it had to guess that you type one of the words. Now:
22
+
23
+ ```
24
+ to do
25
+ Enter to keep todo, or type: doing, done, canceled
26
+ >
27
+ ```
28
+
29
+ The suggestion is no longer repeated among the alternatives, which is what made the old
30
+ line read as four equal options behind a mysterious bracket.
31
+
9
32
  ## 1.16.0 (2026-08-02)
10
33
 
11
34
  - **RTSC-526** — `retasc import` catches up with the Dash on four things.
@@ -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 keeps the suggestion. It comes from the column's type in your tool, not its name.");
183
+ " Press Enter to keep the suggested answer, or type a different one.\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.
@@ -183,7 +197,20 @@ export async function mapStatuses(statuses, reviewers, askFn) {
183
197
  ? s.suggested
184
198
  : "todo";
185
199
  for (let attempt = 0;; attempt++) {
186
- const answer = (await askFn(`\n ${clean(s.name)} [${suggested}] (${allowed.join(" / ")}): `)).trim().toLowerCase();
200
+ // RTSC-528 the prompt has to name the ACTION. It used to read
201
+ // `to do [todo] (todo / doing / done / canceled):`
202
+ // where nothing is a verb: the bracket is the default and the parenthesis is a bare
203
+ // word list, so the first person to run it had to guess that you type one of them.
204
+ // The reviewer picker two lines below says "Choose a number" and is unambiguous,
205
+ // which made the contrast worse.
206
+ //
207
+ // The suggestion is also removed from the alternatives — it is already what Enter
208
+ // does, and listing it again is what made the line read as four equal options with a
209
+ // mysterious bracket in front.
210
+ const others = allowed.filter((m) => m !== suggested);
211
+ const answer = (await askFn(`\n ${clean(s.name)}\n` +
212
+ ` Enter to keep ${suggested}, or type: ${others.join(", ")}\n` +
213
+ ` > `)).trim().toLowerCase();
187
214
  const choice = answer === "" ? suggested : answer;
188
215
  if (allowed.includes(choice)) {
189
216
  statusMap[s.id] = choice;
@@ -224,28 +251,56 @@ export async function mapStatuses(statuses, reviewers, askFn) {
224
251
  * import failure.
225
252
  */
226
253
  function followProgress(orgId) {
227
- const tty = Boolean(stdout.isTTY) && !process.env.NO_COLOR;
228
254
  if (!stdout.isTTY)
229
255
  return () => { };
256
+ const color = !process.env.NO_COLOR;
230
257
  let stopped = false;
231
- const tick = async () => {
258
+ let frame = 0;
259
+ // Declared before the paint timer that reads it. Safe either way (the callback fires
260
+ // after this line runs), but reading a variable above its declaration is a trap to leave
261
+ // for the next person.
262
+ let latest = null;
263
+ // RTSC-528 — DRAW IMMEDIATELY, and keep drawing, rather than waiting to observe
264
+ // `status === "running"`.
265
+ //
266
+ // The first live run showed nothing at all: a 15-issue import is over in a couple of
267
+ // seconds, so the first poll landed before `runImport` had created the row and the second
268
+ // landed after it finished. `running` was never seen, so nothing was ever drawn — and the
269
+ // silent gap this exists to remove was exactly what the human got. A bar that only works
270
+ // on slow imports is a bar nobody sees while testing.
271
+ //
272
+ // So the render loop is independent of the data: it ticks on its own, showing an
273
+ // indeterminate sweep until counts arrive and the real bar once they do.
274
+ const draw = (done, total) => {
275
+ stdout.write(`\r\x1b[2K${done === null ? sweep(frame++, 24, color) : progressBar(done, total, 24, color)}`);
276
+ };
277
+ draw(null, null);
278
+ const paint = setInterval(() => {
279
+ if (!stopped && latest === null)
280
+ draw(null, null);
281
+ }, 120);
282
+ // Counts, whenever the server has them. Every failure is swallowed: this is decoration
283
+ // on a run happening regardless, and must never be what surfaces as an import failure.
284
+ const poll = async () => {
232
285
  while (!stopped) {
233
286
  try {
234
287
  const p = (await api.latestImport({ orgId }));
235
288
  if (!stopped && p && p.status === "running") {
236
- stdout.write(`\r\x1b[2K${progressBar(p.issuesDone ?? 0, p.issuesTotal ?? null, 24, tty)}`);
289
+ latest = { done: p.issuesDone ?? 0, total: p.issuesTotal ?? null };
290
+ draw(latest.done, latest.total);
237
291
  }
238
292
  }
239
293
  catch {
240
- /* progress is decoration; never let it speak for the run */
294
+ /* progress never speaks for the run */
241
295
  }
242
- await new Promise((r) => setTimeout(r, 1200));
296
+ await new Promise((r) => setTimeout(r, 700));
243
297
  }
244
298
  };
245
- void tick();
299
+ void poll();
246
300
  return () => {
247
301
  stopped = true;
248
- // Clear the line so the summary does not land on top of a half-drawn bar.
302
+ clearInterval(paint);
303
+ // Clear the line so the summary never lands on a half-drawn bar.
249
304
  stdout.write("\r\x1b[2K");
250
305
  };
251
306
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@retasc/cli",
3
- "version": "1.16.0",
3
+ "version": "1.16.1",
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": {