@nickchristensen/cliftin 3.0.0 → 4.1.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/README.md CHANGED
@@ -46,6 +46,11 @@ USAGE
46
46
  * [`cliftin help [COMMAND]`](#cliftin-help-command)
47
47
  * [`cliftin programs list`](#cliftin-programs-list)
48
48
  * [`cliftin programs show [SELECTOR]`](#cliftin-programs-show-selector)
49
+ * [`cliftin routines from-workout [WORKOUTID]`](#cliftin-routines-from-workout-workoutid)
50
+ * [`cliftin routines latest`](#cliftin-routines-latest)
51
+ * [`cliftin routines list`](#cliftin-routines-list)
52
+ * [`cliftin routines next`](#cliftin-routines-next)
53
+ * [`cliftin routines show SELECTOR`](#cliftin-routines-show-selector)
49
54
  * [`cliftin workouts list`](#cliftin-workouts-list)
50
55
  * [`cliftin workouts next`](#cliftin-workouts-next)
51
56
  * [`cliftin workouts show [WORKOUTID]`](#cliftin-workouts-show-workoutid)
@@ -73,7 +78,7 @@ DESCRIPTION
73
78
  List exercises
74
79
  ```
75
80
 
76
- _See code: [src/commands/exercises/list.ts](https://github.com/nickchristensen/cliftin/blob/v3.0.0/src/commands/exercises/list.ts)_
81
+ _See code: [src/commands/exercises/list.ts](https://github.com/nickchristensen/cliftin/blob/v4.1.0/src/commands/exercises/list.ts)_
77
82
 
78
83
  ## `cliftin exercises show SELECTOR`
79
84
 
@@ -82,8 +87,8 @@ Show one exercise detail and history
82
87
  ```
83
88
  USAGE
84
89
  $ cliftin exercises show SELECTOR [--json] [--all | --limit <value>] [--from <value>] [--max-reps <value>]
85
- [--max-weight <value>] [--min-reps <value>] [--min-weight <value>] [--program <value>] [--routine <value>] [--to
86
- <value>]
90
+ [--max-weight <value>] [--min-reps <value>] [--min-weight <value>] [--no-warmup] [--program <value>] [--routine
91
+ <value>] [--to <value>]
87
92
 
88
93
  ARGUMENTS
89
94
  SELECTOR exercise id or name
@@ -96,6 +101,7 @@ FLAGS
96
101
  --max-weight=<value> History max top weight
97
102
  --min-reps=<value> History min top reps
98
103
  --min-weight=<value> History min top weight
104
+ --no-warmup Hide warmup sets from output
99
105
  --program=<value> History filter by program id or name
100
106
  --routine=<value> History filter by routine id or name
101
107
  --to=<value> History end date YYYY-MM-DD
@@ -107,7 +113,7 @@ DESCRIPTION
107
113
  Show one exercise detail and history
108
114
  ```
109
115
 
110
- _See code: [src/commands/exercises/show.ts](https://github.com/nickchristensen/cliftin/blob/v3.0.0/src/commands/exercises/show.ts)_
116
+ _See code: [src/commands/exercises/show.ts](https://github.com/nickchristensen/cliftin/blob/v4.1.0/src/commands/exercises/show.ts)_
111
117
 
112
118
  ## `cliftin help [COMMAND]`
113
119
 
@@ -144,7 +150,7 @@ DESCRIPTION
144
150
  List programs
145
151
  ```
146
152
 
147
- _See code: [src/commands/programs/list.ts](https://github.com/nickchristensen/cliftin/blob/v3.0.0/src/commands/programs/list.ts)_
153
+ _See code: [src/commands/programs/list.ts](https://github.com/nickchristensen/cliftin/blob/v4.1.0/src/commands/programs/list.ts)_
148
154
 
149
155
  ## `cliftin programs show [SELECTOR]`
150
156
 
@@ -164,7 +170,103 @@ DESCRIPTION
164
170
  Show one program hierarchy
165
171
  ```
166
172
 
167
- _See code: [src/commands/programs/show.ts](https://github.com/nickchristensen/cliftin/blob/v3.0.0/src/commands/programs/show.ts)_
173
+ _See code: [src/commands/programs/show.ts](https://github.com/nickchristensen/cliftin/blob/v4.1.0/src/commands/programs/show.ts)_
174
+
175
+ ## `cliftin routines from-workout [WORKOUTID]`
176
+
177
+ Show the planned routine for a completed workout
178
+
179
+ ```
180
+ USAGE
181
+ $ cliftin routines from-workout [WORKOUTID] [--json]
182
+
183
+ ARGUMENTS
184
+ [WORKOUTID] workout id (default: latest workout)
185
+
186
+ GLOBAL FLAGS
187
+ --json Format output as json.
188
+
189
+ DESCRIPTION
190
+ Show the planned routine for a completed workout
191
+ ```
192
+
193
+ _See code: [src/commands/routines/from-workout.ts](https://github.com/nickchristensen/cliftin/blob/v4.1.0/src/commands/routines/from-workout.ts)_
194
+
195
+ ## `cliftin routines latest`
196
+
197
+ Show the planned routine for the latest workout
198
+
199
+ ```
200
+ USAGE
201
+ $ cliftin routines latest [--json]
202
+
203
+ GLOBAL FLAGS
204
+ --json Format output as json.
205
+
206
+ DESCRIPTION
207
+ Show the planned routine for the latest workout
208
+ ```
209
+
210
+ _See code: [src/commands/routines/latest.ts](https://github.com/nickchristensen/cliftin/blob/v4.1.0/src/commands/routines/latest.ts)_
211
+
212
+ ## `cliftin routines list`
213
+
214
+ List planned routines
215
+
216
+ ```
217
+ USAGE
218
+ $ cliftin routines list [--json] [--name <value>] [--program <value>] [--week <value>]
219
+
220
+ FLAGS
221
+ --name=<value> Filter by routine name contains
222
+ --program=<value> Filter by program id or name
223
+ --week=<value> Filter by week number
224
+
225
+ GLOBAL FLAGS
226
+ --json Format output as json.
227
+
228
+ DESCRIPTION
229
+ List planned routines
230
+ ```
231
+
232
+ _See code: [src/commands/routines/list.ts](https://github.com/nickchristensen/cliftin/blob/v4.1.0/src/commands/routines/list.ts)_
233
+
234
+ ## `cliftin routines next`
235
+
236
+ Show the up-next routine from the active program
237
+
238
+ ```
239
+ USAGE
240
+ $ cliftin routines next [--json]
241
+
242
+ GLOBAL FLAGS
243
+ --json Format output as json.
244
+
245
+ DESCRIPTION
246
+ Show the up-next routine from the active program
247
+ ```
248
+
249
+ _See code: [src/commands/routines/next.ts](https://github.com/nickchristensen/cliftin/blob/v4.1.0/src/commands/routines/next.ts)_
250
+
251
+ ## `cliftin routines show SELECTOR`
252
+
253
+ Show one planned routine
254
+
255
+ ```
256
+ USAGE
257
+ $ cliftin routines show SELECTOR [--json]
258
+
259
+ ARGUMENTS
260
+ SELECTOR routine id or name
261
+
262
+ GLOBAL FLAGS
263
+ --json Format output as json.
264
+
265
+ DESCRIPTION
266
+ Show one planned routine
267
+ ```
268
+
269
+ _See code: [src/commands/routines/show.ts](https://github.com/nickchristensen/cliftin/blob/v4.1.0/src/commands/routines/show.ts)_
168
270
 
169
271
  ## `cliftin workouts list`
170
272
 
@@ -191,11 +293,11 @@ DESCRIPTION
191
293
  List workouts
192
294
  ```
193
295
 
194
- _See code: [src/commands/workouts/list.ts](https://github.com/nickchristensen/cliftin/blob/v3.0.0/src/commands/workouts/list.ts)_
296
+ _See code: [src/commands/workouts/list.ts](https://github.com/nickchristensen/cliftin/blob/v4.1.0/src/commands/workouts/list.ts)_
195
297
 
196
298
  ## `cliftin workouts next`
197
299
 
198
- Show the up-next routine from the active program
300
+ Redirect to routines next
199
301
 
200
302
  ```
201
303
  USAGE
@@ -205,10 +307,10 @@ GLOBAL FLAGS
205
307
  --json Format output as json.
206
308
 
207
309
  DESCRIPTION
208
- Show the up-next routine from the active program
310
+ Redirect to routines next
209
311
  ```
210
312
 
211
- _See code: [src/commands/workouts/next.ts](https://github.com/nickchristensen/cliftin/blob/v3.0.0/src/commands/workouts/next.ts)_
313
+ _See code: [src/commands/workouts/next.ts](https://github.com/nickchristensen/cliftin/blob/v4.1.0/src/commands/workouts/next.ts)_
212
314
 
213
315
  ## `cliftin workouts show [WORKOUTID]`
214
316
 
@@ -216,11 +318,14 @@ Show one workout with exercises and sets
216
318
 
217
319
  ```
218
320
  USAGE
219
- $ cliftin workouts show [WORKOUTID] [--json]
321
+ $ cliftin workouts show [WORKOUTID] [--json] [--no-warmup]
220
322
 
221
323
  ARGUMENTS
222
324
  [WORKOUTID] workout id (default: latest workout)
223
325
 
326
+ FLAGS
327
+ --no-warmup Hide warmup sets from output
328
+
224
329
  GLOBAL FLAGS
225
330
  --json Format output as json.
226
331
 
@@ -228,5 +333,5 @@ DESCRIPTION
228
333
  Show one workout with exercises and sets
229
334
  ```
230
335
 
231
- _See code: [src/commands/workouts/show.ts](https://github.com/nickchristensen/cliftin/blob/v3.0.0/src/commands/workouts/show.ts)_
336
+ _See code: [src/commands/workouts/show.ts](https://github.com/nickchristensen/cliftin/blob/v4.1.0/src/commands/workouts/show.ts)_
232
337
  <!-- commandsstop -->
@@ -13,6 +13,7 @@ export default class ExercisesShow extends Command {
13
13
  'max-weight': import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
14
  'min-reps': import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
15
15
  'min-weight': import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
16
+ 'no-warmup': import("@oclif/core/interfaces").BooleanFlag<boolean>;
16
17
  program: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
17
18
  routine: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
18
19
  to: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
@@ -40,6 +40,7 @@ export default class ExercisesShow extends Command {
40
40
  'max-weight': Flags.integer({ description: 'History max top weight' }),
41
41
  'min-reps': Flags.integer({ description: 'History min top reps' }),
42
42
  'min-weight': Flags.integer({ description: 'History min top weight' }),
43
+ 'no-warmup': Flags.boolean({ description: 'Hide warmup sets from output' }),
43
44
  program: Flags.string({ description: 'History filter by program id or name' }),
44
45
  routine: Flags.string({ description: 'History filter by routine id or name' }),
45
46
  to: Flags.string({ description: 'History end date YYYY-MM-DD' }),
@@ -58,7 +59,9 @@ export default class ExercisesShow extends Command {
58
59
  const historyUnitPreference = await resolveExerciseWeightUnit(context.db, exerciseId);
59
60
  const history = (await getExerciseHistoryWithSetsRows(context.db, exerciseId, historyFilters)).map((row) => ({
60
61
  ...row,
61
- sets: row.sets.map((set) => ({
62
+ sets: row.sets
63
+ .filter((set) => (parsedFlags['no-warmup'] ? !set.isWarmup : true))
64
+ .map((set) => ({
62
65
  ...set,
63
66
  weight: withWeightUnit(set.weight, historyUnitPreference),
64
67
  })),
@@ -92,7 +95,7 @@ export default class ExercisesShow extends Command {
92
95
  this.log(`Program: ${lastPerformedSnapshot.workout.program ?? 'n/a'}`);
93
96
  this.log(`Date: ${formatWorkoutDate(lastPerformedSnapshot.workout.date)}`);
94
97
  this.log('');
95
- this.log(renderTable(lastPerformedSnapshot.exercise.sets.map((set) => ({
98
+ this.log(renderTable(lastPerformedSnapshot.exercise.sets.filter((set) => (parsedFlags['no-warmup'] ? !set.isWarmup : true)).map((set) => ({
96
99
  id: set.id,
97
100
  isWarmup: set.isWarmup,
98
101
  reps: set.reps,
@@ -0,0 +1,9 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class RoutinesFromWorkout extends Command {
3
+ static args: {
4
+ workoutId: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
5
+ };
6
+ static description: string;
7
+ static enableJsonFlag: boolean;
8
+ run(): Promise<unknown | void>;
9
+ }
@@ -0,0 +1,51 @@
1
+ import { Args, Command } from '@oclif/core';
2
+ import { closeDb, openDb } from '../../lib/db.js';
3
+ import { toJsonErrorPayload } from '../../lib/json-error.js';
4
+ import { serializeRoutineDetailWithWeightUnits } from '../../lib/json-weight.js';
5
+ import { renderTable } from '../../lib/output.js';
6
+ import { getLatestRoutineDetail, getRoutineDetailFromWorkout } from '../../lib/repositories/routines.js';
7
+ import { buildRoutineRows } from '../../lib/routine-output.js';
8
+ import { resolveProgramWeightUnit, weightUnitLabel } from '../../lib/units.js';
9
+ export default class RoutinesFromWorkout extends Command {
10
+ static args = {
11
+ workoutId: Args.string({
12
+ description: 'workout id (default: latest workout)',
13
+ ignoreStdin: true,
14
+ required: false,
15
+ }),
16
+ };
17
+ static description = 'Show the planned routine for a completed workout';
18
+ static enableJsonFlag = true;
19
+ async run() {
20
+ const { args } = await this.parse(RoutinesFromWorkout);
21
+ const context = openDb();
22
+ try {
23
+ if (args.workoutId !== undefined && !/^\d+$/.test(args.workoutId)) {
24
+ throw new Error('Workout id must be numeric.');
25
+ }
26
+ const detail = args.workoutId
27
+ ? await getRoutineDetailFromWorkout(context.db, Number(args.workoutId))
28
+ : await getLatestRoutineDetail(context.db);
29
+ const unitPreference = await resolveProgramWeightUnit(context.db, detail.program.id);
30
+ const unitLabel = weightUnitLabel(unitPreference);
31
+ if (this.jsonEnabled()) {
32
+ return serializeRoutineDetailWithWeightUnits(detail, unitPreference);
33
+ }
34
+ this.log(`[${detail.routine.id}] ${detail.routine.name ?? 'Routine'}`);
35
+ this.log(`Program: ${detail.program.name}`);
36
+ this.log(`Week: ${detail.week.number}`);
37
+ if (detail.workout)
38
+ this.log(`Workout: [${detail.workout.id}] ${detail.workout.routine ?? 'Workout'}`);
39
+ this.log('');
40
+ this.log(renderTable(buildRoutineRows(detail.routine, unitLabel)).replace(/^\n+/, ''));
41
+ }
42
+ catch (error) {
43
+ if (this.jsonEnabled())
44
+ return toJsonErrorPayload(error);
45
+ throw error;
46
+ }
47
+ finally {
48
+ await closeDb(context);
49
+ }
50
+ }
51
+ }
@@ -0,0 +1,6 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class RoutinesLatest extends Command {
3
+ static description: string;
4
+ static enableJsonFlag: boolean;
5
+ run(): Promise<unknown | void>;
6
+ }
@@ -0,0 +1,39 @@
1
+ import { Command } from '@oclif/core';
2
+ import { closeDb, openDb } from '../../lib/db.js';
3
+ import { toJsonErrorPayload } from '../../lib/json-error.js';
4
+ import { serializeRoutineDetailWithWeightUnits } from '../../lib/json-weight.js';
5
+ import { renderTable } from '../../lib/output.js';
6
+ import { getLatestRoutineDetail } from '../../lib/repositories/routines.js';
7
+ import { buildRoutineRows } from '../../lib/routine-output.js';
8
+ import { resolveProgramWeightUnit, weightUnitLabel } from '../../lib/units.js';
9
+ export default class RoutinesLatest extends Command {
10
+ static description = 'Show the planned routine for the latest workout';
11
+ static enableJsonFlag = true;
12
+ async run() {
13
+ await this.parse(RoutinesLatest);
14
+ const context = openDb();
15
+ try {
16
+ const detail = await getLatestRoutineDetail(context.db);
17
+ const unitPreference = await resolveProgramWeightUnit(context.db, detail.program.id);
18
+ const unitLabel = weightUnitLabel(unitPreference);
19
+ if (this.jsonEnabled()) {
20
+ return serializeRoutineDetailWithWeightUnits(detail, unitPreference);
21
+ }
22
+ this.log(`[${detail.routine.id}] ${detail.routine.name ?? 'Routine'}`);
23
+ this.log(`Program: ${detail.program.name}`);
24
+ this.log(`Week: ${detail.week.number}`);
25
+ if (detail.workout)
26
+ this.log(`Workout: [${detail.workout.id}] ${detail.workout.routine ?? 'Workout'}`);
27
+ this.log('');
28
+ this.log(renderTable(buildRoutineRows(detail.routine, unitLabel)).replace(/^\n+/, ''));
29
+ }
30
+ catch (error) {
31
+ if (this.jsonEnabled())
32
+ return toJsonErrorPayload(error);
33
+ throw error;
34
+ }
35
+ finally {
36
+ await closeDb(context);
37
+ }
38
+ }
39
+ }
@@ -0,0 +1,11 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class RoutinesList extends Command {
3
+ static description: string;
4
+ static enableJsonFlag: boolean;
5
+ static flags: {
6
+ name: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
7
+ program: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
+ week: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
+ };
10
+ run(): Promise<unknown | void>;
11
+ }
@@ -0,0 +1,40 @@
1
+ import { Command, Flags } from '@oclif/core';
2
+ import { closeDb, openDb } from '../../lib/db.js';
3
+ import { renderTable } from '../../lib/output.js';
4
+ import { listRoutines } from '../../lib/repositories/routines.js';
5
+ export default class RoutinesList extends Command {
6
+ static description = 'List planned routines';
7
+ static enableJsonFlag = true;
8
+ static flags = {
9
+ name: Flags.string({ description: 'Filter by routine name contains' }),
10
+ program: Flags.string({ description: 'Filter by program id or name' }),
11
+ week: Flags.integer({ description: 'Filter by week number' }),
12
+ };
13
+ async run() {
14
+ const { flags } = await this.parse(RoutinesList);
15
+ const context = openDb();
16
+ try {
17
+ const routines = await listRoutines(context.db, {
18
+ name: flags.name,
19
+ program: flags.program,
20
+ week: flags.week,
21
+ });
22
+ if (this.jsonEnabled()) {
23
+ return routines.map(({ id, ...routine }) => ({
24
+ ...routine,
25
+ routineId: id,
26
+ }));
27
+ }
28
+ this.log(renderTable(routines.map((routine) => ({
29
+ id: routine.id,
30
+ isNext: routine.isNext,
31
+ name: routine.name,
32
+ program: routine.program,
33
+ week: routine.week,
34
+ }))));
35
+ }
36
+ finally {
37
+ await closeDb(context);
38
+ }
39
+ }
40
+ }
@@ -0,0 +1,6 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class RoutinesNext extends Command {
3
+ static description: string;
4
+ static enableJsonFlag: boolean;
5
+ run(): Promise<unknown | void>;
6
+ }
@@ -0,0 +1,37 @@
1
+ import { Command } from '@oclif/core';
2
+ import { closeDb, openDb } from '../../lib/db.js';
3
+ import { toJsonErrorPayload } from '../../lib/json-error.js';
4
+ import { serializeRoutineDetailWithWeightUnits } from '../../lib/json-weight.js';
5
+ import { renderTable } from '../../lib/output.js';
6
+ import { getNextRoutineDetail } from '../../lib/repositories/routines.js';
7
+ import { buildRoutineRows } from '../../lib/routine-output.js';
8
+ import { resolveProgramWeightUnit, weightUnitLabel } from '../../lib/units.js';
9
+ export default class RoutinesNext extends Command {
10
+ static description = 'Show the up-next routine from the active program';
11
+ static enableJsonFlag = true;
12
+ async run() {
13
+ await this.parse(RoutinesNext);
14
+ const context = openDb();
15
+ try {
16
+ const detail = await getNextRoutineDetail(context.db);
17
+ const unitPreference = await resolveProgramWeightUnit(context.db, detail.program.id);
18
+ const unitLabel = weightUnitLabel(unitPreference);
19
+ if (this.jsonEnabled()) {
20
+ return serializeRoutineDetailWithWeightUnits(detail, unitPreference);
21
+ }
22
+ this.log(`[${detail.routine.id}] ${detail.routine.name ?? 'Routine'}`);
23
+ this.log(`Program: ${detail.program.name}`);
24
+ this.log(`Week: ${detail.week.number}`);
25
+ this.log('');
26
+ this.log(renderTable(buildRoutineRows(detail.routine, unitLabel)).replace(/^\n+/, ''));
27
+ }
28
+ catch (error) {
29
+ if (this.jsonEnabled())
30
+ return toJsonErrorPayload(error);
31
+ throw error;
32
+ }
33
+ finally {
34
+ await closeDb(context);
35
+ }
36
+ }
37
+ }
@@ -0,0 +1,9 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class RoutinesShow extends Command {
3
+ static args: {
4
+ selector: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
5
+ };
6
+ static description: string;
7
+ static enableJsonFlag: boolean;
8
+ run(): Promise<unknown | void>;
9
+ }
@@ -0,0 +1,34 @@
1
+ import { Args, Command } from '@oclif/core';
2
+ import { closeDb, openDb } from '../../lib/db.js';
3
+ import { serializeRoutineDetailWithWeightUnits } from '../../lib/json-weight.js';
4
+ import { renderTable } from '../../lib/output.js';
5
+ import { getRoutineDetail } from '../../lib/repositories/routines.js';
6
+ import { buildRoutineRows } from '../../lib/routine-output.js';
7
+ import { resolveProgramWeightUnit, weightUnitLabel } from '../../lib/units.js';
8
+ export default class RoutinesShow extends Command {
9
+ static args = {
10
+ selector: Args.string({ description: 'routine id or name', ignoreStdin: true, required: true }),
11
+ };
12
+ static description = 'Show one planned routine';
13
+ static enableJsonFlag = true;
14
+ async run() {
15
+ const { args } = await this.parse(RoutinesShow);
16
+ const context = openDb();
17
+ try {
18
+ const detail = await getRoutineDetail(context.db, args.selector);
19
+ const unitPreference = await resolveProgramWeightUnit(context.db, detail.program.id);
20
+ const unitLabel = weightUnitLabel(unitPreference);
21
+ if (this.jsonEnabled()) {
22
+ return serializeRoutineDetailWithWeightUnits(detail, unitPreference);
23
+ }
24
+ this.log(`[${detail.routine.id}] ${detail.routine.name ?? 'Routine'}`);
25
+ this.log(`Program: ${detail.program.name}`);
26
+ this.log(`Week: ${detail.week.number}`);
27
+ this.log('');
28
+ this.log(renderTable(buildRoutineRows(detail.routine, unitLabel)).replace(/^\n+/, ''));
29
+ }
30
+ finally {
31
+ await closeDb(context);
32
+ }
33
+ }
34
+ }
@@ -1,44 +1,16 @@
1
1
  import { Command } from '@oclif/core';
2
- import { closeDb, openDb } from '../../lib/db.js';
3
- import { toJsonErrorPayload } from '../../lib/json-error.js';
4
- import { serializeNextWorkoutDetailWithWeightUnits } from '../../lib/json-weight.js';
5
- import { renderTable } from '../../lib/output.js';
6
- import { getNextWorkoutDetail } from '../../lib/repositories/workouts.js';
7
- import { resolveProgramWeightUnit, weightUnitLabel } from '../../lib/units.js';
8
2
  export default class WorkoutsNext extends Command {
9
- static description = 'Show the up-next routine from the active program';
3
+ static description = 'Redirect to routines next';
10
4
  static enableJsonFlag = true;
11
5
  async run() {
12
6
  await this.parse(WorkoutsNext);
13
- const context = openDb();
14
- try {
15
- const detail = await getNextWorkoutDetail(context.db);
16
- const unitPreference = await resolveProgramWeightUnit(context.db, detail.program.id);
17
- const unitLabel = weightUnitLabel(unitPreference);
18
- if (this.jsonEnabled()) {
19
- return serializeNextWorkoutDetailWithWeightUnits(detail, unitPreference);
20
- }
21
- this.log(`[${detail.routine.id}] ${detail.routine.name ?? 'Workout'}`);
22
- this.log(`Program: ${detail.program.name}`);
23
- this.log(`Week: ${detail.week.number}`);
24
- for (const exercise of detail.routine.exercises) {
25
- this.log('');
26
- this.log(`[${exercise.id ?? exercise.exerciseConfigId}] ${exercise.name ?? '(unnamed)'}`);
27
- this.log(renderTable(exercise.sets.map((set) => ({
28
- reps: set.reps,
29
- rpe: set.rpe,
30
- timeSeconds: set.timeSeconds,
31
- weight: set.weight === null ? null : `${set.weight} ${unitLabel}`,
32
- }))));
33
- }
34
- }
35
- catch (error) {
36
- if (this.jsonEnabled())
37
- return toJsonErrorPayload(error);
38
- throw error;
39
- }
40
- finally {
41
- await closeDb(context);
7
+ const message = 'workouts next has moved. Use "cliftin routines next" instead.';
8
+ if (this.jsonEnabled()) {
9
+ return {
10
+ command: 'routines next',
11
+ message,
12
+ };
42
13
  }
14
+ this.log(message);
43
15
  }
44
16
  }
@@ -5,5 +5,8 @@ export default class WorkoutsShow extends Command {
5
5
  };
6
6
  static description: string;
7
7
  static enableJsonFlag: boolean;
8
+ static flags: {
9
+ 'no-warmup': import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
+ };
8
11
  run(): Promise<unknown | void>;
9
12
  }
@@ -1,4 +1,4 @@
1
- import { Args, Command } from '@oclif/core';
1
+ import { Args, Command, Flags } from '@oclif/core';
2
2
  import { format, isValid, parseISO } from 'date-fns';
3
3
  import { closeDb, openDb } from '../../lib/db.js';
4
4
  import { serializeWorkoutDetailWithWeightUnits } from '../../lib/json-weight.js';
@@ -18,14 +18,28 @@ function formatWorkoutDate(dateIso) {
18
18
  return dateIso;
19
19
  return format(parsed, 'yyyy-MM-dd HH:mm');
20
20
  }
21
+ function maybeExcludeWarmupSets(detail, omitWarmups) {
22
+ if (!omitWarmups)
23
+ return detail;
24
+ return {
25
+ ...detail,
26
+ exercises: detail.exercises.map((exercise) => ({
27
+ ...exercise,
28
+ sets: exercise.sets.filter((set) => !set.isWarmup),
29
+ })),
30
+ };
31
+ }
21
32
  export default class WorkoutsShow extends Command {
22
33
  static args = {
23
34
  workoutId: Args.string({ description: 'workout id (default: latest workout)', ignoreStdin: true, required: false }),
24
35
  };
25
36
  static description = 'Show one workout with exercises and sets';
26
37
  static enableJsonFlag = true;
38
+ static flags = {
39
+ 'no-warmup': Flags.boolean({ description: 'Hide warmup sets from output' }),
40
+ };
27
41
  async run() {
28
- const { args } = await this.parse(WorkoutsShow);
42
+ const { args, flags } = await this.parse(WorkoutsShow);
29
43
  const context = openDb();
30
44
  try {
31
45
  if (args.workoutId !== undefined && !/^\d+$/.test(args.workoutId)) {
@@ -38,7 +52,7 @@ export default class WorkoutsShow extends Command {
38
52
  throw new Error('No workouts found.');
39
53
  return rows[0].id;
40
54
  });
41
- const detail = await getWorkoutDetail(context.db, workoutId);
55
+ const detail = maybeExcludeWarmupSets(await getWorkoutDetail(context.db, workoutId), flags['no-warmup']);
42
56
  const unitPreference = await resolveGlobalWeightUnit(context.db);
43
57
  const unitLabel = weightUnitLabel(unitPreference);
44
58
  if (this.jsonEnabled()) {
@@ -1,8 +1,8 @@
1
- import { ExerciseHistoryRow, NextWorkoutDetail, ProgramDetailTree, WorkoutDetail } from './types.js';
1
+ import { ExerciseHistoryRow, ProgramDetailTree, RoutineDetail, WorkoutDetail } from './types.js';
2
2
  import { UnitPreference, withWeightUnit } from './units.js';
3
3
  export declare function serializeExerciseHistoryRowsWithWeightUnits(rows: ExerciseHistoryRow[], unitPreference: UnitPreference): Array<Omit<ExerciseHistoryRow, 'topWeight'> & {
4
4
  topWeight: ReturnType<typeof withWeightUnit>;
5
5
  }>;
6
6
  export declare function serializeProgramDetailWithWeightUnits(detail: ProgramDetailTree, unitPreference: UnitPreference): unknown;
7
- export declare function serializeNextWorkoutDetailWithWeightUnits(detail: NextWorkoutDetail, unitPreference: UnitPreference): unknown;
7
+ export declare function serializeRoutineDetailWithWeightUnits(detail: RoutineDetail, unitPreference: UnitPreference): unknown;
8
8
  export declare function serializeWorkoutDetailWithWeightUnits(detail: WorkoutDetail, unitPreference: UnitPreference): unknown;
@@ -31,9 +31,9 @@ export function serializeProgramDetailWithWeightUnits(detail, unitPreference) {
31
31
  })),
32
32
  };
33
33
  }
34
- export function serializeNextWorkoutDetailWithWeightUnits(detail, unitPreference) {
34
+ export function serializeRoutineDetailWithWeightUnits(detail, unitPreference) {
35
35
  const program = omitId(detail.program);
36
- return {
36
+ const payload = {
37
37
  program: { ...program, programId: detail.program.id },
38
38
  routine: {
39
39
  ...omitId(detail.routine),
@@ -54,6 +54,19 @@ export function serializeNextWorkoutDetailWithWeightUnits(detail, unitPreference
54
54
  weekId: detail.week.id,
55
55
  },
56
56
  };
57
+ if (detail.workout) {
58
+ payload.workout = {
59
+ date: detail.workout.date,
60
+ duration: {
61
+ unit: 'seconds',
62
+ value: detail.workout.duration,
63
+ },
64
+ program: detail.workout.program,
65
+ routine: detail.workout.routine,
66
+ workoutId: detail.workout.id,
67
+ };
68
+ }
69
+ return payload;
57
70
  }
58
71
  export function serializeWorkoutDetailWithWeightUnits(detail, unitPreference) {
59
72
  return {
@@ -0,0 +1,13 @@
1
+ import { Kysely } from 'kysely';
2
+ import { DatabaseSchema } from '../db.js';
3
+ import { RoutineDetail, RoutineSummary } from '../types.js';
4
+ export type RoutineFilters = {
5
+ name?: string;
6
+ program?: string;
7
+ week?: number;
8
+ };
9
+ export declare function listRoutines(db: Kysely<DatabaseSchema>, filters: RoutineFilters): Promise<RoutineSummary[]>;
10
+ export declare function getRoutineDetail(db: Kysely<DatabaseSchema>, selector: string): Promise<RoutineDetail>;
11
+ export declare function getNextRoutineDetail(db: Kysely<DatabaseSchema>): Promise<RoutineDetail>;
12
+ export declare function getRoutineDetailFromWorkout(db: Kysely<DatabaseSchema>, workoutId: number): Promise<RoutineDetail>;
13
+ export declare function getLatestRoutineDetail(db: Kysely<DatabaseSchema>): Promise<RoutineDetail>;
@@ -0,0 +1,171 @@
1
+ import { sql } from 'kysely';
2
+ import { appleSecondsToIso } from '../time.js';
3
+ import { getProgramDetail, resolveProgramSelector } from './programs.js';
4
+ import { resolveIdOrName } from './selectors.js';
5
+ async function getRoutinePlanContext(db, routineId) {
6
+ const row = await db
7
+ .selectFrom('ZROUTINE as r')
8
+ .leftJoin('ZPERIOD as p', 'p.Z_PK', 'r.ZPERIOD')
9
+ .leftJoin('ZWORKOUTPLAN as planDirect', 'planDirect.Z_PK', 'r.ZWORKOUTPLAN')
10
+ .leftJoin('ZWORKOUTPLAN as planFromPeriod', 'planFromPeriod.Z_PK', 'p.ZWORKOUTPLAN')
11
+ .select([
12
+ 'r.Z_PK as id',
13
+ 'p.Z_PK as weekId',
14
+ 'planDirect.Z_PK as programIdDirect',
15
+ 'planFromPeriod.Z_PK as programIdFromPeriod',
16
+ ])
17
+ .where('r.Z_PK', '=', routineId)
18
+ .where('r.ZSOFTDELETED', 'is not', 1)
19
+ .where((eb) => eb.or([eb('planDirect.ZSOFTDELETED', 'is not', 1), eb('planFromPeriod.ZSOFTDELETED', 'is not', 1)]))
20
+ .executeTakeFirst();
21
+ if (!row)
22
+ throw new Error(`Routine not found: ${routineId}`);
23
+ const programId = row.programIdDirect ?? row.programIdFromPeriod;
24
+ if (programId === null)
25
+ throw new Error(`Routine ${routineId} is not linked to a program.`);
26
+ if (row.weekId === null)
27
+ throw new Error(`Routine ${routineId} is not linked to a week.`);
28
+ return { programId, weekId: row.weekId };
29
+ }
30
+ async function getRoutineDetailById(db, routineId, workout) {
31
+ const context = await getRoutinePlanContext(db, routineId);
32
+ const programDetail = await getProgramDetail(db, context.programId);
33
+ const weekIndex = programDetail.weeks.findIndex((week) => week.id === context.weekId);
34
+ if (weekIndex === -1) {
35
+ throw new Error(`Routine ${routineId} is linked to unknown week ${context.weekId}.`);
36
+ }
37
+ const week = programDetail.weeks[weekIndex];
38
+ const routine = week.routines.find((entry) => entry.id === routineId);
39
+ if (!routine) {
40
+ throw new Error(`Routine ${routineId} was not found in program ${programDetail.program.name}.`);
41
+ }
42
+ return {
43
+ program: programDetail.program,
44
+ routine,
45
+ week: {
46
+ id: week.id,
47
+ number: weekIndex + 1,
48
+ },
49
+ workout,
50
+ };
51
+ }
52
+ export async function listRoutines(db, filters) {
53
+ let query = db
54
+ .selectFrom('ZROUTINE as r')
55
+ .leftJoin('ZPERIOD as p', 'p.Z_PK', 'r.ZPERIOD')
56
+ .leftJoin('ZWORKOUTPLAN as planDirect', 'planDirect.Z_PK', 'r.ZWORKOUTPLAN')
57
+ .leftJoin('ZWORKOUTPLAN as planFromPeriod', 'planFromPeriod.Z_PK', 'p.ZWORKOUTPLAN')
58
+ .select([
59
+ 'r.Z_PK as id',
60
+ 'r.ZNAME as name',
61
+ 'r.ZUPNEXT as upNext',
62
+ 'p.Z_PK as weekId',
63
+ 'planDirect.Z_PK as programIdDirect',
64
+ 'planFromPeriod.Z_PK as programIdFromPeriod',
65
+ 'planDirect.ZNAME as programNameDirect',
66
+ 'planFromPeriod.ZNAME as programNameFromPeriod',
67
+ ])
68
+ .where('r.ZSOFTDELETED', 'is not', 1)
69
+ .where((eb) => eb.or([eb('planDirect.ZSOFTDELETED', 'is not', 1), eb('planFromPeriod.ZSOFTDELETED', 'is not', 1)]));
70
+ if (filters.program) {
71
+ const programId = await resolveProgramSelector(db, filters.program, false);
72
+ query = query.where((eb) => eb.or([eb('r.ZWORKOUTPLAN', '=', programId), eb('p.ZWORKOUTPLAN', '=', programId)]));
73
+ }
74
+ if (filters.name) {
75
+ const lowered = `%${filters.name.toLowerCase()}%`;
76
+ query = query.where(sql `lower(r.ZNAME) like ${lowered}`);
77
+ }
78
+ query = query.orderBy('planFromPeriod.ZDATEADDED', 'desc').orderBy('planDirect.ZDATEADDED', 'desc').orderBy('p.Z_FOK_WORKOUTPLAN', 'asc').orderBy('r.Z_FOK_PERIOD', 'asc').orderBy('r.Z_PK', 'asc');
79
+ const rows = await query.execute();
80
+ const weekNumbers = new Map();
81
+ const countsByProgram = new Map();
82
+ for (const row of rows) {
83
+ const programId = row.programIdDirect ?? row.programIdFromPeriod;
84
+ if (programId === null || row.weekId === null)
85
+ continue;
86
+ const key = row.weekId;
87
+ if (!weekNumbers.has(key)) {
88
+ const count = (countsByProgram.get(programId) ?? 0) + 1;
89
+ countsByProgram.set(programId, count);
90
+ weekNumbers.set(key, count);
91
+ }
92
+ }
93
+ return rows
94
+ .map((row) => ({
95
+ id: row.id,
96
+ isNext: row.upNext === 1,
97
+ name: row.name,
98
+ program: row.programNameDirect ?? row.programNameFromPeriod,
99
+ week: row.weekId === null ? null : weekNumbers.get(row.weekId) ?? null,
100
+ }))
101
+ .filter((row) => (filters.week === undefined ? true : row.week === filters.week));
102
+ }
103
+ export async function getRoutineDetail(db, selector) {
104
+ const routineId = await resolveIdOrName(db, 'ZROUTINE', selector);
105
+ return getRoutineDetailById(db, routineId, null);
106
+ }
107
+ export async function getNextRoutineDetail(db) {
108
+ const programId = await resolveProgramSelector(db, undefined, true);
109
+ const nextRoutines = await db
110
+ .selectFrom('ZROUTINE as r')
111
+ .leftJoin('ZPERIOD as p', 'p.Z_PK', 'r.ZPERIOD')
112
+ .select(['r.Z_PK as id'])
113
+ .where('r.ZUPNEXT', '=', 1)
114
+ .where('r.ZSOFTDELETED', 'is not', 1)
115
+ .where((eb) => eb.or([eb('p.ZWORKOUTPLAN', '=', programId), eb('r.ZWORKOUTPLAN', '=', programId)]))
116
+ .orderBy('p.Z_FOK_WORKOUTPLAN', 'asc')
117
+ .orderBy('r.Z_FOK_PERIOD', 'asc')
118
+ .orderBy('r.Z_PK', 'asc')
119
+ .execute();
120
+ if (nextRoutines.length === 0) {
121
+ const programDetail = await getProgramDetail(db, programId);
122
+ throw new Error(`No up-next routine found for active program ${programDetail.program.name}.`);
123
+ }
124
+ if (nextRoutines.length > 1) {
125
+ const programDetail = await getProgramDetail(db, programId);
126
+ throw new Error(`Expected exactly one up-next routine for active program ${programDetail.program.name}. Found ${nextRoutines.length}.`);
127
+ }
128
+ return getRoutineDetailById(db, nextRoutines[0].id, null);
129
+ }
130
+ export async function getRoutineDetailFromWorkout(db, workoutId) {
131
+ const workout = await db
132
+ .selectFrom('ZWORKOUTRESULT as wr')
133
+ .leftJoin('ZROUTINE as r', 'r.Z_PK', 'wr.ZROUTINE')
134
+ .leftJoin('ZPERIOD as p', 'p.Z_PK', 'r.ZPERIOD')
135
+ .leftJoin('ZWORKOUTPLAN as planDirect', 'planDirect.Z_PK', 'r.ZWORKOUTPLAN')
136
+ .leftJoin('ZWORKOUTPLAN as planFromPeriod', 'planFromPeriod.Z_PK', 'p.ZWORKOUTPLAN')
137
+ .select([
138
+ 'wr.Z_PK as id',
139
+ 'wr.ZDURATION as duration',
140
+ 'wr.ZSTARTDATE as startDate',
141
+ 'wr.ZROUTINE as routineId',
142
+ 'wr.ZROUTINENAME as routineNameFromResult',
143
+ 'r.ZNAME as routineNameFromPlan',
144
+ 'planDirect.ZNAME as programNameDirect',
145
+ 'planFromPeriod.ZNAME as programNameFromPeriod',
146
+ ])
147
+ .where('wr.Z_PK', '=', workoutId)
148
+ .executeTakeFirst();
149
+ if (!workout)
150
+ throw new Error(`Workout not found: ${workoutId}`);
151
+ if (workout.routineId === null)
152
+ throw new Error(`Workout ${workoutId} is not linked to a planned routine.`);
153
+ return getRoutineDetailById(db, workout.routineId, {
154
+ date: appleSecondsToIso(workout.startDate),
155
+ duration: workout.duration,
156
+ id: workout.id,
157
+ program: workout.programNameDirect ?? workout.programNameFromPeriod,
158
+ routine: workout.routineNameFromResult ?? workout.routineNameFromPlan,
159
+ });
160
+ }
161
+ export async function getLatestRoutineDetail(db) {
162
+ const latestWorkout = await db
163
+ .selectFrom('ZWORKOUTRESULT')
164
+ .select('Z_PK as id')
165
+ .orderBy('ZSTARTDATE', 'desc')
166
+ .limit(1)
167
+ .executeTakeFirst();
168
+ if (!latestWorkout)
169
+ throw new Error('No workouts found.');
170
+ return getRoutineDetailFromWorkout(db, latestWorkout.id);
171
+ }
@@ -44,6 +44,7 @@ export async function getNextWorkoutDetail(db) {
44
44
  id: week.id,
45
45
  number: weekIndex + 1,
46
46
  },
47
+ workout: null,
47
48
  };
48
49
  }
49
50
  export async function listWorkouts(db, filters) {
@@ -0,0 +1,2 @@
1
+ import { ProgramRoutine } from './types.js';
2
+ export declare function buildRoutineRows(routine: ProgramRoutine, unitLabel: string): Array<Record<string, unknown>>;
@@ -0,0 +1,19 @@
1
+ export function buildRoutineRows(routine, unitLabel) {
2
+ return routine.exercises.flatMap((exercise) => {
3
+ const headingRow = {
4
+ exercise: `[${exercise.id ?? exercise.exerciseConfigId}] ${exercise.name ?? '(unnamed)'}`,
5
+ reps: null,
6
+ rpe: null,
7
+ timeSeconds: null,
8
+ weight: null,
9
+ };
10
+ const setRows = exercise.sets.map((set) => ({
11
+ exercise: '',
12
+ reps: set.reps,
13
+ rpe: set.rpe,
14
+ timeSeconds: set.timeSeconds,
15
+ weight: set.weight === null ? null : `${set.weight} ${unitLabel}`,
16
+ }));
17
+ return [headingRow, ...setRows];
18
+ });
19
+ }
@@ -39,6 +39,23 @@ export type ProgramDetailTree = {
39
39
  program: ProgramSummary;
40
40
  weeks: ProgramWeek[];
41
41
  };
42
+ export type RoutineWeekRef = {
43
+ id: number;
44
+ number: number;
45
+ };
46
+ export type RoutineSummary = {
47
+ id: number;
48
+ isNext: boolean;
49
+ name: null | string;
50
+ program: null | string;
51
+ week: null | number;
52
+ };
53
+ export type RoutineDetail = {
54
+ program: ProgramSummary;
55
+ routine: ProgramRoutine;
56
+ week: RoutineWeekRef;
57
+ workout: null | WorkoutSummary;
58
+ };
42
59
  export type WorkoutSummary = {
43
60
  date: null | string;
44
61
  duration: null | number;
@@ -69,14 +86,7 @@ export type WorkoutDetail = {
69
86
  program: null | string;
70
87
  routine: null | string;
71
88
  };
72
- export type NextWorkoutDetail = {
73
- program: ProgramSummary;
74
- routine: ProgramRoutine;
75
- week: {
76
- id: number;
77
- number: number;
78
- };
79
- };
89
+ export type NextWorkoutDetail = RoutineDetail;
80
90
  export type ExerciseSummary = {
81
91
  equipment: null | string;
82
92
  id: number;
@@ -139,6 +139,12 @@
139
139
  "multiple": false,
140
140
  "type": "option"
141
141
  },
142
+ "no-warmup": {
143
+ "description": "Hide warmup sets from output",
144
+ "name": "no-warmup",
145
+ "allowNo": false,
146
+ "type": "boolean"
147
+ },
142
148
  "program": {
143
149
  "description": "History filter by program id or name",
144
150
  "name": "program",
@@ -241,6 +247,184 @@
241
247
  "show.js"
242
248
  ]
243
249
  },
250
+ "routines:from-workout": {
251
+ "aliases": [],
252
+ "args": {
253
+ "workoutId": {
254
+ "description": "workout id (default: latest workout)",
255
+ "name": "workoutId",
256
+ "required": false
257
+ }
258
+ },
259
+ "description": "Show the planned routine for a completed workout",
260
+ "flags": {
261
+ "json": {
262
+ "description": "Format output as json.",
263
+ "helpGroup": "GLOBAL",
264
+ "name": "json",
265
+ "allowNo": false,
266
+ "type": "boolean"
267
+ }
268
+ },
269
+ "hasDynamicHelp": false,
270
+ "hiddenAliases": [],
271
+ "id": "routines:from-workout",
272
+ "pluginAlias": "@nickchristensen/cliftin",
273
+ "pluginName": "@nickchristensen/cliftin",
274
+ "pluginType": "core",
275
+ "strict": true,
276
+ "enableJsonFlag": true,
277
+ "isESM": true,
278
+ "relativePath": [
279
+ "dist",
280
+ "commands",
281
+ "routines",
282
+ "from-workout.js"
283
+ ]
284
+ },
285
+ "routines:latest": {
286
+ "aliases": [],
287
+ "args": {},
288
+ "description": "Show the planned routine for the latest workout",
289
+ "flags": {
290
+ "json": {
291
+ "description": "Format output as json.",
292
+ "helpGroup": "GLOBAL",
293
+ "name": "json",
294
+ "allowNo": false,
295
+ "type": "boolean"
296
+ }
297
+ },
298
+ "hasDynamicHelp": false,
299
+ "hiddenAliases": [],
300
+ "id": "routines:latest",
301
+ "pluginAlias": "@nickchristensen/cliftin",
302
+ "pluginName": "@nickchristensen/cliftin",
303
+ "pluginType": "core",
304
+ "strict": true,
305
+ "enableJsonFlag": true,
306
+ "isESM": true,
307
+ "relativePath": [
308
+ "dist",
309
+ "commands",
310
+ "routines",
311
+ "latest.js"
312
+ ]
313
+ },
314
+ "routines:list": {
315
+ "aliases": [],
316
+ "args": {},
317
+ "description": "List planned routines",
318
+ "flags": {
319
+ "json": {
320
+ "description": "Format output as json.",
321
+ "helpGroup": "GLOBAL",
322
+ "name": "json",
323
+ "allowNo": false,
324
+ "type": "boolean"
325
+ },
326
+ "name": {
327
+ "description": "Filter by routine name contains",
328
+ "name": "name",
329
+ "hasDynamicHelp": false,
330
+ "multiple": false,
331
+ "type": "option"
332
+ },
333
+ "program": {
334
+ "description": "Filter by program id or name",
335
+ "name": "program",
336
+ "hasDynamicHelp": false,
337
+ "multiple": false,
338
+ "type": "option"
339
+ },
340
+ "week": {
341
+ "description": "Filter by week number",
342
+ "name": "week",
343
+ "hasDynamicHelp": false,
344
+ "multiple": false,
345
+ "type": "option"
346
+ }
347
+ },
348
+ "hasDynamicHelp": false,
349
+ "hiddenAliases": [],
350
+ "id": "routines:list",
351
+ "pluginAlias": "@nickchristensen/cliftin",
352
+ "pluginName": "@nickchristensen/cliftin",
353
+ "pluginType": "core",
354
+ "strict": true,
355
+ "enableJsonFlag": true,
356
+ "isESM": true,
357
+ "relativePath": [
358
+ "dist",
359
+ "commands",
360
+ "routines",
361
+ "list.js"
362
+ ]
363
+ },
364
+ "routines:next": {
365
+ "aliases": [],
366
+ "args": {},
367
+ "description": "Show the up-next routine from the active program",
368
+ "flags": {
369
+ "json": {
370
+ "description": "Format output as json.",
371
+ "helpGroup": "GLOBAL",
372
+ "name": "json",
373
+ "allowNo": false,
374
+ "type": "boolean"
375
+ }
376
+ },
377
+ "hasDynamicHelp": false,
378
+ "hiddenAliases": [],
379
+ "id": "routines:next",
380
+ "pluginAlias": "@nickchristensen/cliftin",
381
+ "pluginName": "@nickchristensen/cliftin",
382
+ "pluginType": "core",
383
+ "strict": true,
384
+ "enableJsonFlag": true,
385
+ "isESM": true,
386
+ "relativePath": [
387
+ "dist",
388
+ "commands",
389
+ "routines",
390
+ "next.js"
391
+ ]
392
+ },
393
+ "routines:show": {
394
+ "aliases": [],
395
+ "args": {
396
+ "selector": {
397
+ "description": "routine id or name",
398
+ "name": "selector",
399
+ "required": true
400
+ }
401
+ },
402
+ "description": "Show one planned routine",
403
+ "flags": {
404
+ "json": {
405
+ "description": "Format output as json.",
406
+ "helpGroup": "GLOBAL",
407
+ "name": "json",
408
+ "allowNo": false,
409
+ "type": "boolean"
410
+ }
411
+ },
412
+ "hasDynamicHelp": false,
413
+ "hiddenAliases": [],
414
+ "id": "routines:show",
415
+ "pluginAlias": "@nickchristensen/cliftin",
416
+ "pluginName": "@nickchristensen/cliftin",
417
+ "pluginType": "core",
418
+ "strict": true,
419
+ "enableJsonFlag": true,
420
+ "isESM": true,
421
+ "relativePath": [
422
+ "dist",
423
+ "commands",
424
+ "routines",
425
+ "show.js"
426
+ ]
427
+ },
244
428
  "workouts:list": {
245
429
  "aliases": [],
246
430
  "args": {},
@@ -337,7 +521,7 @@
337
521
  "workouts:next": {
338
522
  "aliases": [],
339
523
  "args": {},
340
- "description": "Show the up-next routine from the active program",
524
+ "description": "Redirect to routines next",
341
525
  "flags": {
342
526
  "json": {
343
527
  "description": "Format output as json.",
@@ -380,6 +564,12 @@
380
564
  "name": "json",
381
565
  "allowNo": false,
382
566
  "type": "boolean"
567
+ },
568
+ "no-warmup": {
569
+ "description": "Hide warmup sets from output",
570
+ "name": "no-warmup",
571
+ "allowNo": false,
572
+ "type": "boolean"
383
573
  }
384
574
  },
385
575
  "hasDynamicHelp": false,
@@ -399,5 +589,5 @@
399
589
  ]
400
590
  }
401
591
  },
402
- "version": "3.0.0"
592
+ "version": "4.1.0"
403
593
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nickchristensen/cliftin",
3
3
  "description": "CLIftin: A read-only CLI for Liftin'",
4
- "version": "3.0.0",
4
+ "version": "4.1.0",
5
5
  "author": "Nick Christensen",
6
6
  "bin": {
7
7
  "cliftin": "./bin/run.js"
@@ -71,6 +71,9 @@
71
71
  "programs": {
72
72
  "description": "Program hierarchy and planning"
73
73
  },
74
+ "routines": {
75
+ "description": "Planned routine detail and navigation"
76
+ },
74
77
  "workouts": {
75
78
  "description": "Workout history and detail"
76
79
  }