Skip to content

refactor: command-mode architecture, Firebase googleapis migration, and task-based install flow#22

Merged
pitzcarraldo merged 30 commits into
mainfrom
feat/refactor-firebase-apis
Feb 24, 2026
Merged

refactor: command-mode architecture, Firebase googleapis migration, and task-based install flow#22
pitzcarraldo merged 30 commits into
mainfrom
feat/refactor-firebase-apis

Conversation

@pitzcarraldo

@pitzcarraldo pitzcarraldo commented Feb 6, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Remove interactive chat mode and migrate CLI to command-only architecture
  • Replace Firebase/IAM REST API clients with @googleapis/firebase and @googleapis/iam
  • Replace setup wizards with sequential task-based install preparation flow
  • Add mcp and skills commands, simplify doctor with pre-check verification

Key Changes

Architecture: Command-Mode Only

  • Remove ChatApp, session management, slash commands, and interactive skills
  • clix (no args) now shows help and exits
  • install and doctor use agent handoff pattern (preparation → launch agent CLI)

Firebase API Migration

  • Replace custom REST clients with @googleapis/firebase and @googleapis/iam packages
  • Reduce API client code by ~57% (1,042 → 448 lines)
  • Add service account management and sender config support
  • Improve OAuth token handling with proper invalid_grant vs transient error classification

Install Preparation Flow

  • Replace FirebaseWizard + IosSetupFlow with sequential task components
  • Enforced task order: Firebase Config → Service Account → APNS Key → iOS Entitlements → NSE
  • Each task is a focused, reusable component under src/ui/components/

New Commands

  • clix mcp — install Clix MCP Server (renamed from install-mcp, removed agent parameter)
  • clix skills — install Clix skill package via skills CLI

Doctor Pre-Check

  • Run pre-verification (Firebase, APNS, iOS entitlements, NSE) before agent handoff
  • Display interactive status UI summarizing verified items

Code Quality (Review Fixes)

  • 3 critical bug fixes (waitForOperation null guard, install retry reset, token error classification)
  • Extract shared utilities (firebase-detection-utils, BaseExecutor.extractTextDelta, createDefaultOpenCodeEnv)
  • Remove dead types, unused exports, and stale test fixtures
  • Use AgentError consistently instead of generic Error in command modules
  • Prevent double addTargetDependency in pbxproj modifier

Stats

179 files changed, ~14.4K insertions, ~17.1K deletions (net -2.7K lines)

How to Validate

```bash
bun install
bun run check # lint + typecheck (0 warnings, 0 errors)
bun test # 473 unit tests pass
bun run build && bun test tests/e2e/ # 18 E2E tests pass
```

Manual smoke tests:

  • `bun run dev` → shows help
  • `bun run dev install` → runs preparation UI
  • `bun run dev doctor` → runs pre-check + agent handoff
  • `bun run dev agent` → lists available agents
  • `bun run dev mcp` → launches MCP installer

Summary by CodeRabbit

  • New Features

    • New clix install command with pre-flight setup verification and preparation workflow.
    • New clix doctor command for diagnosing project configuration status.
    • Enhanced Firebase setup with GCP project linking and service account import.
    • Android package name auto-detection for multi-platform projects.
  • Bug Fixes

    • Improved error messages for iOS certificate and bundle ID creation limits.
    • OAuth callback cancellation support with proper state cleanup.
  • Documentation

    • Updated CLI documentation and help text to reflect command-first workflows.
    • Simplified README and agent guidance for streamlined onboarding.

- Add IAM API client for Service Account operations (list, create, download key)
- Add GCP project API for listing available projects and adding Firebase
- Extend OAuth scopes to include iam and cloud-platform permissions
- Create Zod-based Service Account JSON validator with real-time feedback
- Extend FirebaseDownloader with SA management methods
- Add new wizard phases: no_projects, service_account_menu, paste_json
- Support JSON paste with real-time validation and error display
- Save service account keys to .clix/service-account.json
Replace direct Firebase Management and IAM REST API implementations with
@googleapis/firebase and @googleapis/iam packages. This reduces code by 57%
while maintaining full backward compatibility through existing public APIs.

Key improvements:
- Reduced firebase-api.ts from 366 to 223 lines (-39%)
- Reduced iam-api.ts from 219 to 126 lines (-42%)
- Removed duplicate pagination and token handling logic
- Type safety improved with generated types from googleapis
- Automatic token refresh via OAuth2Client
- Cleaner error handling with googleapis's built-in support

All 588 unit tests and 20 E2E tests pass.
@coderabbitai

coderabbitai Bot commented Feb 6, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This PR comprehensively transforms the Clix CLI from an interactive chat-driven architecture to a command-first model. It removes chat, debug, firebase, iOS setup, and resume commands while introducing new command-based skills (install, doctor) and external tool integrations (mcp, skills). Firebase integration now uses the Google APIs library with enhanced OAuth and service account handling. A preparation context system tracks project readiness. Embedded skill metadata is replaced with local command prompts. The CLI dispatches directly to agent processes instead of rendering interactive UI.

Changes

Cohort / File(s) Summary
CLI Entry & Command Dispatch
src/cli.tsx, src/commands/chat.tsx, src/commands/debug.tsx, src/commands/install.tsx, src/commands/mcp.ts, src/commands/skills.ts, src/commands/doctor.tsx
Replaced chat-based interactive flow with command-mode dispatch. Removed chat, debug, firebase, ios-setup, resume, install-mcp commands. Added new mcp and skills commands that delegate to external CLIs via handoff. Refactored install to invoke skillCommand with action routing.
Skill System Refactoring
src/lib/skills.ts, src/commands/skill/index.tsx, src/commands/skill/preparation.ts, src/commands/skill/__tests__/preparation.test.ts, src/lib/__tests__/skills.test.ts
Transitioned from embedded skill metadata to local-only skill model. Introduced PreparationContext with Firebase/iOS/APNS status checks. Added preparation.ts module for non-interactive project readiness verification. Updated LOCAL_SKILLS to include install and doctor with public visibility. Removed EMBEDDED_SKILL_METADATA exports and dynamic skill registration.
Firebase Integration
src/lib/services/firebase/api/firebase-api.ts, src/lib/services/firebase/api/types.ts, src/lib/services/firebase/api/index.ts, src/lib/services/firebase/downloader.ts, src/lib/services/firebase/oauth/auth-client.ts, src/lib/services/firebase/oauth/config.ts, src/lib/services/firebase/service-account-validator.ts, src/lib/services/firebase/types.ts, src/lib/services/firebase/index.ts
Replaced REST-based Firebase client with @googleapis/firebase library. Added service account JSON validation and persistence. Enhanced OAuth with logging and cancellation support. Created service-account-validator module with comprehensive validation schemas. Updated API types to remove response interfaces in favor of library types.
Project Configuration & Setup Status
src/lib/config/project-config-schema.ts, src/lib/config/project-config-manager.ts, src/lib/config/index.ts
Added setup status tracking (Firebase, iOS, APNS) to project configuration. Introduced migration-aware config loading with automatic persistence. Added updateSetup method to ProjectConfigManager. Exported setup-related schemas and types for public API access.
Agent Handoff & Service Integration
src/lib/services/agent-handoff.ts, src/lib/services/organization-projects.ts, src/lib/services/agent-selection-service.ts, src/lib/services/mcp-install-service.ts
Introduced agent handoff system to invoke external CLI processes with environment and argument customization. Added concurrent organization/project fetching. Simplified agent selection to config and auto modes. Consolidated MCP installation to use unified add-mcp flow.
iOS Setup Removal & Improvements
src/commands/ios-setup/index.tsx, src/commands/firebase.tsx, src/lib/commands/firebase.tsx, src/lib/commands/ios-setup.tsx, src/lib/ios/extension-generator.ts, src/lib/ios/pbxproj-modifier.ts, src/lib/ios/podfile-modifier.ts, src/lib/ios/apple-auth.ts, src/lib/ios/apple-portal.ts
Removed interactive iOS setup command implementation. Enhanced extension generation with NotificationService Swift patching and project ID injection. Expanded pbxproj modifier with strict-safe APIs for NSE creation and entitlements linking. Improved APNS key handling and Apple Portal bundle ID detection with better error messages.
UI Components & Installation Flow
src/ui/components/InstallPreparationUI.tsx, src/ui/components/FirebaseWizard.tsx, src/ui/components/ProjectSelector.tsx, src/ui/chat/ChatApp.tsx, src/ui/chat/hooks/useCommandHandler.ts, src/ui/chat/hooks/useMessageSending.ts, src/ui/chat/hooks/useOverlays.ts, src/ui/LoginUI.tsx, src/ui/SetupUI.tsx
Added InstallPreparationUI for multi-phase pre-installation workflow. Enhanced FirebaseWizard with GCP project linking and service account setup flows. Implemented ProjectSelector with search, sorting, and filtering. Removed interactive chat, login, and logout UIs. Wired install-preparation overlay into chat command handler. Updated data fetching to use concurrent organization-projects retrieval.
Executor & Stream Processing
src/lib/executors/base-executor.ts, src/lib/executors/stream-delta.ts, src/lib/executors/*.ts (claude, cursor, gemini, opencode, copilot, codex), src/lib/executor.ts, src/lib/utils/stream-text.ts
Removed session/history persistence and compaction from executor lifecycle. Added stream delta computation for append-vs-replace text handling. Introduced environment override hook (getSpawnEnv) for permission/config injection. Simplified execution to stateless per-prompt model. Added streamMode field to AgentMessage. Removed history-related public methods from AgentExecutor interface.
Command Registry & Type System Removal
src/lib/commands/registry.ts, src/lib/commands/types.ts, src/lib/commands/index.ts, src/lib/commands/*.ts (agent, compact, exit, help, login, logout, new, resume, transfer, uninstall, update, whoami)`
Removed entire command registry system that managed static/dynamic commands. Deleted all local-command type definitions and type guards. Removed individual command files for chat-mode slash commands. Eliminated skill command factory that generated commands from embedded metadata.
Build & Embedding
scripts/embed-skills.ts, scripts/build.ts, src/lib/embedded-skills.ts
Updated embed script to only embed local command prompts instead of external package metadata. Removed SkillMetadata interface and metadata-based accessors. Changed EMBEDDED_SKILLS to map local-prefixed keys to prompt content only. Updated build process to reference local prompts instead of skills package.
Session & History Removal
src/lib/services/session-store.ts, src/lib/services/history-compaction.ts, src/lib/services/transfer-service.ts
Deleted session persistence, history compaction, and transfer service. Removed all chat session storage, serialization, and enumeration logic. Eliminated history compression and conversation memory management. Removed agent transfer/handoff via markdown history files.
Package & Dependencies
package.json
Added @googleapis/firebase dependency. Removed @clix-so/clix-agent-skills and open dependencies. Added build:local script.
Documentation & Configuration
README.md, llms.txt, AGENTS.md, .gitignore, src/lib/constants/system-prompt.ts, src/lib/skills/install/SKILL.md, src/lib/skills/doctor/SKILL.md, .github/ISSUE_TEMPLATE/...
Rewrote README and llms.txt for command-first narrative focused on clix install, clix doctor, clix mcp, clix skills. Updated system prompt slash command descriptions. Revised install and doctor skill prompts to reflect preparation-context-aware workflows. Updated issue templates to reference new commands instead of integration. Added conditional workflow documentation to AGENTS.md.
API & Type Exports
src/lib/api/internal-client.ts, src/lib/api/types.ts, src/lib/api/index.ts, src/lib/auth/credentials.ts, src/lib/debug/logger.ts, src/lib/utils/oauth.ts
Extended InternalApiClient with retry logic, timeout control, and new methods (getProject, createOrUpdateSenderConfig). Added sender config types and AppPushSenderConfig. Added OAuth logger export and file-write capability. Enhanced OAuthCallbackServer with cancellation support. Improved credentials clearing to preserve partial state.
Testing Infrastructure
src/commands/__tests__/mcp.test.ts, src/commands/__tests__/skills.test.ts, src/lib/__tests__/embedded-skills.test.ts, src/lib/__tests__/skills.test.ts, src/lib/commands/__tests__/registry.test.ts, src/lib/commands/__tests__/types.test.ts, src/lib/ios/__tests__/pbxproj-modifier.test.ts, src/lib/executors/__tests__/*.test.ts, src/lib/services/__tests__/...test.ts
Added comprehensive tests for mcp/skills commands, handoff/selection services, iOS pbxproj/extension generation, and stream delta computation. Removed tests for command registry, types, transfer service, agent selection interactivity, and history compaction. Refactored executor tests to focus on buildArgs and stream mode behavior instead of session management.

Possibly related PRs

Suggested reviewers

  • JeongwooYoo
  • nyanxyz
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 41.18% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'refactor: command-mode architecture, Firebase googleapis migration, and task-based install flow' is clear, specific, and accurately describes the main changes in the PR.
Description check ✅ Passed The PR description comprehensively covers Summary, Details (via Key Changes), Related Issues context, validation steps, and mostly completes the required template sections with detailed technical content.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/refactor-firebase-apis

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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1c184418fc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/lib/commands/registry.ts Outdated
Comment thread src/ui/components/InstallPreparationUI.tsx Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🤖 Fix all issues with AI agents
In `@package.json`:
- Around line 52-54: Update the invalid dependency versions in package.json:
change "@googleapis/cloudresourcemanager" from ^6.0.1 to ^3.0.1,
"@googleapis/firebase" from ^12.0.1 to ^10.0.1, and "@googleapis/iam" from
^36.0.0 to ^30.1.0 so they match published npm releases; leave
"google-auth-library" at ^10.5.0 as-is, then run npm install (or yarn) and a
quick build/test to verify no further dependency conflicts.

In `@src/commands/skill/preparation.ts`:
- Around line 159-169: The readiness check incorrectly treats projectType.target
=== 'unknown' as not needing Firebase/iOS setup; update the logic in the
preparation routine so that when projectType.target === 'unknown' it is treated
as needing setup (i.e., needed = true or explicitly block readiness) instead of
returning configured true. Modify the branch around the needed variable and the
blocks that set configured, androidConfigured, and iosConfigured (and the
similar checks referenced later) so unknown target forces configured:false (or
prevents ready:true) and flows through normal setup checks for Firebase and iOS.

In `@src/lib/services/firebase/downloader.ts`:
- Around line 417-427: The saveServiceAccountJson function currently writes the
service-account.json with default permissions; change it to enforce owner-only
read/write permissions like credentials.json does by creating the .clix dir as
before, writing the file, then calling fs.chmod(savePath, 0o600) (and
handle/await any errors) so the file is restricted; reference
saveServiceAccountJson and mimic the chmod pattern used in
src/lib/auth/credentials.ts for credentials.json.

In `@src/lib/services/firebase/oauth/config.ts`:
- Around line 157-168: Update the OAuth scopes array in the scopes property so
it uses least-privilege Cloud Resource Manager scope(s) instead of the broad
cloud-platform scope: replace 'https://www.googleapis.com/auth/cloud-platform'
in the scopes array with the more specific
'https://www.googleapis.com/auth/cloudplatformprojects' (or
'https://www.googleapis.com/auth/cloudplatformprojects.readonly' if only
listing/reading projects) while keeping the existing firebase and iam scopes;
locate this change in the scopes definition in config.ts.

In `@src/ui/components/FirebaseWizard.tsx`:
- Around line 208-215: The menu item for "setup-service-account" is incorrectly
gated only on Android; change the condition in the FirebaseWizard component so
the item is shown when OAuth is configured AND a project ID or valid platform
config exists (use getProjectIdFromResult(result) or check result.ios?.valid)
instead of relying solely on result.android?.valid; update the conditional that
calls items.push({ id: 'setup-service-account', ... }) to use
isOAuthConfigured() && (getProjectIdFromResult(result) != null ||
result.ios?.valid || result.android?.valid) so iOS-only projects with a project
ID will see the menu entry.

In `@src/ui/components/InstallPreparationUI.tsx`:
- Around line 418-423: The render branch for phase 'ios_setup' calls
handleIosSkip() directly which triggers setPhase during render; instead remove
that direct call and create a useEffect that watches the phase (or a derived
boolean), context, and handleIosSkip, and invokes handleIosSkip() inside the
effect when phase === 'ios_setup' and context is present; keep the component
returning <CheckingPhase /> from the render path and ensure the effect's
dependency array includes handleIosSkip and context to avoid stale closures.

Comment thread package.json Outdated
Comment thread src/commands/skill/preparation.ts
Comment thread src/lib/services/firebase/downloader.ts
Comment thread src/lib/services/firebase/oauth/config.ts Outdated
Comment thread src/ui/components/FirebaseWizard.tsx Outdated
Comment thread src/ui/components/InstallPreparationUI.tsx Outdated
Remove backward compatibility code for V1→V2 migration:
- Unify ProjectConfigV1Schema and ProjectConfigV2Schema into single ProjectConfigSchema
- Change version field from z.literal(2) to z.number() for flexibility
- Remove isConfigV1(), isConfigV2(), migrateV1ToV2() functions
- Keep ensureLatestVersion() framework for future migrations
- Remove loadV2() method, use load() directly
- Update CURRENT_PROJECT_CONFIG_VERSION to 1

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/lib/config/project-config-manager.ts`:
- Around line 167-192: The updateSetup method currently merges a
Partial<SetupStatus> and saves without validating, which can produce an invalid
ProjectConfig; modify updateSetup to merge updates into config.setup and then
validate or normalize the resulting config before calling save: use an existing
config validation/normalization utility (or add one) to fill missing default
fields for SetupStatus (e.g., getDefaultSetup or schema validate) and run it
against the merged object, and if validation fails throw a ConfigError; ensure
you reference updateSetup, load, save, SetupStatus and ProjectConfig when
locating and changing the code.
🧹 Nitpick comments (1)
src/lib/config/project-config-schema.ts (1)

123-123: Constrain config version to a non‑negative integer.
Currently z.number() allows decimals (e.g., 1.5), which complicates migrations and version comparisons. Versions should be integers.

♻️ Proposed fix
-  version: z.number(),
+  version: z.number().int().nonnegative(),

Comment thread src/lib/config/project-config-manager.ts
- Migrate Firebase API clients from REST to googleapis library
- Remove IAM API (SA keys now downloaded from Firebase Console)
- Add Sender Config API integration (check/register push notification config)
- Extract FirebaseWizard PHASE_TRANSITIONS state machine with transition() validation
- Add Organization/Projects concurrent fetching service
- Add ProjectSelector search/filter functionality
- Improve InstallPreparationUI with explicit action menus
- Add OAuth debug logging infrastructure
- Simplify OAuth scopes to firebase-only
- Add exponential backoff retry logic for API calls

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🤖 Fix all issues with AI agents
In `@src/lib/debug/logger.ts`:
- Around line 201-226: The writeToFile method currently calls JSON.stringify on
optional data in both the file-write branch (building variable line) and the
stderr fallback, which can throw on circular or unserializable values; change
writeToFile (and any helper uses of this.namespace within it) to first attempt
JSON.stringify(data) inside a try/catch (or use a small safeStringify helper)
and on failure replace with a safe placeholder (e.g., "[unserializable data]" or
inspect fallback) so that neither the appendFileSync branch nor the
console.error fallback can throw — update the construction of line and the
console.error call to use the safe stringified value instead of calling
JSON.stringify directly.

In `@src/lib/services/firebase/api/firebase-api.ts`:
- Around line 91-97: The code currently type-asserts p.state to 'ACTIVE' |
'DELETED' which can let unexpected strings through; instead normalize p.state
explicitly before pushing into projects (e.g., compute const normalizedState =
p.state === 'DELETED' ? 'DELETED' : 'ACTIVE' and use that). Update the push that
builds the FirebaseProject (the projects.push block using p.state) to use this
normalized value, and apply the same change to the other occurrence (the similar
block around lines 273-277) so the FirebaseProject union is always respected.

In `@src/lib/services/firebase/oauth/auth-client.ts`:
- Around line 336-345: The current refresh error handler in the auth client
unconditionally rewraps any refreshAccessToken failure as "invalid_grant",
causing unnecessary re-auth flows; update the catch in the token refresh block
(where tokenStore.isExpired(tokens) is checked and refreshAccessToken is called)
to inspect the caught error (e.g., error.message or error.code) and only throw a
new Error prefixed with "invalid_grant: " when the underlying error actually
indicates "invalid_grant"; for other errors rethrow the original error (or throw
a generic refresh error without the invalid_grant prefix) so transient
network/IO errors are not misclassified.

In `@src/lib/services/organization-projects.ts`:
- Around line 22-27: The normalizeConcurrency function currently floors
fractional inputs which can produce 0 for values like 0.5; update
normalizeConcurrency to first validate the input, then Math.floor the value and
clamp the result to a minimum of 1 (and fall back to
DEFAULT_PROJECT_FETCH_CONCURRENCY when input is missing/invalid) so the returned
concurrency is always at least 1; reference the normalizeConcurrency function
and DEFAULT_PROJECT_FETCH_CONCURRENCY constant when making the change.

In `@src/lib/utils/oauth.ts`:
- Around line 261-283: The debug write currently persists full_url and
all_params which can leak secrets; before calling oauthLogger.writeToFile (from
within the OAuth error branch), sanitize the data by redacting sensitive
parameter values (e.g., code, state, access_token, refresh_token, client_secret,
refresh_token, id_token) and replace them with a presence flag or "[REDACTED]"
and reconstruct a sanitized full_url without secret values; then pass
sanitized_all_params and sanitized_full_url to oauthLogger.writeToFile (keep use
of findProjectRoot() and existing metadata) so only non-sensitive info or
presence indicators are persisted.

In `@src/ui/components/FirebaseWizard.tsx`:
- Around line 2087-2091: The setTimeout in the FirebaseWizard component
schedules setPhase(...) and onComplete(...) after 1500ms but doesn't clear the
timer, which can lead to state updates after unmount; capture the timeout id
when calling setTimeout (e.g., store it in a ref or local variable tied to the
component), and ensure you call clearTimeout(timeoutId) in a useEffect cleanup
or when the component is unmounted/cancelled so that the scheduled setPhase and
onComplete are not invoked on an unmounted component.

In `@src/ui/components/InstallPreparationUI.tsx`:
- Around line 62-67: getPostFirebasePhase currently only checks
ios.entitlementsConfigured and skips ios_setup if entitlements are present,
which ignores missing NSE; update getPostFirebasePhase (and the related
handleContinue logic) to require both ios.entitlementsConfigured and
ios.nseConfigured to be true before returning 'ready' when ios.needed is true so
that missing NSE keeps the flow in 'ios_setup'. Locate the PreparationContext
usage in getPostFirebasePhase and the function/method named handleContinue and
change their conditional from checking ios.entitlementsConfigured to checking
(ios.entitlementsConfigured && ios.nseConfigured), preserving the ios.needed
guard and returning 'ios_setup' otherwise.
🧹 Nitpick comments (5)
src/ui/components/ProjectSelector.tsx (2)

6-9: Avoid duplicating OrgWithProjects.

This type already exists in src/lib/services/organization-projects.ts; redefining it here risks drift. Consider importing and re-exporting the shared type.

♻️ Suggested refactor
-import type { Organization, Project } from '@/lib/api';
+import type { Organization, Project } from '@/lib/api';
+import type { OrgWithProjects } from '@/lib/services/organization-projects';
@@
-export interface OrgWithProjects {
-  org: Organization;
-  projects: Project[];
-}
+export type { OrgWithProjects };

84-159: Align selector input handling with GenericSelector + useCancelInput.

This is a selector component but handles ESC/Ctrl+C manually; the UI guidelines prefer the shared selector abstraction and cancellation hook.

As per coding guidelines, “Import useCancelInput from @/ui/hooks for ESC/Ctrl+C cancellation in new components; for selector-based components, extend GenericSelector.”

src/lib/services/firebase/api/firebase-api.ts (1)

49-68: Consider replacing the class with a plain-object factory.

The codebase guidelines prefer plain objects with interfaces over classes for TS modules.

As per coding guidelines, “Prefer plain objects with interfaces over classes; use ES module exports for encapsulation in TypeScript code.”

src/lib/api/internal-client.ts (1)

71-73: Token resolved once before retry loop may become stale during long retry sequences.

The access token is resolved once at line 71, but if maxRetries is high and the server returns 5xx repeatedly, the token could expire during the retry attempts. Consider refreshing the token before each retry attempt, or at least after a 401 response within the loop.

However, since the default maxRetries is 0 and the total retry window is short (150ms + 300ms + ... backoff), this is unlikely to be a practical issue for most use cases.

src/ui/components/FirebaseWizard.tsx (1)

799-823: Clipboard reading lacks Windows support.

The readClipboard function supports macOS (pbpaste) and Linux (xclip/xsel) but returns null on Windows. If Windows users are expected, consider adding powershell -command "Get-Clipboard" as a fallback.

🪟 Optional: Add Windows clipboard support
     if (platform === 'linux') {
       try {
         const { stdout } = await execFileAsync('xclip', ['-selection', 'clipboard', '-o']);
         return stdout;
       } catch {
         const { stdout } = await execFileAsync('xsel', ['--clipboard', '--output']);
         return stdout;
       }
     }
+    if (platform === 'win32') {
+      const { stdout } = await execFileAsync('powershell', ['-command', 'Get-Clipboard']);
+      return stdout;
+    }
     return null;

Comment thread src/lib/debug/logger.ts
Comment thread src/lib/services/firebase/api/firebase-api.ts
Comment thread src/lib/services/firebase/oauth/auth-client.ts
Comment thread src/lib/services/organization-projects.ts
Comment thread src/lib/utils/oauth.ts
Comment thread src/ui/components/FirebaseWizard.tsx Outdated
Comment thread src/ui/components/InstallPreparationUI.tsx Outdated
…ditional workflows

Add Agent Behavior, Conditional Workflows, and Common Mistakes sections
inspired by OpenAI Codex and OpenCode AGENTS.md best practices. Expand
TypeScript Patterns with BAD/GOOD code examples and Testing with mock
patterns. Condense FirebaseWizard, Safe Rendering, OAuth, and UI
Architecture sections. Merge Code Quality and Commits into single section.
Flatten install preparation into sequential leaf tasks for Firebase, APNS, iOS entitlements, and Notification Service Extension.

Remove obsolete wizard/state-machine files and deprecated push setup aliases, and add dedicated task modules plus regression tests.

Update related docs (README, llms, AGENTS) and local ignore entries for the new workflow.
…d refine related UI and Firebase integration.
Prevent command-mode install preparation from treating runtime tasks
(project_build/install_skill) as completed.

Hide project-build from user-facing skill lists while keeping internal
resolution available for install flow orchestration.

Remove GCP project add flow and cloudresourcemanager dependency so
Firebase OAuth uses only Firebase API scope.
Drop project_build from install preparation tasks and UI runtime flow so
the checklist moves directly from setup tasks to SDK installation.

Also validate registered Firebase sender config service account project IDs
against local Firebase config files and surface mismatch/decode errors as
incomplete service-account setup.

Update install skill wiring, embedded skill content, and related tests to
reflect unified install behavior and new validation rules.
Drop chat UI, slash command registry, and session/context persistence
layers to make clix run in command mode only.

Simplify executor interfaces to standalone one-shot execution by
removing history, compaction, and session resume paths.

Update agent selection, docs, and tests to align with the new
command-only workflow.
Remove Vercel Skills checks and guidance from install/doctor command execution paths.

Both commands now proceed directly to agent handoff after agent selection.

Also remove the vercel-skills service, drop the open dependency, and update docs to match the new behavior.
…skill installation workflow, and remove explicit installation phases.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 9

Caution

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

⚠️ Outside diff range comments (3)
src/lib/push/types.ts (1)

20-56: ⚠️ Potential issue | 🟠 Major

Preserve backward‑compatibility for renamed push setup types. Removing PushSetupPhase / PushSetupResult can break TypeScript consumers; consider aliasing the old names to the new ones.

♻️ Suggested compatibility aliases
 export type PushSetupTaskPhase =
   | 'detecting' // Analyzing project
   | 'status' // Showing current status
   | 'key_source' // Asking if user has existing key
   | 'apple_login' // Apple account login for auto key creation
   | 'apple_guide' // Apple Portal guide (manual)
   | 'p8_input' // P8 + Key ID + Team ID input
   | 'validation' // Validating inputs
   | 'firebase_auth' // Authenticating with Firebase
   | 'firebase_projects' // Selecting Firebase project
   | 'firebase_upload' // Firebase upload guide
   | 'complete' // Setup complete
   | 'error'; // Error state
+
+/** `@deprecated` Use PushSetupTaskPhase */
+export type PushSetupPhase = PushSetupTaskPhase;
@@
 export interface PushSetupTaskResult {
   success: boolean;
   message: string;
   context?: PushSetupContext;
 }
+
+/** `@deprecated` Use PushSetupTaskResult */
+export type PushSetupResult = PushSetupTaskResult;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/push/types.ts` around lines 20 - 56, The public types
PushSetupContext, PushSetupTaskPhase, and PushSetupTaskResult were renamed and
removing the old aliases (PushSetupPhase, PushSetupResult) can break downstream
TypeScript consumers; restore backward compatibility by exporting type aliases
mapping the old names to the new ones (e.g., alias PushSetupPhase ->
PushSetupTaskPhase and PushSetupResult -> PushSetupTaskResult) alongside the
existing definitions so both the new and old type names resolve to the same
interfaces/union types.
src/lib/utils/oauth.ts (1)

343-386: ⚠️ Potential issue | 🟠 Major

Avoid leaving waitForCallback() unresolved when stop() is called. stop() currently clears the pending callback without rejecting, which can hang callers if they use stop() to abort. Consider rejecting pending waits.

🔧 Suggested fix
   stop(): void {
-    this.clearPendingCallback();
+    if (this.pendingCallback) {
+      this.rejectPendingCallback(new Error('OAuth callback server stopped'));
+    }
     if (this.server) {
       this.server.close();
       this.server = null;
     }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/utils/oauth.ts` around lines 343 - 386, The stop() method currently
calls clearPendingCallback() which leaves any Promise returned by
waitForCallback() unresolved; update stop() to reject any pending wait by
calling rejectPendingCallback(new Error('Server stopped')) (or use a dedicated
Abort/Error type) before nulling the server so callers don't hang; change the
stop() implementation to invoke rejectPendingCallback(...) instead of
clearPendingCallback(), keeping clearPendingCallback(), rejectPendingCallback(),
resolvePendingCallback(), getPort() unchanged.
src/cli.tsx (1)

21-60: ⚠️ Potential issue | 🟡 Minor

Document the --startTask flag in help output.

The flag is exposed but isn’t shown in the help text, so users won’t discover it.

🛠️ Suggested help text addition
   Options
     --help            Show this help message
     --version         Show version number
     --platform <val>  Target platform (ios, android, react-native, flutter)
+    --startTask <val> Begin install at a specific task (advanced)

Also applies to: 71-73

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cli.tsx` around lines 21 - 60, The generateHelpText function's returned
help string is missing documentation for the --startTask flag; update
generateHelpText to include a line under "Options" documenting --startTask
(e.g., "--startTask <id>   Start a specific task on startup") and add an example
invocation (such as "$ clix --startTask <id>" or "$ clix install --startTask
<id>") in the "Examples" section so the flag is discoverable; modify the string
returned by generateHelpText accordingly.
🧹 Nitpick comments (8)
src/commands/skills.ts (1)

16-19: Add -y to the npx invocation to avoid install prompts in non-interactive runs.

npx can prompt to install skills when not cached, which can hang scripted usage. Consider adding -y so the handoff stays unattended.

♻️ Proposed tweak
-      args: ['skills', 'add', DEFAULT_SKILLS_REPOSITORY],
+      args: ['-y', 'skills', 'add', DEFAULT_SKILLS_REPOSITORY],
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/commands/skills.ts` around lines 16 - 19, The npx call inside the
runHandoff invocation is missing the non-interactive flag; modify the args array
passed to runHandoff (the call where exitCode = await runHandoff({...}) with
command: 'npx') to include '-y' (e.g., ['-y', 'skills', 'add',
DEFAULT_SKILLS_REPOSITORY']) so the npx invocation runs non-interactively and
won’t prompt during scripted runs; update the args array in that runHandoff call
referencing DEFAULT_SKILLS_REPOSITORY accordingly.
src/lib/services/__tests__/agent-handoff.test.ts (1)

46-126: Consider adding test coverage for gemini and copilot agents.

The buildAgentHandoffInvocation tests cover claude, codex, cursor, and opencode, but gemini and copilot are missing. These agents have specific argument patterns (e.g., gemini uses -m gemini-3-flash-preview -y, copilot uses --allow-all-tools) that would benefit from explicit test coverage.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/services/__tests__/agent-handoff.test.ts` around lines 46 - 126, Add
unit tests for gemini and copilot to cover their specific arg patterns: extend
the buildAgentHandoffInvocation test suite by calling
buildAgentHandoffInvocation with createAgent('gemini', ...) and asserting args
include the gemini model flags (e.g., '-m gemini-3-flash-preview' and '-y' or
other expected gemini flags), and another case with createAgent('copilot', ...)
asserting args include '--allow-all-tools' (and any other copilot-specific
flags). Keep test structure consistent with existing tests (use invocation.args
and invocation.env checks) and restore any modified process.env values after
each test.
src/lib/services/mcp-install-service.ts (1)

131-140: Heuristic output detection may be fragile.

The isAlreadyConfiguredOutput function relies on substring matching (already, exist, configured, registered) which could produce false positives or miss legitimate cases if add-mcp's output format changes.

Consider documenting the expected output patterns or adding a more robust detection mechanism (e.g., specific error codes or structured output from add-mcp).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/services/mcp-install-service.ts` around lines 131 - 140, The
isAlreadyConfiguredOutput function uses naive substring checks which can yield
false positives; update isAlreadyConfiguredOutput to use stricter matching
(e.g., regex with word boundaries like /\balready\b/, /\bexist(s|ing)?\b/,
/\bconfigured\b/, /\bregistered\b/) or parse structured output/error codes if
add-mcp can emit them, and document the expected add-mcp output patterns in a
comment above the function (or add a TODO to switch to structured checks once
add-mcp supports machine-readable output); reference the function name
isAlreadyConfiguredOutput when making the change so callers keep the same API.
src/lib/ios/pbxproj-modifier.ts (1)

574-584: Unreachable code path in findAppTargetUuid.

The condition at line 579-581 is logically unreachable. If descriptors.length === 1 and !isLikelyNonMainTarget(descriptors[0].name), then nonAuxiliaryTarget at line 574 would have already found it (since it's the only descriptor and not a non-main target), making this branch never execute.

🧹 Suggested simplification
   const nonAuxiliaryTarget = descriptors.find((target) => !isLikelyNonMainTarget(target.name));
   if (nonAuxiliaryTarget) {
     return nonAuxiliaryTarget.uuid;
   }

-  if (descriptors.length === 1 && !isLikelyNonMainTarget(descriptors[0].name)) {
-    return descriptors[0].uuid;
-  }
-
   return null;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/ios/pbxproj-modifier.ts` around lines 574 - 584, The check in
findAppTargetUuid contains an unreachable branch: after computing
nonAuxiliaryTarget = descriptors.find(target =>
!isLikelyNonMainTarget(target.name)), the subsequent if (descriptors.length ===
1 && !isLikelyNonMainTarget(descriptors[0].name)) can never run; remove that
redundant condition and simply return nonAuxiliaryTarget?.uuid ?? null (or
return null if not found) to simplify logic while keeping the same behavior for
descriptors, nonAuxiliaryTarget, and isLikelyNonMainTarget.
src/commands/skill/preparation.ts (1)

207-210: Simplify redundant condition check.

Line 208 checks configuredPublicApiKey which is always truthy when config.project.public_api_key is truthy (since configuredPublicApiKey = config.project.public_api_key ?? config.project.publicKey).

♻️ Suggested simplification
-  if (config.project.public_api_key && !config.project.publicKey && configuredPublicApiKey) {
+  if (config.project.public_api_key && !config.project.publicKey) {
     return config;
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/commands/skill/preparation.ts` around lines 207 - 210, The condition in
the preparation logic is redundant: since configuredPublicApiKey is defined as
config.project.public_api_key ?? config.project.publicKey, checking
configuredPublicApiKey when config.project.public_api_key is truthy is
unnecessary. Update the if in src/commands/skill/preparation.ts (the block using
configuredPublicApiKey and the condition checking config.project.public_api_key
&& !config.project.publicKey && configuredPublicApiKey) to simply check
config.project.public_api_key && !config.project.publicKey and return config;
remove the redundant configuredPublicApiKey check (and remove or repurpose the
configuredPublicApiKey variable if it becomes unused).
src/lib/executors/__tests__/opencode-executor.test.ts (1)

40-75: Consider simplifying environment restoration logic.

The finally blocks have redundant branches—both paths assign original (or undefined) back to process.env.OPENCODE_PERMISSION. This can be simplified.

♻️ Suggested simplification
       } finally {
-        if (original === undefined) {
-          process.env.OPENCODE_PERMISSION = undefined;
-        } else {
-          process.env.OPENCODE_PERMISSION = original;
-        }
+        process.env.OPENCODE_PERMISSION = original;
       }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/executors/__tests__/opencode-executor.test.ts` around lines 40 - 75,
The finally blocks in the two tests redundantly branch to restore
process.env.OPENCODE_PERMISSION; simplify both by replacing the conditional
restore with a single assignment setting process.env.OPENCODE_PERMISSION =
original so the environment is restored unconditionally after calling (executor
as any).getSpawnEnv() in the permission env tests that reference
OPENCODE_PERMISSION.
src/lib/config/project-config-schema.ts (1)

206-225: Simplify redundant condition in migration logic.

The condition on lines 209-211 is unreachable because publicApiKey is already derived from config.project.public_api_key ?? config.project.publicKey, so if publicApiKey is falsy, both source fields are already falsy.

♻️ Proposed simplification
 export function ensureLatestVersion(config: ProjectConfig): ProjectConfig {
   const publicApiKey = config.project.public_api_key ?? config.project.publicKey;
 
-  if (!publicApiKey && !config.project.public_api_key && !config.project.publicKey) {
-    return config;
-  }
-
-  if (config.project.public_api_key && !config.project.publicKey) {
+  // Already migrated or no key to migrate
+  if (!publicApiKey || (config.project.public_api_key && !config.project.publicKey)) {
     return config;
   }
 
   return {
     ...config,
     project: {
       id: config.project.id,
       name: config.project.name,
-      ...(publicApiKey && { public_api_key: publicApiKey }),
+      public_api_key: publicApiKey,
     },
   };
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/config/project-config-schema.ts` around lines 206 - 225, The
migration function ensureLatestVersion contains an unreachable check that
repeats what publicApiKey already represents: remove the redundant if block that
tests (!publicApiKey && !config.project.public_api_key &&
!config.project.publicKey) and instead rely on the single publicApiKey presence
test; keep the early return when publicApiKey is falsy, retain the existing
branch that returns when config.project.public_api_key exists but
config.project.publicKey is missing, and otherwise return the normalized project
object—refer to ensureLatestVersion, publicApiKey,
config.project.public_api_key, and config.project.publicKey when making the
change.
src/lib/services/firebase/api/firebase-api.ts (1)

161-215: Add logApiError wrappers for config/create calls.

These public methods currently skip the error logging used elsewhere; wrapping them improves diagnostics consistency.

✍️ Suggested pattern (apply similarly to getIosConfig/createAndroidApp/createIosApp)
 async getAndroidConfig(projectId: string, appId: string): Promise<string> {
-  await this.updateCredentials();
-  const res = await this.fb.projects.androidApps.getConfig({
-    name: `projects/${projectId}/androidApps/${appId}/config`,
-  });
-  return Buffer.from(res.data.configFileContents ?? '', 'base64').toString('utf-8');
+  try {
+    await this.updateCredentials();
+    const res = await this.fb.projects.androidApps.getConfig({
+      name: `projects/${projectId}/androidApps/${appId}/config`,
+    });
+    return Buffer.from(res.data.configFileContents ?? '', 'base64').toString('utf-8');
+  } catch (error) {
+    logApiError('getAndroidConfig', error);
+    throw error;
+  }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/services/firebase/api/firebase-api.ts` around lines 161 - 215, The
methods getAndroidConfig, getIosConfig, createAndroidApp, and createIosApp
currently call the Firebase client directly and miss the standardized error
logging wrapper; wrap the API calls (the fb.projects.androidApps.getConfig,
fb.projects.iosApps.getConfig, fb.projects.androidApps.create, and
fb.projects.iosApps.create invocations) with the existing logApiError helper so
any thrown errors are logged consistently, while preserving the surrounding
await this.updateCredentials() and the subsequent handling (Buffer decoding in
get*Config and the check + waitForOperation() flow in create*App); ensure you
call logApiError with the original async call (or as a wrapper returning the
same result) and keep existing return values and error checks intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@README.md`:
- Around line 3-118: The README was updated to change the command/agent surface
and config paths but the companion llms.txt was not; update llms.txt to mirror
all changes made in README.md (including the list of AI agents and any
renamed/added commands such as "clix agent", "clix install", "clix doctor",
"clix mcp", "clix skills", "clix update", and any config path changes or
skill/autonomy flags), ensuring entries for slash commands, AI agents, skills
(interactive/autonomous), CLI commands, and config paths are kept in sync;
search for the symbols "clix agent", "clix install", "clix doctor", "clix mcp",
"clix skills", and "llms.txt" to locate where to apply the updates.

In `@scripts/embed-skills.ts`:
- Around line 3-7: The embed-skills.ts change now embeds only local prompts
(install/doctor) and removes external package-based skills, so update
documentation to reflect this local-only embedding behavior: edit README.md to
describe the new embedding model and any changes to slash commands, AI agents,
interactive/autonomous skills, CLI commands, and config path behavior, and
update llms.txt to reflect supported LLMs/skill sources under the new local-only
scheme; ensure references to the script name embed-skills.ts and the
embedded-skills export (or src/lib/embedded-skills.ts) are updated so consumers
know where prompts now live and what is no longer auto-embedded.

In `@src/lib/constants/system-prompt.ts`:
- Around line 67-77: The slash-command list in
src/lib/constants/system-prompt.ts was changed but README.md and llms.txt were
not updated; update both README.md and llms.txt to reflect the new/edited slash
commands (e.g. /install, /integration, /event-tracking, /user-management,
/personalization, /api-triggered-campaigns, /doctor, /debug, /mcp) and ensure
descriptions match the entries in the system-prompt constants; verify any
related sections that document AI agents, skills, CLI commands, or config paths
are consistent with these changes.

In `@src/lib/executors/cli-process-manager.ts`:
- Around line 80-88: The spawnCLIProcess function currently passes the
caller-provided env directly to spawn which replaces the process environment
(dropping PATH); change it to merge process.env with the provided env so callers
can override keys but default variables (like PATH) are preserved — e.g. build a
mergedEnv from process.env and CLIProcessOptions.env and pass mergedEnv into
spawn; update symbols: spawnCLIProcess, CLIProcessOptions, and the spawn call to
use the merged environment.

In `@src/lib/ios/podfile-modifier.ts`:
- Around line 54-60: The extractTargetBlock function's targetRegex fails when
the closing "end" is indented; update the regex used in extractTargetBlock to
allow optional leading whitespace before the closing end (e.g., match "\n\s*end"
instead of "\nend") so indented blocks are captured; keep the non-greedy
([\s\S]*?) grouping and case-insensitive flag as-is, and optionally note in
comments near extractTargetBlock (or consider replacing it later) that a small
parser should be used if nested do/end blocks inside the target are expected.

In `@src/lib/services/__tests__/agent-handoff.test.ts`:
- Around line 88-106: The test incorrectly manipulates process.env by assigning
'' and undefined (which becomes the string "undefined") and has a redundant
assignment; update the test "injects OPENCODE_PERMISSION by default for
opencode" to delete process.env.OPENCODE_PERMISSION (use delete
process.env.OPENCODE_PERMISSION) instead of assigning undefined or repeating
assignments when preparing the environment before calling
buildAgentHandoffInvocation (and remove the duplicate assignment), and when
restoring state after the test, if previous is undefined delete
process.env.OPENCODE_PERMISSION else restore it via
process.env.OPENCODE_PERMISSION = previous; keep references to
OPENCODE_PERMISSION, buildAgentHandoffInvocation, and createAgent to locate the
change.

In `@src/lib/services/agent-handoff.ts`:
- Around line 192-195: The execve call obtained from getExecve() can throw on
failure; wrap the execve(invocation.command, [invocation.command,
...invocation.args], invocation.env) call in a try/catch, catch the error, log a
clear error including invocation.command, invocation.args and the caught error
details, and then exit or propagate a non-zero failure (e.g., process.exit(1) or
rethrow a wrapped error) so failures don’t silently propagate; update the code
around getExecve/execve to implement this handling.

In `@src/lib/skills/doctor/SKILL.md`:
- Line 101: Replace the incorrect slash-command reference "/install" in SKILL.md
with the supported CLI invocation "clix install" (or remove the slash-command
mention altogether) so the doc aligns with README.md and llms.txt; search for
the literal string "/install" in the document and update it to "clix install"
and, if the project intends to support a slash-command flow, instead add a note
referencing README.md and llms.txt and ensure those files are updated first to
document the feature.

In `@src/lib/utils/__tests__/oauth-callback-server.test.ts`:
- Around line 5-29: Tests currently bind a real HTTP server via
OAuthCallbackServer.start(), which can be flaky; modify the tests to avoid real
network binding by mocking the node:http server creation using mock.module() (or
by injecting a fake server into OAuthCallbackServer). Specifically, in the
failing tests that call OAuthCallbackServer.start(), replace the real start
behavior with a stubbed server that implements the same lifecycle and request
handling used by OAuthCallbackServer so that waitForCallback(), cancel(), and
stop() exercise the same logic without opening a port; keep references to
OAuthCallbackServer, start, waitForCallback, cancel, and stop so the tests still
validate cancellation and concurrent-wait behavior deterministically.

---

Outside diff comments:
In `@src/cli.tsx`:
- Around line 21-60: The generateHelpText function's returned help string is
missing documentation for the --startTask flag; update generateHelpText to
include a line under "Options" documenting --startTask (e.g., "--startTask <id> 
Start a specific task on startup") and add an example invocation (such as "$
clix --startTask <id>" or "$ clix install --startTask <id>") in the "Examples"
section so the flag is discoverable; modify the string returned by
generateHelpText accordingly.

In `@src/lib/push/types.ts`:
- Around line 20-56: The public types PushSetupContext, PushSetupTaskPhase, and
PushSetupTaskResult were renamed and removing the old aliases (PushSetupPhase,
PushSetupResult) can break downstream TypeScript consumers; restore backward
compatibility by exporting type aliases mapping the old names to the new ones
(e.g., alias PushSetupPhase -> PushSetupTaskPhase and PushSetupResult ->
PushSetupTaskResult) alongside the existing definitions so both the new and old
type names resolve to the same interfaces/union types.

In `@src/lib/utils/oauth.ts`:
- Around line 343-386: The stop() method currently calls clearPendingCallback()
which leaves any Promise returned by waitForCallback() unresolved; update stop()
to reject any pending wait by calling rejectPendingCallback(new Error('Server
stopped')) (or use a dedicated Abort/Error type) before nulling the server so
callers don't hang; change the stop() implementation to invoke
rejectPendingCallback(...) instead of clearPendingCallback(), keeping
clearPendingCallback(), rejectPendingCallback(), resolvePendingCallback(),
getPort() unchanged.

---

Duplicate comments:
In `@src/commands/skill/preparation.ts`:
- Around line 354-366: The early return treats projectType.target === 'unknown'
as "not needed", which lets preparation report configured:true incorrectly;
update the logic in preparation.ts (where const needed = projectType.target !==
'unknown') to avoid marking Firebase/iOS as configured when target is unknown —
instead treat unknown as "needs validation" and run the normal checks (or return
needed: true) so functions that rely on configured flags (e.g., the iOS/Firebase
status checks) cannot short-circuit to configured:true; adjust any callers
expecting needed to be false for 'unknown' so readiness reflects actual setup.

In `@src/lib/config/project-config-manager.ts`:
- Around line 175-193: The updateSetup method merges partial SetupStatus into
the existing config but may persist invalid nested state; before calling
save(updatedConfig) merge the updates into config.setup (as already done) then
run the project's validation/normalization routine (e.g., validateProjectConfig
or a new validateSetup/normalizeSetup function) against the merged setup and/or
full ProjectConfig, throw a ConfigError with ERROR_CODES.INVALID_PROJECT_CONFIG
if validation fails, and only call this.save(updatedConfig) when
validation/normalization succeeds; updateSetup, SetupStatus, ProjectConfig, and
save/load are the relevant symbols to locate and modify.

In `@src/lib/services/firebase/api/firebase-api.ts`:
- Around line 74-95: In listProjects(), avoid casting p.state and instead
normalize it into the allowed union; update the loop that builds FirebaseProject
(inside async listProjects()) to map the incoming p.state string to either
'ACTIVE' or 'DELETED' (e.g., check if p.state === 'DELETED' then 'DELETED' else
'ACTIVE') so you return a properly typed state property on each project; locate
this change where res.data.results is iterated and replace the state: (p.state
as 'ACTIVE' | 'DELETED') ?? 'ACTIVE' with a small normalization expression that
derives 'ACTIVE'|'DELETED' from p.state.

In `@src/lib/services/firebase/downloader.ts`:
- Around line 331-342: In saveServiceAccountJson, harden filesystem permissions:
create the .clix directory (clixDir) with mode 0o700 when calling fs.mkdir, and
write the service-account.json (savePath) with mode 0o600 when calling
fs.writeFile (pass the mode option alongside 'utf-8' or as the options object)
so the directory and file are restricted to the owner; update the calls in
saveServiceAccountJson accordingly.

In `@src/lib/services/firebase/oauth/auth-client.ts`:
- Around line 337-344: The catch block around
refreshAccessToken(tokens.refresh_token) currently unconditionally rewraps every
error as "invalid_grant", causing unnecessary re-auth flows; change the handler
in the method in auth-client.ts to inspect the thrown error (e.g., check
error.message, error.code, or error.response?.data?.error) and only throw new
Error(`invalid_grant: ...`) when the underlying error actually indicates an
invalid_grant; otherwise rethrow the original error (or preserve its
message/stack) so transient/network errors are not treated as authentication
failures.

In `@src/lib/utils/oauth.ts`:
- Around line 272-285: The OAuth error logging currently writes raw full_url and
all_params which may contain sensitive query values; before calling
oauthLogger.writeToFile (in the OAuth error handling block where variables url,
req.url, error, errorDescription and all_params are prepared), sanitize by
redacting values for sensitive parameter names (e.g., code, state, token,
access_token, refresh_token, id_token, auth, secret) — produce a
sanitizedFullUrl (reconstruct the URL with sensitive params replaced by
"[REDACTED]") and sanitizedParams (Object.fromEntries with values replaced for
those keys) and pass those sanitized values instead of full_url and all_params
to oauthLogger.writeToFile.

---

Nitpick comments:
In `@src/commands/skill/preparation.ts`:
- Around line 207-210: The condition in the preparation logic is redundant:
since configuredPublicApiKey is defined as config.project.public_api_key ??
config.project.publicKey, checking configuredPublicApiKey when
config.project.public_api_key is truthy is unnecessary. Update the if in
src/commands/skill/preparation.ts (the block using configuredPublicApiKey and
the condition checking config.project.public_api_key &&
!config.project.publicKey && configuredPublicApiKey) to simply check
config.project.public_api_key && !config.project.publicKey and return config;
remove the redundant configuredPublicApiKey check (and remove or repurpose the
configuredPublicApiKey variable if it becomes unused).

In `@src/commands/skills.ts`:
- Around line 16-19: The npx call inside the runHandoff invocation is missing
the non-interactive flag; modify the args array passed to runHandoff (the call
where exitCode = await runHandoff({...}) with command: 'npx') to include '-y'
(e.g., ['-y', 'skills', 'add', DEFAULT_SKILLS_REPOSITORY']) so the npx
invocation runs non-interactively and won’t prompt during scripted runs; update
the args array in that runHandoff call referencing DEFAULT_SKILLS_REPOSITORY
accordingly.

In `@src/lib/config/project-config-schema.ts`:
- Around line 206-225: The migration function ensureLatestVersion contains an
unreachable check that repeats what publicApiKey already represents: remove the
redundant if block that tests (!publicApiKey && !config.project.public_api_key
&& !config.project.publicKey) and instead rely on the single publicApiKey
presence test; keep the early return when publicApiKey is falsy, retain the
existing branch that returns when config.project.public_api_key exists but
config.project.publicKey is missing, and otherwise return the normalized project
object—refer to ensureLatestVersion, publicApiKey,
config.project.public_api_key, and config.project.publicKey when making the
change.

In `@src/lib/executors/__tests__/opencode-executor.test.ts`:
- Around line 40-75: The finally blocks in the two tests redundantly branch to
restore process.env.OPENCODE_PERMISSION; simplify both by replacing the
conditional restore with a single assignment setting
process.env.OPENCODE_PERMISSION = original so the environment is restored
unconditionally after calling (executor as any).getSpawnEnv() in the permission
env tests that reference OPENCODE_PERMISSION.

In `@src/lib/ios/pbxproj-modifier.ts`:
- Around line 574-584: The check in findAppTargetUuid contains an unreachable
branch: after computing nonAuxiliaryTarget = descriptors.find(target =>
!isLikelyNonMainTarget(target.name)), the subsequent if (descriptors.length ===
1 && !isLikelyNonMainTarget(descriptors[0].name)) can never run; remove that
redundant condition and simply return nonAuxiliaryTarget?.uuid ?? null (or
return null if not found) to simplify logic while keeping the same behavior for
descriptors, nonAuxiliaryTarget, and isLikelyNonMainTarget.

In `@src/lib/services/__tests__/agent-handoff.test.ts`:
- Around line 46-126: Add unit tests for gemini and copilot to cover their
specific arg patterns: extend the buildAgentHandoffInvocation test suite by
calling buildAgentHandoffInvocation with createAgent('gemini', ...) and
asserting args include the gemini model flags (e.g., '-m gemini-3-flash-preview'
and '-y' or other expected gemini flags), and another case with
createAgent('copilot', ...) asserting args include '--allow-all-tools' (and any
other copilot-specific flags). Keep test structure consistent with existing
tests (use invocation.args and invocation.env checks) and restore any modified
process.env values after each test.

In `@src/lib/services/firebase/api/firebase-api.ts`:
- Around line 161-215: The methods getAndroidConfig, getIosConfig,
createAndroidApp, and createIosApp currently call the Firebase client directly
and miss the standardized error logging wrapper; wrap the API calls (the
fb.projects.androidApps.getConfig, fb.projects.iosApps.getConfig,
fb.projects.androidApps.create, and fb.projects.iosApps.create invocations) with
the existing logApiError helper so any thrown errors are logged consistently,
while preserving the surrounding await this.updateCredentials() and the
subsequent handling (Buffer decoding in get*Config and the check +
waitForOperation() flow in create*App); ensure you call logApiError with the
original async call (or as a wrapper returning the same result) and keep
existing return values and error checks intact.

In `@src/lib/services/mcp-install-service.ts`:
- Around line 131-140: The isAlreadyConfiguredOutput function uses naive
substring checks which can yield false positives; update
isAlreadyConfiguredOutput to use stricter matching (e.g., regex with word
boundaries like /\balready\b/, /\bexist(s|ing)?\b/, /\bconfigured\b/,
/\bregistered\b/) or parse structured output/error codes if add-mcp can emit
them, and document the expected add-mcp output patterns in a comment above the
function (or add a TODO to switch to structured checks once add-mcp supports
machine-readable output); reference the function name isAlreadyConfiguredOutput
when making the change so callers keep the same API.

Comment thread README.md Outdated
Comment thread scripts/embed-skills.ts
Comment thread src/lib/constants/system-prompt.ts Outdated
Comment thread src/lib/executors/cli-process-manager.ts
Comment thread src/lib/ios/podfile-modifier.ts
Comment thread src/lib/services/__tests__/agent-handoff.test.ts
Comment thread src/lib/services/agent-handoff.ts
Comment thread src/lib/skills/doctor/SKILL.md Outdated
Comment thread src/lib/utils/__tests__/oauth-callback-server.test.ts
Remove the mcp positional agent argument and make mcp run with a\nfixed add-mcp invocation.\n\nDrop the global --platform option from install/doctor command paths\nand related help/docs/tests.\n\nRequire install and doctor prompts to end with an explicit final\nresult block so agents always print a terminal summary.
…I before agent handoff

Doctor command now gathers PreparationContext (reusing install's
gatherPreparationContext) to verify Firebase, APNS, iOS entitlements,
and NSE status before agent handoff. Shows colored status checklist
using shared StatusLine/getStatusRows components. Missing items block
handoff with clix install guidance. Ready state prompts user to accept
AI diagnosis via GenericSelector. SKILL.md updated to remove JSON
output in favor of readable text format, with pre-verified context
as ground truth.
Improve install preparation behavior for Firebase and iOS setup tasks.

- add Android/iOS identifier mismatch handling and app creation paths

- detect Android package matches across all google-services clients

- show Apple Team bundle IDs when bundle identifier is unavailable

- tighten iOS entitlements detection and Apple auth UX handling

- remove generated config header comments and align setup status checks
Ensure clix terminates with the handoff process exit code to prevent

requiring extra Ctrl+C after agent/CLI handoff completes.

Also simplify Firebase auth UI by removing the duplicated Direct URL

section and showing a clickable full URL inline.
Critical fixes:
- Add null guard in waitForOperation response handling
- Reset installSkillRunStartedRef on retry to prevent stuck state
- Classify invalid_grant vs transient token refresh errors correctly

Code deduplication:
- Extract shared firebase-detection-utils module
- Export createDefaultOpenCodeEnv from opencode-executor for reuse
- Move extractTextDelta to BaseExecutor (shared by Claude/Cursor)

Consistency improvements:
- Add streamMode to result path text emissions
- Use AgentError instead of generic Error in command modules
- Add useCancelInput to IosSetupUI authenticating phase
- Wrap execve call with try/catch fallback in agent-handoff
- Prevent double addTargetDependency in pbxproj-modifier

Dead code removal:
- Remove GeminiCLIInitEvent, OpenCodeCLISessionEvent dead types
- Remove unused allowedTools from ExecuteOptions
- Remove SkillMetadata and related dead exports
- Inline single-use buildMcpHandoffArgs helper
- Clean up dead test fixtures and cancelled phase
Restore supported agents table with free/paid tiers, add commands
summary table, improve command descriptions, and add back development,
links, and license sections removed during chat mode removal.
@pitzcarraldo pitzcarraldo changed the title refactor(firebase): replace REST API clients with googleapis library refactor: command-mode architecture, Firebase googleapis migration, and task-based install flow Feb 23, 2026
Security:
- Harden service-account key storage with 0o600 file permissions
- Redact sensitive OAuth params (code, state) before writing to debug.log

Bug fixes:
- Normalize Firebase project state instead of type-asserting
- Clamp fractional concurrency to at least 1
- Merge provided env with process.env in CLI process spawn
- Handle indented end lines in Podfile target extraction
- Wrap execve in try/catch for spawn fallback

Robustness:
- Add safeStringify in logger to handle circular/unserializable data
- Clean up redundant env var assignment in handoff test
- Add supported agents list to llms.txt
@pitzcarraldo pitzcarraldo self-assigned this Feb 23, 2026
The open dependency was intentionally removed but browser.ts still
attempted to import it. Use platform-specific fallback directly.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

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

⚠️ Outside diff range comments (1)
src/lib/executors/base-executor.ts (1)

58-139: ⚠️ Potential issue | 🟠 Major

Reset delta state per execution.
lastTextContent persists across runs, so deltas can be computed against the previous request’s output, yielding missing or mangled chunks.

🐛 Suggested fix (reset per execution)
  async *execute(prompt: string, options?: ExecuteOptions): AsyncGenerator<AgentMessage> {
    if (!(await this.isAvailable())) {
      yield { type: 'error', content: this.notFoundMessage };
      return;
    }

+    this.lastTextContent = '';
     const preparedPrompt = this.getPreparedPrompt(prompt);
     const args = this.buildArgs(preparedPrompt, options);

If you expect concurrent executions on a single executor instance, consider storing the delta state in StreamContext instead of a class field.

Also applies to: 200-207

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/executors/base-executor.ts` around lines 58 - 139, lastTextContent is
stored as a class field and persists across executions causing incorrect deltas;
reset or relocate that state per run. Fix by clearing this.lastTextContent = ''
at the start of the execution flow (e.g., in the method that begins a run such
as execute or runProcess) before any calls to
getPreparedPrompt/extractTextDelta/processStreamData, or move the delta state
into StreamContext (add e.g., streamContext.lastTextContent and update
extractTextDelta to use it) so concurrent runs don’t share mutable state; update
usages of extractTextDelta/lastTextContent accordingly.
♻️ Duplicate comments (1)
src/commands/skill/preparation.ts (1)

377-389: ⚠️ Potential issue | 🟠 Major

Unknown targets can still report “ready”.
Treating target === 'unknown' as “not needed” can incorrectly mark setup as complete. This risks false positives when project detection fails.

🐛 Suggested guard for unknown targets
-  const needed = projectType.target !== 'unknown';
-
-  if (!needed) {
-    return {
-      configured: true,
-      androidConfigured: true,
-      iosConfigured: true,
-      senderConfigConfigured: true,
-      senderConfigProjectMatched: true,
-      needed: false,
-    };
-  }
+  if (projectType.target === 'unknown') {
+    return {
+      configured: false,
+      androidConfigured: false,
+      iosConfigured: false,
+      senderConfigConfigured: false,
+      senderConfigProjectMatched: false,
+      needed: true,
+    };
+  }
-  const needed = projectType.target === 'ios' || projectType.target === 'ios-android';
-
-  if (!needed) {
-    return {
-      needed: false,
-      entitlementsConfigured: true,
-      nseConfigured: true,
-    };
-  }
+  if (projectType.target === 'unknown') {
+    return {
+      needed: true,
+      entitlementsConfigured: false,
+      nseConfigured: false,
+    };
+  }
+  const needed = projectType.target === 'ios' || projectType.target === 'ios-android';
+  if (!needed) {
+    return {
+      needed: false,
+      entitlementsConfigured: true,
+      nseConfigured: true,
+    };
+  }
-  const needed = projectType.target === 'ios' || projectType.target === 'ios-android';
-
-  if (!needed) {
-    return {
-      needed: false,
-      registeredWithFirebase: true,
-    };
-  }
+  if (projectType.target === 'unknown') {
+    return {
+      needed: true,
+      registeredWithFirebase: false,
+    };
+  }
+  const needed = projectType.target === 'ios' || projectType.target === 'ios-android';
+  if (!needed) {
+    return {
+      needed: false,
+      registeredWithFirebase: true,
+    };
+  }

Also applies to: 592-625

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/commands/skill/preparation.ts` around lines 377 - 389, The early-return
treats projectType.target === 'unknown' as "not needed" and thus marks setup as
configured; remove or change that shortcut so unknown targets are treated as
needing setup. Update the logic around the needed variable and the early return
in preparation.ts (the block using projectType.target and the const needed) so
that when projectType.target === 'unknown' you do NOT return the configured:true
object — instead set needed = true (or configured = false) and let the normal
checks flow so the caller sees the project as not configured and requiring
attention.
🧹 Nitpick comments (5)
.gitignore (3)

81-85: Consider adding a section comment for better organization.

The new entries would benefit from a descriptive section header for clarity and consistency with the rest of the file.

📝 Suggested organization
 # Clix CLI local config
 .clix/
+
+# Development artifacts
 .omc
 .ralph-tui
 llm-council
 plan.md
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.gitignore around lines 81 - 85, Add a descriptive section header above the
new .gitignore entries (e.g., a comment like "# Local tools / project-specific"
or "# Generated files and local artifacts") to match the file's existing
organization; place this header immediately above the block containing ".omc",
".ralph-tui", "llm-council", and "plan.md" so it's clear these are local/tooling
or generated artifacts.

82-84: Consider moving tool-specific artifacts to global gitignore.

The entries .omc, .ralph-tui, and llm-council appear to be artifacts from personal development tools or AI coding assistants. Unless these tools are standardized across the entire team, these entries might be better suited for each developer's global gitignore (~/.gitignore_global) rather than the project's .gitignore.

This helps keep the project .gitignore focused on project-specific patterns that apply to all contributors.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.gitignore around lines 82 - 84, The three entries `.omc`, `.ralph-tui`, and
`llm-council` in the project .gitignore look like personal/tool-specific
artifacts; remove these lines from the project .gitignore and instead add them
to each developer's global gitignore (e.g. ~/.gitignore_global) or document them
as optional in CONTRIBUTING.md so the repo only contains project-wide ignore
patterns; reference the `.gitignore` file and the specific entries `.omc`,
`.ralph-tui`, and `llm-council` when making the change.

85-85: plan.md might be too generic to ignore project-wide.

The filename plan.md is quite common and some developers might want to commit actual project planning documents with this name. Consider either:

  • Using a more specific pattern like .plan.md or *.plan.md for personal planning docs
  • Moving this to personal global gitignore if it's for individual developer notes
  • Documenting this convention if it's an intentional team standard
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.gitignore at line 85, The .gitignore currently ignores the generic filename
"plan.md", which may block legitimate project planning files; update the ignore
rule by replacing "plan.md" with a more specific pattern (for example ".plan.md"
or "*.plan.md") or remove it and add the entry to each developer's global
gitignore, and if it's an intentional team convention, document this decision in
the repository so contributors know which planning files are considered personal
versus project assets.
src/lib/services/firebase/api/firebase-api.ts (1)

161-175: Inconsistent error handling in config retrieval methods.

getAndroidConfig and getIosConfig don't wrap calls with try/catch and logApiError, unlike listProjects, listAndroidApps, and listIosApps. This creates inconsistent observability when these operations fail.

♻️ Proposed fix for consistent error logging
  async getAndroidConfig(projectId: string, appId: string): Promise<string> {
+   try {
      await this.updateCredentials();
      const res = await this.fb.projects.androidApps.getConfig({
        name: `projects/${projectId}/androidApps/${appId}/config`,
      });
      return Buffer.from(res.data.configFileContents ?? '', 'base64').toString('utf-8');
+   } catch (error) {
+     logApiError('getAndroidConfig', error);
+     throw error;
+   }
  }

  async getIosConfig(projectId: string, appId: string): Promise<string> {
+   try {
      await this.updateCredentials();
      const res = await this.fb.projects.iosApps.getConfig({
        name: `projects/${projectId}/iosApps/${appId}/config`,
      });
      return Buffer.from(res.data.configFileContents ?? '', 'base64').toString('utf-8');
+   } catch (error) {
+     logApiError('getIosConfig', error);
+     throw error;
+   }
  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/services/firebase/api/firebase-api.ts` around lines 161 - 175,
getAndroidConfig and getIosConfig lack the try/catch + logApiError pattern used
elsewhere, so wrap each method body in a try/catch: call await
this.updateCredentials() and the fb API inside the try, return the decoded
config on success, and in the catch call logApiError(this.logger, err,
'getAndroidConfig') / logApiError(this.logger, err, 'getIosConfig') (matching
the existing logApiError usage), then rethrow the error so callers still receive
it; this makes error logging consistent with
listProjects/listAndroidApps/listIosApps.
src/commands/skill/index.tsx (1)

168-173: Potential race condition with immediate unmount.

When !context.ready, the setTimeout(..., 0) schedules an immediate unmount. However, the component has already been rendered and may be in the process of painting. Consider using setImmediate or moving this check before rendering to avoid the flash of content.

♻️ Suggested refactor
 async function runDoctorPrecheck(context: PreparationContext): Promise<'accepted' | 'cancelled'> {
+  // Early exit if context is not ready - no need to render the UI
+  if (!context.ready) {
+    return 'cancelled';
+  }
+
   return new Promise((resolve) => {
     const { unmount } = safeRender(
       <DoctorStatusDisplay
         context={context}
         onAccept={
-          context.ready
-            ? () => {
+            () => {
                 unmount();
                 resolve('accepted');
               }
-            : undefined
         }
         onCancel={() => {
           unmount();
           resolve('cancelled');
         }}
       />,
     );
-
-    if (!context.ready) {
-      setTimeout(() => {
-        unmount();
-        resolve('cancelled');
-      }, 0);
-    }
   });
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/commands/skill/index.tsx` around lines 168 - 173, The immediate unmount
scheduled with setTimeout when checking `if (!context.ready)` can cause a render
flash; instead perform this readiness check before rendering or bail out
synchronously so the component never paints. Locate the `if (!context.ready)`
block and either move that conditional above the render/mount logic (so you call
`unmount()` and `resolve('cancelled')` before mounting), or replace the delayed
`setTimeout(..., 0)` with a synchronous early-return/unmount sequence (call
`unmount()` then `resolve('cancelled')` immediately) to avoid the race;
reference `context.ready`, `setTimeout`, `unmount`, and `resolve` when applying
the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/lib/config/project-config-manager.ts`:
- Around line 131-142: The migrated config returned by ensureLatestVersion keeps
the old version, causing repeated saves; after calling
ensureLatestVersion(assign to migratedConfig) update migratedConfig.version to
CURRENT_PROJECT_CONFIG_VERSION (or make ensureLatestVersion set it) before
calling this.save(...) so the persisted config reflects the new version; ensure
you still assign this.cachedConfig = migratedConfig and return it from load().

In `@src/lib/constants/system-prompt.ts`:
- Around line 67-70: Update the section header and entries in the system prompt
constant so they no longer reference “Slash Commands” or include the slash
prefix; specifically replace the header string "## Available Slash Commands"
with something like "## Available Commands" and change the list items
"/install", "/doctor", and "/mcp" to "install", "doctor", and "mcp" respectively
in the exported system prompt (the constant in system-prompt.ts) so the wording
matches the command-only UX.

In `@src/lib/ios/apple-auth.ts`:
- Around line 118-134: The code currently returns lastAppleId early which
prevents switching accounts; instead remove the early return in the if
(lastAppleId) block and keep the interactive prompt flow: show the cached value
as the default to promptFn (pass lastAppleId as the second argument to
promptFn), still call removeControlCharacters on the response and validate
username.trim() in the while loop, and optionally log that a cached Apple ID is
being used as the default; update references in this block (lastAppleId,
promptFn, removeControlCharacters, username) so the prompt allows overriding the
cached value rather than returning it immediately.

In `@src/lib/ios/pbxproj-modifier.ts`:
- Around line 1100-1104: The current logic only calls
addTargetDependencyToMainApp when result.targetAdded is true, so existing
targets that lack the dependency are never fixed; change the check to enforce
dependency whenever targetDependencyAlreadyAdded is false (i.e., call
addTargetDependencyToMainApp(project, targetUuid, result.warnings) when
targetDependencyAlreadyAdded is false regardless of result.targetAdded), and if
you still want to distinguish creation vs existing-target paths emit a clear
warning when skipping adding the dependency; use the existing symbols
result.targetAdded, targetDependencyAlreadyAdded, addTargetDependencyToMainApp,
project and targetUuid to locate and update the condition and any warning
messages.

In `@src/lib/ios/podfile-modifier.ts`:
- Around line 138-141: The replacement currently uses
updatedBlock.replace(/\nend$/i, ...) which fails when the block ends with
indented "end" and can leave clixPodAdded=true despite no insertion; update the
regex in the call to updatedBlock.replace to capture any indentation (e.g.
/\n(\s*)end$/i) and use the captured indentation in the replacement (e.g. `\n$1 
pod '${options.clixPodSpec || 'Clix'}'\n$1end'`) so the pod line keeps the same
indentation, and/or perform a .test with the new regex before replacing to
ensure the replacement actually occurred before setting clixPodAdded
(references: updatedBlock.replace, extractTargetBlock, clixPodAdded,
options.clixPodSpec).

---

Outside diff comments:
In `@src/lib/executors/base-executor.ts`:
- Around line 58-139: lastTextContent is stored as a class field and persists
across executions causing incorrect deltas; reset or relocate that state per
run. Fix by clearing this.lastTextContent = '' at the start of the execution
flow (e.g., in the method that begins a run such as execute or runProcess)
before any calls to getPreparedPrompt/extractTextDelta/processStreamData, or
move the delta state into StreamContext (add e.g., streamContext.lastTextContent
and update extractTextDelta to use it) so concurrent runs don’t share mutable
state; update usages of extractTextDelta/lastTextContent accordingly.

---

Duplicate comments:
In `@src/commands/skill/preparation.ts`:
- Around line 377-389: The early-return treats projectType.target === 'unknown'
as "not needed" and thus marks setup as configured; remove or change that
shortcut so unknown targets are treated as needing setup. Update the logic
around the needed variable and the early return in preparation.ts (the block
using projectType.target and the const needed) so that when projectType.target
=== 'unknown' you do NOT return the configured:true object — instead set needed
= true (or configured = false) and let the normal checks flow so the caller sees
the project as not configured and requiring attention.

---

Nitpick comments:
In @.gitignore:
- Around line 81-85: Add a descriptive section header above the new .gitignore
entries (e.g., a comment like "# Local tools / project-specific" or "# Generated
files and local artifacts") to match the file's existing organization; place
this header immediately above the block containing ".omc", ".ralph-tui",
"llm-council", and "plan.md" so it's clear these are local/tooling or generated
artifacts.
- Around line 82-84: The three entries `.omc`, `.ralph-tui`, and `llm-council`
in the project .gitignore look like personal/tool-specific artifacts; remove
these lines from the project .gitignore and instead add them to each developer's
global gitignore (e.g. ~/.gitignore_global) or document them as optional in
CONTRIBUTING.md so the repo only contains project-wide ignore patterns;
reference the `.gitignore` file and the specific entries `.omc`, `.ralph-tui`,
and `llm-council` when making the change.
- Line 85: The .gitignore currently ignores the generic filename "plan.md",
which may block legitimate project planning files; update the ignore rule by
replacing "plan.md" with a more specific pattern (for example ".plan.md" or
"*.plan.md") or remove it and add the entry to each developer's global
gitignore, and if it's an intentional team convention, document this decision in
the repository so contributors know which planning files are considered personal
versus project assets.

In `@src/commands/skill/index.tsx`:
- Around line 168-173: The immediate unmount scheduled with setTimeout when
checking `if (!context.ready)` can cause a render flash; instead perform this
readiness check before rendering or bail out synchronously so the component
never paints. Locate the `if (!context.ready)` block and either move that
conditional above the render/mount logic (so you call `unmount()` and
`resolve('cancelled')` before mounting), or replace the delayed `setTimeout(...,
0)` with a synchronous early-return/unmount sequence (call `unmount()` then
`resolve('cancelled')` immediately) to avoid the race; reference
`context.ready`, `setTimeout`, `unmount`, and `resolve` when applying the
change.

In `@src/lib/services/firebase/api/firebase-api.ts`:
- Around line 161-175: getAndroidConfig and getIosConfig lack the try/catch +
logApiError pattern used elsewhere, so wrap each method body in a try/catch:
call await this.updateCredentials() and the fb API inside the try, return the
decoded config on success, and in the catch call logApiError(this.logger, err,
'getAndroidConfig') / logApiError(this.logger, err, 'getIosConfig') (matching
the existing logApiError usage), then rethrow the error so callers still receive
it; this makes error logging consistent with
listProjects/listAndroidApps/listIosApps.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 400961e and e0b7604.

📒 Files selected for processing (53)
  • .gitignore
  • README.md
  • llms.txt
  • scripts/embed-skills.ts
  • src/cli.tsx
  • src/commands/__tests__/mcp.test.ts
  • src/commands/__tests__/skills.test.ts
  • src/commands/doctor.tsx
  • src/commands/install.tsx
  • src/commands/mcp.ts
  • src/commands/skill/__tests__/preparation.test.ts
  • src/commands/skill/index.tsx
  • src/commands/skill/preparation.ts
  • src/commands/skills.ts
  • src/lib/__tests__/embedded-skills.test.ts
  • src/lib/__tests__/skills.test.ts
  • src/lib/__tests__/test-utils/fixtures.ts
  • src/lib/config/project-config-manager.ts
  • src/lib/constants/system-prompt.ts
  • src/lib/debug/logger.ts
  • src/lib/embedded-skills.ts
  • src/lib/executor.ts
  • src/lib/executors/__tests__/gemini-executor.test.ts
  • src/lib/executors/__tests__/opencode-executor.test.ts
  • src/lib/executors/base-executor.ts
  • src/lib/executors/claude-executor.ts
  • src/lib/executors/cli-process-manager.ts
  • src/lib/executors/cursor-executor.ts
  • src/lib/executors/gemini-executor.ts
  • src/lib/executors/opencode-executor.ts
  • src/lib/executors/types.ts
  • src/lib/ios/apple-auth.ts
  • src/lib/ios/apple-portal.ts
  • src/lib/ios/pbxproj-modifier.ts
  • src/lib/ios/podfile-modifier.ts
  • src/lib/services/__tests__/agent-handoff.test.ts
  • src/lib/services/agent-handoff.ts
  • src/lib/services/firebase/api/firebase-api.ts
  • src/lib/services/firebase/downloader.ts
  • src/lib/services/firebase/oauth/auth-client.ts
  • src/lib/services/organization-projects.ts
  • src/lib/services/project-detector.ts
  • src/lib/skills.ts
  • src/lib/skills/doctor/SKILL.md
  • src/lib/skills/install/SKILL.md
  • src/lib/utils/oauth.ts
  • src/ui/IosSetupUI.tsx
  • src/ui/components/FirebaseConfigFilesSetup.tsx
  • src/ui/components/FirebaseServiceAccountSetup.tsx
  • src/ui/components/InstallPreparationUI.tsx
  • src/ui/components/firebase-detection-utils.ts
  • src/ui/components/ios-setup/IosEntitlementsTask.tsx
  • tests/e2e/cli.test.ts
💤 Files with no reviewable changes (2)
  • src/lib/executors/types.ts
  • src/lib/tests/test-utils/fixtures.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • src/lib/executors/tests/gemini-executor.test.ts
  • src/lib/executors/tests/opencode-executor.test.ts
  • src/lib/services/tests/agent-handoff.test.ts
  • src/commands/tests/mcp.test.ts
  • src/lib/services/organization-projects.ts
  • src/commands/skills.ts

Comment thread src/lib/config/project-config-manager.ts
Comment thread src/lib/constants/system-prompt.ts Outdated
Comment thread src/lib/ios/apple-auth.ts
Comment thread src/lib/ios/pbxproj-modifier.ts
Comment thread src/lib/ios/podfile-modifier.ts
- Set version to CURRENT_PROJECT_CONFIG_VERSION in ensureLatestVersion
  to prevent needless re-save on every startup
- Rename "Slash Commands" to "Available Commands" in system prompt
- Allow Apple ID override from cache instead of early return
- Enforce NSE target dependency for existing targets missing it
- Fix pod insertion regex to handle indented end blocks
…I help, and improve README table formatting.
Previously ensureGitignore was only called after project selection in
SetupUI/LoginUI. If the user interrupted (Ctrl+C) after login but before
project selection, the .clix directory was created without a matching
.gitignore entry. Extracted gitignore logic to shared utility and moved
the call into directory creation (ensureStateDir/ensureConfigDir) so it
runs exactly once at the earliest possible moment.
…rebase iOS app Team ID management

- Introduce setExitCode() utility and centralize process.exit() to a single
  main().finally() guard to prevent terminal hang on command cancellation
- Fix P8 file path input corruption when drag-dropping files from macOS Finder
  by replacing ink-text-input with ref-based accumulation (avoids React state
  batching race condition with multi-chunk stdin paste)
- Add Firebase iOS app patchIosApp API for updating teamId/appStoreId
- Detect Team ID mismatch between APNS key and Firebase iOS app after
  registration, with option to auto-update
- Refactor IosSetupUI Apple login from terminal readline prompts to Ink-based
  TextInput UI with session restore support
- Pass appName from Xcode project analysis to Firebase app creation
@pitzcarraldo pitzcarraldo merged commit 30d0696 into main Feb 24, 2026
5 checks passed
@pitzcarraldo pitzcarraldo deleted the feat/refactor-firebase-apis branch February 24, 2026 04:43
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