ai-zero-token 2.0.1 → 2.0.3
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 +13 -5
- package/README.zh-CN.md +13 -5
- package/admin-ui/dist/assets/InfoRow-0ULI9iI3.js +1 -0
- package/admin-ui/dist/assets/accounts-DymL4WIa.js +2 -0
- package/admin-ui/dist/assets/activity-D21-Xrc4.js +1 -0
- package/admin-ui/dist/assets/app-mark-nsRs4vo7.svg +8 -0
- package/admin-ui/dist/assets/circle-check-ZYtn9GqY.js +1 -0
- package/admin-ui/dist/assets/clock-3-BzDANsVk.js +1 -0
- package/admin-ui/dist/assets/docs-BRNWAMPw.css +1 -0
- package/admin-ui/dist/assets/docs-DtO-AOWU.js +1735 -0
- package/admin-ui/dist/assets/earth-DFdZaQIi.js +1 -0
- package/admin-ui/dist/assets/image-bed-yIVQ4dKs.js +1 -0
- package/admin-ui/dist/assets/index-By4r-wy3.css +1 -0
- package/admin-ui/dist/assets/index-DRe-tByu.js +10 -0
- package/admin-ui/dist/assets/jsx-runtime-DqpGtLhh.js +1 -0
- package/admin-ui/dist/assets/launch-CQXYrl-h.js +1 -0
- package/admin-ui/dist/assets/logs-awABDg1C.js +1 -0
- package/admin-ui/dist/assets/network-detect-sSrnwZqf.js +1 -0
- package/admin-ui/dist/assets/overview-BbSON0Jl.js +1 -0
- package/admin-ui/dist/assets/profiles-DMOjJORP.js +1 -0
- package/admin-ui/dist/assets/refresh-cw-CAAH2rqe.js +1 -0
- package/admin-ui/dist/assets/search-B2hz41D3.js +1 -0
- package/admin-ui/dist/assets/server-BrjJPb9D.js +1 -0
- package/admin-ui/dist/assets/settings-DvRiHS7i.js +1 -0
- package/admin-ui/dist/assets/tester-CftPgRE9.js +3 -0
- package/admin-ui/dist/assets/upload-CwXb7Q1b.js +1 -0
- package/admin-ui/dist/assets/zap-B4_oDbCp.js +1 -0
- package/admin-ui/dist/index.html +5 -3
- package/build/icon.icns +0 -0
- package/build/icon.ico +0 -0
- package/build/icon.png +0 -0
- package/build/icon.svg +8 -0
- package/build/mac-install-guide.txt +25 -0
- package/dist/core/context.js +3 -0
- package/dist/core/providers/http-client.js +11 -4
- package/dist/core/services/auth-service.js +88 -12
- package/dist/core/services/config-service.js +87 -23
- package/dist/core/services/github-image-bed-service.js +264 -0
- package/dist/core/services/network-detect-service.js +189 -74
- package/dist/core/services/version-service.js +1 -1
- package/dist/core/store/github-image-bed-history-store.js +68 -0
- package/dist/core/store/github-image-bed-store.js +52 -0
- package/dist/core/store/profile-store.js +73 -32
- package/dist/core/store/settings-store.js +14 -0
- package/dist/desktop/main.js +158 -6
- package/dist/server/app.js +168 -26
- package/dist/server/index.js +41 -15
- package/docs/DESKTOP_RELEASE.md +35 -3
- package/docs/PRODUCT_UPDATE_DESKTOP_TOOLBOX.md +2 -2
- package/package.json +34 -2
- package/admin-ui/dist/assets/app-mark-Gd2QnHMO.svg +0 -9
- package/admin-ui/dist/assets/index-DNzR8XR7.css +0 -1
- package/admin-ui/dist/assets/index-DZMegNPs.js +0 -1745
- package/dist/server/admin-page.js +0 -4586
package/dist/server/app.js
CHANGED
|
@@ -8,7 +8,6 @@ import cors from "@fastify/cors";
|
|
|
8
8
|
import { z } from "zod";
|
|
9
9
|
import { createGatewayContext } from "../core/context.js";
|
|
10
10
|
import { requestText } from "../core/providers/http-client.js";
|
|
11
|
-
import { renderAdminPage } from "./admin-page.js";
|
|
12
11
|
const packageRoot = path.dirname(fileURLToPath(new URL("../../package.json", import.meta.url)));
|
|
13
12
|
const adminUiDistDir = path.join(packageRoot, "admin-ui", "dist");
|
|
14
13
|
const adminUiIndexPath = path.join(adminUiDistDir, "index.html");
|
|
@@ -26,14 +25,6 @@ const assetContentTypes = {
|
|
|
26
25
|
".svg": "image/svg+xml",
|
|
27
26
|
".webp": "image/webp"
|
|
28
27
|
};
|
|
29
|
-
async function pathExists(targetPath) {
|
|
30
|
-
try {
|
|
31
|
-
await fs.access(targetPath);
|
|
32
|
-
return true;
|
|
33
|
-
} catch {
|
|
34
|
-
return false;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
28
|
function getContentType(filePath) {
|
|
38
29
|
return assetContentTypes[path.extname(filePath).toLowerCase()] ?? "application/octet-stream";
|
|
39
30
|
}
|
|
@@ -123,6 +114,12 @@ const settingsUpdateSchema = z.object({
|
|
|
123
114
|
}).optional(),
|
|
124
115
|
autoSwitch: z.object({
|
|
125
116
|
enabled: z.boolean()
|
|
117
|
+
}).optional(),
|
|
118
|
+
runtime: z.object({
|
|
119
|
+
quotaSyncConcurrency: z.number().int().min(1).max(32).optional()
|
|
120
|
+
}).optional(),
|
|
121
|
+
server: z.object({
|
|
122
|
+
port: z.number().int().min(1).max(65535)
|
|
126
123
|
}).optional()
|
|
127
124
|
});
|
|
128
125
|
const proxyTestSchema = z.object({
|
|
@@ -135,9 +132,15 @@ const proxyTestSchema = z.object({
|
|
|
135
132
|
const profileActionSchema = z.object({
|
|
136
133
|
profileId: z.string().min(1)
|
|
137
134
|
});
|
|
135
|
+
const profileRemoveBatchSchema = z.object({
|
|
136
|
+
profileIds: z.array(z.string().min(1)).min(1)
|
|
137
|
+
});
|
|
138
138
|
const profileImportSchema = z.object({
|
|
139
139
|
profile: z.unknown()
|
|
140
140
|
});
|
|
141
|
+
const runtimeRefreshSchema = z.object({
|
|
142
|
+
staleOnly: z.boolean().optional()
|
|
143
|
+
});
|
|
141
144
|
const profileExportSchema = z.object({
|
|
142
145
|
profileId: z.string().min(1).optional(),
|
|
143
146
|
profileIds: z.array(z.string().min(1)).optional(),
|
|
@@ -146,6 +149,19 @@ const profileExportSchema = z.object({
|
|
|
146
149
|
const codexApplySchema = z.object({
|
|
147
150
|
profileId: z.string().min(1)
|
|
148
151
|
});
|
|
152
|
+
const githubImageBedConfigSchema = z.object({
|
|
153
|
+
token: z.string().min(1)
|
|
154
|
+
});
|
|
155
|
+
const githubImageBedUploadSchema = z.object({
|
|
156
|
+
filename: z.string().min(1),
|
|
157
|
+
dataUrl: z.string().min(1)
|
|
158
|
+
});
|
|
159
|
+
const githubImageBedHistoryQuerySchema = z.object({
|
|
160
|
+
limit: z.coerce.number().int().min(1).max(100).optional()
|
|
161
|
+
});
|
|
162
|
+
const githubImageBedHistoryParamsSchema = z.object({
|
|
163
|
+
id: z.string().min(1)
|
|
164
|
+
});
|
|
149
165
|
const imageGenerationsBodySchema = z.object({
|
|
150
166
|
prompt: z.string().min(1),
|
|
151
167
|
model: z.string().optional(),
|
|
@@ -431,6 +447,7 @@ function serializeProfile(profile) {
|
|
|
431
447
|
email: profile.email,
|
|
432
448
|
quota: profile.quota,
|
|
433
449
|
authStatus: profile.authStatus,
|
|
450
|
+
exportAudit: profile.exportAudit,
|
|
434
451
|
expiresAt: profile.expires,
|
|
435
452
|
accessTokenPreview: maskSecret(profile.access),
|
|
436
453
|
refreshTokenPreview: maskSecret(profile.refresh)
|
|
@@ -444,6 +461,7 @@ function serializeManagedProfile(profile) {
|
|
|
444
461
|
email: profile.email,
|
|
445
462
|
quota: profile.quota,
|
|
446
463
|
authStatus: profile.authStatus,
|
|
464
|
+
exportAudit: profile.exportAudit,
|
|
447
465
|
expiresAt: profile.expiresAt,
|
|
448
466
|
accessTokenPreview: profile.accessTokenPreview,
|
|
449
467
|
refreshTokenPreview: profile.refreshTokenPreview,
|
|
@@ -486,7 +504,7 @@ function createApp(params) {
|
|
|
486
504
|
const ctx = createGatewayContext();
|
|
487
505
|
void app.register(cors, {
|
|
488
506
|
origin: params?.corsOrigin ?? true,
|
|
489
|
-
methods: ["GET", "POST", "PUT", "OPTIONS"]
|
|
507
|
+
methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"]
|
|
490
508
|
});
|
|
491
509
|
app.setErrorHandler((error, request, reply) => {
|
|
492
510
|
const normalized = normalizeError(error);
|
|
@@ -529,6 +547,7 @@ function createApp(params) {
|
|
|
529
547
|
codex: codexStatus,
|
|
530
548
|
adminUrl: `${origin}/`,
|
|
531
549
|
baseUrl: `${origin}/v1`,
|
|
550
|
+
restartSupported: Boolean(params?.onRestart),
|
|
532
551
|
supportedEndpoints: [
|
|
533
552
|
{
|
|
534
553
|
method: "GET",
|
|
@@ -559,12 +578,18 @@ function createApp(params) {
|
|
|
559
578
|
};
|
|
560
579
|
}
|
|
561
580
|
app.get("/", async (_request, reply) => {
|
|
562
|
-
|
|
581
|
+
try {
|
|
563
582
|
reply.header("Content-Type", "text/html; charset=utf-8");
|
|
564
583
|
return fs.readFile(adminUiIndexPath, "utf8");
|
|
584
|
+
} catch {
|
|
585
|
+
reply.code(503);
|
|
586
|
+
return {
|
|
587
|
+
error: {
|
|
588
|
+
type: "admin_ui_missing",
|
|
589
|
+
message: "React \u7BA1\u7406\u9875\u672A\u6784\u5EFA\uFF0C\u8BF7\u5148\u8FD0\u884C npm run build:ui\u3002"
|
|
590
|
+
}
|
|
591
|
+
};
|
|
565
592
|
}
|
|
566
|
-
reply.header("Content-Type", "text/html; charset=utf-8");
|
|
567
|
-
return renderAdminPage();
|
|
568
593
|
});
|
|
569
594
|
app.get("/assets/*", async (request, reply) => {
|
|
570
595
|
const assetPath = request.params["*"];
|
|
@@ -598,10 +623,21 @@ function createApp(params) {
|
|
|
598
623
|
catalog: result.catalog
|
|
599
624
|
};
|
|
600
625
|
});
|
|
601
|
-
app.post("/_gateway/admin/runtime-refresh", async (request) => {
|
|
626
|
+
app.post("/_gateway/admin/runtime-refresh", async (request, reply) => {
|
|
627
|
+
const parsed = runtimeRefreshSchema.safeParse(request.body ?? {});
|
|
628
|
+
if (!parsed.success) {
|
|
629
|
+
reply.code(400);
|
|
630
|
+
return {
|
|
631
|
+
error: {
|
|
632
|
+
type: "validation_error",
|
|
633
|
+
message: parsed.error.issues[0]?.message ?? "\u8BF7\u6C42\u4F53\u683C\u5F0F\u9519\u8BEF"
|
|
634
|
+
}
|
|
635
|
+
};
|
|
636
|
+
}
|
|
602
637
|
const [quotaSync] = await Promise.all([
|
|
603
638
|
ctx.authService.syncAllProfileQuotas("openai-codex", {
|
|
604
|
-
suppressErrors: true
|
|
639
|
+
suppressErrors: true,
|
|
640
|
+
staleAfterMs: parsed.data.staleOnly ? 30 * 60 * 1e3 : void 0
|
|
605
641
|
}),
|
|
606
642
|
ctx.versionService.getVersionStatus({
|
|
607
643
|
force: true
|
|
@@ -674,6 +710,23 @@ function createApp(params) {
|
|
|
674
710
|
await ctx.authService.removeProfile(parsed.data.profileId);
|
|
675
711
|
return buildAdminConfig(request);
|
|
676
712
|
});
|
|
713
|
+
app.post("/_gateway/admin/profiles/remove-batch", async (request, reply) => {
|
|
714
|
+
const parsed = profileRemoveBatchSchema.safeParse(request.body);
|
|
715
|
+
if (!parsed.success) {
|
|
716
|
+
reply.code(400);
|
|
717
|
+
return {
|
|
718
|
+
error: {
|
|
719
|
+
type: "validation_error",
|
|
720
|
+
message: parsed.error.issues[0]?.message ?? "\u8BF7\u6C42\u4F53\u683C\u5F0F\u9519\u8BEF"
|
|
721
|
+
}
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
const removedProfileCount = await ctx.authService.removeProfiles(parsed.data.profileIds);
|
|
725
|
+
return {
|
|
726
|
+
...await buildAdminConfig(request),
|
|
727
|
+
removedProfileCount
|
|
728
|
+
};
|
|
729
|
+
});
|
|
677
730
|
app.post("/_gateway/admin/profiles/import", async (request, reply) => {
|
|
678
731
|
const parsed = profileImportSchema.safeParse(request.body);
|
|
679
732
|
if (!parsed.success) {
|
|
@@ -694,6 +747,23 @@ function createApp(params) {
|
|
|
694
747
|
importedProfileCount: importedProfiles.length
|
|
695
748
|
};
|
|
696
749
|
});
|
|
750
|
+
app.post("/_gateway/admin/profiles/import/validate", async (request, reply) => {
|
|
751
|
+
const parsed = profileImportSchema.safeParse(request.body);
|
|
752
|
+
if (!parsed.success) {
|
|
753
|
+
reply.code(400);
|
|
754
|
+
return {
|
|
755
|
+
error: {
|
|
756
|
+
type: "validation_error",
|
|
757
|
+
message: parsed.error.issues[0]?.message ?? "\u8BF7\u6C42\u4F53\u683C\u5F0F\u9519\u8BEF"
|
|
758
|
+
}
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
const profiles = ctx.authService.validateProfilesImport(parsed.data.profile);
|
|
762
|
+
return {
|
|
763
|
+
valid: true,
|
|
764
|
+
profileCount: profiles.length
|
|
765
|
+
};
|
|
766
|
+
});
|
|
697
767
|
app.get("/_gateway/admin/profiles/import-template", async () => ({
|
|
698
768
|
profile: ctx.authService.getProfileImportTemplate()
|
|
699
769
|
}));
|
|
@@ -710,11 +780,13 @@ function createApp(params) {
|
|
|
710
780
|
}
|
|
711
781
|
if (parsed.data.all || parsed.data.profileIds) {
|
|
712
782
|
return {
|
|
713
|
-
profile: await ctx.authService.exportProfiles(parsed.data.profileIds)
|
|
783
|
+
profile: await ctx.authService.exportProfiles(parsed.data.profileIds, "openai-codex", parsed.data.all ? "all" : "batch"),
|
|
784
|
+
config: await buildAdminConfig(request)
|
|
714
785
|
};
|
|
715
786
|
}
|
|
716
787
|
return {
|
|
717
|
-
profile: await ctx.authService.exportProfile(parsed.data.profileId)
|
|
788
|
+
profile: await ctx.authService.exportProfile(parsed.data.profileId),
|
|
789
|
+
config: await buildAdminConfig(request)
|
|
718
790
|
};
|
|
719
791
|
});
|
|
720
792
|
app.post("/_gateway/admin/codex/apply", async (request, reply) => {
|
|
@@ -744,17 +816,29 @@ function createApp(params) {
|
|
|
744
816
|
}
|
|
745
817
|
};
|
|
746
818
|
}
|
|
747
|
-
|
|
748
|
-
await ctx.configService.setDefaultModel(parsed.data.defaultModel);
|
|
749
|
-
}
|
|
750
|
-
if (parsed.data.networkProxy) {
|
|
751
|
-
await ctx.configService.setNetworkProxy(parsed.data.networkProxy);
|
|
752
|
-
}
|
|
753
|
-
if (parsed.data.autoSwitch) {
|
|
754
|
-
await ctx.configService.setAutoSwitch(parsed.data.autoSwitch);
|
|
755
|
-
}
|
|
819
|
+
await ctx.configService.updateSettings(parsed.data);
|
|
756
820
|
return buildAdminConfig(request);
|
|
757
821
|
});
|
|
822
|
+
app.post("/_gateway/admin/restart", async (_request, reply) => {
|
|
823
|
+
if (!params?.onRestart) {
|
|
824
|
+
reply.code(501);
|
|
825
|
+
return {
|
|
826
|
+
error: {
|
|
827
|
+
type: "not_supported",
|
|
828
|
+
message: "\u5F53\u524D\u73AF\u5883\u4E0D\u652F\u6301\u91CD\u542F\u3002"
|
|
829
|
+
}
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
setTimeout(() => {
|
|
833
|
+
void Promise.resolve(params.onRestart?.()).catch((error) => {
|
|
834
|
+
console.error("[gateway:restart]", error);
|
|
835
|
+
});
|
|
836
|
+
}, 100);
|
|
837
|
+
return {
|
|
838
|
+
ok: true,
|
|
839
|
+
restarting: true
|
|
840
|
+
};
|
|
841
|
+
});
|
|
758
842
|
app.post("/_gateway/admin/settings/proxy-test", async (request, reply) => {
|
|
759
843
|
const parsed = proxyTestSchema.safeParse(request.body);
|
|
760
844
|
if (!parsed.success) {
|
|
@@ -809,6 +893,64 @@ function createApp(params) {
|
|
|
809
893
|
const settings = await ctx.configService.getSettings();
|
|
810
894
|
return ctx.networkDetectService.collectReport(settings.networkProxy);
|
|
811
895
|
});
|
|
896
|
+
app.get("/_gateway/image-bed/config", async () => ctx.githubImageBedService.getConfig());
|
|
897
|
+
app.post("/_gateway/image-bed/validate", async () => ctx.githubImageBedService.testConnection());
|
|
898
|
+
app.get("/_gateway/image-bed/history", async (request, reply) => {
|
|
899
|
+
const parsed = githubImageBedHistoryQuerySchema.safeParse(request.query ?? {});
|
|
900
|
+
if (!parsed.success) {
|
|
901
|
+
reply.code(400);
|
|
902
|
+
return {
|
|
903
|
+
error: {
|
|
904
|
+
type: "validation_error",
|
|
905
|
+
message: parsed.error.issues[0]?.message ?? "\u8BF7\u6C42\u53C2\u6570\u683C\u5F0F\u9519\u8BEF"
|
|
906
|
+
}
|
|
907
|
+
};
|
|
908
|
+
}
|
|
909
|
+
return ctx.githubImageBedService.listHistory(parsed.data.limit ?? 50);
|
|
910
|
+
});
|
|
911
|
+
app.put("/_gateway/image-bed/config", async (request, reply) => {
|
|
912
|
+
const parsed = githubImageBedConfigSchema.safeParse(request.body);
|
|
913
|
+
if (!parsed.success) {
|
|
914
|
+
reply.code(400);
|
|
915
|
+
return {
|
|
916
|
+
error: {
|
|
917
|
+
type: "validation_error",
|
|
918
|
+
message: parsed.error.issues[0]?.message ?? "\u8BF7\u6C42\u4F53\u683C\u5F0F\u9519\u8BEF"
|
|
919
|
+
}
|
|
920
|
+
};
|
|
921
|
+
}
|
|
922
|
+
return ctx.githubImageBedService.saveToken(parsed.data.token);
|
|
923
|
+
});
|
|
924
|
+
app.delete("/_gateway/image-bed/config", async () => ctx.githubImageBedService.clearToken());
|
|
925
|
+
app.post("/_gateway/image-bed/upload", async (request, reply) => {
|
|
926
|
+
const parsed = githubImageBedUploadSchema.safeParse(request.body);
|
|
927
|
+
if (!parsed.success) {
|
|
928
|
+
reply.code(400);
|
|
929
|
+
return {
|
|
930
|
+
error: {
|
|
931
|
+
type: "validation_error",
|
|
932
|
+
message: parsed.error.issues[0]?.message ?? "\u8BF7\u6C42\u4F53\u683C\u5F0F\u9519\u8BEF"
|
|
933
|
+
}
|
|
934
|
+
};
|
|
935
|
+
}
|
|
936
|
+
const uploaded = await ctx.githubImageBedService.uploadImage(parsed.data);
|
|
937
|
+
await ctx.githubImageBedService.rememberUpload(uploaded);
|
|
938
|
+
return uploaded;
|
|
939
|
+
});
|
|
940
|
+
app.delete("/_gateway/image-bed/history/:id", async (request, reply) => {
|
|
941
|
+
const parsed = githubImageBedHistoryParamsSchema.safeParse(request.params);
|
|
942
|
+
if (!parsed.success) {
|
|
943
|
+
reply.code(400);
|
|
944
|
+
return {
|
|
945
|
+
error: {
|
|
946
|
+
type: "validation_error",
|
|
947
|
+
message: parsed.error.issues[0]?.message ?? "\u8BF7\u6C42\u53C2\u6570\u683C\u5F0F\u9519\u8BEF"
|
|
948
|
+
}
|
|
949
|
+
};
|
|
950
|
+
}
|
|
951
|
+
return ctx.githubImageBedService.deleteHistoryItem(parsed.data.id);
|
|
952
|
+
});
|
|
953
|
+
app.delete("/_gateway/image-bed/history", async () => ctx.githubImageBedService.clearHistory());
|
|
812
954
|
app.get("/v1/models", async () => ({
|
|
813
955
|
object: "list",
|
|
814
956
|
data: (await ctx.modelService.listModels()).map((model) => ({
|
package/dist/server/index.js
CHANGED
|
@@ -21,27 +21,53 @@ function resolveBodyLimitBytes() {
|
|
|
21
21
|
}
|
|
22
22
|
return Math.floor(value * 1024 * 1024);
|
|
23
23
|
}
|
|
24
|
+
function isPortInUseError(error) {
|
|
25
|
+
const normalized = error;
|
|
26
|
+
if (normalized.code === "EADDRINUSE") {
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
return typeof normalized.message === "string" && normalized.message.includes("EADDRINUSE");
|
|
30
|
+
}
|
|
24
31
|
async function startServer(params) {
|
|
25
32
|
const bodyLimit = resolveBodyLimitBytes();
|
|
26
|
-
const app = createApp({
|
|
27
|
-
corsOrigin: resolveCorsOrigin(),
|
|
28
|
-
bodyLimit
|
|
29
|
-
});
|
|
30
33
|
const configService = new ConfigService();
|
|
31
34
|
const defaults = await configService.getServerConfig();
|
|
32
35
|
const host = params?.host ?? defaults.host;
|
|
33
36
|
const port = params?.port ?? defaults.port;
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
37
|
+
const maxPortAttempts = 20;
|
|
38
|
+
let lastError;
|
|
39
|
+
for (let offset = 0; offset <= maxPortAttempts; offset += 1) {
|
|
40
|
+
const listenPort = port + offset;
|
|
41
|
+
const app = createApp({
|
|
42
|
+
corsOrigin: resolveCorsOrigin(),
|
|
43
|
+
bodyLimit,
|
|
44
|
+
onRestart: params?.onRestart
|
|
45
|
+
});
|
|
46
|
+
try {
|
|
47
|
+
await app.listen({
|
|
48
|
+
host,
|
|
49
|
+
port: listenPort
|
|
50
|
+
});
|
|
51
|
+
if (offset > 0) {
|
|
52
|
+
console.warn(`[server] port ${port} was busy, using ${listenPort} instead.`);
|
|
53
|
+
await configService.setServerConfig({ port: listenPort });
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
app,
|
|
57
|
+
host,
|
|
58
|
+
port: listenPort,
|
|
59
|
+
corsOrigin: process.env.AZT_CORS_ORIGIN?.trim() || "*",
|
|
60
|
+
bodyLimit
|
|
61
|
+
};
|
|
62
|
+
} catch (error) {
|
|
63
|
+
await app.close().catch(() => void 0);
|
|
64
|
+
if (!isPortInUseError(error) || offset === maxPortAttempts) {
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
lastError = error;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
throw lastError ?? new Error("\u65E0\u6CD5\u542F\u52A8\u672C\u5730\u670D\u52A1\u3002");
|
|
45
71
|
}
|
|
46
72
|
export {
|
|
47
73
|
startServer
|
package/docs/DESKTOP_RELEASE.md
CHANGED
|
@@ -41,6 +41,13 @@ npm run dist:win
|
|
|
41
41
|
|
|
42
42
|
Creates macOS and Windows distributables. macOS builds should be produced on macOS. Windows builds are best produced on Windows CI or a runner with a complete Windows packaging environment.
|
|
43
43
|
|
|
44
|
+
`npm run dist:mac` must build both Apple Silicon and Intel macOS packages. It runs:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npm run dist:mac:arm64
|
|
48
|
+
npm run dist:mac:x64
|
|
49
|
+
```
|
|
50
|
+
|
|
44
51
|
## UI Engineering Standards
|
|
45
52
|
|
|
46
53
|
Before building release artifacts, the desktop React UI should follow:
|
|
@@ -65,6 +72,8 @@ Unsigned builds are suitable for internal testing only. Public commercial distri
|
|
|
65
72
|
|
|
66
73
|
`electron-builder` reads the standard signing environment variables. Configure these in CI instead of committing credentials to the repository.
|
|
67
74
|
|
|
75
|
+
Until macOS Developer ID signing and notarization are configured, each macOS DMG must include `build/mac-install-guide.txt` so users can handle the Gatekeeper verification prompt.
|
|
76
|
+
|
|
68
77
|
## Release Artifacts
|
|
69
78
|
|
|
70
79
|
The packaged output is written to:
|
|
@@ -75,17 +84,40 @@ release/
|
|
|
75
84
|
|
|
76
85
|
The folder is intentionally ignored by git.
|
|
77
86
|
|
|
87
|
+
Every desktop GitHub Release must upload exactly these user-facing artifacts:
|
|
88
|
+
|
|
89
|
+
```text
|
|
90
|
+
AI Zero Token-{version}-mac-arm64.dmg
|
|
91
|
+
AI Zero Token-{version}-mac-x64.dmg
|
|
92
|
+
AI Zero Token Setup {version}.exe
|
|
93
|
+
AI Zero Token-{version}-win.zip
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Artifact purpose:
|
|
97
|
+
|
|
98
|
+
- `AI Zero Token-{version}-mac-arm64.dmg`: macOS Apple Silicon builds for M1/M2/M3/M4 devices.
|
|
99
|
+
- `AI Zero Token-{version}-mac-x64.dmg`: macOS Intel builds.
|
|
100
|
+
- `AI Zero Token Setup {version}.exe`: Windows installer and primary Windows download.
|
|
101
|
+
- `AI Zero Token-{version}-win.zip`: Windows portable zip.
|
|
102
|
+
|
|
103
|
+
Do not upload unpacked app directories, debug metadata, universal macOS builds, or auto-update metadata unless the release explicitly enables an auto-update channel.
|
|
104
|
+
|
|
105
|
+
macOS DMG files should include the in-package `mac-install-guide.txt` helper document. Do not upload this text file as a standalone GitHub Release asset.
|
|
106
|
+
|
|
78
107
|
### Publish Flow
|
|
79
108
|
|
|
80
109
|
1. Build the desktop package:
|
|
81
110
|
|
|
82
111
|
```bash
|
|
83
|
-
npm run dist:
|
|
112
|
+
npm run dist:mac
|
|
113
|
+
npm run dist:win
|
|
84
114
|
```
|
|
85
115
|
|
|
86
|
-
2.
|
|
116
|
+
2. Rename the generated files, if needed, so the GitHub Release uses the standard artifact names listed above.
|
|
117
|
+
|
|
118
|
+
3. Upload only the standard artifact files from `release/` to the matching GitHub Release tag.
|
|
87
119
|
|
|
88
|
-
|
|
120
|
+
4. Publish the npm package after confirming `package.json` and `package-lock.json` both point at the new version:
|
|
89
121
|
|
|
90
122
|
```bash
|
|
91
123
|
npm publish
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
|
|
39
39
|
### 2.2 第二阶段:桌面端 React 管理台
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
管理页已迁移为 React/Vite 应用,网关生产环境只托管 `admin-ui/dist` 静态构建产物。
|
|
42
42
|
|
|
43
43
|
第二阶段将管理界面迁移为 React/Vite 应用,但迁移范围只限 UI 层。
|
|
44
44
|
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
- 封装 `/_gateway/*` 管理接口客户端
|
|
50
50
|
- 网关生产环境托管前端静态构建产物
|
|
51
51
|
- 桌面端加载同一套管理台 UI
|
|
52
|
-
-
|
|
52
|
+
- 删除服务端内嵌管理页,保留一套 React 管理台
|
|
53
53
|
|
|
54
54
|
这一阶段结束后,项目形态应变为:
|
|
55
55
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-zero-token",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.3",
|
|
4
4
|
"description": "Local-first OpenAI-compatible AI CLI and gateway with Codex OAuth, multi-account management, and gpt-image-2 image generation/editing.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "AI Zero Token Contributors",
|
|
@@ -50,7 +50,9 @@
|
|
|
50
50
|
"desktop:dev": "node scripts/dev.mjs desktop",
|
|
51
51
|
"dist": "npm run build && electron-builder",
|
|
52
52
|
"dist:dir": "npm run build && electron-builder --dir",
|
|
53
|
-
"dist:mac": "npm run
|
|
53
|
+
"dist:mac": "npm run dist:mac:arm64 && npm run dist:mac:x64",
|
|
54
|
+
"dist:mac:arm64": "npm run build && electron-builder --mac --arm64",
|
|
55
|
+
"dist:mac:x64": "npm run build && electron-builder --mac --x64",
|
|
54
56
|
"dist:win": "npm run build && electron-builder --win",
|
|
55
57
|
"typecheck": "npm run typecheck:server && npm run typecheck:ui",
|
|
56
58
|
"typecheck:server": "bunx tsc -p tsconfig.json --noEmit",
|
|
@@ -61,6 +63,7 @@
|
|
|
61
63
|
"dependencies": {
|
|
62
64
|
"@fastify/cors": "^11.1.0",
|
|
63
65
|
"fastify": "^5.8.2",
|
|
66
|
+
"fflate": "^0.8.2",
|
|
64
67
|
"zod": "^4.3.6"
|
|
65
68
|
},
|
|
66
69
|
"devDependencies": {
|
|
@@ -92,6 +95,12 @@
|
|
|
92
95
|
"README.zh-CN.md",
|
|
93
96
|
"LICENSE"
|
|
94
97
|
],
|
|
98
|
+
"extraResources": [
|
|
99
|
+
{
|
|
100
|
+
"from": "build/mac-install-guide.txt",
|
|
101
|
+
"to": "mac-install-guide.txt"
|
|
102
|
+
}
|
|
103
|
+
],
|
|
95
104
|
"extraMetadata": {
|
|
96
105
|
"main": "dist/desktop/main.js"
|
|
97
106
|
},
|
|
@@ -104,6 +113,27 @@
|
|
|
104
113
|
"zip"
|
|
105
114
|
]
|
|
106
115
|
},
|
|
116
|
+
"dmg": {
|
|
117
|
+
"contents": [
|
|
118
|
+
{
|
|
119
|
+
"x": 130,
|
|
120
|
+
"y": 160,
|
|
121
|
+
"type": "file"
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"x": 410,
|
|
125
|
+
"y": 160,
|
|
126
|
+
"type": "link",
|
|
127
|
+
"path": "/Applications"
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
"x": 270,
|
|
131
|
+
"y": 300,
|
|
132
|
+
"type": "file",
|
|
133
|
+
"path": "build/mac-install-guide.txt"
|
|
134
|
+
}
|
|
135
|
+
]
|
|
136
|
+
},
|
|
107
137
|
"win": {
|
|
108
138
|
"icon": "build/icon.ico",
|
|
109
139
|
"target": [
|
|
@@ -119,9 +149,11 @@
|
|
|
119
149
|
"files": [
|
|
120
150
|
"dist",
|
|
121
151
|
"admin-ui/dist",
|
|
152
|
+
"build/icon.svg",
|
|
122
153
|
"build/icon.png",
|
|
123
154
|
"build/icon.icns",
|
|
124
155
|
"build/icon.ico",
|
|
156
|
+
"build/mac-install-guide.txt",
|
|
125
157
|
"CHANGELOG.md",
|
|
126
158
|
"docs/API_USAGE.md",
|
|
127
159
|
"docs/DESKTOP_RELEASE.md",
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
<svg width="128" height="128" viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
-
<rect x="10" y="10" width="108" height="108" rx="26" fill="#111827"/>
|
|
3
|
-
<rect x="18" y="18" width="92" height="92" rx="20" fill="#FFFFFF" fill-opacity=".08"/>
|
|
4
|
-
<path d="M39 36h50c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12H39c-6.627 0-12-5.373-12-12V48c0-6.627 5.373-12 12-12Z" fill="#F8FAFC"/>
|
|
5
|
-
<path d="M42 54h20M42 66h34M42 78h25" stroke="#111827" stroke-width="7" stroke-linecap="round"/>
|
|
6
|
-
<path d="M83 53l8 11-8 11-8-11 8-11Z" fill="#2563EB"/>
|
|
7
|
-
<path d="M71 36c3.2-9.2 11-14 23-14 0 12-4.9 19.9-14.7 23.7" stroke="#22C55E" stroke-width="7" stroke-linecap="round" stroke-linejoin="round"/>
|
|
8
|
-
<path d="M48 92c-3.2 9.2-11 14-23 14 0-12 4.9-19.9 14.7-23.7" stroke="#F59E0B" stroke-width="7" stroke-linecap="round" stroke-linejoin="round"/>
|
|
9
|
-
</svg>
|