Skip to content

feat(cli): add org (-o) and token (-t) flags#18

Merged
wiiiimm merged 16 commits into
mainfrom
feature/cli-org-flag
Sep 2, 2025
Merged

feat(cli): add org (-o) and token (-t) flags#18
wiiiimm merged 16 commits into
mainfrom
feature/cli-org-flag

Conversation

@wiiiimm
Copy link
Copy Markdown
Owner

@wiiiimm wiiiimm commented Sep 2, 2025

This PR combines two CLI enhancements and related UI wiring and docs.\n\nFeatures\n- Add organisation context flag: --org with short alias -o\n - Allows starting the app scoped to an accessible organisation\n - Waits for UI prefs to load; applies once per run\n- Add ephemeral token flag: --token with short alias -t\n - Token precedence: CLI > env (GITHUB_TOKEN/GH_TOKEN) > stored config\n - Does not persist when provided via CLI flag\n\nImplementation\n- src/index.tsx: flag parsing and help text updates\n- src/ui/App.tsx: prefer inline token; skip persistence when ephemeral\n- src/ui/RepoList.tsx: initial org flag application; stable onOrgContextChange ref\n\nDocs\n- README + wiki/Usage: document --org/-o and --token/-t with examples\n- wiki/Token-and-Security: add precedence and safety notes for CLI tokens\n- TODOs: track --token/-t work under CLI flags\n\nNotes\n- Includes RepoList optimizations (useCallback; stable handler ref)\n- Tested locally for flag parsing precedence and help output

Summary by CodeRabbit

  • New Features

    • CLI flags: --org / -o (start in an organisation, optional leading @), --token / -t (ephemeral per-run PAT, precedence over env/stored), --help / -h, --version / -v.
    • App accepts initialOrgSlug/inlineToken and can auto‑apply an organisation on startup; improved startup, shutdown, error and debug logging.
  • Documentation

    • Expanded README, wiki and TODOs with CLI flags, navigation/view controls, repository actions, token precedence and security guidance (README contains duplicate block).

cursor[bot]

This comment was marked as outdated.

cursoragent and others added 5 commits September 2, 2025 15:50
Co-authored-by: william <william@alt-x.ventures>
…e --token and -t (value or =value)\n- Prefer inline token over env/config for this run\n- Do not persist when provided via CLI flag\n- Help text: document token flag in usage
…n\n- README: add usage, examples, and non-persistence\n- wiki/Usage: add flag, examples, precedence, and security note
…cument precedence and non-persistence of CLI token\n- Add security considerations and safer usage examples
cursor[bot]

This comment was marked as outdated.

…ontext

Co-authored-by: william <william@alt-x.ventures>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Sep 2, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds CLI flags (--org/-o, --token/-t) and related docs; index.tsx parses and normalises flags, adds startup/shutdown and fatal-error logging; App accepts initialOrgSlug/inlineToken and enforces token precedence and conditional persistence; RepoList applies initial organisation context on mount.

Changes

Cohort / File(s) Summary
Docs: README & TODOs
README.md, TODOs.md
Expanded CLI flags and interactive controls docs; documented --org/-o and --token/-t usage, examples, precedence and security notes; marked --org/--token behaviour in TODOs as implemented.
Wiki updates
wiki/Usage.md, wiki/Token-and-Security.md
Added CLI Flags section and CLI token vs environment guidance; documented precedence (CLI > env > stored), non-persistence of inline tokens, examples and security caveats; minor formatting trim.
CLI bootstrap & runtime
src/index.tsx
Added flag parsing helpers and support for --org/-o and --token/-t (with normalization); extended help output; pass initialOrgSlug, inlineToken, inlineTokenEphemeral to App; added GH_MANAGER_DEBUG, startup logs, signal handlers and uncaught exception/rejection handlers.
App auth precedence & persistence
src/ui/App.tsx
App now accepts initialOrgSlug, inlineToken, inlineTokenEphemeral; introduced sessionTokenOrigin; implemented token selection precedence (cli > env > stored > prompt), conditional persistence logic, updated validation/logout logging, and forwards initialOrgSlug to RepoList.
RepoList initial org context
src/ui/RepoList.tsx
Added optional initialOrgSlug prop; one-time init effect fetches viewer organisations and switches org context if slug matches (case-insensitive, optional leading @); uses stable handler ref, guards by token/prefs readiness, and emits debug messages.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant User
  participant CLI as index.tsx
  participant App as App
  participant RepoList as RepoList
  participant GH as GitHub API

  Note over CLI: Parse flags (--org/-o, --token/-t)
  User->>CLI: Run gh-manager-cli [--org SLUG] [--token PAT]
  CLI->>App: Start App(initialOrgSlug, inlineToken, inlineTokenEphemeral)

  rect rgba(200,230,255,0.18)
  Note right of App: Token selection & validation
  App->>App: Determine token origin (cli > env > stored > prompt)
  alt Token present
    App->>GH: Validate token
    GH-->>App: Valid / Invalid
    alt Valid
      App->>App: Persist token? (!stored && !inlineTokenEphemeral)
    end
  else No token
    App->>User: Prompt for auth method/token
  end
  end

  App->>RepoList: Render with initialOrgSlug
  rect rgba(220,255,220,0.18)
  Note over RepoList: Apply initial --org if provided
  RepoList->>GH: Fetch viewer organisations
  GH-->>RepoList: Organisations list
  alt Slug matches accessible org
    RepoList->>RepoList: Switch org context
  else No match / inaccessible
    RepoList->>RepoList: Keep default context
  end
  end
Loading
sequenceDiagram
  autonumber
  participant Proc as Process
  participant Logger as Logger

  Proc->>Logger: Startup log (version, Node.js)
  Proc->>Logger: GH_MANAGER_DEBUG toggles verbose logs
  Proc->>Proc: Register SIGINT/SIGTERM/exit handlers
  Proc->>Logger: On signal -> log and exit
  Proc->>Logger: On uncaughtException/unhandledRejection -> log fatal and exit(1)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

I twitched my whiskers, parsed a flag,
Hopped to an org without a snag.
Tokens took their proper place,
Logs bowed out with calm, neat grace.
A rabbit ships, then naps — hooray! 🐇✨


📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 6883902 and 4bce3a2.

📒 Files selected for processing (1)
  • src/ui/App.tsx (9 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/ui/App.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cursor Bugbot
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/cli-org-flag

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (3)
src/ui/RepoList.tsx (2)

627-629: Bug: visibilityFilter may be set to unsupported value “internal”

Type is 'all' | 'public' | 'private', but persisted value may be "internal". This leads to inconsistent filtering.

Apply this diff to normalise persisted "internal" to "private":

-    if (ui.visibilityFilter && ['all', 'public', 'private', 'internal'].includes(ui.visibilityFilter)) {
-      setVisibilityFilter(ui.visibilityFilter as VisibilityFilter);
-    }
+    if (ui.visibilityFilter && ['all', 'public', 'private', 'internal'].includes(ui.visibilityFilter)) {
+      const vf = ui.visibilityFilter === 'internal' ? 'private' : ui.visibilityFilter;
+      setVisibilityFilter(vf as VisibilityFilter);
+    }

756-776: Bug: orgLogin is undefined in search bootstrap effect

makeSearchKey call references orgLogin which isn’t defined in this scope. This will crash at runtime.

Apply this diff to keep the key consistent with other usages and remove the bad refs:

-      try {
-        const key = makeSearchKey({
-          viewer: viewerLogin || 'unknown',
-          q: filter.trim(),
-          sortKey,
-          sortDir,
-          pageSize: PAGE_SIZE,
-          forkTracking,
-          ownerContext: orgLogin ? `org:${orgLogin}` : 'personal',
-          affiliations: ownerAffiliations.join(',')
-        });
-        policy = isFresh(key, 90 * 1000) ? 'cache-first' : 'network-only';
-      } catch {}
+      try {
+        const key = makeSearchKey({
+          viewer: viewerLogin || 'unknown',
+          q: filter.trim(),
+          sortKey,
+          sortDir,
+          pageSize: PAGE_SIZE,
+          forkTracking,
+        });
+        policy = isFresh(key, 90 * 1000) ? 'cache-first' : 'network-only';
+      } catch {}
src/ui/App.tsx (1)

51-71: Do not persist env-provided tokens; set tokenSource for inline/env/stored

Currently, env tokens will be persisted if no stored token exists, which is a security footgun. Also, tokenSource isn’t set when using an inline token, which undermines downstream logic/telemetry.

   const env = getTokenFromEnv();
   const stored = getStoredToken();
   const source = getTokenSource();

   setTokenSource(source);

-  if (inlineToken) {
-    // Highest precedence: inline token from CLI flag; do not persist
-    setToken(inlineToken);
-    setMode('validating');
-  } else if (env) {
-    setToken(env);
-    setMode('validating');
-  } else if (stored) {
-    setToken(stored);
-    setMode('validating');
-  } else {
-    setMode('auth_method_selection');
-  }
+  if (inlineToken) {
+    // Highest precedence: inline token from CLI flag; do not persist
+    setTokenSource('inline');
+    setToken(inlineToken);
+    setMode('validating');
+  } else if (env) {
+    setTokenSource('env');
+    setToken(env);
+    setMode('validating');
+  } else if (stored) {
+    setTokenSource('stored');
+    setToken(stored);
+    setMode('validating');
+  } else {
+    setMode('auth_method_selection');
+  }
🧹 Nitpick comments (14)
TODOs.md (1)

345-347: Consistent English variant (“organisation” vs “organization”)

This file mixes variants. Pick one (project seems to prefer British English) and standardise across docs.

wiki/Usage.md (1)

7-10: Document case‑insensitive slug matching (align with code)

The app matches org slugs case‑insensitively. Add a note for users.

Apply this diff:

-  - Leading `@` is optional. Personal usernames are not supported by `--org`/`-o` (use default personal context).
+  - Leading `@` is optional. Personal usernames are not supported by `--org`/`-o` (use default personal context).
+  - Matching is case‑insensitive.
src/ui/RepoList.tsx (3)

345-351: Reuse existing GraphQL client instead of recreating

Avoid recreating the client in handleOrgContextChange.

Apply this diff:

-      const client = makeClient(token);
-      const isEnt = await checkOrganizationIsEnterprise(client, newContext.login);
+      const isEnt = await checkOrganizationIsEnterprise(client, newContext.login);

501-506: Reuse existing GraphQL client in fetchPage

No need to re-instantiate a client for the ENT check.

Apply this diff:

-        const client = makeClient(token);
-        checkOrganizationIsEnterprise(client, orgLogin).then(isEnt => {
+        checkOrganizationIsEnterprise(client, orgLogin).then(isEnt => {
           setIsEnterpriseOrg(isEnt);
         });

1336-1336: Include all modals in modalOpen to ensure consistent dimming

sortMode and changeVisibilityMode aren’t included; header/list may not dim consistently.

Apply this diff:

-  const modalOpen = deleteMode || archiveMode || syncMode || logoutMode || infoMode || visibilityMode;
+  const modalOpen = deleteMode || archiveMode || syncMode || logoutMode || infoMode || visibilityMode || changeVisibilityMode || sortMode;
README.md (3)

211-222: Standardise spelling: use “organization” consistently

README mixes “organisation” and “organization”. Given GitHub uses “organization”, stick to that for consistency.

-### CLI Flags
-
-- `--org, -o <slug>`: Start in a specific organisation context (if accessible). Ignores the flag if you don’t have access or if the slug isn’t an organisation.
+### CLI Flags
+
+- `--org, -o <slug>`: Start in a specific organization context (if accessible). Ignores the flag if you don’t have access or if the slug isn’t an organization.

223-233: Minor punctuation/formatting nits

Hyphenation and punctuation around list items can trip LanguageTool (“loose punctuation”). Not blocking, but consider a blank line before each bullet group and consistent punctuation at ends.


410-411: Fix command typo

There’s an extra “-cli” in the combined env example.

-REPOS_PER_FETCH=5 GH_MANAGER_DEBUG=1 npx gh-manager-cli-cli
+REPOS_PER_FETCH=5 GH_MANAGER_DEBUG=1 npx gh-manager-cli
src/ui/App.tsx (3)

170-175: Logging field inverted/misleading

tokenStored: !getStoredToken() reads as “token stored = true when none exists”. Rename and flip for clarity.

-          tokenStored: !getStoredToken()
+          hadStoredToken: Boolean(getStoredToken())

30-31: Unused state: deviceCodeResponse

deviceCodeResponse is set but never read. Remove state/import if not needed.


322-341: Header formatting nit

org/login renders like org/@viewer. Consider @viewer (org) or org • @viewer for readability. Non-blocking.

src/index.tsx (3)

13-40: CLI helpers are simple and robust

Covers --name=value, --name value, -x=value, -x value. Consider supporting -o@acme as a convenience in future.

-const getShortFlagValue = (short: string): string | undefined => {
+const getShortFlagValue = (short: string): string | undefined => {
   // Supports -x value and -x=value
   const exact = `-${short}`;
-  const idx = argv.findIndex(a => a === exact || a.startsWith(`${exact}=`));
+  const idx = argv.findIndex(a =>
+    a === exact || a.startsWith(`${exact}=`) || a.startsWith(`${exact}@`)
+  );
   if (idx === -1) return undefined;
   const at = argv[idx];
   if (at.includes('=')) {
     const [, v] = at.split('=');
     return v?.trim() || undefined;
   }
+  if (at.startsWith(`${exact}@`)) {
+    return at.slice(exact.length + 1).trim(); // -o@acme
+  }
   const next = argv[idx + 1];
   if (next && !next.startsWith('-')) return next.trim();
   return undefined;
 };

52-56: Help text: US spelling and short flags

Minor copyedit to align with README and GitHub’s terminology; include short flags in the help for symmetry.

-    `  gh-manager-cli --org, -o <slug>    Start in an organisation context (if accessible)\n` +
-    `  gh-manager-cli --token, -t <pat>   Use a token just for this run (not persisted)\n` +
-    `  gh-manager-cli --version           Print version\n` +
-    `  gh-manager-cli --help              Show help\n\n` +
+    `  gh-manager-cli --org, -o <slug>    Start in an organization context (if accessible)\n` +
+    `  gh-manager-cli --token, -t <pat>   Use a token just for this run (not persisted)\n` +
+    `  gh-manager-cli --version, -v       Print version\n` +
+    `  gh-manager-cli --help, -h          Show help\n\n` +

105-112: Normalise org slug to lowercase

GitHub org slugs are lowercase; normalising helps matching.

-  return v.replace(/^@/, '');
+  return v.replace(/^@/, '').toLowerCase();
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between cfadd7a and 48bf291.

📒 Files selected for processing (7)
  • README.md (1 hunks)
  • TODOs.md (1 hunks)
  • src/index.tsx (3 hunks)
  • src/ui/App.tsx (5 hunks)
  • src/ui/RepoList.tsx (3 hunks)
  • wiki/Token-and-Security.md (1 hunks)
  • wiki/Usage.md (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
src/ui/App.tsx (1)
src/config.ts (1)
  • getStoredToken (64-67)
src/index.tsx (1)
src/ui/App.tsx (1)
  • App (16-513)
src/ui/RepoList.tsx (2)
src/config.ts (1)
  • OwnerContext (7-7)
src/github.ts (2)
  • makeClient (10-14)
  • fetchViewerOrganizations (117-136)
🪛 LanguageTool
TODOs.md

[uncategorized] ~345-~345: Do not mix variants of the same word (‘organisation’ and ‘organization’) within a single text.
Context: ...nch with --org <slug> to jump to that organisation context if accessible; otherwise ignore...

(EN_WORD_COHERENCY)

wiki/Usage.md

[uncategorized] ~7-~7: Loose punctuation mark.
Context: ...ies. ## CLI Flags - --org, -o <slug>: Start in a specific organisation contex...

(UNLIKELY_OPENING_PUNCTUATION)


[uncategorized] ~11-~11: Loose punctuation mark.
Context: ...ersonal context). - --token, -t <pat>: Provide a Personal Access Token just fo...

(UNLIKELY_OPENING_PUNCTUATION)


[uncategorized] ~16-~16: Loose punctuation mark.
Context: ... the interactive prompt. - --help, -h: Show usage information and exit. - `--...

(UNLIKELY_OPENING_PUNCTUATION)


[uncategorized] ~18-~18: Loose punctuation mark.
Context: ...information and exit. - --version, -v: Print the current version and exit. ##...

(UNLIKELY_OPENING_PUNCTUATION)

README.md

[uncategorized] ~213-~213: Loose punctuation mark.
Context: ...ow: ### CLI Flags - --org, -o <slug>: Start in a specific organisation contex...

(UNLIKELY_OPENING_PUNCTUATION)


[uncategorized] ~213-~213: Do not mix variants of the same word (‘organisation’ and ‘organization’) within a single text.
Context: ...--org, -o <slug>: Start in a specific organisation context (if accessible). Ignores the fl...

(EN_WORD_COHERENCY)


[uncategorized] ~213-~213: Do not mix variants of the same word (‘organisation’ and ‘organization’) within a single text.
Context: ...n’t have access or if the slug isn’t an organisation. - Examples: - `gh-manager-cli --...

(EN_WORD_COHERENCY)


[uncategorized] ~223-~223: Loose punctuation mark.
Context: ...ersonal context). - --token, -t <pat>: Use a Personal Access Token just for th...

(UNLIKELY_OPENING_PUNCTUATION)


[uncategorized] ~230-~230: Loose punctuation mark.
Context: ...ve prompt when possible. - --help, -h: Show usage information and exit. - `--...

(UNLIKELY_OPENING_PUNCTUATION)


[uncategorized] ~232-~232: Loose punctuation mark.
Context: ...information and exit. - --version, -v: Print the current version and exit. ##...

(UNLIKELY_OPENING_PUNCTUATION)

🔇 Additional comments (10)
TODOs.md (1)

344-350: Clarify actual status of --token support across docs and code

This section lists --token as not done, while wiki pages already document it. Please confirm implementation and either tick this checklist or roll back the wiki entries to avoid drift.

wiki/Token-and-Security.md (1)

18-26: Verify CLI actually accepts --token/-t as documented

Before merging, confirm the flag parsing and precedence (CLI > env > config) are implemented in src/index.tsx/src/ui/App.tsx. If not, gate this section or mark as “planned”.

wiki/Usage.md (1)

7-9: Confirm -o short flag is implemented

Usage shows -o. Please verify the alias exists in CLI parsing.

src/ui/RepoList.tsx (1)

148-174: Good: one‑time --org application with stable handler ref

Nice use of a ref to avoid unstable dependencies and guard with prefsLoaded + appliedInitialOrg.

README.md (1)

214-219: Examples look good

Examples cover long/short flags and optional “@” prefix. Clear and practical.

src/ui/App.tsx (3)

16-16: Props extension LGTM

New props (initialOrgSlug, inlineToken, inlineTokenEphemeral) are a clean way to thread CLI state.


509-510: Propagating initialOrgSlug to RepoList

Forwarding the slug here is correct and aligns with the CLI contract.


51-71: InitialOrgSlug applied only once; env/inline tokens aren’t persisted
RepoList uses the appliedInitialOrg ref to run the initialOrgSlug effect exactly once on mount, and all calls to storeToken reside in the OAuth and manual‐PAT submission flows—there’s no persistence of environment or inline tokens in the initialisation effect.

src/index.tsx (2)

113-118: Inline token parsing LGTM

Trims values and remains ephemeral via prop below. Good.


122-123: Ephemeral inline token wiring is correct

Passing inlineTokenEphemeral={Boolean(inlineToken)} prevents persistence. Matches README behaviour.

Comment thread src/ui/App.tsx Outdated
Comment thread src/ui/RepoList.tsx
wiiiimm and others added 2 commits September 3, 2025 01:14
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
cursor[bot]

This comment was marked as outdated.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

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

⚠️ Outside diff range comments (1)
src/ui/RepoList.tsx (1)

761-781: Fix runtime error: undefined orgLogin in search warm-up effect

orgLogin is referenced but never defined in this scope, which will throw at runtime (and also makes the cache key shape inconsistent with other makeSearchKey calls). Remove the extra fields and align the key shape.

Apply this diff:

       try {
-        const key = makeSearchKey({
-          viewer: viewerLogin || 'unknown',
-          q: filter.trim(),
-          sortKey,
-          sortDir,
-          pageSize: PAGE_SIZE,
-          forkTracking,
-          ownerContext: orgLogin ? `org:${orgLogin}` : 'personal',
-          affiliations: ownerAffiliations.join(',')
-        });
+        const key = makeSearchKey({
+          viewer: viewerLogin || 'unknown',
+          q: filter.trim(),
+          sortKey,
+          sortDir,
+          pageSize: PAGE_SIZE,
+          forkTracking,
+        });
♻️ Duplicate comments (1)
src/ui/RepoList.tsx (1)

51-56: Good fix: stable handler ref to avoid effect churn

Using a ref to hold handleOrgContextChange removes the effect dependency footgun and addresses earlier stale-closure concerns.

🧹 Nitpick comments (4)
src/ui/RepoList.tsx (4)

148-179: Normalise --org slug more defensively

Trim whitespace before stripping a leading @ and bail early if the result is empty to avoid false negatives and noisy fetches.

Apply this diff:

-        const slug = initialOrgSlug.replace(/^@/, '');
+        const slug = initialOrgSlug.trim().replace(/^@/, '');
+        if (!slug) {
+          addDebugMessage('[--org] Empty --org slug after normalisation, ignoring flag');
+          return;
+        }

Also applies to: 161-173


351-353: Avoid re-creating GraphQL client inside context switch

Reuse the memoised client instead of creating a new one per switch.

Apply this diff:

-      const client = makeClient(token);
-      const isEnt = await checkOrganizationIsEnterprise(client, newContext.login);
+      const isEnt = await checkOrganizationIsEnterprise(client, newContext.login);

1045-1050: De-duplicate keybinding: W opens Org Switcher twice

W is handled in two places; keep one to prevent drift and reduce maintenance overhead.

Apply this diff to remove the later duplicate:

-    // Organization switcher (W for Workspace/Who)
-    if (input && input.toUpperCase() === 'W') {
-      setOrgSwitcherOpen(true);
-      return;
-    }

Also applies to: 1138-1142


1233-1252: Remove dead branch: unsupported forks sort case

SortKey does not include 'forks', so this branch is unreachable. Drop it for clarity.

Apply this diff:

       arr.sort((a, b) => {
         switch (sortKey) {
           case 'name':
             return a.nameWithOwner.localeCompare(b.nameWithOwner) * dir;
           case 'stars':
             return (a.stargazerCount - b.stargazerCount) * dir;
-          case 'forks':
-            return (a.forkCount - b.forkCount) * dir;
           case 'pushed':
             return (new Date(a.pushedAt).getTime() - new Date(b.pushedAt).getTime()) * dir;
           case 'updated':
           default:
             return (new Date(a.updatedAt).getTime() - new Date(b.updatedAt).getTime()) * dir;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 48bf291 and 88cf3d7.

📒 Files selected for processing (2)
  • src/ui/App.tsx (5 hunks)
  • src/ui/RepoList.tsx (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/ui/App.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
src/ui/RepoList.tsx (3)
src/ui/RepoList.main.tsx (1)
  • RepoList (119-1741)
src/config.ts (1)
  • OwnerContext (7-7)
src/github.ts (2)
  • makeClient (10-14)
  • fetchViewerOrganizations (117-136)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cursor Bugbot
🔇 Additional comments (1)
src/ui/RepoList.tsx (1)

148-179: Nice: one-shot --org application after prefs load

The guard via appliedInitialOrg and waiting for prefsLoaded matches the intended “override-on-startup” behaviour without flicker.

…ix incorrect identifier passed to useRef\n- Use optional chaining when invoking handler via ref\n- Route direct calls through the stable ref for consistency
@wiiiimm wiiiimm changed the title feat(cli): add --org flag to start in organisation context feat(cli): add org (-o) and token (-t) flags Sep 2, 2025
cursor[bot]

This comment was marked as outdated.

…sessionTokenOrigin (cli/env/stored/oauth/prompt)\n- Set origin when selecting token source (inline/env/stored/oauth)\n- Log tokenOrigin and willPersist instead of misreported tokenSource/tokenStored\n- Set origin on prompt submit and logout
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
src/ui/App.tsx (1)

127-133: Avoid unconditional OAuth store; don’t overwrite an existing stored token

Device-flow persists immediately and may overwrite an existing stored token. Respect the “only store if not already stored” rule here too.

-          // Store the token
-          storeToken(tokenResult.token, 'oauth');
-          setToken(tokenResult.token);
-          setTokenSource('oauth');
-          setSessionTokenOrigin('oauth');
+          // Decide whether to persist (avoid overwriting an existing stored token)
+          const hadStored = Boolean(getStoredToken());
+          setToken(tokenResult.token);
+          setTokenSource('oauth');
+          setSessionTokenOrigin('oauth');
+          if (!hadStored) {
+            storeToken(tokenResult.token, 'oauth');
+          }
♻️ Duplicate comments (2)
src/ui/App.tsx (1)

59-61: Set tokenSource after choosing origin (fixes prior misreporting)

Initialising tokenSource before origin selection can mislead downstream logic. Set it in the chosen branch; keep stored-source only for the stored path.

-    // Baseline from stored config
-    setTokenSource(source);
+    // tokenSource is set per-branch below
@@
     } else if (stored) {
       setToken(stored);
       setSessionTokenOrigin('stored');
+      setTokenSource(source);
       setMode('validating');

Also applies to: 72-76

src/ui/RepoList.tsx (1)

148-179: Apply --org via local handler to update internal state

This calls the parent callback directly and skips RepoList’s own handleOrgContextChange, leaving internal state (ownerContext, affiliations, filters, prefs) out of sync. Invoke the local handler instead.

-        if (match) {
-          await handleOrgContextChangeRef.current?.({
-            type: 'organization',
-            login: match.login,
-            name: match.name || undefined,
-          });
+        if (match) {
+          await handleOrgContextChange({
+            type: 'organization',
+            login: match.login,
+            name: match.name || undefined,
+          });
           addDebugMessage(`[--org] Switched context to @${match.login}`);
🧹 Nitpick comments (1)
src/ui/RepoList.tsx (1)

162-163: Trim whitespace when normalising the slug

Handles inputs like '--org=" acme "' cleanly.

-        const slug = initialOrgSlug.replace(/^@/, '');
+        const slug = initialOrgSlug.trim().replace(/^@/, '');
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 88cf3d7 and 8491f12.

📒 Files selected for processing (2)
  • src/ui/App.tsx (8 hunks)
  • src/ui/RepoList.tsx (5 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/ui/RepoList.tsx (2)
src/config.ts (1)
  • OwnerContext (7-7)
src/github.ts (2)
  • makeClient (10-14)
  • fetchViewerOrganizations (117-136)
src/ui/App.tsx (1)
src/config.ts (2)
  • getStoredToken (64-67)
  • storeToken (69-72)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cursor Bugbot
🔇 Additional comments (3)
src/ui/App.tsx (1)

516-527: Propagating initialOrgSlug looks good

Passing initialOrgSlug through to RepoList is correct and scoped.

src/ui/RepoList.tsx (2)

365-368: Parent notification path is stable and correct

Using the ref to notify the parent avoids unstable deps. LGTM.


641-642: Owner context load: correct parent notification

Reflecting loaded prefs to the parent via the ref is appropriate. LGTM.

Comment thread src/ui/App.tsx
…\n- Call handleOrgContextChange instead of parent handler\n- Ensures ownerContext, affiliations, filters update in component
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

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

⚠️ Outside diff range comments (1)
src/ui/RepoList.tsx (1)

761-781: Bug: ‘orgLogin’ is undefined in this effect

This block references orgLogin when building the search key, but orgLogin isn’t defined in scope, causing a runtime/TS error.

Apply:

   useEffect(() => {
     if (!viewerLogin && searchActive && !searchLoading && searchItems.length === 0) {
       let policy: 'cache-first' | 'network-only' = 'cache-first';
       try {
+        const orgLogin = ownerContext !== 'personal' ? ownerContext.login : undefined;
         const key = makeSearchKey({
           viewer: viewerLogin || 'unknown',
           q: filter.trim(),
           sortKey,
           sortDir,
           pageSize: PAGE_SIZE,
           forkTracking,
-          ownerContext: orgLogin ? `org:${orgLogin}` : 'personal',
-          affiliations: ownerAffiliations.join(',')
+          ownerContext: orgLogin ? `org:${orgLogin}` : 'personal',
+          affiliations: ownerAffiliations.join(',')
         });
         policy = isFresh(key, 90 * 1000) ? 'cache-first' : 'network-only';
       } catch {}
       fetchSearchPage(null, true, policy);
     }
♻️ Duplicate comments (1)
src/ui/RepoList.tsx (1)

165-171: Fix for prior “bypass local handler” issue is now correct

The initial org application now calls handleOrgContextChange (local), ensuring internal state and prefs are updated before notifying parent.

🧹 Nitpick comments (5)
src/ui/RepoList.tsx (5)

148-179: Trim and normalise the org slug; minor effect hygiene

Handle stray whitespace and keep semantics tight. Also consider moving the “applied” flip to finally so we only mark after attempting.

Apply:

-      appliedInitialOrg.current = true;
       try {
         const orgs = await fetchViewerOrganizations(client);
-        const slug = initialOrgSlug.replace(/^@/, '');
+        const slug = initialOrgSlug.trim().replace(/^@/, '');
+        if (!slug) {
+          addDebugMessage('[--org] Empty org slug after trimming; ignoring flag');
+          return;
+        }
         const match = orgs.find(o => o.login.toLowerCase() === slug.toLowerCase());
         if (match) {
           await handleOrgContextChange({
             type: 'organization',
             login: match.login,
             name: match.name || undefined,
           });
           addDebugMessage(`[--org] Switched context to @${match.login}`);
         } else {
           addDebugMessage(`[--org] No access to org @${slug}, ignoring flag`);
         }
       } catch (e: any) {
         addDebugMessage(`[--org] Failed to apply org flag: ${e.message || e}`);
+      } finally {
+        appliedInitialOrg.current = true;
       }

If you intentionally omit handleOrgContextChange from deps to avoid churn, add an inline eslint-disable comment to document it.


349-354: Reuse the memoised client instead of recreating

No need to allocate a new client here; use the outer client.

-    if (newContext !== 'personal') {
-      const client = makeClient(token);
-      const isEnt = await checkOrganizationIsEnterprise(client, newContext.login);
+    if (newContext !== 'personal') {
+      const isEnt = await checkOrganizationIsEnterprise(client, newContext.login);
       setIsEnterpriseOrg(isEnt);
     } else {
       setIsEnterpriseOrg(false);
     }

631-635: Don’t load an unsupported ‘internal’ visibility mode

VisibilityFilter excludes 'internal', but the loader admits it, which can put the UI in an unsupported state. Map legacy 'internal' to 'private' on load.

-    if (ui.visibilityFilter && ['all', 'public', 'private', 'internal'].includes(ui.visibilityFilter)) {
-      setVisibilityFilter(ui.visibilityFilter as VisibilityFilter);
-    }
+    if (ui.visibilityFilter === 'internal') {
+      setVisibilityFilter('private');
+      try { storeUIPrefs({ visibilityFilter: 'private' }); } catch {}
+    } else if (ui.visibilityFilter && ['all', 'public', 'private'].includes(ui.visibilityFilter)) {
+      setVisibilityFilter(ui.visibilityFilter as VisibilityFilter);
+    }

1237-1249: Remove unreachable ‘forks’ sort branch

SortKey doesn’t include 'forks', so this case is dead code and confusing.

-        case 'forks':
-          return (a.forkCount - b.forkCount) * dir;

If you want to support it, add 'forks' to SortKey and sortFieldMap appropriately (note: GitHub GraphQL doesn’t expose a forks order field).


1341-1342: Include changeVisibilityMode in modalOpen

Keeps header dimming consistent when the Change Visibility modal is open.

-  const modalOpen = deleteMode || archiveMode || syncMode || logoutMode || infoMode || visibilityMode;
+  const modalOpen = deleteMode || archiveMode || syncMode || logoutMode || infoMode || visibilityMode || changeVisibilityMode;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 8491f12 and 62622fa.

📒 Files selected for processing (1)
  • src/ui/RepoList.tsx (5 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/ui/RepoList.tsx (2)
src/config.ts (1)
  • OwnerContext (7-7)
src/github.ts (2)
  • makeClient (10-14)
  • fetchViewerOrganizations (117-136)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cursor Bugbot
🔇 Additional comments (3)
src/ui/RepoList.tsx (3)

31-38: Props shape and typing look good

New props for initialOrgSlug and onOrgContextChange are well-scoped and optional.


45-50: Stable debug callback is fine

Memoised addDebugMessage with capped buffer looks good.


51-56: Good: stable ref for parent handler

Using handleOrgContextChangeRef to avoid unstable deps is the right move.

…istence on sessionTokenOrigin === 'prompt' || 'oauth'\n- Prevents persisting env/CLI inline tokens\n- Keep hadStored and inlineTokenEphemeral checks intact
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

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

⚠️ Outside diff range comments (1)
src/ui/App.tsx (1)

259-266: Do not wipe the stored token when validation of a non-stored token fails

This unconditionally removes the persisted token even when the failing token came from CLI/env/prompt. Users can lose a valid stored token. Gate the clear to stored-origin tokens only.

-          // Clear stored token since it's invalid
-          clearStoredToken();
+          // Only clear stored token if the failed token originated from storage
+          if (sessionTokenOrigin === 'stored') {
+            try { clearStoredToken(); } catch {}
+          }
♻️ Duplicate comments (1)
src/ui/App.tsx (1)

184-199: Token persistence correctly gated by origin

Good fix: persistence now occurs only when there’s no prior stored token, the token isn’t inline-ephemeral, and the origin is prompt or OAuth. This addresses prior review feedback to avoid persisting CLI/env tokens.

🧹 Nitpick comments (2)
src/ui/App.tsx (2)

475-476: Tighten copy to reflect actual storage rules

The message implies unconditional storage. Clarify that prompt-entered PATs may be stored, and not when a token already exists.

-              The token will be stored securely in your local config
+              PATs entered here are stored in your local config unless a token is already stored.

120-133: Persist OAuth token after confirming login

Edge case: if tokenResult.token is returned but login resolution fails, you’ve already persisted the token. Safer to persist after login is confirmed.

-          // Store the token
-          storeToken(tokenResult.token, 'oauth');
-          setToken(tokenResult.token);
+          // Set token in memory; persist after login is confirmed
+          setToken(tokenResult.token);
           setTokenSource('oauth');
           setSessionTokenOrigin('oauth');
 
           if (tokenResult.login) {
             setViewer(tokenResult.login);
-            setOAuthStatus('success');
+            // Persist now that login is known
+            storeToken(tokenResult.token, 'oauth');
+            setOAuthStatus('success');
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 62622fa and 74c625d.

📒 Files selected for processing (1)
  • src/ui/App.tsx (8 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/ui/App.tsx (1)
src/config.ts (2)
  • getStoredToken (64-67)
  • storeToken (69-72)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cursor Bugbot
🔇 Additional comments (4)
src/ui/App.tsx (4)

16-17: SessionTokenOrigin type is a clear separation of “provenance”

Nice addition; it simplifies reasoning about precedence and persistence.


525-526: Initial org slug wiring

Prop passthrough looks fine. Please confirm RepoList applies it once post-auth/prefs and silently ignores unknown/inaccessible slugs as per PR description.


18-19: All App call sites updated
App is rendered in src/index.tsx (line 122) with initialOrgSlug, inlineToken and inlineTokenEphemeral; no other render or test usages found.


62-72: tokenSource only used for UI token type and explicit persistence
No implicit reliance on tokenSource for storage or telemetry; persistence occurs exclusively via explicit storeToken calls.

…events reference error when building makeSearchKey for server search\n- Uses current ownerContext to derive org login
…s stored\n\n- Prevent wiping valid persisted token after CLI/env/prompt failures\n- Gate clearStoredToken() by sessionTokenOrigin === 'stored'
@wiiiimm wiiiimm merged commit 029376e into main Sep 2, 2025
3 checks passed
wiiiimm pushed a commit that referenced this pull request Sep 2, 2025
# [1.19.0](v1.18.1...v1.19.0) (2025-09-02)

### Features

* **cli:** add org (-o) and token (-t) flags ([#18](#18)) ([029376e](029376e))
@wiiiimm
Copy link
Copy Markdown
Owner Author

wiiiimm commented Sep 2, 2025

🎉 This PR is included in version 1.19.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants