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
2 changes: 2 additions & 0 deletions .github/workflows/docs-pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ jobs:

- name: Setup Pages
uses: actions/configure-pages@v5
with:
enablement: true

- name: Upload Artifact
uses: actions/upload-pages-artifact@v3
Expand Down
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@ bun run dev -- --help
Output controls:

```bash
bluebubbles contact list
bluebubbles contacts list
bluebubbles chats list -o wide
bluebubbles messages list --chat 'iMessage;+;chat123' -o wide
bluebubbles chats list -o json
```

Expand All @@ -57,6 +58,14 @@ GUID FROM TEXT AGE CHAT
9aa1...77c +1555... sounds good, see you soon 8m iMessage;+;chat123
```

`messages list -o wide` includes extra columns:

```text
GUID FROM TEXT AGE CHAT FROM_ME ATTACHMENTS CREATED_AT CHAT_NAME
4b2f...e91 me hello from bluebubbles 2m iMessage;+;chat123 yes 0 2026-04-12 21:08:14 Weekend Plans
9aa1...77c +1555... sounds good, see you soon 8m iMessage;+;chat123 no 1 2026-04-12 21:02:07 Weekend Plans
```

JSON output is available with `-o json` or `--json`:

```json
Expand All @@ -76,9 +85,12 @@ For messages:

```bash
bluebubbles messages list --chat 'iMessage;+;chat123'
bluebubbles messages list --chat 'iMessage;+;chat123' -o wide
bluebubbles messages list --chat 'iMessage;+;chat123' --json
```

`-o wide` affects table output only. JSON output remains the full payload (`-o json` / `--json`).

Pagination defaults are conservative for message-heavy commands:

- `bluebubbles messages list` defaults to `--limit 50`
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "bluebubbles-cli",
"version": "0.1.4",
"version": "0.1.6",
"description": "Curated BlueBubbles CLI organized around terminal-friendly resources",
"type": "module",
"bin": {
Expand Down Expand Up @@ -33,7 +33,7 @@
"yaml": "^2.8.3"
},
"dependencies": {
"@anmho/bluebubbles-sdk": "^0.1.0",
"@anmho/bluebubbles-sdk": "0.1.1",
"columnify": "^1.6.0",
"commander": "^14.0.3",
"zod": "^4.3.6"
Expand Down
52 changes: 26 additions & 26 deletions scripts/test-commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -353,32 +353,32 @@ async function main(): Promise<number> {
{ name: "server theme set", argv: ["server", "theme", "set", "test-theme", "--file", themeFile], ok: [0], requiresApi: true, destructive: true },
{ name: "server theme delete", argv: ["server", "theme", "delete", "test-theme", "--yes"], ok: [0, 6], requiresApi: true, destructive: true },

{ name: "chat list", argv: ["chat", "list"], ok: [0], requiresApi: true },
{ name: "chat get", argv: ["chat", "get", chatGuid], ok: [0, 6], requiresApi: true },
{ name: "chat messages", argv: ["chat", "messages", chatGuid], ok: [0, 6], requiresApi: true },
{ name: "chat update", argv: ["chat", "update", chatGuid, "--name", "Updated Name"], ok: [0, 6], requiresApi: true, destructive: true },
{ name: "chat delete", argv: ["chat", "delete", chatGuid, "--yes"], ok: [0, 6], requiresApi: true, destructive: true },
{ name: "chat group leave", argv: ["chat", "group", "leave", chatGuid, "--yes"], ok: [0, 6], requiresApi: true, destructive: true },
{ name: "chat group participant add", argv: ["chat", "group", "participant", "add", chatGuid, address], ok: [0, 6], requiresApi: true, destructive: true },
{ name: "chat group participant remove", argv: ["chat", "group", "participant", "remove", chatGuid, address, "--yes"], ok: [0, 6], requiresApi: true, destructive: true },
{ name: "chat group icon set", argv: ["chat", "group", "icon", "set", chatGuid], ok: [0, 6], requiresApi: true, destructive: true },
{ name: "chat group icon remove", argv: ["chat", "group", "icon", "remove", chatGuid, "--yes"], ok: [0, 6], requiresApi: true, destructive: true },
{ name: "chat typing start", argv: ["chat", "typing", "start", chatGuid], ok: [0, 6], requiresApi: true },
{ name: "chat typing stop", argv: ["chat", "typing", "stop", chatGuid], ok: [0, 6], requiresApi: true },

{ name: "message list", argv: ["message", "list"], ok: [0], requiresApi: true },
{ name: "message list common filters", argv: ["message", "list", "--chat", chatGuid, "--text", "hello", "--not-from-me", "--limit", "20"], ok: [0, 6], requiresApi: true },
{ name: "message list raw where", argv: ["message", "list", "--where", '[{"statement":"message.text LIKE :q","args":{"q":"%hello%"}}]'], ok: [0], requiresApi: true },
{ name: "message get", argv: ["message", "get", messageGuid], ok: [0, 6], requiresApi: true },
{ name: "message send", argv: ["message", "send", "--chat", chatGuid, "--message", "hello"], ok: [0, 6], requiresApi: true, destructive: true },
{ name: "message react", argv: ["message", "react", messageGuid, "--chat", chatGuid, "--reaction", "love"], ok: [0, 6], requiresApi: true, destructive: true },
{ name: "message edit", argv: ["message", "edit", messageGuid, "--message", "updated"], ok: [0, 6], requiresApi: true, destructive: true },
{ name: "message unsend", argv: ["message", "unsend", messageGuid, "--yes"], ok: [0, 6], requiresApi: true, destructive: true },
{ name: "message schedule list", argv: ["message", "schedule", "list"], ok: [0], requiresApi: true },
{ name: "message schedule get", argv: ["message", "schedule", "get", scheduleId], ok: [0, 6], requiresApi: true },
{ name: "message schedule create", argv: ["message", "schedule", "create", "--chat", chatGuid, "--message", "later", "--date", String(Date.now() + 60000)], ok: [0, 6], requiresApi: true, destructive: true },
{ name: "message schedule update", argv: ["message", "schedule", "update", scheduleId, "--message", "new value"], ok: [0, 6], requiresApi: true, destructive: true },
{ name: "message schedule delete", argv: ["message", "schedule", "delete", scheduleId, "--yes"], ok: [0, 6], requiresApi: true, destructive: true },
{ name: "chat list", argv: ["chats", "list"], ok: [0], requiresApi: true },
{ name: "chat get", argv: ["chats", "get", chatGuid], ok: [0, 6], requiresApi: true },
{ name: "chat messages", argv: ["chats", "messages", chatGuid], ok: [0, 6], requiresApi: true },
{ name: "chat update", argv: ["chats", "update", chatGuid, "--name", "Updated Name"], ok: [0, 6], requiresApi: true, destructive: true },
{ name: "chat delete", argv: ["chats", "delete", chatGuid, "--yes"], ok: [0, 6], requiresApi: true, destructive: true },
{ name: "chat group leave", argv: ["chats", "group", "leave", chatGuid, "--yes"], ok: [0, 6], requiresApi: true, destructive: true },
{ name: "chat group participant add", argv: ["chats", "group", "participant", "add", chatGuid, address], ok: [0, 6], requiresApi: true, destructive: true },
{ name: "chat group participant remove", argv: ["chats", "group", "participant", "remove", chatGuid, address, "--yes"], ok: [0, 6], requiresApi: true, destructive: true },
{ name: "chat group icon set", argv: ["chats", "group", "icon", "set", chatGuid], ok: [0, 6], requiresApi: true, destructive: true },
{ name: "chat group icon remove", argv: ["chats", "group", "icon", "remove", chatGuid, "--yes"], ok: [0, 6], requiresApi: true, destructive: true },
{ name: "chat typing start", argv: ["chats", "typing", "start", chatGuid], ok: [0, 6], requiresApi: true },
{ name: "chat typing stop", argv: ["chats", "typing", "stop", chatGuid], ok: [0, 6], requiresApi: true },

{ name: "message list", argv: ["messages", "list"], ok: [0], requiresApi: true },
{ name: "message list common filters", argv: ["messages", "list", "--chat", chatGuid, "--text", "hello", "--not-from-me", "--limit", "20"], ok: [0, 6], requiresApi: true },
{ name: "message list raw where", argv: ["messages", "list", "--where", '[{"statement":"message.text LIKE :q","args":{"q":"%hello%"}}]'], ok: [0], requiresApi: true },
{ name: "message get", argv: ["messages", "get", messageGuid], ok: [0, 6], requiresApi: true },
{ name: "message send", argv: ["messages", "send", "--chat", chatGuid, "--message", "hello"], ok: [0, 6], requiresApi: true, destructive: true },
{ name: "message react", argv: ["messages", "react", messageGuid, "--chat", chatGuid, "--reaction", "love"], ok: [0, 6], requiresApi: true, destructive: true },
{ name: "message edit", argv: ["messages", "edit", messageGuid, "--message", "updated"], ok: [0, 6], requiresApi: true, destructive: true },
{ name: "message unsend", argv: ["messages", "unsend", messageGuid, "--yes"], ok: [0, 6], requiresApi: true, destructive: true },
{ name: "message schedule list", argv: ["messages", "schedule", "list"], ok: [0], requiresApi: true },
{ name: "message schedule get", argv: ["messages", "schedule", "get", scheduleId], ok: [0, 6], requiresApi: true },
{ name: "message schedule create", argv: ["messages", "schedule", "create", "--chat", chatGuid, "--message", "later", "--date", String(Date.now() + 60000)], ok: [0, 6], requiresApi: true, destructive: true },
{ name: "message schedule update", argv: ["messages", "schedule", "update", scheduleId, "--message", "new value"], ok: [0, 6], requiresApi: true, destructive: true },
{ name: "message schedule delete", argv: ["messages", "schedule", "delete", scheduleId, "--yes"], ok: [0, 6], requiresApi: true, destructive: true },

{ name: "handle list", argv: ["handle", "list"], ok: [0, 4], requiresApi: true },
{ name: "handle availability", argv: ["handle", "availability", address], ok: [0, 6], requiresApi: true },
Expand Down
7 changes: 4 additions & 3 deletions src/commands/chats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
addDangerousOption,
maybePrint,
requireConfirmation,
isWideOutput,
withBlueBubblesDeps,
withPaging,
} from "~/lib/cli-helpers.js";
Expand All @@ -30,7 +31,7 @@ import { DEFAULT_CHAT_WITH, DEFAULT_MESSAGE_WITH } from "~/lib/constants.js";
import type { CommandOverrides, OutputOptions } from "~/lib/types.js";

export function registerChatCommands(program: Command): void {
const chatCommand = program.command("chat").description("Chat resource operations");
const chatCommand = program.command("chats").description("Chat resource operations");

addConnectionOptions(
withPaging(chatCommand.command("list").description("List chats (POST /api/v1/chat/query)")),
Expand All @@ -51,7 +52,7 @@ export function registerChatCommands(program: Command): void {
with: options.with.length > 0 ? options.with : [...DEFAULT_CHAT_WITH],
});
const chats = result.data ?? [];
maybePrint(chats, options, () => printChats(chats));
maybePrint(chats, options, () => printChats(chats, isWideOutput(options)));
}),
);

Expand Down Expand Up @@ -97,7 +98,7 @@ export function registerChatCommands(program: Command): void {
before: options.before,
with: options.with.length > 0 ? options.with : [...DEFAULT_MESSAGE_WITH],
});
maybePrint(result.data ?? [], options, () => printMessages(result.data ?? []));
maybePrint(result.data ?? [], options, () => printMessages(result.data ?? [], isWideOutput(options)));
}),
);

Expand Down
5 changes: 3 additions & 2 deletions src/commands/contacts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { Command } from "commander";
import {
addConnectionOptions,
collect,
isWideOutput,
maybePrint,
withBlueBubblesDeps,
} from "~/lib/cli-helpers.js";
Expand All @@ -24,7 +25,7 @@ export function registerContactCommands(program: Command): void {
console.log("No contacts found.");
return;
}
printContacts(result.data);
printContacts(result.data, isWideOutput(options));
});
}));

Expand All @@ -41,7 +42,7 @@ export function registerContactCommands(program: Command): void {
console.log("No contacts found.");
return;
}
printContacts(result.data);
printContacts(result.data, isWideOutput(options));
});
}));
}
3 changes: 2 additions & 1 deletion src/commands/handles.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Command } from "commander";
import {
addConnectionOptions,
isWideOutput,
maybePrint,
withBlueBubblesDeps,
withPaging,
Expand Down Expand Up @@ -33,7 +34,7 @@ export function registerHandleCommands(program: Command): void {
console.log("No handles found.");
return;
}
printHandles(result.data);
printHandles(result.data, isWideOutput(options));
});
}),
);
Expand Down
5 changes: 3 additions & 2 deletions src/commands/icloud.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Command } from "commander";
import {
addConnectionOptions,
isWideOutput,
maybePrint,
withBlueBubblesDeps,
} from "~/lib/cli-helpers.js";
Expand Down Expand Up @@ -53,7 +54,7 @@ export function registerICloudCommands(program: Command): void {
console.log("No devices found or FindMy not enabled.");
return;
}
printFindMyDevices(result.data);
printFindMyDevices(result.data, isWideOutput(options));
});
}));

Expand All @@ -75,7 +76,7 @@ export function registerICloudCommands(program: Command): void {
console.log("No friends found or FindMy not enabled.");
return;
}
printFindMyFriends(result.data);
printFindMyFriends(result.data, isWideOutput(options));
});
}));

Expand Down
19 changes: 19 additions & 0 deletions src/commands/local-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
} from "~/lib/output.js";
import { getRemoteLogs } from "~/lib/bluebubbles/server.js";
import {
openServerApp,
restartServer,
serverStatus,
showLogs,
Expand All @@ -24,6 +25,24 @@ import {
import type { CommandOverrides, OutputOptions } from "~/lib/types.js";

export function registerServerLifecycleCommands(serverCommand: Command): void {
addConnectionOptions(
serverCommand.command("open").description("Open the local BlueBubbles desktop app (local process manager, no API endpoint)"),
)
.option("--app-path <path>", "Override the BlueBubbles app bundle or executable path")
.action(
async (options: CommandOverrides & OutputOptions & { appPath?: string; config?: string }) => {
const context = await withConfig({
configPath: options.config,
appPath: options.appPath,
});
const result = await openServerApp({
config: context.config,
appPath: options.appPath,
});
maybePrint(result, options, () => printSuccess(`Opened BlueBubbles app: ${result.appPath}`, false));
},
);

addConnectionOptions(
serverCommand.command("start").description("Start the local BlueBubbles app (local process manager, no API endpoint)"),
)
Expand Down
7 changes: 4 additions & 3 deletions src/commands/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
addDangerousOption,
maybePrint,
requireConfirmation,
isWideOutput,
withBlueBubblesDeps,
withPaging,
} from "~/lib/cli-helpers.js";
Expand Down Expand Up @@ -31,7 +32,7 @@ import type { EditMessageInput, SendReactInput } from "~/lib/bluebubbles/message
import type { CommandOverrides, MessageSummary, OutputOptions } from "~/lib/types.js";

export function registerMessageCommands(program: Command): void {
const messageCommand = program.command("message").description("Message resource operations");
const messageCommand = program.command("messages").description("Message resource operations");

addConnectionOptions(
withPaging(messageCommand.command("list").description("List messages (POST /api/v1/message/query)"), 50),
Expand Down Expand Up @@ -175,7 +176,7 @@ export function registerMessageCommands(program: Command): void {
from: options.from,
hasAttachments: options.hasAttachments,
});
maybePrint(filtered, options, () => printMessages(filtered));
maybePrint(filtered, options, () => printMessages(filtered, isWideOutput(options)));
}),
);

Expand Down Expand Up @@ -311,7 +312,7 @@ export function registerMessageCommands(program: Command): void {
console.log("No scheduled messages.");
return;
}
printScheduledMessages(result.data);
printScheduledMessages(result.data, isWideOutput(options));
});
}));

Expand Down
3 changes: 2 additions & 1 deletion src/commands/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { Command } from "commander";
import {
addConnectionOptions,
addDangerousOption,
isWideOutput,
maybePrint,
requireConfirmation,
withBlueBubblesDeps,
Expand Down Expand Up @@ -51,7 +52,7 @@ export function registerServerCommands(program: Command): void {
console.log("No alerts.");
return;
}
printAlerts(result.data);
printAlerts(result.data, isWideOutput(options));
});
}));

Expand Down
Loading
Loading