@co0ontty/wand 4.44.0 → 4.45.0

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,6 +1,6 @@
1
1
  {
2
- "commit": "1fe712a2510fcedc297a8256f101b7c1638cf552",
3
- "builtAt": "2026-08-22T02:47:13.556Z",
4
- "version": "4.44.0",
2
+ "commit": "6788c46769838380333c0a8ff36fa192041ebae3",
3
+ "builtAt": "2026-08-22T04:51:22.312Z",
4
+ "version": "4.45.0",
5
5
  "channel": "stable"
6
6
  }
package/dist/config.js CHANGED
@@ -70,6 +70,7 @@ export const defaultConfig = () => ({
70
70
  language: "",
71
71
  android: defaultAndroidApkConfig(),
72
72
  macos: defaultMacosDmgConfig(),
73
+ ios: defaultIosIpaConfig(),
73
74
  cardDefaults: defaultCardExpandDefaults(),
74
75
  defaultModel: "",
75
76
  defaultCodexModel: "",
@@ -594,6 +595,28 @@ function normalizeMacosDmgConfig(input) {
594
595
  : defaults.currentDmgFile,
595
596
  };
596
597
  }
598
+ function defaultIosIpaConfig() {
599
+ return {
600
+ enabled: false,
601
+ ipaDir: "ios",
602
+ currentIpaFile: "",
603
+ };
604
+ }
605
+ function normalizeIosIpaConfig(input) {
606
+ if (!input || typeof input !== "object")
607
+ return undefined;
608
+ const defaults = defaultIosIpaConfig();
609
+ const iosInput = input;
610
+ return {
611
+ enabled: typeof iosInput.enabled === "boolean" ? iosInput.enabled : defaults.enabled,
612
+ ipaDir: typeof iosInput.ipaDir === "string" && iosInput.ipaDir.trim()
613
+ ? iosInput.ipaDir.trim()
614
+ : defaults.ipaDir,
615
+ currentIpaFile: typeof iosInput.currentIpaFile === "string"
616
+ ? iosInput.currentIpaFile.trim()
617
+ : defaults.currentIpaFile,
618
+ };
619
+ }
597
620
  function normalizeStructuredChatPersona(input) {
598
621
  if (!input || typeof input !== "object")
599
622
  return undefined;
@@ -664,6 +687,7 @@ function mergeWithDefaults(input) {
664
687
  : crypto.randomBytes(32).toString("hex"),
665
688
  android: normalizeAndroidApkConfig(input.android) ?? defaults.android,
666
689
  macos: normalizeMacosDmgConfig(input.macos) ?? defaults.macos,
690
+ ios: normalizeIosIpaConfig(input.ios) ?? defaults.ios,
667
691
  cardDefaults: normalizeCardDefaults(input.cardDefaults),
668
692
  defaultProvider: input.defaultProvider === "codex" || input.defaultProvider === "opencode" || input.defaultProvider === "grok" || input.defaultProvider === "qoder" || input.defaultProvider === "pi" ? input.defaultProvider : "claude",
669
693
  defaultSessionKind: input.defaultSessionKind === "pty" ? "pty" : "structured",
@@ -22,6 +22,7 @@ export interface ResolvedDistributionAsset {
22
22
  export interface DistributionSettings {
23
23
  androidApk: Record<string, unknown>;
24
24
  macosDmg: Record<string, unknown>;
25
+ iosIpa: Record<string, unknown>;
25
26
  }
26
27
  export interface DistributionManagerOptions {
27
28
  configDir: string;
@@ -46,6 +47,8 @@ export declare class DistributionManager {
46
47
  resolveAndroidDownload(channel?: ApkUpdateChannel): Promise<LocalDistributionAsset | null>;
47
48
  resolveLatestDmg(): Promise<ResolvedDistributionAsset | null>;
48
49
  resolveMacosDownload(): Promise<LocalDistributionAsset | null>;
50
+ resolveLatestIpa(): Promise<ResolvedDistributionAsset | null>;
51
+ resolveIosDownload(): Promise<LocalDistributionAsset | null>;
49
52
  getSettings(): Promise<DistributionSettings>;
50
53
  private readonly sha256Cache;
51
54
  /**
@@ -114,18 +114,49 @@ export class DistributionManager {
114
114
  compareVersions: compareSemver,
115
115
  });
116
116
  }
117
+ async resolveLatestIpa() {
118
+ const localIpa = await this.resolveIosDownload();
119
+ if (localIpa?.version) {
120
+ return {
121
+ version: localIpa.version,
122
+ downloadUrl: localIpa.downloadUrl,
123
+ fileName: localIpa.fileName,
124
+ size: localIpa.size,
125
+ source: "local",
126
+ };
127
+ }
128
+ const github = await this.fetchGitHubAsset(".ipa");
129
+ return github ? { ...github, source: "github" } : null;
130
+ }
131
+ async resolveIosDownload() {
132
+ await this.refreshConfig();
133
+ const { config, configDir } = this.options;
134
+ if (config.ios?.enabled !== true)
135
+ return null;
136
+ return this.resolveLocalAsset({
137
+ directory: resolveConfiguredDir(configDir, config.ios.ipaDir, "ios"),
138
+ extension: ".ipa",
139
+ configuredFile: config.ios.currentIpaFile,
140
+ downloadUrl: "/ios/download",
141
+ compareVersions: compareSemver,
142
+ });
143
+ }
117
144
  async getSettings() {
118
- const [localApk, githubApk, localDmg, githubDmg] = await Promise.all([
145
+ const [localApk, githubApk, localDmg, githubDmg, localIpa, githubIpa] = await Promise.all([
119
146
  this.resolveAndroidDownload("beta"),
120
147
  this.fetchGitHubAsset(".apk"),
121
148
  this.resolveMacosDownload(),
122
149
  this.fetchGitHubAsset(".dmg"),
150
+ this.resolveIosDownload(),
151
+ this.fetchGitHubAsset(".ipa"),
123
152
  ]);
124
153
  const apkDir = resolveConfiguredDir(this.options.configDir, this.options.config.android?.apkDir, "android");
125
154
  const dmgDir = resolveConfiguredDir(this.options.configDir, this.options.config.macos?.dmgDir, "macos");
155
+ const ipaDir = resolveConfiguredDir(this.options.configDir, this.options.config.ios?.ipaDir, "ios");
126
156
  return {
127
157
  androidApk: this.buildSettings("apk", apkDir, this.options.config.android?.enabled === true, localApk, githubApk),
128
158
  macosDmg: this.buildSettings("dmg", dmgDir, this.options.config.macos?.enabled === true, localDmg, githubDmg),
159
+ iosIpa: this.buildSettings("ipa", ipaDir, this.options.config.ios?.enabled === true, localIpa, githubIpa),
129
160
  };
130
161
  }
131
162
  sha256Cache = new Map();
@@ -181,6 +212,18 @@ export class DistributionManager {
181
212
  config.macos.currentDmgFile = typeof macos.currentDmgFile === "string" ? macos.currentDmgFile.trim() : "";
182
213
  }
183
214
  }
215
+ const ios = asRecord(raw.ios);
216
+ if (ios) {
217
+ config.ios = { ...(config.ios ?? {}) };
218
+ if (typeof ios.enabled === "boolean")
219
+ config.ios.enabled = ios.enabled;
220
+ if (Object.hasOwn(ios, "ipaDir")) {
221
+ config.ios.ipaDir = typeof ios.ipaDir === "string" && ios.ipaDir.trim() ? ios.ipaDir.trim() : "ios";
222
+ }
223
+ if (Object.hasOwn(ios, "currentIpaFile")) {
224
+ config.ios.currentIpaFile = typeof ios.currentIpaFile === "string" ? ios.currentIpaFile.trim() : "";
225
+ }
226
+ }
184
227
  }
185
228
  async resolveLocalAsset(options) {
186
229
  await mkdir(options.directory, { recursive: true });
@@ -295,8 +338,8 @@ export class DistributionManager {
295
338
  : github
296
339
  ? { ...github, updatedAt: null, source: "github" }
297
340
  : null;
298
- const hasKey = kind === "apk" ? "hasApk" : "hasDmg";
299
- const dirKey = kind === "apk" ? "apkDir" : "dmgDir";
341
+ const hasKey = kind === "apk" ? "hasApk" : kind === "dmg" ? "hasDmg" : "hasIpa";
342
+ const dirKey = kind === "apk" ? "apkDir" : kind === "dmg" ? "dmgDir" : "ipaDir";
300
343
  return {
301
344
  enabled,
302
345
  [dirKey]: directory,
@@ -433,6 +433,16 @@ export function registerSessionRoutes(app, processes, structured, storage, defau
433
433
  res.status(409).json({ error: "会话列表已更新,请重新加载。", revision: page.revision });
434
434
  return;
435
435
  }
436
+ if (offset === 0 && requestedRevision && requestedRevision === page.revision) {
437
+ res.json({
438
+ unchanged: true,
439
+ entries: [],
440
+ offset: 0,
441
+ total: page.total,
442
+ revision: page.revision,
443
+ });
444
+ return;
445
+ }
436
446
  res.json(page);
437
447
  }
438
448
  catch (error) {
@@ -6,6 +6,7 @@ import type { WandConfig } from "./types.js";
6
6
  interface SettingsDistributionPayload {
7
7
  androidApk: Record<string, unknown>;
8
8
  macosDmg: Record<string, unknown>;
9
+ iosIpa: Record<string, unknown>;
9
10
  }
10
11
  interface SettingsBuildInfo {
11
12
  commit: string | null;
@@ -47,11 +47,13 @@ function publicSystemAi(systemAi) {
47
47
  function publicDistributionInfo(distribution) {
48
48
  const androidApk = { ...distribution.androidApk };
49
49
  const macosDmg = { ...distribution.macosDmg };
50
+ const iosIpa = { ...distribution.iosIpa };
50
51
  // These server filesystem paths are useful to administrators, but the About
51
52
  // panel only needs versions, sizes, and download URLs.
52
53
  delete androidApk.apkDir;
53
54
  delete macosDmg.dmgDir;
54
- return { androidApk, macosDmg };
55
+ delete iosIpa.ipaDir;
56
+ return { androidApk, macosDmg, iosIpa };
55
57
  }
56
58
  export function registerSettingsRoutes(app, deps) {
57
59
  const { storage, config, runtimeConfig, configPath, configDir, requireAdmin, requireAdminOrSessionPreferences, } = deps;
@@ -23,6 +23,8 @@ export interface PublicUpdateRoutesDependencies {
23
23
  resolveAndroidDownload(channel: "stable" | "beta"): Promise<DownloadAsset | null>;
24
24
  resolveLatestDmg(): Promise<ResolvedUpdateAsset | null>;
25
25
  resolveMacosDownload(): Promise<DownloadAsset | null>;
26
+ resolveLatestIpa(): Promise<ResolvedUpdateAsset | null>;
27
+ resolveIosDownload(): Promise<DownloadAsset | null>;
26
28
  }
27
29
  export declare function registerPublicUpdateRoutes(app: Express, deps: PublicUpdateRoutesDependencies): void;
28
30
  export declare class ServerUpdateState {
@@ -46,6 +48,7 @@ export interface AdminUpdateRoutesDependencies {
46
48
  getDistributionSettings(): Promise<{
47
49
  androidApk: Record<string, unknown>;
48
50
  macosDmg: Record<string, unknown>;
51
+ iosIpa: Record<string, unknown>;
49
52
  }>;
50
53
  modelCatalog: ModelCatalogService;
51
54
  getUpdateChannel(): UpdateChannel;
@@ -86,6 +86,20 @@ export function registerPublicUpdateRoutes(app, deps) {
86
86
  readErrorMessage: "读取 DMG 文件失败。",
87
87
  });
88
88
  }));
89
+ app.get("/ios/download", asyncRoute(async (req, res) => {
90
+ const asset = await deps.resolveIosDownload();
91
+ if (!asset) {
92
+ res.status(404).json({ error: "当前没有可下载的 IPA 文件。" });
93
+ return;
94
+ }
95
+ streamFileWithRange(req, res, {
96
+ filePath: asset.filePath,
97
+ size: asset.size,
98
+ contentType: "application/octet-stream",
99
+ disposition: `attachment; filename="${encodeURIComponent(asset.fileName)}"`,
100
+ readErrorMessage: "读取 IPA 文件失败。",
101
+ });
102
+ }));
89
103
  }
90
104
  export class ServerUpdateState {
91
105
  providerCliUpdateCache = null;
@@ -106,6 +120,9 @@ export function registerAdminUpdateRoutes(app, deps) {
106
120
  app.get("/api/macos-dmg", requireAdmin, asyncRoute(async (_req, res) => {
107
121
  res.json((await deps.getDistributionSettings()).macosDmg);
108
122
  }));
123
+ app.get("/api/ios-ipa", requireAdmin, asyncRoute(async (_req, res) => {
124
+ res.json((await deps.getDistributionSettings()).iosIpa);
125
+ }));
109
126
  app.get("/api/provider-cli-updates", requireAdmin, asyncRoute(async (req, res) => {
110
127
  try {
111
128
  const force = req.query.refresh === "1" || !state.providerCliUpdateCache;
package/dist/types.d.ts CHANGED
@@ -70,6 +70,11 @@ export interface MacosDmgConfig {
70
70
  dmgDir?: string;
71
71
  currentDmgFile?: string;
72
72
  }
73
+ export interface IosIpaConfig {
74
+ enabled?: boolean;
75
+ ipaDir?: string;
76
+ currentIpaFile?: string;
77
+ }
73
78
  export interface WandConfig {
74
79
  host: string;
75
80
  port: number;
@@ -104,6 +109,7 @@ export interface WandConfig {
104
109
  appSecret?: string;
105
110
  android?: AndroidApkConfig;
106
111
  macos?: MacosDmgConfig;
112
+ ios?: IosIpaConfig;
107
113
  /** Default expand/collapse state for card types in structured chat view */
108
114
  cardDefaults?: CardExpandDefaults;
109
115
  /** 新建会话时默认使用的 Claude 模型(别名或完整 ID)。留空则不传 --model,由 claude 自行决定。 */