@textcortex/zenocode 0.1.14 → 0.1.15
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/README.md +36 -1
- package/package.json +1 -1
- package/scripts/build-branded-opencode.mjs +292 -215
- package/scripts/build-branded-opencode.test.mjs +85 -59
- package/scripts/opencode-version.mjs +5 -3
- package/scripts/run-zenocode.mjs +600 -763
- package/scripts/run-zenocode.test.mjs +625 -430
- package/scripts/source-branding.mjs +608 -0
- package/scripts/source-branding.test.mjs +76 -0
- package/scripts/branding-patch.mjs +0 -193
- package/scripts/branding-patch.test.mjs +0 -89
|
@@ -0,0 +1,608 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
export const zenocodeLogo = {
|
|
5
|
+
left: [
|
|
6
|
+
' ',
|
|
7
|
+
' \\u2580\\u2580\\u2588 \\u2588\\u2580\\u2580 \\u2588\\u2584 \\u2588 \\u2588\\u2580\\u2588',
|
|
8
|
+
' \\u2588\\u2584\\u2584 \\u2588\\u2588\\u2584 \\u2588 \\u2580\\u2588 \\u2588\\u2584\\u2588',
|
|
9
|
+
' ',
|
|
10
|
+
],
|
|
11
|
+
right: [
|
|
12
|
+
' ',
|
|
13
|
+
' \\u2588\\u2580\\u2580 \\u2588\\u2580\\u2588 \\u2588\\u2580\\u2584 \\u2588\\u2580\\u2580',
|
|
14
|
+
' \\u2588\\u2584\\u2584 \\u2588\\u2584\\u2588 \\u2588\\u2584\\u2580 \\u2588\\u2588\\u2584',
|
|
15
|
+
' ',
|
|
16
|
+
],
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const openCodeLogo = {
|
|
20
|
+
left: [
|
|
21
|
+
' ',
|
|
22
|
+
'█▀▀█ █▀▀█ █▀▀█ █▀▀▄',
|
|
23
|
+
'█__█ █__█ █^^^ █__█',
|
|
24
|
+
'▀▀▀▀ █▀▀▀ ▀▀▀▀ ▀~~▀',
|
|
25
|
+
],
|
|
26
|
+
right: [
|
|
27
|
+
' ▄ ',
|
|
28
|
+
'█▀▀▀ █▀▀█ █▀▀█ █▀▀█',
|
|
29
|
+
'█___ █__█ █__█ █^^^',
|
|
30
|
+
'▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀',
|
|
31
|
+
],
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const zenocodeSourceLogo = {
|
|
35
|
+
left: zenocodeLogo.left.map(decodeLogoLine),
|
|
36
|
+
right: zenocodeLogo.right.map(decodeLogoLine),
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const openCodeWordmark = [
|
|
40
|
+
'⠀ ▄ ',
|
|
41
|
+
'█▀▀█ █▀▀█ █▀▀█ █▀▀▄ █▀▀▀ █▀▀█ █▀▀█ █▀▀█',
|
|
42
|
+
'█ █ █ █ █▀▀▀ █ █ █ █ █ █ █ █▀▀▀',
|
|
43
|
+
'▀▀▀▀ █▀▀▀ ▀▀▀▀ ▀ ▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀',
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
const zenocodeWordmark = [
|
|
47
|
+
'⠀ ▄ ',
|
|
48
|
+
'▀▀▀█ █▀▀█ █▀▀▄ █▀▀█ █▀▀▀ █▀▀█ █▀▀█ █▀▀█',
|
|
49
|
+
' ▄▀ █▀▀▀ █ █ █ █ █ █ █ █ █ █▀▀▀',
|
|
50
|
+
'▀▀▀▀ ▀▀▀▀ ▀ ▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀',
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
const openCodeUpgradeSource = `import { Config } from "@/config/config"
|
|
54
|
+
import { AppRuntime } from "@/effect/app-runtime"
|
|
55
|
+
import { Flag } from "@opencode-ai/core/flag/flag"
|
|
56
|
+
import { Installation } from "@/installation"
|
|
57
|
+
import { InstallationVersion } from "@opencode-ai/core/installation/version"
|
|
58
|
+
import { GlobalBus } from "@/bus/global"
|
|
59
|
+
|
|
60
|
+
export async function upgrade() {
|
|
61
|
+
const config = await AppRuntime.runPromise(Config.Service.use((cfg) => cfg.getGlobal()))
|
|
62
|
+
if (config.autoupdate === false || Flag.OPENCODE_DISABLE_AUTOUPDATE) return
|
|
63
|
+
const method = await Installation.method()
|
|
64
|
+
const latest = await Installation.latest(method).catch(() => {})
|
|
65
|
+
if (!latest) return
|
|
66
|
+
|
|
67
|
+
if (Flag.OPENCODE_ALWAYS_NOTIFY_UPDATE) {
|
|
68
|
+
GlobalBus.emit("event", {
|
|
69
|
+
directory: "global",
|
|
70
|
+
payload: {
|
|
71
|
+
type: Installation.Event.UpdateAvailable.type,
|
|
72
|
+
properties: { version: latest },
|
|
73
|
+
},
|
|
74
|
+
})
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (InstallationVersion === latest) return
|
|
79
|
+
|
|
80
|
+
const kind = Installation.getReleaseType(InstallationVersion, latest)
|
|
81
|
+
|
|
82
|
+
if (config.autoupdate === "notify" || kind !== "patch") {
|
|
83
|
+
GlobalBus.emit("event", {
|
|
84
|
+
directory: "global",
|
|
85
|
+
payload: {
|
|
86
|
+
type: Installation.Event.UpdateAvailable.type,
|
|
87
|
+
properties: { version: latest },
|
|
88
|
+
},
|
|
89
|
+
})
|
|
90
|
+
return
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (method === "unknown") return
|
|
94
|
+
await Installation.upgrade(method, latest)
|
|
95
|
+
.then(() =>
|
|
96
|
+
GlobalBus.emit("event", {
|
|
97
|
+
directory: "global",
|
|
98
|
+
payload: {
|
|
99
|
+
type: Installation.Event.Updated.type,
|
|
100
|
+
properties: { version: latest },
|
|
101
|
+
},
|
|
102
|
+
}),
|
|
103
|
+
)
|
|
104
|
+
.catch(() => {})
|
|
105
|
+
}
|
|
106
|
+
`;
|
|
107
|
+
|
|
108
|
+
const zenocodeUpgradeSource = `/**
|
|
109
|
+
* Zenocode runtimes are released together with the @textcortex/zenocode
|
|
110
|
+
* launcher. Never query or install upstream OpenCode releases from the TUI.
|
|
111
|
+
*/
|
|
112
|
+
export async function upgrade() {}
|
|
113
|
+
`;
|
|
114
|
+
|
|
115
|
+
function decodeLogoLine(line) {
|
|
116
|
+
return line.replace(/\\u([0-9a-fA-F]{4})/g, (_, codePoint) =>
|
|
117
|
+
String.fromCharCode(Number.parseInt(codePoint, 16))
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function sourceArray(values) {
|
|
122
|
+
return `[${values.map((value) => JSON.stringify(value)).join(', ')}]`;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const openCodeLogoLeft = `left: ${sourceArray(openCodeLogo.left)}`;
|
|
126
|
+
const openCodeLogoRight = `right: ${sourceArray(openCodeLogo.right)}`;
|
|
127
|
+
const zenocodeLogoLeft = `left: ${sourceArray(zenocodeSourceLogo.left)}`;
|
|
128
|
+
const zenocodeLogoRight = `right: ${sourceArray(zenocodeSourceLogo.right)}`;
|
|
129
|
+
|
|
130
|
+
const sourceBrandingRules = new Map([
|
|
131
|
+
[
|
|
132
|
+
'packages/opencode/src/cli/upgrade.ts',
|
|
133
|
+
[{ from: openCodeUpgradeSource, to: zenocodeUpgradeSource, count: 1 }],
|
|
134
|
+
],
|
|
135
|
+
[
|
|
136
|
+
'packages/tui/src/logo.ts',
|
|
137
|
+
[
|
|
138
|
+
{ from: openCodeLogoLeft, to: zenocodeLogoLeft, count: 1 },
|
|
139
|
+
{ from: openCodeLogoRight, to: zenocodeLogoRight, count: 1 },
|
|
140
|
+
],
|
|
141
|
+
],
|
|
142
|
+
[
|
|
143
|
+
'packages/tui/src/util/presentation.ts',
|
|
144
|
+
[
|
|
145
|
+
{ from: openCodeLogoLeft, to: zenocodeLogoLeft, count: 1 },
|
|
146
|
+
{ from: openCodeLogoRight, to: zenocodeLogoRight, count: 1 },
|
|
147
|
+
{
|
|
148
|
+
from: 'opencode -s ${input.sessionID}',
|
|
149
|
+
to: 'zenocode -s ${input.sessionID}',
|
|
150
|
+
count: 1,
|
|
151
|
+
},
|
|
152
|
+
],
|
|
153
|
+
],
|
|
154
|
+
[
|
|
155
|
+
'packages/opencode/src/cli/ui.ts',
|
|
156
|
+
openCodeWordmark.map((line, index) => ({
|
|
157
|
+
from: `\`${line}\``,
|
|
158
|
+
to: `\`${zenocodeWordmark[index]}\``,
|
|
159
|
+
count: 1,
|
|
160
|
+
})),
|
|
161
|
+
],
|
|
162
|
+
[
|
|
163
|
+
'packages/opencode/src/index.ts',
|
|
164
|
+
[
|
|
165
|
+
{
|
|
166
|
+
from: 'import { UpgradeCommand } from "./cli/cmd/upgrade"\n',
|
|
167
|
+
to: '',
|
|
168
|
+
count: 1,
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
from: 'import { UninstallCommand } from "./cli/cmd/uninstall"\n',
|
|
172
|
+
to: '',
|
|
173
|
+
count: 1,
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
from: 'text.startsWith("opencode ")',
|
|
177
|
+
to: 'text.startsWith("zenocode ")',
|
|
178
|
+
count: 1,
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
from: '.scriptName("opencode")',
|
|
182
|
+
to: '.scriptName("zenocode")',
|
|
183
|
+
count: 1,
|
|
184
|
+
},
|
|
185
|
+
{ from: ' .command(UpgradeCommand)\n', to: '', count: 1 },
|
|
186
|
+
{ from: ' .command(UninstallCommand)\n', to: '', count: 1 },
|
|
187
|
+
],
|
|
188
|
+
],
|
|
189
|
+
[
|
|
190
|
+
'packages/tui/src/app.tsx',
|
|
191
|
+
[
|
|
192
|
+
{
|
|
193
|
+
from: 'renderer.setTerminalTitle("OpenCode")',
|
|
194
|
+
to: 'renderer.setTerminalTitle("Zenocode")',
|
|
195
|
+
count: 2,
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
from: 'renderer.setTerminalTitle(`OC | ${title}`)',
|
|
199
|
+
to: 'renderer.setTerminalTitle(`ZC | ${title}`)',
|
|
200
|
+
count: 1,
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
from: 'renderer.setTerminalTitle(`OC | ${route.data.id}`)',
|
|
204
|
+
to: 'renderer.setTerminalTitle(`ZC | ${route.data.id}`)',
|
|
205
|
+
count: 1,
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
from: 'Successfully updated to OpenCode',
|
|
209
|
+
to: 'Successfully updated to Zenocode',
|
|
210
|
+
count: 1,
|
|
211
|
+
},
|
|
212
|
+
],
|
|
213
|
+
],
|
|
214
|
+
[
|
|
215
|
+
'packages/tui/src/routes/session/sidebar.tsx',
|
|
216
|
+
[
|
|
217
|
+
{ from: '<b>Open</b>', to: '<b>Zeno</b>', count: 1 },
|
|
218
|
+
{ from: '<b>Code</b>', to: '<b>code</b>', count: 1 },
|
|
219
|
+
],
|
|
220
|
+
],
|
|
221
|
+
[
|
|
222
|
+
'packages/tui/src/feature-plugins/sidebar/footer.tsx',
|
|
223
|
+
[
|
|
224
|
+
{ from: '<b>Open</b>', to: '<b>Zeno</b>', count: 1 },
|
|
225
|
+
{ from: '<b>Code</b>', to: '<b>code</b>', count: 1 },
|
|
226
|
+
{
|
|
227
|
+
from: 'OpenCode includes free models',
|
|
228
|
+
to: 'Zenocode includes free models',
|
|
229
|
+
count: 1,
|
|
230
|
+
},
|
|
231
|
+
],
|
|
232
|
+
],
|
|
233
|
+
[
|
|
234
|
+
'packages/tui/src/routes/session/permission.tsx',
|
|
235
|
+
[
|
|
236
|
+
{
|
|
237
|
+
from: 'until OpenCode is restarted',
|
|
238
|
+
to: 'until Zenocode is restarted',
|
|
239
|
+
count: 2,
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
from: 'Tell OpenCode what to do differently',
|
|
243
|
+
to: 'Tell Zenocode what to do differently',
|
|
244
|
+
count: 1,
|
|
245
|
+
},
|
|
246
|
+
],
|
|
247
|
+
],
|
|
248
|
+
[
|
|
249
|
+
'packages/opencode/src/cli/cmd/run/footer.permission.tsx',
|
|
250
|
+
[
|
|
251
|
+
{
|
|
252
|
+
from: 'Tell OpenCode what to do differently',
|
|
253
|
+
to: 'Tell Zenocode what to do differently',
|
|
254
|
+
count: 2,
|
|
255
|
+
},
|
|
256
|
+
],
|
|
257
|
+
],
|
|
258
|
+
[
|
|
259
|
+
'packages/tui/src/attention.ts',
|
|
260
|
+
[
|
|
261
|
+
{
|
|
262
|
+
from: 'const DEFAULT_TITLE = "opencode"',
|
|
263
|
+
to: 'const DEFAULT_TITLE = "zenocode"',
|
|
264
|
+
count: 1,
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
from: 'name: "OpenCode Default"',
|
|
268
|
+
to: 'name: "Zenocode Default"',
|
|
269
|
+
count: 1,
|
|
270
|
+
},
|
|
271
|
+
],
|
|
272
|
+
],
|
|
273
|
+
[
|
|
274
|
+
'packages/tui/src/component/error-component.tsx',
|
|
275
|
+
[
|
|
276
|
+
{ from: 'opencode crashed', to: 'zenocode crashed', count: 1 },
|
|
277
|
+
{
|
|
278
|
+
from: 'opencode {InstallationVersion}',
|
|
279
|
+
to: 'zenocode {InstallationVersion}',
|
|
280
|
+
count: 1,
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
from: 'the opencode crash screen',
|
|
284
|
+
to: 'the Zenocode crash screen',
|
|
285
|
+
count: 1,
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
from: 'The opencode TUI crashed',
|
|
289
|
+
to: 'The Zenocode TUI crashed',
|
|
290
|
+
count: 1,
|
|
291
|
+
},
|
|
292
|
+
],
|
|
293
|
+
],
|
|
294
|
+
[
|
|
295
|
+
'packages/tui/src/util/error.ts',
|
|
296
|
+
[
|
|
297
|
+
{ from: '`opencode models`', to: '`zenocode models`', count: 1 },
|
|
298
|
+
{
|
|
299
|
+
from: 'opencode does not support MCP',
|
|
300
|
+
to: 'Zenocode does not support MCP',
|
|
301
|
+
count: 1,
|
|
302
|
+
},
|
|
303
|
+
],
|
|
304
|
+
],
|
|
305
|
+
[
|
|
306
|
+
'packages/opencode/src/cli/error.ts',
|
|
307
|
+
[
|
|
308
|
+
{
|
|
309
|
+
from: 'opencode does not support MCP',
|
|
310
|
+
to: 'Zenocode does not support MCP',
|
|
311
|
+
count: 1,
|
|
312
|
+
},
|
|
313
|
+
{ from: '\\`opencode models\\`', to: '\\`zenocode models\\`', count: 1 },
|
|
314
|
+
{
|
|
315
|
+
from: '\\`opencode auth login ${url}\\`',
|
|
316
|
+
to: '\\`zenocode auth login ${url}\\`',
|
|
317
|
+
count: 1,
|
|
318
|
+
},
|
|
319
|
+
],
|
|
320
|
+
],
|
|
321
|
+
[
|
|
322
|
+
'packages/opencode/src/cli/cmd/debug/index.ts',
|
|
323
|
+
[{ from: 'opencode version:', to: 'zenocode version:', count: 1 }],
|
|
324
|
+
],
|
|
325
|
+
[
|
|
326
|
+
'packages/tui/src/feature-plugins/home/tips-view.tsx',
|
|
327
|
+
[
|
|
328
|
+
{
|
|
329
|
+
from: 'Create a plugin to prevent OpenCode from reading sensitive files',
|
|
330
|
+
to: 'Create a plugin to prevent Zenocode from reading sensitive files',
|
|
331
|
+
count: 1,
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
from: 'Use {highlight}opencode run{/highlight}',
|
|
335
|
+
to: 'Use {highlight}zenocode run{/highlight}',
|
|
336
|
+
count: 1,
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
from: 'Use {highlight}opencode --continue{/highlight}',
|
|
340
|
+
to: 'Use {highlight}zenocode --continue{/highlight}',
|
|
341
|
+
count: 1,
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
from: 'Use {highlight}opencode run -f file.ts{/highlight}',
|
|
345
|
+
to: 'Use {highlight}zenocode run -f file.ts{/highlight}',
|
|
346
|
+
count: 1,
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
from: 'Run {highlight}opencode serve{/highlight} for headless API access to OpenCode',
|
|
350
|
+
to: 'Run {highlight}zenocode serve{/highlight} for headless API access to Zenocode',
|
|
351
|
+
count: 1,
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
from: 'Use {highlight}opencode run --attach{/highlight}',
|
|
355
|
+
to: 'Use {highlight}zenocode run --attach{/highlight}',
|
|
356
|
+
count: 1,
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
from: 'Run {highlight}opencode upgrade{/highlight} to update to the latest version',
|
|
360
|
+
to: 'Run {highlight}zenocode upgrade{/highlight} for the managed update command',
|
|
361
|
+
count: 1,
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
from: 'Run {highlight}opencode auth list{/highlight}',
|
|
365
|
+
to: 'Run {highlight}zenocode auth list{/highlight}',
|
|
366
|
+
count: 1,
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
from: 'Run {highlight}opencode agent create{/highlight}',
|
|
370
|
+
to: 'Run {highlight}zenocode agent create{/highlight}',
|
|
371
|
+
count: 1,
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
from: 'Run {highlight}opencode github install{/highlight}',
|
|
375
|
+
to: 'Run {highlight}zenocode github install{/highlight}',
|
|
376
|
+
count: 1,
|
|
377
|
+
},
|
|
378
|
+
{
|
|
379
|
+
from: 'Run {highlight}opencode debug config{/highlight}',
|
|
380
|
+
to: 'Run {highlight}zenocode debug config{/highlight}',
|
|
381
|
+
count: 1,
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
from: 'Use {highlight}/connect{/highlight} with OpenCode Zen for curated, tested models',
|
|
385
|
+
to: 'Use {highlight}/connect{/highlight} with Zenocode for curated, tested models',
|
|
386
|
+
count: 1,
|
|
387
|
+
},
|
|
388
|
+
],
|
|
389
|
+
],
|
|
390
|
+
[
|
|
391
|
+
'packages/tui/src/component/dialog-status.tsx',
|
|
392
|
+
[
|
|
393
|
+
{
|
|
394
|
+
from: 'run: opencode mcp auth',
|
|
395
|
+
to: 'run: zenocode mcp auth',
|
|
396
|
+
count: 1,
|
|
397
|
+
},
|
|
398
|
+
],
|
|
399
|
+
],
|
|
400
|
+
[
|
|
401
|
+
'packages/opencode/src/cli/cmd/tui.ts',
|
|
402
|
+
[
|
|
403
|
+
{
|
|
404
|
+
from: 'describe: "start opencode tui"',
|
|
405
|
+
to: 'describe: "start Zenocode TUI"',
|
|
406
|
+
count: 1,
|
|
407
|
+
},
|
|
408
|
+
{
|
|
409
|
+
from: 'describe: "path to start opencode in"',
|
|
410
|
+
to: 'describe: "path to start Zenocode in"',
|
|
411
|
+
count: 1,
|
|
412
|
+
},
|
|
413
|
+
],
|
|
414
|
+
],
|
|
415
|
+
[
|
|
416
|
+
'packages/opencode/src/cli/network.ts',
|
|
417
|
+
[
|
|
418
|
+
{
|
|
419
|
+
from: 'custom domain name for mDNS service (default: opencode.local)',
|
|
420
|
+
to: 'custom domain name for mDNS service (default: zenocode.local)',
|
|
421
|
+
count: 1,
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
from: 'default: "opencode.local"',
|
|
425
|
+
to: 'default: "zenocode.local"',
|
|
426
|
+
count: 1,
|
|
427
|
+
},
|
|
428
|
+
],
|
|
429
|
+
],
|
|
430
|
+
[
|
|
431
|
+
'packages/opencode/src/cli/cmd/mcp.ts',
|
|
432
|
+
[
|
|
433
|
+
{
|
|
434
|
+
from: 'Add servers with: opencode mcp add',
|
|
435
|
+
to: 'Add servers with: zenocode mcp add',
|
|
436
|
+
count: 1,
|
|
437
|
+
},
|
|
438
|
+
{
|
|
439
|
+
from: 'e.g., opencode x @modelcontextprotocol/server-filesystem',
|
|
440
|
+
to: 'e.g., zenocode x @modelcontextprotocol/server-filesystem',
|
|
441
|
+
count: 1,
|
|
442
|
+
},
|
|
443
|
+
],
|
|
444
|
+
],
|
|
445
|
+
[
|
|
446
|
+
'packages/opencode/src/cli/cmd/run.ts',
|
|
447
|
+
[
|
|
448
|
+
{
|
|
449
|
+
from: 'describe: "run opencode with a message"',
|
|
450
|
+
to: 'describe: "run Zenocode with a message"',
|
|
451
|
+
count: 1,
|
|
452
|
+
},
|
|
453
|
+
{
|
|
454
|
+
from: 'attach to a running opencode server',
|
|
455
|
+
to: 'attach to a running Zenocode server',
|
|
456
|
+
count: 1,
|
|
457
|
+
},
|
|
458
|
+
{ from: '$0: "opencode"', to: '$0: "zenocode"', count: 1 },
|
|
459
|
+
],
|
|
460
|
+
],
|
|
461
|
+
[
|
|
462
|
+
'packages/opencode/src/cli/cmd/web.ts',
|
|
463
|
+
[
|
|
464
|
+
{
|
|
465
|
+
from: 'start opencode server and open web interface',
|
|
466
|
+
to: 'start Zenocode server and open web interface',
|
|
467
|
+
count: 1,
|
|
468
|
+
},
|
|
469
|
+
],
|
|
470
|
+
],
|
|
471
|
+
[
|
|
472
|
+
'packages/opencode/src/cli/cmd/pr.ts',
|
|
473
|
+
[
|
|
474
|
+
{ from: 'then run opencode', to: 'then run Zenocode', count: 1 },
|
|
475
|
+
{
|
|
476
|
+
from: 'Found opencode session:',
|
|
477
|
+
to: 'Found Zenocode session:',
|
|
478
|
+
count: 1,
|
|
479
|
+
},
|
|
480
|
+
{
|
|
481
|
+
from: '["opencode", "import", sessionUrl]',
|
|
482
|
+
to: '["zenocode", "import", sessionUrl]',
|
|
483
|
+
count: 1,
|
|
484
|
+
},
|
|
485
|
+
{ from: 'Starting opencode...', to: 'Starting Zenocode...', count: 1 },
|
|
486
|
+
{
|
|
487
|
+
from: 'Process.spawn(["opencode", ...opencodeArgs]',
|
|
488
|
+
to: 'Process.spawn(["zenocode", ...opencodeArgs]',
|
|
489
|
+
count: 1,
|
|
490
|
+
},
|
|
491
|
+
{
|
|
492
|
+
from: 'opencode exited with code',
|
|
493
|
+
to: 'Zenocode exited with code',
|
|
494
|
+
count: 1,
|
|
495
|
+
},
|
|
496
|
+
],
|
|
497
|
+
],
|
|
498
|
+
[
|
|
499
|
+
'packages/opencode/src/cli/cmd/attach.ts',
|
|
500
|
+
[
|
|
501
|
+
{
|
|
502
|
+
from: 'attach to a running opencode server',
|
|
503
|
+
to: 'attach to a running Zenocode server',
|
|
504
|
+
count: 1,
|
|
505
|
+
},
|
|
506
|
+
],
|
|
507
|
+
],
|
|
508
|
+
[
|
|
509
|
+
'packages/opencode/src/cli/cmd/serve.ts',
|
|
510
|
+
[
|
|
511
|
+
{
|
|
512
|
+
from: 'starts a headless opencode server',
|
|
513
|
+
to: 'starts a headless Zenocode server',
|
|
514
|
+
count: 1,
|
|
515
|
+
},
|
|
516
|
+
{
|
|
517
|
+
from: 'opencode server listening on',
|
|
518
|
+
to: 'Zenocode server listening on',
|
|
519
|
+
count: 1,
|
|
520
|
+
},
|
|
521
|
+
],
|
|
522
|
+
],
|
|
523
|
+
[
|
|
524
|
+
'packages/opencode/src/cli/cmd/run/splash.ts',
|
|
525
|
+
[
|
|
526
|
+
{
|
|
527
|
+
from: 'top, "OpenCode", right',
|
|
528
|
+
to: 'top, "Zenocode", right',
|
|
529
|
+
count: 1,
|
|
530
|
+
},
|
|
531
|
+
],
|
|
532
|
+
],
|
|
533
|
+
[
|
|
534
|
+
'packages/opencode/src/cli/cmd/run/footer.prompt.tsx',
|
|
535
|
+
[
|
|
536
|
+
{
|
|
537
|
+
from: 'description: "close OpenCode"',
|
|
538
|
+
to: 'description: "close Zenocode"',
|
|
539
|
+
count: 1,
|
|
540
|
+
},
|
|
541
|
+
],
|
|
542
|
+
],
|
|
543
|
+
[
|
|
544
|
+
'packages/opencode/src/cli/cmd/run/permission.shared.ts',
|
|
545
|
+
[
|
|
546
|
+
{
|
|
547
|
+
from: 'until OpenCode is restarted',
|
|
548
|
+
to: 'until Zenocode is restarted',
|
|
549
|
+
count: 2,
|
|
550
|
+
},
|
|
551
|
+
],
|
|
552
|
+
],
|
|
553
|
+
[
|
|
554
|
+
'packages/opencode/src/server/mdns.ts',
|
|
555
|
+
[
|
|
556
|
+
{
|
|
557
|
+
from: 'const host = domain ?? "opencode.local"',
|
|
558
|
+
to: 'const host = domain ?? "zenocode.local"',
|
|
559
|
+
count: 1,
|
|
560
|
+
},
|
|
561
|
+
{
|
|
562
|
+
from: 'const name = `opencode-${port}`',
|
|
563
|
+
to: 'const name = `zenocode-${port}`',
|
|
564
|
+
count: 1,
|
|
565
|
+
},
|
|
566
|
+
],
|
|
567
|
+
],
|
|
568
|
+
[
|
|
569
|
+
'packages/opencode/src/acp/service.ts',
|
|
570
|
+
[{ from: 'name: "OpenCode"', to: 'name: "Zenocode"', count: 1 }],
|
|
571
|
+
],
|
|
572
|
+
]);
|
|
573
|
+
|
|
574
|
+
export const sourceBrandingFiles = [...sourceBrandingRules.keys()];
|
|
575
|
+
|
|
576
|
+
function occurrenceCount(text, search) {
|
|
577
|
+
return text.split(search).length - 1;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/** Apply the pinned source transformations for one upstream OpenCode file. */
|
|
581
|
+
export function brandOpenCodeSourceFile(relativePath, source) {
|
|
582
|
+
const rules = sourceBrandingRules.get(relativePath);
|
|
583
|
+
if (!rules) {
|
|
584
|
+
throw new Error(`No Zenocode source-branding rules for ${relativePath}`);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
let branded = source;
|
|
588
|
+
for (const rule of rules) {
|
|
589
|
+
const actualCount = occurrenceCount(branded, rule.from);
|
|
590
|
+
if (actualCount !== rule.count) {
|
|
591
|
+
throw new Error(
|
|
592
|
+
`Upstream branding surface drifted in ${relativePath}: expected ${rule.count} occurrence(s), found ${actualCount}.`
|
|
593
|
+
);
|
|
594
|
+
}
|
|
595
|
+
branded = branded.replaceAll(rule.from, rule.to);
|
|
596
|
+
}
|
|
597
|
+
return branded;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/** Brand the checked-out, pinned OpenCode source tree before it is compiled. */
|
|
601
|
+
export async function applyZenocodeSourceBranding(checkoutDir) {
|
|
602
|
+
for (const relativePath of sourceBrandingFiles) {
|
|
603
|
+
const filePath = path.join(checkoutDir, relativePath);
|
|
604
|
+
const source = await fs.readFile(filePath, 'utf-8');
|
|
605
|
+
const branded = brandOpenCodeSourceFile(relativePath, source);
|
|
606
|
+
await fs.writeFile(filePath, branded, 'utf-8');
|
|
607
|
+
}
|
|
608
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
brandOpenCodeSourceFile,
|
|
6
|
+
sourceBrandingFiles,
|
|
7
|
+
} from './source-branding.mjs';
|
|
8
|
+
|
|
9
|
+
test('source branding replaces the terminal logo before compilation', () => {
|
|
10
|
+
const source = `export const logo = {
|
|
11
|
+
left: [" ", "█▀▀█ █▀▀█ █▀▀█ █▀▀▄", "█__█ █__█ █^^^ █__█", "▀▀▀▀ █▀▀▀ ▀▀▀▀ ▀~~▀"],
|
|
12
|
+
right: [" ▄ ", "█▀▀▀ █▀▀█ █▀▀█ █▀▀█", "█___ █__█ █__█ █^^^", "▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀"],
|
|
13
|
+
}
|
|
14
|
+
`;
|
|
15
|
+
|
|
16
|
+
const branded = brandOpenCodeSourceFile('packages/tui/src/logo.ts', source);
|
|
17
|
+
|
|
18
|
+
assert.match(branded, /▀▀█ █▀▀ █▄ █ █▀█/);
|
|
19
|
+
assert.match(branded, /█▀▀ █▀█ █▀▄ █▀▀/);
|
|
20
|
+
assert.doesNotMatch(branded, /█▀▀█ █▀▀█ █▀▀█ █▀▀▄/);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('source branding replaces visible terminal product names', () => {
|
|
24
|
+
const source = `
|
|
25
|
+
renderer.setTerminalTitle("OpenCode")
|
|
26
|
+
renderer.setTerminalTitle("OpenCode")
|
|
27
|
+
renderer.setTerminalTitle(\`OC | \${title}\`)
|
|
28
|
+
renderer.setTerminalTitle(\`OC | \${route.data.id}\`)
|
|
29
|
+
\`Successfully updated to OpenCode v\${result.data.version}. Please restart the application.\`
|
|
30
|
+
`;
|
|
31
|
+
|
|
32
|
+
const branded = brandOpenCodeSourceFile('packages/tui/src/app.tsx', source);
|
|
33
|
+
|
|
34
|
+
assert.match(branded, /setTerminalTitle\("Zenocode"\)/);
|
|
35
|
+
assert.match(branded, /setTerminalTitle\(`ZC \| \$\{title\}`\)/);
|
|
36
|
+
assert.match(branded, /Successfully updated to Zenocode/);
|
|
37
|
+
assert.doesNotMatch(branded, /OpenCode/);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('source branding replaces the terminal network identity', () => {
|
|
41
|
+
const source = `
|
|
42
|
+
describe: "custom domain name for mDNS service (default: opencode.local)",
|
|
43
|
+
default: "opencode.local",
|
|
44
|
+
`;
|
|
45
|
+
|
|
46
|
+
const branded = brandOpenCodeSourceFile(
|
|
47
|
+
'packages/opencode/src/cli/network.ts',
|
|
48
|
+
source
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
assert.match(branded, /zenocode\.local/);
|
|
52
|
+
assert.doesNotMatch(branded, /opencode\.local/);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('source branding fails closed when a pinned upstream surface drifts', () => {
|
|
56
|
+
assert.throws(
|
|
57
|
+
() =>
|
|
58
|
+
brandOpenCodeSourceFile(
|
|
59
|
+
'packages/tui/src/logo.ts',
|
|
60
|
+
'export const logo = { left: [], right: [] }\n'
|
|
61
|
+
),
|
|
62
|
+
/Upstream branding surface drifted/
|
|
63
|
+
);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test('source branding owns every terminal-facing source file', () => {
|
|
67
|
+
assert.ok(
|
|
68
|
+
sourceBrandingFiles.includes('packages/opencode/src/cli/upgrade.ts')
|
|
69
|
+
);
|
|
70
|
+
assert.ok(sourceBrandingFiles.includes('packages/opencode/src/cli/ui.ts'));
|
|
71
|
+
assert.ok(sourceBrandingFiles.includes('packages/opencode/src/index.ts'));
|
|
72
|
+
assert.ok(sourceBrandingFiles.includes('packages/tui/src/app.tsx'));
|
|
73
|
+
assert.ok(
|
|
74
|
+
sourceBrandingFiles.includes('packages/tui/src/routes/session/sidebar.tsx')
|
|
75
|
+
);
|
|
76
|
+
});
|