Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
1abf786
feat(db): add SQLite module with migrations (Task #1)
lubobill1990 Apr 25, 2026
5b9a0f0
feat(accounts): multi-account pool skeleton (Task #2)
lubobill1990 Apr 25, 2026
9c46b58
feat: add robust model mapping for messages and chat completions
lubobill1990 Apr 25, 2026
6ec05a6
feat(services): thread Account through API helpers (Task #3)
lubobill1990 Apr 25, 2026
5a20f32
feat(accounts): withAccount wrapper for retry/cooldown (Task #4)
lubobill1990 Apr 25, 2026
be2fd05
feat(usage): pure usage normalizer + stream accumulators (Task #5)
lubobill1990 Apr 25, 2026
e9d0876
feat(usage): usage recorder writes events + daily aggregate (Task #6)
lubobill1990 Apr 25, 2026
dd6ea1c
feat(usage): record non-streaming chat-completions usage (Task #7)
lubobill1990 Apr 25, 2026
8999180
feat(usage): record streaming chat-completions usage (Task #8)
lubobill1990 Apr 25, 2026
93bd534
feat(usage): record /v1/embeddings (Task #9)
lubobill1990 Apr 25, 2026
3b5d4bd
feat(usage): record /v1/messages usage (Task #10)
lubobill1990 Apr 25, 2026
eb45f3a
feat(usage): /usage returns three-lens cost stats (Task #11)
lubobill1990 Apr 25, 2026
bd0a68f
feat(pricing): sync sources, LLM caller, validators (Task #12)
lubobill1990 Apr 25, 2026
f16e5ac
feat(pricing): version-write logic for pricing sync (Task #13)
lubobill1990 Apr 25, 2026
c1aa449
feat(pricing): background pricing sync scheduler (Task #14)
lubobill1990 Apr 25, 2026
74d02a9
feat(pricing): add pricing-sync subcommand for manual sync (Task #15)
lubobill1990 Apr 25, 2026
1dffe4c
feat(usage): expose pricing metadata and sync status in /usage (Task …
lubobill1990 Apr 25, 2026
0527f96
fix(pricing): fix 400 error in pricing sync LLM call
lubobill1990 Apr 25, 2026
3e0a5d7
feat: add multi-account auth management (add/list/remove)
lubobill1990 Apr 25, 2026
5d8f8d7
test: add unit tests for account file CRUD helpers
lubobill1990 Apr 25, 2026
9ca3265
feat: cross-runtime SQLite, multi-token CLI, updated README
lubobill1990 Apr 26, 2026
f60b5f1
chore: enforce LF line endings, add npm publish workflow
lubobill1990 Apr 26, 2026
944c531
fix: always parse --github-token format, not only when comma present
lubobill1990 Apr 26, 2026
64ab17e
feat: support repeated --github-token flags
lubobill1990 Apr 26, 2026
2e7c01d
docs: update README with repeated --github-token flag support
lubobill1990 Apr 26, 2026
e1b1a67
feat: auto-detect account type and username from GitHub token
lubobill1990 Apr 26, 2026
b614d5c
feat: log request headers to file for session analysis
lubobill1990 Apr 26, 2026
2899905
feat: replace header logger with configurable request recorder
lubobill1990 Apr 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Force LF line endings everywhere (cross-platform consistency)
* text=auto eol=lf
34 changes: 34 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Publish to npm

on:
release:
types: [published]

permissions:
contents: read
id-token: write

jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: oven-sh/setup-bun@v2
with:
bun-version: latest

- run: bun install --frozen-lockfile

- run: bun test

- run: bun run build

- uses: actions/setup-node@v4
with:
node-version: 22
registry-url: https://registry.npmjs.org

- run: npm publish --access public --ignore-scripts
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
499 changes: 267 additions & 232 deletions README.md

Large diffs are not rendered by default.

77 changes: 77 additions & 0 deletions bun.lock

Large diffs are not rendered by default.

10 changes: 6 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "copilot-api",
"version": "0.7.0",
"name": "@weavejam/copilot-proxy",
"version": "0.9.0",
"description": "Turn GitHub Copilot into OpenAI/Anthropic API compatible server. Usable with Claude Code!",
"keywords": [
"proxy",
Expand All @@ -13,10 +13,10 @@
"type": "git",
"url": "git+https://github.com/ericc-ch/copilot-api.git"
},
"author": "Erick Christian <erickchristian48@gmail.com>",
"author": "Bo Lu <lubo@weavejam.com>",
"type": "module",
"bin": {
"copilot-api": "./dist/main.js"
"copilot-proxy": "./dist/main.js"
},
"files": [
"dist"
Expand All @@ -40,6 +40,7 @@
"*": "bun run lint --fix"
},
"dependencies": {
"better-sqlite3": "^12.9.0",
"citty": "^0.1.6",
"clipboardy": "^5.0.0",
"consola": "^3.4.2",
Expand All @@ -54,6 +55,7 @@
},
"devDependencies": {
"@echristian/eslint-config": "^0.0.54",
"@types/better-sqlite3": "^7.6.13",
"@types/bun": "^1.2.23",
"@types/proxy-from-env": "^1.0.4",
"bumpp": "^10.2.3",
Expand Down
61 changes: 61 additions & 0 deletions src/auth-add.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { defineCommand } from "citty"
import consola from "consola"

import { addAccountEntry } from "./lib/accounts-loader"
import { ensurePaths } from "./lib/paths"
import { state } from "./lib/state"
import { runDeviceFlow } from "./lib/token"
import { detectAccountInfo } from "./services/github/detect-account-info"

interface RunAuthAddOptions {
name?: string
verbose: boolean
}

export async function runAuthAdd(options: RunAuthAddOptions): Promise<void> {
if (options.verbose) {
consola.level = 5
}
await ensurePaths()

consola.info("Starting GitHub Device Flow authentication…")
const token = await runDeviceFlow()

const info = await detectAccountInfo(token)
const name = options.name ?? info.login
state.accountType = info.accountType

consola.info(`Detected GitHub user: ${info.login} (${info.accountType})`)

await addAccountEntry({
name,
github_token: token,
account_type: info.accountType,
})
}

export const authAdd = defineCommand({
meta: {
name: "add",
description: "Add a new GitHub account via Device Flow OAuth",
},
args: {
name: {
alias: "n",
type: "string",
description: "Account name (defaults to GitHub username if not provided)",
},
verbose: {
alias: "v",
type: "boolean",
default: false,
description: "Enable verbose logging",
},
},
run({ args }) {
return runAuthAdd({
name: args.name,
verbose: args.verbose,
})
},
})
71 changes: 71 additions & 0 deletions src/auth-list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { defineCommand } from "citty"
import consola from "consola"

import { readAccountsFile } from "./lib/accounts-loader"
import { ensurePaths } from "./lib/paths"
import { state } from "./lib/state"
import { getGitHubUser } from "./services/github/get-user"

interface RunAuthListOptions {
verbose: boolean
}

export async function runAuthList(options: RunAuthListOptions): Promise<void> {
if (options.verbose) {
consola.level = 5
}
await ensurePaths()

const data = await readAccountsFile()
if (data.accounts.length === 0) {
consola.info("No accounts found. Use `auth add` to add one.")
return
}

const rows: Array<{ name: string; type: string; login: string }> = []
for (const entry of data.accounts) {
let login: string
try {
const user = await getGitHubUser({
account: {
name: entry.name,
accountType: entry.account_type ?? "individual",
githubToken: entry.github_token,
copilotTokenRefreshAt: 0,
inFlight: 0,
lastUsedAt: 0,
failureCount: 0,
},
vsCodeVersion: state.vsCodeVersion,
})
login = user.login
} catch {
login = "(token invalid)"
}
rows.push({
name: entry.name,
type: entry.account_type ?? "individual",
login,
})
}

console.table(rows)
}

export const authList = defineCommand({
meta: {
name: "list",
description: "List all configured GitHub accounts",
},
args: {
verbose: {
alias: "v",
type: "boolean",
default: false,
description: "Enable verbose logging",
},
},
run({ args }) {
return runAuthList({ verbose: args.verbose })
},
})
52 changes: 52 additions & 0 deletions src/auth-remove.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { defineCommand } from "citty"
import consola from "consola"

import { removeAccountEntry } from "./lib/accounts-loader"
import { ensurePaths } from "./lib/paths"

interface RunAuthRemoveOptions {
name: string
verbose: boolean
}

export async function runAuthRemove(
options: RunAuthRemoveOptions,
): Promise<void> {
if (options.verbose) {
consola.level = 5
}
await ensurePaths()

const removed = await removeAccountEntry(options.name)
if (!removed) {
consola.warn(`Account "${options.name}" not found`)
process.exitCode = 1
}
}

export const authRemove = defineCommand({
meta: {
name: "remove",
description: "Remove a GitHub account by name",
},
args: {
name: {
alias: "n",
type: "string",
required: true,
description: "Name of the account to remove",
},
verbose: {
alias: "v",
type: "boolean",
default: false,
description: "Enable verbose logging",
},
},
run({ args }) {
return runAuthRemove({
name: args.name,
verbose: args.verbose,
})
},
})
11 changes: 10 additions & 1 deletion src/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
import { defineCommand } from "citty"
import consola from "consola"

import { authAdd } from "./auth-add"
import { authList } from "./auth-list"
import { authRemove } from "./auth-remove"
import { PATHS, ensurePaths } from "./lib/paths"
import { state } from "./lib/state"
import { setupGitHubToken } from "./lib/token"
Expand All @@ -28,7 +31,8 @@ export async function runAuth(options: RunAuthOptions): Promise<void> {
export const auth = defineCommand({
meta: {
name: "auth",
description: "Run GitHub auth flow without running the server",
description:
"Manage GitHub authentication. Run without subcommand for legacy single-token flow.",
},
args: {
verbose: {
Expand All @@ -43,6 +47,11 @@ export const auth = defineCommand({
description: "Show GitHub token on auth",
},
},
subCommands: {
add: authAdd,
list: authList,
remove: authRemove,
},
run({ args }) {
return runAuth({
verbose: args.verbose,
Expand Down
16 changes: 14 additions & 2 deletions src/check-usage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { defineCommand } from "citty"
import consola from "consola"

import { ensurePaths } from "./lib/paths"
import { state } from "./lib/state"
import { setupGitHubToken } from "./lib/token"
import {
getCopilotUsage,
Expand All @@ -15,9 +16,20 @@ export const checkUsage = defineCommand({
},
async run() {
await ensurePaths()
await setupGitHubToken()
const githubToken = await setupGitHubToken()
try {
const usage = await getCopilotUsage()
const usage = await getCopilotUsage({
account: {
name: "_check-usage",
accountType: state.accountType,
githubToken,
copilotTokenRefreshAt: 0,
inFlight: 0,
lastUsedAt: 0,
failureCount: 0,
},
vsCodeVersion: state.vsCodeVersion,
})
const premium = usage.quota_snapshots.premium_interactions
const premiumTotal = premium.entitlement
const premiumUsed = premiumTotal - premium.remaining
Expand Down
Loading
Loading