Skip to content

Commit 98a2533

Browse files
committed
refactor: Refactor initialization and token management
1 parent c825e10 commit 98a2533

7 files changed

Lines changed: 151 additions & 147 deletions

File tree

src/lib/cli.ts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,12 @@ const args = {
77
default: false,
88
description: "Show this help message",
99
},
10-
"emulate-streaming": {
11-
type: "boolean",
12-
default: false,
13-
description: "Emulate streaming response for chat completions",
14-
},
1510
port: {
1611
alias: "p",
1712
type: "string",
1813
default: "4141",
1914
description: "Port to listen on",
2015
},
21-
logs: {
22-
type: "boolean",
23-
default: false,
24-
description:
25-
"Write logs to the app directory. Only works with emulate-streaming",
26-
},
2716
} satisfies ArgsDef
2817

2918
export async function getOptions() {

src/lib/initialization.ts

Lines changed: 49 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -4,127 +4,72 @@ import { FetchError } from "ofetch"
44

55
import { PATHS } from "~/lib/paths"
66
import { getGitHubUser } from "~/services/github/get-user/service"
7-
8-
import type { getOptions } from "./cli"
7+
import { tokenService } from "~/services/token-service"
98

109
import { getModels } from "../services/copilot/get-models/service"
11-
import { getCopilotToken } from "../services/copilot/get-token/copilot-token"
1210
import { getGitHubToken } from "../services/github/get-token/service"
13-
import { initializeLogger } from "./logger"
14-
import { getGithubToken, saveGithubToken, TOKENS } from "./tokens"
1511

16-
interface InitStep {
17-
name: string
18-
run: () => Promise<void> | void
12+
// Extract to individual functions for each initialization step
13+
async function initializeAppDirectory(): Promise<void> {
14+
await fs.mkdir(PATHS.APP_DIR, { recursive: true })
15+
await fs.writeFile(PATHS.GITHUB_TOKEN_PATH, "", { flag: "a" })
1916
}
2017

21-
const initSteps: Array<InitStep> = [
22-
{
23-
name: "App directory",
24-
run: async () => {
25-
await fs.mkdir(PATHS.APP_DIR, { recursive: true })
26-
await fs.writeFile(PATHS.GITHUB_TOKEN_PATH, "", { flag: "a" })
27-
},
28-
},
29-
{
30-
name: "GitHub authentication",
31-
run: async () => {
32-
TOKENS.GITHUB_TOKEN = await getGithubToken()
18+
async function initializeGithubAuthentication(): Promise<void> {
19+
const githubToken = await tokenService.getGithubToken()
3320

34-
try {
35-
await logUser()
36-
} catch (error) {
37-
if (!(error instanceof FetchError)) throw error
38-
if (error.statusCode !== 401) {
39-
consola.error("Authentication error:", {
40-
error,
41-
request: error.request,
42-
options: error.options,
43-
response: error.response,
44-
data: error.response?._data as Record<string, unknown>,
45-
})
46-
throw error
47-
}
21+
try {
22+
if (githubToken) {
23+
// Set token in the service so github fetcher can use it
24+
await tokenService.setGithubToken(githubToken)
25+
await logUser()
26+
} else {
27+
throw new Error("No GitHub token available")
28+
}
29+
} catch (error) {
30+
if (error instanceof FetchError && error.statusCode !== 401) {
31+
consola.error("Authentication error:", {
32+
error,
33+
request: error.request,
34+
options: error.options,
35+
response: error.response,
36+
data: error.response?._data as Record<string, unknown>,
37+
})
38+
throw error
39+
}
4840

49-
consola.info("Not logged in, getting new access token")
50-
TOKENS.GITHUB_TOKEN = await initializeGithubToken()
51-
await logUser()
52-
}
53-
},
54-
},
55-
{
56-
name: "Copilot token",
57-
run: async () => {
58-
const { token, refresh_in } = await getCopilotToken()
59-
TOKENS.COPILOT_TOKEN = token
41+
consola.info("Not logged in, getting new access token")
42+
const newToken = await initializeGithubToken()
43+
await tokenService.setGithubToken(newToken)
44+
await logUser()
45+
}
46+
}
6047

61-
const refreshInterval = (refresh_in - 60) * 1000
62-
setInterval(async () => {
63-
consola.start("Refreshing copilot token")
64-
const { token: newToken } = await getCopilotToken()
65-
TOKENS.COPILOT_TOKEN = newToken
66-
}, refreshInterval)
67-
},
68-
},
69-
{
70-
name: "Model information",
71-
run: async () => {
72-
const models = await getModels()
73-
consola.info(
74-
`Available models: \n${models.data.map((model) => `- ${model.id}`).join("\n")}`,
75-
)
76-
},
77-
},
78-
]
48+
async function initializeCopilotToken(): Promise<void> {
49+
await tokenService.initCopilotToken()
50+
}
51+
52+
async function initializeModelInformation(): Promise<void> {
53+
const models = await getModels()
54+
consola.info(
55+
`Available models: \n${models.data.map((model) => `- ${model.id}`).join("\n")}`,
56+
)
57+
}
7958

8059
async function initializeGithubToken() {
8160
consola.start("Getting GitHub device code")
82-
const token = await getGitHubToken()
83-
await saveGithubToken(token.access_token)
84-
return token.access_token
61+
const tokenResponse = await getGitHubToken()
62+
return tokenResponse.access_token
8563
}
8664

8765
async function logUser() {
8866
const user = await getGitHubUser()
8967
consola.info(`Logged in as ${JSON.stringify(user.login)}`)
9068
}
9169

92-
import { configManager } from "./config"
93-
import { initializePort } from "./port"
94-
95-
export async function initializeApp(
96-
options: Awaited<ReturnType<typeof getOptions>>,
97-
) {
98-
configManager.setConfig({
99-
EMULATE_STREAMING: options["emulate-streaming"],
100-
LOGGING_ENABLED: options.logs,
101-
})
102-
103-
// Get available port, trying the CLI option first
104-
const port = await initializePort()
105-
106-
// Initialize logger if enabled
107-
await initializeLogger()
108-
109-
await initialize()
110-
111-
const serverUrl = `http://localhost:${port}`
112-
consola.success(`Server started at ${serverUrl}`)
113-
114-
return {
115-
port,
116-
}
117-
}
118-
119-
async function initialize() {
120-
for (const step of initSteps) {
121-
try {
122-
consola.start(`Initializing ${step.name}...`)
123-
await step.run()
124-
consola.success(`${step.name} initialized`)
125-
} catch (error) {
126-
consola.error(`Failed to initialize ${step.name}:`, error)
127-
throw error
128-
}
129-
}
70+
export async function initializeApp() {
71+
await initializeAppDirectory()
72+
await initializeGithubAuthentication()
73+
await initializeCopilotToken()
74+
await initializeModelInformation()
13075
}

src/lib/logger.ts

Lines changed: 0 additions & 19 deletions
This file was deleted.

src/lib/port.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,12 @@ import { getPort } from "get-port-please"
33

44
import { configManager } from "./config"
55

6-
export async function initializePort(): Promise<number> {
6+
export async function initializePort(requestedPort?: number): Promise<number> {
77
const config = configManager.getConfig()
8-
const requestedPort = config.PORT
98

109
const port = await getPort({
1110
name: "copilot-api",
12-
port: config.PORT,
11+
port: requestedPort ?? config.PORT,
1312
portRange: config.PORT_RANGE,
1413
random: false,
1514
})

src/main.ts

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,37 @@
11
#!/usr/bin/env node
22

3+
import { defineCommand, runMain } from "citty"
4+
import consola from "consola"
35
import { serve, type ServerHandler } from "srvx"
46

5-
import { getOptions } from "./lib/cli"
67
import { initializeApp } from "./lib/initialization"
8+
import { initializePort } from "./lib/port"
79
import { server } from "./server"
810

9-
const options = await getOptions()
11+
const main = defineCommand({
12+
args: {
13+
port: {
14+
alias: "p",
15+
type: "string",
16+
default: "4141",
17+
description: "Port to listen on",
18+
},
19+
},
20+
async run({ args }) {
21+
const portInt = parseInt(args.port, 10)
1022

11-
const { port } = await initializeApp(options)
23+
const port = await initializePort(portInt)
1224

13-
serve({
14-
fetch: server.fetch as ServerHandler,
15-
port,
25+
await initializeApp()
26+
27+
const serverUrl = `http://localhost:${port}`
28+
consola.success(`Server started at ${serverUrl}`)
29+
30+
serve({
31+
fetch: server.fetch as ServerHandler,
32+
port,
33+
})
34+
},
1635
})
36+
37+
await runMain(main)

src/services/api-instance.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,17 @@ import {
66
GITHUB_API_CONFIG,
77
GITHUB_WEB_API_CONFIG,
88
} from "~/lib/constants"
9-
import { TOKENS } from "~/lib/tokens"
9+
import { tokenService } from "~/services/token-service"
1010

1111
export const copilot = ofetch.create({
1212
baseURL: COPILOT_API_CONFIG.baseURL,
1313
headers: COPILOT_API_CONFIG.headers,
1414

1515
onRequest({ options }) {
16-
options.headers.set("authorization", `Bearer ${TOKENS.COPILOT_TOKEN}`)
16+
options.headers.set(
17+
"authorization",
18+
`Bearer ${tokenService.getCopilotToken()}`,
19+
)
1720
},
1821

1922
onRequestError({ error, options }) {
@@ -38,8 +41,9 @@ export const copilot = ofetch.create({
3841
export const github = ofetch.create({
3942
baseURL: GITHUB_API_CONFIG.baseURL,
4043

41-
onRequest({ options }) {
42-
options.headers.set("authorization", `token ${TOKENS.GITHUB_TOKEN}`)
44+
async onRequest({ options }) {
45+
const token = await tokenService.getGithubToken()
46+
options.headers.set("authorization", `token ${token}`)
4347
},
4448
})
4549

src/services/token-service.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import consola from "consola"
2+
import fs from "node:fs/promises"
3+
4+
import { PATHS } from "~/lib/paths"
5+
import { getCopilotToken } from "~/services/copilot/get-token/copilot-token"
6+
7+
// Simple token manager with basic encapsulation
8+
export const tokenService = {
9+
// Private token storage
10+
_tokens: {
11+
github: undefined as string | undefined,
12+
copilot: undefined as string | undefined,
13+
},
14+
15+
// Get GitHub token
16+
async getGithubToken(): Promise<string | undefined> {
17+
if (!this._tokens.github) {
18+
try {
19+
this._tokens.github = await fs.readFile(
20+
PATHS.GITHUB_TOKEN_PATH,
21+
"utf-8",
22+
)
23+
} catch (error) {
24+
consola.warn("Failed to load GitHub token", error)
25+
}
26+
}
27+
28+
return this._tokens.github
29+
},
30+
31+
// Set GitHub token
32+
async setGithubToken(token: string): Promise<void> {
33+
this._tokens.github = token
34+
await fs.writeFile(PATHS.GITHUB_TOKEN_PATH, token)
35+
},
36+
37+
// Get Copilot token
38+
getCopilotToken(): string | undefined {
39+
return this._tokens.copilot
40+
},
41+
42+
// Set Copilot token
43+
setCopilotToken(token: string): void {
44+
this._tokens.copilot = token
45+
},
46+
47+
// Initialize Copilot token with auto-refresh
48+
async initCopilotToken(): Promise<void> {
49+
const { token, refresh_in } = await getCopilotToken()
50+
this.setCopilotToken(token)
51+
52+
// Set up refresh timer
53+
const refreshInterval = (refresh_in - 60) * 1000
54+
setInterval(async () => {
55+
consola.start("Refreshing Copilot token")
56+
try {
57+
const { token: newToken } = await getCopilotToken()
58+
this.setCopilotToken(newToken)
59+
consola.success("Copilot token refreshed")
60+
} catch (error) {
61+
consola.error("Failed to refresh Copilot token:", error)
62+
}
63+
}, refreshInterval)
64+
},
65+
}

0 commit comments

Comments
 (0)