@beauraines/rtm-cli 1.11.0 → 1.13.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/CHANGELOG.md CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ## [1.13.0](https://github.com/beauraines/rtm-cli/compare/v1.12.0...v1.13.0) (2025-12-17)
6
+
7
+
8
+ ### Features
9
+
10
+ * **task:** human readeable recurrence and estimates ([#163](https://github.com/beauraines/rtm-cli/issues/163)) ([d78dd0a](https://github.com/beauraines/rtm-cli/commit/d78dd0a1204584a30df2f8a568c25142d93b36cc))
11
+
12
+ ## [1.12.0](https://github.com/beauraines/rtm-cli/compare/v1.11.0...v1.12.0) (2025-12-17)
13
+
14
+
15
+ ### Features
16
+
17
+ * **task:** adds tasks as alias for task command ([#162](https://github.com/beauraines/rtm-cli/issues/162)) ([b9196ee](https://github.com/beauraines/rtm-cli/commit/b9196ee2d73680cce9ab01e39cd73e27b426fb7e)), closes [#152](https://github.com/beauraines/rtm-cli/issues/152)
18
+
5
19
  ## [1.11.0](https://github.com/beauraines/rtm-cli/compare/v1.10.0...v1.11.0) (2025-12-17)
6
20
 
7
21
 
package/README.md CHANGED
@@ -98,7 +98,7 @@ The main usage of the program:
98
98
  setUrl|su [index] [url] Set the URL of a Task
99
99
  start [index] [start...] Set the Start Date of a Task
100
100
  tags|t Display all tags
101
- task [indices...] Display the Task details
101
+ task|tasks [indices...] Display the Task details
102
102
  uncomp|unc [indices...] Mark one or more Tasks as not complete
103
103
  url [options] [indices...] Display the associated URL of a Task
104
104
  whoami Display RTM user information
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@beauraines/rtm-cli",
3
- "version": "1.11.0",
3
+ "version": "1.13.0",
4
4
  "description": "RTM CLI",
5
5
  "keywords": [
6
6
  "rtm",
@@ -44,9 +44,11 @@
44
44
  "dateformat": "^4.0.0",
45
45
  "debug": "^4.3.4",
46
46
  "deepmerge": "^4.0.0",
47
+ "iso8601-duration": "^2.1.3",
47
48
  "open": "^8.4.2",
48
49
  "ora": "^5.0.0",
49
50
  "prompt-sync": "^4.2.0",
51
+ "rrule": "^2.8.1",
50
52
  "should-release": "^1.2.0",
51
53
  "standard-version": "^9.5.0",
52
54
  "window-size": "^1.1.0"
package/src/cmd/task.js CHANGED
@@ -6,6 +6,7 @@ const finish = require('../utils/finish.js');
6
6
  const filter = require('../utils/filter');
7
7
  const { indexPrompt } = require('../utils/prompt')
8
8
  const debug = require('debug')('rtm-cli-task');
9
+ const { humanizeDuration, humanizeRecurrence } = require('../utils/format');
9
10
 
10
11
 
11
12
  let TASKS = [];
@@ -83,6 +84,7 @@ async function action(args, env) {
83
84
 
84
85
  module.exports = {
85
86
  command: 'task [indices...]',
87
+ alias: 'tasks',
86
88
  options: [],
87
89
  description: 'Display the Task details',
88
90
  action: action
@@ -92,7 +94,7 @@ function displayTask(taskDetails) {
92
94
  debug(taskDetails)
93
95
  let index = taskDetails.index;
94
96
  // eslint-disable-next-line no-unused-vars
95
- const { _list, list_id, taskseries_id, task_id, _index, name, priority, start, due, completed, isRecurring, isSubtask, estimate, url, tags, notes ,...otherAttributes } = taskDetails.task;
97
+ const { _list, list_id, taskseries_id, task_id, _index, name, priority, start, due, completed, isRecurring, recurrenceRuleRaw, isSubtask, estimate, url, tags, notes, ...otherAttributes } = taskDetails.task;
96
98
 
97
99
  const listName = LIST_MAP.get(list_id) || "Not found";
98
100
 
@@ -111,10 +113,15 @@ function displayTask(taskDetails) {
111
113
  log(`${completed}`)
112
114
  log.style(`Is Recurring: `,styles.index)
113
115
  log(`${isRecurring}`)
116
+ if (isRecurring && recurrenceRuleRaw) {
117
+ log.style(`Recurrence: `,styles.index)
118
+ log(humanizeRecurrence(recurrenceRuleRaw))
119
+ }
120
+
114
121
  log.style(`Is Subtask: `,styles.index)
115
122
  log(`${isSubtask}`)
116
123
  log.style(`Estimate: `,styles.index)
117
- log(`${estimate}`)
124
+ log(humanizeDuration(estimate))
118
125
  log.style(`Url: `,styles.index)
119
126
  log(`${url}`)
120
127
  log.style(`Tags: `,styles.index)
@@ -0,0 +1,64 @@
1
+ 'use strict';
2
+
3
+ const { humanizeDuration, humanizeRecurrence } = require('../utils/format');
4
+
5
+ describe('humanizeDuration', () => {
6
+ test('parses hours and minutes', () => {
7
+ expect(humanizeDuration('PT1H30M')).toBe('1 hour 30 minutes');
8
+ });
9
+
10
+ test('parses days and seconds', () => {
11
+ expect(humanizeDuration('P2DT15S')).toBe('2 days 15 seconds');
12
+ });
13
+
14
+ test('returns input for invalid strings', () => {
15
+ expect(humanizeDuration('invalid')).toBe('invalid');
16
+ });
17
+
18
+ test('returns empty for non-string', () => {
19
+ expect(humanizeDuration(123)).toBe('');
20
+ });
21
+ });
22
+
23
+ describe('humanizeRecurrence', () => {
24
+ test('parses daily recurrence', () => {
25
+ const rawRule = { $t: 'FREQ=DAILY;INTERVAL=1' };
26
+ expect(humanizeRecurrence(rawRule)).toMatch(/every day/i);
27
+ });
28
+
29
+ test('parses weekly recurrence with interval', () => {
30
+ const rawRule = { $t: 'FREQ=WEEKLY;INTERVAL=2;BYDAY=MO,WE' };
31
+ const result = humanizeRecurrence(rawRule);
32
+ expect(result).toMatch(/every 2 weeks on Monday, Wednesday/i);
33
+ });
34
+
35
+ test('parses default weekly recurrence', () => {
36
+ const rawRule = { every: '0', $t: 'FREQ=WEEKLY;INTERVAL=1;WKST=SU' };
37
+ expect(humanizeRecurrence(rawRule)).toMatch(/every week/i);
38
+ });
39
+
40
+ test('returns raw for invalid rule', () => {
41
+ const rawRule = { $t: 'invalid' };
42
+ expect(humanizeRecurrence(rawRule)).toBe('invalid');
43
+ });
44
+
45
+ test('empty when no $t', () => {
46
+ expect(humanizeRecurrence({ every: '1' })).toBe('');
47
+ });
48
+ });
49
+
50
+ describe('humanizeRecurrence additional input types', () => {
51
+ test('parses recurrence from stringified JSON', () => {
52
+ const str = '{"$t":"FREQ=WEEKLY;INTERVAL=1;WKST=SU"}';
53
+ expect(humanizeRecurrence(str)).toMatch(/every week/i);
54
+ });
55
+
56
+ test('parses recurrence from raw rule string', () => {
57
+ const raw = 'FREQ=DAILY;INTERVAL=1';
58
+ expect(humanizeRecurrence(raw)).toMatch(/every day/i);
59
+ });
60
+
61
+ test('returns empty for non-rule string without JSON', () => {
62
+ expect(humanizeRecurrence('not a rule')).toBe('');
63
+ });
64
+ });
@@ -0,0 +1,68 @@
1
+ 'use strict';
2
+
3
+ const { RRule } = require('rrule');
4
+ const { parse: parseIso } = require('iso8601-duration');
5
+
6
+ /**
7
+ * Convert an ISO8601 duration string (e.g. "PT1H30M") into a human-readable string.
8
+ * @param {string} iso
9
+ * @returns {string}
10
+ */
11
+ function humanizeDuration(iso) {
12
+ if (typeof iso !== 'string') return '';
13
+ let dur;
14
+ try {
15
+ dur = parseIso(iso);
16
+ } catch (e) {
17
+ return iso;
18
+ }
19
+ const parts = [];
20
+ if (dur.years) parts.push(`${dur.years} year${dur.years > 1 ? 's' : ''}`);
21
+ if (dur.months) parts.push(`${dur.months} month${dur.months > 1 ? 's' : ''}`);
22
+ if (dur.days) parts.push(`${dur.days} day${dur.days > 1 ? 's' : ''}`);
23
+ if (dur.hours) parts.push(`${dur.hours} hour${dur.hours > 1 ? 's' : ''}`);
24
+ if (dur.minutes) parts.push(`${dur.minutes} minute${dur.minutes > 1 ? 's' : ''}`);
25
+ if (dur.seconds) parts.push(`${dur.seconds} second${dur.seconds > 1 ? 's' : ''}`);
26
+ return parts.length ? parts.join(' ') : iso;
27
+ }
28
+
29
+ /**
30
+ * Convert a recurrence rule object with property $t (RFC5545 string) into a human-readable string.
31
+ * @param {object} rawRule
32
+ * @returns {string}
33
+ */
34
+ function humanizeRecurrence(input) {
35
+ let ruleObj;
36
+ if (typeof input === 'string') {
37
+ // Try to parse JSON string
38
+ try {
39
+ ruleObj = JSON.parse(input);
40
+ } catch (e) {
41
+ // Not JSON: maybe raw RRULE string
42
+ if (input.includes('FREQ=')) {
43
+ try {
44
+ return RRule.fromString(input).toText();
45
+ } catch (e) {
46
+ return input;
47
+ }
48
+ }
49
+ return '';
50
+ }
51
+ } else if (typeof input === 'object' && input !== null) {
52
+ ruleObj = input;
53
+ } else {
54
+ return '';
55
+ }
56
+
57
+ const ruleString = ruleObj.$t;
58
+ if (typeof ruleString !== 'string') {
59
+ return '';
60
+ }
61
+ try {
62
+ return RRule.fromString(ruleString).toText();
63
+ } catch (e) {
64
+ return ruleString;
65
+ }
66
+ }
67
+
68
+ module.exports = { humanizeDuration, humanizeRecurrence };