@textcortex/zenocode 0.1.2
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/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@textcortex/zenocode",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "Zenocode CLI launcher for TextCortex models and auth flows.",
|
|
5
|
+
"private": false,
|
|
6
|
+
"license": "UNLICENSED",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"publishConfig": {
|
|
9
|
+
"access": "public"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"scripts"
|
|
13
|
+
],
|
|
14
|
+
"bin": {
|
|
15
|
+
"zenocode": "scripts/run-codecortex.mjs",
|
|
16
|
+
"codecortex": "scripts/run-codecortex.mjs"
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build-branded-opencode": "node scripts/build-branded-opencode.mjs",
|
|
20
|
+
"prepare-config": "node scripts/run-codecortex.mjs --prepare-only",
|
|
21
|
+
"dev": "node scripts/run-codecortex.mjs",
|
|
22
|
+
"login": "node scripts/run-codecortex.mjs login",
|
|
23
|
+
"logout": "node scripts/run-codecortex.mjs logout"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const openCodeVersionFooterSnippetPattern =
|
|
2
|
+
/insertNode\(([$\w]+), createTextNode\(`Open`\)\);\s*insertNode\(([$\w]+), ([$\w]+)\);\s*insertNode\(\3, createTextNode\(`Code`\)\);\s*insert\(([$\w]+), \(\) => Installation\.VERSION\);/g;
|
|
3
|
+
|
|
4
|
+
export function padBinaryReplacement(target, replacement) {
|
|
5
|
+
if (replacement.length > target.length) {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
const paddingLength = target.length - replacement.length;
|
|
9
|
+
if (paddingLength === 0) {
|
|
10
|
+
return replacement;
|
|
11
|
+
}
|
|
12
|
+
return `${replacement}${" ".repeat(paddingLength)}`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function _buildZenocodeVersionFooterSnippet(
|
|
16
|
+
openTextRef,
|
|
17
|
+
codeContainerRef,
|
|
18
|
+
codeTextRef,
|
|
19
|
+
versionRef,
|
|
20
|
+
) {
|
|
21
|
+
return [
|
|
22
|
+
`insertNode(${openTextRef}, createTextNode(\`Zeno\`));`,
|
|
23
|
+
`insertNode(${codeContainerRef}, ${codeTextRef});`,
|
|
24
|
+
`insertNode(${codeTextRef}, createTextNode(\`code\`));`,
|
|
25
|
+
`insert(${versionRef}, () => "");`,
|
|
26
|
+
].join(" ");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function patchOpenCodeVersionFooterText(text) {
|
|
30
|
+
openCodeVersionFooterSnippetPattern.lastIndex = 0;
|
|
31
|
+
let patched = false;
|
|
32
|
+
const next = text.replace(
|
|
33
|
+
openCodeVersionFooterSnippetPattern,
|
|
34
|
+
(match, openTextRef, codeContainerRef, codeTextRef, versionRef) => {
|
|
35
|
+
const replacement = _buildZenocodeVersionFooterSnippet(
|
|
36
|
+
openTextRef,
|
|
37
|
+
codeContainerRef,
|
|
38
|
+
codeTextRef,
|
|
39
|
+
versionRef,
|
|
40
|
+
);
|
|
41
|
+
const padded = replacement.length <= match.length ? replacement.padEnd(match.length, " ") : null;
|
|
42
|
+
if (!padded || padded === match) {
|
|
43
|
+
return match;
|
|
44
|
+
}
|
|
45
|
+
patched = true;
|
|
46
|
+
return padded;
|
|
47
|
+
},
|
|
48
|
+
);
|
|
49
|
+
return { patched, text: next };
|
|
50
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { padBinaryReplacement, patchOpenCodeVersionFooterText } from "./branding-patch.mjs";
|
|
4
|
+
|
|
5
|
+
test("padBinaryReplacement keeps binary length stable", () => {
|
|
6
|
+
const padded = padBinaryReplacement("abcdef", "abc");
|
|
7
|
+
assert.equal(padded, "abc ");
|
|
8
|
+
assert.equal(padded.length, 6);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test("patchOpenCodeVersionFooterText rewrites OpenCode + version footer", () => {
|
|
12
|
+
const input = [
|
|
13
|
+
"insertNode(_el$74, createTextNode(`Open`));",
|
|
14
|
+
" insertNode(_el$76, _el$77);",
|
|
15
|
+
" insertNode(_el$77, createTextNode(`Code`));",
|
|
16
|
+
" insert(_el$80, () => Installation.VERSION);",
|
|
17
|
+
].join("\n");
|
|
18
|
+
|
|
19
|
+
const result = patchOpenCodeVersionFooterText(input);
|
|
20
|
+
assert.equal(result.patched, true);
|
|
21
|
+
assert.equal(result.text.length, input.length);
|
|
22
|
+
assert.match(result.text, /createTextNode\(`Zeno`\)/);
|
|
23
|
+
assert.match(result.text, /createTextNode\(`code`\)/);
|
|
24
|
+
assert.match(result.text, /insert\(_el\$80, \(\) => ""\);/);
|
|
25
|
+
assert.doesNotMatch(result.text, /Installation\.VERSION/);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("patchOpenCodeVersionFooterText is a no-op when already branded", () => {
|
|
29
|
+
const branded = [
|
|
30
|
+
"insertNode(_el$74, createTextNode(`Zeno`));",
|
|
31
|
+
"insertNode(_el$76, _el$77);",
|
|
32
|
+
"insertNode(_el$77, createTextNode(`code`));",
|
|
33
|
+
"insert(_el$80, () => \"\");",
|
|
34
|
+
].join(" ");
|
|
35
|
+
|
|
36
|
+
const result = patchOpenCodeVersionFooterText(branded);
|
|
37
|
+
assert.equal(result.patched, false);
|
|
38
|
+
assert.equal(result.text, branded);
|
|
39
|
+
});
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import fs from "node:fs/promises";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import process from "node:process";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
|
|
9
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const appRoot = path.resolve(__dirname, "..");
|
|
11
|
+
const defaultOutputDir = path.join(appRoot, ".zenocode", "brand-build");
|
|
12
|
+
|
|
13
|
+
const forkUrl =
|
|
14
|
+
process.env.ZENOCODE_OPENCODE_FORK_URL ||
|
|
15
|
+
process.env.CODECORTEX_OPENCODE_FORK_URL ||
|
|
16
|
+
"https://github.com/anomalyco/opencode";
|
|
17
|
+
const forkRef = (process.env.ZENOCODE_OPENCODE_REF || process.env.CODECORTEX_OPENCODE_REF || "").trim();
|
|
18
|
+
const outputDir =
|
|
19
|
+
process.env.ZENOCODE_BRANDED_OUTPUT_DIR ||
|
|
20
|
+
process.env.CODECORTEX_BRANDED_OUTPUT_DIR ||
|
|
21
|
+
defaultOutputDir;
|
|
22
|
+
const wrapperPackageName =
|
|
23
|
+
process.env.ZENOCODE_BRANDED_PACKAGE ||
|
|
24
|
+
process.env.CODECORTEX_BRANDED_PACKAGE ||
|
|
25
|
+
"@textcortex/zenocode-ai";
|
|
26
|
+
const wrapperBinName =
|
|
27
|
+
process.env.ZENOCODE_BRANDED_BIN_NAME ||
|
|
28
|
+
process.env.CODECORTEX_BRANDED_BIN_NAME ||
|
|
29
|
+
"zenocode";
|
|
30
|
+
const publishEnabled =
|
|
31
|
+
(process.env.ZENOCODE_PUBLISH || process.env.CODECORTEX_PUBLISH) === "1";
|
|
32
|
+
const publishTag =
|
|
33
|
+
process.env.ZENOCODE_PUBLISH_TAG ||
|
|
34
|
+
process.env.CODECORTEX_PUBLISH_TAG ||
|
|
35
|
+
"latest";
|
|
36
|
+
const buildArgs = (
|
|
37
|
+
process.env.ZENOCODE_OPENCODE_BUILD_ARGS ||
|
|
38
|
+
process.env.CODECORTEX_OPENCODE_BUILD_ARGS ||
|
|
39
|
+
""
|
|
40
|
+
)
|
|
41
|
+
.trim()
|
|
42
|
+
.split(/\s+/)
|
|
43
|
+
.filter(Boolean);
|
|
44
|
+
const cliArgs = process.argv.slice(2);
|
|
45
|
+
|
|
46
|
+
function _command(name) {
|
|
47
|
+
if (process.platform === "win32" && ["npm", "pnpm", "npx"].includes(name)) {
|
|
48
|
+
return `${name}.cmd`;
|
|
49
|
+
}
|
|
50
|
+
return name;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function run(command, args, options = {}) {
|
|
54
|
+
return new Promise((resolve, reject) => {
|
|
55
|
+
const child = spawn(command, args, {
|
|
56
|
+
stdio: "inherit",
|
|
57
|
+
...options,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
child.on("error", reject);
|
|
61
|
+
child.on("exit", (code) => {
|
|
62
|
+
if (code === 0) {
|
|
63
|
+
resolve();
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
reject(new Error(`${command} ${args.join(" ")} failed with exit code ${String(code ?? 1)}`));
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function requireCommand(command, args = ["--version"]) {
|
|
72
|
+
try {
|
|
73
|
+
await run(command, args, { stdio: "ignore" });
|
|
74
|
+
} catch (error) {
|
|
75
|
+
if (error?.code === "ENOENT") {
|
|
76
|
+
throw new Error(`Missing required command: ${command}`);
|
|
77
|
+
}
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function readJson(filePath) {
|
|
83
|
+
return JSON.parse(await fs.readFile(filePath, "utf-8"));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function buildWrapperPackage({
|
|
87
|
+
checkoutDir,
|
|
88
|
+
distDir,
|
|
89
|
+
artifactDir,
|
|
90
|
+
packageName,
|
|
91
|
+
binName,
|
|
92
|
+
}) {
|
|
93
|
+
const distEntries = await fs.readdir(distDir, { withFileTypes: true });
|
|
94
|
+
const binaries = {};
|
|
95
|
+
const versions = new Set();
|
|
96
|
+
|
|
97
|
+
for (const entry of distEntries) {
|
|
98
|
+
if (!entry.isDirectory()) continue;
|
|
99
|
+
const packageJsonPath = path.join(distDir, entry.name, "package.json");
|
|
100
|
+
try {
|
|
101
|
+
const pkg = await readJson(packageJsonPath);
|
|
102
|
+
if (typeof pkg?.name === "string" && typeof pkg?.version === "string") {
|
|
103
|
+
binaries[pkg.name] = pkg.version;
|
|
104
|
+
versions.add(pkg.version);
|
|
105
|
+
}
|
|
106
|
+
} catch {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (!Object.keys(binaries).length) {
|
|
112
|
+
throw new Error(`No binary packages found under ${distDir}`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const version = versions.values().next().value;
|
|
116
|
+
if (!version) {
|
|
117
|
+
throw new Error("Unable to determine branded package version");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const wrapperDir = path.join(artifactDir, "npm-package");
|
|
121
|
+
await fs.rm(wrapperDir, { recursive: true, force: true });
|
|
122
|
+
await fs.mkdir(path.join(wrapperDir, "bin"), { recursive: true });
|
|
123
|
+
|
|
124
|
+
const opencodePkgPath = path.join(checkoutDir, "packages", "opencode", "package.json");
|
|
125
|
+
const opencodePkg = await readJson(opencodePkgPath);
|
|
126
|
+
const license = typeof opencodePkg?.license === "string" ? opencodePkg.license : "MIT";
|
|
127
|
+
|
|
128
|
+
const binMap = { opencode: "./bin/opencode" };
|
|
129
|
+
if (binName && binName !== "opencode") {
|
|
130
|
+
binMap[binName] = "./bin/opencode";
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const wrapperPkg = {
|
|
134
|
+
name: packageName,
|
|
135
|
+
version,
|
|
136
|
+
license,
|
|
137
|
+
type: "commonjs",
|
|
138
|
+
bin: binMap,
|
|
139
|
+
scripts: { postinstall: "node ./postinstall.mjs" },
|
|
140
|
+
optionalDependencies: binaries,
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
await fs.copyFile(
|
|
144
|
+
path.join(checkoutDir, "packages", "opencode", "bin", "opencode"),
|
|
145
|
+
path.join(wrapperDir, "bin", "opencode"),
|
|
146
|
+
);
|
|
147
|
+
await fs.copyFile(
|
|
148
|
+
path.join(checkoutDir, "packages", "opencode", "script", "postinstall.mjs"),
|
|
149
|
+
path.join(wrapperDir, "postinstall.mjs"),
|
|
150
|
+
);
|
|
151
|
+
await fs.copyFile(path.join(checkoutDir, "LICENSE"), path.join(wrapperDir, "LICENSE"));
|
|
152
|
+
await fs.writeFile(path.join(wrapperDir, "package.json"), `${JSON.stringify(wrapperPkg, null, 2)}\n`, "utf-8");
|
|
153
|
+
|
|
154
|
+
const packCommand = _command("npm");
|
|
155
|
+
await run(packCommand, ["pack"], { cwd: wrapperDir });
|
|
156
|
+
|
|
157
|
+
const tarballs = (await fs.readdir(wrapperDir)).filter((entry) => entry.endsWith(".tgz")).sort();
|
|
158
|
+
if (!tarballs.length) {
|
|
159
|
+
throw new Error(`npm pack did not produce a tarball in ${wrapperDir}`);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const latestTarball = path.join(wrapperDir, tarballs[tarballs.length - 1]);
|
|
163
|
+
if (publishEnabled) {
|
|
164
|
+
await run(packCommand, ["publish", latestTarball, "--access", "public", "--tag", publishTag], {
|
|
165
|
+
cwd: wrapperDir,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return latestTarball;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function pickCurrentPlatformBinary(distPackages) {
|
|
173
|
+
const platformMap = { darwin: "darwin", linux: "linux", win32: "windows" };
|
|
174
|
+
const archMap = { x64: "x64", arm64: "arm64" };
|
|
175
|
+
const expectedPlatform = platformMap[process.platform] || process.platform;
|
|
176
|
+
const expectedArch = archMap[process.arch] || process.arch;
|
|
177
|
+
const expectedPrefix = `opencode-${expectedPlatform}-${expectedArch}`;
|
|
178
|
+
|
|
179
|
+
const exact = distPackages.find((name) => name === expectedPrefix);
|
|
180
|
+
if (exact) return exact;
|
|
181
|
+
|
|
182
|
+
const prefixed = distPackages.find((name) => name.startsWith(expectedPrefix));
|
|
183
|
+
if (prefixed) return prefixed;
|
|
184
|
+
|
|
185
|
+
return distPackages[0] || null;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async function main() {
|
|
189
|
+
if (cliArgs.includes("-h") || cliArgs.includes("--help")) {
|
|
190
|
+
console.log("Build a branded Zenocode opencode binary/package from a fork.");
|
|
191
|
+
console.log("");
|
|
192
|
+
console.log("Environment variables:");
|
|
193
|
+
console.log(` ZENOCODE_OPENCODE_FORK_URL Fork URL (default: ${forkUrl})`);
|
|
194
|
+
console.log(" ZENOCODE_OPENCODE_REF Branch/tag/commit to build");
|
|
195
|
+
console.log(` ZENOCODE_BRANDED_OUTPUT_DIR Output directory (default: ${outputDir})`);
|
|
196
|
+
console.log(` ZENOCODE_BRANDED_PACKAGE Wrapper package name (default: ${wrapperPackageName})`);
|
|
197
|
+
console.log(` ZENOCODE_BRANDED_BIN_NAME Extra bin name in wrapper (default: ${wrapperBinName})`);
|
|
198
|
+
console.log(" ZENOCODE_OPENCODE_BUILD_ARGS Additional args for upstream build script");
|
|
199
|
+
console.log(" ZENOCODE_PUBLISH=1 Publish tarball to npm");
|
|
200
|
+
console.log(` ZENOCODE_PUBLISH_TAG npm tag (default: ${publishTag})`);
|
|
201
|
+
console.log(" Legacy CODECORTEX_* env names are still accepted for compatibility.");
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const gitCommand = _command("git");
|
|
206
|
+
const bunCommand = _command("bun");
|
|
207
|
+
const npmCommand = _command("npm");
|
|
208
|
+
await requireCommand(gitCommand);
|
|
209
|
+
await requireCommand(bunCommand);
|
|
210
|
+
await requireCommand(npmCommand);
|
|
211
|
+
|
|
212
|
+
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "zenocode-opencode-"));
|
|
213
|
+
const checkoutDir = path.join(tempRoot, "opencode");
|
|
214
|
+
const distDir = path.join(checkoutDir, "packages", "opencode", "dist");
|
|
215
|
+
const artifactDir = path.resolve(outputDir);
|
|
216
|
+
|
|
217
|
+
try {
|
|
218
|
+
const cloneArgs = ["clone", "--depth=1"];
|
|
219
|
+
if (forkRef) {
|
|
220
|
+
cloneArgs.push("--branch", forkRef);
|
|
221
|
+
}
|
|
222
|
+
cloneArgs.push(forkUrl, checkoutDir);
|
|
223
|
+
await run(gitCommand, cloneArgs);
|
|
224
|
+
|
|
225
|
+
await run(bunCommand, ["install"], { cwd: checkoutDir });
|
|
226
|
+
await run(
|
|
227
|
+
bunCommand,
|
|
228
|
+
["./packages/opencode/script/build.ts", "--single", ...buildArgs],
|
|
229
|
+
{ cwd: checkoutDir },
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
await fs.mkdir(artifactDir, { recursive: true });
|
|
233
|
+
const npmTarball = await buildWrapperPackage({
|
|
234
|
+
checkoutDir,
|
|
235
|
+
distDir,
|
|
236
|
+
artifactDir,
|
|
237
|
+
packageName: wrapperPackageName,
|
|
238
|
+
binName: wrapperBinName,
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
const distEntries = await fs.readdir(distDir, { withFileTypes: true });
|
|
242
|
+
const distPackages = distEntries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
|
|
243
|
+
const selectedPackage = pickCurrentPlatformBinary(distPackages);
|
|
244
|
+
if (!selectedPackage) {
|
|
245
|
+
throw new Error(`No build artifacts found under ${distDir}`);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const builtBinaryName = process.platform === "win32" ? "opencode.exe" : "opencode";
|
|
249
|
+
const builtBinaryPath = path.join(distDir, selectedPackage, "bin", builtBinaryName);
|
|
250
|
+
const localBinaryDir = path.join(artifactDir, "bin");
|
|
251
|
+
const localBinaryName = process.platform === "win32" ? "zenocode-opencode.exe" : "zenocode-opencode";
|
|
252
|
+
const localBinaryPath = path.join(localBinaryDir, localBinaryName);
|
|
253
|
+
|
|
254
|
+
await fs.mkdir(localBinaryDir, { recursive: true });
|
|
255
|
+
await fs.copyFile(builtBinaryPath, localBinaryPath);
|
|
256
|
+
if (process.platform !== "win32") {
|
|
257
|
+
await fs.chmod(localBinaryPath, 0o755);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
console.log("\nZenocode branded build complete.");
|
|
261
|
+
console.log(`Branded binary: ${localBinaryPath}`);
|
|
262
|
+
console.log(`Branded package tarball: ${npmTarball}`);
|
|
263
|
+
if (publishEnabled) {
|
|
264
|
+
console.log(`Published to npm as ${wrapperPackageName} (tag: ${publishTag}).`);
|
|
265
|
+
}
|
|
266
|
+
console.log(`\nRun with local binary:`);
|
|
267
|
+
console.log(` export ZENOCODE_OPENCODE_BIN_PATH="${localBinaryPath}"`);
|
|
268
|
+
console.log(`\nRun with branded package:`);
|
|
269
|
+
console.log(` export ZENOCODE_OPENCODE_PACKAGE="${wrapperPackageName}"`);
|
|
270
|
+
} finally {
|
|
271
|
+
await fs.rm(tempRoot, { recursive: true, force: true });
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
main().catch((error) => {
|
|
276
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
277
|
+
process.exit(1);
|
|
278
|
+
});
|