Implement the penpot_list_projects tool (teams and projects) with unit tests #190

Closed
opened 2026-09-14 23:09:29 +00:00 by david · 1 comment
Owner

Summary

Implement the penpot_list_projects tool: list the token user's teams (with each team's enabled features and the caller's permissions) and their projects. Projects are the container the extension later creates design files in, so this is the first read primitive and the preflight for every write path.

Background

Depends on: #188

Read-only. Confirmed on Penpot 2.17 against a live instance:

  • get-teams takes no parameters and returns the teams the caller belongs to, each carrying its enabled features (e.g. layout/grid, design-tokens/v1) and the caller's permissions on that team.
  • get-projects requires a teamId — it errors without one. There is no "list all projects" call, so the tool must fan out over teams (or over a single team when the caller names one).

The team features/permissions data is directly useful later: the write path needs to know whether the caller can create files in a given team, and whether layout support is enabled.

Documentation Required

A separate process downloads these into the listed folders before this issue is implemented. Check the folders for the actual reference material before starting.

docs/reference/penpot-api/

  • <PENPOT_URL>/api/main/doc/openapi.json — the authoritative list of commands and parameters for this instance; confirm get-teams (no params) and get-projects (requires teamId) before implementing.
  • https://help.penpot.app/technical-guide/integration/ — auth and RPC URL shape (Authorization: Token <token>, /api/rpc/command/<command>).
  • https://help.penpot.app/user-guide/teams/ — product-level meaning of teams, team members and permissions, useful for writing accurate tool output.

docs/reference/pi-coding-agent/

docs/reference/typebox/

Implementation Details

Create extensions/penpot/src/tools/projects.ts exporting the tool definition, and register it in extensions/penpot/src/index.ts alongside penpot_whoami.

Parameters:

  • team_id (string, optional) — when provided, skip get-teams and list only that team's projects.

Behaviour:

  1. Call get-teams (no parameters) when team_id is absent; otherwise use the single given team id.
  2. For each team, GET get-projects with the required teamId query/body parameter — exactly as the spec requires. Use Promise.all but bound concurrency if the team count is large (a simple sequential loop is acceptable for a personal instance; do not fire an unbounded fan-out).
  3. Return a text listing that is easy to scan: team name (id) with its features, the caller's permissions, and each project name (id). Surface the team id prominently — the caller needs it to name a target project.
  4. details carries the structured shape: { teams: [{ id, name, features, permissions, projects: [{ id, name }] }] }.
  5. A team whose get-projects call fails must not abort the whole listing: record the error against that team and continue, reporting it in the text output.

Keep the parameter schema permissive and additive (future steps may add filters) but do not add parameters that are not used.

Acceptance Criteria

  • penpot_list_projects is registered and callable.
  • With no team_id, it calls get-teams and then get-projects for every team, passing a real teamId.
  • With team_id, it calls get-projects once for that team and does not call get-teams.
  • Output lists team name + id, the team's features, the caller's permissions, and each project name + id.
  • details contains the structured teams/projects shape.
  • One failing team does not abort the listing; the failure is reported in the output.
  • A missing/expired token produces a readable auth error (no raw Transit).
  • Unit tests with a stubbed client are green with no network access.

Test Plan

# stubbed client — no credentials required
node --test extensions/penpot/src/tools/projects.test.ts

Live check (requires PENPOT_URL/PENPOT_TOKEN):

  • Call penpot_list_projects with no arguments and confirm the teams/projects match the Penpot UI sidebar.
  • Call it with one of the returned team_ids and confirm only that team is listed.
  • Confirm the get-projects-without-teamId failure mode is avoided (no validation error from the server).
## Summary Implement the `penpot_list_projects` tool: list the token user's teams (with each team's enabled features and the caller's permissions) and their projects. Projects are the container the extension later creates design files in, so this is the first read primitive and the preflight for every write path. ## Background **Depends on:** #188 Read-only. Confirmed on Penpot 2.17 against a live instance: - `get-teams` takes **no parameters** and returns the teams the caller belongs to, each carrying its enabled `features` (e.g. `layout/grid`, `design-tokens/v1`) and the caller's `permissions` on that team. - `get-projects` **requires a `teamId`** — it errors without one. There is no "list all projects" call, so the tool must fan out over teams (or over a single team when the caller names one). The team `features`/`permissions` data is directly useful later: the write path needs to know whether the caller can create files in a given team, and whether layout support is enabled. ## Documentation Required A separate process downloads these into the listed folders before this issue is implemented. Check the folders for the actual reference material before starting. **`docs/reference/penpot-api/`** - `<PENPOT_URL>/api/main/doc/openapi.json` — the authoritative list of commands and parameters for this instance; confirm `get-teams` (no params) and `get-projects` (requires `teamId`) before implementing. - https://help.penpot.app/technical-guide/integration/ — auth and RPC URL shape (`Authorization: Token <token>`, `/api/rpc/command/<command>`). - https://help.penpot.app/user-guide/teams/ — product-level meaning of teams, team members and permissions, useful for writing accurate tool output. **`docs/reference/pi-coding-agent/`** - https://pi.dev/docs/latest/extensions — `pi.registerTool` shape and how a tool returns text plus structured `details`. **`docs/reference/typebox/`** - https://github.com/sinclairzx81/typebox — `Type.Object`, `Type.Optional`, `Type.String`, and `Type.Static<typeof T>` for inferring the parameter type. ## Implementation Details Create `extensions/penpot/src/tools/projects.ts` exporting the tool definition, and register it in `extensions/penpot/src/index.ts` alongside `penpot_whoami`. Parameters: - `team_id` (string, optional) — when provided, skip `get-teams` and list only that team's projects. Behaviour: 1. Call `get-teams` (no parameters) when `team_id` is absent; otherwise use the single given team id. 2. For each team, `GET get-projects` with the required `teamId` query/body parameter — exactly as the spec requires. Use `Promise.all` but bound concurrency if the team count is large (a simple sequential loop is acceptable for a personal instance; do not fire an unbounded fan-out). 3. Return a text listing that is easy to scan: team name (id) with its `features`, the caller's `permissions`, and each project name (id). Surface the team id prominently — the caller needs it to name a target project. 4. `details` carries the structured shape: `{ teams: [{ id, name, features, permissions, projects: [{ id, name }] }] }`. 5. A team whose `get-projects` call fails must not abort the whole listing: record the error against that team and continue, reporting it in the text output. Keep the parameter schema permissive and additive (future steps may add filters) but do not add parameters that are not used. ## Acceptance Criteria - [ ] `penpot_list_projects` is registered and callable. - [ ] With no `team_id`, it calls `get-teams` and then `get-projects` for every team, passing a real `teamId`. - [ ] With `team_id`, it calls `get-projects` once for that team and does not call `get-teams`. - [ ] Output lists team name + id, the team's features, the caller's permissions, and each project name + id. - [ ] `details` contains the structured teams/projects shape. - [ ] One failing team does not abort the listing; the failure is reported in the output. - [ ] A missing/expired token produces a readable auth error (no raw Transit). - [ ] Unit tests with a stubbed client are green with no network access. ## Test Plan ```bash # stubbed client — no credentials required node --test extensions/penpot/src/tools/projects.test.ts ``` Live check (requires `PENPOT_URL`/`PENPOT_TOKEN`): - Call `penpot_list_projects` with no arguments and confirm the teams/projects match the Penpot UI sidebar. - Call it with one of the returned `team_id`s and confirm only that team is listed. - Confirm the `get-projects`-without-`teamId` failure mode is avoided (no validation error from the server).
david closed this issue 2026-09-14 23:56:57 +00:00
Author
Owner

pi-loop opened and merged a pull request for this issue: #223

pi-loop opened and merged a pull request for this issue: https://git.excelera.net/david/pi-extensions-and-skills/pulls/223
Sign in to join this conversation.
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
david/pi-extensions-and-skills#190
No description provided.