asana-cli-skill 1.2.0 → 1.3.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 +1 -1
- package/bin/asana-cli.js +1 -0
- package/package.json +2 -2
- package/skills/asana/SKILL.md +1 -1
- package/src/commands/update.js +21 -3
- package/src/utils.js +10 -0
package/README.md
CHANGED
|
@@ -189,7 +189,7 @@ Line 2"
|
|
|
189
189
|
|
|
190
190
|
### `asana-cli update <url-or-id>`
|
|
191
191
|
|
|
192
|
-
Updates a task: `--name`, `--notes` (replace), `--append-notes` (add to the end), `--assignee` (`none` unassigns), `--due` (`none` clears), `--complete` or `--reopen`,
|
|
192
|
+
Updates a task: `--name`, `--notes` (replace), `--append-notes` (add to the end), `--assignee` (`none` unassigns), `--due` (`none` clears), `--complete` or `--reopen`, custom fields with `--field "Name=Value"` (repeatable), and images shown inline at the end of the description with `--image <path>` (repeatable; the text is kept unless `--notes` replaces it). Enum options match by name ignoring case, accents and icons, so `--field "Priority=Urgent"` selects an option shown with a leading icon.
|
|
193
193
|
|
|
194
194
|
```bash
|
|
195
195
|
asana-cli update 5678 --due 2026-10-31 --append-notes "Blocked by the vendor"
|
package/bin/asana-cli.js
CHANGED
|
@@ -189,6 +189,7 @@ program
|
|
|
189
189
|
.option('--complete', 'Mark as completed (without a comment)')
|
|
190
190
|
.option('--reopen', 'Mark as not completed')
|
|
191
191
|
.option('--field <name=value>', 'Set a custom field, e.g. "Priority=High" (repeatable)', collectMultiple, [])
|
|
192
|
+
.option('--image <path>', 'Attach an image and show it inline at the end of the description (repeatable)', collectMultiple, [])
|
|
192
193
|
.action(async (urlOrId, options) => {
|
|
193
194
|
await updateCommand(urlOrId, options, getClient());
|
|
194
195
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "asana-cli-skill",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Minimal CLI for Asana task management, built to power a Claude Code skill",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"url": "https://github.com/giacomeli/asana-cli-skill/issues"
|
|
15
15
|
},
|
|
16
16
|
"bin": {
|
|
17
|
-
"asana-cli": "
|
|
17
|
+
"asana-cli": "bin/asana-cli.js"
|
|
18
18
|
},
|
|
19
19
|
"files": [
|
|
20
20
|
"bin",
|
package/skills/asana/SKILL.md
CHANGED
|
@@ -144,7 +144,7 @@ Quick reference for `asana-cli`:
|
|
|
144
144
|
| `asana-cli my-tasks` | Open tasks assigned to the user |
|
|
145
145
|
| `asana-cli comments <url-or-id> [--last n]` | Read a task's comments |
|
|
146
146
|
| `asana-cli create-task -n "name" -p <project> [-s section]` | Create a task |
|
|
147
|
-
| `asana-cli update <id> [--name/--notes/--append-notes/--assignee/--due/--complete/--reopen/--field "Name=Value"]` | Update a task, including custom fields |
|
|
147
|
+
| `asana-cli update <id> [--name/--notes/--append-notes/--assignee/--due/--complete/--reopen/--field "Name=Value"/--image <path>]` | Update a task, including custom fields and inline images in the description |
|
|
148
148
|
| `asana-cli move <id> --after <sibling-id>` | Reorder subtasks or change the parent |
|
|
149
149
|
| `asana-cli edit-comment <comment-id> -m "text"` | Fix a comment's text, keeping inline images |
|
|
150
150
|
| `asana-cli delete-comment <comment-id>` | Delete a comment (ID from `comments`) |
|
package/src/commands/update.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { existsSync } from 'fs';
|
|
2
|
+
|
|
3
|
+
import { parseTaskId, buildTaskFields, unescapeNewlines, parseFieldAssignments, buildHtmlNotes } from '../utils.js';
|
|
2
4
|
import { formatSuccess } from '../formatter.js';
|
|
3
5
|
|
|
4
6
|
export async function updateCommand(urlOrId, options, client) {
|
|
@@ -11,6 +13,11 @@ export async function updateCommand(urlOrId, options, client) {
|
|
|
11
13
|
throw new Error('Use either --complete or --reopen, not both');
|
|
12
14
|
}
|
|
13
15
|
|
|
16
|
+
const images = options.image || [];
|
|
17
|
+
for (const filePath of images) {
|
|
18
|
+
if (!existsSync(filePath)) throw new Error(`File not found: ${filePath}`);
|
|
19
|
+
}
|
|
20
|
+
|
|
14
21
|
const data = buildTaskFields(options);
|
|
15
22
|
|
|
16
23
|
if (options.appendNotes !== undefined) {
|
|
@@ -23,8 +30,19 @@ export async function updateCommand(urlOrId, options, client) {
|
|
|
23
30
|
|
|
24
31
|
const fields = parseFieldAssignments(options.field || []);
|
|
25
32
|
|
|
26
|
-
if (Object.keys(data).length === 0 && fields.length === 0) {
|
|
27
|
-
throw new Error('Nothing to update. Pass at least one of --name, --notes, --append-notes, --assignee, --due, --complete, --reopen, --field');
|
|
33
|
+
if (Object.keys(data).length === 0 && fields.length === 0 && images.length === 0) {
|
|
34
|
+
throw new Error('Nothing to update. Pass at least one of --name, --notes, --append-notes, --assignee, --due, --complete, --reopen, --field, --image');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Images go inline after the description text, which is kept when --notes is not passed.
|
|
38
|
+
if (images.length > 0) {
|
|
39
|
+
const text = data.notes ?? (await client.getTask(taskId)).notes ?? '';
|
|
40
|
+
delete data.notes;
|
|
41
|
+
const gids = [];
|
|
42
|
+
for (const filePath of images) {
|
|
43
|
+
gids.push((await client.uploadAttachment(taskId, filePath)).gid);
|
|
44
|
+
}
|
|
45
|
+
data.html_notes = buildHtmlNotes(text, gids);
|
|
28
46
|
}
|
|
29
47
|
|
|
30
48
|
if (Object.keys(data).length > 0) {
|
package/src/utils.js
CHANGED
|
@@ -136,3 +136,13 @@ export function parseFieldAssignments(pairs) {
|
|
|
136
136
|
return { name: pair.slice(0, index).trim(), value: pair.slice(index + 1).trim() };
|
|
137
137
|
});
|
|
138
138
|
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Builds a rich-text description: the plain text (escaped) followed by the task's own
|
|
142
|
+
* attachments shown inline. Asana only renders <img data-asana-gid> for attachments of the same task.
|
|
143
|
+
*/
|
|
144
|
+
export function buildHtmlNotes(text, imageGids) {
|
|
145
|
+
const escaped = text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
146
|
+
const images = imageGids.map((gid) => `<img data-asana-gid="${gid}"/>`).join('');
|
|
147
|
+
return `<body>${escaped ? `${escaped}\n` : ''}${images}</body>`;
|
|
148
|
+
}
|