@jiayunxie/aerial 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/cli.js CHANGED
@@ -1,151 +1,151 @@
1
- #!/usr/bin/env node
2
- import { startDeviceFlow, pollDeviceFlow } from "./auth.js";
3
- import { ensureApiKey, loadConfig, saveConfig } from "./config.js";
4
- import { startServer } from "./server.js";
5
- import { setupClaude, setupCodex } from "./setup.js";
6
- import { doctor } from "./doctor.js";
7
- import { runProbe, formatProbeReport } from "./probe.js";
8
- import { printVersion } from "./version.js";
9
-
10
- function printHelp() {
11
- console.log(`Aerial local Copilot proxy
12
-
13
- Usage:
14
- aerial --version
15
- aerial login
16
- aerial key generate
17
- aerial key print
18
- aerial start [--host 127.0.0.1] [--port 18181]
19
- aerial setup codex [--model <id>]
20
- aerial setup claude [--model <id>]
21
- aerial setup all [--model <id>]
22
- aerial doctor
23
- aerial probe [--live] [--json]
24
-
25
- MVP routes:
26
- GET /health
27
- GET /v1/models
28
- POST /v1/responses
29
- POST /v1/messages
30
- POST /v1/messages/count_tokens
31
- POST /v1/chat/completions`);
32
- }
33
-
34
- function argValue(args, name) {
35
- const index = args.indexOf(name);
36
- return index >= 0 ? args[index + 1] : undefined;
37
- }
38
-
39
- async function main() {
40
- const args = process.argv.slice(2);
41
- const [command, subcommand, ...rest] = args;
42
- if (!command || command === "--help" || command === "-h") return printHelp();
43
- if (command === "--version") return printVersion();
44
-
45
- if (command === "login") {
46
- const flow = await startDeviceFlow();
47
- console.log(`Open: ${flow.verification_uri}`);
48
- console.log(`Code: ${flow.user_code}`);
49
- console.log("Waiting for GitHub authorization...");
50
- await pollDeviceFlow(flow.device_code, flow.interval);
51
- console.log("GitHub login saved.");
52
- return;
53
- }
54
-
55
- if (command === "key") {
56
- if (subcommand === "generate") {
57
- const result = ensureApiKey();
58
- if (result.apiKey) {
59
- console.log("Local Aerial key generated and stored privately.");
60
- } else {
61
- console.log("Aerial API key already configured.");
62
- }
63
- return;
64
- }
65
- if (subcommand === "print") {
66
- const result = ensureApiKey();
67
- if (result.apiKey) console.log(result.apiKey);
68
- else throw new Error("Raw API key is not available. Run: aerial key generate");
69
- return;
70
- }
71
- }
72
-
73
- if (command === "start") {
74
- const config = loadConfig();
75
- const host = argValue(args, "--host") || config.host;
76
- const port = Number(argValue(args, "--port") || config.port);
77
- ensureApiKey();
78
- startServer({ host, port });
79
- return;
80
- }
81
-
82
- if (command === "setup") {
83
- if (subcommand === "codex") {
84
- const result = setupCodex({ model: argValue(rest, "--model") });
85
- console.log(`Updated Codex config: ${result.file}`);
86
- if (result.backup) console.log(`Backup: ${result.backup}`);
87
- if (result.env?.persisted) console.log(`Configured ${result.env.name} for new user sessions.`);
88
- else if (result.env?.reason) console.log(`Note: ${result.env.name} is available in this process, but was not persisted (${result.env.reason}).`);
89
- return;
90
- }
91
- if (subcommand === "claude") {
92
- const result = setupClaude({ model: argValue(rest, "--model") });
93
- console.log(`Updated Claude settings: ${result.file}`);
94
- if (result.model) console.log(`Configured Claude default model: ${result.model}`);
95
- if (result.backup) console.log(`Backup: ${result.backup}`);
96
- return;
97
- }
98
- if (subcommand === "all") {
99
- const model = argValue(rest, "--model");
100
- const codex = setupCodex({ model });
101
- const claude = setupClaude({ model });
102
- console.log(`Updated Codex config: ${codex.file}`);
103
- if (codex.env?.persisted) console.log(`Configured ${codex.env.name} for new user sessions.`);
104
- else if (codex.env?.reason) console.log(`Note: ${codex.env.name} is available in this process, but was not persisted (${codex.env.reason}).`);
105
- console.log(`Updated Claude settings: ${claude.file}`);
106
- if (claude.model) console.log(`Configured Claude default model: ${claude.model}`);
107
- return;
108
- }
109
- }
110
-
111
- if (command === "doctor") {
112
- const result = doctor();
113
- for (const check of result.checks) {
114
- console.log(`${check.ok ? "OK" : "FAIL"} ${check.name}: ${check.detail}`);
115
- }
116
- process.exitCode = result.ok ? 0 : 1;
117
- return;
118
- }
119
-
120
- if (command === "probe") {
121
- const report = await runProbe({ live: args.includes("--live") });
122
- if (args.includes("--json")) console.log(JSON.stringify(report, null, 2));
123
- else console.log(formatProbeReport(report));
124
- process.exitCode = report.ok ? 0 : 1;
125
- return;
126
- }
127
-
128
- if (command === "config") {
129
- const config = loadConfig();
130
- if (subcommand === "set") {
131
- const [key, value] = rest;
132
- if (!key || value === undefined) throw new Error("Usage: aerial config set <key> <value>");
133
- if (!["host", "port", "defaultModel", "logLevel", "promptCacheRetention", "promptCacheKey"].includes(key)) throw new Error(`Unsupported config key: ${key}`);
134
- if (key === "promptCacheRetention" && !["in_memory", "24h", "off"].includes(value)) throw new Error("promptCacheRetention must be one of: in_memory, 24h, off");
135
- if (key === "promptCacheKey" && !value.trim()) throw new Error("promptCacheKey must be auto, off, or a non-empty string");
136
- config[key] = key === "port" ? Number(value) : value;
137
- saveConfig(config);
138
- return;
139
- }
140
- console.log(JSON.stringify(config, null, 2));
141
- return;
142
- }
143
-
144
- printHelp();
145
- process.exitCode = 1;
146
- }
147
-
148
- main().catch((error) => {
149
- console.error(error.message);
150
- process.exitCode = 1;
151
- });
1
+ #!/usr/bin/env node
2
+ import { startDeviceFlow, pollDeviceFlow } from "./auth.js";
3
+ import { ensureApiKey, loadConfig, saveConfig } from "./config.js";
4
+ import { startServer } from "./server.js";
5
+ import { setupClaude, setupCodex } from "./setup.js";
6
+ import { doctor } from "./doctor.js";
7
+ import { runProbe, formatProbeReport } from "./probe.js";
8
+ import { printVersion } from "./version.js";
9
+
10
+ function printHelp() {
11
+ console.log(`Aerial local Copilot proxy
12
+
13
+ Usage:
14
+ aerial --version
15
+ aerial login
16
+ aerial key generate
17
+ aerial key print
18
+ aerial start [--host 127.0.0.1] [--port 18181]
19
+ aerial setup codex [--model <id>]
20
+ aerial setup claude [--model <id>]
21
+ aerial setup all [--model <id>]
22
+ aerial doctor
23
+ aerial probe [--live] [--json]
24
+
25
+ MVP routes:
26
+ GET /health
27
+ GET /v1/models
28
+ POST /v1/responses
29
+ POST /v1/messages
30
+ POST /v1/messages/count_tokens
31
+ POST /v1/chat/completions`);
32
+ }
33
+
34
+ function argValue(args, name) {
35
+ const index = args.indexOf(name);
36
+ return index >= 0 ? args[index + 1] : undefined;
37
+ }
38
+
39
+ async function main() {
40
+ const args = process.argv.slice(2);
41
+ const [command, subcommand, ...rest] = args;
42
+ if (!command || command === "--help" || command === "-h") return printHelp();
43
+ if (command === "--version") return printVersion();
44
+
45
+ if (command === "login") {
46
+ const flow = await startDeviceFlow();
47
+ console.log(`Open: ${flow.verification_uri}`);
48
+ console.log(`Code: ${flow.user_code}`);
49
+ console.log("Waiting for GitHub authorization...");
50
+ await pollDeviceFlow(flow.device_code, flow.interval);
51
+ console.log("GitHub login saved.");
52
+ return;
53
+ }
54
+
55
+ if (command === "key") {
56
+ if (subcommand === "generate") {
57
+ const result = ensureApiKey();
58
+ if (result.apiKey) {
59
+ console.log("Local Aerial key generated and stored privately.");
60
+ } else {
61
+ console.log("Aerial API key already configured.");
62
+ }
63
+ return;
64
+ }
65
+ if (subcommand === "print") {
66
+ const result = ensureApiKey();
67
+ if (result.apiKey) console.log(result.apiKey);
68
+ else throw new Error("Raw API key is not available. Run: aerial key generate");
69
+ return;
70
+ }
71
+ }
72
+
73
+ if (command === "start") {
74
+ const config = loadConfig();
75
+ const host = argValue(args, "--host") || config.host;
76
+ const port = Number(argValue(args, "--port") || config.port);
77
+ ensureApiKey();
78
+ startServer({ host, port });
79
+ return;
80
+ }
81
+
82
+ if (command === "setup") {
83
+ if (subcommand === "codex") {
84
+ const result = setupCodex({ model: argValue(rest, "--model") });
85
+ console.log(`Updated Codex config: ${result.file}`);
86
+ if (result.backup) console.log(`Backup: ${result.backup}`);
87
+ if (result.env?.persisted) console.log(`Configured ${result.env.name} for new user sessions.`);
88
+ else if (result.env?.reason) console.log(`Note: ${result.env.name} is available in this process, but was not persisted (${result.env.reason}).`);
89
+ return;
90
+ }
91
+ if (subcommand === "claude") {
92
+ const result = setupClaude({ model: argValue(rest, "--model") });
93
+ console.log(`Updated Claude settings: ${result.file}`);
94
+ if (result.model) console.log(`Configured Claude default model: ${result.model}`);
95
+ if (result.backup) console.log(`Backup: ${result.backup}`);
96
+ return;
97
+ }
98
+ if (subcommand === "all") {
99
+ const model = argValue(rest, "--model");
100
+ const codex = setupCodex({ model });
101
+ const claude = setupClaude({ model });
102
+ console.log(`Updated Codex config: ${codex.file}`);
103
+ if (codex.env?.persisted) console.log(`Configured ${codex.env.name} for new user sessions.`);
104
+ else if (codex.env?.reason) console.log(`Note: ${codex.env.name} is available in this process, but was not persisted (${codex.env.reason}).`);
105
+ console.log(`Updated Claude settings: ${claude.file}`);
106
+ if (claude.model) console.log(`Configured Claude default model: ${claude.model}`);
107
+ return;
108
+ }
109
+ }
110
+
111
+ if (command === "doctor") {
112
+ const result = doctor();
113
+ for (const check of result.checks) {
114
+ console.log(`${check.ok ? "OK" : "FAIL"} ${check.name}: ${check.detail}`);
115
+ }
116
+ process.exitCode = result.ok ? 0 : 1;
117
+ return;
118
+ }
119
+
120
+ if (command === "probe") {
121
+ const report = await runProbe({ live: args.includes("--live") });
122
+ if (args.includes("--json")) console.log(JSON.stringify(report, null, 2));
123
+ else console.log(formatProbeReport(report));
124
+ process.exitCode = report.ok ? 0 : 1;
125
+ return;
126
+ }
127
+
128
+ if (command === "config") {
129
+ const config = loadConfig();
130
+ if (subcommand === "set") {
131
+ const [key, value] = rest;
132
+ if (!key || value === undefined) throw new Error("Usage: aerial config set <key> <value>");
133
+ if (!["host", "port", "defaultModel", "logLevel", "promptCacheRetention", "promptCacheKey"].includes(key)) throw new Error(`Unsupported config key: ${key}`);
134
+ if (key === "promptCacheRetention" && !["in_memory", "24h", "off"].includes(value)) throw new Error("promptCacheRetention must be one of: in_memory, 24h, off");
135
+ if (key === "promptCacheKey" && !value.trim()) throw new Error("promptCacheKey must be auto, off, or a non-empty string");
136
+ config[key] = key === "port" ? Number(value) : value;
137
+ saveConfig(config);
138
+ return;
139
+ }
140
+ console.log(JSON.stringify(config, null, 2));
141
+ return;
142
+ }
143
+
144
+ printHelp();
145
+ process.exitCode = 1;
146
+ }
147
+
148
+ main().catch((error) => {
149
+ console.error(error.message);
150
+ process.exitCode = 1;
151
+ });
package/src/config.js CHANGED
@@ -2,21 +2,21 @@ import fs from "node:fs";
2
2
  import { CONFIG_VERSION, DEFAULT_HOST, DEFAULT_PORT, DEFAULT_VERSIONS } from "./constants.js";
3
3
  import { apiKeyPath, configPath, readJsonIfExists, writeJsonPrivate, writePrivateFile } from "./paths.js";
4
4
  import { hashApiKey, randomApiKey, verifyApiKey } from "./crypto.js";
5
-
6
- export function defaultConfig() {
7
- return {
8
- version: CONFIG_VERSION,
9
- host: DEFAULT_HOST,
10
- port: DEFAULT_PORT,
11
- apiKeyHash: undefined,
12
- defaultModel: undefined,
13
- logLevel: "info",
5
+
6
+ export function defaultConfig() {
7
+ return {
8
+ version: CONFIG_VERSION,
9
+ host: DEFAULT_HOST,
10
+ port: DEFAULT_PORT,
11
+ apiKeyHash: undefined,
12
+ defaultModel: undefined,
13
+ logLevel: "info",
14
14
  promptCacheRetention: "in_memory",
15
- promptCacheKey: "auto",
16
- versions: DEFAULT_VERSIONS
17
- };
18
- }
19
-
15
+ promptCacheKey: "auto",
16
+ versions: DEFAULT_VERSIONS
17
+ };
18
+ }
19
+
20
20
  export function loadConfig() {
21
21
  const loaded = readJsonIfExists(configPath()) || {};
22
22
  const envRetention = process.env.AERIAL_PROMPT_CACHE_RETENTION;
@@ -25,8 +25,8 @@ export function loadConfig() {
25
25
  const promptCacheRetention = envRetention === undefined ? (loaded.promptCacheRetention ?? defaults.promptCacheRetention) : envRetention;
26
26
  const promptCacheKey = envCacheKey === undefined ? (loaded.promptCacheKey ?? defaults.promptCacheKey) : envCacheKey;
27
27
  return { ...defaults, ...loaded, promptCacheRetention, promptCacheKey, versions: { ...DEFAULT_VERSIONS, ...(loaded.versions || {}) } };
28
- }
29
-
28
+ }
29
+
30
30
  export function saveConfig(config) {
31
31
  writeJsonPrivate(configPath(), config);
32
32
  }
@@ -65,17 +65,17 @@ export function ensureApiKey() {
65
65
  saveConfig(config);
66
66
  return { apiKey, config, created: true, source: hadHash ? "rotated" : "generated" };
67
67
  }
68
-
69
- export function validateLocalAuth(headers, config = loadConfig()) {
70
- const lowerHeaders = Object.fromEntries(Object.entries(headers).map(([key, value]) => [key.toLowerCase(), value]));
71
- const authorization = lowerHeaders.authorization;
72
- const bearer = authorization?.startsWith("Bearer ") ? authorization.slice("Bearer ".length).trim() : undefined;
73
- const apiKey = lowerHeaders["x-api-key"] || bearer;
74
- return verifyApiKey(apiKey, config.apiKeyHash);
75
- }
76
-
77
- export function requireConfigFile() {
78
- if (!fs.existsSync(configPath())) {
79
- throw new Error(`Aerial is not initialized. Run: aerial key generate`);
80
- }
81
- }
68
+
69
+ export function validateLocalAuth(headers, config = loadConfig()) {
70
+ const lowerHeaders = Object.fromEntries(Object.entries(headers).map(([key, value]) => [key.toLowerCase(), value]));
71
+ const authorization = lowerHeaders.authorization;
72
+ const bearer = authorization?.startsWith("Bearer ") ? authorization.slice("Bearer ".length).trim() : undefined;
73
+ const apiKey = lowerHeaders["x-api-key"] || bearer;
74
+ return verifyApiKey(apiKey, config.apiKeyHash);
75
+ }
76
+
77
+ export function requireConfigFile() {
78
+ if (!fs.existsSync(configPath())) {
79
+ throw new Error(`Aerial is not initialized. Run: aerial key generate`);
80
+ }
81
+ }
package/src/constants.js CHANGED
@@ -1,11 +1,11 @@
1
- export const DEFAULT_HOST = "127.0.0.1";
2
- export const DEFAULT_PORT = 18181;
3
- export const CONFIG_VERSION = 1;
4
- export const GITHUB_CLIENT_ID = "Iv1.b507a08c87ecfe98";
5
- export const COPILOT_TOKEN_URL = "https://api.github.com/copilot_internal/v2/token";
6
- export const COPILOT_API_ORIGIN = "https://api.githubcopilot.com";
7
- export const DEFAULT_VERSIONS = {
8
- vscode: "1.105.0",
9
- copilotChat: "0.45.1"
10
- };
11
- export const DEFAULT_ANTHROPIC_VERSION = "2023-06-01";
1
+ export const DEFAULT_HOST = "127.0.0.1";
2
+ export const DEFAULT_PORT = 18181;
3
+ export const CONFIG_VERSION = 1;
4
+ export const GITHUB_CLIENT_ID = "Iv1.b507a08c87ecfe98";
5
+ export const COPILOT_TOKEN_URL = "https://api.github.com/copilot_internal/v2/token";
6
+ export const COPILOT_API_ORIGIN = "https://api.githubcopilot.com";
7
+ export const DEFAULT_VERSIONS = {
8
+ vscode: "1.105.0",
9
+ copilotChat: "0.45.1"
10
+ };
11
+ export const DEFAULT_ANTHROPIC_VERSION = "2023-06-01";
package/src/crypto.js CHANGED
@@ -1,25 +1,25 @@
1
- import crypto from "node:crypto";
2
-
3
- export function randomApiKey() {
4
- return `aerial_${crypto.randomBytes(24).toString("base64url")}`;
5
- }
6
-
7
- export function hashApiKey(key, salt = crypto.randomBytes(16).toString("base64url")) {
8
- const hash = crypto.scryptSync(key, salt, 32).toString("base64url");
9
- return `scrypt$${salt}$${hash}`;
10
- }
11
-
12
- export function verifyApiKey(key, encoded) {
13
- if (!key || !encoded) return false;
14
- const [kind, salt, expected] = encoded.split("$");
15
- if (kind !== "scrypt" || !salt || !expected) return false;
16
- const actual = crypto.scryptSync(key, salt, 32);
17
- const expectedBuffer = Buffer.from(expected, "base64url");
18
- return actual.length === expectedBuffer.length && crypto.timingSafeEqual(actual, expectedBuffer);
19
- }
20
-
21
- export function redact(value) {
22
- if (!value) return "";
23
- if (value.length <= 8) return "********";
24
- return `${value.slice(0, 4)}...${value.slice(-4)}`;
25
- }
1
+ import crypto from "node:crypto";
2
+
3
+ export function randomApiKey() {
4
+ return `aerial_${crypto.randomBytes(24).toString("base64url")}`;
5
+ }
6
+
7
+ export function hashApiKey(key, salt = crypto.randomBytes(16).toString("base64url")) {
8
+ const hash = crypto.scryptSync(key, salt, 32).toString("base64url");
9
+ return `scrypt$${salt}$${hash}`;
10
+ }
11
+
12
+ export function verifyApiKey(key, encoded) {
13
+ if (!key || !encoded) return false;
14
+ const [kind, salt, expected] = encoded.split("$");
15
+ if (kind !== "scrypt" || !salt || !expected) return false;
16
+ const actual = crypto.scryptSync(key, salt, 32);
17
+ const expectedBuffer = Buffer.from(expected, "base64url");
18
+ return actual.length === expectedBuffer.length && crypto.timingSafeEqual(actual, expectedBuffer);
19
+ }
20
+
21
+ export function redact(value) {
22
+ if (!value) return "";
23
+ if (value.length <= 8) return "********";
24
+ return `${value.slice(0, 4)}...${value.slice(-4)}`;
25
+ }
package/src/doctor.js CHANGED
@@ -1,15 +1,15 @@
1
1
  import fs from "node:fs";
2
2
  import { apiKeyPath, configPath, githubTokenPath } from "./paths.js";
3
3
  import { loadConfig } from "./config.js";
4
-
5
- export function doctor() {
6
- const config = loadConfig();
7
- const checks = [
8
- { name: "config", ok: fs.existsSync(configPath()), detail: configPath() },
4
+
5
+ export function doctor() {
6
+ const config = loadConfig();
7
+ const checks = [
8
+ { name: "config", ok: fs.existsSync(configPath()), detail: configPath() },
9
9
  { name: "api_key", ok: Boolean(config.apiKeyHash), detail: fs.existsSync(apiKeyPath()) ? apiKeyPath() : config.apiKeyHash ? "hash configured" : "run: aerial setup all" },
10
- { name: "github_token", ok: fs.existsSync(githubTokenPath()) || Boolean(process.env.AERIAL_GITHUB_TOKEN), detail: fs.existsSync(githubTokenPath()) ? githubTokenPath() : "run: aerial login" },
11
- { name: "node", ok: Number(process.versions.node.split(".")[0]) >= 22, detail: process.version },
12
- { name: "bind", ok: config.host === "127.0.0.1", detail: `${config.host}:${config.port}` }
13
- ];
14
- return { ok: checks.every((check) => check.ok), checks };
15
- }
10
+ { name: "github_token", ok: fs.existsSync(githubTokenPath()) || Boolean(process.env.AERIAL_GITHUB_TOKEN), detail: fs.existsSync(githubTokenPath()) ? githubTokenPath() : "run: aerial login" },
11
+ { name: "node", ok: Number(process.versions.node.split(".")[0]) >= 22, detail: process.version },
12
+ { name: "bind", ok: config.host === "127.0.0.1", detail: `${config.host}:${config.port}` }
13
+ ];
14
+ return { ok: checks.every((check) => check.ok), checks };
15
+ }
package/src/log.js CHANGED
@@ -1,9 +1,9 @@
1
- export function logEvent(event, fields = {}) {
2
- const safeFields = { ...fields };
3
- delete safeFields.authorization;
4
- delete safeFields.token;
5
- delete safeFields.apiKey;
6
- delete safeFields.body;
7
- const line = { ts: new Date().toISOString(), event, ...safeFields };
8
- console.error(JSON.stringify(line));
9
- }
1
+ export function logEvent(event, fields = {}) {
2
+ const safeFields = { ...fields };
3
+ delete safeFields.authorization;
4
+ delete safeFields.token;
5
+ delete safeFields.apiKey;
6
+ delete safeFields.body;
7
+ const line = { ts: new Date().toISOString(), event, ...safeFields };
8
+ console.error(JSON.stringify(line));
9
+ }
package/src/paths.js CHANGED
@@ -1,22 +1,22 @@
1
- import os from "node:os";
2
- import path from "node:path";
3
- import fs from "node:fs";
4
-
5
- export function configDir() {
6
- if (process.env.AERIAL_CONFIG_DIR) return process.env.AERIAL_CONFIG_DIR;
7
- if (process.platform === "darwin") {
8
- return path.join(os.homedir(), "Library", "Application Support", "aerial");
9
- }
10
- if (process.platform === "win32") {
11
- return path.join(process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming"), "aerial");
12
- }
13
- return path.join(process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config"), "aerial");
14
- }
15
-
16
- export function configPath() {
17
- return path.join(configDir(), "config.json");
18
- }
19
-
1
+ import os from "node:os";
2
+ import path from "node:path";
3
+ import fs from "node:fs";
4
+
5
+ export function configDir() {
6
+ if (process.env.AERIAL_CONFIG_DIR) return process.env.AERIAL_CONFIG_DIR;
7
+ if (process.platform === "darwin") {
8
+ return path.join(os.homedir(), "Library", "Application Support", "aerial");
9
+ }
10
+ if (process.platform === "win32") {
11
+ return path.join(process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming"), "aerial");
12
+ }
13
+ return path.join(process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config"), "aerial");
14
+ }
15
+
16
+ export function configPath() {
17
+ return path.join(configDir(), "config.json");
18
+ }
19
+
20
20
  export function githubTokenPath() {
21
21
  return path.join(configDir(), "github_token");
22
22
  }
@@ -24,22 +24,22 @@ export function githubTokenPath() {
24
24
  export function apiKeyPath() {
25
25
  return path.join(configDir(), "api_key");
26
26
  }
27
-
28
- export function ensureDir(dir = configDir()) {
29
- fs.mkdirSync(dir, { recursive: true });
30
- }
31
-
32
- export function writePrivateFile(file, content) {
33
- ensureDir(path.dirname(file));
34
- fs.writeFileSync(file, content, { mode: 0o600 });
35
- if (process.platform !== "win32") fs.chmodSync(file, 0o600);
36
- }
37
-
38
- export function readJsonIfExists(file) {
39
- if (!fs.existsSync(file)) return undefined;
40
- return JSON.parse(fs.readFileSync(file, "utf8"));
41
- }
42
-
43
- export function writeJsonPrivate(file, value) {
44
- writePrivateFile(file, `${JSON.stringify(value, null, 2)}\n`);
45
- }
27
+
28
+ export function ensureDir(dir = configDir()) {
29
+ fs.mkdirSync(dir, { recursive: true });
30
+ }
31
+
32
+ export function writePrivateFile(file, content) {
33
+ ensureDir(path.dirname(file));
34
+ fs.writeFileSync(file, content, { mode: 0o600 });
35
+ if (process.platform !== "win32") fs.chmodSync(file, 0o600);
36
+ }
37
+
38
+ export function readJsonIfExists(file) {
39
+ if (!fs.existsSync(file)) return undefined;
40
+ return JSON.parse(fs.readFileSync(file, "utf8"));
41
+ }
42
+
43
+ export function writeJsonPrivate(file, value) {
44
+ writePrivateFile(file, `${JSON.stringify(value, null, 2)}\n`);
45
+ }