@co0ontty/wand 4.3.0 → 4.4.0-beta.gdcdccb2
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/dist/build-info.json +4 -4
- package/dist/distribution-manager.d.ts +50 -0
- package/dist/distribution-manager.js +276 -0
- package/dist/server.js +10 -420
- package/dist/structured-claude-adapter.d.ts +10 -0
- package/dist/structured-claude-adapter.js +116 -0
- package/dist/structured-claude-protocol.d.ts +34 -0
- package/dist/structured-claude-protocol.js +246 -0
- package/dist/structured-codex-adapter.d.ts +5 -0
- package/dist/structured-codex-adapter.js +94 -0
- package/dist/structured-codex-protocol.d.ts +78 -0
- package/dist/structured-codex-protocol.js +995 -0
- package/dist/structured-content.d.ts +5 -0
- package/dist/structured-content.js +17 -0
- package/dist/structured-opencode-adapter.d.ts +10 -7
- package/dist/structured-opencode-adapter.js +103 -0
- package/dist/structured-runner.d.ts +42 -0
- package/dist/structured-runner.js +1 -0
- package/dist/structured-session-manager.d.ts +13 -78
- package/dist/structured-session-manager.js +507 -2252
- package/package.json +1 -1
package/dist/build-info.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"commit": "
|
|
3
|
-
"builtAt": "2026-07-
|
|
4
|
-
"version": "4.
|
|
5
|
-
"channel": "
|
|
2
|
+
"commit": "dcdccb251729273fd1c919ab495da79dcb88921c",
|
|
3
|
+
"builtAt": "2026-07-15T11:45:23.623Z",
|
|
4
|
+
"version": "4.4.0-beta.gdcdccb2",
|
|
5
|
+
"channel": "beta"
|
|
6
6
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { WandConfig } from "./types.js";
|
|
2
|
+
export type ApkUpdateChannel = "stable" | "beta";
|
|
3
|
+
export interface LocalDistributionAsset {
|
|
4
|
+
fileName: string;
|
|
5
|
+
filePath: string;
|
|
6
|
+
size: number;
|
|
7
|
+
updatedAt: string;
|
|
8
|
+
version: string | null;
|
|
9
|
+
downloadUrl: string;
|
|
10
|
+
source: "local";
|
|
11
|
+
}
|
|
12
|
+
export interface ResolvedDistributionAsset {
|
|
13
|
+
version: string;
|
|
14
|
+
downloadUrl: string;
|
|
15
|
+
fileName: string;
|
|
16
|
+
size: number;
|
|
17
|
+
source: "local" | "github";
|
|
18
|
+
releaseNotes?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface DistributionSettings {
|
|
21
|
+
androidApk: Record<string, unknown>;
|
|
22
|
+
macosDmg: Record<string, unknown>;
|
|
23
|
+
}
|
|
24
|
+
export interface DistributionManagerOptions {
|
|
25
|
+
configDir: string;
|
|
26
|
+
configPath: string;
|
|
27
|
+
config: WandConfig;
|
|
28
|
+
repositoryUrl: string;
|
|
29
|
+
fetch?: typeof fetch;
|
|
30
|
+
now?: () => number;
|
|
31
|
+
}
|
|
32
|
+
export declare class DistributionManager {
|
|
33
|
+
private readonly options;
|
|
34
|
+
private readonly fetchImpl;
|
|
35
|
+
private readonly now;
|
|
36
|
+
private readonly githubCache;
|
|
37
|
+
constructor(options: DistributionManagerOptions);
|
|
38
|
+
resolveLatestApk(channel: ApkUpdateChannel): Promise<ResolvedDistributionAsset | null>;
|
|
39
|
+
resolveAndroidDownload(channel?: ApkUpdateChannel): Promise<LocalDistributionAsset | null>;
|
|
40
|
+
resolveLatestDmg(): Promise<ResolvedDistributionAsset | null>;
|
|
41
|
+
resolveMacosDownload(): Promise<LocalDistributionAsset | null>;
|
|
42
|
+
getSettings(): Promise<DistributionSettings>;
|
|
43
|
+
private refreshConfig;
|
|
44
|
+
private resolveLocalAsset;
|
|
45
|
+
private readLocalAsset;
|
|
46
|
+
private fetchGitHubAsset;
|
|
47
|
+
private fetchGitHubReleaseAsset;
|
|
48
|
+
private buildSettings;
|
|
49
|
+
private publicAsset;
|
|
50
|
+
}
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import { mkdir, readdir, readFile, stat } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { compareApkInstallOrder, compareSemver, extractSemver } from "./version-utils.js";
|
|
4
|
+
const GITHUB_CACHE_TTL_MS = 10 * 60 * 1000;
|
|
5
|
+
function asRecord(value) {
|
|
6
|
+
return value && typeof value === "object" ? value : null;
|
|
7
|
+
}
|
|
8
|
+
function resolveConfiguredDir(configDir, configuredDir, fallback) {
|
|
9
|
+
const value = configuredDir?.trim();
|
|
10
|
+
if (!value)
|
|
11
|
+
return path.join(configDir, fallback);
|
|
12
|
+
return path.isAbsolute(value) ? value : path.resolve(configDir, value);
|
|
13
|
+
}
|
|
14
|
+
function extractArtifactVersion(fileName, extension) {
|
|
15
|
+
return extractSemver(fileName.replace(new RegExp(`\\${extension}$`, "i"), ""));
|
|
16
|
+
}
|
|
17
|
+
function isPrerelease(version) {
|
|
18
|
+
return !!version && version.includes("-");
|
|
19
|
+
}
|
|
20
|
+
export class DistributionManager {
|
|
21
|
+
options;
|
|
22
|
+
fetchImpl;
|
|
23
|
+
now;
|
|
24
|
+
githubCache = new Map();
|
|
25
|
+
constructor(options) {
|
|
26
|
+
this.options = options;
|
|
27
|
+
this.fetchImpl = options.fetch ?? fetch;
|
|
28
|
+
this.now = options.now ?? Date.now;
|
|
29
|
+
}
|
|
30
|
+
async resolveLatestApk(channel) {
|
|
31
|
+
const [localApk, githubApk] = await Promise.all([
|
|
32
|
+
this.resolveAndroidDownload(channel),
|
|
33
|
+
this.fetchGitHubAsset(".apk"),
|
|
34
|
+
]);
|
|
35
|
+
const local = localApk?.version ? {
|
|
36
|
+
version: localApk.version,
|
|
37
|
+
downloadUrl: `${localApk.downloadUrl}?channel=${channel}`,
|
|
38
|
+
fileName: localApk.fileName,
|
|
39
|
+
size: localApk.size,
|
|
40
|
+
source: "local",
|
|
41
|
+
} : null;
|
|
42
|
+
const github = githubApk ? { ...githubApk, source: "github" } : null;
|
|
43
|
+
if (local && github) {
|
|
44
|
+
return compareApkInstallOrder(github.version, local.version) > 0 ? github : local;
|
|
45
|
+
}
|
|
46
|
+
return local ?? github;
|
|
47
|
+
}
|
|
48
|
+
async resolveAndroidDownload(channel = "beta") {
|
|
49
|
+
await this.refreshConfig();
|
|
50
|
+
const { config, configDir } = this.options;
|
|
51
|
+
if (config.android?.enabled !== true)
|
|
52
|
+
return null;
|
|
53
|
+
const directory = resolveConfiguredDir(configDir, config.android.apkDir, "android");
|
|
54
|
+
return this.resolveLocalAsset({
|
|
55
|
+
directory,
|
|
56
|
+
extension: ".apk",
|
|
57
|
+
configuredFile: channel === "beta" ? "" : config.android.currentApkFile,
|
|
58
|
+
downloadUrl: "/android/download",
|
|
59
|
+
compareVersions: compareApkInstallOrder,
|
|
60
|
+
acceptVersion: channel === "stable" ? (version) => !isPrerelease(version) : undefined,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
async resolveLatestDmg() {
|
|
64
|
+
const localDmg = await this.resolveMacosDownload();
|
|
65
|
+
if (localDmg?.version) {
|
|
66
|
+
return {
|
|
67
|
+
version: localDmg.version,
|
|
68
|
+
downloadUrl: localDmg.downloadUrl,
|
|
69
|
+
fileName: localDmg.fileName,
|
|
70
|
+
size: localDmg.size,
|
|
71
|
+
source: "local",
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
const github = await this.fetchGitHubAsset(".dmg");
|
|
75
|
+
return github ? { ...github, source: "github" } : null;
|
|
76
|
+
}
|
|
77
|
+
async resolveMacosDownload() {
|
|
78
|
+
await this.refreshConfig();
|
|
79
|
+
const { config, configDir } = this.options;
|
|
80
|
+
if (config.macos?.enabled !== true)
|
|
81
|
+
return null;
|
|
82
|
+
return this.resolveLocalAsset({
|
|
83
|
+
directory: resolveConfiguredDir(configDir, config.macos.dmgDir, "macos"),
|
|
84
|
+
extension: ".dmg",
|
|
85
|
+
configuredFile: config.macos.currentDmgFile,
|
|
86
|
+
downloadUrl: "/macos/download",
|
|
87
|
+
compareVersions: compareSemver,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
async getSettings() {
|
|
91
|
+
const [localApk, githubApk, localDmg, githubDmg] = await Promise.all([
|
|
92
|
+
this.resolveAndroidDownload("beta"),
|
|
93
|
+
this.fetchGitHubAsset(".apk"),
|
|
94
|
+
this.resolveMacosDownload(),
|
|
95
|
+
this.fetchGitHubAsset(".dmg"),
|
|
96
|
+
]);
|
|
97
|
+
const apkDir = resolveConfiguredDir(this.options.configDir, this.options.config.android?.apkDir, "android");
|
|
98
|
+
const dmgDir = resolveConfiguredDir(this.options.configDir, this.options.config.macos?.dmgDir, "macos");
|
|
99
|
+
return {
|
|
100
|
+
androidApk: this.buildSettings("apk", apkDir, this.options.config.android?.enabled === true, localApk, githubApk),
|
|
101
|
+
macosDmg: this.buildSettings("dmg", dmgDir, this.options.config.macos?.enabled === true, localDmg, githubDmg),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
async refreshConfig() {
|
|
105
|
+
let raw;
|
|
106
|
+
try {
|
|
107
|
+
raw = JSON.parse(await readFile(this.options.configPath, "utf8"));
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const { config } = this.options;
|
|
113
|
+
const android = asRecord(raw.android);
|
|
114
|
+
if (android) {
|
|
115
|
+
config.android = { ...(config.android ?? {}) };
|
|
116
|
+
if (typeof android.enabled === "boolean")
|
|
117
|
+
config.android.enabled = android.enabled;
|
|
118
|
+
if (Object.hasOwn(android, "apkDir")) {
|
|
119
|
+
config.android.apkDir = typeof android.apkDir === "string" && android.apkDir.trim() ? android.apkDir.trim() : "android";
|
|
120
|
+
}
|
|
121
|
+
if (Object.hasOwn(android, "currentApkFile")) {
|
|
122
|
+
config.android.currentApkFile = typeof android.currentApkFile === "string" ? android.currentApkFile.trim() : "";
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const macos = asRecord(raw.macos);
|
|
126
|
+
if (macos) {
|
|
127
|
+
config.macos = { ...(config.macos ?? {}) };
|
|
128
|
+
if (typeof macos.enabled === "boolean")
|
|
129
|
+
config.macos.enabled = macos.enabled;
|
|
130
|
+
if (Object.hasOwn(macos, "dmgDir")) {
|
|
131
|
+
config.macos.dmgDir = typeof macos.dmgDir === "string" && macos.dmgDir.trim() ? macos.dmgDir.trim() : "macos";
|
|
132
|
+
}
|
|
133
|
+
if (Object.hasOwn(macos, "currentDmgFile")) {
|
|
134
|
+
config.macos.currentDmgFile = typeof macos.currentDmgFile === "string" ? macos.currentDmgFile.trim() : "";
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
async resolveLocalAsset(options) {
|
|
139
|
+
await mkdir(options.directory, { recursive: true });
|
|
140
|
+
const configuredFile = options.configuredFile?.trim();
|
|
141
|
+
if (configuredFile) {
|
|
142
|
+
return this.readLocalAsset(path.join(options.directory, path.basename(configuredFile)), options);
|
|
143
|
+
}
|
|
144
|
+
const entries = await readdir(options.directory, { withFileTypes: true });
|
|
145
|
+
const files = entries.filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith(options.extension));
|
|
146
|
+
const candidates = (await Promise.all(files.map(async (entry) => {
|
|
147
|
+
const filePath = path.join(options.directory, entry.name);
|
|
148
|
+
return { entry, filePath, fileStat: await stat(filePath) };
|
|
149
|
+
}))).filter(({ entry }) => options.acceptVersion?.(extractArtifactVersion(entry.name, options.extension)) ?? true);
|
|
150
|
+
if (candidates.length === 0)
|
|
151
|
+
return null;
|
|
152
|
+
candidates.sort((a, b) => {
|
|
153
|
+
const aVersion = extractArtifactVersion(a.entry.name, options.extension);
|
|
154
|
+
const bVersion = extractArtifactVersion(b.entry.name, options.extension);
|
|
155
|
+
if (aVersion && bVersion) {
|
|
156
|
+
const comparison = options.compareVersions(bVersion, aVersion);
|
|
157
|
+
if (comparison !== 0)
|
|
158
|
+
return comparison;
|
|
159
|
+
}
|
|
160
|
+
else if (aVersion) {
|
|
161
|
+
return -1;
|
|
162
|
+
}
|
|
163
|
+
else if (bVersion) {
|
|
164
|
+
return 1;
|
|
165
|
+
}
|
|
166
|
+
return b.fileStat.mtimeMs - a.fileStat.mtimeMs;
|
|
167
|
+
});
|
|
168
|
+
const selected = candidates[0];
|
|
169
|
+
return {
|
|
170
|
+
fileName: selected.entry.name,
|
|
171
|
+
filePath: selected.filePath,
|
|
172
|
+
size: selected.fileStat.size,
|
|
173
|
+
updatedAt: selected.fileStat.mtime.toISOString(),
|
|
174
|
+
version: extractArtifactVersion(selected.entry.name, options.extension),
|
|
175
|
+
downloadUrl: options.downloadUrl,
|
|
176
|
+
source: "local",
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
async readLocalAsset(filePath, options) {
|
|
180
|
+
try {
|
|
181
|
+
const fileStat = await stat(filePath);
|
|
182
|
+
if (!fileStat.isFile())
|
|
183
|
+
return null;
|
|
184
|
+
const fileName = path.basename(filePath);
|
|
185
|
+
const version = extractArtifactVersion(fileName, options.extension);
|
|
186
|
+
if (options.acceptVersion && !options.acceptVersion(version))
|
|
187
|
+
return null;
|
|
188
|
+
return {
|
|
189
|
+
fileName,
|
|
190
|
+
filePath,
|
|
191
|
+
size: fileStat.size,
|
|
192
|
+
updatedAt: fileStat.mtime.toISOString(),
|
|
193
|
+
version,
|
|
194
|
+
downloadUrl: options.downloadUrl,
|
|
195
|
+
source: "local",
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
async fetchGitHubAsset(extension) {
|
|
203
|
+
const cached = this.githubCache.get(extension);
|
|
204
|
+
if (cached && this.now() - cached.timestamp < GITHUB_CACHE_TTL_MS)
|
|
205
|
+
return cached.asset;
|
|
206
|
+
try {
|
|
207
|
+
const hit = await this.fetchGitHubReleaseAsset(extension);
|
|
208
|
+
if (!hit)
|
|
209
|
+
return cached?.asset ?? null;
|
|
210
|
+
const version = extractArtifactVersion(hit.asset.name, extension)
|
|
211
|
+
?? extractArtifactVersion(hit.tagName, extension)
|
|
212
|
+
?? hit.tagName.replace(/^v/, "");
|
|
213
|
+
const asset = {
|
|
214
|
+
version,
|
|
215
|
+
downloadUrl: hit.asset.browser_download_url,
|
|
216
|
+
fileName: hit.asset.name,
|
|
217
|
+
size: hit.asset.size,
|
|
218
|
+
...(extension === ".apk" && hit.body ? { releaseNotes: hit.body.trim().slice(0, 500) } : {}),
|
|
219
|
+
};
|
|
220
|
+
this.githubCache.set(extension, { asset, timestamp: this.now() });
|
|
221
|
+
return asset;
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
return cached?.asset ?? null;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
async fetchGitHubReleaseAsset(extension) {
|
|
228
|
+
const apiUrl = this.options.repositoryUrl.replace("github.com", "api.github.com/repos") + "/releases?per_page=30";
|
|
229
|
+
const response = await this.fetchImpl(apiUrl, {
|
|
230
|
+
headers: { Accept: "application/vnd.github.v3+json", "User-Agent": "wand-server" },
|
|
231
|
+
signal: AbortSignal.timeout(10000),
|
|
232
|
+
});
|
|
233
|
+
if (!response.ok)
|
|
234
|
+
return null;
|
|
235
|
+
const releases = await response.json();
|
|
236
|
+
for (const release of releases) {
|
|
237
|
+
if (release.draft || release.prerelease)
|
|
238
|
+
continue;
|
|
239
|
+
const asset = release.assets.find((candidate) => candidate.name.toLowerCase().endsWith(extension));
|
|
240
|
+
if (asset)
|
|
241
|
+
return { tagName: release.tag_name, body: release.body, asset };
|
|
242
|
+
}
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
buildSettings(kind, directory, enabled, local, github) {
|
|
246
|
+
const selected = local
|
|
247
|
+
? { ...local, source: "local" }
|
|
248
|
+
: github
|
|
249
|
+
? { ...github, updatedAt: null, source: "github" }
|
|
250
|
+
: null;
|
|
251
|
+
const hasKey = kind === "apk" ? "hasApk" : "hasDmg";
|
|
252
|
+
const dirKey = kind === "apk" ? "apkDir" : "dmgDir";
|
|
253
|
+
return {
|
|
254
|
+
enabled,
|
|
255
|
+
[dirKey]: directory,
|
|
256
|
+
[hasKey]: selected !== null,
|
|
257
|
+
fileName: selected?.fileName ?? null,
|
|
258
|
+
version: selected?.version ?? null,
|
|
259
|
+
size: selected?.size ?? null,
|
|
260
|
+
updatedAt: selected?.updatedAt ?? null,
|
|
261
|
+
downloadUrl: selected?.downloadUrl ?? null,
|
|
262
|
+
source: selected?.source ?? null,
|
|
263
|
+
local: local ? this.publicAsset(local) : null,
|
|
264
|
+
github: github ? this.publicAsset(github) : null,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
publicAsset(asset) {
|
|
268
|
+
return {
|
|
269
|
+
fileName: asset.fileName,
|
|
270
|
+
version: asset.version,
|
|
271
|
+
size: asset.size,
|
|
272
|
+
...("updatedAt" in asset ? { updatedAt: asset.updatedAt } : {}),
|
|
273
|
+
downloadUrl: asset.downloadUrl,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
}
|