ai-zero-token 1.0.2 → 1.0.4
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/CHANGELOG.md +20 -0
- package/README.md +9 -0
- package/dist/cli/commands/help.js +2 -1
- package/dist/cli/commands/models.js +14 -3
- package/dist/cli/index.js +1 -1
- package/dist/core/context.js +3 -0
- package/dist/core/models/openai-codex-models.js +89 -0
- package/dist/core/providers/http-client.js +41 -6
- package/dist/core/services/auth-service.js +36 -0
- package/dist/core/services/config-service.js +4 -4
- package/dist/core/services/image-service.js +119 -74
- package/dist/core/services/model-service.js +31 -6
- package/dist/core/services/version-service.js +97 -0
- package/dist/core/store/profile-store.js +11 -15
- package/dist/core/store/settings-store.js +10 -11
- package/dist/core/store/state-paths.js +54 -0
- package/dist/server/admin-page.js +641 -91
- package/dist/server/app.js +40 -3
- package/docs/API_USAGE.md +120 -0
- package/package.json +3 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
getCodexModelCatalog,
|
|
4
|
+
hasCodexModel
|
|
5
5
|
} from "../models/openai-codex-models.js";
|
|
6
6
|
class ModelService {
|
|
7
7
|
constructor(configService) {
|
|
@@ -11,12 +11,37 @@ class ModelService {
|
|
|
11
11
|
if (provider !== "openai-codex") {
|
|
12
12
|
throw new Error(`\u6682\u4E0D\u652F\u6301 provider: ${provider}`);
|
|
13
13
|
}
|
|
14
|
-
const defaultModel = await
|
|
15
|
-
|
|
14
|
+
const [{ models }, defaultModel] = await Promise.all([
|
|
15
|
+
getCodexModelCatalog(),
|
|
16
|
+
this.configService.getDefaultModel(provider)
|
|
17
|
+
]);
|
|
18
|
+
return models.map((model) => ({
|
|
16
19
|
...model,
|
|
17
20
|
isDefault: model.id === defaultModel
|
|
18
21
|
}));
|
|
19
22
|
}
|
|
23
|
+
async getCatalog(provider = "openai-codex") {
|
|
24
|
+
if (provider !== "openai-codex") {
|
|
25
|
+
throw new Error(`\u6682\u4E0D\u652F\u6301 provider: ${provider}`);
|
|
26
|
+
}
|
|
27
|
+
return (await getCodexModelCatalog()).catalog;
|
|
28
|
+
}
|
|
29
|
+
async refreshModels(provider = "openai-codex") {
|
|
30
|
+
if (provider !== "openai-codex") {
|
|
31
|
+
throw new Error(`\u6682\u4E0D\u652F\u6301 provider: ${provider}`);
|
|
32
|
+
}
|
|
33
|
+
const [{ models, catalog }, defaultModel] = await Promise.all([
|
|
34
|
+
getCodexModelCatalog(),
|
|
35
|
+
this.configService.getDefaultModel(provider)
|
|
36
|
+
]);
|
|
37
|
+
return {
|
|
38
|
+
models: models.map((model) => ({
|
|
39
|
+
...model,
|
|
40
|
+
isDefault: model.id === defaultModel
|
|
41
|
+
})),
|
|
42
|
+
catalog
|
|
43
|
+
};
|
|
44
|
+
}
|
|
20
45
|
async getDefaultModel(provider = "openai-codex") {
|
|
21
46
|
if (provider !== "openai-codex") {
|
|
22
47
|
throw new Error(`\u6682\u4E0D\u652F\u6301 provider: ${provider}`);
|
|
@@ -33,8 +58,8 @@ class ModelService {
|
|
|
33
58
|
if (options?.allowUnknown) {
|
|
34
59
|
return requested;
|
|
35
60
|
}
|
|
36
|
-
if (!
|
|
37
|
-
throw new Error(`\u5F53\u524D
|
|
61
|
+
if (!await hasCodexModel(requested)) {
|
|
62
|
+
throw new Error(`\u5F53\u524D\u7F51\u5173\u672A\u627E\u5230\u53EF\u7528\u6A21\u578B: ${requested}`);
|
|
38
63
|
}
|
|
39
64
|
return requested;
|
|
40
65
|
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { requestText } from "../providers/http-client.js";
|
|
6
|
+
const VERSION_CACHE_TTL_MS = 10 * 60 * 1e3;
|
|
7
|
+
const packageJsonPath = path.dirname(fileURLToPath(new URL("../../../package.json", import.meta.url)));
|
|
8
|
+
function compareVersionPart(left, right) {
|
|
9
|
+
const leftNumber = Number.parseInt(left, 10);
|
|
10
|
+
const rightNumber = Number.parseInt(right, 10);
|
|
11
|
+
if (Number.isFinite(leftNumber) && Number.isFinite(rightNumber)) {
|
|
12
|
+
return leftNumber - rightNumber;
|
|
13
|
+
}
|
|
14
|
+
return left.localeCompare(right);
|
|
15
|
+
}
|
|
16
|
+
function compareSemver(left, right) {
|
|
17
|
+
const leftParts = left.split(/[.+-]/);
|
|
18
|
+
const rightParts = right.split(/[.+-]/);
|
|
19
|
+
const maxLength = Math.max(leftParts.length, rightParts.length);
|
|
20
|
+
for (let index = 0; index < maxLength; index += 1) {
|
|
21
|
+
const diff = compareVersionPart(leftParts[index] ?? "0", rightParts[index] ?? "0");
|
|
22
|
+
if (diff !== 0) {
|
|
23
|
+
return diff;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return 0;
|
|
27
|
+
}
|
|
28
|
+
async function readPackageManifest() {
|
|
29
|
+
const raw = await fs.readFile(path.join(packageJsonPath, "package.json"), "utf8");
|
|
30
|
+
const parsed = JSON.parse(raw);
|
|
31
|
+
return {
|
|
32
|
+
name: parsed.name ?? "ai-zero-token",
|
|
33
|
+
version: parsed.version ?? "0.0.0"
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
class VersionService {
|
|
37
|
+
cache = null;
|
|
38
|
+
inFlight = null;
|
|
39
|
+
async getVersionStatus(options) {
|
|
40
|
+
const now = Date.now();
|
|
41
|
+
if (!options?.force && this.cache && now - this.cache.checkedAt < VERSION_CACHE_TTL_MS) {
|
|
42
|
+
return this.cache;
|
|
43
|
+
}
|
|
44
|
+
if (this.inFlight) {
|
|
45
|
+
return this.inFlight;
|
|
46
|
+
}
|
|
47
|
+
this.inFlight = this.fetchVersionStatus().then((status) => {
|
|
48
|
+
this.cache = status;
|
|
49
|
+
return status;
|
|
50
|
+
}).finally(() => {
|
|
51
|
+
this.inFlight = null;
|
|
52
|
+
});
|
|
53
|
+
return this.inFlight;
|
|
54
|
+
}
|
|
55
|
+
async fetchVersionStatus() {
|
|
56
|
+
const manifest = await readPackageManifest();
|
|
57
|
+
const registryUrl = `https://registry.npmjs.org/${encodeURIComponent(manifest.name)}/latest`;
|
|
58
|
+
try {
|
|
59
|
+
const response = await requestText({
|
|
60
|
+
method: "GET",
|
|
61
|
+
url: registryUrl,
|
|
62
|
+
timeoutMs: 5e3
|
|
63
|
+
});
|
|
64
|
+
if (response.status < 200 || response.status >= 300) {
|
|
65
|
+
throw new Error(`npm registry returned ${response.status}`);
|
|
66
|
+
}
|
|
67
|
+
const parsed = JSON.parse(response.body);
|
|
68
|
+
const latestVersion = typeof parsed.version === "string" && parsed.version ? parsed.version : void 0;
|
|
69
|
+
if (!latestVersion) {
|
|
70
|
+
throw new Error("npm registry did not return a version");
|
|
71
|
+
}
|
|
72
|
+
const needsUpdate = compareSemver(manifest.version, latestVersion) < 0;
|
|
73
|
+
return {
|
|
74
|
+
packageName: manifest.name,
|
|
75
|
+
currentVersion: manifest.version,
|
|
76
|
+
latestVersion,
|
|
77
|
+
checkedAt: Date.now(),
|
|
78
|
+
needsUpdate,
|
|
79
|
+
registryUrl,
|
|
80
|
+
status: needsUpdate ? "update-available" : "ok"
|
|
81
|
+
};
|
|
82
|
+
} catch (error) {
|
|
83
|
+
return {
|
|
84
|
+
packageName: manifest.name,
|
|
85
|
+
currentVersion: manifest.version,
|
|
86
|
+
checkedAt: Date.now(),
|
|
87
|
+
needsUpdate: false,
|
|
88
|
+
registryUrl,
|
|
89
|
+
status: "error",
|
|
90
|
+
error: error instanceof Error ? error.message : String(error)
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
export {
|
|
96
|
+
VersionService
|
|
97
|
+
};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
import {
|
|
4
|
+
ensureStateMigrated,
|
|
5
|
+
getStateDir,
|
|
6
|
+
getStorePath
|
|
7
|
+
} from "./state-paths.js";
|
|
8
8
|
const PROFILE_CLAIM_PATH = "https://api.openai.com/profile";
|
|
9
9
|
function createEmptyStore() {
|
|
10
10
|
return {
|
|
@@ -40,15 +40,10 @@ function extractEmailFromAccessToken(token) {
|
|
|
40
40
|
}
|
|
41
41
|
return void 0;
|
|
42
42
|
}
|
|
43
|
-
function getStateDir() {
|
|
44
|
-
return stateDir;
|
|
45
|
-
}
|
|
46
|
-
function getStorePath() {
|
|
47
|
-
return storePath;
|
|
48
|
-
}
|
|
49
43
|
async function loadStore() {
|
|
50
44
|
try {
|
|
51
|
-
|
|
45
|
+
await ensureStateMigrated();
|
|
46
|
+
const raw = await fs.readFile(getStorePath(), "utf8");
|
|
52
47
|
const parsed = JSON.parse(raw);
|
|
53
48
|
const normalizedProfiles = Object.fromEntries(
|
|
54
49
|
Object.entries(parsed.profiles ?? {}).map(([profileId, profile]) => [
|
|
@@ -73,8 +68,9 @@ async function loadStore() {
|
|
|
73
68
|
}
|
|
74
69
|
}
|
|
75
70
|
async function saveStore(store) {
|
|
76
|
-
await
|
|
77
|
-
await fs.
|
|
71
|
+
await ensureStateMigrated();
|
|
72
|
+
await fs.mkdir(getStateDir(), { recursive: true });
|
|
73
|
+
await fs.writeFile(getStorePath(), `${JSON.stringify(store, null, 2)}
|
|
78
74
|
`, "utf8");
|
|
79
75
|
}
|
|
80
76
|
async function saveProfile(profile) {
|
|
@@ -130,7 +126,7 @@ async function removeProfile(profileId) {
|
|
|
130
126
|
return store.activeProfileId ? store.profiles[store.activeProfileId] ?? null : null;
|
|
131
127
|
}
|
|
132
128
|
async function clearStore() {
|
|
133
|
-
await fs.rm(
|
|
129
|
+
await fs.rm(getStateDir(), { recursive: true, force: true });
|
|
134
130
|
}
|
|
135
131
|
export {
|
|
136
132
|
clearStore,
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
import {
|
|
4
|
+
ensureStateMigrated,
|
|
5
|
+
getSettingsPath,
|
|
6
|
+
getStateDir
|
|
7
|
+
} from "./state-paths.js";
|
|
8
8
|
function createDefaultSettings() {
|
|
9
9
|
return {
|
|
10
10
|
version: 1,
|
|
@@ -16,12 +16,10 @@ function createDefaultSettings() {
|
|
|
16
16
|
}
|
|
17
17
|
};
|
|
18
18
|
}
|
|
19
|
-
function getSettingsPath() {
|
|
20
|
-
return settingsPath;
|
|
21
|
-
}
|
|
22
19
|
async function loadSettings() {
|
|
23
20
|
try {
|
|
24
|
-
|
|
21
|
+
await ensureStateMigrated();
|
|
22
|
+
const raw = await fs.readFile(getSettingsPath(), "utf8");
|
|
25
23
|
const parsed = JSON.parse(raw);
|
|
26
24
|
const defaults = createDefaultSettings();
|
|
27
25
|
return {
|
|
@@ -38,8 +36,9 @@ async function loadSettings() {
|
|
|
38
36
|
}
|
|
39
37
|
}
|
|
40
38
|
async function saveSettings(settings) {
|
|
41
|
-
await
|
|
42
|
-
await fs.
|
|
39
|
+
await ensureStateMigrated();
|
|
40
|
+
await fs.mkdir(getStateDir(), { recursive: true });
|
|
41
|
+
await fs.writeFile(getSettingsPath(), `${JSON.stringify(settings, null, 2)}
|
|
43
42
|
`, "utf8");
|
|
44
43
|
}
|
|
45
44
|
export {
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
const packageDir = path.dirname(fileURLToPath(new URL("../../../package.json", import.meta.url)));
|
|
7
|
+
const legacyStateDir = path.join(packageDir, ".state");
|
|
8
|
+
const appHomeDir = process.env.AI_ZERO_TOKEN_HOME || path.join(os.homedir(), ".ai-zero-token");
|
|
9
|
+
const stateDir = path.join(appHomeDir, ".state");
|
|
10
|
+
let migrationPromise = null;
|
|
11
|
+
async function pathExists(targetPath) {
|
|
12
|
+
try {
|
|
13
|
+
await fs.access(targetPath);
|
|
14
|
+
return true;
|
|
15
|
+
} catch {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
async function copyIfMissing(fileName) {
|
|
20
|
+
const legacyPath = path.join(legacyStateDir, fileName);
|
|
21
|
+
const nextPath = path.join(stateDir, fileName);
|
|
22
|
+
if (!await pathExists(legacyPath) || await pathExists(nextPath)) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
await fs.mkdir(stateDir, { recursive: true });
|
|
26
|
+
await fs.copyFile(legacyPath, nextPath);
|
|
27
|
+
}
|
|
28
|
+
function getStateDir() {
|
|
29
|
+
return stateDir;
|
|
30
|
+
}
|
|
31
|
+
function getStorePath() {
|
|
32
|
+
return path.join(stateDir, "store.json");
|
|
33
|
+
}
|
|
34
|
+
function getSettingsPath() {
|
|
35
|
+
return path.join(stateDir, "settings.json");
|
|
36
|
+
}
|
|
37
|
+
async function ensureStateMigrated() {
|
|
38
|
+
if (!migrationPromise) {
|
|
39
|
+
migrationPromise = (async () => {
|
|
40
|
+
if (legacyStateDir === stateDir) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
await copyIfMissing("store.json");
|
|
44
|
+
await copyIfMissing("settings.json");
|
|
45
|
+
})();
|
|
46
|
+
}
|
|
47
|
+
await migrationPromise;
|
|
48
|
+
}
|
|
49
|
+
export {
|
|
50
|
+
ensureStateMigrated,
|
|
51
|
+
getSettingsPath,
|
|
52
|
+
getStateDir,
|
|
53
|
+
getStorePath
|
|
54
|
+
};
|