@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 +16 -0
- package/README.md +1 -1
- package/package.json +2 -2
- package/src/cmd/obsidian.js +34 -5
- package/src/cmd/task.js +16 -1
- package/src/tests/obsidian.test.js +28 -6
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
|
|
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.
|
|
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.
|
|
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",
|
package/src/cmd/obsidian.js
CHANGED
|
@@ -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,
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
34
|
-
|
|
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
|
-
|
|
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
|
});
|