import { Command, Args, Options } from "@effect/cli";
import { Console, Effect, Option } from "effect";
import { api, decodeOrFail } from "../api.js";
import { PagesResponseSchema, PageSchema } from "../config.js";
import { resolveProject } from "../resolve.js";
import { jsonMode, xmlMode, toXml } from "../output.js";
const projectArg = Args.text({ name: "project" }).pipe(
Args.withDescription("Project identifier (e.g. PROJ, WEB, OPS)"),
);
const pageIdArg = Args.text({ name: "page-id" }).pipe(
Args.withDescription("Page UUID (from 'plane pages list')"),
);
const nameOption = Options.text("name").pipe(
Options.withDescription("Page name/title"),
);
const nameOptionalOption = Options.optional(Options.text("name")).pipe(
Options.withDescription("New page name/title"),
);
const descriptionOption = Options.optional(Options.text("description")).pipe(
Options.withDescription("Page description as HTML (e.g. '
Hello
')"),
);
interface PageCreatePayload { name: string; description_html?: string; }
interface PageUpdatePayload { name?: string; description_html?: string; }
// --- pages list ---
export function pagesListHandler({ project }: { project: string }) {
return Effect.gen(function* () {
const { id } = yield* resolveProject(project);
const raw = yield* api.get(`projects/${id}/pages/`);
const { results } = yield* decodeOrFail(PagesResponseSchema, raw);
if (jsonMode) {
yield* Console.log(JSON.stringify(results, null, 2));
return;
}
if (xmlMode) {
yield* Console.log(toXml(results));
return;
}
if (results.length === 0) {
yield* Console.log("No pages");
return;
}
const lines = results.map((p) => {
const updated = (p.updated_at ?? p.created_at).slice(0, 10);
return `${p.id} ${updated} ${p.name}`;
});
yield* Console.log(lines.join("\n"));
});
}
export const pagesList = Command.make(
"list",
{ project: projectArg },
pagesListHandler,
).pipe(
Command.withDescription(
"List pages for a project. Shows page UUID, last updated date, and title.\n\nExample:\n plane pages list PROJ",
),
);
// --- pages get ---
export function pagesGetHandler({
project,
pageId,
}: {
project: string;
pageId: string;
}) {
return Effect.gen(function* () {
const { id } = yield* resolveProject(project);
const raw = yield* api.get(`projects/${id}/pages/${pageId}/`);
const page = yield* decodeOrFail(PageSchema, raw);
yield* Console.log(JSON.stringify(page, null, 2));
});
}
export const pagesGet = Command.make(
"get",
{ project: projectArg, pageId: pageIdArg },
pagesGetHandler,
).pipe(
Command.withDescription(
"Print full JSON for a page including description_html.\n\nExample:\n plane pages get PROJ ",
),
);
// --- pages create ---
export function pagesCreateHandler({
project,
name,
description,
}: {
project: string;
name: string;
description: Option.Option;
}) {
return Effect.gen(function* () {
const { id } = yield* resolveProject(project);
const body: PageCreatePayload = { name };
if (Option.isSome(description)) {
body.description_html = description.value;
}
const raw = yield* api.post(`projects/${id}/pages/`, body);
const page = yield* decodeOrFail(PageSchema, raw);
yield* Console.log(`Created page ${page.id}: ${page.name}`);
});
}
export const pagesCreate = Command.make(
"create",
{ project: projectArg, name: nameOption, description: descriptionOption },
pagesCreateHandler,
).pipe(
Command.withDescription(
"Create a new page.\n\nExample:\n plane pages create --name \"My Page\" PROJ",
),
);
// --- pages update ---
export function pagesUpdateHandler({
project,
pageId,
name,
description,
}: {
project: string;
pageId: string;
name: Option.Option;
description: Option.Option;
}) {
return Effect.gen(function* () {
if (Option.isNone(name) && Option.isNone(description)) {
yield* Effect.fail(new Error("provide at least --name or --description"));
}
const { id } = yield* resolveProject(project);
const body: PageUpdatePayload = {};
if (Option.isSome(name)) body.name = name.value;
if (Option.isSome(description)) body.description_html = description.value;
const raw = yield* api.patch(`projects/${id}/pages/${pageId}/`, body);
const page = yield* decodeOrFail(PageSchema, raw);
yield* Console.log(`Updated page ${page.id}: ${page.name}`);
});
}
export const pagesUpdate = Command.make(
"update",
{ project: projectArg, pageId: pageIdArg, name: nameOptionalOption, description: descriptionOption },
pagesUpdateHandler,
).pipe(
Command.withDescription(
"Update a page's name or description.\n\nExample:\n plane pages update --name \"New Title\" PROJ ",
),
);
// --- pages delete ---
export function pagesDeleteHandler({
project,
pageId,
}: {
project: string;
pageId: string;
}) {
return Effect.gen(function* () {
const { id } = yield* resolveProject(project);
yield* api.delete(`projects/${id}/pages/${pageId}/`);
yield* Console.log(`Deleted page ${pageId}`);
});
}
export const pagesDelete = Command.make(
"delete",
{ project: projectArg, pageId: pageIdArg },
pagesDeleteHandler,
).pipe(
Command.withDescription(
"Delete a page.\n\nExample:\n plane pages delete PROJ ",
),
);
// --- pages archive ---
export function pagesArchiveHandler({
project,
pageId,
}: {
project: string;
pageId: string;
}) {
return Effect.gen(function* () {
const { id } = yield* resolveProject(project);
yield* api.post(`projects/${id}/pages/${pageId}/archive/`, {});
yield* Console.log(`Archived page ${pageId}`);
});
}
export const pagesArchive = Command.make(
"archive",
{ project: projectArg, pageId: pageIdArg },
pagesArchiveHandler,
).pipe(
Command.withDescription(
"Archive a page.\n\nExample:\n plane pages archive PROJ ",
),
);
// --- pages unarchive ---
export function pagesUnarchiveHandler({
project,
pageId,
}: {
project: string;
pageId: string;
}) {
return Effect.gen(function* () {
const { id } = yield* resolveProject(project);
yield* api.delete(`projects/${id}/pages/${pageId}/archive/`);
yield* Console.log(`Unarchived page ${pageId}`);
});
}
export const pagesUnarchive = Command.make(
"unarchive",
{ project: projectArg, pageId: pageIdArg },
pagesUnarchiveHandler,
).pipe(
Command.withDescription(
"Unarchive a page.\n\nExample:\n plane pages unarchive PROJ ",
),
);
// --- pages lock ---
export function pagesLockHandler({
project,
pageId,
}: {
project: string;
pageId: string;
}) {
return Effect.gen(function* () {
const { id } = yield* resolveProject(project);
yield* api.post(`projects/${id}/pages/${pageId}/lock/`, {});
yield* Console.log(`Locked page ${pageId}`);
});
}
export const pagesLock = Command.make(
"lock",
{ project: projectArg, pageId: pageIdArg },
pagesLockHandler,
).pipe(
Command.withDescription(
"Lock a page (prevent edits).\n\nExample:\n plane pages lock PROJ ",
),
);
// --- pages unlock ---
export function pagesUnlockHandler({
project,
pageId,
}: {
project: string;
pageId: string;
}) {
return Effect.gen(function* () {
const { id } = yield* resolveProject(project);
yield* api.delete(`projects/${id}/pages/${pageId}/lock/`);
yield* Console.log(`Unlocked page ${pageId}`);
});
}
export const pagesUnlock = Command.make(
"unlock",
{ project: projectArg, pageId: pageIdArg },
pagesUnlockHandler,
).pipe(
Command.withDescription(
"Unlock a page.\n\nExample:\n plane pages unlock PROJ ",
),
);
// --- pages duplicate ---
export function pagesDuplicateHandler({
project,
pageId,
}: {
project: string;
pageId: string;
}) {
return Effect.gen(function* () {
const { id } = yield* resolveProject(project);
const raw = yield* api.post(`projects/${id}/pages/${pageId}/duplicate/`, {});
const page = yield* decodeOrFail(PageSchema, raw);
yield* Console.log(`Duplicated page ${page.id}: ${page.name}`);
});
}
export const pagesDuplicate = Command.make(
"duplicate",
{ project: projectArg, pageId: pageIdArg },
pagesDuplicateHandler,
).pipe(
Command.withDescription(
"Duplicate a page.\n\nExample:\n plane pages duplicate PROJ ",
),
);
// --- pages (parent) ---
export const pages = Command.make("pages").pipe(
Command.withDescription(
"Manage project pages (documentation). Subcommands: list, get, create, update, delete, archive, unarchive, lock, unlock, duplicate\n\nExamples:\n plane pages list PROJ\n plane pages get PROJ ",
),
Command.withSubcommands([
pagesList, pagesGet, pagesCreate, pagesUpdate, pagesDelete,
pagesArchive, pagesUnarchive, pagesLock, pagesUnlock, pagesDuplicate,
]),
);