Skip to content

Built for people who want to own their automations. Join the waitlist for an invite.

Package listing

@kody/asana

README.md

221 lines · 7.8 KB · Markdown

@kody/asana

Intent

Provide reusable, account-agnostic Asana helpers so Kody agents can read and update workspaces, projects, tasks, and stories/comments through a saved asana / asana-* OAuth integration or a personal access token — without hand-rolling REST. Mutations are previewable with dryRun: true and only run live after confirm: true.

This listing is meant to be forked. After you fork, connect your Asana workspace. Do not treat the live @kody/asana package storage as yours.

When To Use

  • List or inspect workspaces, projects, tasks, and stories/comments
  • Create or update projects, tasks, and comments after explicit confirmation
  • Call an unwrapped Asana REST path through ./request
  • Connect more than one Asana account via integrationName / account

Auth

Asana has no built-in Kody OAuth app. Choose one lane:

LaneWhen to useCredential
OAuth (recommended)Multi-account, refresh tokens, shared helpersSaved integration asana or asana-<purpose>
Personal access tokenFastest for a single workspace / personal scriptsUser secret asanaPat (or asanaPat-<purpose>)

Both lanes send Authorization: Bearer …. Required API host: app.asana.com. Approve it in the account secrets UI.

Lane A — BYO OAuth
  1. Create an OAuth application at https://app.asana.com/0/my-apps
  2. Set the redirect URI exactly to https://kody.codes/connect/oauth
  3. Under OAuth → Permission scopes, enable the scopes this package uses (workspaces:read, projects:read, projects:write, tasks:read, tasks:write, stories:read, stories:write, users:read) or toggle Full permissions (default).
  4. Connect while signed in to Kody:

https://kody.codes/connect/oauth?provider=asana&authorizeUrl=https%3A%2F%2Fapp.asana.com%2F-%2Foauth_authorize&tokenUrl=https%3A%2F%2Fapp.asana.com%2F-%2Foauth_token&apiBaseUrl=https%3A%2F%2Fapp.asana.com%2Fapi%2F1.0&scopes=workspaces%3Aread%20projects%3Aread%20projects%3Awrite%20tasks%3Aread%20tasks%3Awrite%20stories%3Aread%20stories%3Awrite%20users%3Aread&flow=confidential&pkce=true&allowedHosts=app.asana.com&dashboardUrl=https%3A%2F%2Fapp.asana.com%2F0%2Fmy-apps

  1. Paste the Asana client id and client secret into the Kody wizard (never into chat). Approve host app.asana.com.
  2. Reconnect later with https://kody.codes/connect/oauth?provider=asana

OAuth authorize URL: https://app.asana.com/-/oauth_authorize. Token URL: https://app.asana.com/-/oauth_token. Flow: confidential (client secret) plus S256 PKCE. Asana scopes are space-separated.

To connect a second workspace, change provider (for example provider=asana-work) and pass integrationName: 'asana-work' on every call.

Lane B — Personal access token
  1. Create a token at https://app.asana.com/0/my-apps
  2. Save it (do not paste the value in chat):

https://kody.codes/account/secrets/new?name=asanaPat&description=Asana%20personal%20access%20token&allowedHosts=app.asana.com&scope=user

For a second workspace/token, use a distinct secret name such as asanaPat-work and pass secretName: 'asanaPat-work' (or account: 'work', which resolves to asana-work / asanaPat-work).

Scopes

ScopeNeeded for
workspaces:read./list-workspaces, ./get-workspace
projects:read./list-projects, ./get-project
projects:write./create-project
tasks:read./list-tasks, ./get-task
tasks:write./create-task, ./update-task
stories:read./list-stories
stories:write./create-story
users:read./viewer, ./smoke-test
defaultFull permissions when an endpoint has no granular scope

If Asana returns 401/403 or an insufficient-scope error, helpers throw a message that names the missing scope and the next setup URL (reconnect OAuth with that scope, or save a PAT).

Multiple accounts

Every export accepts:

  • integrationName / integration — exact saved OAuth name (asana-work)
  • accountworkasana-work; asana-work used as-is; omitted → asana
  • secretName — PAT secret override
  • auth'oauth' or 'pat' when both exist

Do not hard-code a personal workspace GID or alias.

Safety

Mutating helpers require confirm: true. Pass dryRun: true to inspect the REST payload without calling Asana. ./request treats GET as read-only; POST / PUT / PATCH / DELETE need confirmation.

Exports

ExportDescription
.Package overview, connect URLs, export map
./accountsResolve integration/secret names and report what is connected
./smoke-testLocal helper checks plus optional live viewer read (no email)
./viewerAuthenticated user gid / name
./list-workspacesList workspaces
./get-workspaceGet one workspace by GID
./list-projectsList projects (workspaceGid, teamGid, archived)
./get-projectGet one project by GID
./create-projectPreview or create a project (workspaceGid or teamGid)
./list-tasksList tasks (projectGid, or assignee + workspaceGid)
./get-taskGet one task by GID
./create-taskPreview or create a task (workspaceGid or projects)
./update-taskPreview or update a task
./list-storiesList stories/comments on a task
./create-storyPreview or create a comment on a task
./requestGeneric REST escape hatch
./typesShared TypeScript types

Smoke test

import smokeTest from 'kody:@kody/asana/smoke-test'

export default async function main() {
	return await smokeTest()
}

Without credentials this returns { ok: true, live: false } plus the connect and PAT URLs. After OAuth or a PAT is saved it reads /users/me and returns { live: true, hasViewerGid } without email.

Preview a mutation without credentials:

import createTask from 'kody:@kody/asana/create-task'

export default async function main() {
	return await createTask({
		name: 'Follow up on onboarding',
		workspaceGid: '1234567890',
		dryRun: true,
	})
}

Examples

import listWorkspaces from 'kody:@kody/asana/list-workspaces'
import listProjects from 'kody:@kody/asana/list-projects'

export default async function main() {
	const { items: workspaces } = await listWorkspaces()
	return await listProjects({
		workspaceGid: workspaces[0]?.gid,
		limit: 20,
	})
}
import createTask from 'kody:@kody/asana/create-task'

export default async function main() {
	const preview = await createTask({
		name: 'Investigate checkout timeout',
		workspaceGid: '123',
		assignee: 'me',
		dryRun: true,
	})
	// After the user confirms the exact workspace and title:
	return await createTask({
		name: 'Investigate checkout timeout',
		workspaceGid: '123',
		assignee: 'me',
		confirm: true,
	})
}

Unwrapped REST:

import request from 'kody:@kody/asana/request'

export default async function main() {
	return await request({
		path: '/users/me',
		query: { opt_fields: 'gid,name' },
	})
}

Notes

  • REST base: https://app.asana.com/api/1.0
  • Helpers project slim objects with opt_fields. Use ./request when you need extra fields.
  • assignee: 'me' on ./list-tasks / ./create-task is the authorizing user.
  • List tasks requires projectGid, or both assignee and workspaceGid.
  • This package is not affiliated with or endorsed by Asana, Inc.

Branding

The community icon is Asana's official three-dot logomark from Asana brand (the coral mark published in the horizontal lockup). Paths are unmodified. Asana® is a trademark of Asana, Inc.

Docs