diff --git a/.github/workflows/cli-tui-tests.yml b/.github/workflows/cli-tui-tests.yml deleted file mode 100644 index 379078031f..0000000000 --- a/.github/workflows/cli-tui-tests.yml +++ /dev/null @@ -1,83 +0,0 @@ -name: CLI TUI Tests - -on: - pull_request: - branches: - - main - workflow_dispatch: - workflow_call: - -permissions: - contents: read - -jobs: - cli-tui-tests: - name: CLI TUI Tests - runs-on: ubuntu-latest - timeout-minutes: 15 - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: 22 - - - name: Install dependencies - run: npm ci - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Build CLI - run: npm run cli:build - - - name: Run TUI Tests - id: tui_tests - run: | - npm run test:e2e:cli:tui 2>&1 | tee tui-test-output.log - exit_code=${PIPESTATUS[0]} - echo "tui_exit_code=$exit_code" >> $GITHUB_OUTPUT - exit $exit_code - - - name: Write failure summary - if: always() && steps.tui_tests.outcome != 'success' && steps.tui_tests.outcome != 'skipped' - run: | - echo "## āŒ CLI TUI Tests Failed" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Step outcome:** \`${{ steps.tui_tests.outcome }}\`" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### Test Output" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - if [ -f tui-test-output.log ]; then - cat tui-test-output.log >> $GITHUB_STEP_SUMMARY - else - echo "(no test output captured — process may have been killed before output was flushed)" >> $GITHUB_STEP_SUMMARY - fi - echo '```' >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### Debugging" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "- **TUI traces** are attached as artifacts below — download and inspect them to see terminal state at the point of failure." >> $GITHUB_STEP_SUMMARY - echo "- **To view a trace replay/Run a TUI Trace: ** run \`npx tui-test show-trace path/to/trace/file\` in your terminal" >> $GITHUB_STEP_SUMMARY - echo "- **Full test log** is also attached as an artifact." >> $GITHUB_STEP_SUMMARY - echo "- Tests run with \`retries: 2\` so any failure shown is a consistent failure, not a flake." >> $GITHUB_STEP_SUMMARY - - - name: Upload TUI traces - if: always() && steps.tui_tests.outcome != 'success' && steps.tui_tests.outcome != 'skipped' - uses: actions/upload-artifact@v4 - with: - name: tui-test-traces - path: tests/e2e/cli/tui-traces/ - retention-days: 14 - if-no-files-found: warn - - - name: Upload test log - if: always() && steps.tui_tests.outcome != 'success' && steps.tui_tests.outcome != 'skipped' - uses: actions/upload-artifact@v4 - with: - name: tui-test-log - path: tui-test-output.log - retention-days: 14 - if-no-files-found: warn diff --git a/.github/workflows/cline-evals-regression.yml b/.github/workflows/cline-evals-regression.yml index 276e0f041a..11b170b0e1 100644 --- a/.github/workflows/cline-evals-regression.yml +++ b/.github/workflows/cline-evals-regression.yml @@ -1,21 +1,14 @@ name: Smoke Tests +# Temporarily disabled: this workflow built and linked the legacy CLI +# (`cd cli && npm install && npm run build && npm link`) before running the +# smoke-test scenarios. The legacy CLI publish chain has been retired in +# favor of the SDK CLI at `sdk/apps/cli/`. The scenarios under +# `evals/smoke-tests/scenarios/` are CLI-agnostic and should be re-enabled +# once the build step is repointed at the new SDK CLI. Until then, only +# manual `workflow_dispatch` runs are accepted (and will fail in their +# current form). on: - push: - branches: [main] - paths: - - 'src/core/**' - - 'src/shared/**' - - 'proto/**' - - 'evals/**' - - '.github/workflows/cline-evals-regression.yml' - pull_request: - paths: - - 'src/core/**' - - 'src/shared/**' - - 'proto/**' - - 'evals/**' - - '.github/workflows/cline-evals-regression.yml' workflow_dispatch: permissions: diff --git a/.github/workflows/npm-main.yaml b/.github/workflows/npm-main.yaml deleted file mode 100644 index 27bde4dadd..0000000000 --- a/.github/workflows/npm-main.yaml +++ /dev/null @@ -1,111 +0,0 @@ -name: Publish NPM Release - -on: - workflow_call: - inputs: - confirm_publish: - description: 'Type "publish" to confirm you want to publish to NPM' - required: true - type: string - -permissions: - contents: write # Required for pushing tags - id-token: write # Required for npm trusted publishing (OIDC) - checks: write # Required by test workflow - pull-requests: write # Required by test workflow - -jobs: - test: - uses: ./.github/workflows/test.yml - - publish-npm-release: - needs: test - name: Publish Cline CLI to NPM - if: github.repository == 'cline/cline' && github.ref == 'refs/heads/main' && inputs.confirm_publish == 'publish' - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: "24.x" - registry-url: "https://registry.npmjs.org" - - - name: Install root dependencies and CLI dependencies - run: npm ci --include=optional # this will also install cli deps because "cli" in included in root package.json workspaces field - - - name: Generate Protos - run: npm run protos - - - name: Read release version - id: version - run: | - # Read version from cli/package.json - VERSION=$(node -p "require('./cli/package.json').version") - echo "Release version: $VERSION" - echo "version=$VERSION" >> $GITHUB_OUTPUT - - - name: Build standalone NPM package - env: - TELEMETRY_SERVICE_API_KEY: ${{ secrets.TELEMETRY_SERVICE_API_KEY }} - ERROR_SERVICE_API_KEY: ${{ secrets.ERROR_SERVICE_API_KEY }} - CLINE_ENVIRONMENT: production - OTEL_TELEMETRY_ENABLED: "1" - OTEL_METRICS_EXPORTER: otlp - OTEL_LOGS_EXPORTER: otlp - OTEL_EXPORTER_OTLP_PROTOCOL: ${{ secrets.OTEL_EXPORTER_OTLP_PROTOCOL }} - OTEL_EXPORTER_OTLP_ENDPOINT: ${{ secrets.OTEL_EXPORTER_OTLP_ENDPOINT }} - OTEL_EXPORTER_OTLP_HEADERS: ${{ secrets.OTEL_EXPORTER_OTLP_HEADERS }} - run: node scripts/package-npm.mjs - - - name: Verify build output - run: | - echo "Checking dist-standalone directory..." - ls -la dist-standalone/ - - echo "Verifying CLI binaries..." - ls -lh cli/bin/cline-* || echo "Warning: CLI binaries not found" - - echo "Checking package.json in dist-standalone..." - cat dist-standalone/package.json | grep version - - - name: Publish to NPM with latest tag - run: | - echo "Publishing version ${{ steps.version.outputs.version }} to NPM with tag 'latest'..." - cd dist-standalone - npm publish --tag latest --access public - - - name: Tag release - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git tag "v${{ steps.version.outputs.version }}-cli" - git push origin "v${{ steps.version.outputs.version }}-cli" - - - name: Summary - run: | - echo "āœ… Successfully published cline@${{ steps.version.outputs.version }} to NPM with tag 'latest'" - echo "" - echo "šŸ“¦ Install with: npm install -g cline" - echo "šŸ”— NPM: https://www.npmjs.com/package/cline/v/${{ steps.version.outputs.version }}" - - - name: Post release to Slack - uses: slackapi/slack-github-action@v3.0.1 - with: - method: chat.postMessage - token: ${{ secrets.SLACK_RELEASE_BOT_TOKEN }} - payload: | - channel: "C0APVKGGZFC" - text: "Cline CLI v${{ steps.version.outputs.version }}" - blocks: - - type: "section" - text: - type: "mrkdwn" - text: "*Cline CLI v${{ steps.version.outputs.version }}*" - - type: "context" - elements: - - type: "mrkdwn" - text: "" diff --git a/.github/workflows/npm-nightly.yaml b/.github/workflows/npm-nightly.yaml deleted file mode 100644 index 1ac0fafb17..0000000000 --- a/.github/workflows/npm-nightly.yaml +++ /dev/null @@ -1,136 +0,0 @@ -name: Publish NPM Nightly - -on: - workflow_call: - inputs: - force_publish: - description: "Force publish even if there are no commits in the last 24 hours" - required: false - type: boolean - default: false - -permissions: - contents: read - id-token: write # Required for npm trusted publishing (OIDC) - checks: write # Required by test workflow - pull-requests: write # Required by test workflow - -jobs: - test: - uses: ./.github/workflows/test.yml - - publish-npm-nightly: - needs: test - name: Publish Cline CLI (Nightly) to NPM - if: github.repository == 'cline/cline' && github.ref == 'refs/heads/main' - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Check for recent commits - id: check_commits - env: - FORCE_PUBLISH: ${{ inputs.force_publish }} - run: | - if [ "$FORCE_PUBLISH" = "true" ]; then - echo "force_publish enabled, proceeding with publish" - echo "skip=false" >> $GITHUB_OUTPUT - exit 0 - fi - - if [ $(git rev-list --count HEAD --since="24 hours ago") -eq 0 ]; then - echo "No commits in last 24 hours, skipping publish" - echo "skip=true" >> $GITHUB_OUTPUT - else - echo "Found recent commits, proceeding with publish" - echo "skip=false" >> $GITHUB_OUTPUT - fi - - - name: Setup Node.js - if: steps.check_commits.outputs.skip != 'true' - uses: actions/setup-node@v4 - with: - node-version: "24.x" - registry-url: "https://registry.npmjs.org" - - - name: Install root dependencies and CLI dependencies - if: steps.check_commits.outputs.skip != 'true' - run: npm ci --include=optional # this will also install cli deps because "cli" in included in root package.json workspaces field - - - name: Generate Protos - if: steps.check_commits.outputs.skip != 'true' - run: npm run protos - - - name: Generate nightly version with timestamp - if: steps.check_commits.outputs.skip != 'true' - id: version - run: | - # Read base version from cli/package.json (e.g., "2.0.0") - BASE_VERSION=$(node -p "require('./cli/package.json').version") - - # Generate timestamp (Unix epoch seconds) - TIMESTAMP=$(date +%s) - - # Create unique nightly version: 1.0.9-nightly.1736365200 - VERSION="${BASE_VERSION}-nightly.${TIMESTAMP}" - - echo "Base version: $BASE_VERSION" - echo "Generated nightly version: $VERSION" - echo "version=$VERSION" >> $GITHUB_OUTPUT - - - name: Update cli/package.json with nightly version - if: steps.check_commits.outputs.skip != 'true' - run: | - # Update version with timestamp-based nightly version - node -e " - const fs = require('fs'); - const pkg = JSON.parse(fs.readFileSync('cli/package.json', 'utf8')); - pkg.version = '${{ steps.version.outputs.version }}'; - fs.writeFileSync('cli/package.json', JSON.stringify(pkg, null, '\t')); - " - - echo "Using version ${{ steps.version.outputs.version }} for build" - cat cli/package.json | grep '"version"' - - - name: Build and package CLI - if: steps.check_commits.outputs.skip != 'true' - env: - TELEMETRY_SERVICE_API_KEY: ${{ secrets.TELEMETRY_SERVICE_API_KEY }} - ERROR_SERVICE_API_KEY: ${{ secrets.ERROR_SERVICE_API_KEY }} - CLINE_ENVIRONMENT: production - OTEL_TELEMETRY_ENABLED: "1" - OTEL_METRICS_EXPORTER: otlp - OTEL_LOGS_EXPORTER: otlp - OTEL_EXPORTER_OTLP_PROTOCOL: ${{ secrets.OTEL_EXPORTER_OTLP_PROTOCOL }} - OTEL_EXPORTER_OTLP_ENDPOINT: ${{ secrets.OTEL_EXPORTER_OTLP_ENDPOINT }} - OTEL_EXPORTER_OTLP_HEADERS: ${{ secrets.OTEL_EXPORTER_OTLP_HEADERS }} - run: node scripts/package-npm.mjs - - - name: Verify build output - if: steps.check_commits.outputs.skip != 'true' - run: | - echo "Checking dist-standalone directory..." - ls -la dist-standalone/ - - echo "Verifying CLI binaries..." - ls -lh cli/bin/cline-* || echo "Warning: CLI binaries not found" - - echo "Checking package.json in dist-standalone..." - cat dist-standalone/package.json | grep version - - - name: Publish to NPM with nightly tag - if: steps.check_commits.outputs.skip != 'true' - run: | - echo "Publishing version ${{ steps.version.outputs.version }} to NPM with tag 'nightly'..." - cd dist-standalone - npm publish --tag nightly --access public - - - name: Summary - if: steps.check_commits.outputs.skip != 'true' - run: | - echo "āœ… Successfully published cline@${{ steps.version.outputs.version }} to NPM with tag 'nightly'" - echo "" - echo "šŸ“¦ Install with: npm install -g cline@nightly" - echo "šŸ”— NPM: https://www.npmjs.com/package/cline/v/${{ steps.version.outputs.version }}" diff --git a/.github/workflows/pack-cli.yml b/.github/workflows/pack-cli.yml deleted file mode 100644 index 110b6b44e3..0000000000 --- a/.github/workflows/pack-cli.yml +++ /dev/null @@ -1,215 +0,0 @@ -# Build and Pack CLI -# -# Builds a CLI tarball from any branch/commit and publishes it as a GitHub Release. -# Requires write access to the repository (maintainers/collaborators only). -# -# Security: Split into two jobs to isolate untrusted build code from write tokens. -# The build job runs arbitrary ref code with zero permissions. The release job -# only runs trusted GitHub Actions with write scope. -# -# Usage (helper script, auto-detects current branch): -# ./scripts/build-cli-artifact.sh -# ./scripts/build-cli-artifact.sh feature/my-changes -# ./scripts/build-cli-artifact.sh feature/my-changes 1234 # comments on PR -# -# Usage (gh CLI directly): -# gh workflow run pack-cli.yml -f ref=main -# gh workflow run pack-cli.yml -f ref=abc123 -f pr_number=1234 -# -# Install the built CLI (no auth required): -# npm install -g https://github.com/cline/cline/releases/download/cli-build-/cline-.tgz -# -# Find releases: -# gh release list --limit 10 - -name: Build and Pack CLI - -permissions: - contents: read - -on: - workflow_dispatch: - inputs: - ref: - description: 'Branch, tag, or commit SHA to build (leave empty for default branch)' - required: false - type: string - pr_number: - description: 'PR number to comment on with install instructions (optional)' - required: false - type: number - -jobs: - # ── Build job: runs untrusted ref code with ZERO permissions ── - build: - name: Build CLI - runs-on: ubuntu-latest - permissions: {} - outputs: - commit_sha: ${{ steps.commit.outputs.sha }} - tarball: ${{ steps.pack.outputs.tarball }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - ref: ${{ inputs.ref || github.ref }} - persist-credentials: false - - - name: Get commit SHA - id: commit - run: | - COMMIT_SHA=$(git rev-parse --short HEAD) - echo "sha=$COMMIT_SHA" >> $GITHUB_OUTPUT - echo "Building from commit: $COMMIT_SHA" - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: "20.x" - - - name: Install dependencies - run: npm ci --include=optional - - - name: Generate Protos - run: npm run protos - - - name: Build standalone package - run: node scripts/package-npm.mjs - - - name: Create Tarball - id: pack - run: | - cd dist-standalone - TARBALL=$(npm pack) - echo "tarball=$TARBALL" >> $GITHUB_OUTPUT - echo "Created tarball: $TARBALL" - - - name: Upload artifact - uses: actions/upload-artifact@v4 - with: - name: cli-tarball - path: dist-standalone/*.tgz - - # ── Release job: only trusted Actions code, with write permissions ── - release: - name: Release CLI - needs: build - runs-on: ubuntu-latest - permissions: - contents: write - pull-requests: write - issues: write - steps: - - name: Download artifact - uses: actions/download-artifact@v4 - with: - name: cli-tarball - path: dist-standalone - - - name: Create GitHub Release - id: create_release - uses: actions/github-script@v7 - with: - script: | - const fs = require('fs'); - const path = require('path'); - const commit = '${{ needs.build.outputs.commit_sha }}'; - const tarball = '${{ needs.build.outputs.tarball }}'; - - // Delete existing release/tag if re-running for the same commit - const tagName = `cli-build-${commit}`; - try { - const existing = await github.rest.repos.getReleaseByTag({ - owner: context.repo.owner, - repo: context.repo.repo, - tag: tagName - }); - await github.rest.repos.deleteRelease({ - owner: context.repo.owner, - repo: context.repo.repo, - release_id: existing.data.id - }); - await github.rest.git.deleteRef({ - owner: context.repo.owner, - repo: context.repo.repo, - ref: `tags/${tagName}` - }); - core.info(`Deleted existing release for ${tagName}`); - } catch (e) { - // Release doesn't exist yet, that's fine - } - - // Create a release - const release = await github.rest.repos.createRelease({ - owner: context.repo.owner, - repo: context.repo.repo, - tag_name: tagName, - name: `CLI Build (${commit})`, - body: `Automated CLI build from commit ${commit}\n\nInstall with:\n\`\`\`bash\nnpm install -g https://github.com/${context.repo.owner}/${context.repo.repo}/releases/download/${tagName}/${tarball}\n\`\`\``, - draft: false, - prerelease: true - }); - - // Upload the tarball as a release asset - const tarballPath = path.join('dist-standalone', tarball); - const tarballData = fs.readFileSync(tarballPath); - - await github.rest.repos.uploadReleaseAsset({ - owner: context.repo.owner, - repo: context.repo.repo, - release_id: release.data.id, - name: tarball, - data: tarballData - }); - - const downloadUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/releases/download/${tagName}/${tarball}`; - core.setOutput('release_url', release.data.html_url); - core.setOutput('download_url', downloadUrl); - - - name: Comment on PR with download instructions - if: inputs.pr_number != '' - uses: actions/github-script@v7 - with: - script: | - const commit = '${{ needs.build.outputs.commit_sha }}'; - const releaseUrl = '${{ steps.create_release.outputs.release_url }}'; - const downloadUrl = '${{ steps.create_release.outputs.download_url }}'; - const prNumber = ${{ inputs.pr_number || 0 }}; - if (!prNumber) return; - - const comment = `## šŸ“¦ CLI Build Ready - - A CLI build has been created for commit \`${commit}\`. - - ### Install Directly from URL (No Authentication Required!) - - \`\`\`bash - npm install -g ${downloadUrl} - \`\`\` - - ### Alternative: Download and Install - - \`\`\`bash - curl -L ${downloadUrl} -o cline.tgz - npm install -g ./cline.tgz - \`\`\` - - šŸ“¦ [View Release](${releaseUrl}) - `; - - await github.rest.issues.createComment({ - issue_number: prNumber, - owner: context.repo.owner, - repo: context.repo.repo, - body: comment - }); - - - name: Summary - run: | - echo "āœ… CLI build complete!" - echo "" - echo "šŸ“¦ Release: ${{ steps.create_release.outputs.release_url }}" - echo "šŸ”— Download URL: ${{ steps.create_release.outputs.download_url }}" - echo "" - echo "Install from anywhere (no authentication required):" - echo " npm install -g ${{ steps.create_release.outputs.download_url }}" diff --git a/.github/workflows/publish-cli-trusted.yaml b/.github/workflows/publish-cli-trusted.yaml deleted file mode 100644 index 395bd5f402..0000000000 --- a/.github/workflows/publish-cli-trusted.yaml +++ /dev/null @@ -1,60 +0,0 @@ -name: Publish CLI (Trusted) - -on: - schedule: - - cron: "0 12 * * *" # 4 AM PST (UTC-8) = 12 UTC - workflow_dispatch: - inputs: - publish_target: - description: "Which publish flow to run" - required: true - default: "main" - type: choice - options: - - main - - nightly - confirm_publish: - description: 'Required when publish_target=main. Type "publish" to confirm release publish.' - required: false - type: string - force_nightly_publish: - description: "Force nightly publish even with no commits in last 24h" - required: false - type: boolean - default: false - -permissions: - id-token: write # Required for npm trusted publishing (OIDC) - contents: write # Required because npm-main creates/pushes git tags - checks: write # Required by nested reusable test workflow - pull-requests: write # Required by nested reusable test workflow - -jobs: - cli-tui-tests: - uses: ./.github/workflows/cli-tui-tests.yml - - publish-main: - needs: cli-tui-tests - if: | - github.repository == 'cline/cline' && ( - github.event_name == 'workflow_dispatch' && - github.event.inputs.publish_target == 'main' && - github.event.inputs.confirm_publish == 'publish' && - !endsWith(github.actor, '[bot]') - ) - uses: ./.github/workflows/npm-main.yaml - secrets: inherit - with: - confirm_publish: ${{ github.event.inputs.confirm_publish }} - - publish-nightly: - needs: cli-tui-tests - if: | - github.repository == 'cline/cline' && ( - github.event_name == 'schedule' || - (github.event_name == 'workflow_dispatch' && github.event.inputs.publish_target == 'nightly') - ) - uses: ./.github/workflows/npm-nightly.yaml - secrets: inherit - with: - force_publish: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.force_nightly_publish == 'true' }} diff --git a/sdk/.github/workflows/publish-cli.yaml b/.github/workflows/publish-cli.yaml similarity index 95% rename from sdk/.github/workflows/publish-cli.yaml rename to .github/workflows/publish-cli.yaml index 7744fbc74d..7f1b6944b4 100644 --- a/sdk/.github/workflows/publish-cli.yaml +++ b/.github/workflows/publish-cli.yaml @@ -31,6 +31,10 @@ permissions: contents: read id-token: write +defaults: + run: + working-directory: sdk + jobs: publish-main: name: Publish cline @@ -38,7 +42,7 @@ jobs: contents: write id-token: write if: | - github.repository == 'cline/sdk' && + github.repository == 'cline/cline' && github.ref == 'refs/heads/main' && github.event_name == 'workflow_dispatch' && github.event.inputs.publish_target == 'main' && @@ -101,12 +105,12 @@ jobs: PACKAGE_VERSION=$(node -p "require('./apps/cli/package.json').version") if [ "$PACKAGE_VERSION" != "$VERSION" ]; then - echo "apps/cli/package.json version ${PACKAGE_VERSION} does not match ${TAG}" + echo "sdk/apps/cli/package.json version ${PACKAGE_VERSION} does not match ${TAG}" exit 1 fi if ! printf "%s\n" "$VERSION" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$'; then - echo "apps/cli/package.json has invalid version: ${VERSION}" + echo "sdk/apps/cli/package.json has invalid version: ${VERSION}" exit 1 fi @@ -134,7 +138,7 @@ jobs: - name: Build platform binaries run: bun script/build.ts --install-native-variants --skip-sdk-build - working-directory: apps/cli + working-directory: sdk/apps/cli - name: Verify build output run: | @@ -167,10 +171,11 @@ jobs: ls -lh "$dir/bin/" done - # TODO: re-enable NPM_CONFIG_PROVENANCE: "true" when repo is public - name: Publish to NPM with latest tag + env: + NPM_CONFIG_PROVENANCE: "true" run: bun script/publish-npm.ts --tag latest - working-directory: apps/cli + working-directory: sdk/apps/cli - name: Create GitHub release env: @@ -214,7 +219,7 @@ jobs: contents: read id-token: write if: | - github.repository == 'cline/sdk' && + github.repository == 'cline/cline' && github.ref == 'refs/heads/main' && ( github.event_name == 'schedule' || @@ -321,7 +326,7 @@ jobs: - name: Build platform binaries if: steps.check_commits.outputs.skip != 'true' run: bun script/build.ts --install-native-variants --skip-sdk-build - working-directory: apps/cli + working-directory: sdk/apps/cli - name: Verify build output if: steps.check_commits.outputs.skip != 'true' @@ -355,11 +360,12 @@ jobs: ls -lh "$dir/bin/" done - # TODO: re-enable NPM_CONFIG_PROVENANCE: "true" when repo is public - name: Publish to NPM with nightly tag if: steps.check_commits.outputs.skip != 'true' + env: + NPM_CONFIG_PROVENANCE: "true" run: bun script/publish-npm.ts --tag nightly - working-directory: apps/cli + working-directory: sdk/apps/cli - name: Summary if: steps.check_commits.outputs.skip != 'true' diff --git a/.github/workflows/publish-nightly-sdk.yml b/.github/workflows/publish-new-sdk-extension-nightly.yml similarity index 96% rename from .github/workflows/publish-nightly-sdk.yml rename to .github/workflows/publish-new-sdk-extension-nightly.yml index 4accd48a58..394e79beb8 100644 --- a/.github/workflows/publish-nightly-sdk.yml +++ b/.github/workflows/publish-new-sdk-extension-nightly.yml @@ -1,4 +1,4 @@ -name: "Publish SDK Nightly Release" +name: "Publish New SDK Extension Nightly" on: schedule: @@ -17,7 +17,7 @@ env: jobs: publish: - name: Publish Cline (Nightly SDK) Extension + name: Publish Cline New SDK Extension Nightly if: github.repository == 'cline/cline' && github.ref == 'refs/heads/main' runs-on: ubuntu-latest environment: PublishNightly diff --git a/sdk/.github/workflows/publish-sdk.yaml b/.github/workflows/publish-sdk.yaml similarity index 89% rename from sdk/.github/workflows/publish-sdk.yaml rename to .github/workflows/publish-sdk.yaml index 294f382799..03faf32934 100644 --- a/sdk/.github/workflows/publish-sdk.yaml +++ b/.github/workflows/publish-sdk.yaml @@ -16,15 +16,23 @@ on: required: false type: boolean default: false + confirm_publish: + description: 'Required when channel=latest. Type "publish" to confirm release publish.' + required: false + type: string schedule: # Run nightly at 2:00 AM UTC - cron: "0 2 * * *" +defaults: + run: + working-directory: sdk + jobs: test: permissions: contents: read - uses: ./.github/workflows/test.yml + uses: ./.github/workflows/sdk-test.yml publish-sdk: needs: test @@ -32,7 +40,17 @@ jobs: permissions: contents: write id-token: write - if: github.repository == 'cline/sdk' && github.ref == 'refs/heads/main' + if: | + github.repository == 'cline/cline' && + github.ref == 'refs/heads/main' && + ( + github.event_name != 'workflow_dispatch' || + inputs.channel != 'latest' || + ( + inputs.confirm_publish == 'publish' && + !endsWith(github.actor, '[bot]') + ) + ) runs-on: ubuntu-latest steps: @@ -67,7 +85,7 @@ jobs: exit 0 fi - if [ $(git rev-list --count HEAD --since="24 hours ago") -eq 0 ]; then + if [ "$(git rev-list --count HEAD --since="24 hours ago")" -eq 0 ]; then echo "No commits in last 24 hours, skipping publish" echo "skip=true" >> $GITHUB_OUTPUT else @@ -156,6 +174,8 @@ jobs: # Publish sequentially in dependency order: shared → llms → agents → core → sdk - name: Publish @cline/shared if: steps.check_commits.outputs.skip != 'true' + env: + NPM_CONFIG_PROVENANCE: "true" run: | CHANNEL="${{ steps.channel.outputs.channel }}" echo "Publishing @cline/shared@${{ steps.version.outputs.version }} with tag '${CHANNEL}'..." @@ -165,6 +185,8 @@ jobs: - name: Publish @cline/llms if: steps.check_commits.outputs.skip != 'true' + env: + NPM_CONFIG_PROVENANCE: "true" run: | CHANNEL="${{ steps.channel.outputs.channel }}" echo "Publishing @cline/llms@${{ steps.version.outputs.version }} with tag '${CHANNEL}'..." @@ -174,6 +196,8 @@ jobs: - name: Publish @cline/agents if: steps.check_commits.outputs.skip != 'true' + env: + NPM_CONFIG_PROVENANCE: "true" run: | CHANNEL="${{ steps.channel.outputs.channel }}" echo "Publishing @cline/agents@${{ steps.version.outputs.version }} with tag '${CHANNEL}'..." @@ -183,6 +207,8 @@ jobs: - name: Publish @cline/core if: steps.check_commits.outputs.skip != 'true' + env: + NPM_CONFIG_PROVENANCE: "true" run: | CHANNEL="${{ steps.channel.outputs.channel }}" echo "Publishing @cline/core@${{ steps.version.outputs.version }} with tag '${CHANNEL}'..." @@ -192,6 +218,8 @@ jobs: - name: Publish @cline/sdk if: steps.check_commits.outputs.skip != 'true' + env: + NPM_CONFIG_PROVENANCE: "true" run: | CHANNEL="${{ steps.channel.outputs.channel }}" echo "Publishing @cline/sdk@${{ steps.version.outputs.version }} with tag '${CHANNEL}'..." @@ -225,14 +253,14 @@ jobs: run: | VERSION="${{ steps.version.outputs.version }}" CHANNEL="${{ steps.channel.outputs.channel }}" - echo "āœ… Published SDK packages with tag '${CHANNEL}':" + echo "Published SDK packages with tag '${CHANNEL}':" echo " - @cline/shared@${VERSION}" echo " - @cline/llms@${VERSION}" echo " - @cline/agents@${VERSION}" echo " - @cline/core@${VERSION}" echo " - @cline/sdk@${VERSION}" if [ "$CHANNEL" = "latest" ]; then - echo "āœ… Created git tags:" + echo "Created git tags:" echo " - sdk/shared/v${VERSION}" echo " - sdk/llms/v${VERSION}" echo " - sdk/agents/v${VERSION}" diff --git a/sdk/.github/workflows/test.yml b/.github/workflows/sdk-test.yml similarity index 93% rename from sdk/.github/workflows/test.yml rename to .github/workflows/sdk-test.yml index f904c8425a..5f6725cfaf 100644 --- a/sdk/.github/workflows/test.yml +++ b/.github/workflows/sdk-test.yml @@ -1,18 +1,28 @@ -name: Tests +name: SDK Tests on: push: branches: - main + paths: + - "sdk/**" + - ".github/workflows/sdk-test.yml" workflow_dispatch: pull_request: branches: - main + paths: + - "sdk/**" + - ".github/workflows/sdk-test.yml" workflow_call: permissions: contents: read +defaults: + run: + working-directory: sdk + jobs: quality-checks: runs-on: ubuntu-latest diff --git a/evals/ARCHITECTURE.md b/evals/ARCHITECTURE.md index 179a2fdd4a..5c1d2a1ba3 100644 --- a/evals/ARCHITECTURE.md +++ b/evals/ARCHITECTURE.md @@ -1,5 +1,7 @@ # Cline Evals Architecture +> Note: Smoke tests (Layer 2) are partially disabled while the eval framework is repointed at the new SDK CLI. The scenarios under `evals/smoke-tests/` are preserved and `npm run eval:smoke:run` still works against whatever `cline` is on `$PATH` (install with `npm i -g cline`). The build-and-link helpers (`eval:smoke:build`, `eval:smoke`, `eval:smoke:ci`) and the auto-running `cline-evals-regression.yml` workflow are off until someone wires the build step at the new SDK CLI. + ## Overview The evals system provides multi-layered testing for Cline's AI capabilities. @@ -220,7 +222,7 @@ ls evals/smoke-tests/results/latest///workspace-trial-1/ ## CI Integration -Smoke tests run automatically on merge to `main` via `.github/workflows/cline-evals-regression.yml`. +Smoke tests are temporarily disabled. `.github/workflows/cline-evals-regression.yml` accepts manual `workflow_dispatch` only until the build step is repointed at the new SDK CLI. **Triggers:** - Push to `main` branch (paths: `src/core/**`, `src/shared/**`, `proto/**`) diff --git a/evals/README.md b/evals/README.md index 462ba51347..3d515b0e51 100644 --- a/evals/README.md +++ b/evals/README.md @@ -2,6 +2,8 @@ A layered testing system for measuring Cline's performance at different levels. +> Note: Smoke tests (Layer 2) are partially disabled while the eval framework is repointed at the new SDK CLI. The scenarios under `evals/smoke-tests/` are preserved and `npm run eval:smoke:run` still works against whatever `cline` is on `$PATH` (install with `npm i -g cline`). The build-and-link helpers (`eval:smoke:build`, `eval:smoke`, `eval:smoke:ci`) and the auto-running `cline-evals-regression.yml` workflow are off until someone wires the build step at the new SDK CLI. + ## Directory Structure ``` diff --git a/package.json b/package.json index 427c46343e..c7edc054a5 100644 --- a/package.json +++ b/package.json @@ -384,17 +384,8 @@ "vscode:prepublish": "npm run package", "compile": "npm run check-types && npm run lint && node esbuild.mjs", "compile-standalone": "npm run check-types && npm run lint && node esbuild.mjs --standalone", - "compile-standalone-npm": "npm run protos && npm run check-types && npm run lint && node esbuild.mjs --standalone", - "cli:link": "cd cli && npm run link", - "cli:build": "npm run protos && cd cli && npm run build", - "cli:run": "node cli/dist/cli.mjs", - "cli:build:production": "cd cli && npm run build:production", - "cli:watch": "cd cli && npm run watch", - "cli:test": "cd cli && npm run test", "test:install": "bash scripts/test-install.sh", - "cli:dev": "cd cli && npm run dev", "postcompile-standalone": "node scripts/package-standalone.mjs", - "postcompile-standalone-npm": "node scripts/package-npm.mjs", "dev": "npm run protos && npm run watch", "watch": "npx npm-run-all -p watch:*", "watch:esbuild": "node esbuild.mjs --watch", @@ -409,7 +400,7 @@ "clean:all": "npm run clean:build && npm run clean:deps", "compile-tests": "node ./scripts/build-tests.js", "watch-tests": "tsc -p . -w --outDir out", - "check-types": "npm run protos && npx tsc --noEmit && cd webview-ui && npx tsc --noEmit && cd ../cli && npx tsc --noEmit", + "check-types": "npm run protos && npx tsc --noEmit && cd webview-ui && npx tsc --noEmit", "lint": "biome lint --no-errors-on-unmatched --files-ignore-unknown=true --diagnostic-level=error && npm run lint:proto", "lint:proto": "bash ./scripts/proto-lint.sh", "format": "biome format --changed --no-errors-on-unmatched --files-ignore-unknown=true --diagnostic-level=error", @@ -428,7 +419,6 @@ "test:e2e:build": "vsce package --allow-package-secrets sendgrid --out dist/e2e.vsix", "test:e2e": "playwright install && npm run test:e2e:build && node src/test/e2e/utils/build.mjs && playwright test", "test:e2e:optimal": "npm run test:e2e:build && node src/test/e2e/utils/build.mjs && playwright test", - "test:e2e:cli:tui": "cd tests/e2e/cli && tui-test", "test:e2e:ui": "npx tsx scripts/interactive-playwright.ts", "install:all": "npm install && cd webview-ui && npm install", "dev:webview": "cd webview-ui && npm run dev", @@ -443,11 +433,7 @@ "docs:rename-file": "cd docs && npm run rename", "report-issue": "node scripts/report-issue.js", "storybook": "cd webview-ui && npm run storybook", - "cli:unlink": "cd cli && npm run unlink", - "eval:smoke:build": "npm run cli:build && npm run cli:link", - "eval:smoke:run": "npx tsx evals/smoke-tests/run-smoke-tests.ts", - "eval:smoke": "npm run eval:smoke:build && npm run eval:smoke:run", - "eval:smoke:ci": "npm run eval:smoke:build && npm run eval:smoke:run -- --trials 1 --parallel" + "eval:smoke:run": "npx tsx evals/smoke-tests/run-smoke-tests.ts" }, "lint-staged": { "src/shared/storage/state-keys.ts": [ diff --git a/scripts/build-cli-artifact.sh b/scripts/build-cli-artifact.sh deleted file mode 100755 index f6c73da466..0000000000 --- a/scripts/build-cli-artifact.sh +++ /dev/null @@ -1,44 +0,0 @@ -#!/bin/bash -# Build CLI release for a specific ref/commit using GitHub Actions -# -# Usage: -# ./scripts/build-cli-artifact.sh [ref] [pr_number] -# -# Examples: -# ./scripts/build-cli-artifact.sh # Build from current branch -# ./scripts/build-cli-artifact.sh main # Build from main branch -# ./scripts/build-cli-artifact.sh abc123 # Build from commit abc123 -# ./scripts/build-cli-artifact.sh feature/new 1234 # Build from branch and comment on PR #1234 - -set -e - -REF="${1:-$(git rev-parse --abbrev-ref HEAD)}" -PR_NUMBER="${2:-}" - -echo "šŸš€ Triggering CLI build workflow..." -echo " Branch/commit: $REF" - -# Build args array -ARGS=(-f "ref=$REF") - -if [ -n "$PR_NUMBER" ]; then - ARGS+=(-f "pr_number=$PR_NUMBER") - echo " Will comment on PR #$PR_NUMBER" -fi - -# Trigger the workflow -gh workflow run pack-cli.yml "${ARGS[@]}" - -echo "" -echo "āœ… Workflow triggered!" -echo "" -echo "The workflow will create a GitHub Release with a public download URL." -echo "" -echo "To monitor the workflow:" -echo " gh run list --workflow=pack-cli.yml --limit 5" -echo "" -echo "Once complete, find the release:" -echo " gh release list --limit 10" -echo "" -echo "Install from the release URL (no authentication required):" -echo " npm install -g https://github.com/cline/cline/releases/download/cli-build-/cline-.tgz" diff --git a/scripts/package-npm.mjs b/scripts/package-npm.mjs deleted file mode 100644 index a02f259717..0000000000 --- a/scripts/package-npm.mjs +++ /dev/null @@ -1,192 +0,0 @@ -#!/usr/bin/env node - -/** - * NPM Package Builder for Cline CLI - * - * This script builds the Cline CLI NPM package (dist-standalone/). - * It packages the CLI from cli/. - * - * Usage: node scripts/package-npm.mjs - * - * Prerequisites: - * - cd cli && npm run build:production - */ - -import { fileURLToPath } from "node:url" -import { execSync } from "child_process" -import dotenv from "dotenv" -import fs from "fs" -import { cp } from "fs/promises" -import path from "path" - -const BUILD_DIR = "dist-standalone" -const CLI_DIR = "cli" -const IS_VERBOSE = process.argv.includes("-v") || process.argv.includes("--verbose") - -const __filename = fileURLToPath(import.meta.url) -const __dirname = path.dirname(__filename) -const rootDir = path.resolve(__dirname, "..") -// Load .env from repo root -dotenv.config({ path: path.join(rootDir, ".env") }) - -async function main() { - console.log("šŸš€ Building Cline CLI NPM Package (TypeScript)\n") - - await cleanBuildDir() - setupEnvironmentVariables() - - await buildTypeScriptCli() - await copyCliDist() - await copyPackageJson() - await copyReadme() - await createNpmIgnoreFile() - - console.log("\nāœ… Build complete!") - console.log(`\nšŸ“¦ NPM package ready in ${BUILD_DIR}/`) - console.log(`To publish: cd ${BUILD_DIR} && npm publish`) -} - -function setupEnvironmentVariables() { - // Use a different API key for CLI error capturing, to redirect CLI errors to a different project - const cliErrorTrackingKey = process.env.CLI_ERROR_SERVICE_API_KEY - if (cliErrorTrackingKey) { - process.env.ERROR_SERVICE_API_KEY = cliErrorTrackingKey - // If we're sending to a different project, enable exception autocapture - process.env.ENABLE_ERROR_AUTOCAPTURE = "true" - log_verbose("Set ERROR_SERVICE_API_KEY for build") - } -} - -/** - * Clean the build directory - */ -async function cleanBuildDir() { - console.log("Cleaning build directory...") - await rmrf(BUILD_DIR) - fs.mkdirSync(BUILD_DIR, { recursive: true }) - console.log(`āœ“ ${BUILD_DIR}/ cleaned`) -} - -/** - * Build the TypeScript CLI - */ -async function buildTypeScriptCli() { - console.log("Building TypeScript CLI...") - - // Install dependencies if needed - if (!fs.existsSync(path.join(CLI_DIR, "node_modules"))) { - console.log("Installing cli dependencies...") - execSync("npm install", { stdio: "inherit", cwd: CLI_DIR }) - } - - // Build production bundle - execSync("npm run build:production", { stdio: "inherit", cwd: CLI_DIR, env: process.env }) - console.log("āœ“ TypeScript CLI built") -} - -/** - * Copy the CLI dist folder to build directory - */ -async function copyCliDist() { - console.log("Copying CLI distribution files...") - - const distSource = path.join(CLI_DIR, "dist") - const distDest = path.join(BUILD_DIR, "dist") - - if (!fs.existsSync(distSource)) { - console.error(`Error: CLI dist not found at ${distSource}`) - console.error(`Please run: cd cli && npm run build:production`) - process.exit(1) - } - - await cpr(distSource, distDest) - - // Make the CLI executable - const cliPath = path.join(distDest, "cli.mjs") - if (fs.existsSync(cliPath)) { - fs.chmodSync(cliPath, 0o755) - } - - console.log(`āœ“ CLI dist copied to ${distDest}`) -} - -/** - * Copy package.json from cli/ directory - */ -async function copyPackageJson() { - console.log("Copying package.json...") - const source = path.join(CLI_DIR, "package.json") - const dest = path.join(BUILD_DIR, "package.json") - await cpr(source, dest) - console.log(`āœ“ package.json copied`) -} - -/** - * Copy README.md from cli/ directory - */ -async function copyReadme() { - console.log("Copying README...") - - // Try cli README first, fall back to cli/ README - let readmeSource = path.join(CLI_DIR, "README.md") - - if (!fs.existsSync(readmeSource)) { - readmeSource = path.join("cli", "README.md") - } - - if (!fs.existsSync(readmeSource)) { - console.warn("Warning: No README.md found, skipping") - return - } - - const readmeDest = path.join(BUILD_DIR, "README.md") - await cpr(readmeSource, readmeDest) - console.log(`āœ“ README.md copied from ${readmeSource}`) -} - -/** - * Create .npmignore file to exclude unnecessary files - */ -async function createNpmIgnoreFile() { - console.log("Creating .npmignore file...") - - const npmignoreContent = `# Exclude build artifacts and unnecessary files -*.map -*.ts -!*.d.ts -tsconfig.json -.eslintrc* -.prettierrc* -` - - const npmignorePath = path.join(BUILD_DIR, ".npmignore") - fs.writeFileSync(npmignorePath, npmignoreContent) - - console.log(`āœ“ .npmignore created`) -} - -/* cp -r */ -async function cpr(source, dest) { - log_verbose(`Copying ${source} -> ${dest}`) - await cp(source, dest, { - recursive: true, - preserveTimestamps: true, - dereference: false, - }) -} - -/* rm -rf */ -async function rmrf(dir) { - if (fs.existsSync(dir)) { - log_verbose(`Removing ${dir}`) - fs.rmSync(dir, { recursive: true, force: true }) - } -} - -function log_verbose(...args) { - if (IS_VERBOSE) { - console.log(...args) - } -} - -await main() diff --git a/sdk/apps/cli/.cline/skills/publish-cli/SKILL.md b/sdk/apps/cli/.cline/skills/publish-cli/SKILL.md index f929d12ca1..a5f22fff80 100644 --- a/sdk/apps/cli/.cline/skills/publish-cli/SKILL.md +++ b/sdk/apps/cli/.cline/skills/publish-cli/SKILL.md @@ -9,6 +9,8 @@ Use this skill when the user asks to release the CLI, publish `cline`, bump the The CLI is npm-only. Do not add alternate distribution or signing steps. +> Working directory: this skill lives in the SDK sub-monorepo. Run `cd sdk` (from the repo root) before any of the shell commands below. Paths in commands and instructions (e.g. `apps/cli/package.json`, `bun release cli`) are written relative to `sdk/`. + The skill should guide the user through one release preparation flow, then offer the publish path options. The two normal publish paths are GitHub Actions and local publishing from an authenticated machine. ## Release contract diff --git a/sdk/apps/cli/package.json b/sdk/apps/cli/package.json index 66e8be86de..93f82a6b70 100644 --- a/sdk/apps/cli/package.json +++ b/sdk/apps/cli/package.json @@ -9,8 +9,8 @@ }, "repository": { "type": "git", - "url": "git+https://github.com/cline/sdk.git", - "directory": "apps/cli" + "url": "git+https://github.com/cline/cline.git", + "directory": "sdk/apps/cli" }, "bin": { "cline": "src/index.ts" diff --git a/sdk/packages/agents/package.json b/sdk/packages/agents/package.json index 9f5a9af8cc..88efd0f8b7 100644 --- a/sdk/packages/agents/package.json +++ b/sdk/packages/agents/package.json @@ -3,8 +3,8 @@ "version": "0.0.39", "repository": { "type": "git", - "url": "https://github.com/cline/sdk", - "directory": "packages/agents" + "url": "https://github.com/cline/cline", + "directory": "sdk/packages/agents" }, "main": "dist/index.js", "dependencies": { diff --git a/sdk/packages/core/package.json b/sdk/packages/core/package.json index 6661300009..276bed2ad3 100644 --- a/sdk/packages/core/package.json +++ b/sdk/packages/core/package.json @@ -4,8 +4,8 @@ "version": "0.0.39", "repository": { "type": "git", - "url": "https://github.com/cline/sdk", - "directory": "packages/core" + "url": "https://github.com/cline/cline", + "directory": "sdk/packages/core" }, "type": "module", "types": "./dist/index.d.ts", diff --git a/sdk/packages/llms/package.json b/sdk/packages/llms/package.json index 3ffcf0f9d6..25f8c92aef 100644 --- a/sdk/packages/llms/package.json +++ b/sdk/packages/llms/package.json @@ -4,8 +4,8 @@ "description": "Config-driven SDK for selecting, extending, and instantiating LLM providers and models", "repository": { "type": "git", - "url": "https://github.com/cline/sdk", - "directory": "packages/llms" + "url": "https://github.com/cline/cline", + "directory": "sdk/packages/llms" }, "main": "./dist/index.js", "module": "./dist/index.js", diff --git a/sdk/packages/sdk/package.json b/sdk/packages/sdk/package.json index d4f2697fd4..c75cc63306 100644 --- a/sdk/packages/sdk/package.json +++ b/sdk/packages/sdk/package.json @@ -4,8 +4,8 @@ "version": "0.0.39", "repository": { "type": "git", - "url": "https://github.com/cline/sdk", - "directory": "packages/sdk" + "url": "https://github.com/cline/cline", + "directory": "sdk/packages/sdk" }, "type": "module", "types": "./dist/index.d.ts", diff --git a/sdk/packages/shared/package.json b/sdk/packages/shared/package.json index baca91727d..a689d5da04 100644 --- a/sdk/packages/shared/package.json +++ b/sdk/packages/shared/package.json @@ -4,8 +4,8 @@ "description": "Shared utilities, types, and schemas for Cline packages", "repository": { "type": "git", - "url": "https://github.com/cline/sdk", - "directory": "packages/shared" + "url": "https://github.com/cline/cline", + "directory": "sdk/packages/shared" }, "type": "module", "main": "dist/index.js", diff --git a/tests/e2e/cli/cli/auth.test.ts b/tests/e2e/cli/cli/auth.test.ts deleted file mode 100644 index 40aa1a4af7..0000000000 --- a/tests/e2e/cli/cli/auth.test.ts +++ /dev/null @@ -1,191 +0,0 @@ -// --------------------------------------------------------------------------- -// cline auth — CLI flag and contract tests -// -// These tests cover the `cline auth` subcommand behavior: -// - Interactive auth screen navigation -// - `cline auth -p -k -m ` golden path -// - Invalid provider / key / model error handling -// - Partial-flag fallback to interactive screen -// - `cline auth --help` -// --------------------------------------------------------------------------- - -import { test } from "@microsoft/tui-test" -import { CLINE_BIN, TERMINAL_WIDE } from "../helpers/constants.js" -import { clineEnv } from "../helpers/env.js" -import { waitForAuthScreen } from "../helpers/page-objects/auth.js" -import { expectVisible } from "../helpers/terminal.js" - -// --------------------------------------------------------------------------- -// cline auth (interactive screen — no flags) -// --------------------------------------------------------------------------- -test.describe("cline auth (interactive screen)", () => { - test.use({ - program: { file: CLINE_BIN, args: ["auth"] }, - ...TERMINAL_WIDE, - env: clineEnv("unauthenticated"), - }) - - test("shows all auth options", async ({ terminal }) => { - await waitForAuthScreen(terminal) - }) - - test("can navigate options with keyUp / keyDown", async ({ terminal }) => { - await waitForAuthScreen(terminal) - terminal.keyDown() - terminal.keyUp() - // Still on the auth screen after navigation - await expectVisible(terminal, "Sign in with Cline") - }) -}) - -// --------------------------------------------------------------------------- -// cline auth --help -// --------------------------------------------------------------------------- -test.describe("cline auth --help", () => { - test.use({ - program: { file: CLINE_BIN, args: ["auth", "--help"] }, - ...TERMINAL_WIDE, - env: clineEnv("unauthenticated"), - }) - - test("shows auth help page", async ({ terminal }) => { - await expectVisible(terminal, "Usage:") - await expectVisible(terminal, "--provider") - await expectVisible(terminal, "--apikey") - await expectVisible(terminal, "--modelid") - await expectVisible(terminal, "--baseurl") - }) -}) - -// --------------------------------------------------------------------------- -// cline auth with only partial flags → falls back to interactive screen -// --------------------------------------------------------------------------- -test.describe("cline auth --provider only (partial flags)", () => { - test.use({ - program: { file: CLINE_BIN, args: ["auth", "--provider", "openai"] }, - ...TERMINAL_WIDE, - env: clineEnv("unauthenticated"), - }) - - test("ignores partial flags and shows interactive auth screen", async ({ terminal }) => { - await waitForAuthScreen(terminal) - }) -}) - -test.describe("cline auth --apikey only (partial flags)", () => { - test.use({ - program: { - file: CLINE_BIN, - args: ["auth", "--apikey", "sk-test-key"], - }, - ...TERMINAL_WIDE, - env: clineEnv("unauthenticated"), - }) - - test("ignores partial flags and shows interactive auth screen", async ({ terminal }) => { - await waitForAuthScreen(terminal) - }) -}) - -test.describe("cline auth --modelid only (partial flags)", () => { - test.use({ - program: { - file: CLINE_BIN, - args: ["auth", "--modelid", "gpt-4o"], - }, - ...TERMINAL_WIDE, - env: clineEnv("unauthenticated"), - }) - - test("ignores partial flags and shows interactive auth screen", async ({ terminal }) => { - await waitForAuthScreen(terminal) - }) -}) - -test.describe("cline auth --baseurl only (partial flags)", () => { - test.use({ - program: { - file: CLINE_BIN, - args: ["auth", "--baseurl", "https://api.example.com"], - }, - ...TERMINAL_WIDE, - env: clineEnv("unauthenticated"), - }) - - test("ignores partial flags and shows interactive auth screen", async ({ terminal }) => { - await waitForAuthScreen(terminal) - }) -}) - -test.describe("cline auth --verbose only (partial flags)", () => { - test.use({ - program: { - file: CLINE_BIN, - args: ["auth", "--verbose"], - }, - ...TERMINAL_WIDE, - env: clineEnv("unauthenticated"), - }) - - test("ignores --verbose and shows interactive auth screen", async ({ terminal }) => { - await waitForAuthScreen(terminal) - }) -}) - -// --------------------------------------------------------------------------- -// cline auth --cwd -// User sees interactive auth screen; after authing, footer shows workspace dir -// --------------------------------------------------------------------------- -test.describe("cline auth --cwd", () => { - test.use({ - program: { - file: CLINE_BIN, - args: ["auth", "--cwd", "/tmp"], - }, - ...TERMINAL_WIDE, - env: clineEnv("unauthenticated"), - }) - - test("shows interactive auth screen with --cwd flag", async ({ terminal }) => { - await waitForAuthScreen(terminal) - }) -}) - -// --------------------------------------------------------------------------- -// cline auth --config -// User sees interactive auth screen; after authing, custom config dir exists -// with globalState.json and secrets.json; default ~/.cline does NOT exist -// --------------------------------------------------------------------------- -test.describe("cline auth --config", () => { - test.use({ - program: { - file: CLINE_BIN, - args: ["auth", "--config", "configs/unauthenticated"], - }, - ...TERMINAL_WIDE, - env: clineEnv("unauthenticated"), - }) - - test("shows interactive auth screen with --config flag", async ({ terminal }) => { - await waitForAuthScreen(terminal) - }) -}) - -// --------------------------------------------------------------------------- -// cline auth -p -k -m -// → should show "invalid provider" and exit 1 -// --------------------------------------------------------------------------- -test.describe("cline auth with invalid provider", () => { - test.use({ - program: { - file: CLINE_BIN, - args: ["auth", "--provider", "not-a-real-provider", "--apikey", "sk-test", "--modelid", "gpt-4o"], - }, - ...TERMINAL_WIDE, - env: clineEnv("unauthenticated"), - }) - - test("shows invalid provider error", async ({ terminal }) => { - await expectVisible(terminal, /invalid provider/i, { timeout: 5000 }) - }) -}) diff --git a/tests/e2e/cli/cli/config.test.ts b/tests/e2e/cli/cli/config.test.ts deleted file mode 100644 index 6c6359a24f..0000000000 --- a/tests/e2e/cli/cli/config.test.ts +++ /dev/null @@ -1,51 +0,0 @@ -// --------------------------------------------------------------------------- -// cline config — CLI tests -// -// Covers: -// - `cline config --config ` — shows config for specific directory -// - `cline config --help` — help page -// --------------------------------------------------------------------------- - -import { test } from "@microsoft/tui-test" -import { CLINE_BIN, TERMINAL_WIDE } from "../helpers/constants.js" -import { clineEnv } from "../helpers/env.js" -import { expectVisible } from "../helpers/terminal.js" - -// --------------------------------------------------------------------------- -// cline config --help -// --------------------------------------------------------------------------- -test.describe("cline config --help", () => { - test.use({ - program: { file: CLINE_BIN, args: ["config", "--help"] }, - ...TERMINAL_WIDE, - env: clineEnv("default"), - }) - - test("shows config help page", async ({ terminal }) => { - await expectVisible(terminal, "Usage:") - await expectVisible(terminal, "--config") - }) -}) - -// --------------------------------------------------------------------------- -// cline config --config -// Shows interactive config view for the specified directory -// --------------------------------------------------------------------------- -test.describe("cline config (default config)", () => { - test.use({ - program: { file: CLINE_BIN, args: ["config"] }, - ...TERMINAL_WIDE, - env: clineEnv("default"), - }) -}) - -test.describe("cline config --config (claude-sonnet-4.6)", () => { - test.use({ - program: { - file: CLINE_BIN, - args: ["config", "--config", "configs/claude-sonnet-4.6"], - }, - ...TERMINAL_WIDE, - env: clineEnv("claude-sonnet-4.6"), - }) -}) diff --git a/tests/e2e/cli/cli/dev.test.ts b/tests/e2e/cli/cli/dev.test.ts deleted file mode 100644 index a4ffe4bb63..0000000000 --- a/tests/e2e/cli/cli/dev.test.ts +++ /dev/null @@ -1,18 +0,0 @@ -// --------------------------------------------------------------------------- -// cline dev — CLI tests -// -// Covers: -// - `cline dev log` -// --------------------------------------------------------------------------- - -import { test } from "@microsoft/tui-test" -import { CLINE_BIN, TERMINAL_WIDE } from "../helpers/constants.js" -import { clineEnv } from "../helpers/env.js" - -test.describe("cline dev log", () => { - test.use({ - program: { file: CLINE_BIN, args: ["dev", "log"] }, - ...TERMINAL_WIDE, - env: clineEnv("default"), - }) -}) diff --git a/tests/e2e/cli/cli/flags-behavior.test.ts b/tests/e2e/cli/cli/flags-behavior.test.ts deleted file mode 100644 index dcf35cfbac..0000000000 --- a/tests/e2e/cli/cli/flags-behavior.test.ts +++ /dev/null @@ -1,213 +0,0 @@ -// --------------------------------------------------------------------------- -// CLI flag behavioral tests -// -// These tests verify the runtime behavior of each CLI flag — not just that -// the flag appears in --help output (that's covered in tests/flags.test.ts), -// but that the flag actually changes what cline does. -// -// Tests marked āš ļø in the spec reflect known gaps where the flag is accepted -// but currently has no observable effect. They are still written so the -// behavior can be asserted once the implementation catches up. -// --------------------------------------------------------------------------- - -import { test } from "@microsoft/tui-test" -import { CLINE_BIN, TERMINAL_WIDE } from "../helpers/constants.js" -import { clineEnv } from "../helpers/env.js" -import { expectVisible } from "../utils.js" - -// --------------------------------------------------------------------------- -// cline --act -// Starts cline in Act mode regardless of globalState -// --------------------------------------------------------------------------- -test.describe("cline --act", () => { - test.use({ - program: { file: CLINE_BIN, args: ["--tui", "--act"] }, - ...TERMINAL_WIDE, - env: clineEnv("default"), - }) - - test("starts in Act mode", async ({ terminal }) => { - await expectVisible(terminal, "Act") - }) -}) - -// --------------------------------------------------------------------------- -// cline --plan -// Starts cline in Plan mode regardless of globalState -// --------------------------------------------------------------------------- -test.describe("cline --plan", () => { - test.use({ - program: { file: CLINE_BIN, args: ["--tui", "--plan"] }, - ...TERMINAL_WIDE, - env: clineEnv("default"), - }) - - test("starts in Plan mode", async ({ terminal }) => { - await expectVisible(terminal, "Plan") - }) -}) - -// --------------------------------------------------------------------------- -// cline --timeout āš ļø -// Current behavior: starts interactive mode and ignores timeout value -// --------------------------------------------------------------------------- -test.describe("cline --timeout (interactive mode, flag ignored) āš ļø", () => { - test.use({ - program: { file: CLINE_BIN, args: ["--tui", "--timeout", "30"] }, - ...TERMINAL_WIDE, - env: clineEnv("default"), - }) - - test("starts interactive mode (timeout value currently ignored)", async ({ terminal }) => { - await expectVisible(terminal, /what can i do|plan|act/i) - }) -}) - -// --------------------------------------------------------------------------- -// cline --model āš ļø -// Current behavior: starts interactive mode and ignores model value -// --------------------------------------------------------------------------- -test.describe("cline --model (interactive mode, flag ignored) āš ļø", () => { - test.use({ - program: { - file: CLINE_BIN, - args: ["--tui", "--model", "claude-3-5-haiku-20241022"], - }, - ...TERMINAL_WIDE, - env: clineEnv("default"), - }) - - test("starts interactive mode (model value currently ignored)", async ({ terminal }) => { - await expectVisible(terminal, /what can i do|plan|act/i) - }) -}) - -// --------------------------------------------------------------------------- -// cline --verbose āš ļø -// Current behavior: starts interactive mode and ignores verbose value -// --------------------------------------------------------------------------- -test.describe("cline --verbose (interactive mode, flag ignored) āš ļø", () => { - test.use({ - program: { file: CLINE_BIN, args: ["--tui", "--verbose"] }, - ...TERMINAL_WIDE, - env: clineEnv("default"), - }) - - test("starts interactive mode (verbose value currently ignored)", async ({ terminal }) => { - await expectVisible(terminal, /what can i do|plan|act/i) - }) -}) - -// --------------------------------------------------------------------------- -// cline -c / cline --cwd āš ļø -// Starts cline in interactive mode with the cwd present in the client footer -// --------------------------------------------------------------------------- -test.describe("cline --cwd ", () => { - test.use({ - program: { file: CLINE_BIN, args: ["--tui", "--cwd", "/tmp"] }, - ...TERMINAL_WIDE, - env: clineEnv("default"), - }) - - test("starts interactive mode with --cwd flag", async ({ terminal }) => { - await expectVisible(terminal, /what can i do|plan|act/i) - }) -}) - -test.describe("cline -c (short alias)", () => { - test.use({ - program: { file: CLINE_BIN, args: ["--tui", "-c", "/tmp"] }, - ...TERMINAL_WIDE, - env: clineEnv("default"), - }) - - test("starts interactive mode with -c flag", async ({ terminal }) => { - await expectVisible(terminal, /what can i do|plan|act/i) - }) -}) - -// --------------------------------------------------------------------------- -// cline --config -// Starts cline in interactive mode using settings from the custom config dir -// --------------------------------------------------------------------------- -test.describe("cline --config (claude-sonnet-4.6)", () => { - test.use({ - program: { - file: CLINE_BIN, - args: ["--tui", "--config", "configs/claude-sonnet-4.6"], - }, - ...TERMINAL_WIDE, - env: clineEnv("claude-sonnet-4.6"), - }) - - test("starts interactive mode with custom config directory", async ({ terminal }) => { - await expectVisible(terminal, /what can i do|plan|act/i) - }) -}) - -// --------------------------------------------------------------------------- -// cline --thinking āš ļø -// Starts cline in interactive mode with thinking turned on regardless of globalState -// (if thinking not supported, this flag is a no-op) -// --------------------------------------------------------------------------- -test.describe("cline --thinking āš ļø", () => { - test.use({ - program: { file: CLINE_BIN, args: ["--tui", "--thinking"] }, - ...TERMINAL_WIDE, - env: clineEnv("default"), - }) - - test("starts interactive mode with --thinking flag", async ({ terminal }) => { - await expectVisible(terminal, /what can i do|plan|act/i) - }) -}) - -// --------------------------------------------------------------------------- -// cline --reasoning-effort āš ļø -// Starts cline in interactive mode with reasoning turned on regardless of globalState -// (if reasoning not supported, this flag is a no-op) -// --------------------------------------------------------------------------- -test.describe("cline --reasoning-effort āš ļø", () => { - test.use({ - program: { file: CLINE_BIN, args: ["--tui", "--reasoning-effort", "high"] }, - ...TERMINAL_WIDE, - env: clineEnv("default"), - }) - - test("starts interactive mode with --reasoning-effort flag", async ({ terminal }) => { - await expectVisible(terminal, /what can i do|plan|act/i) - }) -}) - -// --------------------------------------------------------------------------- -// cline --max-consecutive-mistakes -// --------------------------------------------------------------------------- -test.describe("cline --max-consecutive-mistakes", () => { - test.use({ - program: { - file: CLINE_BIN, - args: ["--tui", "--max-consecutive-mistakes", "5"], - }, - ...TERMINAL_WIDE, - env: clineEnv("default"), - }) - - test("starts interactive mode with --max-consecutive-mistakes flag", async ({ terminal }) => { - await expectVisible(terminal, /what can i do|plan|act/i) - }) -}) - -// --------------------------------------------------------------------------- -// cline --double-check-completion -// --------------------------------------------------------------------------- -test.describe("cline --double-check-completion", () => { - test.use({ - program: { file: CLINE_BIN, args: ["--tui", "--double-check-completion"] }, - ...TERMINAL_WIDE, - env: clineEnv("default"), - }) - - test("starts interactive mode with --double-check-completion flag", async ({ terminal }) => { - await expectVisible(terminal, /what can i do|plan|act/i) - }) -}) diff --git a/tests/e2e/cli/cli/history.test.ts b/tests/e2e/cli/cli/history.test.ts deleted file mode 100644 index 6e429a1585..0000000000 --- a/tests/e2e/cli/cli/history.test.ts +++ /dev/null @@ -1,32 +0,0 @@ -// --------------------------------------------------------------------------- -// cline history — CLI tests -// -// Covers: -// - `cline history --limit X` — pagination limit -// - `cline history --page N` — page selection -// - `cline history --config` — custom config directory -// - `cline history --help` — help page -// --------------------------------------------------------------------------- - -import { test } from "@microsoft/tui-test" -import { CLINE_BIN, TERMINAL_WIDE } from "../helpers/constants.js" -import { clineEnv } from "../helpers/env.js" -import { expectVisible } from "../helpers/terminal.js" - -// --------------------------------------------------------------------------- -// cline history --help -// --------------------------------------------------------------------------- -test.describe("cline history --help", () => { - test.use({ - program: { file: CLINE_BIN, args: ["history", "--help"] }, - ...TERMINAL_WIDE, - env: clineEnv("default"), - }) - - test("shows history help page with all flags", async ({ terminal }) => { - await expectVisible(terminal, "Usage:") - await expectVisible(terminal, "--limit") - await expectVisible(terminal, "--page") - await expectVisible(terminal, "--config") - }) -}) diff --git a/tests/e2e/cli/configs/README.md b/tests/e2e/cli/configs/README.md deleted file mode 100644 index 91292cf597..0000000000 --- a/tests/e2e/cli/configs/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# Test Fixture Configs - -Each subdirectory here is a **Cline config directory** used by one or more test suites. -Tests point to a config via `clineEnv("")` in `tests/e2e/cli/helpers/env.ts`. - -## Available fixtures - -| Directory | Purpose | -|-----------|---------| -| `default/` | Minimal config used for local/non-live CLI TUI tests. | -| `unauthenticated/` | Explicitly empty config (no provider or API keys). | - -## Adding a new fixture - -1. Create `configs//data/globalState.json` with the desired state. -2. Create `configs//data/settings/cline_mcp_settings.json` (can be `{ "mcpServers": {} }`). -3. Reference it in tests with `clineEnv("")`. - -## Secrets - -API keys and secrets should never be committed. -If you create authenticated fixtures locally, keep `data/secrets.json` untracked. \ No newline at end of file diff --git a/tests/e2e/cli/configs/claude-sonnet-4.6/data/globalState.json b/tests/e2e/cli/configs/claude-sonnet-4.6/data/globalState.json deleted file mode 100644 index 85490dac6b..0000000000 --- a/tests/e2e/cli/configs/claude-sonnet-4.6/data/globalState.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "remoteRulesToggles": {}, - "remoteWorkflowToggles": {}, - "welcomeViewCompleted": true, - "actModeOpenRouterModelInfo": { - "name": "Anthropic: Claude Sonnet 4.6", - "maxTokens": 128000, - "contextWindow": 200000, - "supportsImages": true, - "supportsPromptCache": true, - "inputPrice": 3, - "outputPrice": 15, - "cacheWritesPrice": 3.75, - "cacheReadsPrice": 0.3, - "description": "Sonnet 4.6 is Anthropic's most capable Sonnet-class model yet, with frontier performance across coding, agents, and professional work. It excels at iterative development, complex codebase navigation, end-to-end project management with memory, polished document creation, and confident computer use for web QA and workflow automation.", - "thinkingConfig": { - "maxBudget": 6000 - } - }, - "planModeOpenRouterModelInfo": { - "name": "Anthropic: Claude Sonnet 4.6", - "maxTokens": 128000, - "contextWindow": 200000, - "supportsImages": true, - "supportsPromptCache": true, - "inputPrice": 3, - "outputPrice": 15, - "cacheWritesPrice": 3.75, - "cacheReadsPrice": 0.3, - "description": "Sonnet 4.6 is Anthropic's most capable Sonnet-class model yet, with frontier performance across coding, agents, and professional work. It excels at iterative development, complex codebase navigation, end-to-end project management with memory, polished document creation, and confident computer use for web QA and workflow automation.", - "thinkingConfig": { - "maxBudget": 6000 - } - }, - "actModeApiProvider": "cline", - "planModeApiProvider": "cline", - "actModeClineModelId": "anthropic/claude-sonnet-4.6", - "planModeClineModelId": "anthropic/claude-sonnet-4.6", - "openAiHeaders": {}, - "sapAiCoreUseOrchestrationMode": true, - "ocaMode": "internal" -} diff --git a/tests/e2e/cli/configs/claude-sonnet-4.6/data/settings/cline_mcp_settings.json b/tests/e2e/cli/configs/claude-sonnet-4.6/data/settings/cline_mcp_settings.json deleted file mode 100644 index 47925bfeca..0000000000 --- a/tests/e2e/cli/configs/claude-sonnet-4.6/data/settings/cline_mcp_settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "mcpServers": {} -} diff --git a/tests/e2e/cli/configs/default/data/globalState.json b/tests/e2e/cli/configs/default/data/globalState.json deleted file mode 100644 index 90a8b7e02b..0000000000 --- a/tests/e2e/cli/configs/default/data/globalState.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "remoteRulesToggles": {}, - "remoteWorkflowToggles": {}, - "actModeOpenRouterModelInfo": { - "name": "Anthropic: Claude Sonnet 4.6", - "maxTokens": 128000, - "contextWindow": 200000, - "supportsImages": true, - "supportsPromptCache": true, - "inputPrice": 3, - "outputPrice": 15, - "cacheWritesPrice": 3.75, - "cacheReadsPrice": 0.3, - "description": "Sonnet 4.6 is Anthropic's most capable Sonnet-class model yet, with frontier performance across coding, agents, and professional work. It excels at iterative development, complex codebase navigation, end-to-end project management with memory, polished document creation, and confident computer use for web QA and workflow automation.", - "thinkingConfig": { - "maxBudget": 6000 - } - }, - "planModeOpenRouterModelInfo": { - "name": "Anthropic: Claude Sonnet 4.6", - "maxTokens": 128000, - "contextWindow": 200000, - "supportsImages": true, - "supportsPromptCache": true, - "inputPrice": 3, - "outputPrice": 15, - "cacheWritesPrice": 3.75, - "cacheReadsPrice": 0.3, - "description": "Sonnet 4.6 is Anthropic's most capable Sonnet-class model yet, with frontier performance across coding, agents, and professional work. It excels at iterative development, complex codebase navigation, end-to-end project management with memory, polished document creation, and confident computer use for web QA and workflow automation.", - "thinkingConfig": { - "maxBudget": 6000 - } - }, - "actModeApiProvider": "cline", - "planModeApiProvider": "cline", - "actModeClineModelId": "anthropic/claude-sonnet-4.6", - "planModeClineModelId": "anthropic/claude-sonnet-4.6", - "welcomeViewCompleted": true, - "actModeThinkingBudgetTokens": 0, - "planModeThinkingBudgetTokens": 0, - "autoApprovalSettings": { - "version": 19, - "enabled": true, - "favorites": [], - "maxRequests": 20, - "actions": { - "readFiles": true, - "readFilesExternally": false, - "editFiles": false, - "editFilesExternally": false, - "executeSafeCommands": true, - "executeAllCommands": false, - "useBrowser": false, - "useMcp": true - }, - "enableNotifications": false - }, - "primaryRootIndex": 0, - "globalWorkflowToggles": {}, - "globalClineRulesToggles": {}, - "isNewUser": false -} diff --git a/tests/e2e/cli/configs/default/data/settings/cline_mcp_settings.json b/tests/e2e/cli/configs/default/data/settings/cline_mcp_settings.json deleted file mode 100644 index 47925bfeca..0000000000 --- a/tests/e2e/cli/configs/default/data/settings/cline_mcp_settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "mcpServers": {} -} diff --git a/tests/e2e/cli/configs/unauthenticated/data/globalState.json b/tests/e2e/cli/configs/unauthenticated/data/globalState.json deleted file mode 100644 index 9a7f30fdcd..0000000000 --- a/tests/e2e/cli/configs/unauthenticated/data/globalState.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "apiProvider": null, - "apiKey": null, - "openRouterApiKey": null, - "awsAccessKey": null, - "awsSecretKey": null, - "awsSessionToken": null, - "awsRegion": null, - "vertexProjectId": null, - "vertexRegion": null, - "openAiBaseUrl": null, - "openAiApiKey": null, - "openAiModelId": null, - "ollamaModelId": null, - "ollamaBaseUrl": null, - "lmStudioModelId": null, - "lmStudioBaseUrl": null, - "anthropicBaseUrl": null, - "geminiApiKey": null, - "openAiNativeApiKey": null, - "deepSeekApiKey": null, - "mistralApiKey": null, - "unboundApiKey": null, - "requestyApiKey": null, - "togetherApiKey": null, - "qwenApiKey": null, - "doubaoApiKey": null, - "liteLlmBaseUrl": null, - "liteLlmApiKey": null, - "liteLlmModelId": null, - "clineApiKey": null, - "taskHistory": [], - "remoteRulesToggles": {}, - "remoteWorkflowToggles": {}, - "actModeThinkingBudgetTokens": 0, - "planModeThinkingBudgetTokens": 0, - "welcomeViewCompleted": false -} diff --git a/tests/e2e/cli/configs/unauthenticated/data/settings/cline_mcp_settings.json b/tests/e2e/cli/configs/unauthenticated/data/settings/cline_mcp_settings.json deleted file mode 100644 index 47925bfeca..0000000000 --- a/tests/e2e/cli/configs/unauthenticated/data/settings/cline_mcp_settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "mcpServers": {} -} diff --git a/tests/e2e/cli/flags.test.ts b/tests/e2e/cli/flags.test.ts deleted file mode 100644 index 523702798a..0000000000 --- a/tests/e2e/cli/flags.test.ts +++ /dev/null @@ -1,141 +0,0 @@ -import { test } from "@microsoft/tui-test" -import { CLINE_BIN } from "./helpers/constants.js" -import { expectVisible, testEnv } from "./utils.js" - -const HELP_TERMINAL = { columns: 120, rows: 50 } - -// =========================================================================== -// Root-level flag descriptions -// =========================================================================== -test.describe("root flag descriptions", () => { - test.use({ - program: { file: CLINE_BIN, args: ["--help"] }, - env: testEnv("default"), - ...HELP_TERMINAL, - }) - - test("all root flags have correct descriptions", async ({ terminal }) => { - await expectVisible(terminal, [ - "Run in act mode", - "Run in plan mode", - "yolo mode", - "timeout in seconds", - "Model to use", - "verbose output", - "Working directory", - "Configuration directory", - "extended thinking", - "Reasoning effort", - "consecutive mistakes", - "Output messages as JSON", - "first completion attempt", - "ACP", - "legacy terminal UI", - "Resume an existing task", - ]) - }) - - test("all short flag aliases are shown", async ({ terminal }) => { - await expectVisible(terminal, ["-a", "-p", "-y", "-t", "-m", "-v", "-c", "-T"]) - }) -}) - -// =========================================================================== -// Task subcommand flag descriptions -// =========================================================================== -test.describe("task flag descriptions", () => { - test.use({ - program: { file: CLINE_BIN, args: ["task", "--help"] }, - env: testEnv("default"), - ...HELP_TERMINAL, - }) - - test("all task flags have correct descriptions", async ({ terminal }) => { - await expectVisible(terminal, [ - "Run in act mode", - "Run in plan mode", - "yolo mode", - "timeout in seconds", - "Model to use", - "verbose output", - "Working directory", - "configuration directory", - "extended thinking", - "Reasoning effort", - "consecutive mistakes", - "Output messages as JSON", - "first completion attempt", - "Resume an existing task", - ]) - }) -}) - -// =========================================================================== -// History subcommand flag descriptions -// =========================================================================== -test.describe("history flag descriptions", () => { - test.use({ - program: { file: CLINE_BIN, args: ["history", "--help"] }, - env: testEnv("default"), - ...HELP_TERMINAL, - }) - - test("all history flags have correct descriptions", async ({ terminal }) => { - await expectVisible(terminal, "Number of tasks to show") - await expectVisible(terminal, "Page number") - await expectVisible(terminal, "configuration directory") - }) -}) - -// =========================================================================== -// Auth subcommand flag descriptions -// =========================================================================== -test.describe("auth flag descriptions", () => { - test.use({ - program: { file: CLINE_BIN, args: ["auth", "--help"] }, - env: testEnv("default"), - ...HELP_TERMINAL, - }) - - test("all auth flags have correct descriptions", async ({ terminal }) => { - await expectVisible(terminal, [ - "Provider ID", - "API key", - "Model ID", - "Base URL", - "verbose output", - "Working directory", - "configuration directory", - ]) - }) -}) - -// =========================================================================== -// Config subcommand flag descriptions -// =========================================================================== -test.describe("config flag descriptions", () => { - test.use({ - program: { file: CLINE_BIN, args: ["config", "--help"] }, - env: testEnv("default"), - ...HELP_TERMINAL, - }) - - test("config --config flag description", async ({ terminal }) => { - await expectVisible(terminal, "configuration directory") - }) -}) - -// =========================================================================== -// Update subcommand flag descriptions -// =========================================================================== -test.describe("update flag descriptions", () => { - test.use({ - program: { file: CLINE_BIN, args: ["update", "--help"] }, - env: testEnv("default"), - ...HELP_TERMINAL, - }) - - test("update --verbose flag description", async ({ terminal }) => { - await expectVisible(terminal, "verbose output") - }) -}) diff --git a/tests/e2e/cli/headless/headless.test.ts b/tests/e2e/cli/headless/headless.test.ts deleted file mode 100644 index 64b96335a3..0000000000 --- a/tests/e2e/cli/headless/headless.test.ts +++ /dev/null @@ -1,86 +0,0 @@ -// --------------------------------------------------------------------------- -// CLI headless use cases (cline -y / cline --json / piped stdin) -// -// These tests run cline as a child process (no TUI harness) and assert on -// stdout, stderr, and exit codes. -// -// Tests tagged @live require a configured provider and are skipped by default. -// Run them with: CLINE_BIN=... npm test -- headless @live -// --------------------------------------------------------------------------- - -import { test } from "@microsoft/tui-test" -import { CLINE_BIN, TERMINAL_WIDE } from "../helpers/constants.js" -import { clineEnv } from "../helpers/env.js" -import { expectVisible } from "../helpers/terminal.js" - -// --------------------------------------------------------------------------- -// cline -y "tell me a joke" -// Golden path: prints "Task started" then LLM output, then exits 0. -// Unauthenticated: prints "Not authenticated" and exits 1. -// --------------------------------------------------------------------------- -test.describe("cline -y (headless yolo mode) — unauthenticated", () => { - test.use({ - program: { file: CLINE_BIN, args: ["-y", "tell me a joke"] }, - ...TERMINAL_WIDE, - env: clineEnv("unauthenticated"), - }) - - test("prints Not authenticated and exits 1", async ({ terminal }) => { - await expectVisible(terminal, /not authenticated/i) - }) -}) - -// --------------------------------------------------------------------------- -// echo "max paulus" | cline -y "print only the second word I gave you" -// Piped stdin test — uses TUI harness with stdin pre-written -// --------------------------------------------------------------------------- -test.describe("piped stdin | cline -y — unauthenticated", () => { - test.use({ - program: { - file: "sh", - args: ["-c", `echo "max paulus" | ${CLINE_BIN} -y "print only the second word I gave you"`], - }, - ...TERMINAL_WIDE, - env: clineEnv("unauthenticated"), - }) - - test("prints Not Authenticated for piped stdin", async ({ terminal }) => { - await expectVisible(terminal, /not authenticated/i) - }) -}) - -// --------------------------------------------------------------------------- -// cline -y --verbose "tell me a joke" 2>&1 -// Golden path: prints task started, prompt, api request, reasoning, task_completion lines -// --------------------------------------------------------------------------- -test.describe("cline -y --verbose — unauthenticated", () => { - test.use({ - program: { - file: "sh", - args: ["-c", `${CLINE_BIN} -y --verbose "tell me a joke" 2>&1`], - }, - ...TERMINAL_WIDE, - env: clineEnv("unauthenticated"), - }) - - test("shows verbose output or not-authenticated", async ({ terminal }) => { - await expectVisible(terminal, /not authenticated|verbose|task/i) - }) -}) - -// --------------------------------------------------------------------------- -// cline --json "tell me a joke" -// All output must conform to JSON (one JSON object per line) -// --------------------------------------------------------------------------- -test.describe("cline --json — unauthenticated", () => { - test.use({ - program: { file: CLINE_BIN, args: ["--json", "tell me a joke"] }, - ...TERMINAL_WIDE, - env: clineEnv("unauthenticated"), - }) - - test("outputs JSON error for unauthenticated", async ({ terminal }) => { - // cline --json when unauthenticated outputs a plain "Not authenticated" message - await expectVisible(terminal, /not authenticated/i) - }) -}) diff --git a/tests/e2e/cli/help.test.ts b/tests/e2e/cli/help.test.ts deleted file mode 100644 index ea63d46815..0000000000 --- a/tests/e2e/cli/help.test.ts +++ /dev/null @@ -1,226 +0,0 @@ -import { test } from "@microsoft/tui-test" -import { CLINE_BIN } from "./helpers/constants.js" -import { expectVisible, testEnv } from "./utils.js" - -const HELP_TERMINAL = { columns: 120, rows: 50 } - -// =========================================================================== -// cline --help (root help) -// =========================================================================== -test.describe("cline --help", () => { - test.use({ - program: { file: CLINE_BIN, args: ["--help"] }, - env: testEnv("claude-sonnet-4.6"), - ...HELP_TERMINAL, - }) - - test("shows Usage line and lists all subcommands", async ({ terminal }) => { - await expectVisible(terminal, [ - "Usage:", - "task|t", - "history|h", - "config [options]", - "auth [options]", - "version", - "update [options]", - "dev ", - ]) - }) - - test("shows all root-level option flags", async ({ terminal }) => { - await expectVisible(terminal, [ - "--act", - "--plan", - "--yolo", - "--timeout", - "--model", - "--verbose", - "--cwd", - "--config", - "--thinking", - "--reasoning-effort", - "--max-consecutive-mistakes", - "--json", - "--double-check-completion", - "--acp", - "--tui", - "--taskId", - ]) - }) -}) - -// =========================================================================== -// cline -h (short help flag) -// =========================================================================== -test.describe("cline -h", () => { - test.use({ - program: { file: CLINE_BIN, args: ["-h"] }, - env: testEnv("claude-sonnet-4.6"), - ...HELP_TERMINAL, - }) - - test("shows Usage line with short flag", async ({ terminal }) => { - await expectVisible(terminal, "Usage:") - }) -}) - -// =========================================================================== -// cline task --help -// =========================================================================== -test.describe("cline task --help", () => { - test.use({ - program: { file: CLINE_BIN, args: ["task", "--help"] }, - env: testEnv("claude-sonnet-4.6"), - ...HELP_TERMINAL, - }) - - test("shows task usage, prompt argument, and all flags", async ({ terminal }) => { - await expectVisible(terminal, [ - "Usage:", - "prompt", - "--act", - "--plan", - "--yolo", - "--timeout", - "--model", - "--verbose", - "--cwd", - "--config", - "--thinking", - "--reasoning-effort", - "--max-consecutive-mistakes", - "--json", - "--double-check-completion", - "--taskId", - ]) - }) -}) - -// =========================================================================== -// cline t --help (task alias) -// =========================================================================== -test.describe("cline t --help (task alias)", () => { - test.use({ - program: { file: CLINE_BIN, args: ["t", "--help"] }, - env: testEnv("claude-sonnet-4.6"), - ...HELP_TERMINAL, - }) - - test("shows task usage and flags via alias", async ({ terminal }) => { - await expectVisible(terminal, ["Usage:", "--yolo"]) - }) -}) - -// =========================================================================== -// cline history --help -// =========================================================================== -test.describe("cline history --help", () => { - test.use({ - program: { file: CLINE_BIN, args: ["history", "--help"] }, - env: testEnv("claude-sonnet-4.6"), - ...HELP_TERMINAL, - }) - - test("shows history usage and all flags", async ({ terminal }) => { - await expectVisible(terminal, ["Usage:", "--limit", "--page", "--config"]) - }) -}) - -// =========================================================================== -// cline h --help (history alias) -// =========================================================================== -test.describe("cline h --help (history alias)", () => { - test.use({ - program: { file: CLINE_BIN, args: ["h", "--help"] }, - env: testEnv("claude-sonnet-4.6"), - ...HELP_TERMINAL, - }) - - test("shows history usage and flags via alias", async ({ terminal }) => { - await expectVisible(terminal, ["Usage:", "--limit"]) - }) -}) - -// =========================================================================== -// cline config --help -// =========================================================================== -test.describe("cline config --help", () => { - test.use({ - program: { file: CLINE_BIN, args: ["config", "--help"] }, - env: testEnv("claude-sonnet-4.6"), - ...HELP_TERMINAL, - }) - - test("shows config usage and --config flag", async ({ terminal }) => { - await expectVisible(terminal, ["Usage:", "--config"]) - }) -}) - -// =========================================================================== -// cline auth --help -// =========================================================================== -test.describe("cline auth --help", () => { - test.use({ - program: { file: CLINE_BIN, args: ["auth", "--help"] }, - env: testEnv("claude-sonnet-4.6"), - ...HELP_TERMINAL, - }) - - test("shows auth usage and all flags", async ({ terminal }) => { - await expectVisible(terminal, [ - "Usage:", - "--provider", - "--apikey", - "--modelid", - "--baseurl", - "--verbose", - "--cwd", - "--config", - ]) - }) -}) - -// =========================================================================== -// cline version --help -// =========================================================================== -test.describe("cline version --help", () => { - test.use({ - program: { file: CLINE_BIN, args: ["version", "--help"] }, - env: testEnv("claude-sonnet-4.6"), - ...HELP_TERMINAL, - }) - - test("shows version command usage", async ({ terminal }) => { - await expectVisible(terminal, "Usage:") - }) -}) - -// =========================================================================== -// cline update --help -// =========================================================================== -test.describe("cline update --help", () => { - test.use({ - program: { file: CLINE_BIN, args: ["update", "--help"] }, - env: testEnv("claude-sonnet-4.6"), - ...HELP_TERMINAL, - }) - - test("shows update usage and --verbose flag", async ({ terminal }) => { - await expectVisible(terminal, ["Usage:", "--verbose"]) - }) -}) - -// =========================================================================== -// cline dev --help -// =========================================================================== -test.describe("cline dev --help", () => { - test.use({ - program: { file: CLINE_BIN, args: ["dev", "--help"] }, - env: testEnv("claude-sonnet-4.6"), - ...HELP_TERMINAL, - }) - - test("shows dev usage and lists log subcommand", async ({ terminal }) => { - await expectVisible(terminal, ["Usage:", "log"]) - }) -}) diff --git a/tests/e2e/cli/helpers/constants.ts b/tests/e2e/cli/helpers/constants.ts deleted file mode 100644 index b1b0df8d38..0000000000 --- a/tests/e2e/cli/helpers/constants.ts +++ /dev/null @@ -1,24 +0,0 @@ -// --------------------------------------------------------------------------- -// Shared constants for all test files. -// -// tui-test workers run with a minimal PATH, so we resolve the binary -// explicitly rather than relying on PATH lookup at test runtime. -// --------------------------------------------------------------------------- - -import fs from "fs" -import path from "path" - -function resolveClineBin(): string { - const localBin = path.resolve(process.cwd(), "..", "..", "..", "cli", "dist", "cli.mjs") - if (fs.existsSync(localBin)) { - return localBin - } - - throw new Error("Unable to resolve cline binary. build cli/dist/cli.mjs.") -} - -export const CLINE_BIN = resolveClineBin() - -// Standard terminal dimensions used across test suites -export const TERMINAL_WIDE = { columns: 120, rows: 50 } as const -export const TERMINAL_NARROW = { columns: 80, rows: 30 } as const diff --git a/tests/e2e/cli/helpers/env.ts b/tests/e2e/cli/helpers/env.ts deleted file mode 100644 index 7274308503..0000000000 --- a/tests/e2e/cli/helpers/env.ts +++ /dev/null @@ -1,44 +0,0 @@ -// --------------------------------------------------------------------------- -// Environment helpers for test setup. -// -// Usage: -// test.use({ env: clineEnv("default") }); -// test.use({ env: clineEnv("claude-sonnet-4.6") }); -// test.use({ env: clineEnv("/absolute/path/to/config") }); -// --------------------------------------------------------------------------- - -import path from "path" - -const TEST_SUITE_ROOT = new URL("../", import.meta.url).pathname - -/** - * Build the process environment for a cline test. - * - * @param configDir - Named config under `configs/`, or an absolute path. - * @param extra - Additional env vars to merge in (override defaults). - */ -export function clineEnv(configDir: string, extra: NodeJS.ProcessEnv = {}): NodeJS.ProcessEnv { - const clinePath = path.isAbsolute(configDir) ? configDir : path.join(TEST_SUITE_ROOT, "configs", configDir) - - // Remove CI env var so Ink's `is-in-ci` check doesn't disable interactive - // rendering. When CI=true (set by GitHub Actions / act), Ink treats the - // environment as non-interactive and skips rendering to stdout — even - // inside a real PTY — which causes tui-test traces to be empty. - const { CI: _ci, ...cleanEnv } = process.env - - return { - ...cleanEnv, - CLINE_TELEMETRY_DISABLED: "1", - CLINE_DIR: clinePath, - NO_UPDATE_NOTIFIER: "1", - CLINE_NO_AUTO_UPDATE: "1", - ...extra, - } -} - -/** - * @deprecated Use `clineEnv` instead. Kept for backward compatibility. - */ -export function testEnv(configDir: string): NodeJS.ProcessEnv { - return clineEnv(configDir) -} diff --git a/tests/e2e/cli/helpers/page-objects/auth.ts b/tests/e2e/cli/helpers/page-objects/auth.ts deleted file mode 100644 index 5cda65eca5..0000000000 --- a/tests/e2e/cli/helpers/page-objects/auth.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { Terminal } from "@microsoft/tui-test/lib/terminal/term" -import { expectVisible } from "../terminal.js" - -export async function waitForAuthScreen(terminal: Terminal): Promise { - await expectVisible(terminal, ["Sign in with Cline", "Sign in with ChatGPT", "Use your own API key"]) -} diff --git a/tests/e2e/cli/helpers/page-objects/chat.ts b/tests/e2e/cli/helpers/page-objects/chat.ts deleted file mode 100644 index 0c5b398a9c..0000000000 --- a/tests/e2e/cli/helpers/page-objects/chat.ts +++ /dev/null @@ -1,84 +0,0 @@ -// --------------------------------------------------------------------------- -// Page-object helpers for the main Cline chat view. -// --------------------------------------------------------------------------- - -import type { Terminal } from "@microsoft/tui-test/lib/terminal/term" -import { expectVisible, typeAndSubmit } from "../terminal.js" - -/** Wait for the main chat view to be ready */ -export async function waitForChatReady(terminal: Terminal): Promise { - await expectVisible(terminal, "What can I do for you?") -} - -/** Submit a prompt in the chat input */ -export async function submitPrompt(terminal: Terminal, prompt: string, delay = 500): Promise { - await waitForChatReady(terminal) - await typeAndSubmit(terminal, prompt, delay) -} - -/** Toggle between Plan and Act mode by pressing Tab */ -export async function togglePlanAct(terminal: Terminal): Promise { - terminal.write("\t") - // Wait for the mode indicator to update rather than sleeping a fixed amount - await expectVisible(terminal, /ā— Plan|ā— Act/) -} - -/** Toggle auto-approve-all with Shift+Tab */ -export async function toggleAutoApproveAll(terminal: Terminal): Promise { - terminal.write("\x1b[Z") // shift tab -} - -/** Wait for "Task completed" to appear */ -export async function waitForTaskCompleted(terminal: Terminal, timeout = 60_000): Promise { - await expectVisible(terminal, "Task completed", { timeout }) -} - -/** Wait for the "Start New Task (1)" and "Exit (2)" buttons */ -export async function waitForTaskButtons(terminal: Terminal, timeout = 60_000): Promise { - await expectVisible(terminal, ["Start New Task", "Exit"], { timeout }) -} - -/** Press "1" to start a new task after task completion */ -export async function startNewTask(terminal: Terminal): Promise { - terminal.write("1") -} - -/** Press "2" to exit after task completion */ -export async function exitAfterTask(terminal: Terminal): Promise { - terminal.write("2") -} - -/** Wait for a permission prompt and approve it (press "1" / Save) */ -export async function approvePermission(terminal: Terminal): Promise { - terminal.write("1") -} - -/** Wait for a permission prompt and reject it (press "2" / Reject) */ -export async function rejectPermission(terminal: Terminal): Promise { - terminal.write("2") -} - -/** Navigate to /settings */ -export async function openSettings(terminal: Terminal): Promise { - await waitForChatReady(terminal) - await typeAndSubmit(terminal, "/settings") - await expectVisible(terminal, "Settings (Esc to close)") -} - -/** Navigate to /history */ -export async function openHistory(terminal: Terminal): Promise { - await waitForChatReady(terminal) - await typeAndSubmit(terminal, "/history") -} - -/** Navigate to /models */ -export async function openModels(terminal: Terminal): Promise { - await waitForChatReady(terminal) - await typeAndSubmit(terminal, "/models") -} - -/** Navigate to /skills */ -export async function openSkills(terminal: Terminal): Promise { - await waitForChatReady(terminal) - await typeAndSubmit(terminal, "/skills") -} diff --git a/tests/e2e/cli/helpers/page-objects/settings.ts b/tests/e2e/cli/helpers/page-objects/settings.ts deleted file mode 100644 index 21af8fa36d..0000000000 --- a/tests/e2e/cli/helpers/page-objects/settings.ts +++ /dev/null @@ -1,69 +0,0 @@ -// --------------------------------------------------------------------------- -// Page-object helpers for the /settings view. -// --------------------------------------------------------------------------- - -import type { Terminal } from "@microsoft/tui-test/lib/terminal/term" -import { expectVisible } from "../terminal.js" - -const TAB_ORDER = ["API", "Auto-approve", "Features", "Account", "Other"] as const - -export type SettingsTab = (typeof TAB_ORDER)[number] - -/** - * Navigate to a specific settings tab by pressing Right from the API tab (index 0). - * Waits for each tab's content to appear before pressing the next key, making - * navigation deterministic regardless of machine speed. - */ -export async function goToSettingsTab(terminal: Terminal, tab: SettingsTab): Promise { - const targetIndex = TAB_ORDER.indexOf(tab) - for (let i = 0; i < targetIndex; i++) { - terminal.keyRight() - // Wait for the next tab's content to appear before pressing again - await assertTabContent(terminal, TAB_ORDER[i + 1]) - } -} - -/** Assert the API tab content is visible */ -export async function assertApiTab(terminal: Terminal): Promise { - await expectVisible(terminal, ["Provider:", "Model ID:"]) -} - -/** Assert the Auto-approve tab content is visible */ -export async function assertAutoApproveTab(terminal: Terminal): Promise { - await expectVisible(terminal, ["Read project files", "Execute safe commands", "Edit project files"]) -} - -/** Assert the Features tab content is visible */ -export async function assertFeaturesTab(terminal: Terminal): Promise { - await expectVisible(terminal, ["Subagents", "Web tools", "Double-check completion"]) -} - -/** Assert the Account tab content is visible */ -export async function assertAccountTab(terminal: Terminal): Promise { - // The account tab shows sign-in options when not authenticated to Cline - await expectVisible(terminal, /sign in|sign out/i) -} - -/** Assert the Other tab content is visible */ -export async function assertOtherTab(terminal: Terminal): Promise { - await expectVisible(terminal, ["Preferred language:", "Cline v"]) -} - -/** - * Assert the content for a given tab is visible. - * Used internally by goToSettingsTab to confirm navigation landed correctly. - */ -export async function assertTabContent(terminal: Terminal, tab: SettingsTab): Promise { - switch (tab) { - case "API": - return assertApiTab(terminal) - case "Auto-approve": - return assertAutoApproveTab(terminal) - case "Features": - return assertFeaturesTab(terminal) - case "Account": - return assertAccountTab(terminal) - case "Other": - return assertOtherTab(terminal) - } -} diff --git a/tests/e2e/cli/helpers/process.ts b/tests/e2e/cli/helpers/process.ts deleted file mode 100644 index 7e9f60e76c..0000000000 --- a/tests/e2e/cli/helpers/process.ts +++ /dev/null @@ -1,100 +0,0 @@ -// --------------------------------------------------------------------------- -// Process-level helpers for headless / contract-style CLI tests. -// -// These helpers spawn cline as a child process and return stdout, stderr, -// and exit code — without going through the TUI harness. Use these for: -// - Exit code assertions -// - Pure stdout/stderr contract tests -// - Piped stdin tests (echo "..." | cline -y ...) -// - JSON output validation -// - Timeout behavior -// --------------------------------------------------------------------------- - -import { SpawnSyncOptions, spawnSync } from "child_process" -import { CLINE_BIN } from "./constants.js" -import { clineEnv } from "./env.js" - -export interface RunResult { - stdout: string - stderr: string - exitCode: number | null - combined: string -} - -export interface RunOptions { - /** Named config dir under `configs/`, or absolute path */ - config?: string - /** Additional env vars */ - env?: NodeJS.ProcessEnv - /** Stdin to pipe into the process */ - stdin?: string - /** Timeout in ms (default: 30_000) */ - timeout?: number - /** Working directory for the spawned process */ - cwd?: string -} - -/** - * Run `cline [args]` synchronously and return stdout/stderr/exitCode. - * - * Suitable for deterministic, fast-exiting commands like: - * cline --help, cline --version, cline auth -p ... -k ..., etc. - */ -export function runCline(args: string[], opts: RunOptions = {}): RunResult { - const { config = "default", env: extraEnv = {}, stdin, timeout = 30_000, cwd = process.cwd() } = opts - - const spawnOpts: SpawnSyncOptions = { - encoding: "utf8", - timeout, - cwd, - env: { ...clineEnv(config), ...extraEnv }, - input: stdin, - } - - const result = spawnSync(CLINE_BIN, args, spawnOpts) - - const stdout = (result.stdout as string) ?? "" - const stderr = (result.stderr as string) ?? "" - - return { - stdout, - stderr, - exitCode: result.status, - combined: stdout + stderr, - } -} - -/** - * Assert that a runCline result exited with the expected code. - * Throws a descriptive error if the code doesn't match. - */ -export function assertExitCode(result: RunResult, expected: number, label = ""): void { - if (result.exitCode !== expected) { - const prefix = label ? `[${label}] ` : "" - throw new Error( - `${prefix}Expected exit code ${expected}, got ${result.exitCode}.\n` + - `stdout: ${result.stdout}\n` + - `stderr: ${result.stderr}`, - ) - } -} - -/** - * Assert that stdout/stderr contains a given string or matches a regex. - */ -export function assertOutput( - result: RunResult, - pattern: string | RegExp, - stream: "stdout" | "stderr" | "combined" = "combined", - label = "", -): void { - const text = result[stream] - const matches = typeof pattern === "string" ? text.includes(pattern) : pattern.test(text) - - if (!matches) { - const prefix = label ? `[${label}] ` : "" - throw new Error( - `${prefix}Expected ${stream} to match ${pattern}.\n` + `stdout: ${result.stdout}\n` + `stderr: ${result.stderr}`, - ) - } -} diff --git a/tests/e2e/cli/helpers/terminal.ts b/tests/e2e/cli/helpers/terminal.ts deleted file mode 100644 index 86614a4dbc..0000000000 --- a/tests/e2e/cli/helpers/terminal.ts +++ /dev/null @@ -1,53 +0,0 @@ -// --------------------------------------------------------------------------- -// Terminal interaction helpers for TUI tests. -// -// These wrap common tui-test patterns to keep test bodies readable. -// --------------------------------------------------------------------------- - -import { expect } from "@microsoft/tui-test" -import type { Terminal } from "@microsoft/tui-test/lib/terminal/term" - -// --------------------------------------------------------------------------- -// Core wait / assertion helpers -// --------------------------------------------------------------------------- - -const maxTimeoutMs = 10_000 - -/** - * Wait for one or more text strings/regexes to appear on screen. - * - * @example - * await expectVisible(terminal, "What can I do for you?"); - * await expectVisible(terminal, ["/help", "/settings"], { timeout: 5000 }); - */ -export async function expectVisible( - terminal: Terminal, - text: string | RegExp | (string | RegExp)[], - options: { timeout?: number } = { timeout: maxTimeoutMs }, -): Promise { - const items = Array.isArray(text) ? text : [text] - await Promise.all( - items.map((t) => { - // tui-test uses String.prototype.matchAll internally, which requires - // the global flag on RegExp arguments. Ensure it is set. - if (t instanceof RegExp && !t.flags.includes("g")) { - t = new RegExp(t.source, `${t.flags}g`) - } - const locator = terminal.getByText(t, { - full: true, - strict: false, - }) - return expect(locator).toBeVisible(options.timeout !== undefined ? { timeout: options.timeout } : undefined) - }), - ) -} - -/** - * Type text into the terminal and press Enter. - * Waits `delay` ms between writing and submitting to let the UI settle. - */ -export async function typeAndSubmit(terminal: Terminal, text: string, delay = 500): Promise { - terminal.write(text) - await new Promise((resolve) => setTimeout(resolve, delay)) - terminal.submit() -} diff --git a/tests/e2e/cli/interactive.test.ts b/tests/e2e/cli/interactive.test.ts deleted file mode 100644 index 0477c04917..0000000000 --- a/tests/e2e/cli/interactive.test.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { test } from "@microsoft/tui-test" -import { CLINE_BIN } from "./helpers/constants.js" -import { assertApiTab, assertAutoApproveTab, assertFeaturesTab, assertOtherTab } from "./helpers/page-objects/settings.js" -import { expectVisible, testEnv, typeAndSubmit } from "./utils.js" - -test.describe("cline interactive basics", () => { - test.use({ - program: { file: CLINE_BIN, args: ["--tui"] }, - rows: 50, - columns: 120, - env: testEnv("default"), - }) - - test("shows logo, prompt, mode toggles, and hints", async ({ terminal }) => { - await expectVisible(terminal, ["What can I do for you?", "@@@@@@", "Plan", "Act", "@ for files", "Tab"]) - }) - - test("shows slash commands after / input", async ({ terminal }) => { - await expectVisible(terminal, "What can I do for you?") - await typeAndSubmit(terminal, "/") - await expectVisible(terminal, ["/help", "/settings", "/models"], { - timeout: 5000, - }) - }) - - test("opens /settings and navigates tabs with left/right arrows", async ({ terminal }) => { - await expectVisible(terminal, "What can I do for you?") - - await typeAndSubmit(terminal, "/settings") - - await expectVisible(terminal, "Settings (Esc to close)") - - // API tab (default) - await assertApiTab(terminal) - await expectVisible(terminal, "Use separate models for Plan and Act") - - // Auto-approve tab - terminal.keyRight() - await assertAutoApproveTab(terminal) - - // Features tab - terminal.keyRight() - await assertFeaturesTab(terminal) - await expectVisible(terminal, ["Strict plan mode", "Native tool call", "Parallel tool calling"]) - - // Account tab - terminal.keyRight() - await expectVisible(terminal, ["Sign in to access Cline features", "Sign in with Cline"]) - - // Other tab - terminal.keyRight() - await assertOtherTab(terminal) - - // Left once from Other should move back to Account - terminal.keyLeft() - await expectVisible(terminal, "Sign in to access Cline features") - - // Two rights from Account should wrap back to API - terminal.keyRight() - await assertOtherTab(terminal) - terminal.keyRight() - await assertApiTab(terminal) - }) -}) diff --git a/tests/e2e/cli/interactive/chat.test.ts b/tests/e2e/cli/interactive/chat.test.ts deleted file mode 100644 index 076e99679e..0000000000 --- a/tests/e2e/cli/interactive/chat.test.ts +++ /dev/null @@ -1,219 +0,0 @@ -// --------------------------------------------------------------------------- -// CLI Interactive use cases — main chat view -// -// Covers: -// - `cline` launches interactive view (authed / unauthed) -// - /settings navigation and tab verification -// - /models view -// - /history view -// - /skills view -// - Plan/Act mode toggle (Tab) -// - Plan task → toggle to Act → task executes -// - Act task → file edit permission prompt → Save / Reject -// - Task completed → Start New Task / Exit buttons -// - Auto-approve settings -// - Subagents -// - Web tools -// - Auto-approve all (Shift+Tab) -// --------------------------------------------------------------------------- - -import { test } from "@microsoft/tui-test" -import { CLINE_BIN, TERMINAL_WIDE } from "../helpers/constants.js" -import { clineEnv } from "../helpers/env.js" -import { - openHistory, - openModels, - openSettings, - openSkills, - toggleAutoApproveAll, - togglePlanAct, - waitForChatReady, -} from "../helpers/page-objects/chat.js" -import { - assertApiTab, - assertAutoApproveTab, - assertFeaturesTab, - assertOtherTab, - goToSettingsTab, -} from "../helpers/page-objects/settings.js" -import { expectVisible } from "../helpers/terminal.js" - -// --------------------------------------------------------------------------- -// cline (unauthenticated) → shows interactive auth view -// --------------------------------------------------------------------------- -test.describe("cline (unauthenticated) — shows auth view", () => { - test.use({ - program: { file: CLINE_BIN, args: ["--tui"] }, - ...TERMINAL_WIDE, - env: clineEnv("unauthenticated"), - }) - - test("shows interactive auth view when not authenticated", async ({ terminal }) => { - await expectVisible(terminal, /sign in|authenticate|api key/i, { - timeout: 10_000, - }) - }) -}) - -// --------------------------------------------------------------------------- -// cline (authenticated) → shows main chat view -// --------------------------------------------------------------------------- -test.describe("cline (authenticated) — shows chat view", () => { - test.use({ - program: { file: CLINE_BIN, args: ["--tui"] }, - ...TERMINAL_WIDE, - env: clineEnv("claude-sonnet-4.6"), - }) - - test("shows interactive chat view", async ({ terminal }) => { - await waitForChatReady(terminal) - }) -}) - -// --------------------------------------------------------------------------- -// /settings — tab navigation -// --------------------------------------------------------------------------- -test.describe("/settings — tab navigation", () => { - test.use({ - program: { file: CLINE_BIN, args: ["--tui"] }, - ...TERMINAL_WIDE, - env: clineEnv("claude-sonnet-4.6"), - }) - - test("opens settings and shows API tab by default", async ({ terminal }) => { - await openSettings(terminal) - await assertApiTab(terminal) - }) - - test("can navigate to Auto-approve tab with keyRight", async ({ terminal }) => { - await openSettings(terminal) - terminal.keyRight() - await assertAutoApproveTab(terminal) - }) - - test("can navigate to Features tab", async ({ terminal }) => { - await openSettings(terminal) - await goToSettingsTab(terminal, "Features") - await assertFeaturesTab(terminal) - }) - - test("can navigate to Other tab", async ({ terminal }) => { - await openSettings(terminal) - await goToSettingsTab(terminal, "Other") - await assertOtherTab(terminal) - }) - - test("pressing Escape closes settings", async ({ terminal }) => { - await openSettings(terminal) - terminal.keyEscape() - await waitForChatReady(terminal) - }) -}) - -// --------------------------------------------------------------------------- -// /models — browse models -// --------------------------------------------------------------------------- -test.describe("/models — model browser", () => { - test.use({ - program: { file: CLINE_BIN, args: ["--tui"] }, - ...TERMINAL_WIDE, - env: clineEnv("claude-sonnet-4.6"), - }) - - test("opens models view with featured models", async ({ terminal }) => { - await openModels(terminal) - await expectVisible(terminal, "Browse all models...") - }) - - test("pressing Escape returns to chat view", async ({ terminal }) => { - await openModels(terminal) - await expectVisible(terminal, /model|browse/i, { timeout: 5000 }) - terminal.keyEscape() - await waitForChatReady(terminal) - }) -}) - -// --------------------------------------------------------------------------- -// /history — task history -// --------------------------------------------------------------------------- -test.describe("/history — task history", () => { - test.use({ - program: { file: CLINE_BIN, args: ["--tui"] }, - ...TERMINAL_WIDE, - env: clineEnv("claude-sonnet-4.6"), - }) - - test("opens history view", async ({ terminal }) => { - await openHistory(terminal) - await expectVisible(terminal, /history|task/i) - }) - - test("pressing Escape closes history", async ({ terminal }) => { - await openHistory(terminal) - await expectVisible(terminal, /history|task/i) - terminal.keyEscape() - await waitForChatReady(terminal) - }) -}) - -// --------------------------------------------------------------------------- -// /skills — skills view -// --------------------------------------------------------------------------- -test.describe("/skills — skills view", () => { - test.use({ - program: { file: CLINE_BIN, args: ["--tui"] }, - ...TERMINAL_WIDE, - env: clineEnv("default"), - }) - - test("opens skills view", async ({ terminal }) => { - await openSkills(terminal) - await expectVisible(terminal, "Skills (Esc to close)") - }) - - test("pressing Escape closes skills", async ({ terminal }) => { - await openSkills(terminal) - await expectVisible(terminal, "Skills (Esc to close)") - terminal.keyEscape() - await waitForChatReady(terminal) - }) -}) - -// --------------------------------------------------------------------------- -// Plan/Act mode toggle -// --------------------------------------------------------------------------- -test.describe("Plan/Act mode toggle", () => { - test.use({ - program: { file: CLINE_BIN, args: ["--tui"] }, - ...TERMINAL_WIDE, - env: clineEnv("default"), - }) - - test("pressing Tab toggles between Plan and Act mode", async ({ terminal }) => { - await waitForChatReady(terminal) - // Default should show Act - await expectVisible(terminal, "ā—‹ Plan ā— Act") - await togglePlanAct(terminal) - // After toggle, the other mode should be active - await expectVisible(terminal, "ā— Plan ā—‹ Act") - }) -}) - -// --------------------------------------------------------------------------- -// Auto-approve all (Shift+Tab) -// --------------------------------------------------------------------------- -test.describe("Auto-approve all — Shift+Tab toggle", () => { - test.use({ - program: { file: CLINE_BIN, args: ["--tui"] }, - ...TERMINAL_WIDE, - env: clineEnv("default"), - }) - - test("Shift+Tab toggles auto-approve-all setting", async ({ terminal }) => { - await waitForChatReady(terminal) - await expectVisible(terminal, "Auto-approve all disabled") - await toggleAutoApproveAll(terminal) - // TODO: verify config store is updated - await expectVisible(terminal, "Auto-approve all enabled") - }) -}) diff --git a/tests/e2e/cli/package.json b/tests/e2e/cli/package.json deleted file mode 100644 index bedb411a91..0000000000 --- a/tests/e2e/cli/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "type": "module" -} diff --git a/tests/e2e/cli/tui-test.config.ts b/tests/e2e/cli/tui-test.config.ts deleted file mode 100644 index 8d43ae251b..0000000000 --- a/tests/e2e/cli/tui-test.config.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { defineConfig } from "@microsoft/tui-test" - -export default defineConfig({ - retries: 2, - workers: 4, - trace: true, - traceFolder: "./tui-traces", - testMatch: "./**/*.test.ts", - timeout: 30_000, -}) diff --git a/tests/e2e/cli/utils.ts b/tests/e2e/cli/utils.ts deleted file mode 100644 index 83dafc0e39..0000000000 --- a/tests/e2e/cli/utils.ts +++ /dev/null @@ -1,9 +0,0 @@ -// --------------------------------------------------------------------------- -// Backward-compatible re-exports. -// -// Existing test files import from "./utils.js" — this file keeps those -// imports working while the canonical implementations live in helpers/. -// --------------------------------------------------------------------------- - -export { clineEnv, testEnv } from "./helpers/env.js" -export { expectVisible, typeAndSubmit } from "./helpers/terminal.js" diff --git a/tests/e2e/cli/version.test.ts b/tests/e2e/cli/version.test.ts deleted file mode 100644 index 2cf77c1b69..0000000000 --- a/tests/e2e/cli/version.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { test } from "@microsoft/tui-test" -import { CLINE_BIN } from "./helpers/constants.js" -import { expectVisible, testEnv } from "./utils.js" - -// --------------------------------------------------------------------------- -// cline --version (root flag) -// --------------------------------------------------------------------------- -test.describe("cline --version", () => { - test.use({ - program: { file: CLINE_BIN, args: ["--version"] }, - env: testEnv("claude-sonnet-4.6"), - }) - - test("prints the version string", async ({ terminal }) => { - await expectVisible(terminal, /\d+\.\d+\.\d+/g) - }) -}) - -// --------------------------------------------------------------------------- -// cline -V (short flag) -// --------------------------------------------------------------------------- -test.describe("cline -V", () => { - test.use({ - program: { file: CLINE_BIN, args: ["-V"] }, - env: testEnv("claude-sonnet-4.6"), - }) - - test("prints the version string with short flag", async ({ terminal }) => { - await expectVisible(terminal, /\d+\.\d+\.\d+/g) - }) -}) - -// --------------------------------------------------------------------------- -// cline version (subcommand) -// --------------------------------------------------------------------------- -test.describe("cline version subcommand", () => { - test.use({ - program: { file: CLINE_BIN, args: ["version"] }, - env: testEnv("claude-sonnet-4.6"), - }) - - test("prints 'Cline CLI version:' message", async ({ terminal }) => { - await expectVisible(terminal, ["Cline CLI version:", /\d+\.\d+\.\d+/g]) - }) -})