style: update section model selection headers to micro typography with uppercase and sticky behavior
This commit is contained in:
@@ -1,389 +0,0 @@
|
|||||||
# GitHub Features Plan (PRD-ish)
|
|
||||||
|
|
||||||
Goal: implement GitHub-powered workflows (PR panel + start sessions from Issue/PR) on top of the existing GitHub auth foundation.
|
|
||||||
|
|
||||||
Non-goals (for this phase)
|
|
||||||
- Rebuild auth/token storage (already implemented)
|
|
||||||
- Rebuild worktree lifecycle/cleanup (already implemented)
|
|
||||||
- Add hard context size caps/fallback logic (explicitly out of scope)
|
|
||||||
|
|
||||||
## Existing Primitives (MUST reuse)
|
|
||||||
|
|
||||||
These already exist; do not reinvent.
|
|
||||||
|
|
||||||
GitHub auth + connected user
|
|
||||||
- UI: `packages/ui/src/components/sections/openchamber/GitHubSettings.tsx`
|
|
||||||
- Runtime API: `GitHubAPI` in `packages/ui/src/lib/api/types.ts`
|
|
||||||
- Web endpoints: `packages/web/server/index.js` (`/api/github/*`)
|
|
||||||
- Desktop Tauri commands: `packages/desktop/src-tauri/src/commands/github.rs`
|
|
||||||
- VS Code bridge + storage: `packages/vscode/src/bridge.ts`, `packages/vscode/src/githubAuth.ts`
|
|
||||||
|
|
||||||
Projects and directories
|
|
||||||
- Projects store (one project == one repo path): `packages/ui/src/stores/useProjectsStore.ts`
|
|
||||||
- Active project selection is already used across the app; use it to scope all GitHub operations.
|
|
||||||
|
|
||||||
Worktree sessions
|
|
||||||
- Worktree creation and session wiring:
|
|
||||||
- `packages/ui/src/lib/worktreeSessionCreator.ts`
|
|
||||||
- Reuse `createWorktreeSession()` and `createWorktreeSessionForBranch(projectDirectory, branchName)`.
|
|
||||||
- Worktree cleanup/delete behavior is already present in session deletion flow:
|
|
||||||
- `packages/ui/src/components/session/SessionDialogs.tsx`
|
|
||||||
|
|
||||||
“Synthetic parts” / hidden context in chat
|
|
||||||
- SDK supports `TextPartInput.synthetic?: boolean`:
|
|
||||||
- `node_modules/@opencode-ai/sdk/dist/v2/gen/types.gen.d.ts`
|
|
||||||
- UI filters synthetic parts out of rendering:
|
|
||||||
- `packages/ui/src/lib/messages/synthetic.ts`
|
|
||||||
- Existing “seed new session from assistant answer” uses a hidden preface part:
|
|
||||||
- `packages/ui/src/stores/useSessionStore.ts` (`createSessionFromAssistantMessage`)
|
|
||||||
- `packages/ui/src/lib/opencode/client.ts` (`sendMessage({ prefaceText })`)
|
|
||||||
|
|
||||||
Git tab layout
|
|
||||||
- Git view where PR panel will be inserted:
|
|
||||||
- `packages/ui/src/components/views/GitView.tsx`
|
|
||||||
- Changes + Commit already exist; History is below.
|
|
||||||
|
|
||||||
## Guiding Principles
|
|
||||||
|
|
||||||
- One directory/project == one git repo. All GitHub actions are scoped to the active project.
|
|
||||||
- Do not show synthetic context to the user; only show the human prompt.
|
|
||||||
- If the user lacks permission for an operation (merge, create branch, etc), degrade to “Open in GitHub”.
|
|
||||||
- Prefer server-side Octokit for web runtime; for desktop/vscode, implement equivalent runtime commands/bridge handlers.
|
|
||||||
- Keep UI consistent with existing patterns (provider OAuth, worktree sessions, commit message generation).
|
|
||||||
|
|
||||||
## Feature A: Git Tab PR Panel (Create / Status / Merge)
|
|
||||||
|
|
||||||
Status: implemented.
|
|
||||||
|
|
||||||
### Intent
|
|
||||||
While working on a feature branch, show PR status and actions inside the Git tab, without leaving the app.
|
|
||||||
|
|
||||||
### Placement
|
|
||||||
Implemented between Commit and History in `packages/ui/src/components/views/GitView.tsx`.
|
|
||||||
|
|
||||||
### Visibility Rules
|
|
||||||
- Show only if:
|
|
||||||
- repo is detected (`isGitRepo === true`)
|
|
||||||
- branch is not the “base branch”
|
|
||||||
|
|
||||||
Base branch source (reuse existing config):
|
|
||||||
- `activeProject.worktreeDefaults.baseBranch` from `packages/ui/src/stores/useProjectsStore.ts`
|
|
||||||
- fallback default: `main`
|
|
||||||
|
|
||||||
### UI States
|
|
||||||
1) GitHub not connected
|
|
||||||
- Show CTA: “Connect GitHub” (link to Settings -> OpenChamber -> GitHub).
|
|
||||||
|
|
||||||
2) Connected but repo not resolvable to GitHub
|
|
||||||
- Show “Open remote in browser” if remote URL exists.
|
|
||||||
- Show error text explaining remote must be GitHub.
|
|
||||||
|
|
||||||
3) PR does not exist for current branch
|
|
||||||
- Show create form:
|
|
||||||
- base branch (default: baseBranch)
|
|
||||||
- title (default from branch name)
|
|
||||||
- draft toggle
|
|
||||||
- description textarea
|
|
||||||
- “Generate description” button (AI)
|
|
||||||
- “Create PR” button
|
|
||||||
|
|
||||||
4) PR exists
|
|
||||||
- Show summary:
|
|
||||||
- state (draft/open/merged)
|
|
||||||
- PR number + title
|
|
||||||
- checks summary
|
|
||||||
- mergeability (if available)
|
|
||||||
- “Open in GitHub”
|
|
||||||
- If user has merge permission and PR is mergeable:
|
|
||||||
- merge method dropdown (merge/squash/rebase)
|
|
||||||
- “Merge” button
|
|
||||||
- If PR is draft:
|
|
||||||
- “Ready” button (mark ready for review)
|
|
||||||
- Merge disabled until ready
|
|
||||||
- If cannot merge:
|
|
||||||
- disable merge button + show “Open in GitHub”
|
|
||||||
|
|
||||||
### AI “Generate description”
|
|
||||||
Implemented as a PR-specific generator (separate prompt/endpoint/command).
|
|
||||||
|
|
||||||
Inputs:
|
|
||||||
- base branch ref (prefers `origin/<base>` when available)
|
|
||||||
- head branch ref
|
|
||||||
- committed range diff: `git diff <base>...<head>` (file list from `git diff --name-only <base>...<head>`)
|
|
||||||
|
|
||||||
Output:
|
|
||||||
- `title` (<= 80 chars, no commit-style prefixes)
|
|
||||||
- `body` (GFM markdown sections: Summary/Testing/Notes)
|
|
||||||
|
|
||||||
### Required GitHub API Calls
|
|
||||||
- Resolve repo from git remote URL (origin)
|
|
||||||
- Find PR by head branch
|
|
||||||
- Create PR
|
|
||||||
- Get PR details + checks
|
|
||||||
- Merge PR
|
|
||||||
- Mark PR ready for review (GraphQL)
|
|
||||||
|
|
||||||
Checks logic (implemented):
|
|
||||||
- prefer GitHub Actions check-runs (`/commits/{sha}/check-runs`)
|
|
||||||
- fallback to classic commit statuses (`/commits/{sha}/status`)
|
|
||||||
|
|
||||||
### Implementation Notes
|
|
||||||
- Web runtime should use server endpoints + Octokit (token stays server-side).
|
|
||||||
- Desktop/vscode should use their runtime handlers (similar to GitHub auth) to avoid exposing token.
|
|
||||||
|
|
||||||
Implemented code pointers
|
|
||||||
- UI section: `packages/ui/src/components/views/git/PullRequestSection.tsx`
|
|
||||||
- Web server endpoints:
|
|
||||||
- `GET /api/github/pr/status`
|
|
||||||
- `POST /api/github/pr/create`
|
|
||||||
- `POST /api/github/pr/merge`
|
|
||||||
- `POST /api/github/pr/ready`
|
|
||||||
- PR description generator:
|
|
||||||
- `POST /api/git/pr-description`
|
|
||||||
- Desktop: `generate_pr_description`
|
|
||||||
- VS Code: `api:git/pr-description`
|
|
||||||
|
|
||||||
## Feature B: Start Session From GitHub Issue
|
|
||||||
|
|
||||||
Status: implemented.
|
|
||||||
|
|
||||||
### Intent
|
|
||||||
Create a new session seeded with issue context, without polluting chat with large issue bodies/comments.
|
|
||||||
|
|
||||||
### Entry Point
|
|
||||||
Project header menu in `packages/ui/src/components/session/SessionSidebar.tsx`.
|
|
||||||
|
|
||||||
Add new item:
|
|
||||||
- “New session from GitHub issue”
|
|
||||||
|
|
||||||
### Modal UI
|
|
||||||
Issue picker modal:
|
|
||||||
- list issues for current repo (open by default)
|
|
||||||
- search by title/number
|
|
||||||
- direct input:
|
|
||||||
- full URL
|
|
||||||
- `#123` or `123`
|
|
||||||
- checkbox: “Create in worktree”
|
|
||||||
|
|
||||||
Implementation notes:
|
|
||||||
- modal layout matches Timeline dialog styling/patterns
|
|
||||||
- “Open Repo” + per-issue “Open in GitHub” use `<a href=... target="_blank">` (desktop webview safe)
|
|
||||||
|
|
||||||
### Worktree option
|
|
||||||
If enabled:
|
|
||||||
- create a worktree session (reuse `createWorktreeSessionForBranch`)
|
|
||||||
- branch naming convention:
|
|
||||||
- `issue-<number>-<slug>` (slug derived from title)
|
|
||||||
- base branch:
|
|
||||||
- `activeProject.worktreeDefaults.baseBranch`
|
|
||||||
|
|
||||||
If disabled:
|
|
||||||
- create normal session in project root directory.
|
|
||||||
|
|
||||||
### Session Bootstrap (message)
|
|
||||||
Send a single user message with:
|
|
||||||
1) Visible text part: concise prompt, e.g.
|
|
||||||
- “Review the issue; summarize requirements + unknowns; ask clarifying questions; gather needed code context; propose plan + next actions; do not implement until user confirms.”
|
|
||||||
2) Hidden synthetic parts: issue payload
|
|
||||||
- issue title/body
|
|
||||||
- labels, assignees, author
|
|
||||||
- comments (ordered)
|
|
||||||
- metadata (repo, number, url)
|
|
||||||
|
|
||||||
This must use SDK-supported `TextPartInput.synthetic = true` so it is not rendered.
|
|
||||||
Do not invent a new hidden-context mechanism.
|
|
||||||
|
|
||||||
### Required GitHub API Calls
|
|
||||||
- List issues
|
|
||||||
- Get issue by number
|
|
||||||
- List issue comments
|
|
||||||
|
|
||||||
Implemented code pointers
|
|
||||||
- UI modal: `packages/ui/src/components/session/GitHubIssuePickerDialog.tsx`
|
|
||||||
- Shared sendMessage synthetic parts: `packages/ui/src/lib/opencode/client.ts`
|
|
||||||
- Web server endpoints:
|
|
||||||
- `GET /api/github/issues/list`
|
|
||||||
- `GET /api/github/issues/get`
|
|
||||||
- `GET /api/github/issues/comments`
|
|
||||||
- Desktop Tauri commands:
|
|
||||||
- `github_issues_list`
|
|
||||||
- `github_issue_get`
|
|
||||||
- `github_issue_comments`
|
|
||||||
- VS Code bridge handlers:
|
|
||||||
- `api:github/issues:list`
|
|
||||||
- `api:github/issues:get`
|
|
||||||
- `api:github/issues:comments`
|
|
||||||
|
|
||||||
## Feature C: Start Session From GitHub PR (with worktree checkout)
|
|
||||||
|
|
||||||
Status: implemented.
|
|
||||||
|
|
||||||
### Intent
|
|
||||||
Create a session seeded with PR context, with optional worktree checkout of PR branch (including forks).
|
|
||||||
|
|
||||||
### Entry Point
|
|
||||||
Project header menu in `packages/ui/src/components/session/SessionSidebar.tsx`.
|
|
||||||
|
|
||||||
Add new item:
|
|
||||||
- “New session from GitHub PR”
|
|
||||||
|
|
||||||
### Modal UI
|
|
||||||
PR picker modal:
|
|
||||||
- list open PRs
|
|
||||||
- search by title/number
|
|
||||||
- direct input:
|
|
||||||
- full URL
|
|
||||||
- `#123` or `123`
|
|
||||||
- checkbox: “Create session in PR worktree”
|
|
||||||
|
|
||||||
Implementation notes:
|
|
||||||
- modal layout matches Timeline dialog styling/patterns
|
|
||||||
- list pagination: “Load more” (page-based, `per_page=50`)
|
|
||||||
- optional toggle: include full diff in hidden context
|
|
||||||
|
|
||||||
### Worktree behavior
|
|
||||||
If enabled:
|
|
||||||
- if PR is from same repo:
|
|
||||||
- fetch PR head into `FETCH_HEAD`
|
|
||||||
- create worktree using the PR branch name, starting at `FETCH_HEAD` (does not change main worktree)
|
|
||||||
- if PR is from fork:
|
|
||||||
- fetch PR head from fork clone URL into `FETCH_HEAD`
|
|
||||||
- create worktree using the PR branch name, starting at `FETCH_HEAD`
|
|
||||||
|
|
||||||
Fallbacks:
|
|
||||||
- if fetch/remote fails or permission denied:
|
|
||||||
- still create a normal session with PR context
|
|
||||||
- show toast with error; user can open PR in GitHub
|
|
||||||
|
|
||||||
### Session Bootstrap (message)
|
|
||||||
Same synthetic-parts approach as Issues.
|
|
||||||
|
|
||||||
Hidden parts should include:
|
|
||||||
- PR title/body
|
|
||||||
- PR comments + review comments
|
|
||||||
- changed files list
|
|
||||||
- optionally full diff (explicitly no caps)
|
|
||||||
- checks/status summary
|
|
||||||
|
|
||||||
Visible prompt text should instruct:
|
|
||||||
- review PR intent; call out intent/implementation mismatch
|
|
||||||
- identify risks + missing pieces
|
|
||||||
- gather needed repo context; no speculation; ask for missing info
|
|
||||||
- propose a plan + next actions; do not implement until user confirms
|
|
||||||
|
|
||||||
Implemented code pointers
|
|
||||||
- UI modal: `packages/ui/src/components/session/GitHubPullRequestPickerDialog.tsx`
|
|
||||||
- Web server endpoints:
|
|
||||||
- `GET /api/github/pulls/list`
|
|
||||||
- `GET /api/github/pulls/context`
|
|
||||||
- Desktop Tauri commands:
|
|
||||||
- `github_prs_list`
|
|
||||||
- `github_pr_context`
|
|
||||||
- VS Code bridge handlers:
|
|
||||||
- `api:github/pulls:list`
|
|
||||||
- `api:github/pulls:context`
|
|
||||||
|
|
||||||
## Feature E: PR Context Helpers (Checks/Comments -> Chat)
|
|
||||||
|
|
||||||
Status: implemented.
|
|
||||||
|
|
||||||
Intent
|
|
||||||
Reduce PR iteration loop time by letting the user add targeted PR signal (failed checks, review feedback) into the next chat message without polluting visible chat.
|
|
||||||
|
|
||||||
Placement
|
|
||||||
Inside the existing Git tab PR panel (`packages/ui/src/components/views/git/PullRequestSection.tsx`).
|
|
||||||
|
|
||||||
UI
|
|
||||||
- “Send failed checks to chat” (only if failures exist)
|
|
||||||
- “Send PR comments to chat” (issue comments + review comments)
|
|
||||||
- “Check details” dialog (shows check runs, app name, and GitHub Actions job steps when available)
|
|
||||||
- “Refresh checks” affordance
|
|
||||||
|
|
||||||
Behavior
|
|
||||||
- On click, sends a new user message in the current session (and switches to Chat tab).
|
|
||||||
- Message contains a short visible prompt plus hidden synthetic parts.
|
|
||||||
- Failed checks payload should include: check name, status/conclusion, details URL, and any available summary/text.
|
|
||||||
- When available, include GitHub Actions job + step breakdown and the check app name.
|
|
||||||
- Comments payload should include: author, body, file/path + line when available, and comment URL.
|
|
||||||
- Keep visible user prompt short; include only human intent.
|
|
||||||
|
|
||||||
Implementation notes
|
|
||||||
- Reuse Feature C PR context endpoint (`/api/github/pulls/context`) as the single source for comments/files/checks/diff.
|
|
||||||
- Prefer a compact checks rollup (passed/total) with optional expand into failing checks.
|
|
||||||
|
|
||||||
Implemented code pointers
|
|
||||||
- UI: `packages/ui/src/components/views/git/PullRequestSection.tsx`
|
|
||||||
|
|
||||||
### Required GitHub API Calls
|
|
||||||
- List PRs
|
|
||||||
- Get PR
|
|
||||||
- List issue comments for PR
|
|
||||||
- List review comments
|
|
||||||
- List files
|
|
||||||
- Get checks/status
|
|
||||||
|
|
||||||
## Cross-cutting: “Synthetic Parts” Sending API
|
|
||||||
|
|
||||||
Current behavior:
|
|
||||||
- `opencodeClient.sendMessage()` supports `prefaceText` which becomes a separate `TextPartInput`.
|
|
||||||
- There is no first-class way (yet) to mark arbitrary parts as `synthetic: true` from callsites.
|
|
||||||
|
|
||||||
Required change (shared for Features B/C):
|
|
||||||
- Extend `opencodeClient.sendMessage()` (in `packages/ui/src/lib/opencode/client.ts`) to support synthetic parts.
|
|
||||||
|
|
||||||
Recommended minimal API change:
|
|
||||||
- allow `prefaceTextSynthetic?: boolean` (default true when used for hidden context)
|
|
||||||
- allow `additionalParts?: Array<{ text: string; synthetic?: boolean; files?: ... }>`
|
|
||||||
- ensure generated `TextPartInput` includes `synthetic` when requested
|
|
||||||
|
|
||||||
This should reuse the existing filtering/rendering logic (no new UI hacks).
|
|
||||||
|
|
||||||
## Cross-cutting: Repo Resolution
|
|
||||||
|
|
||||||
Need a single helper to map current project repo -> GitHub owner/repo.
|
|
||||||
|
|
||||||
Inputs:
|
|
||||||
- project directory root
|
|
||||||
- git remote URL (origin)
|
|
||||||
|
|
||||||
Behavior:
|
|
||||||
- support common GitHub URL formats:
|
|
||||||
- `git@github.com:OWNER/REPO.git`
|
|
||||||
- `https://github.com/OWNER/REPO.git`
|
|
||||||
- `https://github.com/OWNER/REPO`
|
|
||||||
|
|
||||||
Output:
|
|
||||||
- `{ owner, repo }` or null
|
|
||||||
|
|
||||||
Use this for all GitHub feature endpoints.
|
|
||||||
|
|
||||||
## Cross-cutting: Permission / Fallback Rules
|
|
||||||
|
|
||||||
- Merge button enabled only if merge endpoint succeeds or mergeability indicates allowed.
|
|
||||||
- If not allowed:
|
|
||||||
- show “Open in GitHub” as primary action
|
|
||||||
- For PR worktrees from forks:
|
|
||||||
- if remote add/fetch fails => create normal session + “Open in GitHub”
|
|
||||||
|
|
||||||
## Work Breakdown (Suggested Order)
|
|
||||||
|
|
||||||
Phase 1: Shared plumbing
|
|
||||||
1) Repo resolution helper (remote URL -> owner/repo)
|
|
||||||
2) New message sending helper supporting `synthetic: true` parts
|
|
||||||
3) GitHub endpoints/commands for issue + PR fetch (read-only)
|
|
||||||
|
|
||||||
Phase 2: Session bootstrap flows
|
|
||||||
4) Issue picker modal + session bootstrap
|
|
||||||
5) PR picker modal + session bootstrap
|
|
||||||
6) PR worktree checkout (fork support)
|
|
||||||
|
|
||||||
Phase 3: Git tab PR panel
|
|
||||||
7) PR detect/status in Git tab
|
|
||||||
8) Create PR from branch
|
|
||||||
9) AI generate PR description
|
|
||||||
10) Merge (with fallback)
|
|
||||||
|
|
||||||
## Open Questions (for later)
|
|
||||||
|
|
||||||
- PR description generator prompt format: do we want the exact same “highlights” UI as commit gen, or a single-shot body generation?
|
|
||||||
- Worktree naming collision strategy for PR-based worktrees (owner/ref collisions) beyond current `sanitizeWorktreeSlug`.
|
|
||||||
@@ -1,166 +0,0 @@
|
|||||||
# GitHub Integration (Auth Foundation)
|
|
||||||
|
|
||||||
This repo now has a GitHub auth foundation intended to be reused by all future GitHub features (PRs/issues/worktrees/etc).
|
|
||||||
|
|
||||||
It provides:
|
|
||||||
- GitHub OAuth Device Flow connect UX
|
|
||||||
- persistent token storage per runtime
|
|
||||||
- a small runtime API surface for UI
|
|
||||||
- server-side Octokit usage (web runtime)
|
|
||||||
|
|
||||||
## Scopes
|
|
||||||
|
|
||||||
Default scopes requested:
|
|
||||||
|
|
||||||
```
|
|
||||||
repo read:org workflow read:user user:email
|
|
||||||
```
|
|
||||||
|
|
||||||
Notes:
|
|
||||||
- Email is fetched from `/user` when available, otherwise `/user/emails` (requires `user:email`).
|
|
||||||
- Actions performed via this OAuth token are performed “as the user” (not a bot), but the OAuth App is visible under GitHub “Authorized OAuth Apps”.
|
|
||||||
|
|
||||||
## UI
|
|
||||||
|
|
||||||
Settings entry:
|
|
||||||
- `packages/ui/src/components/sections/openchamber/GitHubSettings.tsx`
|
|
||||||
|
|
||||||
Behavior:
|
|
||||||
- shows connected user card (avatar + name/email/login)
|
|
||||||
- Connect triggers Device Flow and polls until authorized
|
|
||||||
- Disconnect clears the stored token
|
|
||||||
|
|
||||||
## Runtime API (UI)
|
|
||||||
|
|
||||||
`RuntimeAPIs.github` is optional (some environments may not expose it).
|
|
||||||
|
|
||||||
Types:
|
|
||||||
- `packages/ui/src/lib/api/types.ts` (`GitHubAPI`, `GitHubAuthStatus`, `GitHubDeviceFlowStart`, `GitHubDeviceFlowComplete`)
|
|
||||||
|
|
||||||
Methods:
|
|
||||||
- `authStatus(): { connected, user?, scope? }`
|
|
||||||
- `authStart(): { deviceCode, userCode, verificationUri, verificationUriComplete?, expiresIn, interval, scope? }`
|
|
||||||
- `authComplete(deviceCode): { connected: true, user, scope? } | { connected: false, status?, error? }`
|
|
||||||
- `authDisconnect(): { removed: boolean }`
|
|
||||||
- `me?(): user` (optional, mostly for debugging)
|
|
||||||
|
|
||||||
Implementations:
|
|
||||||
- Web: `packages/web/src/api/github.ts`
|
|
||||||
- Desktop: `packages/desktop/src/api/github.ts` (calls Tauri commands)
|
|
||||||
- VS Code: `packages/vscode/webview/api/github.ts` (bridge messages)
|
|
||||||
|
|
||||||
## Web Runtime (Express server)
|
|
||||||
|
|
||||||
Endpoints (JSON):
|
|
||||||
|
|
||||||
- `GET /api/github/auth/status`
|
|
||||||
- returns `{ connected: false }` or `{ connected: true, user, scope }`
|
|
||||||
|
|
||||||
- `POST /api/github/auth/start`
|
|
||||||
- returns device flow payload:
|
|
||||||
- `{ deviceCode, userCode, verificationUri, verificationUriComplete?, expiresIn, interval, scope }`
|
|
||||||
|
|
||||||
- `POST /api/github/auth/complete`
|
|
||||||
- request: `{ deviceCode }`
|
|
||||||
- returns either pending or success:
|
|
||||||
- pending: `{ connected: false, status, error }`
|
|
||||||
- success: `{ connected: true, user, scope }`
|
|
||||||
|
|
||||||
- `DELETE /api/github/auth`
|
|
||||||
- clears stored token
|
|
||||||
- returns `{ success: true, removed: boolean }`
|
|
||||||
|
|
||||||
- `GET /api/github/me`
|
|
||||||
- returns the authenticated user summary
|
|
||||||
|
|
||||||
Code:
|
|
||||||
- endpoints: `packages/web/server/index.js`
|
|
||||||
- token store + config defaults: `packages/web/server/lib/github-auth.js`
|
|
||||||
- Octokit factory: `packages/web/server/lib/github-octokit.js`
|
|
||||||
- device flow helpers: `packages/web/server/lib/github-device-flow.js`
|
|
||||||
|
|
||||||
## Desktop Runtime (Tauri)
|
|
||||||
|
|
||||||
Tauri commands:
|
|
||||||
- `github_auth_status`
|
|
||||||
- `github_auth_start`
|
|
||||||
- `github_auth_complete` (param: `deviceCode`)
|
|
||||||
- `github_auth_disconnect`
|
|
||||||
- `github_me`
|
|
||||||
|
|
||||||
Code:
|
|
||||||
- `packages/desktop/src-tauri/src/commands/github.rs`
|
|
||||||
- wired in invoke handler: `packages/desktop/src-tauri/src/main.rs`
|
|
||||||
|
|
||||||
## VS Code Runtime
|
|
||||||
|
|
||||||
Bridge message types handled in extension:
|
|
||||||
- `api:github/auth:status`
|
|
||||||
- `api:github/auth:start`
|
|
||||||
- `api:github/auth:complete`
|
|
||||||
- `api:github/auth:disconnect`
|
|
||||||
- `api:github/me`
|
|
||||||
|
|
||||||
Code:
|
|
||||||
- storage + device flow + `/user` fetch: `packages/vscode/src/githubAuth.ts`
|
|
||||||
- bridge handlers: `packages/vscode/src/bridge.ts`
|
|
||||||
|
|
||||||
## Token Storage
|
|
||||||
|
|
||||||
- Web/server runtime: `~/.config/openchamber/github-auth.json`
|
|
||||||
- file mode `0600` best-effort
|
|
||||||
|
|
||||||
- Desktop runtime: `~/.config/openchamber/github-auth.json`
|
|
||||||
- file mode `0600` best-effort
|
|
||||||
|
|
||||||
- VS Code runtime: `${extensionGlobalStorage}/github-auth.json`
|
|
||||||
- file mode `0600` best-effort
|
|
||||||
|
|
||||||
Stored fields (current shape; can evolve):
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"accessToken": "…",
|
|
||||||
"scope": "…",
|
|
||||||
"tokenType": "bearer",
|
|
||||||
"createdAt": 1730000000000,
|
|
||||||
"user": {
|
|
||||||
"login": "…",
|
|
||||||
"id": 123,
|
|
||||||
"avatarUrl": "…",
|
|
||||||
"name": "…",
|
|
||||||
"email": "…"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Official OAuth App
|
|
||||||
|
|
||||||
Default OAuth client id is baked in:
|
|
||||||
- `Ov23liNd8TxDcMXtAHHM`
|
|
||||||
|
|
||||||
Overrides:
|
|
||||||
- Web/server: `OPENCHAMBER_GITHUB_CLIENT_ID` (env)
|
|
||||||
- Scopes override (web/server): `OPENCHAMBER_GITHUB_SCOPES` (env)
|
|
||||||
|
|
||||||
Note: UI editing of client id/scopes was intentionally removed to reduce user confusion.
|
|
||||||
|
|
||||||
## How to Use in New Features
|
|
||||||
|
|
||||||
Preferred pattern:
|
|
||||||
- UI triggers new feature flows.
|
|
||||||
- Backend (web server or desktop/vscode runtime command/bridge) performs GitHub API calls using the stored token.
|
|
||||||
- Do not expose the token to the UI.
|
|
||||||
|
|
||||||
Web/server feature endpoints should:
|
|
||||||
- require `{ connected: true }` state (return 401 if not connected)
|
|
||||||
- use Octokit with `auth` set to stored token
|
|
||||||
- accept repo/issue/pr identifiers from UI and fetch needed context
|
|
||||||
|
|
||||||
Future “context bootstrap” idea:
|
|
||||||
- Add endpoints that take `{ owner, repo, number }` and return:
|
|
||||||
- issue/PR body
|
|
||||||
- comments
|
|
||||||
- changed files (PR)
|
|
||||||
- diff/patch summary
|
|
||||||
Then UI can start a session with a prefilled prompt.
|
|
||||||
@@ -2028,7 +2028,7 @@ export const ModelControls: React.FC<ModelControlsProps> = ({ className }) => {
|
|||||||
{/* Favorites Section */}
|
{/* Favorites Section */}
|
||||||
{filteredFavorites.length > 0 && (
|
{filteredFavorites.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<DropdownMenuLabel className="typography-ui-header font-semibold text-foreground flex items-center gap-2 px-2 py-1.5">
|
<DropdownMenuLabel className="typography-micro font-semibold text-muted-foreground uppercase tracking-wider flex items-center gap-2 -mx-1 px-3 py-1.5 sticky top-0 z-10 bg-background border-b border-border/30">
|
||||||
<RiStarFill className="h-4 w-4 text-primary" />
|
<RiStarFill className="h-4 w-4 text-primary" />
|
||||||
Favorites
|
Favorites
|
||||||
</DropdownMenuLabel>
|
</DropdownMenuLabel>
|
||||||
@@ -2043,7 +2043,7 @@ export const ModelControls: React.FC<ModelControlsProps> = ({ className }) => {
|
|||||||
{filteredRecents.length > 0 && (
|
{filteredRecents.length > 0 && (
|
||||||
<>
|
<>
|
||||||
{filteredFavorites.length > 0 && <DropdownMenuSeparator />}
|
{filteredFavorites.length > 0 && <DropdownMenuSeparator />}
|
||||||
<DropdownMenuLabel className="typography-ui-header font-semibold text-foreground flex items-center gap-2 px-2 py-1.5">
|
<DropdownMenuLabel className="typography-micro font-semibold text-muted-foreground uppercase tracking-wider flex items-center gap-2 -mx-1 px-3 py-1.5 sticky top-0 z-10 bg-background border-b border-border/30">
|
||||||
<RiTimeLine className="h-4 w-4" />
|
<RiTimeLine className="h-4 w-4" />
|
||||||
Recent
|
Recent
|
||||||
</DropdownMenuLabel>
|
</DropdownMenuLabel>
|
||||||
@@ -2063,7 +2063,7 @@ export const ModelControls: React.FC<ModelControlsProps> = ({ className }) => {
|
|||||||
{filteredProviders.map((provider, index) => (
|
{filteredProviders.map((provider, index) => (
|
||||||
<React.Fragment key={provider.id}>
|
<React.Fragment key={provider.id}>
|
||||||
{index > 0 && <DropdownMenuSeparator />}
|
{index > 0 && <DropdownMenuSeparator />}
|
||||||
<DropdownMenuLabel className="typography-ui-header font-semibold text-foreground flex items-center gap-2 px-2 py-1.5">
|
<DropdownMenuLabel className="typography-micro font-semibold text-muted-foreground uppercase tracking-wider flex items-center gap-2 -mx-1 px-3 py-1.5 sticky top-0 z-10 bg-background border-b border-border/30">
|
||||||
<ProviderLogo
|
<ProviderLogo
|
||||||
providerId={provider.id}
|
providerId={provider.id}
|
||||||
className="h-4 w-4 flex-shrink-0"
|
className="h-4 w-4 flex-shrink-0"
|
||||||
|
|||||||
@@ -411,7 +411,7 @@ export const ModelMultiSelect: React.FC<ModelMultiSelectProps> = ({
|
|||||||
{/* Favorites Section */}
|
{/* Favorites Section */}
|
||||||
{filteredFavorites.length > 0 && (
|
{filteredFavorites.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<div className="typography-ui-header font-semibold text-foreground flex items-center gap-2 px-2 py-1.5">
|
<div className="typography-micro font-semibold text-muted-foreground uppercase tracking-wider flex items-center gap-2 -mx-1 px-3 py-1.5 sticky top-0 z-10 bg-background border-b border-border/30">
|
||||||
<RiStarFill className="h-4 w-4 text-primary" />
|
<RiStarFill className="h-4 w-4 text-primary" />
|
||||||
Favorites
|
Favorites
|
||||||
</div>
|
</div>
|
||||||
@@ -426,7 +426,7 @@ export const ModelMultiSelect: React.FC<ModelMultiSelectProps> = ({
|
|||||||
{filteredRecents.length > 0 && (
|
{filteredRecents.length > 0 && (
|
||||||
<>
|
<>
|
||||||
{filteredFavorites.length > 0 && <div className="h-px bg-border/40 my-1" />}
|
{filteredFavorites.length > 0 && <div className="h-px bg-border/40 my-1" />}
|
||||||
<div className="typography-ui-header font-semibold text-foreground flex items-center gap-2 px-2 py-1.5">
|
<div className="typography-micro font-semibold text-muted-foreground uppercase tracking-wider flex items-center gap-2 -mx-1 px-3 py-1.5 sticky top-0 z-10 bg-background border-b border-border/30">
|
||||||
<RiTimeLine className="h-4 w-4" />
|
<RiTimeLine className="h-4 w-4" />
|
||||||
Recent
|
Recent
|
||||||
</div>
|
</div>
|
||||||
@@ -446,7 +446,7 @@ export const ModelMultiSelect: React.FC<ModelMultiSelectProps> = ({
|
|||||||
{filteredProviders.map((provider, index) => (
|
{filteredProviders.map((provider, index) => (
|
||||||
<React.Fragment key={provider.id}>
|
<React.Fragment key={provider.id}>
|
||||||
{index > 0 && <div className="h-px bg-border/40 my-1" />}
|
{index > 0 && <div className="h-px bg-border/40 my-1" />}
|
||||||
<div className="typography-ui-header font-semibold text-foreground flex items-center gap-2 px-2 py-1.5">
|
<div className="typography-micro font-semibold text-muted-foreground uppercase tracking-wider flex items-center gap-2 -mx-1 px-3 py-1.5 sticky top-0 z-10 bg-background border-b border-border/30">
|
||||||
<ProviderLogo
|
<ProviderLogo
|
||||||
providerId={provider.id}
|
providerId={provider.id}
|
||||||
className="h-4 w-4 flex-shrink-0"
|
className="h-4 w-4 flex-shrink-0"
|
||||||
|
|||||||
@@ -618,7 +618,7 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
|
|||||||
{/* Favorites Section */}
|
{/* Favorites Section */}
|
||||||
{filteredFavorites.length > 0 && (
|
{filteredFavorites.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<DropdownMenuLabel className="typography-ui-header font-semibold text-foreground flex items-center gap-2 px-2 py-1.5">
|
<DropdownMenuLabel className="typography-micro font-semibold text-muted-foreground uppercase tracking-wider flex items-center gap-2 -mx-1 px-3 py-1.5 sticky top-0 z-10 bg-background border-b border-border/30">
|
||||||
<RiStarFill className="h-4 w-4 text-primary" />
|
<RiStarFill className="h-4 w-4 text-primary" />
|
||||||
Favorites
|
Favorites
|
||||||
</DropdownMenuLabel>
|
</DropdownMenuLabel>
|
||||||
@@ -633,7 +633,7 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
|
|||||||
{filteredRecents.length > 0 && (
|
{filteredRecents.length > 0 && (
|
||||||
<>
|
<>
|
||||||
{filteredFavorites.length > 0 && <DropdownMenuSeparator />}
|
{filteredFavorites.length > 0 && <DropdownMenuSeparator />}
|
||||||
<DropdownMenuLabel className="typography-ui-header font-semibold text-foreground flex items-center gap-2 px-2 py-1.5">
|
<DropdownMenuLabel className="typography-micro font-semibold text-muted-foreground uppercase tracking-wider flex items-center gap-2 -mx-1 px-3 py-1.5 sticky top-0 z-10 bg-background border-b border-border/30">
|
||||||
<RiTimeLine className="h-4 w-4" />
|
<RiTimeLine className="h-4 w-4" />
|
||||||
Recent
|
Recent
|
||||||
</DropdownMenuLabel>
|
</DropdownMenuLabel>
|
||||||
@@ -653,7 +653,7 @@ export const ModelSelector: React.FC<ModelSelectorProps> = ({
|
|||||||
{filteredProviders.map((provider, index) => (
|
{filteredProviders.map((provider, index) => (
|
||||||
<React.Fragment key={provider.id}>
|
<React.Fragment key={provider.id}>
|
||||||
{index > 0 && <DropdownMenuSeparator />}
|
{index > 0 && <DropdownMenuSeparator />}
|
||||||
<DropdownMenuLabel className="typography-ui-header font-semibold text-foreground flex items-center gap-2 px-2 py-1.5">
|
<DropdownMenuLabel className="typography-micro font-semibold text-muted-foreground uppercase tracking-wider flex items-center gap-2 -mx-1 px-3 py-1.5 sticky top-0 z-10 bg-background border-b border-border/30">
|
||||||
<ProviderLogo
|
<ProviderLogo
|
||||||
providerId={provider.id}
|
providerId={provider.id}
|
||||||
className="h-4 w-4 flex-shrink-0"
|
className="h-4 w-4 flex-shrink-0"
|
||||||
|
|||||||
-1133
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user