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
4 changes: 2 additions & 2 deletions cli/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "skyboard-cli",
"version": "0.2.1",
"version": "0.3.0",
"description": "CLI for Skyboard – a collaborative kanban board on AT Protocol",
"author": {
"name": "Tim Disney"
Expand Down
18 changes: 1 addition & 17 deletions cli/src/commands/cards.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { requireAgent } from "../lib/auth.js";
import { fetchBoardData } from "../lib/pds.js";
import { getDefaultBoard } from "../lib/config.js";
import { resolveBoard } from "../lib/board-resolver.js";
import { printBoardCards, shortRkey, formatDate } from "../lib/display.js";
import chalk from "chalk";

Expand Down Expand Up @@ -49,19 +49,3 @@ export async function cardsCommand(opts: {
search: opts.search,
});
}

function resolveBoard(boardOpt?: string): { did: string; rkey: string } {
if (boardOpt) {
const defaultBoard = getDefaultBoard();
if (defaultBoard) return defaultBoard;
}

const defaultBoard = getDefaultBoard();
if (!defaultBoard) {
console.error(
chalk.red("No default board set. Run `sb use <board>` first."),
);
process.exit(1);
}
return defaultBoard;
}
19 changes: 1 addition & 18 deletions cli/src/commands/cols.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { requireAgent } from "../lib/auth.js";
import { fetchBoardData } from "../lib/pds.js";
import { getDefaultBoard } from "../lib/config.js";
import { resolveBoard } from "../lib/board-resolver.js";
import chalk from "chalk";

export async function colsCommand(opts: {
Expand Down Expand Up @@ -50,20 +50,3 @@ export async function colsCommand(opts: {
);
}
}

function resolveBoard(boardOpt?: string): { did: string; rkey: string } {
if (boardOpt) {
// TODO: support full board ref parsing like use command
const defaultBoard = getDefaultBoard();
if (defaultBoard) return defaultBoard;
}

const defaultBoard = getDefaultBoard();
if (!defaultBoard) {
console.error(
chalk.red("No default board set. Run `sb use <board>` first."),
);
process.exit(1);
}
return defaultBoard;
}
13 changes: 1 addition & 12 deletions cli/src/commands/comment.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { requireAgent } from "../lib/auth.js";
import { fetchBoardData } from "../lib/pds.js";
import { getDefaultBoard } from "../lib/config.js";
import { resolveBoard } from "../lib/board-resolver.js";
import { resolveCardRef } from "../lib/card-ref.js";
import {
generateTID,
Expand Down Expand Up @@ -65,14 +65,3 @@ export async function commentCommand(
);
}
}

function resolveBoard(boardOpt?: string): { did: string; rkey: string } {
const defaultBoard = getDefaultBoard();
if (!defaultBoard) {
console.error(
chalk.red("No default board set. Run `sb use <board>` first."),
);
process.exit(1);
}
return defaultBoard;
}
13 changes: 1 addition & 12 deletions cli/src/commands/edit.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { requireAgent } from "../lib/auth.js";
import { fetchBoardData } from "../lib/pds.js";
import { getDefaultBoard } from "../lib/config.js";
import { resolveBoard } from "../lib/board-resolver.js";
import { resolveCardRef } from "../lib/card-ref.js";
import {
generateTID,
Expand Down Expand Up @@ -96,14 +96,3 @@ export async function editCommand(
);
}
}

function resolveBoard(boardOpt?: string): { did: string; rkey: string } {
const defaultBoard = getDefaultBoard();
if (!defaultBoard) {
console.error(
chalk.red("No default board set. Run `sb use <board>` first."),
);
process.exit(1);
}
return defaultBoard;
}
13 changes: 1 addition & 12 deletions cli/src/commands/mv.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { requireAgent } from "../lib/auth.js";
import { fetchBoardData } from "../lib/pds.js";
import { getDefaultBoard } from "../lib/config.js";
import { resolveBoard } from "../lib/board-resolver.js";
import { resolveCardRef } from "../lib/card-ref.js";
import { resolveColumn } from "../lib/column-match.js";
import {
Expand Down Expand Up @@ -86,14 +86,3 @@ export async function mvCommand(
);
}
}

function resolveBoard(boardOpt?: string): { did: string; rkey: string } {
const defaultBoard = getDefaultBoard();
if (!defaultBoard) {
console.error(
chalk.red("No default board set. Run `sb use <board>` first."),
);
process.exit(1);
}
return defaultBoard;
}
13 changes: 1 addition & 12 deletions cli/src/commands/new.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { requireAgent } from "../lib/auth.js";
import { fetchBoardData } from "../lib/pds.js";
import { getDefaultBoard } from "../lib/config.js";
import { resolveBoard } from "../lib/board-resolver.js";
import { resolveColumn } from "../lib/column-match.js";
import {
generateTID,
Expand Down Expand Up @@ -90,14 +90,3 @@ export async function newCommand(
);
}
}

function resolveBoard(boardOpt?: string): { did: string; rkey: string } {
const defaultBoard = getDefaultBoard();
if (!defaultBoard) {
console.error(
chalk.red("No default board set. Run `sb use <board>` first."),
);
process.exit(1);
}
return defaultBoard;
}
13 changes: 1 addition & 12 deletions cli/src/commands/rm.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { requireAgent } from "../lib/auth.js";
import { fetchBoardData } from "../lib/pds.js";
import { getDefaultBoard } from "../lib/config.js";
import { resolveBoard } from "../lib/board-resolver.js";
import { resolveCardRef } from "../lib/card-ref.js";
import { shortRkey } from "../lib/display.js";
import { TASK_COLLECTION } from "../lib/tid.js";
Expand Down Expand Up @@ -70,14 +70,3 @@ function confirm(prompt: string): Promise<boolean> {
});
});
}

function resolveBoard(boardOpt?: string): { did: string; rkey: string } {
const defaultBoard = getDefaultBoard();
if (!defaultBoard) {
console.error(
chalk.red("No default board set. Run `sb use <board>` first."),
);
process.exit(1);
}
return defaultBoard;
}
13 changes: 1 addition & 12 deletions cli/src/commands/show.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { requireAgent } from "../lib/auth.js";
import { fetchBoardData } from "../lib/pds.js";
import { getDefaultBoard } from "../lib/config.js";
import { resolveBoard } from "../lib/board-resolver.js";
import { resolveCardRef } from "../lib/card-ref.js";
import { shortRkey, formatDate } from "../lib/display.js";
import { buildAtUri, TASK_COLLECTION } from "../lib/tid.js";
Expand Down Expand Up @@ -110,14 +110,3 @@ export async function showCommand(
}
console.log();
}

function resolveBoard(boardOpt?: string): { did: string; rkey: string } {
const defaultBoard = getDefaultBoard();
if (!defaultBoard) {
console.error(
chalk.red("No default board set. Run `sb use <board>` first."),
);
process.exit(1);
}
return defaultBoard;
}
71 changes: 71 additions & 0 deletions cli/src/lib/board-resolver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { getDefaultBoard, loadConfig } from "./config.js";
import { BOARD_COLLECTION } from "./tid.js";
import chalk from "chalk";

/**
* Shared board resolver for all CLI commands.
*
* Parses --board option in these formats:
* - AT URI: at://did:plc:xxx/dev.skyboard.board/rkey
* - DID:rkey pair: did:plc:xxx:rkey (last colon separates DID from rkey)
* - Falls back to getDefaultBoard() when boardOpt is not provided.
*/
export function resolveBoard(boardOpt?: string): { did: string; rkey: string } {
if (boardOpt) {
const parsed = parseBoardOpt(boardOpt);
if (parsed) return parsed;

console.error(chalk.red(`Could not parse board reference: ${boardOpt}`));
console.error(
"Accepted formats: at://did/dev.skyboard.board/rkey or did:plc:xxx:rkey",
);
process.exit(1);
}

const defaultBoard = getDefaultBoard();
if (!defaultBoard) {
console.error(
chalk.red(
"No default board set. Run `sb use <board>` first, or pass --board.",
),
);
process.exit(1);
}
return defaultBoard;
}

function parseBoardOpt(ref: string): { did: string; rkey: string } | null {
// AT URI: at://did:plc:xxx/dev.skyboard.board/rkey
if (ref.startsWith("at://")) {
const parts = ref.replace("at://", "").split("/");
if (parts.length >= 3 && parts[1] === BOARD_COLLECTION) {
return { did: parts[0], rkey: parts[2] };
}
return null;
}

// DID:rkey pair β€” the DID itself contains colons (e.g. did:plc:abc123)
// so we split on the last colon to get did + rkey
if (ref.startsWith("did:")) {
const lastColon = ref.lastIndexOf(":");
// A bare DID like "did:plc:abc" has its last colon at the plc:abc boundary,
// so we need at least 3 colon-separated parts (did:method:id:rkey)
const parts = ref.split(":");
if (parts.length >= 4) {
const rkey = parts[parts.length - 1];
const did = parts.slice(0, -1).join(":");
return { did, rkey };
}
return null;
}

// Check known boards by name
const config = loadConfig();
const lowerRef = ref.toLowerCase();
const nameMatch = config.knownBoards.find((b) =>
b.name.toLowerCase().includes(lowerRef),
);
if (nameMatch) return { did: nameMatch.did, rkey: nameMatch.rkey };

return null;
}
14 changes: 7 additions & 7 deletions cli/src/lib/ralph/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ This project uses a Skyboard kanban board to drive an iterative development loop

- Board DID: \`{{boardDid}}\`
- Board rkey: \`{{boardRkey}}\`
- Set with: \`sb use {{boardRkey}}\`
- Board flag: \`--board {{boardDid}}:{{boardRkey}}\` (pass this on every \`sb\` command)

## Columns (workflow stages)

Expand All @@ -161,17 +161,17 @@ Each invocation of the loop, follow this protocol:

### 1. ASSESS

Run \`sb cards --json\` to see the full board state. Identify which cards are in which columns and which have the \`blocked\` label.
Run \`sb cards --json --board {{boardDid}}:{{boardRkey}}\` to see the full board state. Identify which cards are in which columns and which have the \`blocked\` label.

### 2. PICK

Select exactly one card to work on. You will only work on this single card during this invocation. Use this priority order:

${buildPickPriority(columns)}

**Skip cards with the \`blocked\` label.** These are waiting on human input. However, if a previously-blocked card has a new comment from the human since it was blocked, the human has likely answered β€” remove the \`blocked\` label (\`sb edit <ref> -l ""\`) and work on it.
**Skip cards with the \`blocked\` label.** These are waiting on human input. However, if a previously-blocked card has a new comment from the human since it was blocked, the human has likely answered β€” remove the \`blocked\` label (\`sb edit <ref> -l "" --board {{boardDid}}:{{boardRkey}}\`) and work on it.

Within a column, pick the topmost (first listed) non-blocked card. Use \`sb show <ref> --json\` to read the card's full details and comments before starting.
Within a column, pick the topmost (first listed) non-blocked card. Use \`sb show <ref> --json --board {{boardDid}}:{{boardRkey}}\` to read the card's full details and comments before starting.

### 3. DO one transition

Expand All @@ -189,8 +189,8 @@ ${buildTransitionTable(columns)}

After doing the work:

1. Add a comment to the card describing what you did: \`sb comment <ref> "<summary>"\`
2. Move the card: \`sb mv <ref> <column>\`
1. Add a comment to the card describing what you did: \`sb comment <ref> "<summary>" --board {{boardDid}}:{{boardRkey}}\`
2. Move the card: \`sb mv <ref> <column> --board {{boardDid}}:{{boardRkey}}\`

### 5. WRITE STATUS AND EXIT

Expand All @@ -205,7 +205,7 @@ echo "CONTINUE" > .skyboard-ralph/loop-status
**If blocked on a card:**

1. Comment on the card explaining what's blocking you
2. Add the \`blocked\` label: \`sb edit <ref> -l blocked\`
2. Add the \`blocked\` label: \`sb edit <ref> -l blocked --board {{boardDid}}:{{boardRkey}}\`
3. Do NOT move the card
4. Try to pick a different non-blocked card instead
5. If no other non-blocked cards are available:
Expand Down
2 changes: 1 addition & 1 deletion commands/cards.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Options:
- `-c, --column <column>` β€” filter by column (name, prefix, or 1-based index)
- `-l, --label <label>` β€” filter by label name
- `-s, --search <text>` β€” search in title and description
- `--board <ref>` β€” override default board
- `--board <ref>` β€” override default board (AT URI, `did:plc:xxx:rkey`, or board name)

Always use `--json`. Returns an array of column objects, each with a `cards[]` array containing: `rkey`, `shortRef`, `title`, `description`, `labels`, `createdAt`, `lastModifiedAt`.

Expand Down
2 changes: 1 addition & 1 deletion commands/cols.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ sb cols --json

Options:

- `--board <ref>` β€” override default board
- `--board <ref>` β€” override default board (AT URI, `did:plc:xxx:rkey`, or board name)

Always use `--json`. Returns an array with: `index`, `id`, `name`, `taskCount`.

Expand Down
2 changes: 1 addition & 1 deletion commands/comment.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ sb comment <ref> "<text>" --json

Options:

- `--board <ref>` β€” override default board
- `--board <ref>` β€” override default board (AT URI, `did:plc:xxx:rkey`, or board name)

Always use `--json`. Returns: `rkey`, `taskRkey`, `text`.

Expand Down
2 changes: 1 addition & 1 deletion commands/edit.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Options:
- `-t, --title <title>` β€” new title
- `-d, --description <desc>` β€” new description
- `-l, --label <label...>` β€” set labels (can be specified multiple times)
- `--board <ref>` β€” override default board
- `--board <ref>` β€” override default board (AT URI, `did:plc:xxx:rkey`, or board name)

At least one of `-t`, `-d`, or `-l` must be provided. Always use `--json`. Returns: `rkey`, `fields`.

Expand Down
2 changes: 1 addition & 1 deletion commands/mv.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ sb mv <ref> <column> --json

Options:

- `--board <ref>` β€” override default board
- `--board <ref>` β€” override default board (AT URI, `did:plc:xxx:rkey`, or board name)

Always use `--json`. Returns: `rkey`, `column`.

Expand Down
2 changes: 1 addition & 1 deletion commands/new.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Options:

- `-c, --column <column>` β€” target column (default: first column). Column by name, prefix, or 1-based index.
- `-d, --description <desc>` β€” card description
- `--board <ref>` β€” override default board
- `--board <ref>` β€” override default board (AT URI, `did:plc:xxx:rkey`, or board name)

Always use `--json`. Returns: `rkey`, `title`, `column`, `position`.

Expand Down
Loading