JSPM

@headlessly/projects

0.1.0
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 7
  • Score
    100M100P100Q42194F
  • License MIT

headless.ly Projects entities — Project, Issue, Comment

Package Exports

  • @headlessly/projects

Readme

@headlessly/projects

Jira has 10,000 settings. Your agent needs Issue.close().

import { Issue } from '@headlessly/projects'

await Issue.create({ title: 'Fix login redirect loop', type: 'Bug', priority: 'High', project: 'project_e5JhLzXc' })
await Issue.close('issue_k7TmPvQx')

// An issue closes — the deal progresses, support links, marketing knows
Issue.closed(async (issue, $) => {
  await $.Deal.update(issue.assignee, { stage: 'Negotiation' })
  await $.Event.create({ type: 'issue.closed', value: issue.title })
  const open = await $.Issue.find({ project: issue.project, status: 'Open' })
  if (open.length === 0) {
    await $.Project.complete(issue.project)
  }
})

No Jira JQL. No Linear GraphQL. No Asana webhook plumbing. Projects, issues, and the entire business graph — in one typed system your agent can operate autonomously. Synced bidirectionally with GitHub.

The Problem

Jira has 10,000 configuration options, a query language nobody memorized, and 200+ API endpoints designed for enterprise PMs who file tickets in their sleep. Linear is cleaner but still human-first — built for engineers dragging cards across a board. Asana is a task list with a marketing budget.

None of them were built for an AI agent to operate.

Your agent doesn't need a sprint board. It needs Issue.close(). It doesn't need a backlog grooming UI. It needs Issue.assign(). It doesn't need a "workflow automation" GUI with drag-and-drop triggers. It needs a BEFORE hook:

Issue.closing((issue) => {
  if (!issue.assignee) throw new Error('Cannot close unassigned issue')
})

And none of them connect to the rest of your business. When a bug closes in Jira, does your CRM know the deal can move forward? Does your support system close the related ticket? Does your marketing team know a feature shipped?

Not without Zapier, webhook handlers, and a prayer.

One Typed Graph

When an issue closes in headless.ly, your CRM already knows. Your billing system already knows. Your support queue already knows. Because they're the same system:

import { Issue, Project } from '@headlessly/projects'

Issue.closed(async (issue, $) => {
  await $.Ticket.update(issue.$id, { status: 'Resolved' })
  await $.Event.create({ type: 'issue.closed', value: issue.title })
  await $.Campaign.create({ name: `${issue.title} shipped`, type: 'Announcement' })
})

Issue.created(async (issue, $) => {
  if (issue.type === 'Bug') {
    await $.Ticket.create({ subject: `Bug: ${issue.title}`, priority: issue.priority })
  }
})

Project.completed(async (project, $) => {
  await $.Deal.update(project.owner, { stage: 'ClosedWon' })
  await $.Event.create({ type: 'project.completed', value: project.name })
})

No webhooks. No Zapier. No integration tax. One graph.

Install

npm install @headlessly/projects

Entities

Project

Top-level containers with ownership, lifecycle, and full GitHub repo sync.

import { Project } from '@headlessly/projects'

const project = await Project.create({
  name: 'Launch Campaign',
  status: 'Active',
  visibility: 'Private',
  owner: 'contact_fX9bL5nRd',
})

await Project.archive(project.$id)
await Project.complete(project.$id)

Project.completed((project, $) => {
  $.Deal.update(project.owner, { stage: 'ClosedWon' })
})

Verbs: archive() · archiving() · archived() · archivedBy · complete() · completing() · completed() · completedBy

Key fields: name, slug, description, status (Active | Archived | Completed), visibility (Public | Private), startDate, targetDate, tags

Relationships: → Organization, → Owner (Contact), ← Issues[]

Issue

Bugs, features, tasks, and epics — synced bidirectionally with GitHub Issues.

import { Issue } from '@headlessly/projects'

const issue = await Issue.create({
  title: 'Fix login redirect loop',
  status: 'Open',
  priority: 'High',
  type: 'Bug',
  project: 'project_e5JhLzXc',
})

await Issue.assign('issue_k7TmPvQx')
await Issue.close('issue_k7TmPvQx')
await Issue.reopen('issue_k7TmPvQx')

Issue.assigned((issue) => {
  console.log(`${issue.title} assigned to ${issue.assignee}`)
})

Verbs: assign() · assigning() · assigned() · assignedBy · close() · closing() · closed() · closedBy · reopen() · reopening() · reopened() · reopenedBy

Key fields: title, description, status (Open | InProgress | Review | Done | Closed), priority (Low | Medium | High | Urgent), type (Bug | Feature | Task | Epic), labels, milestone, dueDate

Relationships: → Project, → Assignee (Contact), → Reporter (Contact), ← Comments[]

Comment

Threaded comments on issues — synced with GitHub issue comments.

import { Comment } from '@headlessly/projects'

await Comment.create({
  body: 'Reproduced on Chrome 120. Stack trace attached.',
  author: 'contact_fX9bL5nRd',
  issue: 'issue_k7TmPvQx',
})

Key fields: body

Relationships: → Author (Contact), → Issue

Agent-Native

Your agent connects to one MCP endpoint. It can operate your entire project management stack:

{ "type": "Issue", "filter": { "priority": "Urgent", "status": "Open" } }
{ "type": "Issue", "id": "issue_k7TmPvQx", "include": ["project", "assignee", "comments"] }
const unassigned = await $.Issue.find({ status: 'Open', assignee: { $exists: false } })
for (const issue of unassigned) {
  await $.Issue.assign(issue.$id)
  await $.Comment.create({
    body: 'Auto-assigned by triage agent',
    issue: issue.$id,
    author: 'contact_mN8pZwKj',
  })
}

Three tools. Not three hundred endpoints.

Cross-Domain Operations

Query results are standard arrays — chain operations with familiar JavaScript:

const projects = await Project.find({ status: 'Active' })
for (const project of projects) {
  const bugs = await Issue.find({
    project: project.$id,
    priority: 'Urgent',
    type: 'Bug',
    status: 'Open',
  })
  for (const bug of bugs) {
    await Issue.assign(bug.$id)
  }
}

License

MIT