@llodev/pm-tasks-asana 0.0.0-pr-22-9002a06
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 +282 -0
- package/LICENSE +21 -0
- package/README.md +100 -0
- package/SKILL.md +198 -0
- package/anti-patterns/asana.md +62 -0
- package/dist/adapter.d.ts +19 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/adapter.js +18 -0
- package/dist/adapter.js.map +1 -0
- package/dist/bin/init.d.ts +3 -0
- package/dist/bin/init.d.ts.map +1 -0
- package/dist/bin/init.js +280 -0
- package/dist/bin/init.js.map +1 -0
- package/dist/doctor-cli.d.ts +6 -0
- package/dist/doctor-cli.d.ts.map +1 -0
- package/dist/doctor-cli.js +166 -0
- package/dist/doctor-cli.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/transport-asana.d.ts +44 -0
- package/dist/transport-asana.d.ts.map +1 -0
- package/dist/transport-asana.js +254 -0
- package/dist/transport-asana.js.map +1 -0
- package/docs/i18n/README.es-ES.md +100 -0
- package/docs/i18n/README.pt-BR.md +100 -0
- package/i18n/en-US.json +24 -0
- package/i18n/es-ES.json +24 -0
- package/i18n/pt-BR.json +24 -0
- package/manifest.json +12 -0
- package/package.json +67 -0
- package/references/operations.md +62 -0
- package/schemas/config.json +173 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Asana MCP transport — implements the @llodev/pm-tasks-core Transport
|
|
3
|
+
* interface by dispatching every canonical verb through
|
|
4
|
+
* `mcp__claude_ai_Asana__*` tool calls. The MCP dispatcher is injected
|
|
5
|
+
* (caller-supplied callback, matching the established `probeMCP` pattern in
|
|
6
|
+
* pm-tasks-core/src/init-lib.ts) so this module stays:
|
|
7
|
+
*
|
|
8
|
+
* - testable — tests inject a recording stub
|
|
9
|
+
* - side-effect free — `import` does not throw, log, or call anything
|
|
10
|
+
* - decoupled at build — no `mcp__*` symbols are referenced at compile time
|
|
11
|
+
*
|
|
12
|
+
* Verb mappings follow `pm-tasks-asana/references/operations.md` verbatim.
|
|
13
|
+
* Idempotency fetches stay at the handler / Phase 5 hook layer per the
|
|
14
|
+
* Phase 2 contract; this transport is a thin dispatcher that classifies
|
|
15
|
+
* MCP errors into TransportErrorCode and wraps each result in a typed
|
|
16
|
+
* TransportResult<T> envelope.
|
|
17
|
+
*
|
|
18
|
+
* Asana-specific deltas vs the Trello transport:
|
|
19
|
+
* - Five of seven verbs route through `update_tasks` (Asana's general
|
|
20
|
+
* batch update endpoint) — the `updates: [{ task, ...fields }]` shape
|
|
21
|
+
* is uniform across move / checklist.check / close / due-date / assignee.
|
|
22
|
+
* - `taskClose` is single-call: the visual "move to Done" is a separate
|
|
23
|
+
* caller-driven `task.move` per operations.md L12. `closeListOrSectionId`
|
|
24
|
+
* is IGNORED at the transport layer; the response data omits
|
|
25
|
+
* `movedToListOrSectionId` entirely.
|
|
26
|
+
* - `taskDueDateSet` converts ISO 8601 → `YYYY-MM-DD` via `.slice(0,10)`
|
|
27
|
+
* because Asana's `due_on` field rejects full timestamps. Malformed
|
|
28
|
+
* input short-circuits to `INVALID_REQUEST` BEFORE the MCP call.
|
|
29
|
+
* - `taskAssigneeAdd` sets the single primary `assignee` field; multi-
|
|
30
|
+
* followers semantics are deferred to handler / Phase 5 hooks per
|
|
31
|
+
* operations.md L14 simplification.
|
|
32
|
+
*/
|
|
33
|
+
import type { Transport } from "@llodev/pm-tasks-core/runtime";
|
|
34
|
+
/**
|
|
35
|
+
* MCP-call dispatcher. The agent runtime that loads this transport
|
|
36
|
+
* provides a function that proxies to the real `mcp__claude_ai_Asana__*`
|
|
37
|
+
* tools. Tests inject a recording stub.
|
|
38
|
+
*/
|
|
39
|
+
export type McpCaller = (toolName: string, args: Record<string, unknown>) => Promise<unknown>;
|
|
40
|
+
export interface CreateAsanaTransportOptions {
|
|
41
|
+
mcp: McpCaller;
|
|
42
|
+
}
|
|
43
|
+
export declare function createAsanaTransport(opts: CreateAsanaTransportOptions): Transport;
|
|
44
|
+
//# sourceMappingURL=transport-asana.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport-asana.d.ts","sourceRoot":"","sources":["../src/transport-asana.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,KAAK,EACV,SAAS,EAiBV,MAAM,+BAA+B,CAAC;AAEvC;;;;GAIG;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;AAE9F,MAAM,WAAW,2BAA2B;IAC1C,GAAG,EAAE,SAAS,CAAC;CAChB;AA0DD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,2BAA2B,GAAG,SAAS,CAsLjF"}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Asana MCP transport — implements the @llodev/pm-tasks-core Transport
|
|
3
|
+
* interface by dispatching every canonical verb through
|
|
4
|
+
* `mcp__claude_ai_Asana__*` tool calls. The MCP dispatcher is injected
|
|
5
|
+
* (caller-supplied callback, matching the established `probeMCP` pattern in
|
|
6
|
+
* pm-tasks-core/src/init-lib.ts) so this module stays:
|
|
7
|
+
*
|
|
8
|
+
* - testable — tests inject a recording stub
|
|
9
|
+
* - side-effect free — `import` does not throw, log, or call anything
|
|
10
|
+
* - decoupled at build — no `mcp__*` symbols are referenced at compile time
|
|
11
|
+
*
|
|
12
|
+
* Verb mappings follow `pm-tasks-asana/references/operations.md` verbatim.
|
|
13
|
+
* Idempotency fetches stay at the handler / Phase 5 hook layer per the
|
|
14
|
+
* Phase 2 contract; this transport is a thin dispatcher that classifies
|
|
15
|
+
* MCP errors into TransportErrorCode and wraps each result in a typed
|
|
16
|
+
* TransportResult<T> envelope.
|
|
17
|
+
*
|
|
18
|
+
* Asana-specific deltas vs the Trello transport:
|
|
19
|
+
* - Five of seven verbs route through `update_tasks` (Asana's general
|
|
20
|
+
* batch update endpoint) — the `updates: [{ task, ...fields }]` shape
|
|
21
|
+
* is uniform across move / checklist.check / close / due-date / assignee.
|
|
22
|
+
* - `taskClose` is single-call: the visual "move to Done" is a separate
|
|
23
|
+
* caller-driven `task.move` per operations.md L12. `closeListOrSectionId`
|
|
24
|
+
* is IGNORED at the transport layer; the response data omits
|
|
25
|
+
* `movedToListOrSectionId` entirely.
|
|
26
|
+
* - `taskDueDateSet` converts ISO 8601 → `YYYY-MM-DD` via `.slice(0,10)`
|
|
27
|
+
* because Asana's `due_on` field rejects full timestamps. Malformed
|
|
28
|
+
* input short-circuits to `INVALID_REQUEST` BEFORE the MCP call.
|
|
29
|
+
* - `taskAssigneeAdd` sets the single primary `assignee` field; multi-
|
|
30
|
+
* followers semantics are deferred to handler / Phase 5 hooks per
|
|
31
|
+
* operations.md L14 simplification.
|
|
32
|
+
*/
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Internal helpers
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
function isObjectWith(value, key) {
|
|
37
|
+
return typeof value === "object" && value !== null && key in value;
|
|
38
|
+
}
|
|
39
|
+
function classifyError(e) {
|
|
40
|
+
if (!(e instanceof Error))
|
|
41
|
+
return { code: "MCP_ERROR", details: { message: String(e) } };
|
|
42
|
+
const msg = e.message.toLowerCase();
|
|
43
|
+
if (msg.includes("not found") || msg.includes("404")) {
|
|
44
|
+
return { code: "NOT_FOUND", details: { message: e.message } };
|
|
45
|
+
}
|
|
46
|
+
if (msg.includes("401") ||
|
|
47
|
+
msg.includes("403") ||
|
|
48
|
+
msg.includes("unauthorized") ||
|
|
49
|
+
msg.includes("forbidden")) {
|
|
50
|
+
return { code: "AUTH_ERROR", details: { message: e.message } };
|
|
51
|
+
}
|
|
52
|
+
if (msg.includes("429") || msg.includes("rate limit")) {
|
|
53
|
+
return { code: "RATE_LIMITED", details: { message: e.message } };
|
|
54
|
+
}
|
|
55
|
+
return { code: "MCP_ERROR", details: { message: e.message } };
|
|
56
|
+
}
|
|
57
|
+
function shapeError(verb) {
|
|
58
|
+
return {
|
|
59
|
+
ok: false,
|
|
60
|
+
code: "MCP_ERROR",
|
|
61
|
+
details: { message: "Asana MCP returned unexpected response shape", verb },
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Validate ISO 8601 prefix `YYYY-MM-DD` and return the date portion.
|
|
66
|
+
* Returns null when the input is empty or doesn't start with that shape.
|
|
67
|
+
* Asana's `due_on` field accepts only `YYYY-MM-DD`, not full timestamps.
|
|
68
|
+
*/
|
|
69
|
+
function isoToDueOn(input) {
|
|
70
|
+
if (typeof input !== "string" || input.length < 10)
|
|
71
|
+
return null;
|
|
72
|
+
const head = input.slice(0, 10);
|
|
73
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(head))
|
|
74
|
+
return null;
|
|
75
|
+
return head;
|
|
76
|
+
}
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
// Factory
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
export function createAsanaTransport(opts) {
|
|
81
|
+
const { mcp } = opts;
|
|
82
|
+
return {
|
|
83
|
+
// -------------------------------------------------------------------
|
|
84
|
+
// 1. task.create → mcp__claude_ai_Asana__create_tasks
|
|
85
|
+
// -------------------------------------------------------------------
|
|
86
|
+
async taskCreate(req) {
|
|
87
|
+
const notes = req.clientToken
|
|
88
|
+
? `[ct:${req.clientToken}] ${req.description ?? ""}`.trim()
|
|
89
|
+
: req.description;
|
|
90
|
+
const taskPayload = {
|
|
91
|
+
name: req.name,
|
|
92
|
+
memberships: [{ project: req.boardOrProjectId, section: req.listOrSectionId }],
|
|
93
|
+
};
|
|
94
|
+
if (notes !== undefined)
|
|
95
|
+
taskPayload.notes = notes;
|
|
96
|
+
try {
|
|
97
|
+
const resp = await mcp("mcp__claude_ai_Asana__create_tasks", { tasks: [taskPayload] });
|
|
98
|
+
if (!isObjectWith(resp, "tasks") || !Array.isArray(resp.tasks) || resp.tasks.length === 0) {
|
|
99
|
+
return shapeError("task.create");
|
|
100
|
+
}
|
|
101
|
+
const first = resp.tasks[0];
|
|
102
|
+
if (!isObjectWith(first, "gid") || typeof first.gid !== "string") {
|
|
103
|
+
return shapeError("task.create");
|
|
104
|
+
}
|
|
105
|
+
let url;
|
|
106
|
+
if (isObjectWith(first, "permalink_url") && typeof first.permalink_url === "string") {
|
|
107
|
+
url = first.permalink_url;
|
|
108
|
+
}
|
|
109
|
+
return { ok: true, data: { id: first.gid, ...(url !== undefined ? { url } : {}) } };
|
|
110
|
+
}
|
|
111
|
+
catch (e) {
|
|
112
|
+
const { code, details } = classifyError(e);
|
|
113
|
+
return { ok: false, code, details };
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
// -------------------------------------------------------------------
|
|
117
|
+
// 2. task.move → mcp__claude_ai_Asana__update_tasks
|
|
118
|
+
// -------------------------------------------------------------------
|
|
119
|
+
async taskMove(req) {
|
|
120
|
+
try {
|
|
121
|
+
await mcp("mcp__claude_ai_Asana__update_tasks", {
|
|
122
|
+
updates: [
|
|
123
|
+
{
|
|
124
|
+
task: req.taskId,
|
|
125
|
+
memberships: [{ project: "", section: req.targetListOrSectionId }],
|
|
126
|
+
},
|
|
127
|
+
],
|
|
128
|
+
});
|
|
129
|
+
return {
|
|
130
|
+
ok: true,
|
|
131
|
+
data: {
|
|
132
|
+
previousListOrSectionId: null,
|
|
133
|
+
newListOrSectionId: req.targetListOrSectionId,
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
catch (e) {
|
|
138
|
+
const { code, details } = classifyError(e);
|
|
139
|
+
return { ok: false, code, details };
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
// -------------------------------------------------------------------
|
|
143
|
+
// 3. checklist.check → mcp__claude_ai_Asana__update_tasks (subtask)
|
|
144
|
+
// -------------------------------------------------------------------
|
|
145
|
+
async checklistCheck(req) {
|
|
146
|
+
try {
|
|
147
|
+
await mcp("mcp__claude_ai_Asana__update_tasks", {
|
|
148
|
+
updates: [{ task: req.itemId, completed: req.targetState === "complete" }],
|
|
149
|
+
});
|
|
150
|
+
const previousState = req.targetState === "complete" ? "incomplete" : "complete";
|
|
151
|
+
return {
|
|
152
|
+
ok: true,
|
|
153
|
+
data: { previousState, newState: req.targetState },
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
catch (e) {
|
|
157
|
+
const { code, details } = classifyError(e);
|
|
158
|
+
return { ok: false, code, details };
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
// -------------------------------------------------------------------
|
|
162
|
+
// 4. task.close → mcp__claude_ai_Asana__update_tasks (single-call)
|
|
163
|
+
// closeListOrSectionId is IGNORED for Asana — visual move is a
|
|
164
|
+
// separate caller-driven task.move per operations.md L12.
|
|
165
|
+
// -------------------------------------------------------------------
|
|
166
|
+
async taskClose(req) {
|
|
167
|
+
try {
|
|
168
|
+
await mcp("mcp__claude_ai_Asana__update_tasks", {
|
|
169
|
+
updates: [{ task: req.taskId, completed: true }],
|
|
170
|
+
});
|
|
171
|
+
return { ok: true, data: { closed: true } };
|
|
172
|
+
}
|
|
173
|
+
catch (e) {
|
|
174
|
+
const { code, details } = classifyError(e);
|
|
175
|
+
return { ok: false, code, details };
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
// -------------------------------------------------------------------
|
|
179
|
+
// 5. task.due-date.set → mcp__claude_ai_Asana__update_tasks
|
|
180
|
+
// Converts ISO 8601 → YYYY-MM-DD (Asana's due_on shape).
|
|
181
|
+
// Malformed input short-circuits to INVALID_REQUEST.
|
|
182
|
+
// -------------------------------------------------------------------
|
|
183
|
+
async taskDueDateSet(req) {
|
|
184
|
+
const dueOn = isoToDueOn(req.dueAt);
|
|
185
|
+
if (dueOn === null) {
|
|
186
|
+
return {
|
|
187
|
+
ok: false,
|
|
188
|
+
code: "INVALID_REQUEST",
|
|
189
|
+
details: { message: "dueAt must be ISO 8601", verb: "taskDueDateSet" },
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
try {
|
|
193
|
+
await mcp("mcp__claude_ai_Asana__update_tasks", {
|
|
194
|
+
updates: [{ task: req.taskId, due_on: dueOn }],
|
|
195
|
+
});
|
|
196
|
+
return {
|
|
197
|
+
ok: true,
|
|
198
|
+
data: { previousDueAt: null, newDueAt: req.dueAt },
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
catch (e) {
|
|
202
|
+
const { code, details } = classifyError(e);
|
|
203
|
+
return { ok: false, code, details };
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
// -------------------------------------------------------------------
|
|
207
|
+
// 6. task.assignee.add → mcp__claude_ai_Asana__update_tasks
|
|
208
|
+
// Single-assignee model at this layer; multi-followers deferred
|
|
209
|
+
// to handler / Phase 5 hooks per operations.md L14.
|
|
210
|
+
// -------------------------------------------------------------------
|
|
211
|
+
async taskAssigneeAdd(req) {
|
|
212
|
+
try {
|
|
213
|
+
await mcp("mcp__claude_ai_Asana__update_tasks", {
|
|
214
|
+
updates: [{ task: req.taskId, assignee: req.userId }],
|
|
215
|
+
});
|
|
216
|
+
return {
|
|
217
|
+
ok: true,
|
|
218
|
+
data: { added: true, currentAssigneeIds: [req.userId] },
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
catch (e) {
|
|
222
|
+
const { code, details } = classifyError(e);
|
|
223
|
+
return { ok: false, code, details };
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
// -------------------------------------------------------------------
|
|
227
|
+
// 7. task.comment.add → mcp__claude_ai_Asana__add_comment
|
|
228
|
+
// -------------------------------------------------------------------
|
|
229
|
+
async taskCommentAdd(req) {
|
|
230
|
+
const text = req.clientToken ? `[ct:${req.clientToken}] ${req.text}` : req.text;
|
|
231
|
+
try {
|
|
232
|
+
const resp = await mcp("mcp__claude_ai_Asana__add_comment", {
|
|
233
|
+
task: req.taskId,
|
|
234
|
+
text,
|
|
235
|
+
});
|
|
236
|
+
if (!isObjectWith(resp, "gid") || typeof resp.gid !== "string") {
|
|
237
|
+
return shapeError("task.comment.add");
|
|
238
|
+
}
|
|
239
|
+
const postedAt = isObjectWith(resp, "created_at") && typeof resp.created_at === "string"
|
|
240
|
+
? resp.created_at
|
|
241
|
+
: new Date().toISOString();
|
|
242
|
+
return {
|
|
243
|
+
ok: true,
|
|
244
|
+
data: { commentId: resp.gid, postedAt },
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
catch (e) {
|
|
248
|
+
const { code, details } = classifyError(e);
|
|
249
|
+
return { ok: false, code, details };
|
|
250
|
+
}
|
|
251
|
+
},
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
//# sourceMappingURL=transport-asana.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport-asana.js","sourceRoot":"","sources":["../src/transport-asana.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
<!-- readme-selector:start -->
|
|
2
|
+
<p align="center">
|
|
3
|
+
<a href="https://github.com/llodev/skills/blob/main/pm-tasks/pm-tasks-asana/README.md"><img src="https://raw.githubusercontent.com/lloliveiradev/public-assets/main/images/usa.svg" width="30" alt="English"></a>
|
|
4
|
+
<a href="https://github.com/llodev/skills/blob/main/pm-tasks/pm-tasks-asana/docs/i18n/README.pt-BR.md"><img src="https://raw.githubusercontent.com/lloliveiradev/public-assets/main/images/brazil.svg" width="30" alt="Português"></a>
|
|
5
|
+
<a href="https://github.com/llodev/skills/blob/main/pm-tasks/pm-tasks-asana/docs/i18n/README.es-ES.md"><img src="https://raw.githubusercontent.com/lloliveiradev/public-assets/main/images/spain.svg" width="30" alt="Español"></a>
|
|
6
|
+
</p>
|
|
7
|
+
<!-- readme-selector:end -->
|
|
8
|
+
|
|
9
|
+
# @llodev/pm-tasks-asana
|
|
10
|
+
|
|
11
|
+
> Adapter para Asana de la familia `@llodev/pm-tasks-*` — convierte planes de implementación en parent tasks + subtasks de Asana y los opera mediante paste, MCP publish o autonomous write-through.
|
|
12
|
+
|
|
13
|
+
[](https://www.npmjs.com/package/@llodev/pm-tasks-asana)
|
|
14
|
+
[](../../LICENSE)
|
|
15
|
+
[](https://nodejs.org)
|
|
16
|
+
[](https://agentskills.io)
|
|
17
|
+
|
|
18
|
+
Lo que obtienes:
|
|
19
|
+
|
|
20
|
+
- **Output paste-ready** — el agente renderiza un generic card; tú lo pegas en Asana manualmente.
|
|
21
|
+
- **MCP publish** mediante el servidor MCP `claude.ai Asana` (OAuth, sin PATs en JSON). Parent task + subtasks planas + custom fields + colocación en section en un único batch.
|
|
22
|
+
- **Operaciones CRUD** sobre tasks existentes: `checklist.check`, `task.close`, `task.comment.add`, `task.due-date.set`, `task.assignee.add`.
|
|
23
|
+
- **Autonomous mode** — centinela `[autonomous]` / `--auto` para write-through bajo allowlist + scope + rate limits. Los loops multi-task replican el estado a Asana en tiempo real.
|
|
24
|
+
|
|
25
|
+
## Instalación
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
# npm (con skillpm o marketplace de Claude Code)
|
|
29
|
+
npm i @llodev/pm-tasks-core @llodev/pm-tasks-asana
|
|
30
|
+
|
|
31
|
+
# Vercel CLI (instala el core manualmente también)
|
|
32
|
+
npx skills add llodev/skills/pm-tasks/pm-tasks-core
|
|
33
|
+
npx skills add llodev/skills/pm-tasks/pm-tasks-asana
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Setup del MCP
|
|
37
|
+
|
|
38
|
+
Asana usa OAuth mediante el MCP `claude.ai Asana`. Si ya conectaste tu cuenta de Asana en los ajustes de Cursor o Claude Code, ya estás listo.
|
|
39
|
+
|
|
40
|
+
Para cualquier agente compatible con MCP (Claude Code, Cursor, Codex, Windsurf, Cline, Roo Code):
|
|
41
|
+
|
|
42
|
+
1. Abre los ajustes de MCP del agente.
|
|
43
|
+
2. Activa / registra `claude.ai Asana` (o el MCP de Asana equivalente de tu agente).
|
|
44
|
+
3. Aprueba el flujo OAuth en tu navegador.
|
|
45
|
+
|
|
46
|
+
En Claude Code, verifica con `claude mcp list` — `claude.ai Asana` debe aparecer como autenticado.
|
|
47
|
+
|
|
48
|
+
## Setup de la config
|
|
49
|
+
|
|
50
|
+
El script `init` se ejecuta **fuera** del MCP, así que necesita un Personal Access Token para enumerar tus workspaces / projects / sections / custom fields. Genera uno en https://app.asana.com/0/my-apps, después:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
export LLODEV_PM_TASKS_ASANA_PAT=...
|
|
54
|
+
npx @llodev/pm-tasks-asana init
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Sigue los prompts. Elige dónde debe vivir la config:
|
|
58
|
+
|
|
59
|
+
- **local** → `./.asana.json` (recomendado para configs con scope de proyecto, se puede commitear).
|
|
60
|
+
- **global** → un default de la plataforma, personalizable. Defaults:
|
|
61
|
+
- macOS / Linux (XDG): `$XDG_CONFIG_HOME/llodev/pm-tasks/asana.json`, con fallback a `~/.config/llodev/pm-tasks/asana.json`.
|
|
62
|
+
- Windows: `%APPDATA%\llodev\pm-tasks\asana.json`.
|
|
63
|
+
- Sobrescribe en cualquier OS con `LLODEV_PM_TASKS_CONFIG_HOME=/your/path` — el archivo acaba en `$LLODEV_PM_TASKS_CONFIG_HOME/asana.json`.
|
|
64
|
+
|
|
65
|
+
El prompt de `init` imprime la ruta absoluta donde va a escribir, así siempre ves exactamente adónde va el archivo.
|
|
66
|
+
|
|
67
|
+
> [!IMPORTANT]
|
|
68
|
+
> El PAT solo lo usa el `init`. El MCP en sí usa OAuth — nunca pongas tokens en el JSON.
|
|
69
|
+
|
|
70
|
+
## Uso
|
|
71
|
+
|
|
72
|
+
| Prompt example | Qué hace el agente |
|
|
73
|
+
| ---------------------------------------------------------------- | --------------------------------------------------------------------------- |
|
|
74
|
+
| `"publish this plan as Asana tasks"` | Phase 5 publish — parent + subtasks + custom fields en un único batch |
|
|
75
|
+
| `"check subtask 3 on task X in Asana"` | Phase 6 CRUD — `checklist.check` sobre la subtask |
|
|
76
|
+
| `"close task Y"` | Phase 6 CRUD — `task.close` (mueve a la section de close + marca completed) |
|
|
77
|
+
| `"comment on task X: shipped"` | Phase 6 CRUD — `task.comment.add` |
|
|
78
|
+
| `"[autonomous] create task in asana from plan @docs/plans/X.md"` | Phase 5b autonomous (requiere `autonomous.enabled: true`) |
|
|
79
|
+
|
|
80
|
+
## Notas específicas de Asana
|
|
81
|
+
|
|
82
|
+
> [!NOTE]
|
|
83
|
+
> **Las subtasks tienen un único nivel** — el adapter aplana checklists anidadas en una sola capa de subtask.
|
|
84
|
+
|
|
85
|
+
> [!WARNING]
|
|
86
|
+
> **Los custom fields NO se heredan por defecto** — lista los IDs de field en `subtaskDefaults.inheritParentFields` para que el adapter los copie del parent a las subtasks en el momento del create.
|
|
87
|
+
|
|
88
|
+
- **Assignee es un campo único** — usa `task.assignee.add` para añadir followers; el assignee principal se reemplaza en caso de conflicto.
|
|
89
|
+
- **El `get_task` del MCP no devuelve activity stories** — el activity feed de la UI de Asana es la fuente de verdad para auditorías de atribución. Mira [`anti-patterns/asana.md`](../../anti-patterns/asana.md).
|
|
90
|
+
|
|
91
|
+
## Documentación
|
|
92
|
+
|
|
93
|
+
- [`SKILL.md`](../../SKILL.md) — enrutado de phases + vocabulario CRUD.
|
|
94
|
+
- [`schemas/config.json`](../../schemas/config.json) — JSON Schema de `.asana.json`.
|
|
95
|
+
- [`anti-patterns/asana.md`](../../anti-patterns/asana.md) — gotchas recurrentes.
|
|
96
|
+
- [`../pm-tasks-core/references/autonomous-mode.md`](../../../pm-tasks-core/references/autonomous-mode.md) — contrato de autonomous mode.
|
|
97
|
+
|
|
98
|
+
## Licencia
|
|
99
|
+
|
|
100
|
+
MIT — mira [LICENSE](../../LICENSE).
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
<!-- readme-selector:start -->
|
|
2
|
+
<p align="center">
|
|
3
|
+
<a href="https://github.com/llodev/skills/blob/main/pm-tasks/pm-tasks-asana/README.md"><img src="https://raw.githubusercontent.com/lloliveiradev/public-assets/main/images/usa.svg" width="30" alt="English"></a>
|
|
4
|
+
<a href="https://github.com/llodev/skills/blob/main/pm-tasks/pm-tasks-asana/docs/i18n/README.pt-BR.md"><img src="https://raw.githubusercontent.com/lloliveiradev/public-assets/main/images/brazil.svg" width="30" alt="Português"></a>
|
|
5
|
+
<a href="https://github.com/llodev/skills/blob/main/pm-tasks/pm-tasks-asana/docs/i18n/README.es-ES.md"><img src="https://raw.githubusercontent.com/lloliveiradev/public-assets/main/images/spain.svg" width="30" alt="Español"></a>
|
|
6
|
+
</p>
|
|
7
|
+
<!-- readme-selector:end -->
|
|
8
|
+
|
|
9
|
+
# @llodev/pm-tasks-asana
|
|
10
|
+
|
|
11
|
+
> Adapter para Asana da família `@llodev/pm-tasks-*` — transforma planos de implementação em parent tasks + subtasks no Asana e opera via paste, MCP publish ou autonomous write-through.
|
|
12
|
+
|
|
13
|
+
[](https://www.npmjs.com/package/@llodev/pm-tasks-asana)
|
|
14
|
+
[](../../LICENSE)
|
|
15
|
+
[](https://nodejs.org)
|
|
16
|
+
[](https://agentskills.io)
|
|
17
|
+
|
|
18
|
+
O que você ganha:
|
|
19
|
+
|
|
20
|
+
- **Output paste-ready** — o agente renderiza um generic card; você cola no Asana manualmente.
|
|
21
|
+
- **MCP publish** via servidor MCP `claude.ai Asana` (OAuth, sem PATs no JSON). Parent task + subtasks flat + custom fields + placement em section, tudo num batch só.
|
|
22
|
+
- **Operações CRUD** em tasks existentes: `checklist.check`, `task.close`, `task.comment.add`, `task.due-date.set`, `task.assignee.add`.
|
|
23
|
+
- **Autonomous mode** — sentinela `[autonomous]` / `--auto` para write-through sob allowlist + scope + rate limits. Loops multi-task espelham o estado para o Asana em tempo real.
|
|
24
|
+
|
|
25
|
+
## Instalação
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
# npm (com skillpm ou marketplace do Claude Code)
|
|
29
|
+
npm i @llodev/pm-tasks-core @llodev/pm-tasks-asana
|
|
30
|
+
|
|
31
|
+
# Vercel CLI (instale o core manualmente também)
|
|
32
|
+
npx skills add llodev/skills/pm-tasks/pm-tasks-core
|
|
33
|
+
npx skills add llodev/skills/pm-tasks/pm-tasks-asana
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Setup do MCP
|
|
37
|
+
|
|
38
|
+
O Asana usa OAuth via o MCP `claude.ai Asana`. Se você já conectou sua conta do Asana nas configurações do Cursor ou Claude Code, está pronto.
|
|
39
|
+
|
|
40
|
+
Para qualquer agente compatível com MCP (Claude Code, Cursor, Codex, Windsurf, Cline, Roo Code):
|
|
41
|
+
|
|
42
|
+
1. Abra as configurações de MCP do agente.
|
|
43
|
+
2. Habilite / registre `claude.ai Asana` (ou o MCP de Asana equivalente do seu agente).
|
|
44
|
+
3. Aprove o fluxo OAuth no navegador.
|
|
45
|
+
|
|
46
|
+
No Claude Code, verifique com `claude mcp list` — `claude.ai Asana` deve aparecer como autenticado.
|
|
47
|
+
|
|
48
|
+
## Setup da config
|
|
49
|
+
|
|
50
|
+
O script `init` roda **fora** do MCP, então precisa de um Personal Access Token para enumerar seus workspaces / projects / sections / custom fields. Gere um em https://app.asana.com/0/my-apps, depois:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
export LLODEV_PM_TASKS_ASANA_PAT=...
|
|
54
|
+
npx @llodev/pm-tasks-asana init
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Siga os prompts. Escolha onde a config deve ficar:
|
|
58
|
+
|
|
59
|
+
- **local** → `./.asana.json` (recomendado para configs com escopo de projeto, pode ser commitado).
|
|
60
|
+
- **global** → um default da plataforma, customizável. Defaults:
|
|
61
|
+
- macOS / Linux (XDG): `$XDG_CONFIG_HOME/llodev/pm-tasks/asana.json`, com fallback para `~/.config/llodev/pm-tasks/asana.json`.
|
|
62
|
+
- Windows: `%APPDATA%\llodev\pm-tasks\asana.json`.
|
|
63
|
+
- Sobrescreva em qualquer OS com `LLODEV_PM_TASKS_CONFIG_HOME=/your/path` — o arquivo vai parar em `$LLODEV_PM_TASKS_CONFIG_HOME/asana.json`.
|
|
64
|
+
|
|
65
|
+
O prompt de `init` imprime o caminho absoluto onde vai escrever, então você sempre vê exatamente para onde o arquivo vai.
|
|
66
|
+
|
|
67
|
+
> [!IMPORTANT]
|
|
68
|
+
> O PAT é usado **somente** pelo `init`. O MCP em si usa OAuth — nunca coloque tokens no JSON.
|
|
69
|
+
|
|
70
|
+
## Uso
|
|
71
|
+
|
|
72
|
+
| Prompt example | O que o agente faz |
|
|
73
|
+
| ---------------------------------------------------------------- | ---------------------------------------------------------------------------- |
|
|
74
|
+
| `"publish this plan as Asana tasks"` | Phase 5 publish — parent + subtasks + custom fields num batch só |
|
|
75
|
+
| `"check subtask 3 on task X in Asana"` | Phase 6 CRUD — `checklist.check` na subtask |
|
|
76
|
+
| `"close task Y"` | Phase 6 CRUD — `task.close` (move para a section de close + marca completed) |
|
|
77
|
+
| `"comment on task X: shipped"` | Phase 6 CRUD — `task.comment.add` |
|
|
78
|
+
| `"[autonomous] create task in asana from plan @docs/plans/X.md"` | Phase 5b autonomous (requer `autonomous.enabled: true`) |
|
|
79
|
+
|
|
80
|
+
## Notas específicas do Asana
|
|
81
|
+
|
|
82
|
+
> [!NOTE]
|
|
83
|
+
> **Subtasks têm um nível só** — o adapter achata checklists aninhadas em uma única camada de subtask.
|
|
84
|
+
|
|
85
|
+
> [!WARNING]
|
|
86
|
+
> **Custom fields NÃO são herdados por default** — liste os IDs de field em `subtaskDefaults.inheritParentFields` para que o adapter copie do parent para as subtasks no momento do create.
|
|
87
|
+
|
|
88
|
+
- **Assignee é um campo único** — use `task.assignee.add` para adicionar followers; o assignee primário é substituído em caso de conflito.
|
|
89
|
+
- **O `get_task` do MCP não retorna activity stories** — o activity feed da UI do Asana é a fonte de verdade para auditorias de atribuição. Veja [`anti-patterns/asana.md`](../../anti-patterns/asana.md).
|
|
90
|
+
|
|
91
|
+
## Documentação
|
|
92
|
+
|
|
93
|
+
- [`SKILL.md`](../../SKILL.md) — roteamento de phases + vocabulário CRUD.
|
|
94
|
+
- [`schemas/config.json`](../../schemas/config.json) — JSON Schema do `.asana.json`.
|
|
95
|
+
- [`anti-patterns/asana.md`](../../anti-patterns/asana.md) — pegadinhas recorrentes.
|
|
96
|
+
- [`../pm-tasks-core/references/autonomous-mode.md`](../../../pm-tasks-core/references/autonomous-mode.md) — contrato do autonomous mode.
|
|
97
|
+
|
|
98
|
+
## Licença
|
|
99
|
+
|
|
100
|
+
MIT — veja [LICENSE](../../LICENSE).
|
package/i18n/en-US.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"header": "@llodev/pm-tasks-asana init",
|
|
3
|
+
"patMissingTitle": "Asana MCP detected, but credentials missing for init probe.",
|
|
4
|
+
"patMissingBody": "Generate a Personal Access Token at https://app.asana.com/0/my-apps,\nthen set it in your shell and re-run:\n export LLODEV_PM_TASKS_ASANA_PAT=...\n(The token is used only by this init script; the MCP itself uses OAuth.)",
|
|
5
|
+
"mcpMissingTitle": "Asana MCP not available. Connect your Asana account first:",
|
|
6
|
+
"mcpMissingBody": " In Cursor / Claude Code settings → MCP → enable 'claude.ai Asana'.\nThen re-run this init.",
|
|
7
|
+
"workspacePrompt": "Available workspaces (select 1):",
|
|
8
|
+
"noWorkspace": "no workspace selected, aborting",
|
|
9
|
+
"projectsPrompt": "Projects in \"{workspace}\" (select 1+):",
|
|
10
|
+
"noProject": "no project selected, aborting",
|
|
11
|
+
"sectionsPrompt": "Sections in \"{project}\":",
|
|
12
|
+
"customFieldsPrompt": "Custom fields in \"{project}\" (select fields to expose to the adapter):",
|
|
13
|
+
"inheritPrompt": "Of those, which should subtasks inherit from the parent?",
|
|
14
|
+
"openSectionPrompt": "Which section is the default for newly-created tasks?",
|
|
15
|
+
"closeSectionPrompt": "Which section means 'closed / done'?",
|
|
16
|
+
"escalationPrompt": "Pick the escalation contact (will receive escalation comments + add_member on critical cards):",
|
|
17
|
+
"noMembersPrompt": "Project membership listing returned nothing (PAT scope or solo project). Add an escalation contact manually?",
|
|
18
|
+
"manualMemberGid": "Escalation member gid (Asana user ID): ",
|
|
19
|
+
"manualMemberName": "Display name: ",
|
|
20
|
+
"manualMemberAlias": "Alias (default \"owner\"): ",
|
|
21
|
+
"tryItTitle": "Try it in Claude Code: 'create an Asana task from this plan'.",
|
|
22
|
+
"tokenReminderTitle": "Reminder: the Asana MCP holds OAuth — never put tokens in this JSON.",
|
|
23
|
+
"tokenReminderBody": "The LLODEV_PM_TASKS_ASANA_PAT env var is only used by this init script."
|
|
24
|
+
}
|
package/i18n/es-ES.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"header": "@llodev/pm-tasks-asana init",
|
|
3
|
+
"patMissingTitle": "MCP de Asana detectado, pero faltan credenciales para el probe del init.",
|
|
4
|
+
"patMissingBody": "Genera un Personal Access Token en https://app.asana.com/0/my-apps,\nluego expórtalo en tu shell y vuelve a ejecutar:\n export LLODEV_PM_TASKS_ASANA_PAT=...\n(El token solo lo usa este script de init; el MCP en sí usa OAuth.)",
|
|
5
|
+
"mcpMissingTitle": "MCP de Asana no disponible. Conecta tu cuenta Asana primero:",
|
|
6
|
+
"mcpMissingBody": " En Cursor / Claude Code settings → MCP → activa 'claude.ai Asana'.\nLuego ejecuta este init nuevamente.",
|
|
7
|
+
"workspacePrompt": "Workspaces disponibles (selecciona 1):",
|
|
8
|
+
"noWorkspace": "ningún workspace seleccionado, abortando",
|
|
9
|
+
"projectsPrompt": "Proyectos en \"{workspace}\" (selecciona 1+):",
|
|
10
|
+
"noProject": "ningún proyecto seleccionado, abortando",
|
|
11
|
+
"sectionsPrompt": "Secciones en \"{project}\":",
|
|
12
|
+
"customFieldsPrompt": "Custom fields en \"{project}\" (selecciona cuáles exponer al adapter):",
|
|
13
|
+
"inheritPrompt": "De esos, ¿cuáles deben heredar las subtareas del padre?",
|
|
14
|
+
"openSectionPrompt": "¿Qué sección es el default para tareas nuevas?",
|
|
15
|
+
"closeSectionPrompt": "¿Qué sección significa 'cerrada / hecha'?",
|
|
16
|
+
"escalationPrompt": "Elige el contacto de escalation (recibe comentarios de escalation + add_member en tarjetas críticas):",
|
|
17
|
+
"noMembersPrompt": "El listado de miembros del proyecto vino vacío (alcance del PAT o proyecto en solitario). ¿Agregar un contacto de escalation manualmente?",
|
|
18
|
+
"manualMemberGid": "Gid del miembro de escalation (Asana user ID): ",
|
|
19
|
+
"manualMemberName": "Nombre a mostrar: ",
|
|
20
|
+
"manualMemberAlias": "Alias (default \"owner\"): ",
|
|
21
|
+
"tryItTitle": "Pruébalo en Claude Code: 'crea una tarea de Asana desde este plan'.",
|
|
22
|
+
"tokenReminderTitle": "Recordatorio: el MCP de Asana usa OAuth — nunca pongas tokens en este JSON.",
|
|
23
|
+
"tokenReminderBody": "La variable LLODEV_PM_TASKS_ASANA_PAT solo la usa este script de init."
|
|
24
|
+
}
|
package/i18n/pt-BR.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"header": "@llodev/pm-tasks-asana init",
|
|
3
|
+
"patMissingTitle": "MCP do Asana detectado, mas faltam credenciais para o probe do init.",
|
|
4
|
+
"patMissingBody": "Gere um Personal Access Token em https://app.asana.com/0/my-apps,\ndepois exporte no shell e rode de novo:\n export LLODEV_PM_TASKS_ASANA_PAT=...\n(O token é usado SÓ por este script de init; o MCP em si usa OAuth.)",
|
|
5
|
+
"mcpMissingTitle": "MCP do Asana não disponível. Conecte sua conta Asana primeiro:",
|
|
6
|
+
"mcpMissingBody": " No Cursor / Claude Code settings → MCP → habilite 'claude.ai Asana'.\nDepois rode esse init novamente.",
|
|
7
|
+
"workspacePrompt": "Workspaces disponíveis (selecione 1):",
|
|
8
|
+
"noWorkspace": "nenhum workspace selecionado, abortando",
|
|
9
|
+
"projectsPrompt": "Projetos em \"{workspace}\" (selecione 1+):",
|
|
10
|
+
"noProject": "nenhum projeto selecionado, abortando",
|
|
11
|
+
"sectionsPrompt": "Seções em \"{project}\":",
|
|
12
|
+
"customFieldsPrompt": "Custom fields em \"{project}\" (selecione quais expor ao adapter):",
|
|
13
|
+
"inheritPrompt": "Desses, quais subtasks devem herdar do parent?",
|
|
14
|
+
"openSectionPrompt": "Qual seção é o default para novas tasks?",
|
|
15
|
+
"closeSectionPrompt": "Qual seção significa 'fechada / concluída'?",
|
|
16
|
+
"escalationPrompt": "Escolha o contato de escalation (recebe comentários de escalation + add_member em cards críticos):",
|
|
17
|
+
"noMembersPrompt": "Listagem de membros do projeto retornou vazia (escopo do PAT ou projeto solo). Adicionar um contato de escalation manualmente?",
|
|
18
|
+
"manualMemberGid": "Gid do membro de escalation (Asana user ID): ",
|
|
19
|
+
"manualMemberName": "Nome de exibição: ",
|
|
20
|
+
"manualMemberAlias": "Alias (default \"owner\"): ",
|
|
21
|
+
"tryItTitle": "Teste no Claude Code: 'crie uma task no Asana a partir desse plano'.",
|
|
22
|
+
"tokenReminderTitle": "Lembrete: o MCP do Asana usa OAuth — nunca coloque tokens neste JSON.",
|
|
23
|
+
"tokenReminderBody": "A env LLODEV_PM_TASKS_ASANA_PAT é usada SÓ por este script de init."
|
|
24
|
+
}
|