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.
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- CODEX_MODEL_INFOS,
4
- isSupportedCodexModel
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 this.configService.getDefaultModel(provider);
15
- return CODEX_MODEL_INFOS.map((model) => ({
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 (!isSupportedCodexModel(requested)) {
37
- throw new Error(`\u5F53\u524D demo \u672A\u5185\u7F6E\u6A21\u578B: ${requested}`);
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 path from "node:path";
4
- import { fileURLToPath } from "node:url";
5
- const projectDir = path.dirname(fileURLToPath(new URL("../../../package.json", import.meta.url)));
6
- const stateDir = path.join(projectDir, ".state");
7
- const storePath = path.join(stateDir, "store.json");
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
- const raw = await fs.readFile(storePath, "utf8");
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 fs.mkdir(stateDir, { recursive: true });
77
- await fs.writeFile(storePath, `${JSON.stringify(store, null, 2)}
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(stateDir, { recursive: true, force: true });
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 path from "node:path";
4
- import { fileURLToPath } from "node:url";
5
- const projectDir = path.dirname(fileURLToPath(new URL("../../../package.json", import.meta.url)));
6
- const stateDir = path.join(projectDir, ".state");
7
- const settingsPath = path.join(stateDir, "settings.json");
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
- const raw = await fs.readFile(settingsPath, "utf8");
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 fs.mkdir(stateDir, { recursive: true });
42
- await fs.writeFile(settingsPath, `${JSON.stringify(settings, null, 2)}
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
+ };