@jiayunxie/aerial 0.2.5 → 0.2.6

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.
@@ -1,23 +0,0 @@
1
- export function computeAppStatus(setup, service) {
2
- const githubTokenPresent = setup.auth.github_token.source !== "missing";
3
- const apiKeyPresent = setup.auth.api_key.exists;
4
- const hasAerialClient = setup.clients.codex.state === "aerial" || setup.clients.claude.state === "aerial";
5
- const serviceHealthy = service.supported !== false && service.health?.aerial === true;
6
- const ok = apiKeyPresent && githubTokenPresent && hasAerialClient && serviceHealthy;
7
- const nextSteps = [];
8
- const hints = [];
9
- if (!githubTokenPresent) nextSteps.push("run: aerial login");
10
- if (!hasAerialClient) nextSteps.push("run: aerial setup codex or aerial setup claude");
11
- if (hasAerialClient && !apiKeyPresent) nextSteps.push("run: aerial setup codex or aerial setup claude to recreate the local Aerial key");
12
- if (service.supported !== false && !service.service?.loaded) {
13
- if (serviceHealthy) {
14
- hints.push("Aerial is running in the foreground but no background service is installed; run `aerial service install` so it starts on reboot.");
15
- } else {
16
- nextSteps.push("run: aerial service install");
17
- }
18
- }
19
- if (setup.auth.github_token.source === "env") {
20
- hints.push("AERIAL_GITHUB_TOKEN is set for this process only; run aerial login without that env var to persist a service-readable login.");
21
- }
22
- return { schema: "aerial.status.v1", ok, setup, service, nextSteps, hints };
23
- }
package/src/cli/args.js DELETED
@@ -1,28 +0,0 @@
1
- export function argValue(args, name) {
2
- const index = args.indexOf(name);
3
- return index >= 0 ? args[index + 1] : undefined;
4
- }
5
-
6
- export function requiredArgValue(args, name) {
7
- const index = args.indexOf(name);
8
- if (index < 0) return undefined;
9
- const value = args[index + 1];
10
- if (value === undefined || value.startsWith("--")) {
11
- throw new Error(`${name} requires a value.`);
12
- }
13
- return value;
14
- }
15
-
16
- export function parseConfigPort(value) {
17
- const text = String(value).trim();
18
- if (!/^\d+$/.test(text)) throw new Error("port must be an integer between 1 and 65535");
19
- const port = Number(text);
20
- if (port < 1 || port > 65535) throw new Error("port must be an integer between 1 and 65535");
21
- return port;
22
- }
23
-
24
- export function parseConfigHost(value) {
25
- const host = String(value).trim().toLowerCase();
26
- if (host === "127.0.0.1" || host === "localhost" || host === "::1") return host;
27
- throw new Error("host must be a loopback address: 127.0.0.1, localhost, or ::1");
28
- }
package/src/cli/help.js DELETED
@@ -1,23 +0,0 @@
1
- export function printHelp() {
2
- console.log(`Aerial local Copilot proxy
3
-
4
- Usage:
5
- aerial --version
6
- aerial login
7
- aerial setup codex [--model <id>] [--effort <low|medium|high|xhigh|max>]
8
- aerial setup claude [--model <id>] [--effort <low|medium|high|xhigh|max>]
9
- aerial service install
10
- aerial status [--json]
11
- aerial proxy status|enable|disable [--json]
12
-
13
- Diagnostics and rollback:
14
- aerial setup status [--json]
15
- aerial setup restore <codex|claude|all> --latest
16
- aerial service status [--json]
17
- aerial disable
18
- aerial doctor
19
- aerial probe [--live] [--json]
20
-
21
- Debug:
22
- aerial start [--host 127.0.0.1] [--port 18181]`);
23
- }
@@ -1,119 +0,0 @@
1
- import { createInterface } from "node:readline/promises";
2
- import { stdin as input, stdout as output } from "node:process";
3
- import { proxyModels } from "../proxy/index.js";
4
- import { readJsonSafely } from "../shared/http-utils.js";
5
- import { modelsForRoute } from "../proxy/model-utils.js";
6
- import { parseNumberChoice } from "../shared/prompt-utils.js";
7
-
8
- const MAX_LISTED_MODELS = 20;
9
- const GPT_VERSION_RE = /^gpt-(\d+)(?:\.(\d+))?/i;
10
- const STABLE_GPT_RE = /^gpt-\d+(?:\.\d+)?$/i;
11
-
12
- export { modelsForRoute } from "../proxy/model-utils.js";
13
-
14
- function gptVersionScore(id) {
15
- const match = GPT_VERSION_RE.exec(id);
16
- if (!match) return undefined;
17
- const major = Number(match[1]);
18
- const minor = match[2] === undefined ? 0 : Number(match[2]);
19
- return major * 1000 + minor;
20
- }
21
-
22
- export function rankModels(choices) {
23
- return [...choices]
24
- .map((choice, index) => ({ choice, index, score: gptVersionScore(choice.id) }))
25
- .sort((a, b) => {
26
- if (a.score !== undefined && b.score !== undefined && a.score !== b.score) return b.score - a.score;
27
- if (a.score !== undefined && b.score === undefined) return -1;
28
- if (a.score === undefined && b.score !== undefined) return 1;
29
- return a.index - b.index;
30
- })
31
- .map((entry) => entry.choice);
32
- }
33
-
34
- export function pickRecommended(choices) {
35
- if (!choices.length) return { recommended: undefined, source: "first_available" };
36
- const ranked = rankModels(choices);
37
- const stable = ranked.filter((c) => STABLE_GPT_RE.test(c.id));
38
- if (stable.length) return { recommended: stable[0].id, source: "recommended_stable", ranked };
39
- return { recommended: ranked[0].id, source: "recommended_fallback", ranked };
40
- }
41
-
42
- export function orderForPrompt(ranked, recommended) {
43
- if (!recommended) return [...ranked];
44
- const found = ranked.find((c) => c.id === recommended);
45
- if (!found) return [...ranked];
46
- return [found, ...ranked.filter((c) => c.id !== recommended)];
47
- }
48
-
49
- export async function discoverModelsForRoute(route) {
50
- const response = await proxyModels(new Request("http://aerial.local/v1/models", { method: "GET" }));
51
- const payload = await readJsonSafely(response);
52
- if (!response.ok) {
53
- const detail = payload.error || payload.raw || JSON.stringify(payload);
54
- throw new Error(`Could not load Copilot models (${response.status}): ${detail}`);
55
- }
56
- const models = Array.isArray(payload.data) ? payload.data : [];
57
- return modelsForRoute(models, route);
58
- }
59
-
60
- export async function chooseSetupModel({ target, route, explicitModel, prompt = input.isTTY }) {
61
- if (explicitModel) return { model: explicitModel, choices: [], source: "explicit" };
62
- let raw;
63
- try {
64
- raw = await discoverModelsForRoute(route);
65
- } catch (err) {
66
- throw new Error(`${err.message}\nRun \`aerial login\` first, or pass \`--model <id>\` if you already know which ${target} model to use.`);
67
- }
68
- if (!raw.length) {
69
- throw new Error(`No Copilot models currently advertise the ${route} route needed by ${target}. Run \`aerial probe\` to inspect the full model list.`);
70
- }
71
- const { recommended, source: recommendedSource, ranked } = pickRecommended(raw);
72
- const choices = ranked;
73
- const promptListed = orderForPrompt(ranked, recommended).slice(0, MAX_LISTED_MODELS);
74
- if (!prompt) return { model: recommended, choices, source: recommendedSource, recommended };
75
-
76
- const rl = createInterface({ input, output });
77
- try {
78
- output.write(`Available ${target} models (${route} route):\n`);
79
- if (recommendedSource === "recommended_fallback") {
80
- output.write(` No stable gpt-N.M model available; recommending ${recommended} as a fallback. Pass --model <id> to override.\n`);
81
- }
82
- for (const [index, choice] of promptListed.entries()) {
83
- const marker = choice.id === recommended ? " (recommended)" : "";
84
- output.write(` ${index + 1}. ${choice.id}${marker}\n`);
85
- }
86
- if (choices.length > MAX_LISTED_MODELS) output.write(` ... ${choices.length - MAX_LISTED_MODELS} more\n`);
87
- while (true) {
88
- const answer = await rl.question(`Choose ${target} model [1-${promptListed.length}, default 1 = ${recommended}]: `);
89
- const selected = parseNumberChoice(answer, { max: promptListed.length, defaultIndex: 1, oneBased: true });
90
- if (selected) return { model: promptListed[selected - 1].id, choices, source: "prompt", displayed: true, recommended };
91
- output.write(`Enter a number from 1 to ${promptListed.length}, or press Enter for 1.\n`);
92
- }
93
- } finally {
94
- rl.close();
95
- }
96
- }
97
-
98
- export function formatModelChoices({ target, route, choices, selectedModel, source, recommended }) {
99
- if (!choices.length) return [];
100
- const lines = [
101
- `Available ${target} models (${route} route):`
102
- ];
103
- for (const [index, choice] of choices.slice(0, MAX_LISTED_MODELS).entries()) {
104
- const markers = [];
105
- if (choice.id === recommended) markers.push("recommended");
106
- if (choice.id === selectedModel) markers.push("selected");
107
- const suffix = markers.length ? ` (${markers.join(", ")})` : "";
108
- lines.push(` ${index + 1}. ${choice.id}${suffix}`);
109
- }
110
- if (choices.length > MAX_LISTED_MODELS) lines.push(` ... ${choices.length - MAX_LISTED_MODELS} more`);
111
- if (source === "first_available" || source === "recommended_stable") {
112
- lines.push(`No interactive terminal detected; selected ${selectedModel}. Pass --model <id> to choose a different model.`);
113
- } else if (source === "recommended_fallback") {
114
- lines.push(`No stable gpt-N.M model available; selected ${selectedModel}. Pass --model <id> to override.`);
115
- } else {
116
- lines.push(`Selected ${target} model: ${selectedModel}`);
117
- }
118
- return lines;
119
- }
@@ -1,21 +0,0 @@
1
- import path from "node:path";
2
- import { fileURLToPath } from "node:url";
3
-
4
- const CLI_ENTRY = path.join(path.dirname(fileURLToPath(import.meta.url)), "index.js");
5
-
6
- export function codexAuthCommand() {
7
- return {
8
- command: process.execPath,
9
- args: [CLI_ENTRY, "key", "print"],
10
- timeout_ms: 5000,
11
- refresh_interval_ms: 0
12
- };
13
- }
14
-
15
- function quoteCommandPart(value) {
16
- return `"${String(value).replace(/"/g, '\\"')}"`;
17
- }
18
-
19
- export function claudeApiKeyHelper() {
20
- return [process.execPath, CLI_ENTRY, "key", "print"].map(quoteCommandPart).join(" ");
21
- }
@@ -1,47 +0,0 @@
1
- import { createInterface } from "node:readline/promises";
2
- import { stdin as input, stdout as output } from "node:process";
3
- import { parseNumberChoice } from "../shared/prompt-utils.js";
4
- import { DEFAULT_EFFORT, EFFORT_VALUES, assertValidEffort } from "../shared/effort.js";
5
-
6
- export { DEFAULT_EFFORT, EFFORT_VALUES, normalizeEffort, assertValidEffort } from "../shared/effort.js";
7
-
8
- export async function chooseSetupEffort({
9
- target,
10
- explicitEffort,
11
- prompt,
12
- input: inputStream = input,
13
- output: outputStream = output
14
- } = {}) {
15
- if (explicitEffort !== undefined) {
16
- return { effort: assertValidEffort(explicitEffort), source: "explicit", displayed: false };
17
- }
18
- const shouldPrompt = prompt === undefined ? Boolean(inputStream.isTTY) : prompt;
19
- if (!shouldPrompt) {
20
- return { effort: DEFAULT_EFFORT, source: "default_non_tty", displayed: false };
21
- }
22
- const defaultIndex = EFFORT_VALUES.indexOf(DEFAULT_EFFORT);
23
- const rl = createInterface({ input: inputStream, output: outputStream });
24
- try {
25
- outputStream.write(`Choose ${target} reasoning effort:\n`);
26
- for (const [index, value] of EFFORT_VALUES.entries()) {
27
- const marker = index === defaultIndex ? " (default)" : "";
28
- outputStream.write(` ${index + 1}. ${value}${marker}\n`);
29
- }
30
- while (true) {
31
- const answer = await rl.question(`Choose effort [1-${EFFORT_VALUES.length}, default ${defaultIndex + 1} = ${DEFAULT_EFFORT}]: `);
32
- const selectedIndex = parseNumberChoice(answer, { max: EFFORT_VALUES.length, defaultIndex });
33
- if (selectedIndex !== undefined) {
34
- return { effort: EFFORT_VALUES[selectedIndex], source: "prompt", displayed: true };
35
- }
36
- outputStream.write(`Enter a number from 1 to ${EFFORT_VALUES.length}, or press Enter for ${defaultIndex + 1}.\n`);
37
- }
38
- } finally {
39
- rl.close();
40
- }
41
- }
42
-
43
- export function formatEffortSelection({ target, effort, source }) {
44
- if (source === "explicit") return `Selected ${target} effort: ${effort}`;
45
- if (source === "prompt") return `Selected ${target} effort: ${effort}`;
46
- return `No interactive terminal detected; selected ${target} effort: ${effort}. Pass --effort <low|medium|high|xhigh|max> to choose a different effort.`;
47
- }
@@ -1,19 +0,0 @@
1
- import fs from "node:fs";
2
-
3
- const PACKAGE_JSON_URL = new URL("../../package.json", import.meta.url);
4
-
5
- export function readPackageVersion(packageJsonUrl = PACKAGE_JSON_URL) {
6
- try {
7
- const pkg = JSON.parse(fs.readFileSync(packageJsonUrl, "utf8"));
8
- if (typeof pkg.version === "string" && pkg.version.length > 0) return pkg.version;
9
- throw new Error("missing version field");
10
- } catch (error) {
11
- const reason = error?.message || String(error);
12
- console.warn(`aerial: cannot read package version (${reason}); reporting "unknown"`);
13
- return "unknown";
14
- }
15
- }
16
-
17
- export function printVersion(packageJsonUrl = PACKAGE_JSON_URL) {
18
- console.log(readPackageVersion(packageJsonUrl));
19
- }
@@ -1,20 +0,0 @@
1
- export function aerialRoutes(model) {
2
- return Array.isArray(model?.aerial?.routes) ? model.aerial.routes : [];
3
- }
4
-
5
- export function modelsForRoute(models, route) {
6
- return models
7
- .filter((model) => typeof model?.id === "string" && aerialRoutes(model).includes(route))
8
- .map((model) => ({ id: model.id, routes: aerialRoutes(model), notes: model.aerial?.notes || [] }));
9
- }
10
-
11
- export function usageSummary(payload) {
12
- const usage = payload?.usage || payload?.response?.usage || {};
13
- return {
14
- input: usage.input_tokens ?? usage.prompt_tokens,
15
- output: usage.output_tokens ?? usage.completion_tokens,
16
- cached: usage.input_tokens_details?.cached_tokens
17
- ?? usage.prompt_tokens_details?.cached_tokens
18
- ?? usage.cache_read_input_tokens
19
- };
20
- }
@@ -1,38 +0,0 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
-
4
- const BACKUP_PREFIX = ".aerial-backup-";
5
- const ISO_STAMP_RE = /^\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}-\d{3}Z$/;
6
-
7
- export function backupIfExists(file) {
8
- if (!fs.existsSync(file)) return undefined;
9
- const backup = `${file}.aerial-backup-${new Date().toISOString().replace(/[:.]/g, "-")}`;
10
- fs.copyFileSync(file, backup);
11
- return backup;
12
- }
13
-
14
- function listBackups(file) {
15
- const dir = path.dirname(file);
16
- const base = path.basename(file);
17
- if (!fs.existsSync(dir)) return [];
18
- const prefix = `${base}${BACKUP_PREFIX}`;
19
- const entries = fs.readdirSync(dir);
20
- const matches = [];
21
- for (const entry of entries) {
22
- if (!entry.startsWith(prefix)) continue;
23
- const stamp = entry.slice(prefix.length);
24
- if (!ISO_STAMP_RE.test(stamp)) continue;
25
- matches.push({ name: entry, path: path.join(dir, entry), stamp });
26
- }
27
- matches.sort((a, b) => (a.stamp < b.stamp ? -1 : a.stamp > b.stamp ? 1 : 0));
28
- return matches;
29
- }
30
-
31
- export function findLatestBackup(file) {
32
- const all = listBackups(file);
33
- return all.length ? all[all.length - 1] : undefined;
34
- }
35
-
36
- export function backupPathsFor(file) {
37
- return listBackups(file).map((entry) => entry.path);
38
- }
@@ -1,24 +0,0 @@
1
- import fs from "node:fs";
2
- import { loadConfig } from "../shared/config.js";
3
- import { apiKeyPath, githubTokenPath } from "../shared/paths.js";
4
- import { gitHubTokenSource } from "../shared/auth.js";
5
- import { CLIENTS } from "./clients.js";
6
-
7
- export function setupStatus() {
8
- const config = loadConfig();
9
- const apiKeyFile = apiKeyPath();
10
- const githubTokenFile = githubTokenPath();
11
- return {
12
- schema: "aerial.setup-status.v1",
13
- platform: process.platform,
14
- config: { host: config.host, port: config.port },
15
- auth: {
16
- api_key: { file: apiKeyFile, exists: fs.existsSync(apiKeyFile) },
17
- github_token: (() => {
18
- const source = gitHubTokenSource();
19
- return { file: githubTokenFile, exists: source !== "missing", source };
20
- })()
21
- },
22
- clients: Object.fromEntries(Object.entries(CLIENTS).map(([target, client]) => [target, client.status()]))
23
- };
24
- }
@@ -1,9 +0,0 @@
1
- export async function readJsonSafely(response) {
2
- const text = await response.text();
3
- if (!text) return {};
4
- try {
5
- return JSON.parse(text);
6
- } catch {
7
- return { raw: text };
8
- }
9
- }
@@ -1,8 +0,0 @@
1
- export function parseNumberChoice(value, { max, defaultIndex = 0, oneBased = false } = {}) {
2
- const trimmed = String(value || "").trim();
3
- if (!trimmed) return defaultIndex;
4
- if (!/^\d+$/.test(trimmed)) return undefined;
5
- const n = Number(trimmed);
6
- if (n < 1 || n > max) return undefined;
7
- return oneBased ? n : n - 1;
8
- }