@nickchristensen/cliftin 1.0.2 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,28 +5,40 @@ CLIftin: A read-only CLI for Liftin'
5
5
 
6
6
 
7
7
  [![oclif](https://img.shields.io/badge/cli-oclif-brightgreen.svg)](https://oclif.io)
8
- [![Version](https://img.shields.io/npm/v/cliftin.svg)](https://npmjs.org/package/cliftin)
9
- [![Downloads/week](https://img.shields.io/npm/dw/cliftin.svg)](https://npmjs.org/package/cliftin)
8
+ [![Version](https://img.shields.io/npm/v/@nickchristensen/cliftin.svg)](https://npmjs.org/package/cliftin)
9
+ [![Downloads/week](https://img.shields.io/npm/dw/@nickchristensen/cliftin.svg)](https://npmjs.org/package/cliftin)
10
10
 
11
11
 
12
12
  <!-- toc -->
13
+ * [Configuration](#configuration)
13
14
  * [Usage](#usage)
14
15
  * [Commands](#commands)
15
16
  <!-- tocstop -->
17
+
18
+ # Configuration
19
+
20
+ cliftin reads your Liftin database directly. By default it looks for the Liftin app's SQLite file at:
21
+
22
+ ```
23
+ $HOME/Library/Containers/com.nstrm.Bello/Data/Library/Application Support/Liftin/BelloDataModel.sqlite
24
+ ```
25
+
26
+ To use a different path, set `LIFTIN_DB_PATH` in your environment or in a `.env.local` file at the project root:
27
+
28
+ ```sh
29
+ LIFTIN_DB_PATH=/path/to/BelloDataModel.sqlite
30
+ ```
31
+
16
32
  # Usage
17
- <!-- usage -->
18
33
  ```sh-session
19
34
  $ npm install -g @nickchristensen/cliftin
20
35
  $ cliftin COMMAND
21
36
  running command...
22
- $ cliftin (--version)
23
- @nickchristensen/cliftin/1.0.2 darwin-arm64 node-v25.6.0
24
37
  $ cliftin --help [COMMAND]
25
38
  USAGE
26
39
  $ cliftin COMMAND
27
40
  ...
28
41
  ```
29
- <!-- usagestop -->
30
42
  # Commands
31
43
  <!-- commands -->
32
44
  * [`cliftin exercises list`](#cliftin-exercises-list)
@@ -35,7 +47,7 @@ USAGE
35
47
  * [`cliftin programs list`](#cliftin-programs-list)
36
48
  * [`cliftin programs show [SELECTOR]`](#cliftin-programs-show-selector)
37
49
  * [`cliftin workouts list`](#cliftin-workouts-list)
38
- * [`cliftin workouts show WORKOUTID`](#cliftin-workouts-show-workoutid)
50
+ * [`cliftin workouts show [WORKOUTID]`](#cliftin-workouts-show-workoutid)
39
51
 
40
52
  ## `cliftin exercises list`
41
53
 
@@ -60,7 +72,7 @@ DESCRIPTION
60
72
  List exercises
61
73
  ```
62
74
 
63
- _See code: [src/commands/exercises/list.ts](https://github.com/nickchristensen/cliftin/blob/v1.0.2/src/commands/exercises/list.ts)_
75
+ _See code: [src/commands/exercises/list.ts](https://github.com/nickchristensen/cliftin/blob/v1.2.0/src/commands/exercises/list.ts)_
64
76
 
65
77
  ## `cliftin exercises show SELECTOR`
66
78
 
@@ -94,7 +106,7 @@ DESCRIPTION
94
106
  Show one exercise detail and history
95
107
  ```
96
108
 
97
- _See code: [src/commands/exercises/show.ts](https://github.com/nickchristensen/cliftin/blob/v1.0.2/src/commands/exercises/show.ts)_
109
+ _See code: [src/commands/exercises/show.ts](https://github.com/nickchristensen/cliftin/blob/v1.2.0/src/commands/exercises/show.ts)_
98
110
 
99
111
  ## `cliftin help [COMMAND]`
100
112
 
@@ -131,7 +143,7 @@ DESCRIPTION
131
143
  List programs
132
144
  ```
133
145
 
134
- _See code: [src/commands/programs/list.ts](https://github.com/nickchristensen/cliftin/blob/v1.0.2/src/commands/programs/list.ts)_
146
+ _See code: [src/commands/programs/list.ts](https://github.com/nickchristensen/cliftin/blob/v1.2.0/src/commands/programs/list.ts)_
135
147
 
136
148
  ## `cliftin programs show [SELECTOR]`
137
149
 
@@ -139,14 +151,10 @@ Show one program hierarchy
139
151
 
140
152
  ```
141
153
  USAGE
142
- $ cliftin programs show [SELECTOR] [--json] [--active] [--current]
154
+ $ cliftin programs show [SELECTOR] [--json]
143
155
 
144
156
  ARGUMENTS
145
- [SELECTOR] program id or name
146
-
147
- FLAGS
148
- --active Show active program detail
149
- --current Alias for --active
157
+ [SELECTOR] program id or name (default: active program)
150
158
 
151
159
  GLOBAL FLAGS
152
160
  --json Format output as json.
@@ -155,7 +163,7 @@ DESCRIPTION
155
163
  Show one program hierarchy
156
164
  ```
157
165
 
158
- _See code: [src/commands/programs/show.ts](https://github.com/nickchristensen/cliftin/blob/v1.0.2/src/commands/programs/show.ts)_
166
+ _See code: [src/commands/programs/show.ts](https://github.com/nickchristensen/cliftin/blob/v1.2.0/src/commands/programs/show.ts)_
159
167
 
160
168
  ## `cliftin workouts list`
161
169
 
@@ -182,18 +190,18 @@ DESCRIPTION
182
190
  List workouts
183
191
  ```
184
192
 
185
- _See code: [src/commands/workouts/list.ts](https://github.com/nickchristensen/cliftin/blob/v1.0.2/src/commands/workouts/list.ts)_
193
+ _See code: [src/commands/workouts/list.ts](https://github.com/nickchristensen/cliftin/blob/v1.2.0/src/commands/workouts/list.ts)_
186
194
 
187
- ## `cliftin workouts show WORKOUTID`
195
+ ## `cliftin workouts show [WORKOUTID]`
188
196
 
189
197
  Show one workout with exercises and sets
190
198
 
191
199
  ```
192
200
  USAGE
193
- $ cliftin workouts show WORKOUTID [--json]
201
+ $ cliftin workouts show [WORKOUTID] [--json]
194
202
 
195
203
  ARGUMENTS
196
- WORKOUTID workout id
204
+ [WORKOUTID] workout id (default: latest workout)
197
205
 
198
206
  GLOBAL FLAGS
199
207
  --json Format output as json.
@@ -202,5 +210,5 @@ DESCRIPTION
202
210
  Show one workout with exercises and sets
203
211
  ```
204
212
 
205
- _See code: [src/commands/workouts/show.ts](https://github.com/nickchristensen/cliftin/blob/v1.0.2/src/commands/workouts/show.ts)_
213
+ _See code: [src/commands/workouts/show.ts](https://github.com/nickchristensen/cliftin/blob/v1.2.0/src/commands/workouts/show.ts)_
206
214
  <!-- commandsstop -->
@@ -5,9 +5,6 @@ export default class ProgramsShow extends Command {
5
5
  };
6
6
  static description: string;
7
7
  static enableJsonFlag: boolean;
8
- static flags: {
9
- active: import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
- current: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
- };
8
+ static flags: {};
12
9
  run(): Promise<unknown | void>;
13
10
  }
@@ -1,4 +1,4 @@
1
- import { Args, Command, Flags } from '@oclif/core';
1
+ import { Args, Command } from '@oclif/core';
2
2
  import { closeDb, openDb } from '../../lib/db.js';
3
3
  import { serializeProgramDetailWithWeightUnits } from '../../lib/json-weight.js';
4
4
  import { renderTable } from '../../lib/output.js';
@@ -30,26 +30,16 @@ function buildProgramRows(weeks, unitLabel) {
30
30
  }
31
31
  export default class ProgramsShow extends Command {
32
32
  static args = {
33
- selector: Args.string({ description: 'program id or name', ignoreStdin: true, required: false }),
33
+ selector: Args.string({ description: 'program id or name (default: active program)', ignoreStdin: true, required: false }),
34
34
  };
35
35
  static description = 'Show one program hierarchy';
36
36
  static enableJsonFlag = true;
37
- static flags = {
38
- active: Flags.boolean({ description: 'Show active program detail' }),
39
- current: Flags.boolean({ description: 'Alias for --active' }),
40
- };
37
+ static flags = {};
41
38
  async run() {
42
- const { args, flags } = await this.parse(ProgramsShow);
39
+ const { args } = await this.parse(ProgramsShow);
43
40
  const context = openDb();
44
41
  try {
45
- const useActive = flags.active || flags.current;
46
- if (args.selector && useActive) {
47
- throw new Error('Use either a selector or --active/--current, not both.');
48
- }
49
- if (!args.selector && !useActive) {
50
- throw new Error('Provide a selector or use --active/--current.');
51
- }
52
- const programId = await resolveProgramSelector(context.db, args.selector, Boolean(useActive));
42
+ const programId = await resolveProgramSelector(context.db, args.selector, !args.selector);
53
43
  const detail = await getProgramDetail(context.db, programId);
54
44
  const unitPreference = await resolveProgramWeightUnit(context.db, detail.program.id);
55
45
  const unitLabel = weightUnitLabel(unitPreference);
@@ -57,7 +47,8 @@ export default class ProgramsShow extends Command {
57
47
  return serializeProgramDetailWithWeightUnits(detail, unitPreference);
58
48
  }
59
49
  this.log(`[${detail.program.id}] ${detail.program.name}`);
60
- this.log(`Active: ${detail.program.isActive} Template: ${detail.program.isTemplate}`);
50
+ this.log(`Active: ${detail.program.isActive}`);
51
+ this.log(`Template: ${detail.program.isTemplate}`);
61
52
  this.log('');
62
53
  const programRows = buildProgramRows(detail.weeks, unitLabel);
63
54
  const renderedTable = renderTable(programRows).replace(/^\n+/, '');
@@ -1,7 +1,7 @@
1
1
  import { Command } from '@oclif/core';
2
2
  export default class WorkoutsShow extends Command {
3
3
  static args: {
4
- workoutId: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
4
+ workoutId: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
5
5
  };
6
6
  static description: string;
7
7
  static enableJsonFlag: boolean;
@@ -3,7 +3,7 @@ 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';
5
5
  import { renderTable } from '../../lib/output.js';
6
- import { getWorkoutDetail } from '../../lib/repositories/workouts.js';
6
+ import { getWorkoutDetail, listWorkouts } from '../../lib/repositories/workouts.js';
7
7
  import { resolveGlobalWeightUnit, weightUnitLabel } from '../../lib/units.js';
8
8
  function formatDurationMinutes(durationSeconds) {
9
9
  if (durationSeconds === null)
@@ -20,7 +20,7 @@ function formatWorkoutDate(dateIso) {
20
20
  }
21
21
  export default class WorkoutsShow extends Command {
22
22
  static args = {
23
- workoutId: Args.string({ description: 'workout id', required: true }),
23
+ workoutId: Args.string({ description: 'workout id (default: latest workout)', required: false }),
24
24
  };
25
25
  static description = 'Show one workout with exercises and sets';
26
26
  static enableJsonFlag = true;
@@ -28,10 +28,17 @@ export default class WorkoutsShow extends Command {
28
28
  const { args } = await this.parse(WorkoutsShow);
29
29
  const context = openDb();
30
30
  try {
31
- if (!/^\d+$/.test(args.workoutId)) {
32
- throw new Error('Workout detail requires numeric workout id.');
31
+ if (args.workoutId !== undefined && !/^\d+$/.test(args.workoutId)) {
32
+ throw new Error('Workout id must be numeric.');
33
33
  }
34
- const detail = await getWorkoutDetail(context.db, Number(args.workoutId));
34
+ const workoutId = args.workoutId
35
+ ? Number(args.workoutId)
36
+ : await listWorkouts(context.db, { limit: 1 }).then((rows) => {
37
+ if (rows.length === 0)
38
+ throw new Error('No workouts found.');
39
+ return rows[0].id;
40
+ });
41
+ const detail = await getWorkoutDetail(context.db, workoutId);
35
42
  const unitPreference = await resolveGlobalWeightUnit(context.db);
36
43
  const unitLabel = weightUnitLabel(unitPreference);
37
44
  if (this.jsonEnabled()) {
@@ -0,0 +1,3 @@
1
+ import { Hook } from '@oclif/core';
2
+ declare const hook: Hook.Init;
3
+ export default hook;
@@ -0,0 +1,8 @@
1
+ const hook = async function (options) {
2
+ if (['--version', '-v'].includes(process.argv[2])) {
3
+ this.log(`v${options.config.version}`);
4
+ // eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit
5
+ process.exit(0);
6
+ }
7
+ };
8
+ export default hook;
@@ -10,15 +10,13 @@ function loadEnv() {
10
10
  }
11
11
  export function getDbPath() {
12
12
  loadEnv();
13
- const path = process.env.LIFTIN_DB_PATH;
14
- if (!path) {
15
- throw new Error('Missing LIFTIN_DB_PATH. Add it to .env.local, for example: LIFTIN_DB_PATH=/Users/nick/code/cliftin/data/BelloDataModel.sqlite');
16
- }
13
+ const defaultPath = `${process.env.HOME}/Library/Containers/com.nstrm.Bello/Data/Library/Application Support/Liftin/BelloDataModel.sqlite`;
14
+ const path = process.env.LIFTIN_DB_PATH ?? defaultPath;
17
15
  try {
18
16
  accessSync(path, constants.R_OK);
19
17
  }
20
18
  catch {
21
- throw new Error(`Database file is not readable at LIFTIN_DB_PATH=${path}`);
19
+ throw new Error(`Database file is not readable at path=${path}`);
22
20
  }
23
21
  return path;
24
22
  }
@@ -54,7 +54,7 @@ export async function resolveProgramSelector(db, selector, activeOnly) {
54
54
  return fallbackRows[0].id;
55
55
  }
56
56
  if (!selector)
57
- throw new Error('Program selector is required unless --active/--current is set.');
57
+ throw new Error('Program selector is required.');
58
58
  const programId = await resolveIdOrName(db, 'ZWORKOUTPLAN', selector);
59
59
  const nonDeleted = await db
60
60
  .selectFrom('ZWORKOUTPLAN')
@@ -73,7 +73,12 @@ export async function getWorkoutDetail(db, workoutId) {
73
73
  .selectFrom('ZEXERCISERESULT as er')
74
74
  .leftJoin('ZEXERCISECONFIGURATION as ec', 'ec.Z_PK', 'er.ZCONFIGURATION')
75
75
  .leftJoin('ZEXERCISEINFORMATION as ei', 'ei.Z_PK', 'ec.ZINFORMATION')
76
- .select(['er.Z_PK as exerciseResultId', 'ei.Z_PK as exerciseId', 'ei.ZISUSERCREATED as isUserCreated', 'ei.ZNAME as exerciseName'])
76
+ .select([
77
+ 'er.Z_PK as exerciseResultId',
78
+ 'ei.Z_PK as exerciseId',
79
+ 'ei.ZISUSERCREATED as isUserCreated',
80
+ 'ei.ZNAME as exerciseName',
81
+ ])
77
82
  .where('er.ZWORKOUT', '=', workoutId)
78
83
  .orderBy('er.Z_FOK_WORKOUT', 'asc')
79
84
  .orderBy('er.Z_PK', 'asc')
@@ -210,7 +210,7 @@
210
210
  "aliases": [],
211
211
  "args": {
212
212
  "selector": {
213
- "description": "program id or name",
213
+ "description": "program id or name (default: active program)",
214
214
  "name": "selector",
215
215
  "required": false
216
216
  }
@@ -223,18 +223,6 @@
223
223
  "name": "json",
224
224
  "allowNo": false,
225
225
  "type": "boolean"
226
- },
227
- "active": {
228
- "description": "Show active program detail",
229
- "name": "active",
230
- "allowNo": false,
231
- "type": "boolean"
232
- },
233
- "current": {
234
- "description": "Alias for --active",
235
- "name": "current",
236
- "allowNo": false,
237
- "type": "boolean"
238
226
  }
239
227
  },
240
228
  "hasDynamicHelp": false,
@@ -350,9 +338,9 @@
350
338
  "aliases": [],
351
339
  "args": {
352
340
  "workoutId": {
353
- "description": "workout id",
341
+ "description": "workout id (default: latest workout)",
354
342
  "name": "workoutId",
355
- "required": true
343
+ "required": false
356
344
  }
357
345
  },
358
346
  "description": "Show one workout with exercises and sets",
@@ -382,5 +370,5 @@
382
370
  ]
383
371
  }
384
372
  },
385
- "version": "1.0.2"
373
+ "version": "1.2.0"
386
374
  }
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": "1.0.2",
4
+ "version": "1.2.0",
5
5
  "author": "Nick Christensen",
6
6
  "bin": {
7
7
  "cliftin": "./bin/run.js"
@@ -57,6 +57,9 @@
57
57
  "flagSortOrder": "none"
58
58
  },
59
59
  "commands": "./dist/commands",
60
+ "hooks": {
61
+ "init": "./dist/hooks/init"
62
+ },
60
63
  "plugins": [
61
64
  "@oclif/plugin-help"
62
65
  ],