@beauraines/rtm-cli 1.13.2 → 1.14.1

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,22 @@
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.1](https://github.com/beauraines/rtm-cli/compare/v1.14.0...v1.14.1) (2025-12-19)
6
+
7
+
8
+ ### Bug Fixes
9
+
10
+ * properly handles empty location tag, file name written to std err ([#166](https://github.com/beauraines/rtm-cli/issues/166)) ([7834ab0](https://github.com/beauraines/rtm-cli/commit/7834ab00d8105f67c35a07f396fbe3cb5de61eb5))
11
+
12
+ ## [1.14.0](https://github.com/beauraines/rtm-cli/compare/v1.13.2...v1.14.0) (2025-12-19)
13
+
14
+
15
+ ### Features
16
+
17
+ * **obsidian:** includes location tag in export ([1a19ff7](https://github.com/beauraines/rtm-cli/commit/1a19ff7b9c6763917bb310a578ade2bb2a2babec))
18
+ * **obsidian:** includes task object in file export ([023a784](https://github.com/beauraines/rtm-cli/commit/023a784d49881dbc4631fb5f5f2ec448f5fca6f9))
19
+ * **tasks:** includes location in the output ([4c0b3da](https://github.com/beauraines/rtm-cli/commit/4c0b3daa2731a563de15ed70d6423d27a01e31a4))
20
+
5
21
  ### [1.13.2](https://github.com/beauraines/rtm-cli/compare/v1.13.1...v1.13.2) (2025-12-18)
6
22
 
7
23
 
package/README.md CHANGED
@@ -176,5 +176,5 @@ and `rtm obsidian 4330` would output
176
176
  which could be written to a file in your Obsidian Vault.
177
177
 
178
178
  ```shell
179
- rtm ls due:today | cut -wf1 | sort | xargs ./src/cli.js obsidian >> ~/LocalDocs/Test/Tasks/rtm.md
179
+ rtm ls due:today | cut -wf1 | sort | xargs rtm obsidian >> ~/LocalDocs/Test/Tasks/rtm.md
180
180
  ```
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.1",
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,20 @@ 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;
94
+ let locationTag ='';
95
+ locationName ? locationTag = '#location/'+locationName.replace(/\s+/g, '-') : null ;
96
+ task.location = location;
97
+
98
+
76
99
  const checkbox = completed ? 'x' : ' ';
77
100
  let line = `- [${checkbox}] ${name}`;
78
101
 
@@ -124,16 +147,15 @@ function displayObsidianTask(idx, task) {
124
147
  line += ` ${priorityMap[priority]}`;
125
148
  }
126
149
 
127
- // TODO add location as tags https://github.com/beauraines/rtm-cli/issues/159
128
150
  // Add list tag first, then other tags
129
- const allTags = [`#${listTag}`, ...tags.map(t => `#${sanitizeTag(t)}`)];
151
+ const allTags = [`#${listTag}`, `${locationTag}`, ...tags.map(t => `#${sanitizeTag(t)}`)];
130
152
  const tagStr = allTags.map(t => ` ${t}`).join('');
131
153
  line += `${tagStr}`;
132
154
 
133
155
  line += ` 🆔 ${idx}`;
134
156
 
135
157
  if (url || notes.length) {
136
- exportDetails(idx, url, notes);
158
+ exportDetails(idx, task);
137
159
  }
138
160
 
139
161
  log(line);
@@ -214,11 +236,12 @@ function formatRecurrence(raw) {
214
236
  }
215
237
 
216
238
  // Helper: export URL and notes to a file in /tmp
217
- function exportDetails(idx, url, notes) {
239
+ function exportDetails(idx, task) {
218
240
  const fileName = `${idx}.md`;
219
241
  const exportDir = (process.env.NODE_ENV === 'test' ? os.tmpdir() : (config.config.obsidianTaskDir || os.tmpdir()));
220
242
  const filePath = path.join(exportDir, 'rtm', fileName);
221
243
  let content = '';
244
+ const {url,notes} = task;
222
245
  if (url) {
223
246
  content += `🔗 [${url}](${url})\n\n---\n\n`;
224
247
  }
@@ -231,6 +254,10 @@ function exportDetails(idx, url, notes) {
231
254
  content += `\n---\n\n`;
232
255
  });
233
256
  }
257
+ content += '```json\n';
258
+ content += JSON.stringify(task,2,4);
259
+ content += '\n```\n';
260
+
234
261
  // Trim trailing newline for combined URL and notes case
235
262
  if (url && notes && notes.length) {
236
263
  content = content.replace(/\n$/, '');
@@ -239,6 +266,8 @@ function exportDetails(idx, url, notes) {
239
266
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
240
267
  try {
241
268
  fs.writeFileSync(filePath, content);
269
+ console.error(`Task detail files written to ${filePath}`)
270
+
242
271
  } catch (e) {
243
272
  console.error(`Failed to write details file for task ${idx}: ${e}`);
244
273
  }
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
  });