@trawlme/cli 3.11.0 → 3.12.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 +5 -2
- package/dist/commands/create.d.ts +0 -28
- package/dist/commands/create.js +0 -89
- package/dist/commands/doctor.d.ts +0 -79
- package/dist/commands/doctor.js +1 -187
- package/dist/commands/login.js +0 -67
- package/dist/commands/ping.d.ts +0 -15
- package/dist/commands/ping.js +0 -15
- package/dist/commands/scraps.d.ts +0 -120
- package/dist/commands/scraps.js +142 -656
- package/dist/commands/skills.js +0 -22
- package/dist/commands/spec.d.ts +0 -85
- package/dist/commands/spec.js +0 -67
- package/dist/commands/telemetry.js +0 -4
- package/dist/commands/token.js +0 -28
- package/dist/commands/upgrade.js +0 -22
- package/dist/commands/whoami.d.ts +0 -12
- package/dist/commands/whoami.js +0 -6
- package/dist/index.d.ts +0 -188
- package/dist/index.js +0 -349
- package/dist/lib/api.d.ts +0 -78
- package/dist/lib/api.js +1 -320
- package/dist/lib/cdp-pipe.d.ts +31 -0
- package/dist/lib/cdp-pipe.js +141 -0
- package/dist/lib/chrome-discovery.d.ts +1 -0
- package/dist/lib/chrome-discovery.js +30 -0
- package/dist/lib/chrome-launch.d.ts +8 -0
- package/dist/lib/chrome-launch.js +53 -0
- package/dist/lib/config.d.ts +0 -53
- package/dist/lib/config.js +0 -55
- package/dist/lib/confirm.d.ts +0 -55
- package/dist/lib/confirm.js +0 -47
- package/dist/lib/docs.d.ts +0 -123
- package/dist/lib/docs.js +0 -169
- package/dist/lib/errors.d.ts +0 -134
- package/dist/lib/errors.js +0 -151
- package/dist/lib/format.d.ts +0 -6
- package/dist/lib/format.js +0 -6
- package/dist/lib/json.d.ts +0 -35
- package/dist/lib/json.js +0 -48
- package/dist/lib/jwt.d.ts +0 -7
- package/dist/lib/jwt.js +0 -7
- package/dist/lib/pinch.d.ts +0 -53
- package/dist/lib/pinch.js +6 -112
- package/dist/lib/pinchAnimation.d.ts +0 -16
- package/dist/lib/pinchAnimation.js +8 -29
- package/dist/lib/posthog.d.ts +0 -9
- package/dist/lib/posthog.js +0 -23
- package/dist/lib/prompt.js +1 -20
- package/dist/lib/secure-transport.d.ts +1 -0
- package/dist/lib/secure-transport.js +15 -0
- package/dist/lib/session-capture-guard.d.ts +6 -0
- package/dist/lib/session-capture-guard.js +9 -0
- package/dist/lib/session-capture.d.ts +55 -0
- package/dist/lib/session-capture.js +319 -0
- package/dist/lib/skills.d.ts +0 -175
- package/dist/lib/skills.js +1 -216
- package/dist/lib/skillsNudge.d.ts +0 -17
- package/dist/lib/skillsNudge.js +0 -83
- package/dist/lib/spinner.d.ts +0 -39
- package/dist/lib/spinner.js +0 -40
- package/dist/lib/storage-state.d.ts +55 -0
- package/dist/lib/storage-state.js +96 -0
- package/dist/lib/tips.d.ts +0 -38
- package/dist/lib/tips.js +0 -77
- package/dist/lib/updateCheckWorker.js +0 -14
- package/dist/lib/updateNotifier.d.ts +0 -17
- package/dist/lib/updateNotifier.js +0 -53
- package/dist/lib/validate.d.ts +0 -8
- package/dist/lib/validate.js +0 -8
- package/dist/lib/version.d.ts +0 -12
- package/dist/lib/version.js +1 -13
- package/package.json +2 -2
package/dist/lib/json.d.ts
CHANGED
|
@@ -1,37 +1,2 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared, tolerant JSON parsing for server response bodies (#159).
|
|
3
|
-
*
|
|
4
|
-
* The Trawl API is known to occasionally emit a raw, unescaped control
|
|
5
|
-
* character (U+0000–U+001F — a literal newline, NUL, BEL, …) inside a JSON
|
|
6
|
-
* string value, most often nested inside `scrap.history[0].data` (a
|
|
7
|
-
* worker-envelope blob embedded as a string field of the outer response —
|
|
8
|
-
* the exact server-side emission point is not yet pinned down; tracked in
|
|
9
|
-
* comes-io/trawl_node#1768). That is invalid per RFC 8259 — our own QA
|
|
10
|
-
* tooling already works around it by parsing raw API responses with
|
|
11
|
-
* Python's `json.loads(text, strict=False)` — but native `JSON.parse`
|
|
12
|
-
* has no equivalent lenient mode: it throws outright, and until this fix
|
|
13
|
-
* every one of `api.ts`'s response-parsing call sites (`request`, `upload`,
|
|
14
|
-
* `publicGet`, `publicPost`) turned that into a bare "Invalid JSON in server
|
|
15
|
-
* response" error, discarding the ENTIRE response — even a successful
|
|
16
|
-
* create/run whose scrap was already persisted server-side. Exactly the
|
|
17
|
-
* "payload-dependent, not a constant break" shape reported in #159: it only
|
|
18
|
-
* fires when the specific scraped content/log text happens to carry a raw
|
|
19
|
-
* control byte.
|
|
20
|
-
*
|
|
21
|
-
* `parseServerJson` recovers from that ONE known shape — nothing else. It
|
|
22
|
-
* tries a normal strict `JSON.parse` first (the common case, zero extra
|
|
23
|
-
* cost) and only falls back to a sanitizing re-parse when that throws.
|
|
24
|
-
* Genuinely malformed JSON (truncated body, stray token, …) still throws
|
|
25
|
-
* after the fallback also fails — this is a targeted recovery, not a
|
|
26
|
-
* general-purpose lenient parser.
|
|
27
|
-
*/
|
|
28
1
|
export declare function escapeRawControlCharsInJsonStrings(text: string): string;
|
|
29
|
-
/**
|
|
30
|
-
* Parse a server response body, tolerating the known #159 quirk above. Never
|
|
31
|
-
* silently drops data: a byte that reaches here as a real control character
|
|
32
|
-
* comes back out as the same character (via the escape → JSON.parse
|
|
33
|
-
* unescape round trip) once parsed, and JSON.stringify guarantees it is
|
|
34
|
-
* re-escaped correctly on the way back out through the shared `--json`
|
|
35
|
-
* output seam (`lib/format.ts#json`).
|
|
36
|
-
*/
|
|
37
2
|
export declare function parseServerJson(text: string): unknown;
|
package/dist/lib/json.js
CHANGED
|
@@ -1,40 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared, tolerant JSON parsing for server response bodies (#159).
|
|
3
|
-
*
|
|
4
|
-
* The Trawl API is known to occasionally emit a raw, unescaped control
|
|
5
|
-
* character (U+0000–U+001F — a literal newline, NUL, BEL, …) inside a JSON
|
|
6
|
-
* string value, most often nested inside `scrap.history[0].data` (a
|
|
7
|
-
* worker-envelope blob embedded as a string field of the outer response —
|
|
8
|
-
* the exact server-side emission point is not yet pinned down; tracked in
|
|
9
|
-
* comes-io/trawl_node#1768). That is invalid per RFC 8259 — our own QA
|
|
10
|
-
* tooling already works around it by parsing raw API responses with
|
|
11
|
-
* Python's `json.loads(text, strict=False)` — but native `JSON.parse`
|
|
12
|
-
* has no equivalent lenient mode: it throws outright, and until this fix
|
|
13
|
-
* every one of `api.ts`'s response-parsing call sites (`request`, `upload`,
|
|
14
|
-
* `publicGet`, `publicPost`) turned that into a bare "Invalid JSON in server
|
|
15
|
-
* response" error, discarding the ENTIRE response — even a successful
|
|
16
|
-
* create/run whose scrap was already persisted server-side. Exactly the
|
|
17
|
-
* "payload-dependent, not a constant break" shape reported in #159: it only
|
|
18
|
-
* fires when the specific scraped content/log text happens to carry a raw
|
|
19
|
-
* control byte.
|
|
20
|
-
*
|
|
21
|
-
* `parseServerJson` recovers from that ONE known shape — nothing else. It
|
|
22
|
-
* tries a normal strict `JSON.parse` first (the common case, zero extra
|
|
23
|
-
* cost) and only falls back to a sanitizing re-parse when that throws.
|
|
24
|
-
* Genuinely malformed JSON (truncated body, stray token, …) still throws
|
|
25
|
-
* after the fallback also fails — this is a targeted recovery, not a
|
|
26
|
-
* general-purpose lenient parser.
|
|
27
|
-
*/
|
|
28
|
-
/**
|
|
29
|
-
* Escape any raw control character (U+0000–U+001F) found INSIDE a JSON
|
|
30
|
-
* string literal, leaving everything else — including legitimate raw
|
|
31
|
-
* whitespace between tokens (space/tab/CR/LF are valid there per RFC 8259) —
|
|
32
|
-
* untouched. A small state machine tracks whether the scan is currently
|
|
33
|
-
* inside a `"…"` string and whether the previous character was an unescaped
|
|
34
|
-
* backslash; it does not otherwise validate structure, so text that is
|
|
35
|
-
* invalid JSON for any OTHER reason still fails the subsequent `JSON.parse`
|
|
36
|
-
* call unchanged.
|
|
37
|
-
*/
|
|
38
1
|
const NAMED_CONTROL_CHAR_ESCAPES = {
|
|
39
2
|
'\n': '\\n',
|
|
40
3
|
'\r': '\\r',
|
|
@@ -71,9 +34,6 @@ export function escapeRawControlCharsInJsonStrings(text) {
|
|
|
71
34
|
continue;
|
|
72
35
|
}
|
|
73
36
|
if (code <= 0x1f) {
|
|
74
|
-
// Raw control character inside a string literal — recover the same
|
|
75
|
-
// way JSON.stringify would have escaped it on the way out, instead of
|
|
76
|
-
// losing the whole response to a SyntaxError.
|
|
77
37
|
out += NAMED_CONTROL_CHAR_ESCAPES[ch] ?? `\\u${code.toString(16).padStart(4, '0')}`;
|
|
78
38
|
continue;
|
|
79
39
|
}
|
|
@@ -81,14 +41,6 @@ export function escapeRawControlCharsInJsonStrings(text) {
|
|
|
81
41
|
}
|
|
82
42
|
return out;
|
|
83
43
|
}
|
|
84
|
-
/**
|
|
85
|
-
* Parse a server response body, tolerating the known #159 quirk above. Never
|
|
86
|
-
* silently drops data: a byte that reaches here as a real control character
|
|
87
|
-
* comes back out as the same character (via the escape → JSON.parse
|
|
88
|
-
* unescape round trip) once parsed, and JSON.stringify guarantees it is
|
|
89
|
-
* re-escaped correctly on the way back out through the shared `--json`
|
|
90
|
-
* output seam (`lib/format.ts#json`).
|
|
91
|
-
*/
|
|
92
44
|
export function parseServerJson(text) {
|
|
93
45
|
try {
|
|
94
46
|
return JSON.parse(text);
|
package/dist/lib/jwt.d.ts
CHANGED
|
@@ -1,8 +1 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Decode the `exp` claim from a JWT (middle segment, base64url-encoded JSON).
|
|
3
|
-
* Returns null if the payload cannot be decoded or has no `exp` field.
|
|
4
|
-
*
|
|
5
|
-
* Shared between `trawl token` (expiry advisory) and `trawl login` (reject
|
|
6
|
-
* already-expired tokens instead of silently storing them). (#68)
|
|
7
|
-
*/
|
|
8
1
|
export declare function decodeExp(jwt: string): number | null;
|
package/dist/lib/jwt.js
CHANGED
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Decode the `exp` claim from a JWT (middle segment, base64url-encoded JSON).
|
|
3
|
-
* Returns null if the payload cannot be decoded or has no `exp` field.
|
|
4
|
-
*
|
|
5
|
-
* Shared between `trawl token` (expiry advisory) and `trawl login` (reject
|
|
6
|
-
* already-expired tokens instead of silently storing them). (#68)
|
|
7
|
-
*/
|
|
8
1
|
export function decodeExp(jwt) {
|
|
9
2
|
try {
|
|
10
3
|
const parts = jwt.split('.');
|
package/dist/lib/pinch.d.ts
CHANGED
|
@@ -1,61 +1,8 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pinch — Trawl's voxel lobster mascot, rendered as 24-bit ANSI half-block
|
|
3
|
-
* art. Distinct from Clawd's 8-bit lane: Pinch is drawn with full 24-bit
|
|
4
|
-
* (`\x1b[38;2;r;g;bm` / `\x1b[48;2;r;g;bm`) color blocks, not a fixed palette.
|
|
5
|
-
*
|
|
6
|
-
* Each grid is packed two rows into one terminal row: the upper row's color
|
|
7
|
-
* becomes the half-block's foreground, the lower row's becomes its
|
|
8
|
-
* background, using the upper-half-block glyph '▀' (or '▄' when only the
|
|
9
|
-
* lower half is filled). '.' cells are transparent — no color escape is
|
|
10
|
-
* emitted for that half, so the terminal's own background shows through.
|
|
11
|
-
*
|
|
12
|
-
* The grids are the founder-approved 2D front avatar (`AVATAR_GRIDS`, rev
|
|
13
|
-
* r6f) from trawl_vue `src/modules/trawl/assets/mascot/_src/pinch.model.mjs`,
|
|
14
|
-
* ported VERBATIM (no cross-repo import — cli is a standalone npm package).
|
|
15
|
-
* Each is 14 cols × 8 rows (the source's 9th all-'.' pad row is dropped) →
|
|
16
|
-
* renders in 4 terminal rows, 14 columns wide. This is the mascot "de face":
|
|
17
|
-
* antennae on top, 2-wide white eyes + navy pupils, pink cheek blush, a
|
|
18
|
-
* U-smile (corners + a 2-wide dropped center), and the signature 2-WIDE
|
|
19
|
-
* orange claws on the body flanks (cols 0-1 / 12-13).
|
|
20
|
-
*
|
|
21
|
-
* History: #133 shrank this to a 9×6 mini (1-wide claws, a single-pixel
|
|
22
|
-
* mouth) — #141 reverted to this faithful 14×9 founder art after it read as
|
|
23
|
-
* "trop loin du 3D".
|
|
24
|
-
*
|
|
25
|
-
* State parity (#135): the CLI carries all 9 canonical AVATAR_STATES with the
|
|
26
|
-
* source's own names — idle · working · celebrating · thumbsup · confused ·
|
|
27
|
-
* wave · heart · sleepy · shocked.
|
|
28
|
-
*
|
|
29
|
-
* See comes-io/trawl_cli#94, #122, #135, #141.
|
|
30
|
-
*/
|
|
31
1
|
export type PinchState = 'idle' | 'working' | 'celebrating' | 'thumbsup' | 'confused' | 'wave' | 'heart' | 'sleepy' | 'shocked';
|
|
32
|
-
/** All 9 canonical states, in the source model's AVATAR_STATES order. */
|
|
33
2
|
export declare const PINCH_STATES: readonly PinchState[];
|
|
34
|
-
/**
|
|
35
|
-
* Render Pinch as 24-bit ANSI half-block art for the given state, plus a
|
|
36
|
-
* one-line caption. Pure — never touches process.env/stdout; callers must
|
|
37
|
-
* gate on `pinchEnabled()` before printing the result.
|
|
38
|
-
*
|
|
39
|
-
* `frame` only affects `working` (the cyan antenna-tip blip alternates every
|
|
40
|
-
* frame); every other state's grid is static.
|
|
41
|
-
*/
|
|
42
3
|
export declare function renderPinch(state: PinchState, frame?: number): string;
|
|
43
|
-
/**
|
|
44
|
-
* #131 — the ART lines only (no caption), optionally COMPACT: trailing and
|
|
45
|
-
* leading fully-transparent grid rows are dropped so Pinch takes fewer
|
|
46
|
-
* terminal lines (confused/sleepy carry an all-'.' leading row). `frame` is
|
|
47
|
-
* passed through to `gridForState` — only `working` reacts to it (the cyan
|
|
48
|
-
* antenna blip toggles), so the loop can pulse the tips while the body stays
|
|
49
|
-
* still.
|
|
50
|
-
*/
|
|
51
4
|
export declare function renderPinchArt(state: PinchState, opts?: {
|
|
52
5
|
compact?: boolean;
|
|
53
6
|
frame?: number;
|
|
54
7
|
}): string;
|
|
55
|
-
/**
|
|
56
|
-
* True when it's safe to print Pinch art: a real color-capable interactive
|
|
57
|
-
* terminal. False under NO_COLOR (https://no-color.org — presence, not
|
|
58
|
-
* value, disables color output), a non-TTY stdout (piped/redirected output —
|
|
59
|
-
* covers --json/--quiet/CI log capture), or TERM=dumb.
|
|
60
|
-
*/
|
|
61
8
|
export declare function pinchEnabled(): boolean;
|
package/dist/lib/pinch.js
CHANGED
|
@@ -1,46 +1,12 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pinch — Trawl's voxel lobster mascot, rendered as 24-bit ANSI half-block
|
|
3
|
-
* art. Distinct from Clawd's 8-bit lane: Pinch is drawn with full 24-bit
|
|
4
|
-
* (`\x1b[38;2;r;g;bm` / `\x1b[48;2;r;g;bm`) color blocks, not a fixed palette.
|
|
5
|
-
*
|
|
6
|
-
* Each grid is packed two rows into one terminal row: the upper row's color
|
|
7
|
-
* becomes the half-block's foreground, the lower row's becomes its
|
|
8
|
-
* background, using the upper-half-block glyph '▀' (or '▄' when only the
|
|
9
|
-
* lower half is filled). '.' cells are transparent — no color escape is
|
|
10
|
-
* emitted for that half, so the terminal's own background shows through.
|
|
11
|
-
*
|
|
12
|
-
* The grids are the founder-approved 2D front avatar (`AVATAR_GRIDS`, rev
|
|
13
|
-
* r6f) from trawl_vue `src/modules/trawl/assets/mascot/_src/pinch.model.mjs`,
|
|
14
|
-
* ported VERBATIM (no cross-repo import — cli is a standalone npm package).
|
|
15
|
-
* Each is 14 cols × 8 rows (the source's 9th all-'.' pad row is dropped) →
|
|
16
|
-
* renders in 4 terminal rows, 14 columns wide. This is the mascot "de face":
|
|
17
|
-
* antennae on top, 2-wide white eyes + navy pupils, pink cheek blush, a
|
|
18
|
-
* U-smile (corners + a 2-wide dropped center), and the signature 2-WIDE
|
|
19
|
-
* orange claws on the body flanks (cols 0-1 / 12-13).
|
|
20
|
-
*
|
|
21
|
-
* History: #133 shrank this to a 9×6 mini (1-wide claws, a single-pixel
|
|
22
|
-
* mouth) — #141 reverted to this faithful 14×9 founder art after it read as
|
|
23
|
-
* "trop loin du 3D".
|
|
24
|
-
*
|
|
25
|
-
* State parity (#135): the CLI carries all 9 canonical AVATAR_STATES with the
|
|
26
|
-
* source's own names — idle · working · celebrating · thumbsup · confused ·
|
|
27
|
-
* wave · heart · sleepy · shocked.
|
|
28
|
-
*
|
|
29
|
-
* See comes-io/trawl_cli#94, #122, #135, #141.
|
|
30
|
-
*/
|
|
31
|
-
/** Grid-char → RGB, ported from AVCOLORS (pinch.model.mjs, rev r6f). 'C'
|
|
32
|
-
* (cyan) is the `working` thinking-blip — not present in any static grid,
|
|
33
|
-
* injected per-frame on the antenna tips. */
|
|
34
1
|
const PALETTE = {
|
|
35
|
-
B: [0x29, 0x79, 0xff],
|
|
36
|
-
O: [0xfb, 0x92, 0x3c],
|
|
37
|
-
W: [0xff, 0xff, 0xff],
|
|
38
|
-
K: [0x12, 0x30, 0x33],
|
|
39
|
-
P: [0xf4, 0x72, 0xb6],
|
|
40
|
-
C: [0x06, 0xb6, 0xd4],
|
|
2
|
+
B: [0x29, 0x79, 0xff],
|
|
3
|
+
O: [0xfb, 0x92, 0x3c],
|
|
4
|
+
W: [0xff, 0xff, 0xff],
|
|
5
|
+
K: [0x12, 0x30, 0x33],
|
|
6
|
+
P: [0xf4, 0x72, 0xb6],
|
|
7
|
+
C: [0x06, 0xb6, 0xd4],
|
|
41
8
|
};
|
|
42
9
|
const TRANSPARENT = '.';
|
|
43
|
-
/** All 9 canonical states, in the source model's AVATAR_STATES order. */
|
|
44
10
|
export const PINCH_STATES = [
|
|
45
11
|
'idle',
|
|
46
12
|
'working',
|
|
@@ -52,21 +18,6 @@ export const PINCH_STATES = [
|
|
|
52
18
|
'sleepy',
|
|
53
19
|
'shocked',
|
|
54
20
|
];
|
|
55
|
-
/**
|
|
56
|
-
* The founder 2D front avatar grids (`AVATAR_GRIDS`, pinch.model.mjs rev
|
|
57
|
-
* r6f), ported VERBATIM — rows 0-7 of each 9-row grid (the trailing all-'.'
|
|
58
|
-
* pad row 8 is dropped; it only exists to square the SVG canvas). 14 cols,
|
|
59
|
-
* 8 rows → 4 terminal lines. Layout:
|
|
60
|
-
* rows 0-1 antennae (2 orange, drooped/spread for confused & sleepy)
|
|
61
|
-
* row 2 head top (+ claw at cols 0-1/12-13 when RAISED)
|
|
62
|
-
* rows 3-4 eyes: 2-wide W whites (row 3) + K pupils (row 4)
|
|
63
|
-
* row 5 head mid
|
|
64
|
-
* rows 6-7 mouth + blush + the 2-WIDE claws on the flanks
|
|
65
|
-
* The claws are 2×2 O blocks OUTSIDE the body silhouette (cols 0-1 / 12-13),
|
|
66
|
-
* raised to rows 3-4 for celebrating / shocked (and the right claw for
|
|
67
|
-
* wave / thumbsup). The smile is a U: corners K on row 6, a 2-wide center
|
|
68
|
-
* KK on row 7.
|
|
69
|
-
*/
|
|
70
21
|
const GRIDS = {
|
|
71
22
|
idle: [
|
|
72
23
|
'....O....O....',
|
|
@@ -78,8 +29,6 @@ const GRIDS = {
|
|
|
78
29
|
'OOBBPKBBKPBBOO',
|
|
79
30
|
'OOBBBBKKBBBBOO',
|
|
80
31
|
],
|
|
81
|
-
// idle pose + the cyan thinking-blip on the antenna tips (frame-toggled in
|
|
82
|
-
// `gridForState`) — the only animated static state.
|
|
83
32
|
working: [
|
|
84
33
|
'....O....O....',
|
|
85
34
|
'....O....O....',
|
|
@@ -90,7 +39,6 @@ const GRIDS = {
|
|
|
90
39
|
'OOBBPKBBKPBBOO',
|
|
91
40
|
'OOBBBBKKBBBBOO',
|
|
92
41
|
],
|
|
93
|
-
// both claws raised to rows 3-4 + a big open mouth (rows 6-7).
|
|
94
42
|
celebrating: [
|
|
95
43
|
'....O....O....',
|
|
96
44
|
'....O....O....',
|
|
@@ -101,8 +49,6 @@ const GRIDS = {
|
|
|
101
49
|
'..BBPKKKKPBB..',
|
|
102
50
|
'..BBBKKKKBBB..',
|
|
103
51
|
],
|
|
104
|
-
// right claw raised (rows 3-4) + a thumb pixel poking up (row 2, col 12);
|
|
105
|
-
// left claw resting on the flank (rows 6-7).
|
|
106
52
|
thumbsup: [
|
|
107
53
|
'....O....O....',
|
|
108
54
|
'....O....O....',
|
|
@@ -113,8 +59,6 @@ const GRIDS = {
|
|
|
113
59
|
'OOBBPKBBKPBB..',
|
|
114
60
|
'OOBBBBKKBBBB..',
|
|
115
61
|
],
|
|
116
|
-
// antennae drooped + spread (row 1, cols 3 & 10), flat mouth (blush only, no
|
|
117
|
-
// smile K on row 6), claws resting. A '?' is appended by `renderPinch`.
|
|
118
62
|
confused: [
|
|
119
63
|
'..............',
|
|
120
64
|
'...O......O...',
|
|
@@ -125,7 +69,6 @@ const GRIDS = {
|
|
|
125
69
|
'OOBBPBBBBPBBOO',
|
|
126
70
|
'OOBBBBKKBBBBOO',
|
|
127
71
|
],
|
|
128
|
-
// right claw raised (rows 3-4), left claw resting; base face.
|
|
129
72
|
wave: [
|
|
130
73
|
'....O....O....',
|
|
131
74
|
'....O....O....',
|
|
@@ -136,8 +79,6 @@ const GRIDS = {
|
|
|
136
79
|
'OOBBPKBBKPBB..',
|
|
137
80
|
'OOBBBBKKBBBB..',
|
|
138
81
|
],
|
|
139
|
-
// pink heart-eyes (P fills both eye blocks, rows 3-4), claws resting, normal
|
|
140
|
-
// smile.
|
|
141
82
|
heart: [
|
|
142
83
|
'....O....O....',
|
|
143
84
|
'....O....O....',
|
|
@@ -148,8 +89,6 @@ const GRIDS = {
|
|
|
148
89
|
'OOBBPKBBKPBBOO',
|
|
149
90
|
'OOBBBBKKBBBBOO',
|
|
150
91
|
],
|
|
151
|
-
// closed eyes (flat K line on row 4, no white), antennae drooped + spread,
|
|
152
|
-
// flat mouth, claws resting — like confused but eyes shut and no '?'.
|
|
153
92
|
sleepy: [
|
|
154
93
|
'..............',
|
|
155
94
|
'...O......O...',
|
|
@@ -160,9 +99,6 @@ const GRIDS = {
|
|
|
160
99
|
'OOBBPBBBBPBBOO',
|
|
161
100
|
'OOBBBBKKBBBBOO',
|
|
162
101
|
],
|
|
163
|
-
// pupils up a row (row 3 carries K, row 4 all white), both claws raised
|
|
164
|
-
// (rows 3-4), a small round mouth (row 6 only) — startled, not the wide
|
|
165
|
-
// celebrating grin.
|
|
166
102
|
shocked: [
|
|
167
103
|
'....O....O....',
|
|
168
104
|
'....O....O....',
|
|
@@ -185,19 +121,13 @@ const CAPTIONS = {
|
|
|
185
121
|
sleepy: 'Pinch is sleepy…',
|
|
186
122
|
shocked: 'Pinch is shocked!',
|
|
187
123
|
};
|
|
188
|
-
/** Antenna-tip cells (grid row 0) for the `working` cyan blip: cols 4 & 9. */
|
|
189
124
|
const ANTENNA_TIP_COLS = [4, 9];
|
|
190
|
-
/** Replace the chars at `indices` in `row` with `ch` — never mutates `row`. */
|
|
191
125
|
function setCells(row, indices, ch) {
|
|
192
126
|
const chars = row.split('');
|
|
193
127
|
for (const i of indices)
|
|
194
128
|
chars[i] = ch;
|
|
195
129
|
return chars.join('');
|
|
196
130
|
}
|
|
197
|
-
/** Look up the grid for `state`. `frame` only affects `working`: on even
|
|
198
|
-
* frames its antenna tips blip cyan (the thinking indicator), on odd frames
|
|
199
|
-
* they stay orange — a 2-frame pulse with no layout shift. Every other
|
|
200
|
-
* state's grid is static and ignores `frame`. */
|
|
201
131
|
function gridForState(state, frame) {
|
|
202
132
|
if (state === 'working' && frame % 2 === 0) {
|
|
203
133
|
const rows = [...GRIDS.working];
|
|
@@ -209,29 +139,17 @@ function gridForState(state, frame) {
|
|
|
209
139
|
const RESET = '\x1b[0m';
|
|
210
140
|
const fgCode = ([r, g, b]) => `\x1b[38;2;${r};${g};${b}m`;
|
|
211
141
|
const bgCode = ([r, g, b]) => `\x1b[48;2;${r};${g};${b}m`;
|
|
212
|
-
/**
|
|
213
|
-
* One terminal column: combine an upper (fg-half) + lower (bg-half) grid
|
|
214
|
-
* cell into a single half-block character. Either half may be transparent
|
|
215
|
-
* independently — no bg/fg escape is emitted for a transparent half, so the
|
|
216
|
-
* terminal's own background/foreground shows through it.
|
|
217
|
-
*/
|
|
218
142
|
function renderCell(upper, lower) {
|
|
219
143
|
const upperColor = upper === TRANSPARENT ? null : PALETTE[upper];
|
|
220
144
|
const lowerColor = lower === TRANSPARENT ? null : PALETTE[lower];
|
|
221
145
|
if (!upperColor && !lowerColor)
|
|
222
146
|
return ' ';
|
|
223
|
-
// Lower half transparent — draw the upper half-block with fg only, no bg
|
|
224
|
-
// (the glyph's own unpainted lower half already shows the terminal bg).
|
|
225
147
|
if (!lowerColor)
|
|
226
148
|
return `${fgCode(upperColor)}▀${RESET}`;
|
|
227
|
-
// Upper half transparent — draw the LOWER half-block glyph instead, with
|
|
228
|
-
// fg set to the lower color and no bg, so the painted half is the bottom.
|
|
229
149
|
if (!upperColor)
|
|
230
150
|
return `${fgCode(lowerColor)}▄${RESET}`;
|
|
231
151
|
return `${fgCode(upperColor)}${bgCode(lowerColor)}▀${RESET}`;
|
|
232
152
|
}
|
|
233
|
-
/** Pack a grid into terminal lines, 2 grid rows per line (upper=fg, lower=bg).
|
|
234
|
-
* The 14×8 founder grid is 8 rows → 4 lines. */
|
|
235
153
|
function renderGrid(rows) {
|
|
236
154
|
const lines = [];
|
|
237
155
|
for (let i = 0; i < rows.length; i += 2) {
|
|
@@ -245,32 +163,14 @@ function renderGrid(rows) {
|
|
|
245
163
|
}
|
|
246
164
|
return lines;
|
|
247
165
|
}
|
|
248
|
-
/**
|
|
249
|
-
* Render Pinch as 24-bit ANSI half-block art for the given state, plus a
|
|
250
|
-
* one-line caption. Pure — never touches process.env/stdout; callers must
|
|
251
|
-
* gate on `pinchEnabled()` before printing the result.
|
|
252
|
-
*
|
|
253
|
-
* `frame` only affects `working` (the cyan antenna-tip blip alternates every
|
|
254
|
-
* frame); every other state's grid is static.
|
|
255
|
-
*/
|
|
256
166
|
export function renderPinch(state, frame = 0) {
|
|
257
167
|
const grid = gridForState(state, frame);
|
|
258
168
|
const lines = renderGrid(grid);
|
|
259
169
|
if (state === 'confused') {
|
|
260
|
-
// "beside the art" — a bold '?' to the right of the eye row. The eyes are
|
|
261
|
-
// grid rows 2-3 (head-top + eye-whites), which pack into terminal line 1.
|
|
262
170
|
lines[1] = `${lines[1]} \x1b[1m?${RESET}`;
|
|
263
171
|
}
|
|
264
172
|
return [...lines, CAPTIONS[state]].join('\n');
|
|
265
173
|
}
|
|
266
|
-
/**
|
|
267
|
-
* #131 — the ART lines only (no caption), optionally COMPACT: trailing and
|
|
268
|
-
* leading fully-transparent grid rows are dropped so Pinch takes fewer
|
|
269
|
-
* terminal lines (confused/sleepy carry an all-'.' leading row). `frame` is
|
|
270
|
-
* passed through to `gridForState` — only `working` reacts to it (the cyan
|
|
271
|
-
* antenna blip toggles), so the loop can pulse the tips while the body stays
|
|
272
|
-
* still.
|
|
273
|
-
*/
|
|
274
174
|
export function renderPinchArt(state, opts = {}) {
|
|
275
175
|
let grid = gridForState(state, opts.frame ?? 0);
|
|
276
176
|
if (opts.compact) {
|
|
@@ -288,12 +188,6 @@ export function renderPinchArt(state, opts = {}) {
|
|
|
288
188
|
lines[1] = `${lines[1]} \x1b[1m?${RESET}`;
|
|
289
189
|
return lines.join('\n');
|
|
290
190
|
}
|
|
291
|
-
/**
|
|
292
|
-
* True when it's safe to print Pinch art: a real color-capable interactive
|
|
293
|
-
* terminal. False under NO_COLOR (https://no-color.org — presence, not
|
|
294
|
-
* value, disables color output), a non-TTY stdout (piped/redirected output —
|
|
295
|
-
* covers --json/--quiet/CI log capture), or TERM=dumb.
|
|
296
|
-
*/
|
|
297
191
|
export function pinchEnabled() {
|
|
298
192
|
if (process.env['NO_COLOR'] !== undefined)
|
|
299
193
|
return false;
|
|
@@ -1,20 +1,4 @@
|
|
|
1
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
2
|
stop(): void;
|
|
5
3
|
}
|
|
6
|
-
/**
|
|
7
|
-
* #131 / #139 — show Pinch IN PLACE on STDERR while a long operation runs (the
|
|
8
|
-
* `create` wizard, `--watch`). Pinch stays a STILL `working` sprite; the ONLY
|
|
9
|
-
* animation is the cyan antenna-tip blip pulsing on/off every {@link FRAME_MS}
|
|
10
|
-
* (frame 0 = cyan, frame 1 = orange). The body — mouth, claws, eyes — never
|
|
11
|
-
* moves (the old loop cycled whole poses, which flickered "in every
|
|
12
|
-
* direction"). Redrawn via ANSI cursor moves; `stop()` clears the block.
|
|
13
|
-
*
|
|
14
|
-
* Contract: the CALLER must have already checked `pinchEnabled()` (TTY /
|
|
15
|
-
* !NO_COLOR) and that the command is NOT `--json` — this writes only to
|
|
16
|
-
* STDERR, so stdout stays pure regardless, but the animation is human-only.
|
|
17
|
-
* The interval is `unref()`'d so it can never keep the process alive on its
|
|
18
|
-
* own.
|
|
19
|
-
*/
|
|
20
4
|
export declare function startPinchAnimation(statusText: string): PinchAnimation;
|
|
@@ -1,32 +1,13 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { renderPinchArt } from './pinch.js';
|
|
3
|
-
/** Frame cadence — a calm, ~2/second pulse. The only thing that moves is the
|
|
4
|
-
* cyan antenna blip, so this is intentionally slow: a fast rate on a 2-pixel
|
|
5
|
-
* color change reads as a nervous flicker. */
|
|
6
3
|
const FRAME_MS = 600;
|
|
7
|
-
/**
|
|
8
|
-
* #131 / #139 — show Pinch IN PLACE on STDERR while a long operation runs (the
|
|
9
|
-
* `create` wizard, `--watch`). Pinch stays a STILL `working` sprite; the ONLY
|
|
10
|
-
* animation is the cyan antenna-tip blip pulsing on/off every {@link FRAME_MS}
|
|
11
|
-
* (frame 0 = cyan, frame 1 = orange). The body — mouth, claws, eyes — never
|
|
12
|
-
* moves (the old loop cycled whole poses, which flickered "in every
|
|
13
|
-
* direction"). Redrawn via ANSI cursor moves; `stop()` clears the block.
|
|
14
|
-
*
|
|
15
|
-
* Contract: the CALLER must have already checked `pinchEnabled()` (TTY /
|
|
16
|
-
* !NO_COLOR) and that the command is NOT `--json` — this writes only to
|
|
17
|
-
* STDERR, so stdout stays pure regardless, but the animation is human-only.
|
|
18
|
-
* The interval is `unref()`'d so it can never keep the process alive on its
|
|
19
|
-
* own.
|
|
20
|
-
*/
|
|
21
4
|
export function startPinchAnimation(statusText) {
|
|
22
|
-
// Two frames of the SAME `working` sprite — identical geometry, the antenna
|
|
23
|
-
// tips just toggle cyan↔orange. Nothing else changes between frames.
|
|
24
5
|
const frames = [
|
|
25
|
-
renderPinchArt('working', { compact: true, frame: 0 }),
|
|
26
|
-
renderPinchArt('working', { compact: true, frame: 1 }),
|
|
6
|
+
renderPinchArt('working', { compact: true, frame: 0 }),
|
|
7
|
+
renderPinchArt('working', { compact: true, frame: 1 }),
|
|
27
8
|
];
|
|
28
9
|
const artHeight = frames[0].split('\n').length;
|
|
29
|
-
const totalHeight = artHeight + 1;
|
|
10
|
+
const totalHeight = artHeight + 1;
|
|
30
11
|
const out = process.stderr;
|
|
31
12
|
let i = 0;
|
|
32
13
|
const block = (frame) => `${frame}\n${chalk.dim(statusText)}\n`;
|
|
@@ -34,13 +15,11 @@ export function startPinchAnimation(statusText) {
|
|
|
34
15
|
if (n > 0)
|
|
35
16
|
out.write(`\x1b[${n}A`);
|
|
36
17
|
};
|
|
37
|
-
// Redraw a frame over the previous one — clear each line to EOL first so a
|
|
38
|
-
// narrower frame can't leave trailing pixels from a wider one.
|
|
39
18
|
const redraw = (frame) => {
|
|
40
19
|
const lines = `${frame}\n${chalk.dim(statusText)}`.split('\n');
|
|
41
20
|
out.write(lines.map((l) => `\x1b[2K${l}`).join('\n') + '\n');
|
|
42
21
|
};
|
|
43
|
-
out.write('\x1b[?25l');
|
|
22
|
+
out.write('\x1b[?25l');
|
|
44
23
|
out.write(block(frames[0]));
|
|
45
24
|
const timer = setInterval(() => {
|
|
46
25
|
i += 1;
|
|
@@ -56,11 +35,11 @@ export function startPinchAnimation(statusText) {
|
|
|
56
35
|
return;
|
|
57
36
|
stopped = true;
|
|
58
37
|
clearInterval(timer);
|
|
59
|
-
cursorUp(totalHeight);
|
|
38
|
+
cursorUp(totalHeight);
|
|
60
39
|
for (let l = 0; l < totalHeight; l++)
|
|
61
|
-
out.write('\x1b[2K\n');
|
|
62
|
-
cursorUp(totalHeight);
|
|
63
|
-
out.write('\x1b[?25h');
|
|
40
|
+
out.write('\x1b[2K\n');
|
|
41
|
+
cursorUp(totalHeight);
|
|
42
|
+
out.write('\x1b[?25h');
|
|
64
43
|
},
|
|
65
44
|
};
|
|
66
45
|
}
|
package/dist/lib/posthog.d.ts
CHANGED
|
@@ -1,14 +1,5 @@
|
|
|
1
1
|
export declare const initPostHog: () => void;
|
|
2
|
-
/**
|
|
3
|
-
* Register the complete set of valid "<parent> <name>" telemetry tokens,
|
|
4
|
-
* derived from the actual commander command tree (see resolveCommandName /
|
|
5
|
-
* collectCommandNames in src/index.ts). captureCommand refuses any command
|
|
6
|
-
* string outside this set — defense in depth so free-form user input (argv
|
|
7
|
-
* operands, flag values) can never reach PostHog as the event name, even if a
|
|
8
|
-
* future change accidentally reintroduces argv-derived naming.
|
|
9
|
-
*/
|
|
10
2
|
export declare const registerAllowedCommands: (names: readonly string[]) => void;
|
|
11
3
|
export declare const captureCommand: (command: string, props?: Record<string, unknown>) => void;
|
|
12
4
|
export declare const shutdown: () => Promise<void>;
|
|
13
|
-
/** Reset singleton state — for testing only */
|
|
14
5
|
export declare const _resetForTesting: () => void;
|
package/dist/lib/posthog.js
CHANGED
|
@@ -6,28 +6,17 @@ import { readFileSync } from 'node:fs';
|
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
7
7
|
import { dirname, resolve } from 'node:path';
|
|
8
8
|
import config from './config.js';
|
|
9
|
-
// public-by-design — cf memory feedback_public_keys_in_config.md
|
|
10
9
|
const POSTHOG_KEY = 'phc_pGgSLivbq8rDZzXC9trWWJG7sBjNghXeTJjETzBcnEDv';
|
|
11
10
|
const POSTHOG_HOST = 'https://eu.i.posthog.com';
|
|
12
|
-
// Fail fast: a firewalled/offline endpoint must never add tens of seconds of
|
|
13
|
-
// trailing latency to a CLI command. Library defaults are requestTimeout 10s x
|
|
14
|
-
// 3 retries at a 3s delay (~40s+ worst case per call), and a 30s shutdown
|
|
15
|
-
// ceiling on top of that — cap all three low.
|
|
16
11
|
const REQUEST_TIMEOUT_MS = 3000;
|
|
17
12
|
const FETCH_RETRY_COUNT = 1;
|
|
18
13
|
const FETCH_RETRY_DELAY_MS = 250;
|
|
19
|
-
// A touch above one request + one retry, so the shutdown-time flush has room
|
|
20
|
-
// to complete normally without inheriting the library's 30s default ceiling.
|
|
21
14
|
const SHUTDOWN_TIMEOUT_MS = 8000;
|
|
22
15
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
23
16
|
const pkg = JSON.parse(readFileSync(resolve(__dirname, '../../package.json'), 'utf8'));
|
|
24
17
|
let client = null;
|
|
25
18
|
let distinctId = null;
|
|
26
19
|
let allowedCommands = new Set();
|
|
27
|
-
/**
|
|
28
|
-
* Cross-vendor opt-out (https://consoledonottrack.com) on top of the existing
|
|
29
|
-
* config/env gates. Any one of these disables telemetry entirely.
|
|
30
|
-
*/
|
|
31
20
|
function isTelemetryDisabled() {
|
|
32
21
|
return (config.get('telemetry') === false ||
|
|
33
22
|
process.env['TRAWL_TELEMETRY'] === '0' ||
|
|
@@ -39,7 +28,6 @@ export const initPostHog = () => {
|
|
|
39
28
|
const isFirstRun = !config.get('telemetryUserId');
|
|
40
29
|
if (isFirstRun) {
|
|
41
30
|
config.set('telemetryUserId', `cli_${crypto.randomUUID()}`);
|
|
42
|
-
// One-time disclosure — telemetry is on by default and otherwise silent.
|
|
43
31
|
console.error(chalk.dim('ℹ Trawl CLI collects anonymous usage telemetry (no credentials or command arguments). ' +
|
|
44
32
|
'Opt out: trawl telemetry off, TRAWL_TELEMETRY=0, or DO_NOT_TRACK=1.'));
|
|
45
33
|
}
|
|
@@ -53,14 +41,6 @@ export const initPostHog = () => {
|
|
|
53
41
|
fetchRetryDelay: FETCH_RETRY_DELAY_MS,
|
|
54
42
|
});
|
|
55
43
|
};
|
|
56
|
-
/**
|
|
57
|
-
* Register the complete set of valid "<parent> <name>" telemetry tokens,
|
|
58
|
-
* derived from the actual commander command tree (see resolveCommandName /
|
|
59
|
-
* collectCommandNames in src/index.ts). captureCommand refuses any command
|
|
60
|
-
* string outside this set — defense in depth so free-form user input (argv
|
|
61
|
-
* operands, flag values) can never reach PostHog as the event name, even if a
|
|
62
|
-
* future change accidentally reintroduces argv-derived naming.
|
|
63
|
-
*/
|
|
64
44
|
export const registerAllowedCommands = (names) => {
|
|
65
45
|
allowedCommands = new Set(names);
|
|
66
46
|
};
|
|
@@ -83,12 +63,9 @@ export const captureCommand = (command, props = {}) => {
|
|
|
83
63
|
});
|
|
84
64
|
};
|
|
85
65
|
export const shutdown = async () => {
|
|
86
|
-
// Explicit ceiling — client.shutdown() otherwise defaults to a 30s internal
|
|
87
|
-
// race, which would undercut the whole point of capping request/retry time.
|
|
88
66
|
if (client)
|
|
89
67
|
await client.shutdown(SHUTDOWN_TIMEOUT_MS);
|
|
90
68
|
};
|
|
91
|
-
/** Reset singleton state — for testing only */
|
|
92
69
|
export const _resetForTesting = () => {
|
|
93
70
|
client = null;
|
|
94
71
|
distinctId = null;
|
package/dist/lib/prompt.js
CHANGED
|
@@ -12,27 +12,19 @@ export async function promptPassword(prompt) {
|
|
|
12
12
|
process.stdin.removeListener('data', onData);
|
|
13
13
|
};
|
|
14
14
|
const onData = (chunk) => {
|
|
15
|
-
// A stdin chunk is NOT a single keystroke. Terminals deliver a paste as
|
|
16
|
-
// one chunk (Windows Terminal always chunk-pastes), and a paste that
|
|
17
|
-
// ends in a newline embeds a literal \r mid-chunk. Iterate per code
|
|
18
|
-
// point so an embedded Enter finalizes the password instead of being
|
|
19
|
-
// appended to it and hanging for a second Enter. (#73)
|
|
20
15
|
for (const char of chunk) {
|
|
21
16
|
if (char === '\r' || char === '\n') {
|
|
22
17
|
restore();
|
|
23
18
|
process.stderr.write('\n');
|
|
24
19
|
resolve(password);
|
|
25
|
-
return;
|
|
20
|
+
return;
|
|
26
21
|
}
|
|
27
22
|
if (char === '') {
|
|
28
|
-
// Ctrl+C: restore the terminal and terminate the line before
|
|
29
|
-
// exiting so the shell prompt isn't left mid-line. 130 = 128+SIGINT.
|
|
30
23
|
restore();
|
|
31
24
|
process.stderr.write('\n');
|
|
32
25
|
process.exit(130);
|
|
33
26
|
}
|
|
34
27
|
if (char === '' || char === '\b') {
|
|
35
|
-
// Backspace
|
|
36
28
|
password = password.slice(0, -1);
|
|
37
29
|
}
|
|
38
30
|
else {
|
|
@@ -43,17 +35,6 @@ export async function promptPassword(prompt) {
|
|
|
43
35
|
process.stdin.on('data', onData);
|
|
44
36
|
}
|
|
45
37
|
else {
|
|
46
|
-
// Non-TTY fallback (e.g. piped input in CI). If stdin closes (EOF)
|
|
47
|
-
// before an answer arrives — e.g. `trawl login < /dev/null` — the
|
|
48
|
-
// `question` callback never fires; without a `close` handler the
|
|
49
|
-
// promise hangs forever and the process exits 0 without logging in.
|
|
50
|
-
// Reject loudly instead. (#68)
|
|
51
|
-
//
|
|
52
|
-
// No `output` is wired to the interface: readline echoes typed input
|
|
53
|
-
// whenever its output is a TTY, so `trawl login < creds` run from a real
|
|
54
|
-
// terminal would print the password. Omitting output (and terminal:false)
|
|
55
|
-
// guarantees the secret is never echoed. The prompt itself was already
|
|
56
|
-
// written to stderr above. (#73)
|
|
57
38
|
import('readline').then(({ createInterface }) => {
|
|
58
39
|
const rl = createInterface({ input: process.stdin, terminal: false });
|
|
59
40
|
let answered = false;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function assertSecureTransport(apiUrl: string): string | null;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const LOOPBACK_HOSTS = new Set(['127.0.0.1', '::1', '[::1]', 'localhost']);
|
|
2
|
+
export function assertSecureTransport(apiUrl) {
|
|
3
|
+
let url;
|
|
4
|
+
try {
|
|
5
|
+
url = new URL(apiUrl);
|
|
6
|
+
}
|
|
7
|
+
catch {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
if (url.protocol === 'https:')
|
|
11
|
+
return null;
|
|
12
|
+
if (LOOPBACK_HOSTS.has(url.hostname.toLowerCase()))
|
|
13
|
+
return null;
|
|
14
|
+
return `The configured API URL (${url.origin}) is not https: — a captured/uploaded browser session is a bearer-equivalent secret and this command refuses to send it in cleartext. An https:// API URL is accepted (trawl login --url https://…); so is any URL on 127.0.0.1, ::1, or localhost, for local testing.`;
|
|
15
|
+
}
|