Skip to content

Commit cfc60a2

Browse files
feat(cli): wire runtime, security, ux, protocol and plugin surfaces
Implement the capability lots from the feature audit across seven areas, then wire them together so declared surfaces reach a real executor. Runtime and protocols: server-owned tool results are never re-executed locally, incomplete streams and unsuccessful terminal events never report success, sessions are origin-bound with persistent multi-turn JSON-RPC and event-independent deadlines. MCP gains async stdio plus Streamable HTTP with OAuth keyring reuse; the app server shares one live-session registry with independent WebSocket and SSE subscribers and returns 501 for unsupported compatibility endpoints. Execution security: one authorization gate covers direct, Batch, child and plugin paths with exact single-use approvals, workspace confinement, traversal and symlink checks, final environment filtering, bounded capture and controlled process-group cancellation. Interactive sessions now honor --sandbox, --ask-for-approval, --full-auto, --dangerously-bypass and --add-dir instead of ignoring them, and the TUI and agent executors build an explicit authorized context. Policy evaluation and process-group termination run in-process; the Linux sandbox wrapper is the binary itself when no sibling helper is installed, and an unregistered host still fails closed. UX and sessions: a shared local store backs persisted new, resume and fork, durable rename, favorite and protection, JSON and YAML export and import, and conversation-scoped undo and redo, with unified dispatch across CLI and TUI. Plugins gain an explicit ABI, activation and packaging contract with a Node runtime. Platform work aligns the opentelemetry family, corrects release asset names, and makes installers validate URLs, architecture, checksums and bounds with staging and rollback. Config overrides for sandbox mode and writable roots are now applied, shutdown no longer deletes other sessions' files, and --debug writes a content-free local journal instead of a full trace dump. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
1 parent b79c8ef commit cfc60a2

255 files changed

Lines changed: 30375 additions & 17344 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,23 @@ jobs:
5454
retention-days: 14
5555

5656
version-check:
57-
name: CLI Version Check
58-
runs-on: ubuntu-latest
57+
name: CLI Version and Distribution (${{ matrix.os }})
58+
runs-on: ${{ matrix.os }}
59+
strategy:
60+
fail-fast: false
61+
matrix:
62+
os: [ubuntu-latest, macos-latest, macos-15-intel, windows-latest]
5963
steps:
6064
- uses: actions/checkout@v7
65+
- uses: actions/setup-python@v5
66+
with:
67+
python-version: '3.12'
68+
- run: python -m pip install -r scripts/readiness/requirements.txt
6169
- name: Verify CLI version consistency
70+
shell: bash
6271
run: ./scripts/check-cli-version.sh
72+
- name: Verify installer fixtures and package contracts
73+
run: python -B -m unittest discover -s scripts/readiness -p test_distribution.py -v
6374
- name: Verify Windows installer arch detection
6475
shell: pwsh
6576
run: ./scripts/test-install-ps1.ps1

.github/workflows/homebrew.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ jobs:
3131
with:
3232
formula-name: cortex
3333
homebrew-tap: CortexLM/homebrew-tap
34-
download-url: https://github.com/${{ github.repository }}/releases/download/${{ steps.tag.outputs.tag }}/cortex-macos-arm64.tar.gz
34+
download-url: https://github.com/${{ github.repository }}/releases/download/${{ steps.tag.outputs.tag }}/cortex-cli-macos-arm64.tar.gz
3535
commit-message: |
3636
{{formulaName}} {{version}}
3737

.github/workflows/publish-r2.yml

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,8 @@ jobs:
129129
EXPECTED_ARTIFACTS=(
130130
"cortex-cli-linux-x64"
131131
"cortex-cli-linux-arm64"
132+
"cortex-cli-linux-x64-static"
133+
"cortex-cli-linux-arm64-static"
132134
"cortex-cli-macos-x64"
133135
"cortex-cli-macos-arm64"
134136
"cortex-cli-windows-x64"
@@ -152,11 +154,11 @@ jobs:
152154
run: |
153155
mkdir -p dist
154156
155-
for mapping in linux-x86_64:cortex-cli-linux-x64 linux-aarch64:cortex-cli-linux-arm64 darwin-x86_64:cortex-cli-macos-x64 darwin-aarch64:cortex-cli-macos-arm64; do
157+
for mapping in linux-x86_64:cortex-cli-linux-x64 linux-aarch64:cortex-cli-linux-arm64 linux-x86_64-musl:cortex-cli-linux-x64-static linux-aarch64-musl:cortex-cli-linux-arm64-static darwin-x86_64:cortex-cli-macos-x64 darwin-aarch64:cortex-cli-macos-arm64; do
156158
IFS=':' read -r platform artifact <<< "$mapping"
157159
158-
ARCHIVE=$(find "artifacts/$artifact" -name "*.tar.gz" | head -1)
159-
if [ -z "$ARCHIVE" ]; then
160+
ARCHIVE="artifacts/$artifact/$artifact.tar.gz"
161+
if [ ! -f "$ARCHIVE" ]; then
160162
echo "ERROR: No .tar.gz found in artifacts/$artifact"
161163
ls -la "artifacts/$artifact/"
162164
exit 1
@@ -171,8 +173,8 @@ jobs:
171173
for mapping in windows-x86_64:cortex-cli-windows-x64; do
172174
IFS=':' read -r platform artifact <<< "$mapping"
173175
174-
ARCHIVE=$(find "artifacts/$artifact" -name "*.zip" | head -1)
175-
if [ -z "$ARCHIVE" ]; then
176+
ARCHIVE="artifacts/$artifact/$artifact.zip"
177+
if [ ! -f "$ARCHIVE" ]; then
176178
echo "ERROR: No .zip found in artifacts/$artifact"
177179
ls -la "artifacts/$artifact/"
178180
exit 1
@@ -218,6 +220,8 @@ jobs:
218220
--arg release_notes "$RELEASE_NOTES" \
219221
--argjson linux_x86_64 "$(asset_obj linux-x86_64 cortex.tar.gz)" \
220222
--argjson linux_aarch64 "$(asset_obj linux-aarch64 cortex.tar.gz)" \
223+
--argjson linux_x86_64_musl "$(asset_obj linux-x86_64-musl cortex.tar.gz)" \
224+
--argjson linux_aarch64_musl "$(asset_obj linux-aarch64-musl cortex.tar.gz)" \
221225
--argjson darwin_x86_64 "$(asset_obj darwin-x86_64 cortex.tar.gz)" \
222226
--argjson darwin_aarch64 "$(asset_obj darwin-aarch64 cortex.tar.gz)" \
223227
--argjson windows_x86_64 "$(asset_obj windows-x86_64 cortex.zip)" \
@@ -230,6 +234,8 @@ jobs:
230234
assets: {
231235
"linux-x86_64": $linux_x86_64,
232236
"linux-aarch64": $linux_aarch64,
237+
"linux-x86_64-musl": $linux_x86_64_musl,
238+
"linux-aarch64-musl": $linux_aarch64_musl,
233239
"darwin-x86_64": $darwin_x86_64,
234240
"darwin-aarch64": $darwin_aarch64,
235241
"windows-x86_64": $windows_x86_64
@@ -271,7 +277,7 @@ jobs:
271277
rclone copyto "$src" "$REMOTE/$dest" --progress
272278
}
273279
274-
for platform in linux-x86_64 linux-aarch64 darwin-x86_64 darwin-aarch64; do
280+
for platform in linux-x86_64 linux-aarch64 linux-x86_64-musl linux-aarch64-musl darwin-x86_64 darwin-aarch64; do
275281
echo "Uploading $platform..."
276282
r2_copy "dist/$platform/cortex.tar.gz" "v1/assets/$platform/$VERSION/cortex.tar.gz"
277283
r2_copy "dist/$platform/cortex.tar.gz.sha256" "v1/assets/$platform/$VERSION/cortex.tar.gz.sha256"

.github/workflows/release.yml

Lines changed: 29 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ jobs:
237237
# =========================================================================
238238
- name: Build release binary (dynamic)
239239
if: matrix.static == false
240-
run: cargo +nightly build --release --target ${{ matrix.target }} -p cortex-cli
240+
run: cargo +nightly build --locked --release --target ${{ matrix.target }} -p cortex-cli
241241
env:
242242
RUSTFLAGS: ""
243243
CARGO_PROFILE_RELEASE_LTO: thin
@@ -246,7 +246,7 @@ jobs:
246246
if: matrix.target == 'x86_64-unknown-linux-musl'
247247
run: |
248248
# Build without audio feature (rodio/alsa) for musl - uses terminal bell fallback
249-
cargo +nightly build --release --target ${{ matrix.target }} -p cortex-cli --no-default-features --features cortex-tui
249+
cargo +nightly build --locked --release --target ${{ matrix.target }} -p cortex-cli --no-default-features --features cortex-tui
250250
env:
251251
RUSTFLAGS: "-Zthreads=32 -C target-feature=+crt-static"
252252
CARGO_PROFILE_RELEASE_LTO: thin
@@ -259,7 +259,7 @@ jobs:
259259
run: |
260260
# Build without audio feature (rodio/alsa) for musl - uses terminal bell fallback
261261
# Using native musl-gcc since we're on an ARM64 runner building for ARM64 target
262-
cargo +nightly build --release --target ${{ matrix.target }} -p cortex-cli --no-default-features --features cortex-tui
262+
cargo +nightly build --locked --release --target ${{ matrix.target }} -p cortex-cli --no-default-features --features cortex-tui
263263
env:
264264
RUSTFLAGS: "-Zthreads=32 -C target-feature=+crt-static"
265265
CARGO_PROFILE_RELEASE_LTO: thin
@@ -273,22 +273,17 @@ jobs:
273273
- name: Verify static binary
274274
if: matrix.static == true
275275
run: |
276-
echo "=== Verifying static binary ==="
276+
set -euo pipefail
277277
BINARY="target/${{ matrix.target }}/release/${{ env.BINARY_NAME }}"
278278
file "$BINARY"
279-
echo ""
280-
echo "=== Checking dynamic dependencies ==="
281-
# For a truly static binary, ldd should report "not a dynamic executable"
282-
# or show no dynamic dependencies
283-
if ldd "$BINARY" 2>&1 | grep -q "not a dynamic executable\|statically linked"; then
284-
echo "✅ Binary is statically linked"
285-
else
286-
echo "⚠️ Binary has some dynamic dependencies (expected for musl with linux-keyutils):"
287-
ldd "$BINARY" 2>&1 || true
279+
# readelf is deterministic for static PIE too; ldd text is not.
280+
readelf -l "$BINARY" > program-headers.txt
281+
readelf -d "$BINARY" > dynamic-headers.txt
282+
if grep -q INTERP program-headers.txt || grep -q NEEDED dynamic-headers.txt; then
283+
echo "::error::musl artifact is dynamically linked"
284+
exit 1
288285
fi
289-
echo ""
290-
echo "=== Binary size ==="
291-
ls -lh "$BINARY"
286+
"$BINARY" --version
292287
293288
- name: Prepare artifact (Unix)
294289
if: runner.os != 'Windows'
@@ -313,7 +308,7 @@ jobs:
313308
path: |
314309
${{ matrix.artifact }}.tar.gz
315310
${{ matrix.artifact }}.zip
316-
if-no-files-found: ignore
311+
if-no-files-found: error
317312

318313
# ==========================================================================
319314
# Create GitHub Release (lightweight - 4 vCPU)
@@ -341,14 +336,22 @@ jobs:
341336

342337
- name: Flatten and generate checksums
343338
run: |
344-
cd artifacts
345-
find . -type f \( -name "*.tar.gz" -o -name "*.zip" -o -name "*.dmg" -o -name "*.msi" -o -name "*.AppImage" \) -exec mv {} . \;
346-
rm -rf cortex-*/ || true
347-
sha256sum * > checksums-sha256.txt 2>/dev/null || true
348-
echo "=== Release artifacts ==="
349-
ls -la
350-
echo "=== Checksums ==="
351-
cat checksums-sha256.txt
339+
set -euo pipefail
340+
mkdir release-assets
341+
for artifact in artifacts/*; do
342+
[ -d "$artifact" ] || { echo "Unexpected artifact layout"; exit 1; }
343+
name=$(basename "$artifact")
344+
case "$name" in
345+
cortex-cli-windows-x64) ext=zip ;;
346+
cortex-cli-macos-x64|cortex-cli-macos-arm64|cortex-cli-linux-x64|cortex-cli-linux-arm64|cortex-cli-linux-x64-static|cortex-cli-linux-arm64-static) ext=tar.gz ;;
347+
*) echo "Unexpected artifact: $name"; exit 1 ;;
348+
esac
349+
cp "$artifact/$name.$ext" release-assets/
350+
done
351+
[ "$(find release-assets -type f | wc -l)" -eq 7 ]
352+
cd release-assets
353+
sha256sum -- *.tar.gz *.zip > checksums-sha256.txt
354+
sha256sum --check checksums-sha256.txt
352355
353356
- name: Determine release name
354357
id: release_name
@@ -370,7 +373,7 @@ jobs:
370373
prerelease: ${{ contains(needs.prepare.outputs.version, '-') }}
371374
generate_release_notes: true
372375
files: |
373-
artifacts/*
376+
release-assets/*
374377
env:
375378
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
376379

.github/workflows/winget.yml

Lines changed: 15 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -40,24 +40,13 @@ jobs:
4040
$baseUrl = "https://github.com/${{ github.repository }}/releases/download/$tag"
4141
4242
# Download and hash x64
43-
$x64Url = "$baseUrl/cortex-windows-x64.zip"
43+
$x64Url = "$baseUrl/cortex-cli-windows-x64.zip"
4444
Invoke-WebRequest -Uri $x64Url -OutFile "cortex-x64.zip" -ErrorAction Stop
4545
$x64Hash = (Get-FileHash -Path "cortex-x64.zip" -Algorithm SHA256).Hash
4646
47-
# Download and hash arm64
48-
$arm64Url = "$baseUrl/cortex-windows-arm64.zip"
49-
try {
50-
Invoke-WebRequest -Uri $arm64Url -OutFile "cortex-arm64.zip" -ErrorAction Stop
51-
$arm64Hash = (Get-FileHash -Path "cortex-arm64.zip" -Algorithm SHA256).Hash
52-
} catch {
53-
$arm64Hash = ""
54-
$arm64Url = ""
55-
}
56-
47+
# ARM64 is intentionally absent from the release matrix.
5748
echo "x64_url=$x64Url" >> $env:GITHUB_OUTPUT
5849
echo "x64_hash=$x64Hash" >> $env:GITHUB_OUTPUT
59-
echo "arm64_url=$arm64Url" >> $env:GITHUB_OUTPUT
60-
echo "arm64_hash=$arm64Hash" >> $env:GITHUB_OUTPUT
6150
6251
- name: Create winget manifest directory
6352
shell: pwsh
@@ -99,15 +88,6 @@ jobs:
9988
InstallerSha256: ${{ steps.release.outputs.x64_hash }}
10089
"@
10190
102-
if ("${{ steps.release.outputs.arm64_hash }}" -ne "") {
103-
$installers += @"
104-
105-
- Architecture: arm64
106-
InstallerUrl: ${{ steps.release.outputs.arm64_url }}
107-
InstallerSha256: ${{ steps.release.outputs.arm64_hash }}
108-
"@
109-
}
110-
11191
$installers += @"
11292
11393
ManifestType: installer
@@ -125,11 +105,11 @@ jobs:
125105
PackageLocale: en-US
126106
Publisher: CortexLM
127107
PublisherUrl: https://github.com/CortexLM
128-
PublisherSupportUrl: https://github.com/CortexLM/cortex-cli/issues
108+
PublisherSupportUrl: https://github.com/CortexLM/cli/issues
129109
PackageName: Cortex CLI
130-
PackageUrl: https://github.com/CortexLM/cortex-cli
110+
PackageUrl: https://github.com/CortexLM/cli
131111
License: Apache-2.0
132-
LicenseUrl: https://github.com/CortexLM/cortex-cli/blob/master/LICENSE
112+
LicenseUrl: https://github.com/CortexLM/cli/blob/main/LICENSE
133113
ShortDescription: Cortex CLI - A modern AI coding agent
134114
Description: Cortex is a powerful command-line AI coding assistant that helps developers write, debug, and maintain code more efficiently.
135115
Tags:
@@ -143,20 +123,26 @@ jobs:
143123
"@
144124
$content | Out-File -FilePath "${{ env.MANIFEST_DIR }}/${{ env.PACKAGE_ID }}.locale.en-US.yaml" -Encoding utf8
145125
146-
- name: Install wingetcreate
126+
- name: Require provisioned package tooling
147127
shell: pwsh
148128
run: |
149-
iwr https://aka.ms/wingetcreate/latest -OutFile wingetcreate.exe
129+
# Do not execute an unpinned executable downloaded at publish time.
130+
# The Windows runner image must provision the reviewed tooling.
131+
$tool = Get-Command wingetcreate.exe -CommandType Application -ErrorAction Stop
132+
$signature = Get-AuthenticodeSignature -FilePath $tool.Source
133+
if ($signature.Status -ne 'Valid' -or $signature.SignerCertificate.Subject -notmatch 'O=Microsoft Corporation') {
134+
throw 'wingetcreate must have a valid Microsoft signature'
135+
}
150136
151137
- name: Validate manifests
152138
shell: pwsh
153139
run: |
154-
.\wingetcreate.exe validate ${{ env.MANIFEST_DIR }}
140+
wingetcreate.exe validate ${{ env.MANIFEST_DIR }}
155141
156142
- name: Submit to winget-pkgs
157143
shell: pwsh
158144
run: |
159-
.\wingetcreate.exe submit ${{ env.MANIFEST_DIR }} --token ${{ secrets.WINGET_PAT }}
145+
wingetcreate.exe submit ${{ env.MANIFEST_DIR }} --token $env:WINGET_PAT
160146
env:
161147
WINGET_PAT: ${{ secrets.WINGET_PAT }}
162148

0 commit comments

Comments
 (0)