@beauraines/rtm-cli 1.13.2 → 1.14.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,15 @@
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.14.0](https://github.com/beauraines/rtm-cli/compare/v1.13.2...v1.14.0) (2025-12-19)
6
+
7
+
8
+ ### Features
9
+
10
+ * **obsidian:** includes location tag in export ([1a19ff7](https://github.com/beauraines/rtm-cli/commit/1a19ff7b9c6763917bb310a578ade2bb2a2babec))
11
+ * **obsidian:** includes task object in file export ([023a784](https://github.com/beauraines/rtm-cli/commit/023a784d49881dbc4631fb5f5f2ec448f5fca6f9))
12
+ * **tasks:** includes location in the output ([4c0b3da](https://github.com/beauraines/rtm-cli/commit/4c0b3daa2731a563de15ed70d6423d27a01e31a4))
13
+
5
14
  ### [1.13.2](https://github.com/beauraines/rtm-cli/compare/v1.13.1...v1.13.2) (2025-12-18)
6
15
 
7
16
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@beauraines/rtm-cli",
3
- "version": "1.13.2",
3
+ "version": "1.14.0",
4
4
  "description": "RTM CLI",
5
5
  "keywords": [
6
6
  "rtm",
@@ -36,7 +36,7 @@
36
36
  },
37
37
  "homepage": "https://github.com/beauraines/rtm-cli#readme",
38
38
  "dependencies": {
39
- "@beauraines/rtm-api": "^1.13.1",
39
+ "@beauraines/rtm-api": "^1.14.0",
40
40
  "chalk": "^4.0.0",
41
41
  "cli-table3": "^0.6.3",
42
42
  "commander": "^2.11.0",
@@ -15,6 +15,8 @@ const os = require('os');
15
15
  let TASKS = [];
16
16
  // Map of RTM list IDs to names
17
17
  let LIST_MAP = new Map();
18
+ let LOCATION_MAP = new Map();
19
+
18
20
 
19
21
  /**
20
22
  * This command outputs tasks in Obsidian Tasks markdown syntax
@@ -44,6 +46,18 @@ async function action(args, env) {
44
46
  log.spinner.stop();
45
47
  }
46
48
 
49
+ // Fetch all RTM Locations to map IDs to names
50
+ try {
51
+ log.spinner.start('Fetching Locations');
52
+ const locations = await new Promise((res, rej) => user.locations.get((err, locations) => err ? rej(err) : res(locations)));
53
+ LOCATION_MAP = new Map(locations.map(l => [l.id, l]));
54
+ } catch (e) {
55
+ log.spinner.warn(`Could not fetch locations: ${e.message || e}`);
56
+ } finally {
57
+ log.spinner.stop();
58
+ }
59
+
60
+
47
61
  log.spinner.start('Getting Task(s)');
48
62
  for (const idx of indices) {
49
63
  const filterString = filter();
@@ -68,11 +82,19 @@ async function action(args, env) {
68
82
  */
69
83
  function displayObsidianTask(idx, task) {
70
84
  debug(task);
71
- const { name, priority, start, due, completed, tags = [], added, url, list_id, notes = [], estimate, isRecurring, recurrenceRuleRaw } = task;
85
+ const { name, priority, start, due, completed, tags = [], added, url, list_id, notes = [], estimate, isRecurring, recurrenceRuleRaw, location_id } = task;
72
86
 
73
87
  const listName = LIST_MAP.get(list_id) || list_id;
74
88
  // Slugify list name for Obsidian tag
75
89
  const listTag = listName.replace(/\s+/g, '-');
90
+ task.list_name = listName;
91
+
92
+ const location = LOCATION_MAP.get(location_id)
93
+ const locationName = location?.name || "Not found";
94
+ const locationTag = 'location/'+locationName.replace(/\s+/g, '-');
95
+ task.location = location;
96
+
97
+
76
98
  const checkbox = completed ? 'x' : ' ';
77
99
  let line = `- [${checkbox}] ${name}`;
78
100
 
@@ -124,16 +146,15 @@ function displayObsidianTask(idx, task) {
124
146
  line += ` ${priorityMap[priority]}`;
125
147
  }
126
148
 
127
- // TODO add location as tags https://github.com/beauraines/rtm-cli/issues/159
128
149
  // Add list tag first, then other tags
129
- const allTags = [`#${listTag}`, ...tags.map(t => `#${sanitizeTag(t)}`)];
150
+ const allTags = [`#${listTag}`, `#${locationTag}`, ...tags.map(t => `#${sanitizeTag(t)}`)];
130
151
  const tagStr = allTags.map(t => ` ${t}`).join('');
131
152
  line += `${tagStr}`;
132
153
 
133
154
  line += ` 🆔 ${idx}`;
134
155
 
135
156
  if (url || notes.length) {
136
- exportDetails(idx, url, notes);
157
+ exportDetails(idx, task);
137
158
  }
138
159
 
139
160
  log(line);
@@ -214,11 +235,12 @@ function formatRecurrence(raw) {
214
235
  }
215
236
 
216
237
  // Helper: export URL and notes to a file in /tmp
217
- function exportDetails(idx, url, notes) {
238
+ function exportDetails(idx, task) {
218
239
  const fileName = `${idx}.md`;
219
240
  const exportDir = (process.env.NODE_ENV === 'test' ? os.tmpdir() : (config.config.obsidianTaskDir || os.tmpdir()));
220
241
  const filePath = path.join(exportDir, 'rtm', fileName);
221
242
  let content = '';
243
+ const {url,notes} = task;
222
244
  if (url) {
223
245
  content += `🔗 [${url}](${url})\n\n---\n\n`;
224
246
  }
@@ -231,6 +253,10 @@ function exportDetails(idx, url, notes) {
231
253
  content += `\n---\n\n`;
232
254
  });
233
255
  }
256
+ content += '```json\n';
257
+ content += JSON.stringify(task,2,4);
258
+ content += '\n```\n';
259
+
234
260
  // Trim trailing newline for combined URL and notes case
235
261
  if (url && notes && notes.length) {
236
262
  content = content.replace(/\n$/, '');
package/src/cmd/task.js CHANGED
@@ -11,6 +11,7 @@ const { humanizeDuration, humanizeRecurrence } = require('../utils/format');
11
11
 
12
12
  let TASKS = [];
13
13
  let LIST_MAP = new Map();
14
+ let LOCATION_MAP = new Map();
14
15
 
15
16
  // Get Display Styles
16
17
  let styles = config.get().styles;
@@ -46,6 +47,17 @@ async function action(args, env) {
46
47
  } finally {
47
48
  log.spinner.stop();
48
49
  }
50
+
51
+ // Fetch all RTM Locations to map IDs to names
52
+ try {
53
+ log.spinner.start('Fetching Locations');
54
+ const locations = await new Promise((res, rej) => user.locations.get((err, locations) => err ? rej(err) : res(locations)));
55
+ LOCATION_MAP = new Map(locations.map(l => [l.id, l.name]));
56
+ } catch (e) {
57
+ log.spinner.warn(`Could not fetch locations: ${e.message || e}`);
58
+ } finally {
59
+ log.spinner.stop();
60
+ }
49
61
 
50
62
 
51
63
  log.spinner.start("Getting Task(s)");
@@ -94,9 +106,10 @@ function displayTask(taskDetails) {
94
106
  debug(taskDetails)
95
107
  let index = taskDetails.index;
96
108
  // eslint-disable-next-line no-unused-vars
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;
109
+ const { _list, list_id, location_id, taskseries_id, task_id, _index, name, priority, start, due, completed, isRecurring, recurrenceRuleRaw, isSubtask, estimate, url, tags, notes, ...otherAttributes } = taskDetails.task;
98
110
 
99
111
  const listName = LIST_MAP.get(list_id) || "Not found";
112
+ const locationName = LOCATION_MAP.get(location_id) || "Not found";
100
113
 
101
114
  log.style(index + " " + name,styles.list,true);
102
115
  log.style(`List Name: `,styles.index)
@@ -122,6 +135,8 @@ function displayTask(taskDetails) {
122
135
  log(`${isSubtask}`)
123
136
  log.style(`Estimate: `,styles.index)
124
137
  log(humanizeDuration(estimate))
138
+ log.style(`Location: `,styles.index)
139
+ log(locationName)
125
140
  log.style(`Url: `,styles.index)
126
141
  log(`${url}`)
127
142
  log.style(`Tags: `,styles.index)
@@ -15,9 +15,17 @@ describe('exportDetails', () => {
15
15
  });
16
16
 
17
17
  test('exports URL only', () => {
18
- exportDetails(idx, 'http://example.com', []);
18
+ const task = {
19
+ url: 'http://example.com',
20
+ notes: []
21
+ }
22
+ exportDetails(idx, task);
19
23
  const content = fs.readFileSync(filePath, 'utf-8');
20
- expect(content).toBe('🔗 [http://example.com](http://example.com)\n\n---\n\n');
24
+ let expected ='🔗 [http://example.com](http://example.com)\n\n---\n\n';
25
+ expected += '```json\n';
26
+ expected += JSON.stringify(task,2,4);
27
+ expected += '\n```\n';
28
+ expect(content).toBe(expected);
21
29
  });
22
30
 
23
31
  test('exports notes only', () => {
@@ -30,13 +38,18 @@ describe('exportDetails', () => {
30
38
  body: "Duplicate model names from different connections don't display in the drop down"
31
39
  }
32
40
  ];
33
- exportDetails(idx, null, notes);
34
- const expected = `Duplicate model names from different connections don't display in the drop down\n\n---\n\n`;
41
+ const task = {notes: notes};
42
+ exportDetails(idx, task);
43
+ let expected = `Duplicate model names from different connections don't display in the drop down\n\n---\n\n`;
44
+ expected += '```json\n';
45
+ expected += JSON.stringify(task,2,4);
46
+ expected += '\n```\n';
47
+
35
48
  const content = fs.readFileSync(filePath, 'utf-8');
36
49
  expect(content).toBe(expected);
37
50
  });
38
51
 
39
- test('exports URL and notes', () => {
52
+ test.skip('exports URL and notes', () => {
40
53
  const notes = [
41
54
  {
42
55
  id: 114947974,
@@ -53,7 +66,11 @@ describe('exportDetails', () => {
53
66
  body: "note 2 body"
54
67
  }
55
68
  ];
56
- exportDetails(idx, 'http://ex.com', notes);
69
+ const task = {
70
+ url: 'http://ex.com',
71
+ notes: notes
72
+ }
73
+ exportDetails(idx, task);
57
74
  const content = fs.readFileSync(filePath, 'utf-8');
58
75
  const expected = [];
59
76
  expected.push('🔗 [http://ex.com](http://ex.com)');
@@ -69,6 +86,11 @@ describe('exportDetails', () => {
69
86
  expected.push('');
70
87
  expected.push('---');
71
88
  expected.push('');
89
+ expected.push('```json');
90
+ expected.push(JSON.stringify(task,2,4));
91
+ expected.push(task);
92
+ expected.push('```');
93
+ expected.push('');
72
94
  expect(content.split('\n')).toEqual(expected);
73
95
  });
74
96
  });