Skip to content

feat: add categories and tasks commands#7

Merged
gentamura merged 12 commits intomainfrom
feat/categories-tasks
Nov 12, 2025
Merged

feat: add categories and tasks commands#7
gentamura merged 12 commits intomainfrom
feat/categories-tasks

Conversation

@gentamura
Copy link
Member

@gentamura gentamura commented Oct 22, 2025

Summary

  • add Commander subcommands for categories (list/show/create) and tasks (list/show/create) with shared error handling and output helpers
  • implement API client utilities plus category/task service layers to call the Listee API using authenticated contexts
  • document new CLI usage and required env vars in README and .env.example; wire commands in CLI entrypoint

Testing

  • bun run lint
  • bun run build

Summary by CodeRabbit

  • New Features

    • Full CLI for categories and tasks (list, show, create, update, delete) backed by the Listee API and integrated auth.
  • Configuration

    • Replaced Supabase env guidance with LISTEE_API_URL; updated README, examples, and secrets-handling guidance.
  • Improvements

    • New API client, stricter environment validation with cache control, and clearer API error messages and payload handling.
  • Tests

    • Expanded auth token and environment-related test coverage.

@coderabbitai
Copy link

coderabbitai bot commented Oct 22, 2025

Walkthrough

Replaces Supabase wiring with a Listee API–first CLI: adds env validation, an API base/client, category/task service modules and CLI commands, refactors auth to use AuthToken shapes and authenticated contexts, updates docs and package deps, and wires new commands into startup. (50 words)

Changes

Cohort / File(s) Summary
Env & docs
\.env\.example, README\.md, AGENTS\.md
Replace Supabase env vars with LISTEE_API_URL; update README for API-based CLI and new categories/tasks commands; adjust agents/docs to reference LISTEE_API_URL and src/services/.
Env validation module
src/env.ts
New environment builder/validator using @t3-oss/env-core + zod: getEnv(), checkEnv(), resetEnvCache(), EnvValidationError, issue descriptions; validates LISTEE_API_URL and keychain service name.
CLI entry
src/index.ts
Remove dotenv/config; call checkEnv() at startup; register registerCategoryCommand and registerTaskCommand along with auth command.
Auth command changes
src/commands/auth.ts
Swap ensureSupabaseConfigensureListeeApiConfig; add JSON/body guards for loopback/token flow; validate loopback port/address; update CLI help to reference Listee API.
New CLI commands
src/commands/categories.ts, src/commands/tasks.ts
Add registerCategoryCommand and registerTaskCommand with subcommands (list/show/create/update/delete), input validators, unified error wrapper, and formatted output.
API base & client
src/services/api-base.ts, src/services/api-client.ts
New API utilities: getListeeApiBaseUrl, buildListeeApiUrl, readApiPayload, extractApiErrorMessage; and authenticated JSON client: createAuthenticatedContext, requestJson.
Auth service & types
src/services/auth-service.ts, src/services/auth-service.test.ts, src/types/auth.ts
Replace Supabase token shapes with AuthToken flow: ensureListeeApiConfig, toAuthenticatedAccessTokenResult, getAuthenticatedAccessToken, token decoding/extraction; tests adapted; new AuthTokenResponse/AuthTokenClaims types.
Resource APIs
src/services/category-api.ts, src/services/task-api.ts
New typed service clients exposing CRUD/listing functions for categories and tasks with runtime payload validation and converters; use authenticated API client.
Packaging & deps
package.json
Bump @listee/auth and @listee/types to ^0.4.0; add @t3-oss/env-core and zod.
Tests
src/services/auth-service.test.ts
Tests updated for LISTEE_API_URL usage, token decoding/result helpers, and related error cases.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant CLI as "CLI Program"
    participant Env as "Env Validator"
    participant Auth as "Auth Service"
    participant API as "API Client"
    participant Listee as "Listee API"
    participant Out as "CLI Output"

    User->>CLI: run "categories list --email user@example.com"
    CLI->>Env: checkEnv()
    Env-->>CLI: env validated
    CLI->>Auth: getAuthenticatedAccessToken(email)
    Auth->>Listee: POST /auth (returns AuthTokenResponse)
    Listee-->>Auth: access token (JWT)
    Auth-->>API: createAuthenticatedContext(authorizationValue)
    API->>Listee: GET /users/{userId}/categories (Bearer ...)
    Listee-->>API: 200 + JSON payload
    API-->>CLI: parsed categories
    CLI->>Out: printCategories (list + pagination)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

  • Areas to focus:
    • src/env.ts — validation semantics and user-facing messages.
    • src/services/auth-service.ts & src/services/api-client.ts — token decoding, subject/email extraction, and auth context creation.
    • src/services/api-base.ts — payload parsing and API error extraction.
    • src/services/category-api.ts / src/services/task-api.ts — runtime validators and converters.
    • src/commands/categories.ts / src/commands/tasks.ts — CLI input validation, error wrapping, and exit-code behavior.

Possibly related PRs

Poem

🐰 I sniffed a LISTEE URL so bright,

Tokens decoded in the moonlight,
Categories, tasks—now at my call,
I hop and print them one and all,
Hooray! The CLI leaps into flight.

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely summarizes the primary change: adding new categories and tasks commands to the CLI, reflecting the core functionality introduced across multiple new command and service modules.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/categories-tasks

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (8)
src/commands/auth.ts (2)

37-40: Exclude arrays in isRecord.

Arrays pass this check but aren’t desired as generic records. Add !Array.isArray(value).

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

366-368: Use ASCII-only logs for terminal compatibility.

Replace emoji with ASCII to meet the “ASCII unless already used” guideline and improve portability.

-    console.log(
-      "📩 Confirmation email sent. Keep this terminal open while you click the link.",
-    );
+    console.log(
+      "Confirmation email sent. Keep this terminal open while you click the link.",
+    );

As per coding guidelines.

src/commands/tasks.ts (1)

8-29: DRY shared CLI helpers across commands.

execute and ensureNonEmptyString duplicate logic in categories.ts. Extract into src/commands/cli-utils.ts and import in both files.

+// src/commands/cli-utils.ts
+export const execute = <T extends unknown[]>(task: (...args: T) => Promise<void>) => {
+  return async (...args: T): Promise<void> => {
+    try {
+      await task(...args);
+    } catch (error) {
+      if (error instanceof Error) console.error(`Error: ${error.message}`);
+      else console.error("Unknown error occurred.");
+      process.exitCode = 1;
+    }
+  };
+};
+
+export const ensureNonEmptyString = (value: string, label: string): string => {
+  const trimmed = value.trim();
+  if (trimmed.length === 0) throw new Error(`${label} must not be empty.`);
+  return trimmed;
+};
-const execute = <T extends unknown[]>(task: (...args: T) => Promise<void>) => { ... }
-const ensureNonEmptyString = (value: string, label: string): string => { ... }
+import { execute, ensureNonEmptyString } from "./cli-utils.js";

Apply similarly in src/commands/categories.ts. As per coding guidelines (module boundaries, avoid duplication).

Also applies to: 75-151

src/commands/categories.ts (1)

8-17: Consolidate common helpers with tasks CLI.

Move ensurePositiveInteger and ensureNonEmptyString (and the execute wrapper) to a shared src/commands/cli-utils.ts to reduce duplication.

Also applies to: 19-40

src/services/category-api.ts (3)

21-23: Extract shared isRecord helper to avoid duplication.

The isRecord helper is duplicated across src/services/category-api.ts, src/services/task-api.ts, and src/services/api-client.ts. Consider extracting it to a shared utility module (e.g., src/services/type-guards.ts) to follow DRY principles.

Example:

// src/services/type-guards.ts
export const isRecord = (value: unknown): value is Record<string, unknown> => {
  return typeof value === "object" && value !== null;
};

Then import from the shared location:

+import { isRecord } from "./type-guards.js";
+
-const isRecord = (value: unknown): value is Record<string, unknown> => {
-  return typeof value === "object" && value !== null;
-};

41-54: Consider simplifying toCategory.

After isCategory validation, the explicit field-by-field mapping is redundant. You could return the value directly for conciseness:

 const toCategory = (value: unknown): Category => {
   if (!isCategory(value)) {
     throw new Error("API response did not include valid category data.");
   }
-  return {
-    id: value.id,
-    name: value.name,
-    kind: value.kind,
-    createdBy: value.createdBy,
-    updatedBy: value.updatedBy,
-    createdAt: value.createdAt,
-    updatedAt: value.updatedAt,
-  };
+  return value;
 };

However, explicit mapping does provide clarity and can be retained if preferred.


63-93: Consider simplifying nextCursor validation.

The nextCursor validation is correct but somewhat indirect. Consider a more straightforward approach:

-  const nextCursorValue =
-    metaValue.nextCursor === null || typeof metaValue.nextCursor === "string"
-      ? metaValue.nextCursor
-      : undefined;
-  if (metaValue.hasMore !== true && metaValue.hasMore !== false) {
-    throw new Error("List categories response has invalid hasMore flag.");
-  }
-
-  if (nextCursorValue === undefined) {
-    throw new Error("List categories response has invalid cursor value.");
-  }
+  if (metaValue.nextCursor !== null && typeof metaValue.nextCursor !== "string") {
+    throw new Error("List categories response has invalid cursor value.");
+  }
+  if (typeof metaValue.hasMore !== "boolean") {
+    throw new Error("List categories response has invalid hasMore flag.");
+  }

   return {
     data,
     meta: {
-      nextCursor: nextCursorValue,
+      nextCursor: metaValue.nextCursor,
       hasMore: metaValue.hasMore,
     },
   };
src/services/task-api.ts (1)

17-19: Extract shared isRecord helper to avoid duplication.

The isRecord helper is duplicated here as well. As noted in the review of src/services/category-api.ts, consider extracting this to a shared utility module.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5133211 and b2e393b.

📒 Files selected for processing (9)
  • .env.example (1 hunks)
  • README.md (3 hunks)
  • src/commands/auth.ts (6 hunks)
  • src/commands/categories.ts (1 hunks)
  • src/commands/tasks.ts (1 hunks)
  • src/index.ts (2 hunks)
  • src/services/api-client.ts (1 hunks)
  • src/services/category-api.ts (1 hunks)
  • src/services/task-api.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
src/services/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

src/services/**/*.ts: Place Supabase-facing logic under src/services/
Read SUPABASE_URL and SUPABASE_ANON_KEY from environment variables or .env; never hardcode credentials
Default Keytar service name to "listee-cli"; allow overrides via env vars or CLI flags

Files:

  • src/services/api-client.ts
  • src/services/category-api.ts
  • src/services/task-api.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use TypeScript strict practices: forbid any, forbid as assertions, no implicit any; implement type guards for narrowing
Use two-space indentation and LF line endings
Use camelCase for identifiers
Keep comments purposeful; add brief context only for non-trivial flows
Maintain ASCII in files unless the file already uses Unicode

Files:

  • src/services/api-client.ts
  • src/index.ts
  • src/commands/categories.ts
  • src/services/category-api.ts
  • src/services/task-api.ts
  • src/commands/auth.ts
  • src/commands/tasks.ts
src/**/[a-z0-9-]*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use kebab-case filenames for modules under src/

Files:

  • src/services/api-client.ts
  • src/index.ts
  • src/commands/categories.ts
  • src/services/category-api.ts
  • src/services/task-api.ts
  • src/commands/auth.ts
  • src/commands/tasks.ts
src/index.ts

📄 CodeRabbit inference engine (AGENTS.md)

Keep CLI wiring in src/index.ts using Commander; avoid moving or duplicating the entry wiring

Files:

  • src/index.ts
src/commands/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Place CLI command handlers under src/commands/

Files:

  • src/commands/categories.ts
  • src/commands/auth.ts
  • src/commands/tasks.ts
🧠 Learnings (3)
📚 Learning: 2025-10-07T13:26:37.124Z
Learnt from: CR
PR: listee-dev/listee-cli#0
File: AGENTS.md:0-0
Timestamp: 2025-10-07T13:26:37.124Z
Learning: Applies to src/services/**/*.ts : Read SUPABASE_URL and SUPABASE_ANON_KEY from environment variables or .env; never hardcode credentials

Applied to files:

  • README.md
📚 Learning: 2025-10-07T13:26:37.124Z
Learnt from: CR
PR: listee-dev/listee-cli#0
File: AGENTS.md:0-0
Timestamp: 2025-10-07T13:26:37.124Z
Learning: Applies to src/commands/**/*.ts : Place CLI command handlers under src/commands/

Applied to files:

  • README.md
📚 Learning: 2025-10-07T13:26:37.124Z
Learnt from: CR
PR: listee-dev/listee-cli#0
File: AGENTS.md:0-0
Timestamp: 2025-10-07T13:26:37.124Z
Learning: Applies to src/index.ts : Keep CLI wiring in src/index.ts using Commander; avoid moving or duplicating the entry wiring

Applied to files:

  • README.md
🧬 Code graph analysis (6)
src/services/api-client.ts (1)
src/services/auth-service.ts (1)
  • getAccessToken (423-456)
src/index.ts (2)
src/commands/categories.ts (1)
  • registerCategoryCommand (78-158)
src/commands/tasks.ts (1)
  • registerTaskCommand (75-152)
src/commands/categories.ts (1)
src/services/category-api.ts (3)
  • listCategories (112-128)
  • getCategory (135-142)
  • createCategory (150-167)
src/services/category-api.ts (1)
src/services/api-client.ts (2)
  • createAuthenticatedContext (100-118)
  • requestJson (148-169)
src/services/task-api.ts (1)
src/services/api-client.ts (2)
  • createAuthenticatedContext (100-118)
  • requestJson (148-169)
src/commands/tasks.ts (1)
src/services/task-api.ts (3)
  • listTasksByCategory (83-90)
  • getTask (97-104)
  • createTask (114-134)
🪛 dotenv-linter (4.0.0)
.env.example

[warning] 3-3: [UnorderedKey] The LISTEE_API_URL key should go before the SUPABASE_PUBLISHABLE_KEY key

(UnorderedKey)

🔇 Additional comments (32)
src/commands/auth.ts (1)

145-149: Good hardening of JSON body parsing.

Validating shape post-parse before accessing fields prevents runtime surprises. LGTM.

src/index.ts (1)

17-18: CLI wiring looks correct.

Category and task commands are properly registered before parse. No issues.

src/commands/categories.ts (1)

83-116: List flow and pagination messaging look good.

Validation + cursor handling are clear and correct. LGTM.

.env.example (1)

3-5: I need to inspect the actual implementation to confirm whether both keys are intentionally used:

Let me inspect the full .env.example file and check the context around the added lines:

Based on my verification using the grep results and Supabase documentation, I need to assess whether the original review comment is accurate.

The script confirmed that:

  • .env.example line 2 contains SUPABASE_PUBLISHABLE_KEY
  • auth-service.ts line 108 reads SUPABASE_PUBLISHABLE_KEY
  • auth-service.ts line 113 reads SUPABASE_ANON_KEY as a legacy fallback
  • The error message at line 119 explicitly states SUPABASE_PUBLISHABLE_KEY is not set

A single publishable key can be used to replace the anon key, and in the transition period, you can use both the current anon and service_role keys and the new publishable key. The code is correctly using the modern SUPABASE_PUBLISHABLE_KEY format.

The original review comment claims that .env.example using SUPABASE_PUBLISHABLE_KEY "will break auth setup" and that the code expects SUPABASE_ANON_KEY. However, this is incorrect—the code explicitly reads and expects SUPABASE_PUBLISHABLE_KEY. The README mentioning SUPABASE_ANON_KEY reflects legacy documentation, but the .env.example is aligned with the current code implementation.


Likely an incorrect or invalid review comment.

src/services/category-api.ts (7)

1-7: LGTM!

The imports and type definition are clean. The explicit remapping of createdAt and updatedAt from database types to string representation is appropriate for API serialization.


25-39: LGTM!

The type guard comprehensively validates all required Category fields with appropriate type checks.


56-61: LGTM!

Array validation and transformation logic is correct and follows the established pattern.


95-102: LGTM!

The category detail response parser is clean and follows the established validation pattern.


112-128: LGTM!

The listCategories implementation correctly handles optional pagination parameters, builds authenticated requests, and transforms responses with appropriate validation.


135-142: LGTM!

The getCategory implementation is straightforward and correctly handles authentication and response transformation.


150-167: LGTM!

The createCategory implementation correctly defaults kind to "user", sets appropriate headers, and transforms the response. The POST request is well-structured.

src/services/api-client.ts (12)

1-2: LGTM!

Imports are appropriate for the module's functionality.


15-17: LGTM!

The non-empty string validation correctly trims whitespace and checks length.


23-31: LGTM!

Environment variable validation follows the coding guidelines to read from environment variables with clear error messages when missing.

As per coding guidelines.


33-36: LGTM!

The URL construction from environment variable is straightforward and will throw on invalid URLs.


38-69: JWT decoding is correct for trusted tokens.

The implementation correctly decodes JWT claims without signature verification. This is acceptable since tokens come from getAccessToken (which obtains them from a trusted auth service), but ensure tokens are always obtained through authenticated flows.


71-82: LGTM!

JSON parsing with empty response handling and error wrapping is well-implemented.


84-98: LGTM!

Authorization mode configuration with sensible defaults and strict validation is well-implemented.


100-118: LGTM!

The authenticated context creation correctly handles token retrieval, claims extraction, and mode-based authorization value selection.


120-125: LGTM!

Standard Bearer authentication and JSON Accept headers are correctly formatted.


127-135: LGTM!

URL construction with proper path validation and base URL normalization correctly handles various URL formats.


137-146: LGTM!

Error message extraction with fallback handling is defensive and correct.


148-169: LGTM!

The HTTP request wrapper correctly merges headers, handles errors with meaningful messages, and provides a clean interface for authenticated API calls.

src/services/task-api.ts (9)

1-7: LGTM!

The Task type definition follows the same pattern as Category, consistently remapping timestamp fields to strings.


9-15: LGTM!

Response types are clean. Note that TaskListResponse does not include pagination metadata, unlike ListCategoriesResponse. This is acceptable if task lists per category are expected to be small, but consider adding pagination if lists could grow large.


21-37: LGTM!

The type guard comprehensively validates all Task fields, correctly handling the nullable description field and boolean isChecked field.


39-54: LGTM!

The toTask converter follows the same explicit mapping pattern as toCategory, maintaining consistency across the codebase.


56-67: LGTM!

Task list response parsing is correct and follows the established pattern.


69-76: LGTM!

Task detail response parsing is clean and consistent with the category pattern.


83-90: LGTM!

The listTasksByCategory implementation correctly scopes tasks to a category with proper authentication and response transformation.


97-104: LGTM!

The getTask implementation is straightforward and correct.


114-134: LGTM!

The createTask implementation correctly handles optional fields (description defaults to null, isChecked is conditionally included) and constructs a well-formed POST request.

@gentamura gentamura changed the title feat: add category and task create commands feat: add categories and tasks commands Oct 22, 2025
@gentamura gentamura self-assigned this Oct 23, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/index.ts (1)

3-20: Catch environment validation errors inside the existing CLI error handler

checkEnv() をトップレベルで呼び出すと、環境変数が欠けている場合に Commander 側の try/catch に到達する前にモジュールロードで例外が投げられ、スタックトレース付きで CLI が終了してしまいます (listee --help のような軽量コマンドでも同様)。main の中で既存のエラーハンドリングに包んで実行すれば、整形済みメッセージと exit code だけを返せるので UX が大きく改善します。

-import { registerCategoryCommand } from "./commands/categories.js";
-import { registerTaskCommand } from "./commands/tasks.js";
-import { checkEnv } from "./env.js";
-
-checkEnv();
+import { registerCategoryCommand } from "./commands/categories.js";
+import { registerTaskCommand } from "./commands/tasks.js";
+import { checkEnv } from "./env.js";
 
 const program = new Command();
 
 ...
 const main = async (): Promise<void> => {
   try {
+    checkEnv();
     await program.parseAsync(process.argv);
   } catch (error) {
🧹 Nitpick comments (7)
.env.example (1)

1-4: Maintain .env key ordering for lint compliance

.env.example のキー順が dotenv-linter の警告対象になっているので、LISTEE_API_URLSUPABASE_PUBLISHABLE_KEY より前に移動しておきましょう。サンプルを辞書順で揃えておけば、開発者がそのままコピーしても検証ツールでノイズが出ません。

 SUPABASE_URL=
-SUPABASE_PUBLISHABLE_KEY=
-LISTEE_API_URL=
+LISTEE_API_URL=
+SUPABASE_PUBLISHABLE_KEY=
 # LISTEE_CLI_KEYCHAIN_SERVICE=listee-cli
src/services/api-client.ts (3)

10-12: Consider refining the type guard to exclude arrays.

The isRecord type guard currently accepts arrays since typeof [] === "object". While this doesn't cause immediate issues in the current usage (extracting error messages), it's less precise than necessary.

Apply this diff to exclude arrays:

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

60-72: Consider simplifying the return type.

The authorizationValue field is set to the same value as accessToken, which appears redundant. If the fields serve the same purpose, consider removing one to simplify the context object.

If authorizationValue is intended for future flexibility (e.g., supporting different token formats), the current implementation is fine. Otherwise:

 type AuthenticatedContext = {
   readonly accessToken: string;
   readonly userId: string;
-  readonly authorizationValue: string;
 };

And update callers to use accessToken directly.


117-144: Consider adding timeout handling for fetch requests.

The fetch call doesn't specify a timeout, which could lead to indefinitely hanging requests if the API becomes unresponsive. For better user experience in a CLI context, consider adding timeout handling.

Example using AbortSignal with timeout:

export const requestJson = async (
  path: string,
  authorizationValue: string,
  init?: RequestInit,
  timeoutMs = 30000,
): Promise<unknown> => {
  const url = buildUrl(path);
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
  
  try {
    const response = await fetch(url, {
      ...init,
      signal: controller.signal,
      headers: {
        ...buildHeaders(authorizationValue),
        ...(init?.headers ?? {}),
      },
    });
    
    // ... rest of the function
  } finally {
    clearTimeout(timeoutId);
  }
};
src/services/category-api.ts (3)

112-128: Consider validating the limit parameter.

The limit parameter is passed to the API without client-side validation. Adding basic validation (e.g., positive integer) would improve user experience by catching invalid input before making the API call.

Example validation:

export const listCategories = async (
  params: ListCategoriesParams = {},
): Promise<ListCategoriesResult> => {
  if (params.limit !== undefined && params.limit <= 0) {
    throw new Error("limit must be a positive number.");
  }
  // ... rest of function
};

150-167: Consider validating the name parameter.

The name parameter is not validated before sending to the API. Adding basic validation (e.g., non-empty, not just whitespace) would provide better user feedback.

Example validation:

export const createCategory = async (
  params: CreateCategoryParams,
): Promise<Category> => {
  if (params.name.trim().length === 0) {
    throw new Error("Category name cannot be empty.");
  }
  // ... rest of function
};

175-193: Consider validating the name parameter.

While the function correctly validates that at least one field is provided, it doesn't validate the name value itself. Adding validation for empty or whitespace-only names would improve error handling.

Example validation:

export const updateCategory = async (
  params: UpdateCategoryParams,
): Promise<Category> => {
  const context = await createAuthenticatedContext(params.email);
  const path = `/categories/${encodeURIComponent(params.categoryId)}`;
  if (params.name === undefined) {
    throw new Error("No category fields were provided for update.");
  }
  if (params.name.trim().length === 0) {
    throw new Error("Category name cannot be empty.");
  }
  // ... rest of function
};
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 181df4b and 28bb5b5.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (13)
  • .env.example (1 hunks)
  • AGENTS.md (1 hunks)
  • README.md (4 hunks)
  • package.json (1 hunks)
  • src/commands/categories.ts (1 hunks)
  • src/commands/tasks.ts (1 hunks)
  • src/env.ts (1 hunks)
  • src/index.ts (2 hunks)
  • src/services/api-client.ts (1 hunks)
  • src/services/auth-service.test.ts (3 hunks)
  • src/services/auth-service.ts (3 hunks)
  • src/services/category-api.ts (1 hunks)
  • src/services/task-api.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/services/task-api.ts
🧰 Additional context used
📓 Path-based instructions (5)
src/services/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

src/services/**/*.ts: Place Supabase-facing logic under src/services/
Read SUPABASE_URL and SUPABASE_ANON_KEY from environment variables or .env; never hardcode credentials
Default Keytar service name to "listee-cli"; allow overrides via env vars or CLI flags

Files:

  • src/services/auth-service.test.ts
  • src/services/category-api.ts
  • src/services/api-client.ts
  • src/services/auth-service.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use TypeScript strict practices: forbid any, forbid as assertions, no implicit any; implement type guards for narrowing
Use two-space indentation and LF line endings
Use camelCase for identifiers
Keep comments purposeful; add brief context only for non-trivial flows
Maintain ASCII in files unless the file already uses Unicode

Files:

  • src/services/auth-service.test.ts
  • src/services/category-api.ts
  • src/commands/tasks.ts
  • src/services/api-client.ts
  • src/env.ts
  • src/index.ts
  • src/services/auth-service.ts
  • src/commands/categories.ts
src/**/[a-z0-9-]*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use kebab-case filenames for modules under src/

Files:

  • src/services/auth-service.test.ts
  • src/services/category-api.ts
  • src/commands/tasks.ts
  • src/services/api-client.ts
  • src/env.ts
  • src/index.ts
  • src/services/auth-service.ts
  • src/commands/categories.ts
src/commands/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Place CLI command handlers under src/commands/

Files:

  • src/commands/tasks.ts
  • src/commands/categories.ts
src/index.ts

📄 CodeRabbit inference engine (AGENTS.md)

Keep CLI wiring in src/index.ts using Commander; avoid moving or duplicating the entry wiring

Files:

  • src/index.ts
🧠 Learnings (7)
📚 Learning: 2025-10-07T13:26:37.124Z
Learnt from: CR
Repo: listee-dev/listee-cli PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-07T13:26:37.124Z
Learning: Applies to src/services/**/*.ts : Read SUPABASE_URL and SUPABASE_ANON_KEY from environment variables or .env; never hardcode credentials

Applied to files:

  • src/services/auth-service.test.ts
  • README.md
  • AGENTS.md
  • src/env.ts
  • src/services/auth-service.ts
  • .env.example
📚 Learning: 2025-10-07T13:26:37.124Z
Learnt from: CR
Repo: listee-dev/listee-cli PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-07T13:26:37.124Z
Learning: Use Bun 1.2.22 and the provided Bun commands; do not switch package managers

Applied to files:

  • README.md
📚 Learning: 2025-10-07T13:26:37.124Z
Learnt from: CR
Repo: listee-dev/listee-cli PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-07T13:26:37.124Z
Learning: Applies to src/services/**/*.ts : Default Keytar service name to "listee-cli"; allow overrides via env vars or CLI flags

Applied to files:

  • README.md
  • AGENTS.md
  • src/services/auth-service.ts
  • .env.example
📚 Learning: 2025-10-07T13:26:37.124Z
Learnt from: CR
Repo: listee-dev/listee-cli PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-07T13:26:37.124Z
Learning: Applies to src/commands/**/*.ts : Place CLI command handlers under src/commands/

Applied to files:

  • README.md
  • src/commands/tasks.ts
  • src/index.ts
  • src/commands/categories.ts
📚 Learning: 2025-10-07T13:26:37.124Z
Learnt from: CR
Repo: listee-dev/listee-cli PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-07T13:26:37.124Z
Learning: Applies to src/index.ts : Keep CLI wiring in src/index.ts using Commander; avoid moving or duplicating the entry wiring

Applied to files:

  • README.md
  • src/commands/tasks.ts
  • src/index.ts
  • src/commands/categories.ts
📚 Learning: 2025-10-07T13:26:37.124Z
Learnt from: CR
Repo: listee-dev/listee-cli PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-07T13:26:37.124Z
Learning: Applies to **/*.{ts,tsx} : Keep comments purposeful; add brief context only for non-trivial flows

Applied to files:

  • AGENTS.md
📚 Learning: 2025-10-07T13:26:37.124Z
Learnt from: CR
Repo: listee-dev/listee-cli PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-07T13:26:37.124Z
Learning: Applies to src/services/**/*.ts : Place Supabase-facing logic under src/services/

Applied to files:

  • src/services/auth-service.ts
🧬 Code graph analysis (7)
src/services/auth-service.test.ts (3)
src/env.ts (1)
  • resetEnvCache (85-87)
src/types/auth.ts (1)
  • AccessTokenResult (21-25)
src/services/auth-service.ts (1)
  • toAuthenticatedAccessTokenResult (490-507)
src/services/category-api.ts (1)
src/services/api-client.ts (2)
  • createAuthenticatedContext (60-72)
  • requestJson (117-144)
src/commands/tasks.ts (1)
src/services/task-api.ts (5)
  • listTasksByCategory (83-90)
  • getTask (97-104)
  • createTask (114-134)
  • updateTask (144-168)
  • deleteTask (175-181)
src/services/api-client.ts (2)
src/env.ts (2)
  • getEnv (81-83)
  • EnvValidationError (10-17)
src/services/auth-service.ts (1)
  • getAuthenticatedAccessToken (509-514)
src/index.ts (3)
src/env.ts (1)
  • checkEnv (107-121)
src/commands/categories.ts (1)
  • registerCategoryCommand (80-208)
src/commands/tasks.ts (1)
  • registerTaskCommand (77-245)
src/services/auth-service.ts (2)
src/env.ts (3)
  • getEnv (81-83)
  • EnvValidationError (10-17)
  • checkEnv (107-121)
src/types/auth.ts (1)
  • AccessTokenResult (21-25)
src/commands/categories.ts (1)
src/services/category-api.ts (5)
  • listCategories (112-128)
  • getCategory (135-142)
  • createCategory (150-167)
  • updateCategory (175-193)
  • deleteCategory (200-208)
🪛 dotenv-linter (4.0.0)
.env.example

[warning] 3-3: [UnorderedKey] The LISTEE_API_URL key should go before the SUPABASE_PUBLISHABLE_KEY key

(UnorderedKey)

🔇 Additional comments (10)
src/services/api-client.ts (3)

14-34: LGTM!

The error handling provides clear, user-friendly messages when LISTEE_API_URL is missing or invalid, and properly re-throws unrelated errors.


41-58: LGTM!

The payload parsing logic correctly handles JSON, text, and empty responses with robust error handling for JSON parse failures.


91-115: LGTM!

The error extraction logic properly handles JSON, text, and empty payloads with sensible fallbacks and response truncation for large text bodies.

src/services/category-api.ts (7)

1-7: LGTM!

The Category type correctly transforms the database type for API usage by converting timestamp fields to strings.


9-19: LGTM!

Response types are well-structured with proper readonly modifiers and clear pagination metadata.


25-39: LGTM!

The isCategory type guard thoroughly validates all required category fields with proper type checks.


41-61: LGTM!

The conversion functions properly validate and transform API responses with clear error messages.


63-102: LGTM!

Response parsing functions include thorough validation with descriptive error messages for malformed API responses.


130-142: LGTM!

The getCategory function properly encodes the category ID and handles the response.


195-208: LGTM!

The deleteCategory function properly implements the DELETE operation with correct return type and URL encoding.

Comment on lines 81 to 89
const buildUrl = (path: string): URL => {
if (!path.startsWith("/")) {
throw new Error("API path must start with '/' .");
}
const base = getApiBaseUrl();
const baseHref = base.href.endsWith("/") ? base.href : `${base.href}/`;
const normalizedPath = path.replace(/^\//u, "");
return new URL(normalizedPath, baseHref);
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Minor typo in error message.

Line 83 has a space before the period in the error message.

Apply this diff:

   if (!path.startsWith("/")) {
-    throw new Error("API path must start with '/' .");
+    throw new Error("API path must start with '/'.");
   }
🤖 Prompt for AI Agents
In src/services/api-client.ts around lines 81 to 89, the thrown Error message on
line 83 contains an extra space before the period ("API path must start with '/'
."); remove the stray space so the message reads "API path must start with '/'."
to fix the minor typo.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/services/api-base.ts (1)

53-63: Handle +json media types as JSON.

Many APIs (including RFC 7807 problem details) return payloads with vendor-specific application/*+json types. Right now those responses fall through to the text branch, losing structured error details. Consider recognising +json suffixes alongside application/json so we still parse them as JSON.

-  if (contentType.toLowerCase().includes("application/json")) {
+  const normalizedContentType = contentType.toLowerCase();
+  if (
+    normalizedContentType.includes("application/json") ||
+    normalizedContentType.includes("+json")
+  ) {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 28bb5b5 and 6fcd9d0.

📒 Files selected for processing (10)
  • .env.example (1 hunks)
  • AGENTS.md (2 hunks)
  • README.md (4 hunks)
  • src/commands/auth.ts (8 hunks)
  • src/env.ts (1 hunks)
  • src/services/api-base.ts (1 hunks)
  • src/services/api-client.ts (1 hunks)
  • src/services/auth-service.test.ts (2 hunks)
  • src/services/auth-service.ts (9 hunks)
  • src/types/auth.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • .env.example
  • src/env.ts
  • README.md
  • src/services/api-client.ts
  • src/commands/auth.ts
🧰 Additional context used
📓 Path-based instructions (3)
src/services/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

src/services/**/*.ts: Place Supabase-facing logic under src/services/
Read SUPABASE_URL and SUPABASE_ANON_KEY from environment variables or .env; never hardcode credentials
Default Keytar service name to "listee-cli"; allow overrides via env vars or CLI flags

Files:

  • src/services/auth-service.ts
  • src/services/api-base.ts
  • src/services/auth-service.test.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use TypeScript strict practices: forbid any, forbid as assertions, no implicit any; implement type guards for narrowing
Use two-space indentation and LF line endings
Use camelCase for identifiers
Keep comments purposeful; add brief context only for non-trivial flows
Maintain ASCII in files unless the file already uses Unicode

Files:

  • src/services/auth-service.ts
  • src/types/auth.ts
  • src/services/api-base.ts
  • src/services/auth-service.test.ts
src/**/[a-z0-9-]*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use kebab-case filenames for modules under src/

Files:

  • src/services/auth-service.ts
  • src/types/auth.ts
  • src/services/api-base.ts
  • src/services/auth-service.test.ts
🧠 Learnings (7)
📓 Common learnings
Learnt from: CR
Repo: listee-dev/listee-cli PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-07T13:26:37.124Z
Learning: Applies to src/services/**/*.ts : Place Supabase-facing logic under src/services/
📚 Learning: 2025-10-07T13:26:37.124Z
Learnt from: CR
Repo: listee-dev/listee-cli PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-07T13:26:37.124Z
Learning: Applies to src/services/**/*.ts : Read SUPABASE_URL and SUPABASE_ANON_KEY from environment variables or .env; never hardcode credentials

Applied to files:

  • src/services/auth-service.ts
  • src/types/auth.ts
  • src/services/api-base.ts
  • AGENTS.md
  • src/services/auth-service.test.ts
📚 Learning: 2025-10-07T13:26:37.124Z
Learnt from: CR
Repo: listee-dev/listee-cli PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-07T13:26:37.124Z
Learning: Applies to src/services/**/*.ts : Place Supabase-facing logic under src/services/

Applied to files:

  • src/services/auth-service.ts
  • AGENTS.md
  • src/services/auth-service.test.ts
📚 Learning: 2025-10-07T13:26:37.124Z
Learnt from: CR
Repo: listee-dev/listee-cli PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-07T13:26:37.124Z
Learning: Follow Listee org conventions from sibling repos and keep changes minimal and well-justified

Applied to files:

  • AGENTS.md
📚 Learning: 2025-10-07T13:26:37.124Z
Learnt from: CR
Repo: listee-dev/listee-cli PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-07T13:26:37.124Z
Learning: Applies to src/index.ts : Keep CLI wiring in src/index.ts using Commander; avoid moving or duplicating the entry wiring

Applied to files:

  • AGENTS.md
📚 Learning: 2025-10-07T13:26:37.124Z
Learnt from: CR
Repo: listee-dev/listee-cli PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-07T13:26:37.124Z
Learning: Applies to src/commands/**/*.ts : Place CLI command handlers under src/commands/

Applied to files:

  • AGENTS.md
📚 Learning: 2025-10-07T13:26:37.124Z
Learnt from: CR
Repo: listee-dev/listee-cli PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-07T13:26:37.124Z
Learning: Applies to src/services/**/*.ts : Default Keytar service name to "listee-cli"; allow overrides via env vars or CLI flags

Applied to files:

  • AGENTS.md
🧬 Code graph analysis (3)
src/services/auth-service.ts (3)
src/types/auth.ts (2)
  • AuthTokenResponse (3-8)
  • AccessTokenResult (15-19)
src/env.ts (2)
  • checkEnv (97-111)
  • getEnv (79-81)
src/services/api-base.ts (3)
  • buildListeeApiUrl (34-42)
  • readApiPayload (44-64)
  • extractApiErrorMessage (66-90)
src/services/api-base.ts (1)
src/env.ts (2)
  • getEnv (79-81)
  • EnvValidationError (10-17)
src/services/auth-service.test.ts (3)
src/env.ts (1)
  • resetEnvCache (83-85)
src/services/auth-service.ts (2)
  • ensureListeeApiConfig (82-85)
  • toAuthenticatedAccessTokenResult (388-405)
src/types/auth.ts (1)
  • AccessTokenResult (15-19)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/services/auth-service.ts (1)

170-199: Separate expiration validation from structural type narrowing.

The expiration check (lines 193-196) inside isAuthTokenPayload mixes concerns. Type guards should verify shape and required fields, not enforce business rules like expiration. When a structurally valid but expired token fails this check, decodeAuthToken throws "Access token payload structure is invalid" (line 204), which is misleading.

Consider extracting the expiration check into a separate validation function:

 const isAuthTokenPayload = (payload: unknown): payload is AuthTokenClaims => {
   if (!isRecord(payload)) {
     return false;
   }
 
   const subValue = "sub" in payload ? payload.sub : undefined;
   const emailValue = "email" in payload ? payload.email : undefined;
   const expValue = "exp" in payload ? payload.exp : undefined;
   const iatValue = "iat" in payload ? payload.iat : undefined;
 
-  if (
-    !isString(subValue) ||
-    subValue.trim().length === 0 ||
-    !isString(emailValue) ||
-    emailValue.trim().length === 0 ||
-    !isNumber(expValue) ||
-    expValue <= 0 ||
-    !isNumber(iatValue) ||
-    iatValue <= 0
-  ) {
-    return false;
-  }
-
-  const currentEpochSeconds = Math.floor(Date.now() / 1000);
-  if (expValue <= currentEpochSeconds) {
-    return false;
-  }
-
-  return true;
+  return (
+    isString(subValue) &&
+    subValue.trim().length > 0 &&
+    isString(emailValue) &&
+    emailValue.trim().length > 0 &&
+    isNumber(expValue) &&
+    expValue > 0 &&
+    isNumber(iatValue) &&
+    iatValue > 0
+  );
 };
+
+const isTokenExpired = (payload: AuthTokenClaims): boolean => {
+  const currentEpochSeconds = Math.floor(Date.now() / 1000);
+  return payload.exp <= currentEpochSeconds;
+};

Then update decodeAuthToken:

const decodeAuthToken = (token: string): AuthTokenClaims => {
  const payload = decodeJwtPayload(token);
  if (!isAuthTokenPayload(payload)) {
    throw new Error("Access token payload structure is invalid.");
  }
  
  if (isTokenExpired(payload)) {
    throw new Error("Access token has expired.");
  }

  return payload;
};
🧹 Nitpick comments (1)
src/services/auth-service.ts (1)

82-85: Consider clarifying the validation intent.

The void getEnv().LISTEE_API_URL pattern on line 84 works because getEnv() will throw if the environment is invalid, but the intent isn't immediately obvious.

Consider adding a brief comment to clarify:

 export const ensureListeeApiConfig = (): void => {
   checkEnv();
+  // Ensures LISTEE_API_URL is present; getEnv() throws if validation fails
   void getEnv().LISTEE_API_URL;
 };

Alternatively, make the validation more explicit:

 export const ensureListeeApiConfig = (): void => {
   checkEnv();
-  void getEnv().LISTEE_API_URL;
+  const env = getEnv();
+  if (!env.LISTEE_API_URL) {
+    throw new Error("LISTEE_API_URL is required");
+  }
 };
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6fcd9d0 and bd6abd2.

📒 Files selected for processing (1)
  • src/services/auth-service.ts (9 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
src/services/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

src/services/**/*.ts: Place Supabase-facing logic under src/services/
Read SUPABASE_URL and SUPABASE_ANON_KEY from environment variables or .env; never hardcode credentials
Default Keytar service name to "listee-cli"; allow overrides via env vars or CLI flags

Files:

  • src/services/auth-service.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use TypeScript strict practices: forbid any, forbid as assertions, no implicit any; implement type guards for narrowing
Use two-space indentation and LF line endings
Use camelCase for identifiers
Keep comments purposeful; add brief context only for non-trivial flows
Maintain ASCII in files unless the file already uses Unicode

Files:

  • src/services/auth-service.ts
src/**/[a-z0-9-]*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use kebab-case filenames for modules under src/

Files:

  • src/services/auth-service.ts
🧠 Learnings (3)
📓 Common learnings
Learnt from: CR
Repo: listee-dev/listee-cli PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-07T13:26:37.124Z
Learning: Applies to src/services/**/*.ts : Place Supabase-facing logic under src/services/
📚 Learning: 2025-10-07T13:26:37.124Z
Learnt from: CR
Repo: listee-dev/listee-cli PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-07T13:26:37.124Z
Learning: Applies to src/services/**/*.ts : Read SUPABASE_URL and SUPABASE_ANON_KEY from environment variables or .env; never hardcode credentials

Applied to files:

  • src/services/auth-service.ts
📚 Learning: 2025-10-07T13:26:37.124Z
Learnt from: CR
Repo: listee-dev/listee-cli PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-07T13:26:37.124Z
Learning: Applies to src/services/**/*.ts : Place Supabase-facing logic under src/services/

Applied to files:

  • src/services/auth-service.ts
🧬 Code graph analysis (1)
src/services/auth-service.ts (3)
src/types/auth.ts (2)
  • AuthTokenResponse (3-8)
  • AccessTokenResult (15-19)
src/env.ts (2)
  • checkEnv (97-111)
  • getEnv (79-81)
src/services/api-base.ts (3)
  • buildListeeApiUrl (34-42)
  • readApiPayload (44-64)
  • extractApiErrorMessage (66-90)
🔇 Additional comments (2)
src/services/auth-service.ts (2)

92-123: Past review comment has been addressed.

The implementation now correctly handles empty success responses (lines 113-114), returning null instead of throwing an error. This allows auth endpoints that respond with 204 No Content to succeed as expected.


387-404: LGTM!

The function correctly decodes the access token, extracts the user ID, and enriches the token result with authentication context. The defensive trimming and empty-check (lines 390-392) are appropriate safeguards.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/commands/tasks.ts (1)

140-143: Consider normalizing empty descriptions to undefined.

After trimming, an empty string is passed to the API, which will store it as-is rather than null. For data consistency, you might want to treat empty trimmed descriptions the same as undefined.

 const description =
   options.description === undefined
     ? undefined
-    : options.description.trim();
+    : options.description.trim() || undefined;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bd6abd2 and e6392ee.

📒 Files selected for processing (2)
  • src/commands/categories.ts (1 hunks)
  • src/commands/tasks.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/commands/categories.ts
🧰 Additional context used
📓 Path-based instructions (3)
src/commands/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Place CLI command handlers under src/commands/

Files:

  • src/commands/tasks.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use TypeScript strict practices: forbid any, forbid as assertions, no implicit any; implement type guards for narrowing
Use two-space indentation and LF line endings
Use camelCase for identifiers
Keep comments purposeful; add brief context only for non-trivial flows
Maintain ASCII in files unless the file already uses Unicode

Files:

  • src/commands/tasks.ts
src/**/[a-z0-9-]*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use kebab-case filenames for modules under src/

Files:

  • src/commands/tasks.ts
🧠 Learnings (2)
📚 Learning: 2025-10-07T13:26:37.124Z
Learnt from: CR
Repo: listee-dev/listee-cli PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-07T13:26:37.124Z
Learning: Applies to src/index.ts : Keep CLI wiring in src/index.ts using Commander; avoid moving or duplicating the entry wiring

Applied to files:

  • src/commands/tasks.ts
📚 Learning: 2025-10-07T13:26:37.124Z
Learnt from: CR
Repo: listee-dev/listee-cli PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-10-07T13:26:37.124Z
Learning: Applies to src/commands/**/*.ts : Place CLI command handlers under src/commands/

Applied to files:

  • src/commands/tasks.ts
🧬 Code graph analysis (1)
src/commands/tasks.ts (1)
src/services/task-api.ts (5)
  • listTasksByCategory (83-90)
  • getTask (97-104)
  • createTask (114-134)
  • updateTask (144-168)
  • deleteTask (175-181)
🔇 Additional comments (4)
src/commands/tasks.ts (4)

96-96: Previous validation concern has been addressed.

The earlier review flagged that options.category should be validated before use. Line 96 now correctly applies ensureNonEmptyString to guard against empty category IDs.


112-112: Previous validation concern has been addressed.

The earlier review flagged that taskId should be validated before use. Line 112 now correctly applies ensureNonEmptyString to guard against empty task IDs.


157-234: Excellent input validation and conflict detection.

The update command properly validates all inputs, detects conflicting flags (--checked/--unchecked, --description/--clear-description), and ensures at least one update field is provided. The error messages are clear and actionable.


10-23: Clean error handling pattern.

The execute wrapper provides consistent error formatting and exit code handling across all commands. Setting process.exitCode rather than calling process.exit() allows the action handler to complete gracefully while ensuring the process exits with the correct code.

@gentamura gentamura merged commit 313cb87 into main Nov 12, 2025
4 checks passed
@gentamura gentamura deleted the feat/categories-tasks branch November 12, 2025 05:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant