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
3 changes: 1 addition & 2 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
SUPABASE_URL=
SUPABASE_PUBLISHABLE_KEY=
LISTEE_API_URL=
# LISTEE_CLI_KEYCHAIN_SERVICE=listee-cli
6 changes: 3 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
- Agents must follow the Listee org conventions observed in sibling repos (`listee-libs`, `listee-ci`) and keep changes minimal and well-justified.

## Repository Layout Awareness
- Source of truth: `src/index.ts` wires Commander, `src/commands/` hosts CLI handlers, and `src/services/` stores Supabase-facing logic. Tests will expand under `tests/`.
- Source of truth: `src/index.ts` wires Commander, `src/commands/` hosts CLI handlers, and `src/services/` stores Listee API-facing logic. Tests will expand under `tests/`.
- Keep generated output in `dist/` (never commit). Respect any existing files—do not alter unrelated modules.
- When referencing other org repos, treat them as read-only unless explicitly instructed.

Expand All @@ -23,8 +23,8 @@
- Indentation is two spaces, LF line endings, `kebab-case` filenames for modules, `camelCase` for identifiers. Maintain ASCII unless the file already uses Unicode.
- Keep comments purposeful; avoid restating the obvious. Add brief context only for non-trivial flows (e.g., token refresh sequencing).

## Supabase & Secrets Handling
- Read `SUPABASE_URL` and `SUPABASE_ANON_KEY` from environment variables or `.env`; never hardcode credentials.
## Listee API & Secrets Handling
- Read `LISTEE_API_URL` from environment variables or `.env`; never hardcode endpoints or secrets.
- Keytar service name defaults to `listee-cli`. If a feature demands overrides, surface them via env vars or CLI flags.

## Safety & Review Protocol
Expand Down
22 changes: 18 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# listee-cli

Official command-line interface for Listee — manage authentication, categories, and tasks directly from your terminal. The MVP focuses on Supabase email/password flows (`signup`, `login`, `logout`, `status`).
Official command-line interface for Listee — manage authentication, categories, and tasks directly from your terminal via the Listee API (`signup`, `login`, `logout`, `status`).

## Requirements
- Bun 1.2.22 (`bun --version`)
- Node.js 20+ (runtime for the compiled CLI)
- Supabase project credentials (`SUPABASE_URL`, `SUPABASE_ANON_KEY`)
- Listee API base URL (`LISTEE_API_URL`)

## Installation
```bash
Expand All @@ -15,8 +15,7 @@ bun install
## Configuration
Create a `.env` file or export environment variables before running commands:
```bash
export SUPABASE_URL="https://your-project.supabase.co"
export SUPABASE_ANON_KEY="your-anon-key"
export LISTEE_API_URL="https://api.your-listee-instance.dev"
# optional: override the Keytar service name
export LISTEE_CLI_KEYCHAIN_SERVICE="listee-cli"
```
Expand All @@ -32,6 +31,16 @@ listee auth signup --email you@example.com
listee auth login --email you@example.com
listee auth status
listee auth logout
listee categories list [--email you@example.com]
listee categories show <categoryId> [--email you@example.com]
listee categories create --name "Inbox" [--email you@example.com]
listee categories update <categoryId> --name "New name" [--email you@example.com]
listee categories delete <categoryId> [--email you@example.com]
listee tasks list --category <categoryId> [--email you@example.com]
listee tasks create --category <categoryId> --name "Task title" [--description "..."] [--checked] [--email you@example.com]
listee tasks show <taskId> [--email you@example.com]
listee tasks update <taskId> [--name "New title"] [--description "..."] [--clear-description] [--checked|--unchecked] [--email you@example.com]
listee tasks delete <taskId> [--email you@example.com]
```

`listee auth signup` starts a temporary local callback server. Leave the command running, open the confirmation email, and the CLI will finish automatically once the browser redirects back to the loopback URL.
Expand All @@ -49,7 +58,12 @@ listee auth logout
src/
index.ts # CLI entrypoint (Commander wiring)
commands/auth.ts # Auth subcommands
commands/categories.ts
commands/tasks.ts
services/auth-service.ts
services/api-client.ts
services/category-api.ts
services/task-api.ts
AGENTS.md # Agent-specific automation guidelines
```

Expand Down
16 changes: 11 additions & 5 deletions bun.lock

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

8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@
"test": "bun test"
},
"dependencies": {
"@listee/auth": "^0.2.3",
"@listee/types": "^0.2.3",
"@listee/auth": "^0.4.0",
"@listee/types": "^0.4.0",
"@napi-rs/keyring": "^1.2.0",
"@t3-oss/env-core": "^0.13.8",
"commander": "^12.1.0",
"dotenv": "^16.4.5"
"dotenv": "^16.4.5",
"zod": "^4.1.12"
},
"devDependencies": {
"@biomejs/biome": "^2.2.4",
Expand Down
36 changes: 21 additions & 15 deletions src/commands/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { createInterface } from "node:readline";
import type { Command } from "commander";
import {
completeSignupFromFragment,
ensureSupabaseConfig,
ensureListeeApiConfig,
login,
logout,
signup,
Expand Down Expand Up @@ -34,6 +34,10 @@ const ensureEmail = (value: unknown): string => {
return ensureNonEmpty(value, "Email");
};

const isRecord = (value: unknown): value is Record<string, unknown> => {
return typeof value === "object" && value !== null;
};

const handleError = (error: unknown): void => {
if (error instanceof Error) {
console.error(`Error: ${error.message}`);
Expand Down Expand Up @@ -138,7 +142,10 @@ const startLoopbackServer = async (): Promise<LoopbackServer> => {
return;
}
try {
const parsed = JSON.parse(data) as { hash?: string };
const parsed = JSON.parse(data);
if (!isRecord(parsed)) {
throw new Error("Invalid request body received.");
}
const hash = parsed.hash;
if (typeof hash !== "string" || hash.length === 0) {
throw new Error("Missing hash in request body.");
Expand Down Expand Up @@ -181,11 +188,12 @@ const startLoopbackServer = async (): Promise<LoopbackServer> => {
});

const address = server.address();
if (
address === null ||
typeof address !== "object" ||
address.port === undefined
) {
if (address === null || typeof address === "string") {
server.close();
throw new Error("Failed to determine loopback server port.");
}
const { port } = address;
if (port === undefined) {
server.close();
throw new Error("Failed to determine loopback server port.");
}
Expand All @@ -210,7 +218,7 @@ const startLoopbackServer = async (): Promise<LoopbackServer> => {
};

return {
redirectUrl: `http://${LOOPBACK_HOST}:${address.port}/callback`,
redirectUrl: `http://${LOOPBACK_HOST}:${port}/callback`,
waitForConfirmation: () =>
waitForConfirmation.finally(() => clearTimeout(timeout)),
shutdown,
Expand Down Expand Up @@ -331,15 +339,15 @@ const printStatus = (result: AuthStatus): void => {
};

const loginAction = async (options: EmailOption): Promise<void> => {
ensureSupabaseConfig();
ensureListeeApiConfig();
const email = ensureEmail(options.email);
const password = await promptHiddenInput("Password: ");
await login(email, password);
console.log("✅ Logged in.");
};

const signupAction = async (options: EmailOption): Promise<void> => {
ensureSupabaseConfig();
ensureListeeApiConfig();
const email = ensureEmail(options.email);
const password = await promptHiddenInput("Password: ");
const loopback = await startLoopbackServer();
Expand Down Expand Up @@ -385,13 +393,11 @@ const statusAction = async (): Promise<void> => {
export const registerAuthCommand = (program: Command): void => {
const auth = program
.command("auth")
.description("Manage Supabase authentication for Listee.");
.description("Manage Listee API authentication for Listee.");

auth
.command("signup")
.description(
"Sign up for a new Listee account via Supabase email/password.",
)
.description("Sign up for a new Listee account via the Listee API.")
.requiredOption("--email <email>", "Email address to register")
.action(
execute(async (options: EmailOption) => {
Expand All @@ -402,7 +408,7 @@ export const registerAuthCommand = (program: Command): void => {
auth
.command("login")
.description(
"Authenticate with Supabase using email/password and store refresh token in keychain.",
"Authenticate with the Listee API using email/password and store refresh token in keychain.",
)
.requiredOption("--email <email>", "Email address to log in")
.action(
Expand Down
Loading