@happyvertical/projects
v0.88.0
Published
Standardized project management interface for GitHub Projects, Jira, ZenHub, and Linear
Downloads
8,406
Readme
@happyvertical/projects
Standardized project management interface for GitHub Projects, Jira, ZenHub, and Linear.
Installation
pnpm add @happyvertical/projects @happyvertical/reposClaude Code Context
Install Claude Code context files for AI-assisted development:
npx have-projects-contextThis copies the package's AGENT.md documentation and metadata.json metadata to your project's .claude/ directory, enabling Claude to provide better assistance when working with this package.
Usage
Basic Example
import { getProject } from '@happyvertical/projects';
import { getRepository } from '@happyvertical/repos';
// Get repository to retrieve issue node IDs
const repo = await getRepository({
type: 'github',
owner: 'happyvertical',
repo: 'sdk',
token: process.env.GITHUB_TOKEN
});
// Get project with status field configuration
const project = await getProject({
type: 'github',
projectId: 'PVT_kwDOB9Y8ns4A8-TY',
token: process.env.GITHUB_TOKEN,
statusFieldId: 'PVTSSF_lADOB9Y8ns4A8-TYzgw0GaY',
statusOptions: {
'New': 'option-id-1',
'Backlog': 'option-id-2',
'In Progress': 'option-id-3',
'Done': 'option-id-4'
}
});
// Add issue to project
const issue = await repo.getIssue(352);
const item = await project.addItem(issue.id);
// Update status
await project.updateItemStatus(item.id, 'In Progress');Kanban Workflow
// List all items
const items = await project.listItems();
// Get available statuses
const statuses = await project.listStatuses();
// Move item through workflow
await project.updateItemStatus(itemId, 'Ready');
await project.updateItemStatus(itemId, 'In Progress');
await project.updateItemStatus(itemId, 'Review');
await project.updateItemStatus(itemId, 'Done');Custom Field Management
// List all project fields
const fields = await project.listFields();
// Update custom field
const priorityField = fields.find(f => f.name === 'Priority');
if (priorityField) {
await project.updateItemField(itemId, priorityField.id, 'high-priority-option-id');
}API Reference
Factory Function
getProject(options)
Creates a project management client instance.
Parameters:
options: ProjectConfig | IProject- Project configuration or existing instance
Returns: Promise<IProject>
Example:
const project = await getProject({
type: 'github',
projectId: 'PVT_kwDOB9Y8ns4A8-TY',
token: process.env.GITHUB_TOKEN,
statusFieldId: 'PVTSSF_lADOB9Y8ns4A8-TYzgw0GaY',
statusOptions: {
'New': 'option-id-1',
'Backlog': 'option-id-2',
'In Progress': 'option-id-3',
'Done': 'option-id-4'
}
});IProject Interface
Project Information
getProject(): Promise<Project>
Get project details including fields and statuses.
Returns: Project metadata with all fields and status options
Item Management
addItem(contentId: string): Promise<ProjectItem>
Add an issue or pull request to the project.
The GitHub adapter returns the created item's identity and content type. Use
listItems() when content metadata, status, or provider fields are required.
Parameters:
contentId: string- Node ID of the issue or PR (from @happyvertical/repos)
Returns: Created project item
removeItem(itemId: string): Promise<void>
Remove an item from the project.
Parameters:
itemId: string- Project item ID
getItem(itemId: string): Promise<ProjectItem | null>
Get a specific project item.
The GitHub adapter returns item identity and content type only. Use
listItems() for the enriched content metadata, status, and provider fields.
Parameters:
itemId: string- Project item ID
Returns: Project item or null if not found
listItems(filters?: ItemFilters): Promise<ProjectItem[]>
List items in the project. The GitHub adapter follows Projects V2 pagination,
skips deleted or inaccessible content, and returns each item's content type,
status, provider field values, and top-level title, url, and assignees
metadata for provider-neutral rendering. Provider fields remain separately
keyed by their original names, even when a field has the same name as top-level
metadata. Results default to at most 100 items and are fetched in pages sized to
stay below GitHub's GraphQL node limit. A limit of 0 returns no items.
The GitHub adapter currently applies only limit and cursor; callers should
apply status, assignees, or labels filters to the returned items.
Parameters:
filters?: ItemFilters- Optional filters (limitdefaults to 100)
Returns: Array of project items
Status Management
updateItemStatus(itemId: string, status: string): Promise<void>
Update the status of a project item.
Parameters:
itemId: string- Project item IDstatus: string- Status name (must match statusOptions in config)
Example:
await project.updateItemStatus(itemId, 'In Progress');listStatuses(): Promise<Status[]>
Get all available status options.
Returns: Array of status definitions with IDs, names, colors
Field Management
updateItemField(itemId: string, fieldId: string, value: unknown): Promise<void>
Update a custom field value.
Parameters:
itemId: string- Project item IDfieldId: string- Field IDvalue: unknown- Field value (type depends on field type)
listFields(): Promise<Field[]>
Get all project fields.
Returns: Array of field definitions
GitHub Projects V2 Setup
Finding Your Project ID
- Navigate to your GitHub Project
- In the URL, find the project number:
https://github.com/orgs/ORG/projects/123 - Use GitHub CLI to get the node ID:
gh api graphql -f query=' query { organization(login: "ORG") { projectV2(number: 123) { id } } } '
Finding Status Field ID and Options
gh api graphql -f query='
query {
node(id: "PVT_kwDOB9Y8ns4A8-TY") {
... on ProjectV2 {
fields(first: 20) {
nodes {
... on ProjectV2SingleSelectField {
id
name
options {
id
name
}
}
}
}
}
}
}
'Look for the "Status" field and note:
- Field ID (
PVTSSF_...) - Option IDs for each status
Configuration Example
const project = await getProject({
type: 'github',
projectId: 'PVT_kwDOB9Y8ns4A8-TY', // From step 1
token: process.env.GITHUB_TOKEN,
statusFieldId: 'PVTSSF_lADOB9Y8ns4A8-TYzgw0GaY', // From step 2
statusOptions: {
'New': '47fc9ee4', // Option IDs from step 2
'Backlog': '4c6e9e0c',
'Planning': 'f75ad846',
'Ready': '98236657',
'In Progress': 'b5f9e3e1',
'Review': '5e1b3e8f',
'Done': '3f4e5d6c'
}
});Standard Kanban Statuses
The package exports standard kanban status names:
import { KANBAN_STATUSES } from '@happyvertical/projects';
// ['New', 'Backlog', 'Planning', 'Ready', 'In Progress', 'Review', 'Done']Error Handling
import { ProjectError, ProjectErrorCode } from '@happyvertical/projects';
try {
await project.updateItemStatus(itemId, 'Invalid Status');
} catch (error) {
if (error instanceof ProjectError) {
switch (error.code) {
case ProjectErrorCode.INVALID_STATUS:
console.error('Status not found:', error.message);
break;
case ProjectErrorCode.UNAUTHORIZED:
console.error('Token lacks permissions');
break;
case ProjectErrorCode.RATE_LIMITED:
console.error('Rate limited, retry after delay');
break;
}
}
}Integration with @happyvertical/repos
Projects work seamlessly with repositories:
import { getRepository } from '@happyvertical/repos';
import { getProject } from '@happyvertical/projects';
const repo = await getRepository({
type: 'github',
owner: 'happyvertical',
repo: 'sdk',
token: process.env.GITHUB_TOKEN
});
const project = await getProject({
type: 'github',
projectId: 'PVT_kwDOB9Y8ns4A8-TY',
token: process.env.GITHUB_TOKEN,
statusFieldId: 'PVTSSF_lADOB9Y8ns4A8-TYzgw0GaY',
statusOptions: {
'New': 'option-id-1',
'In Progress': 'option-id-2',
'Done': 'option-id-3'
}
});
// Create issue and add to project
const issue = await repo.createIssue({
title: 'New feature',
body: 'Description',
labels: ['type: feature']
});
const item = await project.addItem(issue.id);
await project.updateItemStatus(item.id, 'In Progress');Supported Platforms
| Platform | Status | Notes | |----------|--------|-------| | GitHub Projects V2 | ✅ Complete | Full GraphQL implementation | | Jira | 🚧 Planned | Coming soon | | ZenHub | 🚧 Planned | Coming soon | | Linear | 🚧 Planned | Coming soon |
License
MIT
