incremnt 0.1.0 → 0.1.2
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/package.json +4 -1
- package/src/format.js +330 -29
- package/src/lib.js +17 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "incremnt",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Command-line tool for querying your incremnt strength training data",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -13,5 +13,8 @@
|
|
|
13
13
|
"scripts": {
|
|
14
14
|
"test": "node --test",
|
|
15
15
|
"dev:sync-fixture": "node ./scripts/dev-sync-fixture-server.js"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"chalk": "^5.6.2"
|
|
16
19
|
}
|
|
17
20
|
}
|
package/src/format.js
CHANGED
|
@@ -1,44 +1,72 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
|
|
1
3
|
const shortMonths = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
2
4
|
const shortDays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
|
3
5
|
|
|
4
|
-
|
|
6
|
+
// --- Shared helpers ---
|
|
7
|
+
|
|
8
|
+
function parseDate(dateString) {
|
|
5
9
|
const date = new Date(dateString);
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
10
|
+
return Number.isNaN(date.getTime()) ? null : date;
|
|
11
|
+
}
|
|
9
12
|
|
|
10
|
-
|
|
13
|
+
function formatShortDate(dateString) {
|
|
14
|
+
const date = parseDate(dateString);
|
|
15
|
+
return date ? `${date.getDate()} ${shortMonths[date.getMonth()]}` : dateString;
|
|
11
16
|
}
|
|
12
17
|
|
|
13
18
|
function formatDayAndDate(dateString) {
|
|
14
|
-
const date =
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
19
|
+
const date = parseDate(dateString);
|
|
20
|
+
return date
|
|
21
|
+
? `${shortDays[date.getDay()]} ${date.getDate()} ${shortMonths[date.getMonth()]}`
|
|
22
|
+
: dateString;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function header(text) {
|
|
26
|
+
return chalk.bold(` ${text}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function dimDot() {
|
|
30
|
+
return chalk.dim(' \u00b7 ');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function formatWeight(weight) {
|
|
34
|
+
return chalk.bold.cyan(`${Number(weight).toFixed(1)} kg`);
|
|
35
|
+
}
|
|
18
36
|
|
|
19
|
-
|
|
37
|
+
function formatDuration(seconds) {
|
|
38
|
+
return `${Math.round(seconds / 60)} min`;
|
|
20
39
|
}
|
|
21
40
|
|
|
41
|
+
function keyValue(key, value, keyWidth = 12) {
|
|
42
|
+
return ` ${chalk.dim(key.padEnd(keyWidth))} ${chalk.bold(value)}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// --- Formatters ---
|
|
46
|
+
|
|
22
47
|
function formatRecords(payload) {
|
|
23
48
|
if (!Array.isArray(payload) || payload.length === 0) {
|
|
24
49
|
return 'No records found.';
|
|
25
50
|
}
|
|
26
51
|
|
|
27
52
|
const maxNameLength = Math.max(...payload.map((record) => record.exerciseName.length));
|
|
53
|
+
const lines = [header('PERSONAL RECORDS'), ''];
|
|
28
54
|
|
|
29
|
-
|
|
55
|
+
for (const record of payload) {
|
|
30
56
|
const name = record.exerciseName.padEnd(maxNameLength);
|
|
31
57
|
const date = formatShortDate(record.sessionDate);
|
|
32
58
|
const isBodyweight = Number(record.weight) === 0;
|
|
33
59
|
|
|
34
60
|
if (isBodyweight) {
|
|
35
61
|
const reps = `${record.reps} reps`.padStart(12);
|
|
36
|
-
|
|
62
|
+
lines.push(` ${name} ${chalk.bold.cyan(reps)} ${chalk.dim('BW')}${dimDot()}${chalk.dim(date)}`);
|
|
63
|
+
} else {
|
|
64
|
+
const weight = `${Number(record.weight).toFixed(1)} kg`.padStart(12);
|
|
65
|
+
lines.push(` ${name} ${chalk.bold.cyan(weight)} ${chalk.dim('e1RM')}${dimDot()}${chalk.dim(date)}`);
|
|
37
66
|
}
|
|
67
|
+
}
|
|
38
68
|
|
|
39
|
-
|
|
40
|
-
return `${name} ${weight} e1RM \u00b7 ${date}`;
|
|
41
|
-
}).join('\n');
|
|
69
|
+
return lines.join('\n');
|
|
42
70
|
}
|
|
43
71
|
|
|
44
72
|
function formatSessionInsights(payload) {
|
|
@@ -46,27 +74,300 @@ function formatSessionInsights(payload) {
|
|
|
46
74
|
return 'No sessions found.';
|
|
47
75
|
}
|
|
48
76
|
|
|
49
|
-
|
|
50
|
-
|
|
77
|
+
const lines = [header('RECENT SESSIONS'), ''];
|
|
78
|
+
const maxDateLength = Math.max(...payload.map((session) => formatDayAndDate(session.sessionDate).length));
|
|
79
|
+
|
|
80
|
+
for (const session of payload) {
|
|
81
|
+
const date = formatDayAndDate(session.sessionDate).padEnd(maxDateLength);
|
|
51
82
|
const dayName = session.dayName ?? 'Workout';
|
|
52
83
|
const exercises = `${session.exerciseCount ?? '?'} exercises`;
|
|
53
|
-
const duration = session.durationSeconds
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
84
|
+
const duration = session.durationSeconds ? formatDuration(session.durationSeconds) : '';
|
|
85
|
+
const suffix = duration
|
|
86
|
+
? `${dimDot()}${chalk.dim(exercises)}${dimDot()}${chalk.dim(duration)}`
|
|
87
|
+
: `${dimDot()}${chalk.dim(exercises)}`;
|
|
88
|
+
|
|
89
|
+
lines.push(` ${chalk.bold(date)} ${dayName}${suffix}`);
|
|
90
|
+
}
|
|
57
91
|
|
|
58
|
-
|
|
59
|
-
}).join('\n');
|
|
92
|
+
return lines.join('\n');
|
|
60
93
|
}
|
|
61
94
|
|
|
62
|
-
|
|
63
|
-
if (
|
|
64
|
-
return
|
|
95
|
+
function formatSessionShow(payload) {
|
|
96
|
+
if (!payload) {
|
|
97
|
+
return 'Session not found.';
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const date = formatDayAndDate(payload.sessionDate);
|
|
101
|
+
const lines = [` ${chalk.bold('SESSION')}${dimDot()}${date}`, ''];
|
|
102
|
+
|
|
103
|
+
if (payload.programName) {
|
|
104
|
+
lines.push(keyValue('Program', payload.programName));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (payload.dayName) {
|
|
108
|
+
const dayDetail = payload.programDayIndex != null
|
|
109
|
+
? `${payload.dayName} (Day ${payload.programDayIndex + 1})`
|
|
110
|
+
: payload.dayName;
|
|
111
|
+
lines.push(keyValue('Day', dayDetail));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (payload.historicalContext?.programWeekNumber) {
|
|
115
|
+
lines.push(keyValue('Week', String(payload.historicalContext.programWeekNumber)));
|
|
65
116
|
}
|
|
66
117
|
|
|
67
|
-
if (
|
|
68
|
-
|
|
118
|
+
if (payload.totalVolume) {
|
|
119
|
+
lines.push(keyValue('Volume', `${Number(payload.totalVolume).toLocaleString()} kg`));
|
|
69
120
|
}
|
|
70
121
|
|
|
71
|
-
|
|
122
|
+
if (payload.durationSeconds) {
|
|
123
|
+
lines.push(keyValue('Duration', formatDuration(payload.durationSeconds)));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (payload.effortScore != null) {
|
|
127
|
+
lines.push(keyValue('Effort', String(payload.effortScore)));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (payload.averageHeartRate != null || payload.maxHeartRate != null) {
|
|
131
|
+
const parts = [];
|
|
132
|
+
if (payload.averageHeartRate != null) {
|
|
133
|
+
parts.push(`${payload.averageHeartRate} avg`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (payload.maxHeartRate != null) {
|
|
137
|
+
parts.push(`${payload.maxHeartRate} max`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
lines.push(keyValue('HR', parts.join(chalk.dim(' \u00b7 '))));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (payload.activeCalories != null) {
|
|
144
|
+
lines.push(keyValue('Calories', `${payload.activeCalories} kcal`));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return lines.join('\n');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function formatExerciseHistory(payload) {
|
|
151
|
+
if (!Array.isArray(payload) || payload.length === 0) {
|
|
152
|
+
return 'No history found.';
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const exerciseName = payload[0]?.exerciseName ?? 'Exercise';
|
|
156
|
+
const lines = [` ${chalk.bold(exerciseName.toUpperCase())}${dimDot()}History`, ''];
|
|
157
|
+
const maxDateLength = Math.max(...payload.map((entry) => formatShortDate(entry.sessionDate).length));
|
|
158
|
+
|
|
159
|
+
for (const entry of payload) {
|
|
160
|
+
const date = formatShortDate(entry.sessionDate).padEnd(maxDateLength);
|
|
161
|
+
const weight = entry.weight > 0 ? `${formatWeight(entry.weight)} \u00d7 ${entry.reps}` : `${chalk.bold.cyan(`${entry.reps} reps`)}`;
|
|
162
|
+
const rir = entry.rir != null ? ` ${chalk.dim(`RIR ${entry.rir}`)}` : '';
|
|
163
|
+
lines.push(` ${chalk.dim(date)} ${weight}${rir}`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return lines.join('\n');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function formatProgramSummary(payload) {
|
|
170
|
+
if (!payload) {
|
|
171
|
+
return 'No active program.';
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const lines = [header('CURRENT PROGRAM'), ''];
|
|
175
|
+
|
|
176
|
+
lines.push(keyValue('Name', payload.programName));
|
|
177
|
+
lines.push(keyValue('Week', String(payload.currentWeek)));
|
|
178
|
+
|
|
179
|
+
if (payload.currentDayTitle) {
|
|
180
|
+
const nextDay = payload.currentDayIndex != null
|
|
181
|
+
? `${payload.currentDayTitle} (Day ${payload.currentDayIndex + 1})`
|
|
182
|
+
: payload.currentDayTitle;
|
|
183
|
+
lines.push(keyValue('Next', nextDay));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (payload.trainingWeekdays?.length > 0) {
|
|
187
|
+
const dayNames = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
|
188
|
+
const schedule = payload.trainingWeekdays.map((d) => dayNames[d] ?? d).join(', ');
|
|
189
|
+
lines.push(keyValue('Schedule', schedule));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
lines.push(keyValue('Cycles', `${payload.completedCyclesCount} completed`));
|
|
193
|
+
|
|
194
|
+
return lines.join('\n');
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function formatProgramList(payload) {
|
|
198
|
+
if (!Array.isArray(payload) || payload.length === 0) {
|
|
199
|
+
return 'No programs found.';
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const lines = [header('PROGRAMS'), ''];
|
|
203
|
+
|
|
204
|
+
for (const program of payload) {
|
|
205
|
+
const dot = program.isActive ? chalk.green('\u25cf') : chalk.dim('\u25cb');
|
|
206
|
+
const name = program.isActive ? chalk.bold(program.programName) : program.programName;
|
|
207
|
+
const week = `Week ${program.currentWeek}`;
|
|
208
|
+
const day = program.currentDayTitle ? `Day ${program.currentDayIndex + 1} (${program.currentDayTitle})` : `Day ${program.currentDayIndex + 1}`;
|
|
209
|
+
lines.push(` ${dot} ${name} ${chalk.dim(`${week}${' \u00b7 '}${day}`)}`);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return lines.join('\n');
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function formatPlannedVsActual(payload) {
|
|
216
|
+
if (!payload) {
|
|
217
|
+
return 'No comparison data found.';
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const date = formatShortDate(payload.sessionDate);
|
|
221
|
+
const dayTitle = payload.dayTitle ?? 'Workout';
|
|
222
|
+
const lines = [` ${chalk.bold('PLANNED vs ACTUAL')}${dimDot()}${dayTitle} (${date})`, ''];
|
|
223
|
+
|
|
224
|
+
for (const exercise of payload.exercises ?? []) {
|
|
225
|
+
lines.push(` ${chalk.bold(exercise.exerciseName)}`);
|
|
226
|
+
|
|
227
|
+
if (exercise.plannedSets.length > 0) {
|
|
228
|
+
const weights = [...new Set(exercise.plannedSets.map((s) => s.weight))];
|
|
229
|
+
const reps = [...new Set(exercise.plannedSets.map((s) => s.reps))];
|
|
230
|
+
const setCount = exercise.plannedSets.length;
|
|
231
|
+
const weightStr = weights.length === 1 ? `${weights[0]} kg` : `${Math.min(...weights)}-${Math.max(...weights)} kg`;
|
|
232
|
+
const repStr = reps.length === 1 ? `${setCount}\u00d7${reps[0]}` : `${setCount}\u00d7${reps.join('/')}`;
|
|
233
|
+
const rirStr = exercise.plannedRir != null ? ` ${chalk.dim(`RIR ${exercise.plannedRir}`)}` : '';
|
|
234
|
+
lines.push(` ${chalk.dim('Planned')} ${repStr} @ ${weightStr}${rirStr}`);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (exercise.actualSets.length > 0) {
|
|
238
|
+
const repsList = exercise.actualSets.map((s) => s.reps).join(', ');
|
|
239
|
+
const weights = [...new Set(exercise.actualSets.map((s) => s.weight))];
|
|
240
|
+
const weightStr = weights.length === 1 ? `${weights[0]} kg` : `${Math.min(...weights)}-${Math.max(...weights)} kg`;
|
|
241
|
+
const rirStr = exercise.actualRir != null ? ` ${chalk.dim(`RIR ${exercise.actualRir}`)}` : '';
|
|
242
|
+
lines.push(` ${chalk.dim('Actual')} ${repsList} @ ${weightStr}${rirStr}`);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
lines.push('');
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Remove trailing blank line
|
|
249
|
+
if (lines.at(-1) === '') {
|
|
250
|
+
lines.pop();
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return lines.join('\n');
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function formatWhyDidThisChange(payload) {
|
|
257
|
+
if (!payload) {
|
|
258
|
+
return 'No session context found.';
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const date = formatShortDate(payload.sessionDate);
|
|
262
|
+
const lines = [` ${chalk.bold('SESSION CONTEXT')}${dimDot()}${date}`, ''];
|
|
263
|
+
|
|
264
|
+
if (payload.programWeekNumber != null) {
|
|
265
|
+
lines.push(keyValue('Week', String(payload.programWeekNumber)));
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (payload.sessionIntent) {
|
|
269
|
+
lines.push(keyValue('Intent', payload.sessionIntent));
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (payload.programProgressionType) {
|
|
273
|
+
lines.push(keyValue('Progression', payload.programProgressionType));
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (payload.effortScore != null) {
|
|
277
|
+
lines.push(keyValue('Effort', String(payload.effortScore)));
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (payload.averageHeartRate != null || payload.maxHeartRate != null) {
|
|
281
|
+
const parts = [];
|
|
282
|
+
if (payload.averageHeartRate != null) {
|
|
283
|
+
parts.push(`${payload.averageHeartRate} avg`);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (payload.maxHeartRate != null) {
|
|
287
|
+
parts.push(`${payload.maxHeartRate} max`);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
lines.push(keyValue('HR', parts.join(chalk.dim(' \u00b7 '))));
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (payload.latestAdaptationSummary) {
|
|
294
|
+
lines.push(keyValue('Adaptation', payload.latestAdaptationSummary));
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const recommendations = payload.recommendations ?? {};
|
|
298
|
+
const recEntries = Object.entries(recommendations);
|
|
299
|
+
|
|
300
|
+
if (recEntries.length > 0) {
|
|
301
|
+
lines.push('');
|
|
302
|
+
lines.push(` ${chalk.bold('Recommendations')}`);
|
|
303
|
+
|
|
304
|
+
for (const [exerciseName, rec] of recEntries) {
|
|
305
|
+
const description = rec.kind === 'increaseWeight'
|
|
306
|
+
? `Increase weight by ${rec.amount} ${rec.unit ?? 'kg'}`
|
|
307
|
+
: rec.kind === 'maintainWeight'
|
|
308
|
+
? 'Maintain current weight'
|
|
309
|
+
: rec.kind ?? 'Adjust';
|
|
310
|
+
lines.push(` ${exerciseName.padEnd(20)} ${chalk.dim(description)}`);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return lines.join('\n');
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// --- Main export ---
|
|
318
|
+
|
|
319
|
+
export function formatHelp() {
|
|
320
|
+
const cmd = (name, desc) => ` ${chalk.bold(name.padEnd(38))} ${chalk.dim(desc)}`;
|
|
321
|
+
const lines = [
|
|
322
|
+
'',
|
|
323
|
+
header('INCREMNT') + dimDot() + chalk.dim('strength tracking CLI'),
|
|
324
|
+
'',
|
|
325
|
+
header('USAGE'),
|
|
326
|
+
` incremnt <command> [options]`,
|
|
327
|
+
'',
|
|
328
|
+
header('COMMANDS'),
|
|
329
|
+
cmd('sessions list', 'List recent sessions'),
|
|
330
|
+
cmd('sessions show --id <id>', 'Show session details'),
|
|
331
|
+
cmd('sessions compare --id <id>', 'Compare planned vs actual'),
|
|
332
|
+
cmd('sessions explain --id <id>', 'Explain session context'),
|
|
333
|
+
cmd('programs list', 'List all programs'),
|
|
334
|
+
cmd('programs current', 'Show active program'),
|
|
335
|
+
cmd('exercises history --name <name>', 'Exercise history'),
|
|
336
|
+
cmd('records', 'Personal records'),
|
|
337
|
+
'',
|
|
338
|
+
header('AUTH'),
|
|
339
|
+
cmd('login --base-url <url>', 'Sign in (device flow)'),
|
|
340
|
+
cmd('login --base-url <url> --token <t>', 'Sign in with token'),
|
|
341
|
+
cmd('login --snapshot <file>', 'Use local snapshot'),
|
|
342
|
+
cmd('login --session-file <file>', 'Import session file'),
|
|
343
|
+
cmd('logout', 'Clear session'),
|
|
344
|
+
'',
|
|
345
|
+
header('OTHER'),
|
|
346
|
+
cmd('status', 'Connection & auth info'),
|
|
347
|
+
cmd('contract', 'API contract info'),
|
|
348
|
+
cmd('--json', 'Force JSON output'),
|
|
349
|
+
cmd('--help', 'Show this help'),
|
|
350
|
+
'',
|
|
351
|
+
header('ALIASES'),
|
|
352
|
+
` ${chalk.dim('insights')} ${chalk.dim('→')} sessions list${dimDot()}${chalk.dim('prs')} ${chalk.dim('→')} records`,
|
|
353
|
+
` ${chalk.dim('history')} ${chalk.dim('→')} exercises history${dimDot()}${chalk.dim('program')} ${chalk.dim('→')} programs current`,
|
|
354
|
+
''
|
|
355
|
+
];
|
|
356
|
+
|
|
357
|
+
return lines.join('\n');
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
export function formatPretty(command, payload) {
|
|
361
|
+
const formatter = {
|
|
362
|
+
records: formatRecords,
|
|
363
|
+
'session-insights': formatSessionInsights,
|
|
364
|
+
'session-show': formatSessionShow,
|
|
365
|
+
'exercise-history': formatExerciseHistory,
|
|
366
|
+
'program-summary': formatProgramSummary,
|
|
367
|
+
'program-list': formatProgramList,
|
|
368
|
+
'planned-vs-actual': formatPlannedVsActual,
|
|
369
|
+
'why-did-this-change': formatWhyDidThisChange
|
|
370
|
+
}[command];
|
|
371
|
+
|
|
372
|
+
return formatter ? formatter(payload) : null;
|
|
72
373
|
}
|
package/src/lib.js
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
} from './auth.js';
|
|
12
12
|
import { clearSessionState, isSessionExpired, readSessionState, resolveConfigDir } from './state.js';
|
|
13
13
|
import { createTransport } from './transport.js';
|
|
14
|
-
import { formatPretty } from './format.js';
|
|
14
|
+
import { formatPretty, formatHelp } from './format.js';
|
|
15
15
|
|
|
16
16
|
function parseArgs(argv) {
|
|
17
17
|
const commandTokens = [];
|
|
@@ -68,9 +68,9 @@ export async function runCli(argv, stdout, stderr) {
|
|
|
68
68
|
explain: 'why-did-this-change'
|
|
69
69
|
})[command] ?? command;
|
|
70
70
|
|
|
71
|
-
if (!command) {
|
|
72
|
-
|
|
73
|
-
return
|
|
71
|
+
if (!command || options.help) {
|
|
72
|
+
stdout.write(`${formatHelp()}\n`);
|
|
73
|
+
return 0;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
const sessionState = await readSessionState();
|
|
@@ -160,7 +160,11 @@ export async function runCli(argv, stdout, stderr) {
|
|
|
160
160
|
} else if (providers.length === 1 && auth?.providers?.google?.configured) {
|
|
161
161
|
verificationUrl = `${baseUrlNormalized}/auth/google/start?userCode=${userCodeParam}`;
|
|
162
162
|
} else {
|
|
163
|
-
|
|
163
|
+
const path = challenge.verificationUri ?? '/auth/device/approve';
|
|
164
|
+
if (!/^\/[a-zA-Z0-9\-._~/]+$/.test(path)) {
|
|
165
|
+
throw new Error(`Server returned an invalid verificationUri: ${path}`);
|
|
166
|
+
}
|
|
167
|
+
verificationUrl = `${baseUrlNormalized}${path}?userCode=${userCodeParam}`;
|
|
164
168
|
}
|
|
165
169
|
|
|
166
170
|
stderr.write('Signing in...\n');
|
|
@@ -266,8 +270,14 @@ export async function runCli(argv, stdout, stderr) {
|
|
|
266
270
|
|
|
267
271
|
try {
|
|
268
272
|
const payload = await transport.executeReadCommand(normalizedCommand, options);
|
|
269
|
-
const
|
|
270
|
-
|
|
273
|
+
const wantJson = options.json || !(stdout.isTTY ?? false);
|
|
274
|
+
if (wantJson) {
|
|
275
|
+
stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
276
|
+
} else {
|
|
277
|
+
const pretty = formatPretty(normalizedCommand, payload);
|
|
278
|
+
stdout.write(`${pretty ?? JSON.stringify(payload, null, 2)}\n`);
|
|
279
|
+
}
|
|
280
|
+
|
|
271
281
|
return 0;
|
|
272
282
|
} catch (error) {
|
|
273
283
|
stderr.write(`${error.message}\n`);
|