Skip to content
Merged
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
6 changes: 3 additions & 3 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
blank_issues_enabled: false
contact_links:
- name: Security policy
url: https://github.com/backslash-ux/plane-cli-cli/blob/main/SECURITY.md
url: https://github.com/backslash-ux/plane-cli/blob/main/SECURITY.md
about: Report suspected vulnerabilities privately instead of opening a public issue.
- name: Contributing guide
url: https://github.com/backslash-ux/plane-cli-cli/blob/main/CONTRIBUTING.md
url: https://github.com/backslash-ux/plane-cli/blob/main/CONTRIBUTING.md
about: Review contribution expectations, quality gates, and documentation requirements first.
- name: Existing issues
url: https://github.com/backslash-ux/plane-cli-cli/issues
url: https://github.com/backslash-ux/plane-cli/issues
about: Check open issues and feature requests before filing a new one.
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,25 @@ Earlier project history may predate this file.

## [Unreleased]

## 1.1.0

### Added

- `plane modules create` with optional description, status, schedule, and lead resolution.
- `plane init --local` now prompts whether to import the SKILL.md CLI usage guide into AGENTS.md. First-time prompt defaults to `N`; subsequent runs (section already present) default to `Y`. The skill section is wrapped in idempotent HTML comment markers so repeated runs update it in place.

### Changed

- **Consistent project defaulting for create commands.** `issue create`, `modules create`, `labels create`, and `pages create` now use `--title`/`--name` options instead of positional args, so the project positional can be omitted to use the saved current project.
- `hasSkillSectionInAgentsFile` now requires both the start and end delimiters to be present before treating an existing skill section as complete, preventing duplicate sections in malformed files.

### Validated

- `plane init --local` skill import prompt exercised: accept (`y`), decline (empty/default N), and idempotent re-run paths all verified via tests.
- All 261 tests pass with line and function coverage above the 95% threshold.

## 1.0.0

### Added

- Public open-source repository baseline with contributor, governance, security, architecture, and release documentation.
Expand Down
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# plane

[![CI](https://github.com/backslash-ux/plane-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/backslash-ux/plane-cli-cli/actions/workflows/ci.yml) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
[![CI](https://github.com/backslash-ux/plane-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/backslash-ux/plane-cli/actions/workflows/ci.yml) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)

CLI for the [Plane](https://plane.so) project management API.

Expand Down Expand Up @@ -103,8 +103,8 @@ plane issues list
plane issues list PROJ
plane issues list PROJ --state started
plane issue get PROJ-29
plane issue create PROJ "Title"
plane issue create @current "Title"
plane issue create --title "Title"
plane issue create --title "Title" PROJ
plane issue update --state completed --priority high PROJ-29
plane issue delete PROJ-29

Expand Down Expand Up @@ -134,6 +134,7 @@ plane cycles issues add PROJ CYCLE_ID PROJ-29

# Modules
plane modules list PROJ
plane modules create --name "Sprint 3"
plane modules delete PROJ MODULE_ID
plane modules issues list PROJ MODULE_ID
plane modules issues add PROJ MODULE_ID PROJ-29
Expand Down Expand Up @@ -179,6 +180,8 @@ plane cycles list PROJ --json
- `--description` for issue and page create or update commands is sent through to Plane as HTML in `description_html`.
- `plane issue link add` accepts an optional link title via `--title`.
- `plane labels delete` accepts either the label UUID or the exact label name returned by `plane labels list`.
- `plane modules create --lead` accepts a member display name, email, or UUID from `plane members list`.
- `plane modules create --status in_progress` is normalized to Plane's `in-progress` API value.
- `plane modules delete` accepts either the module UUID or the exact module name returned by `plane modules list`.
- `plane modules issues remove` expects the module-issue identifier returned by `plane modules issues list`, not an issue ref.
- `plane members list` is workspace-scoped and does not take a project argument.
Expand All @@ -197,7 +200,7 @@ bun update -g @backslash-ux/plane-cli
## Development

```bash
git clone https://github.com/backslash-ux/plane-cli-cli
git clone https://github.com/backslash-ux/plane-cli
cd plane-cli
bun install

Expand Down
21 changes: 12 additions & 9 deletions SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,12 +144,12 @@ plane issue get PROJ-29
### Create

```bash
plane issue create PROJ "Issue title"
plane issue create @current "Issue title"
plane issue create --priority high --state started PROJ "Fix lint pipeline"
plane issue create --description '<p>Detailed context</p>' PROJ "Add dark mode"
plane issue create --assignee "Jane Doe" PROJ "Onboarding bug"
plane issue create --label "bug" PROJ "Regression in login flow"
plane issue create --title "Issue title"
plane issue create --title "Issue title" PROJ
plane issue create --priority high --state started --title "Fix lint pipeline"
plane issue create --description '<p>Detailed context</p>' --title "Add dark mode" PROJ
plane issue create --assignee "Jane Doe" --title "Onboarding bug" PROJ
plane issue create --label "bug" --title "Regression in login flow" PROJ
```

### Update
Expand Down Expand Up @@ -233,8 +233,8 @@ State IDs are UUIDs unique per project. Always fetch live — never hardcode.
plane labels list
plane labels list PROJ
plane labels list PROJ --xml
plane labels create PROJ "bug"
plane labels create --color "#ff0000" PROJ "critical"
plane labels create --name "bug"
plane labels create --name "critical" --color "#ff0000" PROJ
plane labels delete PROJ bug
```

Expand Down Expand Up @@ -271,6 +271,7 @@ Cycle IDs are UUIDs. Fetch them from `plane cycles list PROJ`.
plane modules list
plane modules list PROJ
plane modules list PROJ --xml
plane modules create --name "Sprint 3"
plane modules delete PROJ <module-id>
plane modules issues list PROJ <module-id>
plane modules issues add PROJ <module-id> PROJ-29
Expand Down Expand Up @@ -299,7 +300,7 @@ plane pages list
plane pages list PROJ
plane pages list PROJ --xml
plane pages get PROJ <page-id> # full JSON including description_html
plane pages create --name "My Page" PROJ
plane pages create --name "My Page"
plane pages create --name "My Page" --description '<p>Content here</p>' PROJ
plane pages update --name "New Title" PROJ <page-id>
plane pages update --description '<p>New content</p>' PROJ <page-id>
Expand Down Expand Up @@ -337,6 +338,8 @@ Some deployments do not expose page endpoints even when the project advertises p
- No server-side text search — fetch all issues and filter locally.
- No epics — use labels or modules to group related issues.
- `description` in issue or page create and update flows is passed through to `description_html`; send HTML such as `<p>Details</p>` when you want formatted output.
- `plane modules create --lead` accepts a member display name, email, or UUID from `plane members list`.
- `plane modules create --status in_progress` is normalized to Plane's `in-progress` API value.
- Always fetch state/label/member IDs live — never hardcode UUIDs across workspaces.
- `plane issue get PROJ-N` is the fastest way to inspect all fields on a single issue.

Expand Down
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"publishConfig": {
"access": "public"
},
"version": "1.0.0",
"version": "1.1.0",
"description": "CLI for the Plane project management API",
"author": "Gabriel Reynold and Contributors",
"license": "MIT",
Expand Down Expand Up @@ -33,12 +33,12 @@
},
"repository": {
"type": "git",
"url": "git+https://github.com/backslash-ux/plane-cli-cli.git"
"url": "git+https://github.com/backslash-ux/plane-cli.git"
},
"bugs": {
"url": "https://github.com/backslash-ux/plane-cli-cli/issues"
"url": "https://github.com/backslash-ux/plane-cli/issues"
},
"homepage": "https://github.com/backslash-ux/plane-cli-cli#readme",
"homepage": "https://github.com/backslash-ux/plane-cli#readme",
"engines": {
"bun": ">=1.0.0"
},
Expand Down
9 changes: 5 additions & 4 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@ QUICK START
plane issues list List issues for the saved current project
plane issues list PROJ List issues for a project
plane issue get PROJ-29 Get full JSON for an issue
plane issue create PROJ "title" Create an issue
plane issue create @current "title" Create an issue in the saved current project
plane issue create --title "title" Create an issue in the saved current project
plane issue create --title "title" PROJ
plane modules create --name "Sprint 3"
plane issue update --state done PROJ-29
plane issue comment PROJ-29 "text" Add a comment

Expand All @@ -55,7 +56,7 @@ ALL SUBCOMMANDS
issue get | create | update | delete | comment | activity |
link | comments | worklogs
cycles list | issues (list, add)
modules list | delete | issues (list, add, remove)
modules list | create | delete | issues (list, add, remove)
intake list | accept | reject
pages list | get | create | update | delete | archive | unarchive | lock | unlock | duplicate
states list List workflow states for a project
Expand Down Expand Up @@ -94,5 +95,5 @@ FOR AI AGENTS / BOTS

export const cli = Command.run(plane, {
name: "plane",
version: "1.0.0",
version: "1.1.0",
});
33 changes: 32 additions & 1 deletion src/commands/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import {
} from "../config.js";
import {
getLocalAgentsFilePath,
hasSkillSectionInAgentsFile,
importSkillIntoAgentsFile,
readPackageSkillContent,
writeLocalProjectAgentsFile,
} from "../project-agents.js";
import {
Expand Down Expand Up @@ -545,6 +548,34 @@ export function initHandler(
yield* Console.log(" Estimate: disabled");
}
yield* Console.log(`Local AGENTS.md updated at ${agentsPath}`);

const skillContent = readPackageSkillContent();
if (skillContent) {
const alreadyHasSkill = hasSkillSectionInAgentsFile();
const skillPromptText = alreadyHasSkill
? "Update SKILL.md (CLI usage guide) in AGENTS.md? [Y/n]: "
: "Import SKILL.md (CLI usage guide) into AGENTS.md? [y/N]: ";
const skillRl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
let skillAnswer: string;
try {
skillAnswer = yield* Effect.promise(() =>
prompt(skillRl, skillPromptText),
);
} finally {
skillRl.close();
}
const trimmed = skillAnswer.trim().toLowerCase();
const shouldImport = alreadyHasSkill
? trimmed !== "n" && trimmed !== "no"
: trimmed === "y" || trimmed === "yes";
if (shouldImport) {
importSkillIntoAgentsFile(skillContent);
yield* Console.log(" SKILL.md imported into AGENTS.md");
}
}
} else {
yield* Console.log(
`\nWarning: could not load project helper data for ${selectedProject.identifier}: ${projectHelper.left.message}`,
Expand All @@ -569,6 +600,6 @@ export const localInit = Command.make("init", {}, () =>
initHandler({ global: false, local: true }, "local"),
).pipe(
Command.withDescription(
"Interactive local setup. Saves overrides to ./.plane/config.json in the current directory, reports project feature flags, writes a local project helper snapshot for states, labels, and estimate points, and updates AGENTS.md with project-context guidance for AI agents.",
"Interactive local setup. Saves overrides to ./.plane/config.json in the current directory, reports project feature flags, writes a local project helper snapshot for states, labels, and estimate points, updates AGENTS.md with project-context guidance for AI agents, and optionally imports the SKILL.md CLI usage guide into AGENTS.md.",
),
);
15 changes: 8 additions & 7 deletions src/commands/issue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,13 +204,14 @@ export const issueComment = Command.make(
),
);
// --- issue create ---
const titleArg = Args.text({ name: "title" }).pipe(
Args.withDescription("Issue title"),
const createTitleOption = Options.text("title").pipe(
Options.withDescription("Issue title"),
);
const projectRefArg = Args.text({ name: "project" }).pipe(
const createProjectArg = Args.text({ name: "project" }).pipe(
Args.withDescription(
"Project identifier (e.g. PROJ). Use '@current' for the saved default project.",
"Project identifier (e.g. PROJ). Omit to use the saved current project.",
),
Args.withDefault(""),
);

const createPriorityOption = Options.optional(
Expand Down Expand Up @@ -285,13 +286,13 @@ export const issueCreate = Command.make(
description: createDescriptionOption,
assignee: createAssigneeOption,
label: createLabelOption,
project: projectRefArg,
title: titleArg,
title: createTitleOption,
project: createProjectArg,
},
issueCreateHandler,
).pipe(
Command.withDescription(
'Create a new issue in a project. Use @current to target the saved default project.\n\nExamples:\n plane issue create PROJ "Migrate Button component"\n plane issue create @current "Migrate Button component"\n plane issue create --priority high --state started PROJ "Fix lint pipeline"\n plane issue create --description "Detailed context here" PROJ "Add dark mode"\n plane issue create --assignee "Jane Doe" PROJ "Onboarding bug"',
'Create a new issue in a project. Omit PROJECT to use the saved current project.\n\nExamples:\n plane issue create --title "Migrate Button component"\n plane issue create --title "Migrate Button component" PROJ\n plane issue create --priority high --state started --title "Fix lint pipeline"\n plane issue create --description "Detailed context here" --title "Add dark mode" PROJ\n plane issue create --assignee "Jane Doe" --title "Onboarding bug" PROJ',
),
);
// --- issue activity ---
Expand Down
16 changes: 10 additions & 6 deletions src/commands/labels.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Args, Command, Options } from "@effect/cli";
import { Console, Effect } from "effect";
import { Console, Effect, Option } from "effect";
import { api, decodeOrFail } from "../api.js";
import { LabelSchema, LabelsResponseSchema } from "../config.js";
import { jsonMode, toXml, xmlMode } from "../output.js";
Expand Down Expand Up @@ -47,8 +47,8 @@ export const labelsList = Command.make(

// --- labels create ---

const nameArg = Args.text({ name: "name" }).pipe(
Args.withDescription("Label name"),
const createNameOption = Options.text("name").pipe(
Options.withDescription("Label name"),
);
const colorOption = Options.optional(Options.text("color")).pipe(
Options.withDescription("Hex color e.g. #ff0000"),
Expand All @@ -61,8 +61,12 @@ const labelArg = Args.text({ name: "label" }).pipe(

export const labelsCreate = Command.make(
"create",
{ color: colorOption, project: projectArg, name: nameArg },
{ color: colorOption, project: listProjectArg, name: createNameOption },
labelsCreateHandler,
).pipe(
Command.withDescription(
'Create a new label in a project. Omit PROJECT to use the saved current project.\n\nExamples:\n plane labels create --name bug\n plane labels create --name bug --color "#ff0000" PROJ',
),
);

export function labelsCreateHandler({
Expand All @@ -72,7 +76,7 @@ export function labelsCreateHandler({
}: {
project: string;
name: string;
color: { _tag: "Some"; value: string } | { _tag: "None" };
color: Option.Option<string>;
}) {
return Effect.gen(function* () {
const { id } = yield* resolveProject(project);
Expand All @@ -81,7 +85,7 @@ export function labelsCreateHandler({
color?: string;
}
const body: LabelPayload = { name };
if (color._tag === "Some") body.color = color.value;
if (Option.isSome(color)) body.color = color.value;
const raw = yield* api.post(`projects/${id}/labels/`, body);
const label = yield* decodeOrFail(LabelSchema, raw);
yield* Console.log(`Created label: ${label.name} (${label.id})`);
Expand Down
Loading
Loading