capix-code 2.2.4 → 2.3.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.
- package/package.json +7 -2
- package/scripts/postinstall.cjs +96 -32
- package/scripts/assert-artifact.sh +0 -31
- package/scripts/assert-customer-brand.sh +0 -54
- package/scripts/assert-upstream-brand.sh +0 -46
- package/scripts/bootstrap.sh +0 -38
- package/scripts/build-manifest.mjs +0 -219
- package/scripts/build.sh +0 -154
- package/scripts/checksum.sh +0 -8
- package/scripts/dev-install.sh +0 -30
- package/scripts/dev.sh +0 -35
- package/scripts/install-config.sh +0 -66
- package/scripts/install.sh +0 -115
- package/scripts/package-customer.sh +0 -28
- package/scripts/package.sh +0 -84
- package/scripts/prepare-npm-meta.mjs +0 -12
- package/scripts/prepare-npm-platform.mjs +0 -59
- package/scripts/rebrand.sh +0 -247
- package/scripts/resolve-version.mjs +0 -126
- package/scripts/validate-manifest.mjs +0 -167
- package/scripts/verify-runtime-provider.ts +0 -9
- package/scripts/write-release-entry.mjs +0 -38
package/scripts/rebrand.sh
DELETED
|
@@ -1,247 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# rebrand.sh — apply Capix branding.
|
|
3
|
-
# ONLY renames: binary name, config dirs, env var prefixes, install script.
|
|
4
|
-
# Does NOT rename: workspace package name, npm package name, or import paths.
|
|
5
|
-
set -euo pipefail
|
|
6
|
-
|
|
7
|
-
DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
|
8
|
-
CAPIX_CODE_DIR="${CAPIX_CODE_DIR:-$DIR/upstream}"
|
|
9
|
-
|
|
10
|
-
if [ ! -d "$CAPIX_CODE_DIR" ]; then
|
|
11
|
-
echo "✗ No $CAPIX_CODE_DIR. Run ./scripts/bootstrap.sh first."
|
|
12
|
-
exit 1
|
|
13
|
-
fi
|
|
14
|
-
|
|
15
|
-
cd "$CAPIX_CODE_DIR"
|
|
16
|
-
|
|
17
|
-
# 0. Rename the upstream package directory.
|
|
18
|
-
if [ -d "packages/opencode" ]; then
|
|
19
|
-
mv packages/opencode packages/capix-code
|
|
20
|
-
echo " ✓ package directory renamed"
|
|
21
|
-
fi
|
|
22
|
-
|
|
23
|
-
echo "▸ Rebranding in $CAPIX_CODE_DIR"
|
|
24
|
-
|
|
25
|
-
# 0a. Patch the script package to use CAPIX_CODE_VERSION env var instead of OPENCODE_VERSION
|
|
26
|
-
SCRIPT_SRC="$CAPIX_CODE_DIR/packages/script/src/index.ts"
|
|
27
|
-
if [ -f "$SCRIPT_SRC" ]; then
|
|
28
|
-
sed -i.bak 's/env\.OPENCODE_VERSION/env.CAPIX_CODE_VERSION/g' "$SCRIPT_SRC"
|
|
29
|
-
sed -i.bak 's/env\.OPENCODE_BUMP/env.CAPIX_CODE_BUMP/g' "$SCRIPT_SRC"
|
|
30
|
-
sed -i.bak 's/env\.OPENCODE_RELEASE/env.CAPIX_CODE_RELEASE/g' "$SCRIPT_SRC"
|
|
31
|
-
sed -i.bak 's/env\.OPENCODE_CHANNEL/env.CAPIX_CODE_CHANNEL/g' "$SCRIPT_SRC"
|
|
32
|
-
sed -i.bak 's/OPENCODE_CHANNEL/CAPIX_CODE_CHANNEL/g' "$SCRIPT_SRC"
|
|
33
|
-
sed -i.bak 's/process\.env\["OPENCODE_CHANNEL"\]/process.env["CAPIX_CODE_CHANNEL"]/g' "$SCRIPT_SRC"
|
|
34
|
-
rm -f "$SCRIPT_SRC.bak"
|
|
35
|
-
echo " ✓ script package env vars rebranded"
|
|
36
|
-
fi
|
|
37
|
-
|
|
38
|
-
# 0b. Patch the build script to define CAPIX_CODE_VERSION instead of OPENCODE_VERSION
|
|
39
|
-
BUILD_SCRIPT="$CAPIX_CODE_DIR/packages/capix-code/script/build.ts"
|
|
40
|
-
if [ -f "$BUILD_SCRIPT" ]; then
|
|
41
|
-
sed -i.bak 's/OPENCODE_VERSION/CAPIX_CODE_VERSION/g' "$BUILD_SCRIPT"
|
|
42
|
-
sed -i.bak 's/OPENCODE_CHANNEL/CAPIX_CODE_CHANNEL/g' "$BUILD_SCRIPT"
|
|
43
|
-
rm -f "$BUILD_SCRIPT.bak"
|
|
44
|
-
echo " ✓ build script defines rebranded"
|
|
45
|
-
fi
|
|
46
|
-
|
|
47
|
-
# 0c. Patch the version.ts to use CAPIX_CODE_VERSION global
|
|
48
|
-
VERSION_SRC="$CAPIX_CODE_DIR/packages/core/src/installation/version.ts"
|
|
49
|
-
if [ -f "$VERSION_SRC" ]; then
|
|
50
|
-
sed -i.bak 's/OPENCODE_VERSION/CAPIX_CODE_VERSION/g' "$VERSION_SRC"
|
|
51
|
-
sed -i.bak 's/OPENCODE_CHANNEL/CAPIX_CODE_CHANNEL/g' "$VERSION_SRC"
|
|
52
|
-
rm -f "$VERSION_SRC.bak"
|
|
53
|
-
echo " ✓ version.ts globals rebranded"
|
|
54
|
-
fi
|
|
55
|
-
|
|
56
|
-
# 0d. Patch the installation index.ts user-agent string
|
|
57
|
-
INSTALL_SRC="$CAPIX_CODE_DIR/packages/capix-code/src/installation/index.ts"
|
|
58
|
-
if [ -f "$INSTALL_SRC" ]; then
|
|
59
|
-
sed -i.bak 's|opencode/|capix-code/|g' "$INSTALL_SRC"
|
|
60
|
-
rm -f "$INSTALL_SRC.bak"
|
|
61
|
-
echo " ✓ installation user-agent rebranded"
|
|
62
|
-
fi
|
|
63
|
-
|
|
64
|
-
# 1. Binary name in packages/capix-code/package.json — ONLY the "bin" field.
|
|
65
|
-
PKG="$CAPIX_CODE_DIR/packages/capix-code/package.json"
|
|
66
|
-
if [ -f "$PKG" ]; then
|
|
67
|
-
# Change only the binary name, not the package name (workspace deps depend on it).
|
|
68
|
-
sed -i.bak 's|"bin": {"opencode"|"bin": {"capix-code"|g' "$PKG"
|
|
69
|
-
rm -f "$PKG.bak"
|
|
70
|
-
echo " ✓ binary name renamed"
|
|
71
|
-
fi
|
|
72
|
-
|
|
73
|
-
# 2. Build script — only the output directory/file naming.
|
|
74
|
-
BUILD="$CAPIX_CODE_DIR/packages/capix-code/script/build.ts"
|
|
75
|
-
if [ -f "$BUILD" ]; then
|
|
76
|
-
sed -i.bak 's|bin/opencode|bin/capix-code|g' "$BUILD"
|
|
77
|
-
rm -f "$BUILD.bak"
|
|
78
|
-
echo " ✓ build.ts: output binary renamed"
|
|
79
|
-
fi
|
|
80
|
-
|
|
81
|
-
# 3. Env prefixes in environment accesses only. Never rewrite imported symbols.
|
|
82
|
-
echo "▸ Replacing env var prefixes…"
|
|
83
|
-
find "$CAPIX_CODE_DIR/packages/capix-code/src" \( -name '*.ts' -o -name '*.js' -o -name '*.tsx' \) -type f 2>/dev/null | while IFS= read -r f; do
|
|
84
|
-
perl -0pi.bak -e 's/(process[.]env|Bun[.]env)[.]OPENCODE_/$1.CAPIX_CODE_/g' "$f"
|
|
85
|
-
rm -f "$f.bak"
|
|
86
|
-
done
|
|
87
|
-
echo " ✓ env prefix replaced"
|
|
88
|
-
|
|
89
|
-
# 4. Config directory paths in source.
|
|
90
|
-
echo "▸ Replacing config directory paths…"
|
|
91
|
-
find "$CAPIX_CODE_DIR/packages/capix-code/src" \( -name '*.ts' -o -name '*.js' -o -name '*.tsx' \) -type f 2>/dev/null | while IFS= read -r f; do
|
|
92
|
-
sed -i.bak \
|
|
93
|
-
-e 's|\.config/opencode|.config/capix-code|g' \
|
|
94
|
-
-e 's|\.opencode/|.capix-code/|g' \
|
|
95
|
-
-e "s|opencode/auth|capix-code/auth|g" \
|
|
96
|
-
"$f"
|
|
97
|
-
rm -f "$f.bak"
|
|
98
|
-
done
|
|
99
|
-
echo " ✓ config dirs updated"
|
|
100
|
-
|
|
101
|
-
# 5. Config filename: opencode.json → capix-code.json (only in the default config path lookup).
|
|
102
|
-
echo "▸ Replacing config filename…"
|
|
103
|
-
find "$CAPIX_CODE_DIR/packages/capix-code/src" \( -name '*.ts' -o -name '*.js' \) -type f 2>/dev/null | while IFS= read -r f; do
|
|
104
|
-
sed -i.bak "s/opencode\.json/capix-code.json/g" "$f"
|
|
105
|
-
rm -f "$f.bak"
|
|
106
|
-
done
|
|
107
|
-
echo " ✓ config filename renamed"
|
|
108
|
-
|
|
109
|
-
# 6. Display name in source — "OpenCode" → "CapixCode" (display strings only, not imports).
|
|
110
|
-
echo "▸ Replacing display names…"
|
|
111
|
-
find "$CAPIX_CODE_DIR/packages/capix-code/src" \( -name '*.ts' -o -name '*.tsx' \) -type f 2>/dev/null | while IFS= read -r f; do
|
|
112
|
-
# Only replace in string literals, not import paths. Conservative: replace "OpenCode" in quotes.
|
|
113
|
-
sed -i.bak \
|
|
114
|
-
-e 's/"OpenCode"/"CapixCode"/g' \
|
|
115
|
-
-e "s/'OpenCode'/'CapixCode'/g" \
|
|
116
|
-
-e 's/`OpenCode`/`CapixCode`/g' \
|
|
117
|
-
"$f"
|
|
118
|
-
rm -f "$f.bak"
|
|
119
|
-
done
|
|
120
|
-
echo " ✓ display names updated"
|
|
121
|
-
|
|
122
|
-
# 6b. Replace the terminal identity and customer-visible command copy. These
|
|
123
|
-
# files are an explicit reviewed allowlist: internal package names, protocol
|
|
124
|
-
# identifiers and import contracts remain untouched.
|
|
125
|
-
cp "$DIR/assets/tui-logo.ts" "$CAPIX_CODE_DIR/packages/tui/src/logo.ts"
|
|
126
|
-
CLI_UI="$CAPIX_CODE_DIR/packages/capix-code/src/cli/ui.ts"
|
|
127
|
-
perl -0pi.bak -e 's/const wordmark = \[.*?\]\n/const wordmark = [\n ` ██████╗ █████╗ ██████╗ ██╗██╗ ██╗ ██████╗ ██████╗ ██████╗ ███████╗`,\n ` ██╔════╝██╔══██╗██╔══██╗██║╚██╗██╔╝ ██╔════╝██╔═══██╗██╔══██╗██╔════╝`,\n ` ██║ ███████║██████╔╝██║ ╚███╔╝ ██║ ██║ ██║██║ ██║█████╗ `,\n ` ██║ ██╔══██║██╔═══╝ ██║ ██╔██╗ ██║ ██║ ██║██║ ██║██╔══╝ `,\n ` ╚██████╗██║ ██║██║ ██║██╔╝ ██╗ ╚██████╗╚██████╔╝██████╔╝███████╗`,\n ` ╚═════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝`,\n ` CAPIX CODE`,\n]\n/s' "$CLI_UI"
|
|
128
|
-
rm -f "$CLI_UI.bak"
|
|
129
|
-
TUI_PRESENTATION="$CAPIX_CODE_DIR/packages/tui/src/util/presentation.ts"
|
|
130
|
-
perl -0pi.bak -e 's/const logo = \{.*?\}\n/const logo = {\n left: [" ██████╗ █████╗ ██████╗ ██╗██╗ ██╗", "██╔════╝ ██╔══██╗██╔══██╗██║╚██╗██╔╝", "██║ ███████║██████╔╝██║ ╚███╔╝ ", "██║ ██╔══██║██╔═══╝ ██║ ██╔██╗ ", "╚██████╗ ██║ ██║██║ ██║██╔╝ ██╗", " ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝"],\n right: [" ██████╗ ██████╗ ██████╗ ███████╗", "██╔════╝██╔═══██╗██╔══██╗██╔════╝", "██║ ██║ ██║██║ ██║█████╗ ", "██║ ██║ ██║██║ ██║██╔══╝ ", "╚██████╗╚██████╔╝██████╔╝███████╗", " ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝"],\n}\n/s; s/opencode -s/capix-code -s/g' "$TUI_PRESENTATION"
|
|
131
|
-
rm -f "$TUI_PRESENTATION.bak"
|
|
132
|
-
PRESENTATION_FILES=(
|
|
133
|
-
packages/tui/src/app.tsx
|
|
134
|
-
packages/tui/src/util/presentation.ts
|
|
135
|
-
packages/tui/src/feature-plugins/sidebar/footer.tsx
|
|
136
|
-
packages/tui/src/feature-plugins/home/footer.tsx
|
|
137
|
-
packages/tui/src/feature-plugins/home/tips-view.tsx
|
|
138
|
-
packages/tui/src/keymap.tsx
|
|
139
|
-
packages/capix-code/src/cli/ui.ts
|
|
140
|
-
packages/capix-code/src/index.ts
|
|
141
|
-
packages/capix-code/src/cli/error.ts
|
|
142
|
-
packages/capix-code/src/cli/cmd/run.ts
|
|
143
|
-
packages/capix-code/src/cli/cmd/run/splash.ts
|
|
144
|
-
packages/capix-code/src/cli/cmd/run/footer.permission.tsx
|
|
145
|
-
packages/capix-code/src/cli/cmd/run/footer.prompt.tsx
|
|
146
|
-
packages/capix-code/src/cli/cmd/run/footer.view.tsx
|
|
147
|
-
packages/capix-code/src/cli/cmd/run/permission.shared.ts
|
|
148
|
-
packages/capix-code/src/provider/error.ts
|
|
149
|
-
packages/capix-code/src/cli/cmd/attach.ts
|
|
150
|
-
packages/capix-code/src/cli/cmd/upgrade.ts
|
|
151
|
-
packages/capix-code/src/cli/cmd/uninstall.ts
|
|
152
|
-
packages/capix-code/src/cli/cmd/serve.ts
|
|
153
|
-
packages/capix-code/src/cli/cmd/web.ts
|
|
154
|
-
packages/capix-code/src/cli/cmd/pr.ts
|
|
155
|
-
packages/capix-code/src/cli/cmd/tui.ts
|
|
156
|
-
packages/capix-code/src/cli/network.ts
|
|
157
|
-
packages/capix-code/src/plugin/xai.ts
|
|
158
|
-
packages/capix-code/src/config/tui-migrate.ts
|
|
159
|
-
packages/capix-code/src/config/tui.ts
|
|
160
|
-
packages/capix-code/src/config/config.ts
|
|
161
|
-
packages/capix-code/src/config/paths.ts
|
|
162
|
-
packages/capix-code/src/config/managed.ts
|
|
163
|
-
packages/capix-code/src/temporary.ts
|
|
164
|
-
)
|
|
165
|
-
for relative in "${PRESENTATION_FILES[@]}"; do
|
|
166
|
-
file="$CAPIX_CODE_DIR/$relative"
|
|
167
|
-
test -f "$file" || { echo "ERROR: customer presentation source missing: $relative"; exit 1; }
|
|
168
|
-
perl -0pi.bak -e '
|
|
169
|
-
s/OpenCode/Capix Code/g;
|
|
170
|
-
s/OC \|/Capix |/g;
|
|
171
|
-
s/opencode --mini/capix-code --mini/g;
|
|
172
|
-
s/run opencode/run Capix Code/g;
|
|
173
|
-
s/opencode models/capix-code models/g;
|
|
174
|
-
s/opencode auth login/capix-code login/g;
|
|
175
|
-
s/opencode server/Capix Code server/g;
|
|
176
|
-
s/start opencode/start Capix Code/g;
|
|
177
|
-
s/path to start opencode/path to start Capix Code/g;
|
|
178
|
-
s/upgrade opencode/upgrade Capix Code/g;
|
|
179
|
-
s/opencode upgrade/Capix Code upgrade/g;
|
|
180
|
-
s/opencode session/Capix Code session/g;
|
|
181
|
-
s/Starting opencode/Starting Capix Code/g;
|
|
182
|
-
s/uninstall opencode/uninstall Capix Code/g;
|
|
183
|
-
s/running opencode/running Capix Code/g;
|
|
184
|
-
s/path to start opencode/path to start Capix Code/g;
|
|
185
|
-
s/Thank you for using opencode/Thank you for using Capix Code/g;
|
|
186
|
-
s/[.]scriptName\("opencode"\)/.scriptName("capix-code")/g;
|
|
187
|
-
s/startsWith\("opencode /startsWith("capix-code /g;
|
|
188
|
-
s/opencode[.]local/capix.local/g;
|
|
189
|
-
s/OPENCODE_SERVER/CAPIX_CODE_SERVER/g;
|
|
190
|
-
s/or '\''opencode'\''/or '\''capix'\''/g;
|
|
191
|
-
s/opencode does not support/Capix Code does not support/g;
|
|
192
|
-
s/opencode includes free models/Capix Code includes free models/g;
|
|
193
|
-
s/public opencode\.ai link/public capix.network link/g;
|
|
194
|
-
s/opencode\.ai link/capix.network link/g;
|
|
195
|
-
s/opencode\.json/capix-code.json/g;
|
|
196
|
-
s/~\/\.config\/opencode/~\/.config\/capix-code/g;
|
|
197
|
-
s/\.opencode\//.capix-code\//g;
|
|
198
|
-
s/"opencode"/"capix-code"/g;
|
|
199
|
-
s/opencode run/capix-code run/g;
|
|
200
|
-
s/opencode serve/Capix Code serve/g;
|
|
201
|
-
s/opencode --continue/capix-code --continue/g;
|
|
202
|
-
s/prevent OpenCode from reading/prevent Capix Code from reading/g;
|
|
203
|
-
s/to OpenCode/to Capix Code/g;
|
|
204
|
-
s/opencode\//capix-code\//g;
|
|
205
|
-
s/ai\.opencode\.managed/ai.capix-code.managed/g;
|
|
206
|
-
s/Support\/opencode/Support\/capix-code/g;
|
|
207
|
-
s/sst-dev\.opencode/sst-dev.capix/g;
|
|
208
|
-
s/\.opencode-version/.capix-code-version/g;
|
|
209
|
-
s/opencode\.ai\/tui\.json/capix.network\/tui.json/g;
|
|
210
|
-
s/opencode\.ai\/config\.json/capix.network\/config.json/g;
|
|
211
|
-
s/opencode status/capix-code status/g;
|
|
212
|
-
s/opencode debug/capix-code debug/g;
|
|
213
|
-
s/opencode\.mode/capix-code.mode/g;
|
|
214
|
-
' "$file"
|
|
215
|
-
rm -f "$file.bak"
|
|
216
|
-
done
|
|
217
|
-
echo " ✓ terminal title, splash, logo and customer command copy replaced"
|
|
218
|
-
|
|
219
|
-
# 6c. Fix split-span: <b>Open</b><b>Code</b> → <b>Capix Code</b>
|
|
220
|
-
for file in \
|
|
221
|
-
"$CAPIX_CODE_DIR/packages/tui/src/routes/session/sidebar.tsx" \
|
|
222
|
-
"$CAPIX_CODE_DIR/packages/tui/src/feature-plugins/sidebar/footer.tsx" \
|
|
223
|
-
"$CAPIX_CODE_DIR/packages/tui/src/feature-plugins/home/footer.tsx" \
|
|
224
|
-
"$CAPIX_CODE_DIR/packages/tui/src/feature-plugins/home/tips-view.tsx"; do
|
|
225
|
-
if [ -f "$file" ]; then
|
|
226
|
-
perl -0pi.bak -e 's/<b>Open<\/b>\s*\n\s*<b>Code<\/b>/<b>Capix Code<\/b>/gs' "$file"
|
|
227
|
-
perl -0pi.bak -e 's/OpenCode/Capix Code/g' "$file"
|
|
228
|
-
rm -f "$file.bak"
|
|
229
|
-
fi
|
|
230
|
-
done
|
|
231
|
-
echo " ✓ split-span branding fixed"
|
|
232
|
-
|
|
233
|
-
# 7. Install script references.
|
|
234
|
-
INSTALL="$CAPIX_CODE_DIR/install"
|
|
235
|
-
if [ -f "$INSTALL" ]; then
|
|
236
|
-
sed -i.bak \
|
|
237
|
-
-e 's|anomalyco/opencode|CapIX-Protocol/CapIX-Code|g' \
|
|
238
|
-
-e 's|opencode-ai|capix-code|g' \
|
|
239
|
-
"$INSTALL"
|
|
240
|
-
rm -f "$INSTALL.bak"
|
|
241
|
-
echo " ✓ install script updated"
|
|
242
|
-
fi
|
|
243
|
-
|
|
244
|
-
echo "✓ Rebrand complete. Only the binary name, config dirs, and env vars are rebranded."
|
|
245
|
-
echo " Runtime plugin/provider are staged by scripts/build.sh and verified fail-closed."
|
|
246
|
-
|
|
247
|
-
"$DIR/scripts/assert-upstream-brand.sh" "$CAPIX_CODE_DIR"
|
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Resolve a requested version ("latest", empty, or an explicit tag) to an
|
|
3
|
-
* immutable vMAJOR.MINOR.PATCH tag.
|
|
4
|
-
*
|
|
5
|
-
* "latest" is permitted ONLY by resolving it to a concrete immutable version
|
|
6
|
-
* before any download — never by blindly trusting mutable content. Resolution
|
|
7
|
-
* sources, in priority order:
|
|
8
|
-
*
|
|
9
|
-
* 1. CAPIX_STABLE_VERSION env (deterministic, offline, release-pinned)
|
|
10
|
-
* 2. a materialized manifest's stableVersion (single source of truth)
|
|
11
|
-
* 3. the GitHub releases API /releases/latest tag_name (--allow-network only)
|
|
12
|
-
*
|
|
13
|
-
* If none resolves, the script fails closed (exit 2) so the installer never
|
|
14
|
-
* proceeds with an unbounded/mutable version.
|
|
15
|
-
*
|
|
16
|
-
* Usage:
|
|
17
|
-
* node scripts/resolve-version.mjs [latest|<tag>] [--manifest manifest/release-manifest.json] [--allow-network]
|
|
18
|
-
*
|
|
19
|
-
* Prints the resolved immutable tag to stdout.
|
|
20
|
-
*/
|
|
21
|
-
import { readFileSync } from 'node:fs';
|
|
22
|
-
import { resolve } from 'node:path';
|
|
23
|
-
import { pathToFileURL } from 'node:url';
|
|
24
|
-
|
|
25
|
-
const SEMVER_TAG = /^v\d+\.\d+\.\d+$/;
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Resolve a version request. Pure (no IO except the optional fetch passed in)
|
|
29
|
-
* so it is unit-testable without a network.
|
|
30
|
-
*
|
|
31
|
-
* @param {string|null} request "latest", "" or null
|
|
32
|
-
* @param {object} ctx
|
|
33
|
-
* @param {string|undefined} ctx.envStable CAPIX_STABLE_VERSION value
|
|
34
|
-
* @param {object|undefined} ctx.manifest parsed manifest with stableVersion
|
|
35
|
-
* @param {(() => Promise<string|null>)|undefined} ctx.fetchLatestTag network resolver
|
|
36
|
-
* @returns {{tag:string, source:string}}
|
|
37
|
-
*/
|
|
38
|
-
export function resolveVersion(request, ctx) {
|
|
39
|
-
const requested = (request ?? '').trim();
|
|
40
|
-
// An explicit immutable tag is always accepted as-is.
|
|
41
|
-
if (requested && requested !== 'latest') {
|
|
42
|
-
if (!SEMVER_TAG.test(requested)) {
|
|
43
|
-
throw new Error(`invalid version '${requested}' (expected vMAJOR.MINOR.PATCH)`);
|
|
44
|
-
}
|
|
45
|
-
return { tag: requested, source: 'explicit' };
|
|
46
|
-
}
|
|
47
|
-
// latest / empty → resolve to an immutable tag.
|
|
48
|
-
if (ctx.envStable) {
|
|
49
|
-
if (!SEMVER_TAG.test(ctx.envStable)) {
|
|
50
|
-
throw new Error(`CAPIX_STABLE_VERSION is not a vMAJOR.MINOR.PATCH tag: '${ctx.envStable}'`);
|
|
51
|
-
}
|
|
52
|
-
return { tag: ctx.envStable, source: 'env' };
|
|
53
|
-
}
|
|
54
|
-
if (
|
|
55
|
-
ctx.manifest &&
|
|
56
|
-
typeof ctx.manifest.stableVersion === 'string' &&
|
|
57
|
-
SEMVER_TAG.test(ctx.manifest.stableVersion)
|
|
58
|
-
) {
|
|
59
|
-
return { tag: ctx.manifest.stableVersion, source: 'manifest' };
|
|
60
|
-
}
|
|
61
|
-
// Network resolver is invoked lazily by the caller (kept out of the pure
|
|
62
|
-
// path so tests never touch the network).
|
|
63
|
-
return null; // signals "caller may try network, else fail closed"
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/** Read a manifest file and return its parsed object, or null if unreadable. */
|
|
67
|
-
export function loadManifest(path) {
|
|
68
|
-
if (!path) return null;
|
|
69
|
-
try {
|
|
70
|
-
return JSON.parse(readFileSync(path, 'utf8'));
|
|
71
|
-
} catch {
|
|
72
|
-
return null;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/** GitHub releases API resolver. Returns the latest tag_name or null. */
|
|
77
|
-
async function githubLatest(repo) {
|
|
78
|
-
try {
|
|
79
|
-
const res = await fetch(`https://api.github.com/repos/${repo}/releases/latest`, {
|
|
80
|
-
headers: { Accept: 'application/vnd.github+json', 'User-Agent': 'capix-code-installer' },
|
|
81
|
-
});
|
|
82
|
-
if (!res.ok) return null;
|
|
83
|
-
const body = await res.json();
|
|
84
|
-
const tag = body?.tag_name;
|
|
85
|
-
return typeof tag === 'string' && SEMVER_TAG.test(tag) ? tag : null;
|
|
86
|
-
} catch {
|
|
87
|
-
return null;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
async function main() {
|
|
92
|
-
const argv = process.argv.slice(2);
|
|
93
|
-
const request = argv.find((a) => !a.startsWith('--')) ?? '';
|
|
94
|
-
const manifestPath = argv[argv.indexOf('--manifest') + 1];
|
|
95
|
-
const allowNetwork = argv.includes('--allow-network');
|
|
96
|
-
const repo = process.env.CAPIX_RELEASE_REPO ?? 'CapIX-Protocol/Capix-Code';
|
|
97
|
-
|
|
98
|
-
const ctx = {
|
|
99
|
-
envStable: process.env.CAPIX_STABLE_VERSION?.trim() || undefined,
|
|
100
|
-
manifest: loadManifest(manifestPath),
|
|
101
|
-
};
|
|
102
|
-
const resolved = resolveVersion(request, ctx);
|
|
103
|
-
if (resolved) {
|
|
104
|
-
process.stdout.write(resolved.tag);
|
|
105
|
-
process.exit(0);
|
|
106
|
-
}
|
|
107
|
-
if (allowNetwork) {
|
|
108
|
-
const tag = await githubLatest(repo);
|
|
109
|
-
if (tag) {
|
|
110
|
-
process.stdout.write(tag);
|
|
111
|
-
process.exit(0);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
console.error(
|
|
115
|
-
`ERROR: cannot resolve '${request || 'latest'}' to an immutable version. ` +
|
|
116
|
-
`Set CAPIX_STABLE_VERSION (e.g. v1.2.3) or pass --manifest <path> [--allow-network].`
|
|
117
|
-
);
|
|
118
|
-
process.exit(2);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) {
|
|
122
|
-
main().catch((err) => {
|
|
123
|
-
console.error(err?.message ? `ERROR: ${err.message}` : String(err));
|
|
124
|
-
process.exit(2);
|
|
125
|
-
});
|
|
126
|
-
}
|
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Release manifest validator.
|
|
3
|
-
*
|
|
4
|
-
* Guarantees that a production release-manifest.json cannot ship with
|
|
5
|
-
* unmaterialized TEMPLATE placeholders, zero-size artifacts, non-HTTPS URLs,
|
|
6
|
-
* or missing/invalid checksums. A manifest that fails any check is rejected
|
|
7
|
-
* with a concrete, actionable error — it must never be silently published.
|
|
8
|
-
*
|
|
9
|
-
* Usage:
|
|
10
|
-
* node scripts/validate-manifest.mjs <manifest.json> [--strict]
|
|
11
|
-
*
|
|
12
|
-
* Exit 0 = valid, 1 = invalid (message on stderr).
|
|
13
|
-
*/
|
|
14
|
-
import { readFileSync } from 'node:fs';
|
|
15
|
-
import { resolve } from 'node:path';
|
|
16
|
-
import { pathToFileURL } from 'node:url';
|
|
17
|
-
|
|
18
|
-
const SEMVER_TAG = /^v\d+\.\d+\.\d+$/;
|
|
19
|
-
const SEMVER = /^\d+\.\d+\.\d+$/;
|
|
20
|
-
const SHA256 = /^[0-9a-f]{64}$/;
|
|
21
|
-
const SHA1 = /^[0-9a-f]{40}$/;
|
|
22
|
-
const ISO8601 = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$/;
|
|
23
|
-
const REQUIRED_PLATFORMS = ['darwin-arm64', 'darwin-x64', 'linux-arm64', 'linux-x64', 'win32-x64'];
|
|
24
|
-
|
|
25
|
-
/** Deep scan: any string field equal to "TEMPLATE" is a release blocker. */
|
|
26
|
-
export function findTemplatePlaceholders(value, path = '$') {
|
|
27
|
-
const hits = [];
|
|
28
|
-
if (typeof value === 'string') {
|
|
29
|
-
if (value === 'TEMPLATE') hits.push(path);
|
|
30
|
-
} else if (Array.isArray(value)) {
|
|
31
|
-
for (let i = 0; i < value.length; i++)
|
|
32
|
-
hits.push(...findTemplatePlaceholders(value[i], `${path}[${i}]`));
|
|
33
|
-
} else if (value && typeof value === 'object') {
|
|
34
|
-
for (const [k, v] of Object.entries(value))
|
|
35
|
-
hits.push(...findTemplatePlaceholders(v, `${path}.${k}`));
|
|
36
|
-
}
|
|
37
|
-
return hits;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Validate a parsed manifest object. Returns an array of error strings
|
|
42
|
-
* (empty when valid). Pure function — safe to unit-test without IO.
|
|
43
|
-
*/
|
|
44
|
-
export function validateManifest(manifest, { strict = false } = {}) {
|
|
45
|
-
const errors = [];
|
|
46
|
-
|
|
47
|
-
if (!manifest || typeof manifest !== 'object' || Array.isArray(manifest)) {
|
|
48
|
-
return ['manifest must be a JSON object'];
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// 1. No unmaterialized TEMPLATE placeholders anywhere.
|
|
52
|
-
const placeholders = findTemplatePlaceholders(manifest);
|
|
53
|
-
for (const p of placeholders) errors.push(`${p} is an unmaterialized TEMPLATE placeholder`);
|
|
54
|
-
|
|
55
|
-
// 2. Top-level identity.
|
|
56
|
-
if (typeof manifest.id !== 'string' || !manifest.id)
|
|
57
|
-
errors.push('$.id must be a non-empty string');
|
|
58
|
-
if (manifest.immutable !== true)
|
|
59
|
-
errors.push('$.immutable must be true for a publishable manifest');
|
|
60
|
-
if (typeof manifest.createdAt !== 'string' || !ISO8601.test(manifest.createdAt)) {
|
|
61
|
-
errors.push('$.createdAt must be an ISO-8601 timestamp');
|
|
62
|
-
}
|
|
63
|
-
if (typeof manifest.stableVersion !== 'string' || !SEMVER_TAG.test(manifest.stableVersion)) {
|
|
64
|
-
errors.push(
|
|
65
|
-
'$.stableVersion must be an immutable vMAJOR.MINOR.PATCH tag (single source of truth)'
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// 3. Component provenance (launcher/opencode/plugin/provider).
|
|
70
|
-
for (const comp of ['launcher', 'opencode', 'plugin', 'provider']) {
|
|
71
|
-
const node = manifest[comp];
|
|
72
|
-
if (!node || typeof node !== 'object') {
|
|
73
|
-
errors.push(`$.${comp} must be an object`);
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
if (typeof node.sourceSha !== 'string' || !SHA1.test(node.sourceSha)) {
|
|
77
|
-
errors.push(`$.${comp}.sourceSha must be a 40-character lowercase hex commit SHA`);
|
|
78
|
-
}
|
|
79
|
-
if (typeof node.version !== 'string' || !SEMVER.test(node.version)) {
|
|
80
|
-
errors.push(`$.${comp}.version must be MAJOR.MINOR.PATCH`);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// 4. Platforms — every required platform present with a real artifact.
|
|
85
|
-
if (!manifest.platforms || typeof manifest.platforms !== 'object') {
|
|
86
|
-
errors.push('$.platforms must be an object');
|
|
87
|
-
} else {
|
|
88
|
-
for (const plat of REQUIRED_PLATFORMS) {
|
|
89
|
-
const entry = manifest.platforms[plat];
|
|
90
|
-
if (!entry || typeof entry !== 'object') {
|
|
91
|
-
errors.push(`$.platforms.${plat} is missing`);
|
|
92
|
-
continue;
|
|
93
|
-
}
|
|
94
|
-
if (typeof entry.url !== 'string' || !/^https:\/\//.test(entry.url)) {
|
|
95
|
-
errors.push(`$.platforms.${plat}.url must be an https URL`);
|
|
96
|
-
}
|
|
97
|
-
if (typeof entry.sha256 !== 'string' || !SHA256.test(entry.sha256)) {
|
|
98
|
-
errors.push(`$.platforms.${plat}.sha256 must be a 64-character lowercase hex SHA-256`);
|
|
99
|
-
}
|
|
100
|
-
if (
|
|
101
|
-
typeof entry.sizeBytes !== 'number' ||
|
|
102
|
-
!Number.isFinite(entry.sizeBytes) ||
|
|
103
|
-
entry.sizeBytes <= 0
|
|
104
|
-
) {
|
|
105
|
-
errors.push(`$.platforms.${plat}.sizeBytes must be a positive number`);
|
|
106
|
-
}
|
|
107
|
-
// signatureUrl may be null for unsigned artifacts, but must not be TEMPLATE.
|
|
108
|
-
if (
|
|
109
|
-
entry.signatureUrl !== null &&
|
|
110
|
-
(typeof entry.signatureUrl !== 'string' || !/^https:\/\//.test(entry.signatureUrl))
|
|
111
|
-
) {
|
|
112
|
-
errors.push(`$.platforms.${plat}.signatureUrl must be an https URL or null`);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
// Reject unknown platforms that snuck in (keeps the manifest honest).
|
|
116
|
-
for (const plat of Object.keys(manifest.platforms)) {
|
|
117
|
-
if (!REQUIRED_PLATFORMS.includes(plat)) {
|
|
118
|
-
errors.push(`$.platforms.${plat} is not a recognized platform`);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// 5. Supply-chain refs — must be present and materialized (or explicitly null
|
|
124
|
-
// with a documented reason, in non-strict mode).
|
|
125
|
-
for (const ref of ['sbomRef', 'provenanceRef', 'thirdPartyNoticesRef']) {
|
|
126
|
-
const v = manifest[ref];
|
|
127
|
-
if (typeof v !== 'string' || !v) errors.push(`$.${ref} must be a non-empty reference`);
|
|
128
|
-
}
|
|
129
|
-
if (strict) {
|
|
130
|
-
if (typeof manifest.signatureRef !== 'string' || !manifest.signatureRef) {
|
|
131
|
-
errors.push('$.signatureRef must be present in strict mode (signed releases)');
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
return errors;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
function main() {
|
|
139
|
-
const [file, ...rest] = process.argv.slice(2);
|
|
140
|
-
if (!file) {
|
|
141
|
-
console.error('usage: node scripts/validate-manifest.mjs <manifest.json> [--strict]');
|
|
142
|
-
process.exit(2);
|
|
143
|
-
}
|
|
144
|
-
const strict = rest.includes('--strict');
|
|
145
|
-
let parsed;
|
|
146
|
-
try {
|
|
147
|
-
parsed = JSON.parse(readFileSync(file, 'utf8'));
|
|
148
|
-
} catch (err) {
|
|
149
|
-
console.error(`cannot read/parse manifest ${file}: ${err.message}`);
|
|
150
|
-
process.exit(1);
|
|
151
|
-
}
|
|
152
|
-
const errors = validateManifest(parsed, { strict });
|
|
153
|
-
if (errors.length) {
|
|
154
|
-
for (const e of errors) console.error(`✗ ${e}`);
|
|
155
|
-
console.error(
|
|
156
|
-
`manifest ${file} is not publishable (${errors.length} blocker${errors.length === 1 ? '' : 's'})`
|
|
157
|
-
);
|
|
158
|
-
process.exit(1);
|
|
159
|
-
}
|
|
160
|
-
console.log(`✓ manifest ${file} is publishable`);
|
|
161
|
-
process.exit(0);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Run as CLI only when invoked directly, not when imported by tests.
|
|
165
|
-
if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) {
|
|
166
|
-
main();
|
|
167
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
const runtime = await import('@capix/runtime-provider');
|
|
2
|
-
if (typeof runtime.capix !== 'function') {
|
|
3
|
-
throw new Error('@capix/runtime-provider does not export the capix provider factory');
|
|
4
|
-
}
|
|
5
|
-
const model = runtime.capix('auto');
|
|
6
|
-
if (model.specificationVersion !== 'v2' || model.provider !== 'capix') {
|
|
7
|
-
throw new Error('@capix/runtime-provider does not implement the pinned LanguageModelV2 contract');
|
|
8
|
-
}
|
|
9
|
-
console.log('@capix/runtime-provider: bundled provider contract verified');
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto';
|
|
2
|
-
import { readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
3
|
-
import { basename, dirname, join } from 'node:path';
|
|
4
|
-
import { execFileSync } from 'node:child_process';
|
|
5
|
-
|
|
6
|
-
const [version, platform, arch, archive] = process.argv.slice(2);
|
|
7
|
-
if (!version || !platform || !arch || !archive) process.exit(2);
|
|
8
|
-
|
|
9
|
-
const shaPattern = /^[0-9a-f]{40}$/i;
|
|
10
|
-
const githubSha = process.env.GITHUB_SHA?.trim();
|
|
11
|
-
let sourceSha;
|
|
12
|
-
if (githubSha) {
|
|
13
|
-
if (!shaPattern.test(githubSha)) throw new Error('GITHUB_SHA must be a 40-character hexadecimal commit SHA');
|
|
14
|
-
sourceSha = githubSha.toLowerCase();
|
|
15
|
-
} else {
|
|
16
|
-
if (process.env.CI) throw new Error('GITHUB_SHA is required when generating release metadata in CI');
|
|
17
|
-
sourceSha = execFileSync('git', ['rev-parse', 'HEAD'], {
|
|
18
|
-
cwd: new URL('..', import.meta.url).pathname,
|
|
19
|
-
encoding: 'utf8',
|
|
20
|
-
}).trim();
|
|
21
|
-
if (!shaPattern.test(sourceSha)) throw new Error('git rev-parse returned an invalid source SHA');
|
|
22
|
-
}
|
|
23
|
-
const entry = {
|
|
24
|
-
schemaVersion: 1,
|
|
25
|
-
product: 'capix-code',
|
|
26
|
-
version,
|
|
27
|
-
platform: `${platform}-${arch}`,
|
|
28
|
-
artifact: basename(archive),
|
|
29
|
-
sha256: createHash('sha256').update(readFileSync(archive)).digest('hex'),
|
|
30
|
-
sizeBytes: statSync(archive).size,
|
|
31
|
-
sourceSha,
|
|
32
|
-
signed: false,
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
writeFileSync(
|
|
36
|
-
join(dirname(archive), `capix-code-${version}-${platform}-${arch}.release.json`),
|
|
37
|
-
`${JSON.stringify(entry, null, 2)}\n`
|
|
38
|
-
);
|