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
11 changes: 11 additions & 0 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,17 @@
"source": "./bankr-agent-dev",
"category": "development",
"homepage": "https://bankr.bot"
},
{
"name": "taskmarket",
"description": "Read-only Taskmarket discovery: browse open work, inspect a task, and list public submissions. Create-task is a CLI preview only — no spend, no keys.",
"author": {
"name": "DuReef (community)",
"email": "hello@dureef.com"
},
"source": "./taskmarket",
"category": "tools",
"homepage": "https://taskmarket.dev"
}
]
}
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,16 @@ _Maintained by the Bankr team._

[View Plugin →](./x402-sdk-dev/)

### taskmarket

**Read-only Taskmarket discovery (community)**

- Browse open Taskmarket work and inspect a task
- List public submissions for human review
- Create-task is a first-party CLI preview only — no spend, no keys, no POST

[View Plugin →](./taskmarket/)

## Installation

### Claude Code
Expand All @@ -63,6 +73,9 @@ claude plugin install bankr-agent-dev@bankr-claude-plugins

# For bankr-x402-sdk-dev (Web3 development SDK)
claude plugin install bankr-x402-sdk-dev@bankr-claude-plugins

# For taskmarket (read-only Taskmarket discovery)
claude plugin install taskmarket@bankr-claude-plugins
```

### Other Coding Tools (Cursor, OpenCode, Gemini CLI, Antigravity, etc.)
Expand Down
10 changes: 10 additions & 0 deletions taskmarket/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "taskmarket",
"version": "1.0.0",
"description": "Read-only Taskmarket discovery for Bankr/Claude agents: browse open work, inspect a task, and track public submissions. Creating a task is a CLI preview only — no spend, no keys.",
"author": {
"name": "DuReef (community)",
"email": "hello@dureef.com"
},
"keywords": ["taskmarket", "bounty", "agents", "delegation", "base", "usdc"]
}
8 changes: 8 additions & 0 deletions taskmarket/.mcp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"mcpServers": {
"taskmarket": {
"command": "node",
"args": ["${CLAUDE_PLUGIN_ROOT}/mcp-server/src/index.js"]
}
}
}
55 changes: 55 additions & 0 deletions taskmarket/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Taskmarket plugin for Claude Code

Read-only [Taskmarket](https://taskmarket.dev) discovery inside Bankr's Claude plugin marketplace.

Agents can **browse open work**, **inspect a task**, and **list public submissions**. Creating a bounty is a **CLI preview only** — this plugin never spends USDC, never holds keys, and never POSTs to the API.

This is a community contribution. It does not impersonate Bankr or Taskmarket, and it is not affiliated with Base or Coinbase.

## Why it belongs here

Bankr already gives Claude Code agents wallets, DeFi, and x402. Taskmarket is the complementary **delegation** surface: when a request is better done by an external worker than by burning more local inference, the agent can discover funded work (and a human can post work via the first-party CLI).

Pre-check (2026-08-17): this marketplace had `bankr-agent`, `bankr-agent-dev`, and `bankr-x402-sdk-dev` only — no Taskmarket plugin.

## Tools

| Tool | HTTP | Spend |
|------|------|--------|
| `taskmarket_list_tasks` | GET `/api/tasks` | none |
| `taskmarket_get_task` | GET `/api/tasks/{id}` | none |
| `taskmarket_list_submissions` | GET `/api/tasks/{id}/submissions` | none |
| `taskmarket_create_preview` | none (prints CLI) | none |

Host allowlist: `https://api.taskmarket.dev` only.

## Install

```bash
claude plugin marketplace add BankrBot/claude-plugins
claude plugin install taskmarket@bankr-claude-plugins
```

Requires Node.js 18+. No API key. No bun. No `BANKR_API_KEY`.

## Tests

```bash
cd taskmarket/mcp-server
node --test
```

## Create a task (human + official CLI)

```bash
npm i -g @lucid-agents/taskmarket
taskmarket task create --description "..." --reward 50 --duration-hours 72 --mode bounty
```

Wallet import and x402 stay in that CLI. Do not paste keys into Claude.

## Links

- Taskmarket: https://taskmarket.dev
- Docs: https://docs.taskmarket.dev
- Bankr: https://bankr.bot
15 changes: 15 additions & 0 deletions taskmarket/mcp-server/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "taskmarket-mcp-server",
"version": "1.0.0",
"description": "GET-only Taskmarket MCP server (no wallet, no spend)",
"type": "module",
"main": "src/index.js",
"scripts": {
"start": "node src/index.js",
"test": "node --test src/*.test.js"
},
"engines": {
"node": ">=18.0.0"
},
"license": "MIT"
}
76 changes: 76 additions & 0 deletions taskmarket/mcp-server/src/client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import {
assertGetOnly,
assertTaskId,
clampLimit,
resolveApiOrigin,
} from "./safety.js";

async function getJson(origin, path, { fetchImpl = fetch } = {}) {
assertGetOnly("GET");
const url = `${origin}${path}`;
const response = await fetchImpl(url, {
method: "GET",
headers: { Accept: "application/json" },
});
const text = await response.text();
if (!response.ok) {
throw new Error(`Taskmarket GET ${path} failed: ${response.status} ${text.slice(0, 240)}`);
}
try {
return JSON.parse(text);
} catch {
throw new Error(`Taskmarket GET ${path} returned non-JSON`);
}
}

export function createClient({ env = process.env, fetchImpl = fetch } = {}) {
const origin = resolveApiOrigin(env);

return {
origin,
async listTasks({ status = "open", limit = 20 } = {}) {
const capped = clampLimit(limit);
const params = new URLSearchParams({
status: String(status || "open"),
limit: String(capped),
});
return getJson(origin, `/api/tasks?${params}`, { fetchImpl });
},
async getTask(taskId) {
const id = assertTaskId(taskId);
return getJson(origin, `/api/tasks/${id}`, { fetchImpl });
},
async listSubmissions(taskId) {
const id = assertTaskId(taskId);
return getJson(origin, `/api/tasks/${id}/submissions`, { fetchImpl });
},
createPreview({ description, rewardUsdc, durationHours = 72 } = {}) {
const desc = String(description || "").trim();
const reward = Number(rewardUsdc);
const hours = Number(durationHours);
if (!desc) {
throw new Error("description is required for create_preview");
}
if (!Number.isFinite(reward) || reward <= 0) {
throw new Error("rewardUsdc must be a positive number");
}
if (!Number.isFinite(hours) || hours < 1) {
throw new Error("durationHours must be >= 1");
}
const cmd = [
"taskmarket task create",
`--description ${JSON.stringify(desc)}`,
`--reward ${reward}`,
`--duration-hours ${Math.trunc(hours)}`,
"--mode bounty",
].join(" ");
return {
previewOnly: true,
fetched: false,
instruction:
"This plugin never creates tasks or spends USDC. Run the first-party CLI yourself after an explicit human confirm. Wallet keys stay in the CLI.",
command: cmd,
};
},
};
}
85 changes: 85 additions & 0 deletions taskmarket/mcp-server/src/client.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import assert from "node:assert/strict";
import test from "node:test";
import { createClient } from "./client.js";
import { assertHttpsApiOrigin, assertGetOnly } from "./safety.js";

function mockFetch(handler) {
return async (url, init = {}) => {
assert.equal(init.method, "GET");
return handler(String(url), init);
};
}

function jsonResponse(body, status = 200) {
return {
ok: status >= 200 && status < 300,
status,
async text() {
return JSON.stringify(body);
},
};
}

test("listTasks uses public GET /api/tasks and caps limit", async () => {
const seen = [];
const client = createClient({
fetchImpl: mockFetch((url) => {
seen.push(url);
return jsonResponse({ tasks: [{ id: "0x" + "ab".repeat(32) }] });
}),
});
const out = await client.listTasks({ status: "open", limit: 99 });
assert.equal(out.tasks.length, 1);
assert.match(seen[0], /^https:\/\/api\.taskmarket\.dev\/api\/tasks\?/);
assert.match(seen[0], /limit=20/);
});

test("getTask and listSubmissions hit allowlisted GET paths", async () => {
const id = "0x" + "cd".repeat(32);
const seen = [];
const client = createClient({
fetchImpl: mockFetch((url) => {
seen.push(url);
return jsonResponse({ ok: true, url });
}),
});
await client.getTask(id);
await client.listSubmissions(id);
assert.deepEqual(seen, [
`https://api.taskmarket.dev/api/tasks/${id}`,
`https://api.taskmarket.dev/api/tasks/${id}/submissions`,
]);
});

test("createPreview never fetches", () => {
let fetches = 0;
const client = createClient({
fetchImpl: async () => {
fetches += 1;
throw new Error("should not fetch");
},
});
const preview = client.createPreview({
description: "Audit one GitHub Actions pipeline",
rewardUsdc: 50,
durationHours: 48,
});
assert.equal(preview.previewOnly, true);
assert.equal(preview.fetched, false);
assert.equal(fetches, 0);
assert.match(preview.command, /taskmarket task create/);
assert.doesNotMatch(preview.command, /PRIVATE|0x[0-9a-fA-F]{64}/);
});

test("rejects http and foreign hosts", () => {
assert.throws(() => assertHttpsApiOrigin("http://api.taskmarket.dev"), /https/);
assert.throws(() => assertHttpsApiOrigin("https://evil.example"), /host/);
assert.throws(() => assertGetOnly("POST"), /GET-only/);
assert.throws(
() =>
createClient({
env: { TASKMARKET_API_URL: "https://example.com" },
}),
/host/,
);
});
Loading