Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .agents/plugins/marketplace.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "dashi-taskboard-local",
"interface": {
"displayName": "Dashi Taskboard Local"
},
"plugins": [
{
"name": "dashi-taskboard",
"source": {
"source": "local",
"path": "./"
},
"policy": {
"installation": "AVAILABLE",
"authentication": "ON_INSTALL"
},
"category": "Productivity"
}
]
}
37 changes: 37 additions & 0 deletions .codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"name": "dashi-taskboard",
"version": "0.1.0",
"description": "Manage taskboard issues and share durable project context through a local companion.",
"author": {
"name": "chuspeeism",
"url": "https://github.com/chuspeeism"
},
"homepage": "https://github.com/chuspeeism/dashi-taskboard",
"repository": "https://github.com/chuspeeism/dashi-taskboard",
"license": "UNLICENSED",
"keywords": [
"codex",
"taskboard",
"project-context"
],
"skills": "./skills/",
"mcpServers": "./.mcp.json",
"interface": {
"displayName": "Dashi Taskboard",
"shortDescription": "Manage issues and durable shared project context.",
"longDescription": "Use taskboard issue workflows and a local stdio MCP server to read and publish shared project context through the companion HTTP API.",
"developerName": "chuspeeism",
"category": "Productivity",
"capabilities": [
"Read",
"Write"
],
"websiteURL": "https://github.com/chuspeeism/dashi-taskboard",
"defaultPrompt": [
"Review the current taskboard issue and its shared project context.",
"Search this project's durable decisions and constraints.",
"Publish a concise handoff after completing the issue."
],
"brandColor": "#2563EB"
}
}
8 changes: 8 additions & 0 deletions .mcp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"mcpServers": {
"dashi-taskboard": {
"command": "dashi-taskboard-mcp",
"args": []
}
}
}
41 changes: 33 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Codex Taskboard

A local-first issue board that runs in a browser and can be embedded in Codex through the standalone CDP launcher or its injection script. The same HTTP API powers the React UI and the `taskctl` CLI used by the bundled Codex Skill.
A local-first issue board with an official Codex plugin for issue workflows and durable shared project context. The plugin bundles two Skills and a stdio MCP server; the React UI and `taskctl` use the same companion HTTP API. An optional CDP injector can place the browser UI inside Codex, but it is separate from the official plugin.

## Requirements

Expand Down Expand Up @@ -44,18 +44,40 @@ npm run taskctl -- issue create \

Use `npm link` if you want `taskctl` on your shell path. Set `CODEX_TASKBOARD_URL` to point the CLI at another local or LAN service. Cloud deployments are configured through the loopback companion with `taskctl cloud login`.

## Install the Codex Skill
## Install the official Codex plugin

Copy or symlink `skills/manage-taskboard` into the Codex skills directory, then start a new Codex task:
Install dependencies and expose the plugin's `taskctl` and `dashi-taskboard-mcp` bins:

```bash
ln -s /absolute/path/to/codex-taskboard/skills/manage-taskboard \
~/.codex/skills/manage-taskboard
npm ci
npm link
npm run build:web
npm run plugin:validate
```

The Skill teaches Codex to inspect an issue, move it to `in_progress`, use optimistic versions, verify the work, and then move it to `in_review`; it moves the issue to `done` only after the user explicitly confirms acceptance or asks to mark it complete.
Keep the loopback companion running, then register this checkout's repository marketplace and install the plugin:

## Embed in Codex
```bash
CODEX_TASKBOARD_HOST=127.0.0.1 npm start
```

Run these in another terminal on first installation:

```bash
codex plugin marketplace add .
codex plugin add dashi-taskboard@dashi-taskboard-local
codex plugin list --json
```

Restart Codex or start a new thread after installation so it discovers `manage-taskboard`, `shared-project-context`, and the seven `taskboard_context_*` MCP tools. The plugin does not start the companion and does not add a visual sidebar; open <http://127.0.0.1:47823> for the board UI. See [Shared project context](docs/shared-project-context.md) for the tool contract, two-device setup, security boundary, and troubleshooting.

After pulling plugin changes, `npm link` alone does not refresh Codex's cached Skills and MCP declaration. Follow the [local plugin refresh procedure](docs/shared-project-context.md#refresh-an-existing-installation), then start a new thread.

`manage-taskboard` teaches Codex to inspect and claim issues with optimistic versions, verify work, publish durable context when useful, and hand work to `in_review`. It moves an issue to `done` only after explicit user acceptance. `shared-project-context` reads and writes context through the MCP server without direct SQLite, D1, credential, or Codex-state access.

## Optional CDP UI injection (not the official plugin)

The following launcher adds the browser board as a visual Codex sidebar entry. It is optional compatibility tooling and is not the official plugin or its MCP transport. The plugin remains usable when CDP injection is disabled.

### Manual: use a dedicated CDP port

Expand Down Expand Up @@ -122,6 +144,9 @@ To use a different UI origin, set `window.__CODEX_TASKBOARD_URL__` before the us
| `CODEX_TASKBOARD_PORT` | `47823` | Local HTTP port |
| `CODEX_TASKBOARD_DATA_DIR` | `.data` | SQLite data directory |
| `CODEX_TASKBOARD_URL` | `http://127.0.0.1:47823` | CLI API origin |
| `CODEX_TASKBOARD_COMPANION_URL` | `CODEX_TASKBOARD_URL` or `http://127.0.0.1:47823` | Loopback companion origin for cloud control and MCP |

When `CODEX_TASKBOARD_URL` points `taskctl` at a LAN service, set `CODEX_TASKBOARD_COMPANION_URL` separately to the local loopback companion before starting Codex. The MCP server intentionally rejects a LAN origin with `INVALID_COMPANION_URL`.

`npm start` prints both the local URL and the available LAN URLs. Teammates on the same trusted network can open one of those LAN URLs and use the same taskboard service. Task, comment, and attachment changes are broadcast to every open client through server-sent events; reconnecting clients perform a full refresh so changes made while disconnected are not missed. A teammate using `taskctl` can point it at the shared service with `CODEX_TASKBOARD_URL=http://<host-ip>:47823`.

Expand All @@ -131,7 +156,7 @@ LAN mode has no account authentication: anyone on the trusted local network who

For two trusted collaborators, the taskboard can run on Cloudflare with Worker Static Assets and API routes, D1 as the authoritative business database, and a private R2 bucket for attachments. The deployment uses HTTPS Basic Authentication with a shared password and refreshes open boards after a global revision changes.

Each device keeps its own project checkout mapping and continues to use a local companion for Codex, Git/worktree, Skill, and MCP capabilities. Cloud mode never falls back to or double-writes the local SQLite database.
Each device installs the plugin, keeps its own project checkout mapping, and continues to use a local companion for Codex, Git/worktree, Skill, and MCP capabilities. Installing the plugin does not distribute board data, the shared password, or device mappings. Cloud mode never falls back to or double-writes the local SQLite database.

See [Cloud collaboration](docs/cloud-collaboration.md) for owner deployment, existing GitHub installation setup, password rotation, local path mapping, and the one-time local-data migration flow.

Expand Down
11 changes: 11 additions & 0 deletions cloud/migrations/0002_project_context.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
CREATE TABLE project_context_entries (id TEXT PRIMARY KEY, project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE, kind TEXT NOT NULL CHECK (kind IN ('requirement', 'decision', 'constraint', 'fact', 'risk', 'handoff', 'summary')), title TEXT NOT NULL, body TEXT NOT NULL CHECK (length(CAST(body AS BLOB)) <= 65536), tags TEXT NOT NULL DEFAULT '[]' CHECK (json_valid(tags) = 1 AND json_type(tags) = 'array'), source_type TEXT NOT NULL CHECK (source_type IN ('manual', 'issue', 'comment', 'thread_summary', 'agent')), source_id TEXT, source_thread_id TEXT, author_type TEXT NOT NULL CHECK (author_type IN ('user', 'agent')), author_id TEXT NOT NULL, author_name TEXT NOT NULL, pinned INTEGER NOT NULL DEFAULT 0 CHECK (pinned IN (0, 1)), archived_at TEXT, version INTEGER NOT NULL DEFAULT 1 CHECK (version > 0), idempotency_key TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL);
CREATE UNIQUE INDEX project_context_entries_project_idempotency ON project_context_entries(project_id, idempotency_key) WHERE idempotency_key IS NOT NULL;
CREATE INDEX project_context_entries_project_page ON project_context_entries(project_id, archived_at, created_at DESC, id DESC);
CREATE INDEX project_context_entries_project_kind ON project_context_entries(project_id, archived_at, kind, created_at DESC, id DESC);
CREATE INDEX project_context_entries_project_pinned ON project_context_entries(project_id, archived_at, pinned DESC, updated_at DESC, id);
CREATE TABLE project_context_revisions (id TEXT PRIMARY KEY, entry_id TEXT NOT NULL REFERENCES project_context_entries(id) ON DELETE CASCADE, version INTEGER NOT NULL CHECK (version > 0), title TEXT NOT NULL, body TEXT NOT NULL CHECK (length(CAST(body AS BLOB)) <= 65536), kind TEXT NOT NULL CHECK (kind IN ('requirement', 'decision', 'constraint', 'fact', 'risk', 'handoff', 'summary')), tags TEXT NOT NULL DEFAULT '[]' CHECK (json_valid(tags) = 1 AND json_type(tags) = 'array'), author_id TEXT NOT NULL, author_name TEXT NOT NULL, created_at TEXT NOT NULL);
CREATE UNIQUE INDEX project_context_revisions_entry_version_unique ON project_context_revisions(entry_id, version);
CREATE INDEX project_context_revisions_entry_versions ON project_context_revisions(entry_id, version DESC);
CREATE TRIGGER project_context_entries_global_revision_insert AFTER INSERT ON project_context_entries BEGIN UPDATE global_revision SET revision = revision + 1 WHERE singleton = 1; END;
CREATE TRIGGER project_context_entries_global_revision_update AFTER UPDATE ON project_context_entries BEGIN UPDATE global_revision SET revision = revision + 1 WHERE singleton = 1; END;
CREATE TRIGGER project_context_entries_global_revision_delete AFTER DELETE ON project_context_entries BEGIN UPDATE global_revision SET revision = revision + 1 WHERE singleton = 1; END;
Loading