jaitus 0.3.19 → 0.3.21
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/LICENSE +1 -1
- package/README.md +66 -30
- package/package.json +1 -1
- package/src/agent/loop.js +10 -11
- package/src/api.js +3 -3
- package/src/brand.js +233 -232
- package/src/commands/doctor.js +11 -10
- package/src/commands/login.js +3 -3
- package/src/commands/logout.js +1 -1
- package/src/commands/update.js +11 -2
- package/src/errors.js +10 -9
- package/src/index.js +44 -44
- package/src/logo-data.js +2 -2
- package/src/planView.js +89 -29
- package/src/skills.js +1 -1
- package/src/tui/entries.js +2 -2
- package/src/tui/theme.js +21 -21
- package/src/tui/wordmark.js +1 -1
- package/src/tui-ink.js +2941 -2921
- package/src/tui.js +2556 -2560
- package/src/ui.js +11 -11
package/src/brand.js
CHANGED
|
@@ -1,232 +1,233 @@
|
|
|
1
|
-
// ─── Jaitus terminal identity ─────────────────────────────────────────────────
|
|
2
|
-
// A coding session should look like a tool, not a launch screen. The product mark is
|
|
3
|
-
// intentionally one compact
|
|
4
|
-
// the right place for the detailed mark, while a terminal needs recognition without
|
|
5
|
-
// stealing the first screen from the work.
|
|
6
|
-
|
|
7
|
-
import { c, columns, colorLevel, unicode, visibleWidth } from './ui.js';
|
|
8
|
-
|
|
9
|
-
export function badge() {
|
|
10
|
-
// JAITUS_ASCII forces plain text so the mark never renders as tofu.
|
|
11
|
-
if (process.env.JAITUS_ASCII) return '[J]';
|
|
12
|
-
const level = colorLevel();
|
|
13
|
-
|
|
14
|
-
if (level >=
|
|
15
|
-
if (level
|
|
16
|
-
return '[J
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
"...▐███████...",
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"..........▐███",
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"███▌...████...",
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
//
|
|
73
|
-
//
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
' ###
|
|
77
|
-
' ### ##
|
|
78
|
-
' ###
|
|
79
|
-
'
|
|
80
|
-
' ## ### ## ## ##### ### ## ## ##',
|
|
81
|
-
'
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
' █
|
|
93
|
-
' █
|
|
94
|
-
'
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
'
|
|
99
|
-
' █
|
|
100
|
-
' █
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
*
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
'/
|
|
130
|
-
'/
|
|
131
|
-
'/
|
|
132
|
-
'/
|
|
133
|
-
'/
|
|
134
|
-
'/
|
|
135
|
-
'/
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
*
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
`
|
|
148
|
-
`
|
|
149
|
-
`
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
return [...meta.map((m) => ` ${m}`), ` Tip: ${startupTip()}`];
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
const
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
row(`
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
if (process.
|
|
181
|
-
|
|
182
|
-
//
|
|
183
|
-
//
|
|
184
|
-
//
|
|
185
|
-
//
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
process.stdout.write(
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
//
|
|
202
|
-
//
|
|
203
|
-
//
|
|
204
|
-
//
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
const
|
|
208
|
-
const
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
if (d
|
|
215
|
-
return c.
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
//
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
}
|
|
1
|
+
// ─── Jaitus terminal identity ─────────────────────────────────────────────────
|
|
2
|
+
// A coding session should look like a tool, not a launch screen. The product mark is
|
|
3
|
+
// intentionally one compact mint token beside the Jaitus name; the web logo remains
|
|
4
|
+
// the right place for the detailed mark, while a terminal needs recognition without
|
|
5
|
+
// stealing the first screen from the work.
|
|
6
|
+
|
|
7
|
+
import { c, columns, colorLevel, unicode, visibleWidth } from './ui.js';
|
|
8
|
+
|
|
9
|
+
export function badge() {
|
|
10
|
+
// JAITUS_ASCII forces plain text so the mark never renders as tofu.
|
|
11
|
+
if (process.env.JAITUS_ASCII) return '[J]';
|
|
12
|
+
const level = colorLevel();
|
|
13
|
+
// Dark ink on mint, like the logo: white on mint would fail contrast.
|
|
14
|
+
if (level >= 3) return '\x1b[48;2;61;242;176m\x1b[38;2;3;20;13m J \x1b[0m';
|
|
15
|
+
if (level >= 2) return '\x1b[48;5;85m\x1b[38;5;16m J \x1b[0m';
|
|
16
|
+
if (level === 1) return '\x1b[42m\x1b[30m J \x1b[0m';
|
|
17
|
+
return '[J]';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function wordmark(version = '') {
|
|
21
|
+
return `${badge()} ${c.brand(c.bold('JAITUS'))}${version ? ` ${c.dim(`v${version}`)}` : ''}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// The Jaitus J alone as mascot art — no disc, no block. Sampled once from
|
|
25
|
+
// the checked-in logo pixels (white-J bbox) into quadrant cells: hard-edged
|
|
26
|
+
// vertices and corners, single brand ink, small and condensed.
|
|
27
|
+
const MASCOT_ART = [
|
|
28
|
+
"..████▌.",
|
|
29
|
+
"......▟█",
|
|
30
|
+
"██..▟█▛▀",
|
|
31
|
+
"▀█████▌.",
|
|
32
|
+
];
|
|
33
|
+
// Full J for the banner showcase: same geometry, full height.
|
|
34
|
+
const MARK_LARGE = [
|
|
35
|
+
"...▐███████...",
|
|
36
|
+
"...▐███████...",
|
|
37
|
+
"...▝██████▛...",
|
|
38
|
+
"..........▐███",
|
|
39
|
+
"..........▐███",
|
|
40
|
+
"▄▄▄▖...▄▄▄▟▀▀▀",
|
|
41
|
+
"███▌...████...",
|
|
42
|
+
"███▌...████...",
|
|
43
|
+
"▜██▙▄▄▄████...",
|
|
44
|
+
".▀█████████...",
|
|
45
|
+
"..▝████████...",
|
|
46
|
+
];
|
|
47
|
+
/**
|
|
48
|
+
* The mascot J as raw rows, for the app view to ink itself.
|
|
49
|
+
*
|
|
50
|
+
* Deliberately used in preference to downsampling the real badge at small sizes. A
|
|
51
|
+
* 125×125 photograph reduced to eight cells is a mint blob — the white J inside it
|
|
52
|
+
* lands on about two pixels and disappears. These quadrant cells were drawn for this
|
|
53
|
+
* size, so the letterform survives. The photographic mark is for somewhere it has room.
|
|
54
|
+
*/
|
|
55
|
+
export function MASCOT_ROWS() {
|
|
56
|
+
return MASCOT_ART.map((row) => row.replace(/\./g, ' '));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Mascot rows in the brand ink (pad to equal width for text lockups).
|
|
60
|
+
* Dots in the grid are transparent cells, never printed. */
|
|
61
|
+
export function renderMascot() {
|
|
62
|
+
const clean = MASCOT_ART.map((r) => r.replace(/\./g, ' '));
|
|
63
|
+
const width = Math.max(...clean.map((r) => [...r].length));
|
|
64
|
+
return clean.map((r) => ` ${c.brand(r + ' '.repeat(Math.max(0, width - [...r].length)))}`);
|
|
65
|
+
}
|
|
66
|
+
/** Full-height mark for `banner`: the complete J geometry, same ink. */
|
|
67
|
+
export function renderMarkLarge() {
|
|
68
|
+
const clean = MARK_LARGE.map((r) => r.replace(/\./g, ' '));
|
|
69
|
+
const width = Math.max(...clean.map((r) => [...r].length));
|
|
70
|
+
return clean.map((r) => ` ${c.brand(r + ' '.repeat(Math.max(0, width - [...r].length)))}`);
|
|
71
|
+
}
|
|
72
|
+
// The JAITUS name as pure-ASCII block letters for terminals that render
|
|
73
|
+
// nothing else (classic conhost raster fonts). 7 rows, 45 columns, only `#`
|
|
74
|
+
// and spaces — the grand opening where block pixels would be tofu.
|
|
75
|
+
const ASCII_GRAND = [
|
|
76
|
+
' ### ### ##### ####### ## ## ######',
|
|
77
|
+
' ### ## ## ##### ####### ## ## ##',
|
|
78
|
+
' ### ## ## ##### ### ## ## ##',
|
|
79
|
+
' ### ####### ##### ### ## ## ######',
|
|
80
|
+
' ## ### ## ## ##### ### ## ## ##',
|
|
81
|
+
' ## ### ## ## ##### ### ## ## ##',
|
|
82
|
+
' ###### ## ## ##### ### ##### ######',
|
|
83
|
+
];
|
|
84
|
+
/** ASCII grand logo rows (unstyled plain text — color never reaches these terminals). */
|
|
85
|
+
export function renderAsciiGrand() {
|
|
86
|
+
return ASCII_GRAND.map((r) => ` ${r.replace(/\s+$/, '')}`);
|
|
87
|
+
}
|
|
88
|
+
// Fresh-session home mark in the opencode idiom: the name as 4-row block
|
|
89
|
+
// letters, left half muted, right half bright, centered — the whole welcome.
|
|
90
|
+
// ASCII terminals get the plain wordmark line instead of tofu.
|
|
91
|
+
const HOME_LOGO_LEFT = [
|
|
92
|
+
' █ ▀▀ █ ',
|
|
93
|
+
' █ █ █ ',
|
|
94
|
+
' █ █▀▀█ █ ',
|
|
95
|
+
'▀▀▀ ▀ █ █ ',
|
|
96
|
+
];
|
|
97
|
+
const HOME_LOGO_RIGHT = [
|
|
98
|
+
' █ █ █ ▀▀ ',
|
|
99
|
+
'███ █ █ █ ',
|
|
100
|
+
' █ █ █ ▀▀█',
|
|
101
|
+
' █ ▀▀▀▀ ▀▀█',
|
|
102
|
+
];
|
|
103
|
+
/**
|
|
104
|
+
* The same block letters as data, for the app view.
|
|
105
|
+
*
|
|
106
|
+
* The app renders them through a real per-character gradient, which cannot be done
|
|
107
|
+
* with a pre-coloured string: the escape codes would have to be shredded mid-frame.
|
|
108
|
+
* Kept here beside the art so the two surfaces can never drift apart.
|
|
109
|
+
*/
|
|
110
|
+
export function HOME_LOGO_ROWS() {
|
|
111
|
+
return HOME_LOGO_LEFT.map((left, i) => ({ left, right: HOME_LOGO_RIGHT[i] }));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Centered two-tone JAITUS block rows for a fresh fullscreen session.
|
|
115
|
+
* Both halves wear the brand mint (plain left, bold right) — the opencode
|
|
116
|
+
* idiom in Jaitus ink. A full reset closes each row so no attribute leaks. */
|
|
117
|
+
export function renderHomeLogo({ version = '', width = 80 } = {}) {
|
|
118
|
+
if (!unicode() || width < 40) {
|
|
119
|
+
const line = `JAITUS${version ? ` v${version}` : ''}`;
|
|
120
|
+
const pad = Math.max(0, Math.floor((width - visibleWidth(line)) / 2));
|
|
121
|
+
return [`${' '.repeat(pad)}${c.bold(line)}`];
|
|
122
|
+
}
|
|
123
|
+
const pad = Math.max(0, Math.floor((width - 29) / 2));
|
|
124
|
+
const end = colorLevel() > 0 ? '\x1b[0m' : '';
|
|
125
|
+
return HOME_LOGO_LEFT.map((l, i) => `${' '.repeat(pad)}${c.brand(l)} ${c.bold(c.brand(HOME_LOGO_RIGHT[i]))}${end}`);
|
|
126
|
+
}
|
|
127
|
+
/** Rotating startup tips — every one names a real command. */
|
|
128
|
+
const STARTUP_TIPS = [
|
|
129
|
+
'/research digs up sources with citations',
|
|
130
|
+
'/code-review --fix ships cleaner code',
|
|
131
|
+
'/rewind un-breaks the timeline',
|
|
132
|
+
'/btw asks off the record',
|
|
133
|
+
'/branch tries the other way',
|
|
134
|
+
'/test --fix repairs failures',
|
|
135
|
+
'/explain reads the code for you',
|
|
136
|
+
'/commit writes the message',
|
|
137
|
+
];
|
|
138
|
+
export function startupTip() {
|
|
139
|
+
const day = Math.floor(Date.now() / 86400000);
|
|
140
|
+
return STARTUP_TIPS[day % STARTUP_TIPS.length];
|
|
141
|
+
}
|
|
142
|
+
/** Compact classic open: a one-block session header. Lowercase wordmark,
|
|
143
|
+
* model, approval posture, directory, account — one block, pure ASCII,
|
|
144
|
+
* instant. Returns plain-text lines. */
|
|
145
|
+
export function renderClassicOpen({ version, model, approval, cwd, account, session, narrow }) {
|
|
146
|
+
const meta = [
|
|
147
|
+
`jaitus v${version}`,
|
|
148
|
+
`model ${model}`,
|
|
149
|
+
`approval ${approval}`,
|
|
150
|
+
`directory ${cwd}`,
|
|
151
|
+
...(account ? [`account ${account} - session ${session}`] : [`session ${session}`]),
|
|
152
|
+
];
|
|
153
|
+
if (narrow) return [...meta.map((m) => ` ${m}`), ` Tip: ${startupTip()}`];
|
|
154
|
+
return [...meta.map((m) => ` ${m}`), ` Tip: ${startupTip()}`];
|
|
155
|
+
}
|
|
156
|
+
export function logoCard(version = '') {
|
|
157
|
+
if (process.env.JAITUS_ASCII || !unicode()) {
|
|
158
|
+
const inner = 40;
|
|
159
|
+
const bar = ` +${'-'.repeat(inner)}+`;
|
|
160
|
+
const row = (text) => ` |${text}${' '.repeat(Math.max(0, inner - visibleWidth(text)))}|`;
|
|
161
|
+
return [
|
|
162
|
+
bar,
|
|
163
|
+
row(` ${c.brand(c.bold('J A I T U S'))}`),
|
|
164
|
+
row(` a coding agent in your terminal ${c.dim(`v${version}`)}`),
|
|
165
|
+
bar,
|
|
166
|
+
];
|
|
167
|
+
}
|
|
168
|
+
return [
|
|
169
|
+
...renderMarkLarge(),
|
|
170
|
+
` ${c.brand(c.bold('JAITUS'))} ${c.dim(`a coding agent in your terminal v${version}`)}`,
|
|
171
|
+
];
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function bannerLines({ version = '', width = columns() } = {}) {
|
|
175
|
+
const suffix = width >= 44 ? ` ${c.dim('coding agent')}` : '';
|
|
176
|
+
return [` ${wordmark(version)}${suffix}`];
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export async function banner(opts = {}) {
|
|
180
|
+
if (!process.stdout.isTTY && !opts.force) return;
|
|
181
|
+
if (process.env.JAITUS_NO_BANNER && !opts.force) return;
|
|
182
|
+
// `jaitus banner --image`: the README block-letter mark is ALWAYS printed,
|
|
183
|
+
// because a sixel-only path goes invisible on terminals that misreport the
|
|
184
|
+
// DA capability (seen on conhost: probe says yes, pixels never land, and the
|
|
185
|
+
// user sees the same quiet line as before). Real pixels go first when the
|
|
186
|
+
// probe positively confirms sixel, then the block letters guarantee ink.
|
|
187
|
+
if (opts.image) {
|
|
188
|
+
let sixelSeq = '';
|
|
189
|
+
try {
|
|
190
|
+
const { sixelSupported } = await import('./terminal.js');
|
|
191
|
+
if (sixelSupported()) {
|
|
192
|
+
const { markSixel } = await import('./sixel.js');
|
|
193
|
+
sixelSeq = markSixel({}) || '';
|
|
194
|
+
}
|
|
195
|
+
} catch { sixelSeq = ''; }
|
|
196
|
+
if (sixelSeq) process.stdout.write(`\n${sixelSeq}\n`);
|
|
197
|
+
for (const line of logoCard(opts.version || '')) process.stdout.write(`${line}\n`);
|
|
198
|
+
process.stdout.write('\n');
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
// One-shot wordmark shimmer on interactive start: a light sweep across the
|
|
202
|
+
// brand row (~600 ms), once, only when the terminal animates. Skipped for
|
|
203
|
+
// --image, non-TTY, reduced motion, and the /banner re-draw (opts.force).
|
|
204
|
+
// The sweep paints per-character from the ANSI-stripped text — spreading the
|
|
205
|
+
// styled string would shred escape sequences mid-frame.
|
|
206
|
+
if (!opts.image && !opts.force && process.stdout.isTTY && !process.env.JAITUS_NO_ANIMATION && colorLevel() > 0 && unicode()) {
|
|
207
|
+
const styled = bannerLines(opts);
|
|
208
|
+
const plainText = (styled[0] || '').replace(/\x1b\[[0-9;]*m/g, '');
|
|
209
|
+
const chars = [...plainText];
|
|
210
|
+
const sweep = async () => {
|
|
211
|
+
for (let pos = -3; pos <= chars.length + 3; pos++) {
|
|
212
|
+
const frame = chars.map((ch, idx) => {
|
|
213
|
+
const d = Math.abs(idx - pos);
|
|
214
|
+
if (d === 0) return c.bold(`\x1b[97m${ch}\x1b[0m`);
|
|
215
|
+
if (d <= 2) return c.shimmer(ch);
|
|
216
|
+
return c.brand(ch);
|
|
217
|
+
}).join('');
|
|
218
|
+
process.stdout.write(`\r${frame}`);
|
|
219
|
+
await new Promise((r) => setTimeout(r, 24));
|
|
220
|
+
}
|
|
221
|
+
process.stdout.write(`\r${styled.join('\n')}\n\n`);
|
|
222
|
+
};
|
|
223
|
+
try { await sweep(); } catch { process.stdout.write(`${styled.join('\n')}\n\n`); }
|
|
224
|
+
} else {
|
|
225
|
+
process.stdout.write(`${bannerLines(opts).join('\n')}\n\n`);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Kept as a small pure helper for the classic renderer and external integrations.
|
|
230
|
+
// It is deliberately not a welcome card.
|
|
231
|
+
export function welcomeLines({ version = '' } = {}) {
|
|
232
|
+
return [` ${wordmark(version)}`];
|
|
233
|
+
}
|
package/src/commands/doctor.js
CHANGED
|
@@ -14,6 +14,7 @@ import { c, out, table, heading, present, colorLevel, glyph } from '../ui.js';
|
|
|
14
14
|
import { endpoints, loadCredentials, loadSettings, HOME, version } from '../config.js';
|
|
15
15
|
import { signedIn, accessToken } from '../session.js';
|
|
16
16
|
import { checkRegistry, isNewer } from './update.js';
|
|
17
|
+
import { freeAllowance } from '../planView.js';
|
|
17
18
|
|
|
18
19
|
const TIMEOUT_MS = 8000;
|
|
19
20
|
|
|
@@ -77,9 +78,9 @@ export default async function doctor(argv = [], { json = false } = {}) {
|
|
|
77
78
|
}
|
|
78
79
|
}
|
|
79
80
|
|
|
80
|
-
// ── The
|
|
81
|
+
// ── The server ────────────────────────────────────────────────────────────────
|
|
81
82
|
const { baseUrl, siteUrl } = endpoints();
|
|
82
|
-
add('
|
|
83
|
+
add('Server', PASS, baseUrl);
|
|
83
84
|
|
|
84
85
|
let reachable = false;
|
|
85
86
|
try {
|
|
@@ -91,18 +92,18 @@ export default async function doctor(argv = [], { json = false } = {}) {
|
|
|
91
92
|
const body = await res.json().catch(() => ({}));
|
|
92
93
|
const ids = (body?.data || []).map((m) => m.id);
|
|
93
94
|
add('Reachable', PASS, `HTTP ${res.status}`);
|
|
94
|
-
add('Models', ids.length ? PASS : WARN, ids.length ? ids.join(', ') : '
|
|
95
|
-
ids.length ? null : 'Nothing to run against. This is a
|
|
95
|
+
add('Models', ids.length ? PASS : WARN, ids.length ? ids.join(', ') : 'Jaitus sent no model list',
|
|
96
|
+
ids.length ? null : 'Nothing to run against. This is a problem on the Jaitus side, not on your computer.');
|
|
96
97
|
} else if (res.status === 401 || res.status === 403) {
|
|
97
98
|
add('Reachable', PASS, `HTTP ${res.status}`);
|
|
98
99
|
add('Session accepted', FAIL, 'Jaitus did not accept this device\'s session',
|
|
99
100
|
'Run `jaitus login` again — this device may have been signed out from the website.');
|
|
100
101
|
} else {
|
|
101
|
-
add('Reachable', WARN, `HTTP ${res.status}`, '
|
|
102
|
+
add('Reachable', WARN, `HTTP ${res.status}`, 'Jaitus answered, but not with a model list.');
|
|
102
103
|
}
|
|
103
104
|
} catch (e) {
|
|
104
105
|
add('Reachable', FAIL, describeNetworkError(e, baseUrl),
|
|
105
|
-
'Check your connection, and whether a proxy or firewall is between you and
|
|
106
|
+
'Check your connection, and whether a proxy or firewall is between you and Jaitus.');
|
|
106
107
|
}
|
|
107
108
|
|
|
108
109
|
// ── The account ───────────────────────────────────────────────────────────────
|
|
@@ -112,11 +113,11 @@ export default async function doctor(argv = [], { json = false } = {}) {
|
|
|
112
113
|
const body = await res.json().catch(() => ({}));
|
|
113
114
|
const u = body.user || body.data || {};
|
|
114
115
|
if (res.ok && (u.email || u.id)) {
|
|
115
|
-
const plan = u.isPaid ? (u.planStatus?.name || 'paid') : `free · ${u.
|
|
116
|
+
const plan = u.isPaid ? (u.planStatus?.name || 'paid') : `free · ${freeAllowance(u).text} used`;
|
|
116
117
|
add('Account', PASS, `${u.email || 'signed in'} (${plan})`);
|
|
117
118
|
} else {
|
|
118
119
|
add('Account', WARN, body?.message || `HTTP ${res.status}`,
|
|
119
|
-
'
|
|
120
|
+
'You are signed in, but your account details did not load. Try again in a minute.');
|
|
120
121
|
}
|
|
121
122
|
} catch (e) {
|
|
122
123
|
add('Account', WARN, e.message);
|
|
@@ -126,7 +127,7 @@ export default async function doctor(argv = [], { json = false } = {}) {
|
|
|
126
127
|
// ── The terminal ──────────────────────────────────────────────────────────────
|
|
127
128
|
const settings = loadSettings();
|
|
128
129
|
add('Model', settings.model ? PASS : PASS,
|
|
129
|
-
settings.model || '
|
|
130
|
+
settings.model || 'automatic — Jaitus picks the model for each request');
|
|
130
131
|
add('Website', PASS, siteUrl);
|
|
131
132
|
|
|
132
133
|
// ── Is there a newer one ──────────────────────────────────────────────────────
|
|
@@ -155,7 +156,7 @@ export default async function doctor(argv = [], { json = false } = {}) {
|
|
|
155
156
|
// Labeled swatches: if brand and error reds are indistinguishable on this
|
|
156
157
|
// console, this row shows exactly which token renders wrong.
|
|
157
158
|
out('');
|
|
158
|
-
out(` ${c.dim('
|
|
159
|
+
out(` ${c.dim('colours:')} level ${colorLevel()} · TERM=${process.env.TERM || '?'}${process.env.WT_SESSION ? ' · WT' : ''}${process.env.TERM_PROGRAM ? ` · ${process.env.TERM_PROGRAM}` : ''} · theme ${loadSettings().theme || 'auto'}`);
|
|
159
160
|
out(` ${c.dim('brand')} ${c.brand('[Ja]')} ${c.dim('accent')} ${c.accent('[Te]')} ${c.dim('ok')} ${c.green('[✓]')} ${c.dim('warn')} ${c.yellow('[!]')} ${c.dim('error')} ${c.red('[✗]')}`);
|
|
160
161
|
if (failed.length || warned.length) {
|
|
161
162
|
out('');
|
package/src/commands/login.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// ─── jaitus login ───────────────────────────────────────────────────────────────
|
|
2
2
|
//
|
|
3
3
|
// The loopback-redirect flow from RFC 8252 (OAuth 2.0 for Native Apps) with PKCE
|
|
4
|
-
// (RFC 7636), which is what `gh auth login
|
|
4
|
+
// (RFC 7636), which is what `gh auth login` and the cloud CLIs use:
|
|
5
5
|
//
|
|
6
6
|
// 1. Start an HTTP server on a random port, bound to 127.0.0.1 only, and make a PKCE
|
|
7
7
|
// verifier that never leaves this process.
|
|
@@ -49,7 +49,7 @@ const PAGE = (title, body, accent) => `<!doctype html><meta charset="utf-8">
|
|
|
49
49
|
body{background:#0b0f16;color:#e6edf6} h1{color:#fff} p{color:#94a3b8}
|
|
50
50
|
}
|
|
51
51
|
</style>
|
|
52
|
-
<div class="card"><div class="dot">${accent === '#
|
|
52
|
+
<div class="card"><div class="dot">${accent === '#1fc48a' ? '✓' : '✕'}</div>
|
|
53
53
|
<h1>${title}</h1><p>${body}</p></div>`;
|
|
54
54
|
|
|
55
55
|
/** Wait for the browser to come back with a code. Resolves { code } or rejects. */
|
|
@@ -98,7 +98,7 @@ function awaitCallback(server, expectedState) {
|
|
|
98
98
|
return reject(new Error('No code returned.'));
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
finish(200, PAGE('
|
|
101
|
+
finish(200, PAGE("You're signed in to Jaitus", 'You can close this tab and go back to your terminal.', '#1fc48a'));
|
|
102
102
|
resolve({ code });
|
|
103
103
|
});
|
|
104
104
|
});
|
package/src/commands/logout.js
CHANGED
|
@@ -21,7 +21,7 @@ export default async function logout() {
|
|
|
21
21
|
ok('Signed out of this device.');
|
|
22
22
|
} else {
|
|
23
23
|
ok('Signed out here.');
|
|
24
|
-
warn(`Could not reach Jaitus to end the session on the server. It will expire on its own, or sign it out
|
|
24
|
+
warn(`Could not reach Jaitus to end the session on the server. It will expire on its own, or sign it out under Signed-in devices on your dashboard (${endpoints().siteUrl}/gateway).`);
|
|
25
25
|
}
|
|
26
26
|
return 0;
|
|
27
27
|
}
|
package/src/commands/update.js
CHANGED
|
@@ -83,13 +83,22 @@ export async function cachedLatest({ now = Date.now(), file = CHECK_FILE, check
|
|
|
83
83
|
return latest || null;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
/**
|
|
86
|
+
/**
|
|
87
|
+
* One line for the app to show when a newer version is out, or null. Only a global npm
|
|
88
|
+
* install (with auto-update left on) updates itself as it starts; every other copy is
|
|
89
|
+
* told the command that gets the new version, the same advice autoUpdate() prints.
|
|
90
|
+
*/
|
|
87
91
|
export async function updateNotice(opts = {}) {
|
|
88
92
|
if (!updateCheckEnabled(opts.env)) return null;
|
|
89
93
|
const current = opts.current || version();
|
|
90
94
|
const latest = await cachedLatest(opts).catch(() => null);
|
|
91
95
|
if (!latest || !isNewer(latest, current)) return null;
|
|
92
|
-
|
|
96
|
+
const head = `Jaitus v${latest} is out — you have v${current}.`;
|
|
97
|
+
const how = await (opts.method || (() => installMethod()))().catch(() => 'other');
|
|
98
|
+
const env = opts.env || process.env;
|
|
99
|
+
if (how === 'npm-global' && !env.JAITUS_NO_AUTOUPDATE) return `${head} Quit and start jaitus again: it updates itself as it starts.`;
|
|
100
|
+
if (how === 'npx') return `${head} Start it with npx jaitus@latest.`;
|
|
101
|
+
return `${head} Update it with npm install -g jaitus@latest.`;
|
|
93
102
|
}
|
|
94
103
|
|
|
95
104
|
function run(cmd, args, { capture = false } = {}) {
|