Skip to content

feat: migrate storage to project-local .clix/#21

Merged
pitzcarraldo merged 4 commits into
mainfrom
feat/migrate-storage-paths
Feb 5, 2026
Merged

feat: migrate storage to project-local .clix/#21
pitzcarraldo merged 4 commits into
mainfrom
feat/migrate-storage-paths

Conversation

@pitzcarraldo

@pitzcarraldo pitzcarraldo commented Feb 5, 2026

Copy link
Copy Markdown
Contributor

Summary

Consolidates session and credential storage into project-local .clix/ directory for improved multi-workspace management. Sessions move from XDG_STATE_HOME to .clix/sessions/, and both Clix Auth0 and Firebase OAuth tokens are unified in .clix/credentials.json.

Details

Storage Changes:

  • Sessions: ~/.local/state/clix/sessions/project/.clix/sessions/
  • Clix credentials: ~/.local/state/clix/credentials.jsonproject/.clix/credentials.json
  • Firebase tokens: ~/.config/clix/firebase-tokens.jsonproject/.clix/credentials.json (unified)
  • Global config: remains in ~/.config/clix/config.json

Unified Credentials Structure:

{
  version: 1,
  clix?: { accessToken, refreshToken, ... },
  firebase?: { access_token, refresh_token, ... }
}

API Changes:

  • New: CredentialsManager.getClixCredentials(), saveClixCredentials()
  • New: CredentialsManager.getFirebaseTokens(), saveFirebaseTokens()
  • TokenStore now delegates to CredentialsManager

How to Validate

  1. Build and test: bun run check && bun test && bun run build && bun test tests/e2e/
  2. Login and verify session stored in .clix/sessions/
  3. Test Firebase OAuth: verify tokens in .clix/credentials.json under firebase key
  4. Test project isolation: sessions from different projects are separate

Pre-Merge Checklist

  • Code builds without errors
  • Types check correctly
  • Linter passes
  • Tests pass (578 unit + 20 E2E)
  • Updated documentation (CLAUDE.md)
  • Conventional Commits format

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Unified on-disk credentials combining Clix (Auth) and Firebase tokens; added unified token management and retrieval.
  • Documentation

    • Security and config guidance updated; default storage referenced as project-local .clix/ and reminder not to commit API keys/user data.
  • Refactor

    • Credential and session IO centralized into the unified store and session directory moved to project-local .clix/; project root discovery utility added.
  • Bug Fixes

    • Logout now clears login credentials while preserving other stored tokens.

Consolidate all project-specific data into .clix/ directory:
- Move sessions from XDG_STATE_HOME/clix/sessions/ to .clix/sessions/
- Move auth credentials from XDG_STATE_HOME/clix/ to .clix/credentials.json
- Move Firebase tokens into unified credentials.json structure
- Keep global config in XDG_CONFIG_HOME/clix/config.json

Unified credentials structure:
  version: 1
  clix?: { Auth0 tokens }
  firebase?: { Firebase OAuth tokens }

This enables per-project credential management while maintaining
global user preferences, improving multi-workspace workflows.

Co-Authored-By: Claude (global.anthropic.claude-haiku-4-5-20251001-v1:0) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Feb 5, 2026

Copy link
Copy Markdown

Walkthrough

Move credential storage to a project-local .clix/ directory, introduce separate Clix and Firebase credential schemas with a composite credentials file, extend CredentialsManager with Clix- and Firebase-specific APIs and refresh logic, delegate Firebase token IO to the manager, update session path discovery to project root, and update UI and docs to the new shapes and paths.

Changes

Cohort / File(s) Summary
Auth schema & exports
src/lib/auth/schema.ts, src/lib/auth/index.ts
Add ClixCredentialsSchema and FirebaseTokensSchema, create composite CredentialsSchema, export ClixCredentials, FirebaseTokens, createClixCredentials (with deprecated alias), and update public type exports.
Credentials manager
src/lib/auth/credentials.ts
Unify on-disk store format to include clix and firebase subobjects under project .clix/credentials.json; add Clix-specific APIs (getClixCredentials, saveClixCredentials, clearClixCredentials, isClixExpired, refreshAccessToken, deprecated isExpired) and Firebase APIs (getFirebaseTokens, saveFirebaseTokens, clearFirebaseTokens, hasFirebaseTokens, isFirebaseExpired, hasFirebaseRefreshToken); refactor token refresh/get flows to use Clix path.
Firebase token IO → manager
src/lib/services/firebase/oauth/token-store.ts
Remove file-based IO and internal tokenPath; delegate load/save/clear/exists/isExpired/hasRefreshToken to CredentialsManager Firebase methods.
Session & transfer storage paths
src/lib/services/session-store.ts, src/lib/services/transfer-service.ts, src/lib/utils/path.ts
Change default session/transfer resolution from XDG state to project-root .clix/sessions/ via findProjectRoot(); add findProjectRoot and formatPath utilities; remove xdg usage.
UI updates
src/ui/LoginUI.tsx, src/ui/SetupUI.tsx, src/ui/LogoutUI.tsx
Switch UIs to use ClixCredentials and createClixCredentials, update persistence calls to saveClixCredentials / clearClixCredentials, read stored credentials via credentials?.clix, and adjust onComplete/logout checks accordingly (prop types/signatures updated).
Docs & examples
AGENTS.md, README.md, llms.txt
Update documentation and examples to reference global config at $XDG_CONFIG_HOME/clix/config.json (default ~/.config/clix/config.json) and project-local .clix/ (.clix/sessions/, .clix/credentials.json) for sessions and credentials; adjust transfer examples and paths.

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 66.67% 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 clearly and concisely describes the main change: migrating storage from global/XDG paths to a project-local .clix/ directory structure.
Description check ✅ Passed The description covers all required template sections: Summary explains the consolidation goal, Details lists specific storage paths and API changes, How to Validate provides validation steps, and Pre-Merge Checklist confirms all checks passed.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/migrate-storage-paths

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: fe9e078f0e

ℹ️ 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/auth/credentials.ts
Use clearClixCredentials() instead of delete() in LogoutUI to only
clear Clix auth tokens while preserving Firebase OAuth tokens.

Co-Authored-By: Claude (global.anthropic.claude-haiku-4-5-20251001-v1:0) <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: 2

Caution

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

⚠️ Outside diff range comments (3)
src/lib/services/session-store.ts (1)

42-53: ⚠️ Potential issue | 🟠 Major

Ensure project-local sessions are gitignored (or keep the XDG state path).

Project-local session storage can be committed accidentally, and this path bypasses any gitignore enforcement. If the migration is intentional, add an automatic .clix/.gitignore (or root .gitignore entry) when using the default path; otherwise keep the XDG state directory.

🛡️ Suggested guard to prevent accidental commits
 async function ensureSessionsDir(): Promise<string> {
   const dir = resolveSessionsDir();
   await fs.mkdir(dir, { recursive: true });
+  if (!process.env.CLIX_SESSION_DIR) {
+    const clixDir = path.dirname(dir);
+    const gitignorePath = path.join(clixDir, '.gitignore');
+    await fs
+      .writeFile(gitignorePath, '*\n!.gitignore\n', { flag: 'wx' })
+      .catch(() => undefined);
+  }
   return dir;
 }

As per coding guidelines: Do not commit API keys or user data. Store local config in $XDG_CONFIG_HOME/clix/config.json and sessions in $XDG_STATE_HOME/clix/sessions/.

src/lib/services/transfer-service.ts (1)

41-58: ⚠️ Potential issue | 🟠 Major

Session markdown under .clix needs gitignore protection.

Conversation history is user data; writing it under .clix without ensuring ignore rules risks accidental commits. Consider creating a .clix/.gitignore when using the default path, or keep session artifacts in the XDG state directory.

🛡️ Suggested guard to prevent accidental commits
 async function saveSessionHistory(history: ConversationMessage[]): Promise<string> {
   const clixDir = join(process.cwd(), '.clix');

   // Ensure .clix directory exists
   try {
     await mkdir(clixDir, { recursive: true });
   } catch (_error) {
     // Directory might already exist, ignore error
   }
+  await writeFile(join(clixDir, '.gitignore'), '*\n!.gitignore\n', {
+    encoding: 'utf-8',
+    flag: 'wx',
+  }).catch(() => undefined);

As per coding guidelines: Do not commit API keys or user data. Store local config in $XDG_CONFIG_HOME/clix/config.json and sessions in $XDG_STATE_HOME/clix/sessions/.

src/lib/auth/credentials.ts (1)

24-47: ⚠️ Potential issue | 🟠 Major

Keep credentials in XDG user scope, not project/.clix.

Storing credentials under the project directory risks accidental commits and conflicts with the established two-config architecture. Please keep credentials in the user-level XDG state/config location and only keep project metadata in .clix/ (or namespace per-project under XDG if isolation is needed).

💡 Possible fix (align with XDG state home)
+import { homedir } from 'node:os';
 import { chmod, mkdir, readFile, rm, stat, writeFile } from 'node:fs/promises';
 import { join } from 'node:path';

   constructor(customStateDir?: string) {
-    this.stateDirPath = customStateDir ?? join(process.cwd(), '.clix');
+    const xdgStateHome =
+      process.env.XDG_STATE_HOME ?? join(homedir(), '.local', 'state');
+    this.stateDirPath = customStateDir ?? join(xdgStateHome, 'clix');
     this.credentialsFilePath = join(this.stateDirPath, 'credentials.json');
   }

As per coding guidelines: Do not commit API keys or user data. Store local config in $XDG_CONFIG_HOME/clix/config.json and sessions in $XDG_STATE_HOME/clix/sessions/.
Based on learnings: Clix CLI uses a two-config architecture: (1) User-level config at $XDG_CONFIG_HOME/clix/config.json for credentials and global preferences, (2) Project-local config at .clix/config.jsonc in the project root for project-specific metadata (which Clix project/org the directory is linked to).

🤖 Fix all issues with AI agents
In `@AGENTS.md`:
- Around line 260-265: The documentation is inconsistent: AGENTS.md documents
global config at `$XDG_CONFIG_HOME/clix/config.json` (default
`~/.config/clix/config.json`) and project-local storage at `.clix/sessions/` and
`.clix/credentials.json`, but README.md and llms.txt still reference the old
`~/.clix/config.json` path and do not mention the project-local `.clix/` layout;
update README.md and llms.txt to match AGENTS.md by changing any
`~/.clix/config.json` references to `~/.config/clix/config.json` (or
`$XDG_CONFIG_HOME/clix/config.json`) and add concise entries documenting the
project-local storage locations `.clix/sessions/` and `.clix/credentials.json`
so all three files (AGENTS.md, README.md, llms.txt) consistently describe global
vs project-local config paths.

In `@src/lib/auth/credentials.ts`:
- Around line 377-397: The current checks allow empty/partial FirebaseTokens to
be treated as valid: update hasFirebaseTokens() to verify the token object
contains required, non-empty fields (e.g., access_token and expiry_date, and
optionally refresh_token) rather than only non-null, and update
isFirebaseExpired(tokens: FirebaseTokens) to treat a missing or falsy
expiry_date as expired (return true) so tokens without expiry info will be
refreshed; refer to the methods hasFirebaseTokens and isFirebaseExpired to
locate and change the logic accordingly.
🧹 Nitpick comments (1)
src/lib/services/firebase/oauth/token-store.ts (1)

18-77: Consider module-level functions instead of a class for TokenStore.

Project conventions prefer plain objects/interfaces; exporting functions would align better and avoid class instantiation.

As per coding guidelines: Prefer plain objects with interfaces over classes. Use ES module exports for encapsulation.

Comment thread AGENTS.md
Comment thread src/lib/auth/credentials.ts
…lidation

- Update README.md and llms.txt to reflect project-local storage paths
- Fix config path reference from ~/.clix/config.json to ~/.config/clix/config.json
- Update session path references from ~/.local/state/clix to .clix/sessions
- Improve hasFirebaseTokens() to check for actual token content
- Improve isFirebaseExpired() to treat missing access_token or expiry_date as expired

Addresses PR review feedback about documentation inconsistency and
empty/partial Firebase token handling.

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/auth/credentials.ts`:
- Around line 45-47: The constructor for the credentials manager currently sets
stateDirPath using process.cwd(), causing per-subdirectory credential stores;
change it to resolve the project root instead (reuse the existing
ProjectConfigManager/project-root resolver used elsewhere) and set
this.stateDirPath = join(resolvedProjectRoot, '.clix') (fall back to
process.cwd() only if resolver returns undefined). Update usage of
credentialsFilePath accordingly; ensure you import and call the same resolver
that ProjectConfigManager/ensureGitignore() uses so credentials are always
stored relative to the actual project root.
🧹 Nitpick comments (1)
llms.txt (1)

1285-1287: Add the project-local .clix/config.jsonc to the storage paths summary.
Right now it only mentions sessions and credentials under .clix/, which can leave out the project metadata file.

Based on learnings: Clix CLI uses a two-config architecture: (1) User-level config at $XDG_CONFIG_HOME/clix/config.json for credentials and global preferences, (2) Project-local config at .clix/config.jsonc in the project root for project-specific metadata (which Clix project/org the directory is linked to). The project-local config is automatically gitignored and follows patterns from tools like VSCode, IntelliJ, and Claude Code.

Comment thread src/lib/auth/credentials.ts
Add findProjectRoot() utility that walks up directories to find project
root markers (package.json, .git, .clix, etc.) instead of using
process.cwd() directly. This ensures credentials and sessions are stored
relative to the actual project root, not the current working directory.

Applied to:
- CredentialsManager: credentials stored at project_root/.clix/
- session-store: sessions stored at project_root/.clix/sessions/
- transfer-service: session files saved to project_root/.clix/

This prevents inconsistent behavior when users run clix from
subdirectories.

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: 0

Caution

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

⚠️ Outside diff range comments (1)
src/lib/auth/credentials.ts (1)

25-48: ⚠️ Potential issue | 🟠 Major

Potential policy conflict: credentials now stored inside project tree.

Storing credentials in project/.clix/credentials.json increases the risk of accidental commit and conflicts with the documented split between user‑level credentials and project‑level metadata. If this change is intentional, please update the policy/docs and ensure .clix/ is always gitignored (and possibly excluded from tooling that scans repo files). Otherwise, keep credentials in the user‑level XDG config/state paths.
As per coding guidelines: “Do not commit API keys or user data. Store local config in $XDG_CONFIG_HOME/clix/config.json and sessions in $XDG_STATE_HOME/clix/sessions/.”
Based on learnings: “Clix CLI uses a two-config architecture: (1) User-level config at $XDG_CONFIG_HOME/clix/config.json for credentials and global preferences, (2) Project-local config at .clix/config.jsonc for project-specific metadata.”

🧹 Nitpick comments (1)
src/lib/utils/path.ts (1)

24-42: Minor: Redundant process.cwd() call.

The fallback at line 41 recalculates startDir ?? process.cwd() even though the same value was already computed at line 25. Consider capturing the original starting directory to avoid the redundant call.

♻️ Suggested simplification
 export function findProjectRoot(startDir?: string): string {
-  let currentDir = startDir ?? process.cwd();
+  const originalDir = startDir ?? process.cwd();
+  let currentDir = originalDir;
   const { root } = parse(currentDir);
 
   while (currentDir !== root) {
     // Check for any project markers
     for (const marker of PROJECT_MARKERS) {
       const markerPath = join(currentDir, marker);
       if (existsSync(markerPath)) {
         return currentDir;
       }
     }
     // Move up one directory
     currentDir = dirname(currentDir);
   }
 
   // No markers found, return the original starting directory
-  return startDir ?? process.cwd();
+  return originalDir;
 }

@pitzcarraldo pitzcarraldo merged commit 5e5ad31 into main Feb 5, 2026
5 checks passed
@pitzcarraldo pitzcarraldo deleted the feat/migrate-storage-paths branch February 5, 2026 06:55
@pitzcarraldo pitzcarraldo self-assigned this Feb 5, 2026
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