diff --git a/README.md b/README.md index 24e9e27..9c940dc 100644 --- a/README.md +++ b/README.md @@ -32,128 +32,486 @@ arena logout Running `arena` with no arguments opens an interactive session. Pass a command for direct access. +## Quick Start + +```bash +arena login +arena whoami +arena search "brutalist architecture" --type Image +arena channel worldmaking +arena add my-channel "Hello world" +arena upload photo.jpg --channel my-channel +``` + +## Getting Help + +- `arena --help` shows a concise overview and common workflows. +- `arena help ` shows command-specific help. +- `arena --help` also shows command-specific help. + +Examples: + +```bash +arena help search +arena channel --help +arena channel contents --help +``` + +## Global Flags + +| Flag | Description | +| --------- | ----------------------------------------------- | +| `--json` | Output as JSON (`import --json` streams NDJSON) | +| `--quiet` | Compact JSON output when supported | +| `--yes` | Bypass destructive confirmation prompts | +| `--help` | Show help | + +## Command Reference + +Examples are shown first, then options. + +### Authentication + +#### `login` + +Authenticate via OAuth. + +Examples: + +```bash +arena login +``` + +Options: + +- `--token ` + +#### `whoami` + +Show current authenticated user. + +Examples: + +```bash +arena whoami +``` + +Options: + +- None + +#### `logout` + +Clear local auth token. + +Examples: + +```bash +arena logout +``` + +Options: + +- None + +### Search + +#### `search` + +Search across blocks, channels, users, and groups. + +Examples: + +```bash +arena search "brutalist architecture" +arena search "photography" --type Image +arena search "design" --scope my +arena search "*" --type Attachment --ext pdf +arena search "architecture" --sort created_at_desc +arena search "*" --sort random --seed 42 +arena search "*" --channel-id 789 +``` + +Options: + +- `--page ` +- `--per ` +- `--type ` +- `--scope ` +- `--sort ` +- `--ext ` +- `--after ` +- `--seed ` +- `--user-id ` +- `--group-id ` +- `--channel-id ` + ### Channels +#### `channel` + +View channel details. + +Examples: + ```bash -arena channel worldmaking # View a channel -arena channel contents worldmaking # Paginated contents +arena channel worldmaking +``` + +Options: + +- `--page ` +- `--per ` + +#### `channel contents` + +List channel contents. + +Examples: + +```bash +arena channel contents worldmaking arena channel contents worldmaking --sort updated_at_desc --user-id 123 +``` + +Options: + +- `--page ` +- `--per ` +- `--sort ` +- `--user-id ` + +#### `channel create` + +Create a channel. + +Examples: + +```bash arena channel create "My Research" --visibility private arena channel create "Team Notes" --group-id 123 +``` + +Options: + +- `--visibility ` +- `--description ` +- `--group-id ` + +#### `channel update` + +Update a channel. + +Examples: + +```bash arena channel update my-research --title "New Title" --description "Updated" +``` + +Options: + +- `--title ` +- `--description ` +- `--visibility ` + +#### `channel delete` + +Delete a channel. + +Examples: + +```bash arena channel delete my-research +``` + +Options: + +- None + +#### `channel connections` + +Show where a channel appears. + +Examples: + +```bash arena channel connections worldmaking --sort connected_at_desc +``` + +Options: + +- `--page ` +- `--per ` +- `--sort ` + +#### `channel followers` + +List channel followers. + +Examples: + +```bash arena channel followers worldmaking --sort connected_at_desc ``` +Options: + +- `--page ` +- `--per ` +- `--sort ` + ### Blocks +#### `block` + +View a block. + +Examples: + +```bash +arena block 12345 +``` + +Options: + +- None + +#### `block update` + +Update block metadata/content. + +Examples: + ```bash -arena block 12345 # View a block arena block update 12345 --title "New Title" +``` + +Options: + +- `--title ` +- `--description ` +- `--content ` +- `--alt-text ` + +#### `block comments` + +List block comments. + +Examples: + +```bash arena block comments 12345 --sort connected_at_desc +``` + +Options: + +- `--page ` +- `--per ` +- `--sort ` + +#### `block connections` + +Show channels connected to a block. + +Examples: + +```bash arena block connections 12345 --sort connected_at_desc --filter OWN -arena add my-channel "Hello world" # Add text -arena add my-channel https://example.com # Add a URL +``` + +Options: + +- `--page ` +- `--per ` +- `--sort ` +- `--filter ` + +#### `add` + +Add text/URL content to a channel. + +Examples: + +```bash +arena add my-channel "Hello world" arena add my-channel "Hello" --title "Greeting" --description "Pinned note" arena add my-channel https://example.com --alt-text "Cover image" --insert-at 1 arena add my-channel https://example.com --original-source-url https://source.com --original-source-title "Original" +echo "piped text" | arena add my-channel +``` + +Options: + +- `--title ` +- `--description ` +- `--alt-text ` +- `--original-source-url ` +- `--original-source-title ` +- `--insert-at ` + +#### `upload` + +Upload local file content. + +Examples: + +```bash arena upload photo.jpg --channel my-channel +arena upload photo.jpg --channel my-channel --title "Cover" --description "Homepage image" +``` + +Options: + +- `--channel ` (required) +- `--title ` +- `--description ` + +#### `batch` + +Create many blocks asynchronously. + +Examples: + +```bash arena batch my-channel "https://a.com" "https://b.com" arena batch status 1234 +``` + +Options: + +- `--title ` +- `--description ` + +#### `import` + +Import local files in bulk. + +Examples: + +```bash arena import my-channel --dir ./assets arena import my-channel --dir ./assets --recursive arena import my-channel --interactive -echo "piped text" | arena add my-channel ``` +Options: + +- `--dir ` +- `--recursive` +- `--interactive` +- `--batch-size ` +- `--upload-concurrency ` +- `--poll-interval ` + ### Connections +#### `connect` + +Connect a block/channel to a channel. + +Examples: + ```bash -arena connect 12345 my-channel # Connect block to channel +arena connect 12345 my-channel arena connect 12345 my-channel --type Channel --position 1 -arena connection 67890 # View a connection +``` + +Options: + +- `--type ` +- `--position ` + +#### `connection` + +Inspect/delete/move connections. + +Examples: + +```bash +arena connection 67890 +arena connection delete 67890 arena connection move 67890 --movement move_to_top arena connection move 67890 --movement insert_at --position 1 -arena connection delete 67890 ``` +Options: + +- `--movement ` (for `connection move`) +- `--position ` (for `connection move`) + ### Comments +#### `comment` + +Create or delete comments. + +Examples: + ```bash -arena comment 12345 "Nice find" # Add a comment +arena comment 12345 "Nice find" arena comment delete 67890 ``` -### Users +Options: + +- None + +### Users & Groups + +#### `user` + +View users and relationships. + +Examples: ```bash -arena whoami # Current user -arena user damon-zucconi # View a user +arena user damon-zucconi arena user contents damon-zucconi --type Image --sort updated_at_desc arena user followers damon-zucconi --sort connected_at_desc arena user following damon-zucconi --type User --sort connected_at_desc -arena group are-na-team # View a group -arena group contents are-na-team --type Image --sort updated_at_desc -arena group followers are-na-team --sort connected_at_desc ``` -### Search +Options: + +- `--page ` +- `--per ` +- `--type ` +- `--sort ` + +#### `group` + +View groups and group activity. + +Examples: ```bash -arena search "brutalist architecture" -arena search "photography" --type Image -arena search "design" --scope my -arena search "*" --type Attachment --ext pdf -arena search "architecture" --sort created_at_desc -arena search "*" --user-id 12345 -arena search "*" --channel-id 789 -arena search "art" --after 2024-01-01T00:00:00Z -arena search "*" --sort random --seed 42 +arena group are-na-team +arena group contents are-na-team --type Image --sort updated_at_desc +arena group followers are-na-team --sort connected_at_desc ``` +Options: + +- `--page ` +- `--per ` +- `--type ` +- `--sort ` + ### Other -```bash -arena ping # API health check -``` +#### `ping` -## Flags +Check API health. -### Global flags +Examples: -| Flag | Description | -| --------- | ----------------------------------------------- | -| `--json` | Output as JSON (`import --json` streams NDJSON) | -| `--quiet` | Compact JSON output when supported | -| `--yes` | Bypass destructive confirmation prompts | -| `--help` | Show help | +```bash +arena ping +``` + +Options: -### Common query flags - -| Flag | Description | -| -------------- | ----------------------------------------------- | -| `--page ` | Page number | -| `--per ` | Items per page | -| `--sort ` | Sort order | -| `--type ` | Type filter | -| `--filter ` | Connection filter (`ALL`, `OWN`, `EXCLUDE_OWN`) | - -### Command-specific flags - -| Command | Flags | -| ------------------ | ----------------------------------------------------------------------------------------------------------- | -| `channel create` | `--description`, `--visibility`, `--group-id` | -| `channel update` | `--title`, `--description`, `--visibility` | -| `channel contents` | `--user-id` | -| `block update` | `--title`, `--description`, `--content`, `--alt-text` | -| `add` | `--title`, `--description`, `--alt-text`, `--original-source-url`, `--original-source-title`, `--insert-at` | -| `batch` | `--title`, `--description` | -| `upload` | `--channel`, `--title`, `--description` | -| `connect` | `--type`, `--position` | -| `connection move` | `--movement`, `--position` | -| `search` | `--scope`, `--sort`, `--ext`, `--after`, `--seed`, `--user-id`, `--group-id`, `--channel-id` | -| `import` | `--dir`, `--recursive`, `--interactive`, `--batch-size`, `--upload-concurrency`, `--poll-interval` | +- None ## Aliases diff --git a/src/cli.tsx b/src/cli.tsx index 5e219a7..d43747d 100644 --- a/src/cli.tsx +++ b/src/cli.tsx @@ -5,7 +5,11 @@ import React, { useEffect } from "react"; import { render, Box, Text, useApp } from "ink"; import { SWRConfig } from "swr"; import { parseArgs, type Flags } from "./lib/args"; -import { commandMap, groupedCommands } from "./lib/registry"; +import { + commandMap, + commandHelpDocs, + type CommandHelpDoc, +} from "./lib/registry"; import { exitCodeFromError, formatJsonError } from "./lib/exit-codes"; import { CLI_PACKAGE_NAME, getCliVersion } from "./lib/version"; import { confirmDestructiveIfNeeded } from "./lib/destructive-confirmation"; @@ -13,12 +17,10 @@ import { SessionMode } from "./commands/session"; // ── Help ── -function Help() { +function TopLevelHelp() { const { exit } = useApp(); useEffect(() => exit(), [exit]); - const groups = groupedCommands(); - return ( @@ -31,75 +33,148 @@ function Help() { Usage - $ arena <command> [options] + arena <command> [flags] + arena help <command> + arena <command> --help - {groups.map(([group, cmds]) => ( - - {group} - {cmds.flatMap((cmd) => - cmd.help.map((h) => ( - - {" "} - {h.usage.padEnd(32)} - {h.description} - - )), - )} - - ))} + + Common Commands + login Authenticate your account + whoami Show current authenticated user + search Search across Are.na content + channel View/manage channels + add Add text/URLs to a channel + upload Upload local files + import Bulk import from a directory + - Global Options - --json Output as JSON (import streams NDJSON events) + Examples + arena login + arena search "brutalist architecture" --type Image + arena add my-channel "Hello world" + arena import my-channel --dir ./assets --recursive + + + + Global Flags + --json Output JSON (import streams NDJSON events) --quiet Compact JSON output when supported --yes Bypass destructive confirmation prompts --version Show CLI version --help Show help - - Common Query Flags - --page <n>, --per <n> Pagination - --sort <s> Sort order - --type <t> Type filter - - {" "} - --filter <f> Connection filter (ALL, OWN, EXCLUDE_OWN) + + Learn More + README: https://github.com/aredotna/cli#readme + Issues: https://github.com/aredotna/cli/issues + + + ); +} + +function section(title: string, lines: string[]) { + if (lines.length === 0) return null; + return ( + + {title} + {lines.map((line) => ( + {line} + ))} + + ); +} + +function renderCommandHelp(doc: CommandHelpDoc, commandPath: string) { + const usageLines = doc.usage; + const optionLines = + doc.options?.map((opt) => `${opt.flag.padEnd(34)}${opt.description}`) ?? []; + const exampleLines = doc.examples; + const noteLines = doc.notes ?? []; + const seeAlsoLines = doc.seeAlso?.map((entry) => `arena help ${entry}`) ?? []; + + return ( + + + + ** + Are.na + v{getCliVersion()} - Command-Specific Flags - channel create/update: --title --description --visibility - block update: --title --description --content --alt-text - add/batch: --title --description - upload: --channel --title --description - connect: --type --position - connection move: --movement --position - import: --dir --recursive --interactive --batch-size - import: --upload-concurrency --poll-interval - - search: --scope --ext --after --seed --user-id --group-id --channel-id - + SUMMARY + {doc.summary} + {section("USAGE", usageLines)} + {section("OPTIONS", optionLines)} + {section("EXAMPLES", exampleLines)} + {section("NOTES", noteLines)} + {section("SEE ALSO", seeAlsoLines)} + - Examples - $ arena channel worldmaking - $ arena search "brutalist architecture" --type Image - $ arena add my-channel "Hello world" - $ echo "piped text" | arena add my-channel --json - $ arena block 12345 --json - $ arena channel create "My Research" --visibility private - $ arena user damon-zucconi - $ arena upload photo.jpg --channel my-channel - $ arena import my-channel --dir ./assets --recursive + LEARN MORE + arena help {commandPath} + https://github.com/aredotna/cli#readme ); } +function lookupHelp( + commandName?: string, + subcommandName?: string, +): { + doc?: Omit; + canonicalCommand?: string; +} { + if (!commandName) return {}; + const def = commandMap.get(commandName); + if (!def) return {}; + const canonicalCommand = def.name; + const topDoc = commandHelpDocs[canonicalCommand]; + if (!topDoc) return { canonicalCommand }; + if (subcommandName && topDoc.subcommands?.[subcommandName]) { + return { + doc: topDoc.subcommands[subcommandName], + canonicalCommand, + }; + } + const { subcommands: _subcommands, ...doc } = topDoc; + return { doc, canonicalCommand }; +} + +function CommandHelp({ + commandName, + subcommandName, +}: { + commandName?: string; + subcommandName?: string; +}) { + const { exit } = useApp(); + useEffect(() => exit(), [exit]); + const { doc, canonicalCommand } = lookupHelp(commandName, subcommandName); + + if (!commandName || !doc || !canonicalCommand) { + return ( + + Unknown command help target. + Try: arena --help + + ); + } + + const commandPath = subcommandName + ? `${canonicalCommand} ${subcommandName}` + : canonicalCommand; + + return renderCommandHelp(doc, commandPath); +} + function RenderError({ message }: { message: string }) { const { exit } = useApp(); useEffect(() => { @@ -171,7 +246,7 @@ function routeCommand( const def = commandMap.get(command); if (!def) { - return ; + return ; } try { @@ -229,10 +304,30 @@ if (flags.json && command) { await handleJson(command, rest, flags); } else if (!command && (flags.version || flags.v)) { process.stdout.write(`${CLI_PACKAGE_NAME} v${getCliVersion()}\n`); -} else if (flags.help || flags.h) { - await runInk(, { fullscreen: false }); +} else if ((flags.help || flags.h) && command) { + const subcommandName = rest[0]; + await runInk( + , + { fullscreen: false }, + ); +} else if ((flags.help || flags.h) && !command) { + await runInk(, { fullscreen: false }); +} else if (command === "help") { + const targetCommand = rest[0]; + const targetSubcommand = rest[1]; + if (!targetCommand) { + await runInk(, { fullscreen: false }); + } else { + await runInk( + , + { fullscreen: false }, + ); + } } else if (!command) { - const element = process.stdin.isTTY ? : ; + const element = process.stdin.isTTY ? : ; await runInk({element}, { fullscreen: Boolean(process.stdin.isTTY && process.stdout.isTTY), }); diff --git a/src/lib/registry.tsx b/src/lib/registry.tsx index a4b1282..2dfac1d 100644 --- a/src/lib/registry.tsx +++ b/src/lib/registry.tsx @@ -86,6 +86,21 @@ interface HelpLine { description: string; } +export interface HelpOption { + flag: string; + description: string; +} + +export interface CommandHelpDoc { + summary: string; + usage: string[]; + options?: HelpOption[]; + examples: string[]; + notes?: string[]; + seeAlso?: string[]; + subcommands?: Record>; +} + export interface CommandDefinition { name: string; aliases?: string[]; @@ -114,32 +129,59 @@ export const commands: CommandDefinition[] = [ aliases: ["ch"], group: "Channels", help: [ - { usage: "channel ", description: "View a channel" }, { - usage: "channel contents ", - description: "Channel contents (paginated)", + usage: "channel [--page ] [--per ]", + description: "Options", + }, + { usage: "channel worldmaking", description: "Example" }, + { + usage: + "channel contents [--page ] [--per ] [--sort ] [--user-id ]", + description: "Options", }, { - usage: "channel contents --sort updated_at_desc --user-id 123", - description: "Sort/filter channel contents", + usage: + "channel contents worldmaking --sort updated_at_desc --user-id 123", + description: "Example", }, { - usage: "channel create --visibility private --group-id 123", - description: "Create a channel (optionally in a group)", + usage: + "channel create <title> [--visibility <public|private|closed>] [--description <text>] [--group-id <id>]", + description: "Options", }, { usage: - 'channel update <slug> --title "New title" --description "Updated"', - description: "Update channel metadata", + 'channel create "My Research" --visibility private --group-id 123', + description: "Example", }, - { usage: "channel delete <slug>", description: "Delete a channel" }, { - usage: "channel connections <slug> --sort connected_at_desc", - description: "Where channel appears (sortable)", + usage: + "channel update <slug> [--title <text>] [--description <text>] [--visibility <public|private|closed>]", + description: "Options", }, { - usage: "channel followers <slug> --sort connected_at_desc", - description: "Channel followers (sortable)", + usage: + 'channel update my-research --title "New title" --description "Updated"', + description: "Example", + }, + { usage: "channel delete <slug>", description: "Options" }, + { usage: "channel delete my-research", description: "Example" }, + { + usage: + "channel connections <slug> [--page <n>] [--per <n>] [--sort <s>]", + description: "Options", + }, + { + usage: "channel connections worldmaking --sort connected_at_desc", + description: "Example", + }, + { + usage: "channel followers <slug> [--page <n>] [--per <n>] [--sort <s>]", + description: "Options", + }, + { + usage: "channel followers worldmaking --sort connected_at_desc", + description: "Example", }, ], destructive: { @@ -300,18 +342,33 @@ export const commands: CommandDefinition[] = [ aliases: ["bl"], group: "Blocks", help: [ - { usage: "block <id>", description: "View a block" }, + { usage: "block <id>", description: "Options" }, + { usage: "block 12345", description: "Example" }, { - usage: 'block update <id> --title "New" --description "Updated"', - description: "Update block metadata/content", + usage: + "block update <id> [--title <text>] [--description <text>] [--content <text>] [--alt-text <text>]", + description: "Options", }, { - usage: "block comments <id> --sort connected_at_desc", - description: "View block comments (sortable)", + usage: 'block update 12345 --title "New" --description "Updated"', + description: "Example", }, { - usage: "block connections <id> --sort connected_at_desc --filter OWN", - description: "Where block appears (sortable/filterable)", + usage: "block comments <id> [--page <n>] [--per <n>] [--sort <s>]", + description: "Options", + }, + { + usage: "block comments 12345 --sort connected_at_desc", + description: "Example", + }, + { + usage: + "block connections <id> [--page <n>] [--per <n>] [--sort <s>] [--filter <ALL|OWN|EXCLUDE_OWN>]", + description: "Options", + }, + { + usage: "block connections 12345 --sort connected_at_desc --filter OWN", + description: "Example", }, ], session: { args: "<id>", desc: "View a block" }, @@ -408,37 +465,42 @@ export const commands: CommandDefinition[] = [ aliases: ["s"], group: "Other", help: [ - { usage: "search <query>", description: "Search Are.na" }, + { + usage: + "search <query> [--page <n>] [--per <n>] [--type <t>] [--scope <all|my|following>] [--sort <s>] [--ext <ext>] [--after <iso8601>] [--seed <n>] [--user-id <id>] [--group-id <id>] [--channel-id <id>]", + description: "Options", + }, + { + usage: 'search "brutalist architecture"', + description: "Example", + }, { usage: "search <query> --type Image", - description: - "Filter by type (Text, Image, Link, Attachment, Embed, Channel, Block, User, Group)", + description: "Example", }, { usage: "search <query> --scope my", - description: "Limit scope (all, my, following)", + description: "Example", }, { usage: "search <query> --sort created_at_desc", - description: - "Sort order (score_desc, created_at_desc, created_at_asc, updated_at_desc, updated_at_asc, name_asc, name_desc, connections_count_desc, random)", + description: "Example", }, { usage: "search <query> --ext pdf", - description: "Filter by file extension", + description: "Example", }, { usage: "search <query> --after 2024-01-01T00:00:00Z", - description: "Only results updated after timestamp (ISO 8601)", + description: "Example", }, { usage: "search <query> --channel-id 789", - description: - "Limit to a channel (--user-id, --group-id also available)", + description: "Example", }, { usage: "search <query> --sort random --seed 42", - description: "Reproducible random ordering", + description: "Example", }, ], session: { args: "<query>", desc: "Search Are.na" }, @@ -493,25 +555,26 @@ export const commands: CommandDefinition[] = [ group: "Blocks", help: [ { - usage: "add <channel> <value>", - description: "Add content to a channel", + usage: + "add <channel> <value> [--title <text>] [--description <text>] [--alt-text <text>] [--original-source-url <url>] [--original-source-title <text>] [--insert-at <n>]", + description: "Options", }, { usage: 'add <channel> <value> --title "Title" --description "Notes"', - description: "Add with title/description", + description: "Example", }, { usage: 'add <channel> <value> --alt-text "Accessible text"', - description: "Add image alt text", + description: "Example", }, { usage: 'add <channel> <value> --original-source-url <url> --original-source-title "Source"', - description: "Attach original source metadata", + description: "Example", }, { usage: "add <channel> <value> --insert-at 1", - description: "Insert at a specific position", + description: "Example", }, ], render(args, flags) { @@ -564,13 +627,14 @@ export const commands: CommandDefinition[] = [ group: "Blocks", help: [ { - usage: "upload <file> --channel <ch>", - description: "Upload a file", + usage: + "upload <file> --channel <ch> [--title <text>] [--description <text>]", + description: "Options", }, { usage: 'upload <file> --channel <ch> --title "Title" --description "Notes"', - description: "Upload with metadata", + description: "Example", }, ], render(args, flags) { @@ -609,12 +673,21 @@ export const commands: CommandDefinition[] = [ group: "Blocks", help: [ { - usage: "batch <channel> [values...]", - description: "Batch create blocks (async)", + usage: + "batch <channel> [values...] [--title <text>] [--description <text>]", + description: "Options", + }, + { + usage: 'batch my-channel "https://a.com" "https://b.com"', + description: "Example", }, { usage: "batch status <batch_id>", - description: "Check batch status", + description: "Options", + }, + { + usage: "batch status 1234", + description: "Example", }, ], render(_args) { @@ -662,12 +735,17 @@ export const commands: CommandDefinition[] = [ group: "Blocks", help: [ { - usage: "import <channel>", - description: "Import files from a directory (defaults to .)", + usage: + "import <channel> [--dir <path>] [--recursive] [--interactive] [--batch-size <n>] [--upload-concurrency <n>] [--poll-interval <ms>]", + description: "Options", + }, + { + usage: "import my-channel --dir ./assets --recursive", + description: "Example", }, { - usage: "import <channel> --interactive", - description: "Open interactive file picker before importing", + usage: "import my-channel --interactive", + description: "Example", }, ], render(args, flags) { @@ -686,12 +764,13 @@ export const commands: CommandDefinition[] = [ group: "Connections", help: [ { - usage: "connect <id> <channel>", - description: "Connect block to channel", + usage: + "connect <id> <channel> [--type <Block|Channel>] [--position <n>]", + description: "Options", }, { usage: "connect <id> <channel> --type Channel --position 1", - description: "Set connectable type and insertion position", + description: "Example", }, ], render(args, flags) { @@ -723,15 +802,18 @@ export const commands: CommandDefinition[] = [ aliases: [], group: "Connections", help: [ - { usage: "connection <id>", description: "View a connection" }, - { usage: "connection delete <id>", description: "Remove a connection" }, + { usage: "connection <id>", description: "Options" }, + { usage: "connection 67890", description: "Example" }, + { usage: "connection delete <id>", description: "Options" }, + { usage: "connection delete 67890", description: "Example" }, { - usage: "connection move <id> --movement move_to_top", - description: "Reposition a connection", + usage: + "connection move <id> [--movement <move_to_top|move_to_bottom|insert_at>] [--position <n>]", + description: "Options", }, { usage: "connection move <id> --movement insert_at --position 1", - description: "Move connection to explicit position", + description: "Example", }, ], destructive: { @@ -793,9 +875,11 @@ export const commands: CommandDefinition[] = [ help: [ { usage: "comment <blockId> <text>", - description: "Add a comment", + description: "Options", }, - { usage: "comment delete <id>", description: "Delete a comment" }, + { usage: 'comment 12345 "Nice find"', description: "Example" }, + { usage: "comment delete <id>", description: "Options" }, + { usage: "comment delete 67890", description: "Example" }, ], destructive: { subcommands: { @@ -838,18 +922,33 @@ export const commands: CommandDefinition[] = [ aliases: [], group: "Users & Groups", help: [ - { usage: "user <slug>", description: "View a user" }, + { usage: "user <slug>", description: "Options" }, + { usage: "user damon-zucconi", description: "Example" }, + { + usage: + "user contents <slug> [--page <n>] [--per <n>] [--type <t>] [--sort <s>]", + description: "Options", + }, { usage: "user contents <slug> --type Image --sort updated_at_desc", - description: "User's content (filter/sort)", + description: "Example", + }, + { + usage: "user followers <slug> [--page <n>] [--per <n>] [--sort <s>]", + description: "Options", }, { usage: "user followers <slug> --sort connected_at_desc", - description: "User's followers (sortable)", + description: "Example", + }, + { + usage: + "user following <slug> [--page <n>] [--per <n>] [--type <t>] [--sort <s>]", + description: "Options", }, { usage: "user following <slug> --type User --sort connected_at_desc", - description: "Who user follows (filter/sort)", + description: "Example", }, ], session: { args: "<slug>", desc: "View a user profile" }, @@ -948,14 +1047,24 @@ export const commands: CommandDefinition[] = [ aliases: [], group: "Users & Groups", help: [ - { usage: "group <slug>", description: "View a group" }, + { usage: "group <slug>", description: "Options" }, + { usage: "group are-na-team", description: "Example" }, + { + usage: + "group contents <slug> [--page <n>] [--per <n>] [--type <t>] [--sort <s>]", + description: "Options", + }, { usage: "group contents <slug> --type Image --sort updated_at_desc", - description: "Group's content (filter/sort)", + description: "Example", + }, + { + usage: "group followers <slug> [--page <n>] [--per <n>] [--sort <s>]", + description: "Options", }, { usage: "group followers <slug> --sort connected_at_desc", - description: "Group's followers (sortable)", + description: "Example", }, ], session: { args: "<slug>", desc: "View a group profile" }, @@ -1029,7 +1138,10 @@ export const commands: CommandDefinition[] = [ name: "version", aliases: ["v"], group: "Other", - help: [{ usage: "version", description: "Show CLI version" }], + help: [ + { usage: "version", description: "Options" }, + { usage: "version", description: "Example" }, + ], render() { return <VersionCommand />; }, @@ -1043,8 +1155,9 @@ export const commands: CommandDefinition[] = [ aliases: ["upgrade"], group: "Other", help: [ - { usage: "update", description: "Check for a newer CLI version" }, - { usage: "update --yes", description: "Install latest CLI globally" }, + { usage: "update [--yes]", description: "Options" }, + { usage: "update", description: "Example" }, + { usage: "update --yes", description: "Example" }, ], render(_args, flags) { const apply = flags["yes"] !== undefined || flags["y"] !== undefined; @@ -1063,7 +1176,10 @@ export const commands: CommandDefinition[] = [ name: "whoami", aliases: ["me"], group: "Other", - help: [{ usage: "whoami", description: "Show current user" }], + help: [ + { usage: "whoami", description: "Options" }, + { usage: "whoami", description: "Example" }, + ], session: { args: null, desc: "View your profile" }, render() { return <WhoamiCommand />; @@ -1078,7 +1194,10 @@ export const commands: CommandDefinition[] = [ name: "login", aliases: [], group: "Other", - help: [{ usage: "login", description: "Authenticate via OAuth" }], + help: [ + { usage: "login [--token <token>]", description: "Options" }, + { usage: "login", description: "Example" }, + ], render(args, flags) { return <LoginCommand token={flag(flags, "token") || args[0]} />; }, @@ -1088,7 +1207,10 @@ export const commands: CommandDefinition[] = [ name: "logout", aliases: [], group: "Other", - help: [{ usage: "logout", description: "Log out of your account" }], + help: [ + { usage: "logout", description: "Options" }, + { usage: "logout", description: "Example" }, + ], session: { args: null, desc: "Log out of your account" }, render() { return <LogoutCommand />; @@ -1104,7 +1226,10 @@ export const commands: CommandDefinition[] = [ name: "ping", aliases: [], group: "Other", - help: [{ usage: "ping", description: "API health check" }], + help: [ + { usage: "ping", description: "Options" }, + { usage: "ping", description: "Example" }, + ], render() { return <PingCommand />; }, @@ -1147,3 +1272,448 @@ export function groupedCommands(): [string, CommandDefinition[]][] { groups.get(g)!, ]); } + +export const commandHelpDocs: Record<string, CommandHelpDoc> = { + login: { + summary: "Authenticate your Are.na account via OAuth.", + usage: ["arena login", "arena login --token <token>"], + options: [ + { + flag: "--token <token>", + description: "Use an existing token directly", + }, + ], + examples: ["arena login"], + seeAlso: ["whoami", "logout"], + }, + whoami: { + summary: "Show your authenticated user profile.", + usage: ["arena whoami"], + examples: ["arena whoami", "arena whoami --json"], + seeAlso: ["login", "logout"], + }, + logout: { + summary: "Clear the locally stored access token.", + usage: ["arena logout"], + examples: ["arena logout"], + seeAlso: ["login"], + }, + search: { + summary: "Search across Are.na blocks, channels, users, and groups.", + usage: ["arena search <query> [flags]"], + options: [ + { flag: "--page <n>", description: "Page number (default: 1)" }, + { flag: "--per <n>", description: "Results per page (default: 24)" }, + { + flag: "--type <t>", + description: + "Type filter (Text, Image, Link, Attachment, Embed, Channel, Block, User, Group)", + }, + { flag: "--scope <all|my|following>", description: "Search scope" }, + { + flag: "--sort <s>", + description: + "Sort order (score_desc, created_at_desc, created_at_asc, updated_at_desc, updated_at_asc, name_asc, name_desc, connections_count_desc, random)", + }, + { + flag: "--ext <ext>", + description: "File extension filter (pdf, jpg, png, ...)", + }, + { + flag: "--after <iso8601>", + description: "Only results updated after timestamp", + }, + { + flag: "--seed <n>", + description: "Random seed (use with --sort random)", + }, + { + flag: "--user-id <id>", + description: "Limit to a specific user's content", + }, + { + flag: "--group-id <id>", + description: "Limit to a specific group's content", + }, + { + flag: "--channel-id <id>", + description: "Limit to a specific channel's content", + }, + ], + examples: [ + 'arena search "brutalist architecture"', + 'arena search "photography" --type Image', + 'arena search "*" --sort random --seed 42', + 'arena search "*" --channel-id 789 --ext pdf', + ], + seeAlso: ["channel", "user", "group"], + }, + channel: { + summary: "View and manage channels.", + usage: ["arena channel <slug>", "arena channel <subcommand> ..."], + examples: [ + "arena channel worldmaking", + "arena channel contents worldmaking --sort updated_at_desc", + 'arena channel create "My Research" --visibility private', + ], + subcommands: { + contents: { + summary: "List channel contents with pagination and filtering.", + usage: ["arena channel contents <slug> [flags]"], + options: [ + { flag: "--page <n>", description: "Page number" }, + { flag: "--per <n>", description: "Items per page" }, + { flag: "--sort <s>", description: "Sort order" }, + { + flag: "--user-id <id>", + description: "Limit to a specific user within the channel", + }, + ], + examples: [ + "arena channel contents worldmaking --sort updated_at_desc --user-id 123", + ], + }, + create: { + summary: "Create a channel.", + usage: ["arena channel create <title> [flags]"], + options: [ + { + flag: "--visibility <public|private|closed>", + description: "Channel visibility", + }, + { flag: "--description <text>", description: "Optional description" }, + { + flag: "--group-id <id>", + description: "Create channel under a group", + }, + ], + examples: ['arena channel create "Team Notes" --group-id 123'], + }, + update: { + summary: "Update a channel.", + usage: ["arena channel update <slug> [flags]"], + options: [ + { flag: "--title <text>", description: "New title" }, + { flag: "--description <text>", description: "New description" }, + { + flag: "--visibility <public|private|closed>", + description: "New visibility", + }, + ], + examples: ['arena channel update my-research --title "New Title"'], + }, + delete: { + summary: "Delete a channel.", + usage: ["arena channel delete <slug>"], + examples: ["arena channel delete my-research"], + }, + connections: { + summary: "Show where a channel appears.", + usage: ["arena channel connections <slug> [flags]"], + options: [ + { flag: "--page <n>", description: "Page number" }, + { flag: "--per <n>", description: "Items per page" }, + { flag: "--sort <s>", description: "Sort order" }, + ], + examples: [ + "arena channel connections worldmaking --sort connected_at_desc", + ], + }, + followers: { + summary: "List channel followers.", + usage: ["arena channel followers <slug> [flags]"], + options: [ + { flag: "--page <n>", description: "Page number" }, + { flag: "--per <n>", description: "Items per page" }, + { flag: "--sort <s>", description: "Sort order" }, + ], + examples: [ + "arena channel followers worldmaking --sort connected_at_desc", + ], + }, + }, + seeAlso: ["search", "add", "connect"], + }, + block: { + summary: "View and manage blocks.", + usage: ["arena block <id>", "arena block <subcommand> ..."], + examples: [ + "arena block 12345", + "arena block comments 12345 --sort connected_at_desc", + ], + subcommands: { + update: { + summary: "Update a block's metadata or content.", + usage: ["arena block update <id> [flags]"], + options: [ + { flag: "--title <text>", description: "New title" }, + { flag: "--description <text>", description: "New description" }, + { flag: "--content <text>", description: "Text content" }, + { flag: "--alt-text <text>", description: "Image alt text" }, + ], + examples: [ + 'arena block update 12345 --title "New Title" --description "Updated"', + ], + }, + comments: { + summary: "List comments on a block.", + usage: ["arena block comments <id> [flags]"], + options: [ + { flag: "--page <n>", description: "Page number" }, + { flag: "--per <n>", description: "Items per page" }, + { flag: "--sort <s>", description: "Sort order" }, + ], + examples: ["arena block comments 12345 --sort connected_at_desc"], + }, + connections: { + summary: "Show channels connected to a block.", + usage: ["arena block connections <id> [flags]"], + options: [ + { flag: "--page <n>", description: "Page number" }, + { flag: "--per <n>", description: "Items per page" }, + { flag: "--sort <s>", description: "Sort order" }, + { + flag: "--filter <ALL|OWN|EXCLUDE_OWN>", + description: "Connection filter", + }, + ], + examples: [ + "arena block connections 12345 --sort connected_at_desc --filter OWN", + ], + }, + }, + seeAlso: ["add", "connect", "comment"], + }, + add: { + summary: "Add text or URL content to a channel.", + usage: ["arena add <channel> <value> [flags]"], + options: [ + { flag: "--title <text>", description: "Optional block title" }, + { + flag: "--description <text>", + description: "Optional block description", + }, + { flag: "--alt-text <text>", description: "Image alt text" }, + { + flag: "--original-source-url <url>", + description: "Original source URL", + }, + { + flag: "--original-source-title <text>", + description: "Original source title", + }, + { + flag: "--insert-at <n>", + description: "Insert position within the channel", + }, + ], + examples: [ + 'arena add my-channel "Hello world"', + 'arena add my-channel https://example.com --alt-text "Cover image" --insert-at 1', + 'echo "piped text" | arena add my-channel', + ], + seeAlso: ["upload", "batch", "channel"], + }, + upload: { + summary: "Upload a local file and add it as a block.", + usage: ["arena upload <file> --channel <slug|id> [flags]"], + options: [ + { flag: "--channel <slug|id>", description: "Target channel (required)" }, + { flag: "--title <text>", description: "Optional block title" }, + { + flag: "--description <text>", + description: "Optional block description", + }, + ], + examples: ["arena upload photo.jpg --channel my-channel"], + seeAlso: ["add", "batch", "import"], + }, + batch: { + summary: "Create many blocks asynchronously.", + usage: [ + "arena batch <channel> [values...] [flags]", + "arena batch status <batch_id>", + ], + options: [ + { flag: "--title <text>", description: "Default title for each block" }, + { + flag: "--description <text>", + description: "Default description for each block", + }, + ], + examples: [ + 'arena batch my-channel "https://a.com" "https://b.com"', + "arena batch status 1234", + ], + seeAlso: ["add", "import"], + }, + import: { + summary: "Bulk import local files into a channel.", + usage: ["arena import <channel> [flags]"], + options: [ + { flag: "--dir <path>", description: "Directory to scan (default: .)" }, + { flag: "--recursive", description: "Scan directories recursively" }, + { + flag: "--interactive", + description: "Pick files interactively before import", + }, + { + flag: "--batch-size <n>", + description: "Batch size for async create calls", + }, + { + flag: "--upload-concurrency <n>", + description: "Concurrent file uploads", + }, + { + flag: "--poll-interval <ms>", + description: "Batch status polling interval", + }, + ], + examples: [ + "arena import my-channel --dir ./assets --recursive", + "arena import my-channel --interactive", + ], + seeAlso: ["upload", "batch"], + }, + connect: { + summary: "Connect a block or channel to a channel.", + usage: ["arena connect <id> <channel> [flags]"], + options: [ + { flag: "--type <Block|Channel>", description: "Connectable type" }, + { flag: "--position <n>", description: "Insertion position" }, + ], + examples: ["arena connect 12345 my-channel --type Channel --position 1"], + seeAlso: ["connection", "block", "channel"], + }, + connection: { + summary: "Inspect, move, or delete a connection.", + usage: [ + "arena connection <id>", + "arena connection delete <id>", + "arena connection move <id> [flags]", + ], + options: [ + { + flag: "--movement <move_to_top|move_to_bottom|insert_at>", + description: "Move strategy (for move subcommand)", + }, + { + flag: "--position <n>", + description: "Target position (for move subcommand)", + }, + ], + examples: [ + "arena connection 67890", + "arena connection move 67890 --movement insert_at --position 1", + ], + seeAlso: ["connect"], + }, + comment: { + summary: "Create or delete comments.", + usage: ["arena comment <blockId> <text>", "arena comment delete <id>"], + examples: ['arena comment 12345 "Nice find"', "arena comment delete 67890"], + seeAlso: ["block"], + }, + user: { + summary: "View users and user relationships.", + usage: ["arena user <slug>", "arena user <subcommand> ..."], + examples: [ + "arena user damon-zucconi", + "arena user contents damon-zucconi --type Image --sort updated_at_desc", + ], + subcommands: { + contents: { + summary: "List a user's published content.", + usage: ["arena user contents <slug> [flags]"], + options: [ + { flag: "--page <n>", description: "Page number" }, + { flag: "--per <n>", description: "Items per page" }, + { flag: "--type <t>", description: "Content type filter" }, + { flag: "--sort <s>", description: "Sort order" }, + ], + examples: [ + "arena user contents damon-zucconi --type Image --sort updated_at_desc", + ], + }, + followers: { + summary: "List a user's followers.", + usage: ["arena user followers <slug> [flags]"], + options: [ + { flag: "--page <n>", description: "Page number" }, + { flag: "--per <n>", description: "Items per page" }, + { flag: "--sort <s>", description: "Sort order" }, + ], + examples: [ + "arena user followers damon-zucconi --sort connected_at_desc", + ], + }, + following: { + summary: "List who a user follows.", + usage: ["arena user following <slug> [flags]"], + options: [ + { flag: "--page <n>", description: "Page number" }, + { flag: "--per <n>", description: "Items per page" }, + { flag: "--type <t>", description: "Filter followable type" }, + { flag: "--sort <s>", description: "Sort order" }, + ], + examples: [ + "arena user following damon-zucconi --type User --sort connected_at_desc", + ], + }, + }, + seeAlso: ["group", "search"], + }, + group: { + summary: "View groups and group activity.", + usage: ["arena group <slug>", "arena group <subcommand> ..."], + examples: [ + "arena group are-na-team", + "arena group contents are-na-team --type Image --sort updated_at_desc", + ], + subcommands: { + contents: { + summary: "List group content.", + usage: ["arena group contents <slug> [flags]"], + options: [ + { flag: "--page <n>", description: "Page number" }, + { flag: "--per <n>", description: "Items per page" }, + { flag: "--type <t>", description: "Content type filter" }, + { flag: "--sort <s>", description: "Sort order" }, + ], + examples: [ + "arena group contents are-na-team --type Image --sort updated_at_desc", + ], + }, + followers: { + summary: "List group followers.", + usage: ["arena group followers <slug> [flags]"], + options: [ + { flag: "--page <n>", description: "Page number" }, + { flag: "--per <n>", description: "Items per page" }, + { flag: "--sort <s>", description: "Sort order" }, + ], + examples: [ + "arena group followers are-na-team --sort connected_at_desc", + ], + }, + }, + seeAlso: ["user", "search"], + }, + ping: { + summary: "Check API health.", + usage: ["arena ping"], + examples: ["arena ping", "arena ping --json"], + }, + version: { + summary: "Show CLI version.", + usage: ["arena version"], + examples: ["arena version"], + }, + update: { + summary: "Check for and install CLI updates.", + usage: ["arena update [--yes]"], + options: [{ flag: "--yes", description: "Install update without prompt" }], + examples: ["arena update", "arena update --yes"], + }, +};