@trawlme/cli 3.1.0 → 3.2.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/dist/commands/create.js +20 -15
- package/dist/lib/pinch.d.ts +17 -0
- package/dist/lib/pinch.js +31 -0
- package/dist/lib/pinchAnimation.d.ts +17 -0
- package/dist/lib/pinchAnimation.js +56 -0
- package/package.json +1 -1
package/dist/commands/create.js
CHANGED
|
@@ -6,6 +6,7 @@ import { json } from '../lib/format.js';
|
|
|
6
6
|
import { requireUrl, requireString } from '../lib/validate.js';
|
|
7
7
|
import { UsageError } from '../lib/errors.js';
|
|
8
8
|
import { renderPinch, pinchEnabled } from '../lib/pinch.js';
|
|
9
|
+
import { startPinchAnimation } from '../lib/pinchAnimation.js';
|
|
9
10
|
/** Best-effort, honest first-run summary — never claims a background retry
|
|
10
11
|
* happened when auto-fix was disabled for this call, and never claims a
|
|
11
12
|
* scrap was persisted when the response carries none (#114-F3 — a hard
|
|
@@ -130,24 +131,25 @@ export const create = new Command('create')
|
|
|
130
131
|
data = await call();
|
|
131
132
|
}
|
|
132
133
|
else {
|
|
133
|
-
// #122 —
|
|
134
|
-
//
|
|
135
|
-
//
|
|
136
|
-
//
|
|
137
|
-
//
|
|
138
|
-
//
|
|
139
|
-
|
|
140
|
-
console.log(renderPinch('thinking'));
|
|
134
|
+
// #122/#131 — while the server-side wizard runs (legitimately 30-250s+,
|
|
135
|
+
// see LONG_RUN_TIMEOUT_MS above) Pinch ANIMATES in place (claw-wiggle)
|
|
136
|
+
// on a color-capable TTY; otherwise the ora spinner (which itself
|
|
137
|
+
// no-ops under a non-TTY, so piped human output stays clean). Both
|
|
138
|
+
// write to stderr only — stdout purity under `--json` is guaranteed by
|
|
139
|
+
// the `opts.json` branch above, never by this code.
|
|
140
|
+
const anim = pinchEnabled() ? startPinchAnimation(`Creating a scrap from ${url}…`) : null;
|
|
141
141
|
try {
|
|
142
|
-
data =
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
142
|
+
data = anim
|
|
143
|
+
? await call()
|
|
144
|
+
: await spin(call, {
|
|
145
|
+
// No verdict symbol (#106-F3) — ora's success only means "the
|
|
146
|
+
// HTTP call didn't throw", not "the first run succeeded".
|
|
147
|
+
text: `Creating a scrap from ${url}…`,
|
|
148
|
+
successText: 'Request complete',
|
|
149
|
+
});
|
|
149
150
|
}
|
|
150
151
|
catch (err) {
|
|
152
|
+
anim?.stop();
|
|
151
153
|
// #114-F1 — a client-side timeout (NetworkError, "timed out after
|
|
152
154
|
// …ms" per api.ts's safeFetch) does NOT mean the wizard failed
|
|
153
155
|
// server-side: the scrap creation + first run keep going on the
|
|
@@ -165,6 +167,9 @@ export const create = new Command('create')
|
|
|
165
167
|
// stays intact (this stays a NetworkError -> exit 5, same as before).
|
|
166
168
|
throw err;
|
|
167
169
|
}
|
|
170
|
+
// Success — stop + erase the animation so the result prints on a clean
|
|
171
|
+
// line (the celebrating/confused frame below is the one-shot outcome).
|
|
172
|
+
anim?.stop();
|
|
168
173
|
}
|
|
169
174
|
if (opts.json) {
|
|
170
175
|
json(data);
|
package/dist/lib/pinch.d.ts
CHANGED
|
@@ -29,6 +29,23 @@ export type PinchState = 'wave' | 'thinking' | 'celebrating' | 'confused';
|
|
|
29
29
|
* state's grid is static (see `gridForState`).
|
|
30
30
|
*/
|
|
31
31
|
export declare function renderPinch(state: PinchState, frame?: number): string;
|
|
32
|
+
/**
|
|
33
|
+
* #131 — the ART lines only (no caption), optionally COMPACT: trailing and
|
|
34
|
+
* leading fully-transparent grid rows are dropped so Pinch takes fewer
|
|
35
|
+
* terminal lines (the r6f grids carry an all-'.' bottom row + blank-ish
|
|
36
|
+
* antenna padding). Used by the animation loop, where a caption + full
|
|
37
|
+
* height would be too heavy for a frame redrawn every ~380ms.
|
|
38
|
+
*/
|
|
39
|
+
export declare function renderPinchArt(state: PinchState, opts?: {
|
|
40
|
+
compact?: boolean;
|
|
41
|
+
}): string;
|
|
42
|
+
/**
|
|
43
|
+
* #131 — frame sequence for the "working" animation shown during long waits
|
|
44
|
+
* (the `create` wizard, `--watch`). Reuses the existing r6f state grids: the
|
|
45
|
+
* claws pump up (wave → celebrating) and back down, reading as Pinch busily
|
|
46
|
+
* waving while it works. No new art — just an order over the grids we have.
|
|
47
|
+
*/
|
|
48
|
+
export declare const WORKING_FRAMES: readonly PinchState[];
|
|
32
49
|
/**
|
|
33
50
|
* True when it's safe to print Pinch art: a real color-capable interactive
|
|
34
51
|
* terminal. False under NO_COLOR (https://no-color.org — presence, not
|
package/dist/lib/pinch.js
CHANGED
|
@@ -154,6 +154,37 @@ export function renderPinch(state, frame = 0) {
|
|
|
154
154
|
}
|
|
155
155
|
return [...lines, CAPTIONS[state]].join('\n');
|
|
156
156
|
}
|
|
157
|
+
/**
|
|
158
|
+
* #131 — the ART lines only (no caption), optionally COMPACT: trailing and
|
|
159
|
+
* leading fully-transparent grid rows are dropped so Pinch takes fewer
|
|
160
|
+
* terminal lines (the r6f grids carry an all-'.' bottom row + blank-ish
|
|
161
|
+
* antenna padding). Used by the animation loop, where a caption + full
|
|
162
|
+
* height would be too heavy for a frame redrawn every ~380ms.
|
|
163
|
+
*/
|
|
164
|
+
export function renderPinchArt(state, opts = {}) {
|
|
165
|
+
let grid = gridForState(state, 0);
|
|
166
|
+
if (opts.compact) {
|
|
167
|
+
const blank = (row) => [...row].every((c) => c === TRANSPARENT);
|
|
168
|
+
let start = 0;
|
|
169
|
+
let end = grid.length;
|
|
170
|
+
while (end > start && blank(grid[end - 1]))
|
|
171
|
+
end--;
|
|
172
|
+
while (start < end && blank(grid[start]))
|
|
173
|
+
start++;
|
|
174
|
+
grid = grid.slice(start, end);
|
|
175
|
+
}
|
|
176
|
+
const lines = renderGrid(grid);
|
|
177
|
+
if (state === 'confused')
|
|
178
|
+
lines[2] = `${lines[2]} \x1b[1m?${RESET}`;
|
|
179
|
+
return lines.join('\n');
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* #131 — frame sequence for the "working" animation shown during long waits
|
|
183
|
+
* (the `create` wizard, `--watch`). Reuses the existing r6f state grids: the
|
|
184
|
+
* claws pump up (wave → celebrating) and back down, reading as Pinch busily
|
|
185
|
+
* waving while it works. No new art — just an order over the grids we have.
|
|
186
|
+
*/
|
|
187
|
+
export const WORKING_FRAMES = ['thinking', 'wave', 'celebrating', 'wave'];
|
|
157
188
|
/**
|
|
158
189
|
* True when it's safe to print Pinch art: a real color-capable interactive
|
|
159
190
|
* terminal. False under NO_COLOR (https://no-color.org — presence, not
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface PinchAnimation {
|
|
2
|
+
/** Stop the loop and erase the whole animation block, leaving the cursor at
|
|
3
|
+
* its top-left so the caller prints its result on a clean line. Idempotent. */
|
|
4
|
+
stop(): void;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* #131 — animate Pinch (a claw-wiggle over the r6f grids) IN PLACE on STDERR
|
|
8
|
+
* while a long operation runs (the `create` wizard, `--watch`). The frame is
|
|
9
|
+
* redrawn every {@link FRAME_MS} via ANSI cursor moves; `stop()` clears it.
|
|
10
|
+
*
|
|
11
|
+
* Contract: the CALLER must have already checked `pinchEnabled()` (TTY /
|
|
12
|
+
* !NO_COLOR) and that the command is NOT `--json` — this writes only to
|
|
13
|
+
* STDERR, so stdout stays pure regardless, but the animation is human-only.
|
|
14
|
+
* The interval is `unref()`'d so it can never keep the process alive on its
|
|
15
|
+
* own.
|
|
16
|
+
*/
|
|
17
|
+
export declare function startPinchAnimation(statusText: string): PinchAnimation;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { renderPinchArt, WORKING_FRAMES } from './pinch.js';
|
|
3
|
+
/** Frame cadence — slow enough to read as a wave, fast enough to feel alive. */
|
|
4
|
+
const FRAME_MS = 380;
|
|
5
|
+
/**
|
|
6
|
+
* #131 — animate Pinch (a claw-wiggle over the r6f grids) IN PLACE on STDERR
|
|
7
|
+
* while a long operation runs (the `create` wizard, `--watch`). The frame is
|
|
8
|
+
* redrawn every {@link FRAME_MS} via ANSI cursor moves; `stop()` clears it.
|
|
9
|
+
*
|
|
10
|
+
* Contract: the CALLER must have already checked `pinchEnabled()` (TTY /
|
|
11
|
+
* !NO_COLOR) and that the command is NOT `--json` — this writes only to
|
|
12
|
+
* STDERR, so stdout stays pure regardless, but the animation is human-only.
|
|
13
|
+
* The interval is `unref()`'d so it can never keep the process alive on its
|
|
14
|
+
* own.
|
|
15
|
+
*/
|
|
16
|
+
export function startPinchAnimation(statusText) {
|
|
17
|
+
const frames = WORKING_FRAMES.map((s) => renderPinchArt(s, { compact: true }));
|
|
18
|
+
const artHeight = frames[0].split('\n').length;
|
|
19
|
+
const totalHeight = artHeight + 1; // + the status line below the art
|
|
20
|
+
const out = process.stderr;
|
|
21
|
+
let i = 0;
|
|
22
|
+
const block = (frame) => `${frame}\n${chalk.dim(statusText)}\n`;
|
|
23
|
+
const cursorUp = (n) => {
|
|
24
|
+
if (n > 0)
|
|
25
|
+
out.write(`\x1b[${n}A`);
|
|
26
|
+
};
|
|
27
|
+
// Redraw a frame over the previous one — clear each line to EOL first so a
|
|
28
|
+
// narrower frame can't leave trailing pixels from a wider one.
|
|
29
|
+
const redraw = (frame) => {
|
|
30
|
+
const lines = `${frame}\n${chalk.dim(statusText)}`.split('\n');
|
|
31
|
+
out.write(lines.map((l) => `\x1b[2K${l}`).join('\n') + '\n');
|
|
32
|
+
};
|
|
33
|
+
out.write('\x1b[?25l'); // hide cursor for a flicker-free redraw
|
|
34
|
+
out.write(block(frames[0]));
|
|
35
|
+
const timer = setInterval(() => {
|
|
36
|
+
i += 1;
|
|
37
|
+
cursorUp(totalHeight);
|
|
38
|
+
redraw(frames[i % frames.length]);
|
|
39
|
+
}, FRAME_MS);
|
|
40
|
+
if (typeof timer.unref === 'function')
|
|
41
|
+
timer.unref();
|
|
42
|
+
let stopped = false;
|
|
43
|
+
return {
|
|
44
|
+
stop() {
|
|
45
|
+
if (stopped)
|
|
46
|
+
return;
|
|
47
|
+
stopped = true;
|
|
48
|
+
clearInterval(timer);
|
|
49
|
+
cursorUp(totalHeight); // back to the top of the block
|
|
50
|
+
for (let l = 0; l < totalHeight; l++)
|
|
51
|
+
out.write('\x1b[2K\n'); // erase each line
|
|
52
|
+
cursorUp(totalHeight); // back to top so the caller's next print lands here
|
|
53
|
+
out.write('\x1b[?25h'); // restore the cursor
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|