Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5cb7419fc2 | |||
| a565774122 | |||
| ec432f0954 | |||
| 4a57795abb | |||
| 10992dd9f9 | |||
| cee98aee89 |
@@ -33,35 +33,6 @@ evaluation.
|
||||
- **Warning**: Do not lose test fidelity by making prompts too direct/easy.
|
||||
- **Primary Fix Trigger**: Adjust tool descriptions, system prompts
|
||||
(`snippets.ts`), or **modules that contribute to the prompt template**.
|
||||
- Fixes should generally try to improve the prompt
|
||||
`@packages/core/src/prompts/snippets.ts` first.
|
||||
- **Instructional Generality**: Changes to the system prompt should aim to
|
||||
be as general as possible while still accomplishing the goal. Specificity
|
||||
should be added only as needed.
|
||||
- **Principle**: Instead of creating "forbidden lists" for specific syntax
|
||||
(e.g., "Don't use `Object.create()`"), formulate a broader engineering
|
||||
principle that covers the underlying issue (e.g., "Prioritize explicit
|
||||
composition over hidden prototype manipulation"). This improves
|
||||
steerability across a wider range of similar scenarios.
|
||||
- _Low Specificity_: "Follow ecosystem best practices"
|
||||
- _Medium Specificity_: "Utilize OOP and functional best practices, as
|
||||
applicable"
|
||||
- _High Specificity_: Provide ecosystem-specific hints as examples of a
|
||||
broader principle rather than direct instructions. e.g., "NEVER use
|
||||
hacks like bypassing the type system or employing 'hidden' logic (e.g.:
|
||||
reflection, prototype manipulation). Instead, use explicit and idiomatic
|
||||
language features (e.g.: type guards, explicit class instantiation, or
|
||||
object spread) that maintain structural integrity."
|
||||
- **Prompt Simplification**: Once the test is passing, use `ask_user` to
|
||||
determine if prompt simplification is desired.
|
||||
- **Criteria**: Simplification should be attempted only if there are
|
||||
related clauses that can be de-duplicated or reparented under a single
|
||||
heading.
|
||||
- **Verification**: As part of simplification, you MUST identify and run
|
||||
any behavioral eval tests that might be affected by the changes to
|
||||
ensure no regressions are introduced.
|
||||
- Test fixes should not "cheat" by changing a test's `GEMINI.md` file or by
|
||||
updating the test's prompt to instruct it to not repro the bug.
|
||||
- **Warning**: Prompts have multiple configurations; ensure your fix targets
|
||||
the correct config for the model in question.
|
||||
4. **Architecture Options**: If prompt or instruction tuning triggers no
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
name: 'Evals: PR Evaluation & Regression'
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
pull_request:
|
||||
types: ['opened', 'synchronize', 'reopened', 'ready_for_review']
|
||||
paths:
|
||||
- 'packages/core/src/prompts/**'
|
||||
@@ -23,73 +23,13 @@ permissions:
|
||||
actions: 'read'
|
||||
|
||||
jobs:
|
||||
detect-changes:
|
||||
name: 'Detect Steering Changes'
|
||||
runs-on: 'gemini-cli-ubuntu-16-core'
|
||||
# Security: pull_request_target allows secrets, so we must gate carefully.
|
||||
# Detection should not run code from the fork.
|
||||
if: "github.repository == 'google-gemini/gemini-cli' && github.event.pull_request.draft == false"
|
||||
outputs:
|
||||
SHOULD_RUN: '${{ steps.detect.outputs.SHOULD_RUN }}'
|
||||
STEERING_DETECTED: '${{ steps.detect.outputs.STEERING_DETECTED }}'
|
||||
steps:
|
||||
- name: 'Checkout'
|
||||
uses: 'actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955' # ratchet:actions/checkout@v5
|
||||
with:
|
||||
# Check out the trusted code from main for detection
|
||||
fetch-depth: 0
|
||||
|
||||
- name: 'Detect Steering Changes'
|
||||
id: 'detect'
|
||||
env:
|
||||
# Use the PR's head SHA for comparison without checking it out
|
||||
PR_HEAD_SHA: '${{ github.event.pull_request.head.sha }}'
|
||||
run: |
|
||||
# Fetch the fork's PR branch for analysis
|
||||
git fetch origin pull/${{ github.event.pull_request.number }}/head:pr-head
|
||||
|
||||
# Run the trusted script from main
|
||||
SHOULD_RUN=$(node scripts/changed_prompt.js)
|
||||
STEERING_DETECTED=$(node scripts/changed_prompt.js --steering-only)
|
||||
echo "SHOULD_RUN=$SHOULD_RUN" >> "$GITHUB_OUTPUT"
|
||||
echo "STEERING_DETECTED=$STEERING_DETECTED" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: 'Notify Approval Required'
|
||||
if: "steps.detect.outputs.SHOULD_RUN == 'true'"
|
||||
env:
|
||||
GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
|
||||
run: |
|
||||
RUN_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||
COMMENT_BODY="### 🛑 Action Required: Evaluation Approval
|
||||
|
||||
Steering changes have been detected in this PR. To prevent regressions, a maintainer must approve the evaluation run before this PR can be merged.
|
||||
|
||||
**Maintainers:**
|
||||
1. Go to the [**Workflow Run Summary**]($RUN_URL).
|
||||
2. Click the yellow **'Review deployments'** button.
|
||||
3. Select the **'eval-gate'** environment and click **'Approve'**.
|
||||
|
||||
Once approved, the evaluation results will be posted here automatically.
|
||||
|
||||
<!-- eval-approval-notification -->"
|
||||
|
||||
# Check if comment already exists to avoid spamming
|
||||
COMMENT_ID=$(gh pr view ${{ github.event.pull_request.number }} --json comments --jq '.comments[] | select(.body | contains("<!-- eval-approval-notification -->")) | .url' | grep -oE "[0-9]+$" | head -n 1)
|
||||
|
||||
if [ -z "$COMMENT_ID" ]; then
|
||||
gh pr comment ${{ github.event.pull_request.number }} --body "$COMMENT_BODY"
|
||||
else
|
||||
echo "Updating existing notification comment $COMMENT_ID..."
|
||||
gh api -X PATCH "repos/${{ github.repository }}/issues/comments/$COMMENT_ID" -F body="$COMMENT_BODY"
|
||||
fi
|
||||
|
||||
pr-evaluation:
|
||||
name: 'Evaluate Steering & Regressions'
|
||||
needs: 'detect-changes'
|
||||
if: "needs.detect-changes.outputs.SHOULD_RUN == 'true'"
|
||||
# Manual approval gate via environment
|
||||
environment: 'eval-gate'
|
||||
runs-on: 'gemini-cli-ubuntu-16-core'
|
||||
if: "github.repository == 'google-gemini/gemini-cli' && (github.event_name != 'pull_request' || (github.event.pull_request.draft == false && github.event.pull_request.head.repo.full_name == github.repository))"
|
||||
# External contributors' PRs will wait for approval in this environment
|
||||
environment: |-
|
||||
${{ (github.event.pull_request.head.repo.full_name == github.repository) && 'internal' || 'external-evals' }}
|
||||
env:
|
||||
# CENTRALIZED MODEL LIST
|
||||
MODEL_LIST: 'gemini-3-flash-preview'
|
||||
@@ -98,26 +38,8 @@ jobs:
|
||||
- name: 'Checkout'
|
||||
uses: 'actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955' # ratchet:actions/checkout@v5
|
||||
with:
|
||||
# Check out the fork's PR code for the actual evaluation
|
||||
# This only runs AFTER manual approval
|
||||
ref: '${{ github.event.pull_request.head.sha }}'
|
||||
fetch-depth: 0
|
||||
|
||||
- name: 'Remove Approval Notification'
|
||||
# Run even if other steps fail, to ensure we clean up the "Action Required" message
|
||||
if: 'always()'
|
||||
env:
|
||||
GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
|
||||
PR_NUMBER: '${{ github.event.pull_request.number }}'
|
||||
run: |
|
||||
echo "Debug: PR_NUMBER is '$PR_NUMBER'"
|
||||
# Search for the notification comment by its hidden tag
|
||||
COMMENT_ID=$(gh pr view "$PR_NUMBER" --json comments --jq '.comments[] | select(.body | contains("<!-- eval-approval-notification -->")) | .url' | grep -oE "[0-9]+$" | head -n 1)
|
||||
if [ -n "$COMMENT_ID" ]; then
|
||||
echo "Removing notification comment $COMMENT_ID now that run is approved..."
|
||||
gh api -X DELETE "repos/${{ github.repository }}/issues/comments/$COMMENT_ID"
|
||||
fi
|
||||
|
||||
- name: 'Set up Node.js'
|
||||
uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' # ratchet:actions/setup-node@v4.4.0
|
||||
with:
|
||||
@@ -130,8 +52,16 @@ jobs:
|
||||
- name: 'Build project'
|
||||
run: 'npm run build'
|
||||
|
||||
- name: 'Detect Steering Changes'
|
||||
id: 'detect'
|
||||
run: |
|
||||
SHOULD_RUN=$(node scripts/changed_prompt.js)
|
||||
STEERING_DETECTED=$(node scripts/changed_prompt.js --steering-only)
|
||||
echo "SHOULD_RUN=$SHOULD_RUN" >> "$GITHUB_OUTPUT"
|
||||
echo "STEERING_DETECTED=$STEERING_DETECTED" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: 'Analyze PR Content (Guidance)'
|
||||
if: "needs.detect-changes.outputs.STEERING_DETECTED == 'true'"
|
||||
if: "steps.detect.outputs.STEERING_DETECTED == 'true'"
|
||||
id: 'analysis'
|
||||
env:
|
||||
GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
|
||||
@@ -149,6 +79,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: 'Execute Regression Check'
|
||||
if: "steps.detect.outputs.SHOULD_RUN == 'true'"
|
||||
env:
|
||||
GEMINI_API_KEY: '${{ secrets.GEMINI_API_KEY }}'
|
||||
GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
|
||||
@@ -163,7 +94,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: 'Post or Update PR Comment'
|
||||
if: "always() && (needs.detect-changes.outputs.STEERING_DETECTED == 'true' || env.REPORT_FILE != '')"
|
||||
if: "always() && steps.detect.outputs.STEERING_DETECTED == 'true'"
|
||||
env:
|
||||
GH_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
|
||||
run: |
|
||||
@@ -173,20 +104,17 @@ jobs:
|
||||
cat eval_regression_report.md
|
||||
echo ""
|
||||
fi
|
||||
echo "### 🧠 Model Steering Guidance"
|
||||
echo ""
|
||||
echo "This PR modifies files that affect the model's behavior (prompts, tools, or instructions)."
|
||||
echo ""
|
||||
|
||||
if [[ "${{ needs.detect-changes.outputs.STEERING_DETECTED }}" == "true" ]]; then
|
||||
echo "### 🧠 Model Steering Guidance"
|
||||
echo ""
|
||||
echo "This PR modifies files that affect the model's behavior (prompts, tools, or instructions)."
|
||||
echo ""
|
||||
if [[ "${{ steps.analysis.outputs.MISSING_EVALS }}" == "true" ]]; then
|
||||
echo "- ⚠️ **Consider adding Evals:** No behavioral evaluations (\`evals/*.eval.ts\`) were added or updated in this PR. Consider [adding a test case](https://github.com/google-gemini/gemini-cli/blob/main/evals/README.md#creating-an-evaluation) to verify the new behavior and prevent regressions."
|
||||
fi
|
||||
|
||||
if [[ "${{ steps.analysis.outputs.MISSING_EVALS }}" == "true" ]]; then
|
||||
echo "- ⚠️ **Consider adding Evals:** No behavioral evaluations (\`evals/*.eval.ts\`) were added or updated in this PR. Consider [adding a test case](https://github.com/google-gemini/gemini-cli/blob/main/evals/README.md#creating-an-evaluation) to verify the new behavior and prevent regressions."
|
||||
fi
|
||||
|
||||
if [[ "${{ steps.analysis.outputs.IS_MAINTAINER }}" == "true" ]]; then
|
||||
echo "- 🚀 **Maintainer Reminder:** Please ensure that these changes do not regress results on benchmark evals before merging."
|
||||
fi
|
||||
if [[ "${{ steps.analysis.outputs.IS_MAINTAINER }}" == "true" ]]; then
|
||||
echo "- 🚀 **Maintainer Reminder:** Please ensure that these changes do not regress results on benchmark evals before merging."
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
name: 'Memory Tests: Nightly'
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 2 * * *' # Runs at 2 AM every day
|
||||
workflow_dispatch: # Allow manual trigger
|
||||
|
||||
permissions:
|
||||
contents: 'read'
|
||||
|
||||
jobs:
|
||||
memory-test:
|
||||
name: 'Run Memory Usage Tests'
|
||||
runs-on: 'gemini-cli-ubuntu-16-core'
|
||||
if: "github.repository == 'google-gemini/gemini-cli'"
|
||||
steps:
|
||||
- name: 'Checkout'
|
||||
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5
|
||||
|
||||
- name: 'Set up Node.js'
|
||||
uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' # ratchet:actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'npm'
|
||||
|
||||
- name: 'Install dependencies'
|
||||
run: 'npm ci'
|
||||
|
||||
- name: 'Build project'
|
||||
run: 'npm run build'
|
||||
|
||||
- name: 'Run Memory Tests'
|
||||
run: 'npm run test:memory'
|
||||
@@ -44,8 +44,6 @@ powerful tool for developers.
|
||||
- **Test Commands:**
|
||||
- **Unit (All):** `npm run test`
|
||||
- **Integration (E2E):** `npm run test:e2e`
|
||||
- **Memory (Nightly):** `npm run test:memory` (Runs memory regression tests
|
||||
against baselines. Excluded from `preflight`, run nightly.)
|
||||
- **Workspace-Specific:** `npm test -w <pkg> -- <path>` (Note: `<path>` must
|
||||
be relative to the workspace root, e.g.,
|
||||
`-w @google/gemini-cli-core -- src/routing/modelRouterService.test.ts`)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Preview release: v0.37.0-preview.2
|
||||
# Preview release: v0.37.0-preview.1
|
||||
|
||||
Released: April 07, 2026
|
||||
Released: April 02, 2026
|
||||
|
||||
Our preview release includes the latest, new, and experimental features. This
|
||||
release may not be as stable as our [latest weekly release](latest.md).
|
||||
@@ -33,10 +33,6 @@ npm install -g @google/gemini-cli@preview
|
||||
|
||||
## What's Changed
|
||||
|
||||
- fix(patch): cherry-pick cb7f7d6 to release/v0.37.0-preview.1-pr-24342 to patch
|
||||
version v0.37.0-preview.1 and create version 0.37.0-preview.2 by
|
||||
@gemini-cli-robot in
|
||||
[#24842](https://github.com/google-gemini/gemini-cli/pull/24842)
|
||||
- fix(patch): cherry-pick 64c928f to release/v0.37.0-preview.0-pr-23257 to patch
|
||||
version v0.37.0-preview.0 and create version 0.37.0-preview.1 by
|
||||
@gemini-cli-robot in
|
||||
@@ -423,4 +419,4 @@ npm install -g @google/gemini-cli@preview
|
||||
[#23275](https://github.com/google-gemini/gemini-cli/pull/23275)
|
||||
|
||||
**Full Changelog**:
|
||||
https://github.com/google-gemini/gemini-cli/compare/v0.36.0-preview.8...v0.37.0-preview.2
|
||||
https://github.com/google-gemini/gemini-cli/compare/v0.36.0-preview.8...v0.37.0-preview.1
|
||||
|
||||
@@ -136,58 +136,6 @@ gemini -p "build the snap"
|
||||
absolute path — the path must be writable inside the container.
|
||||
- Used with tools like Snapcraft or Rockcraft that require a full system.
|
||||
|
||||
## Tool sandboxing
|
||||
|
||||
Tool-level sandboxing provides granular isolation for individual tool executions
|
||||
(like `shell_exec` and `write_file`) instead of sandboxing the entire Gemini CLI
|
||||
process.
|
||||
|
||||
This approach offers better integration with your local environment for non-tool
|
||||
tasks (like UI rendering and configuration loading) while still providing
|
||||
security for tool-driven operations.
|
||||
|
||||
### How to turn off tool sandboxing
|
||||
|
||||
If you experience issues with tool sandboxing or prefer full-process isolation,
|
||||
you can disable it by setting `security.toolSandboxing` to `false` in your
|
||||
`settings.json` file.
|
||||
|
||||
```json
|
||||
{
|
||||
"security": {
|
||||
"toolSandboxing": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
> [!NOTE]
|
||||
> Changing the `security.toolSandboxing` setting requires a restart of Gemini
|
||||
> CLI to take effect.
|
||||
|
||||
## Sandbox expansion
|
||||
|
||||
Sandbox expansion is a dynamic permission system that lets Gemini CLI request
|
||||
additional permissions for a command when needed.
|
||||
|
||||
When a sandboxed command fails due to permission restrictions (like restricted
|
||||
file paths or network access), or when a command is proactively identified as
|
||||
requiring extra permissions (like `npm install`), Gemini CLI will present you
|
||||
with a "Sandbox Expansion Request."
|
||||
|
||||
### How sandbox expansion works
|
||||
|
||||
1. **Detection**: Gemini CLI detects a sandbox denial or proactively identifies
|
||||
a command that requires extra permissions.
|
||||
2. **Request**: A modal dialog is shown, explaining which additional
|
||||
permissions (e.g., specific directories or network access) are required.
|
||||
3. **Approval**: If you approve the expansion, the command is executed with the
|
||||
extended permissions for that specific run.
|
||||
|
||||
This mechanism ensures you don't have to manually re-run commands with more
|
||||
permissive sandbox settings, while still maintaining control over what the AI
|
||||
can access.
|
||||
|
||||
## Quickstart
|
||||
|
||||
```bash
|
||||
|
||||
@@ -75,7 +75,7 @@ they appear in the UI.
|
||||
| Show User Identity | `ui.showUserIdentity` | Show the signed-in user's identity (e.g. email) in the UI. | `true` |
|
||||
| Use Alternate Screen Buffer | `ui.useAlternateBuffer` | Use an alternate screen buffer for the UI, preserving shell history. | `false` |
|
||||
| Render Process | `ui.renderProcess` | Enable Ink render process for the UI. | `true` |
|
||||
| Terminal Buffer | `ui.terminalBuffer` | Use the new terminal buffer architecture for rendering. | `false` |
|
||||
| Terminal Buffer | `ui.terminalBuffer` | Use the new terminal buffer architecture for rendering. | `true` |
|
||||
| Use Background Color | `ui.useBackgroundColor` | Whether to use background colors in the UI. | `true` |
|
||||
| Incremental Rendering | `ui.incrementalRendering` | Enable incremental rendering for the UI. This option will reduce flickering but may cause rendering artifacts. Only supported when useAlternateBuffer is enabled. | `true` |
|
||||
| Show Spinner | `ui.showSpinner` | Show the spinner during operations. | `true` |
|
||||
@@ -140,7 +140,7 @@ they appear in the UI.
|
||||
|
||||
| UI Label | Setting | Description | Default |
|
||||
| ------------------------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------- |
|
||||
| Tool Sandboxing | `security.toolSandboxing` | Tool-level sandboxing. Isolates individual tools instead of the entire CLI process. | `false` |
|
||||
| Tool Sandboxing | `security.toolSandboxing` | Experimental tool-level sandboxing (implementation in progress). | `false` |
|
||||
| Disable YOLO Mode | `security.disableYoloMode` | Disable YOLO mode, even if enabled by a flag. | `false` |
|
||||
| Disable Always Allow | `security.disableAlwaysAllow` | Disable "Always allow" options in tool confirmation dialogs. | `false` |
|
||||
| Allow Permanent Tool Approval | `security.enablePermanentToolApproval` | Enable the "Allow for all future sessions" option in tool confirmation dialogs. | `false` |
|
||||
|
||||
@@ -117,46 +117,6 @@ npm run test:integration:sandbox:docker
|
||||
npm run test:integration:sandbox:podman
|
||||
```
|
||||
|
||||
## Memory regression tests
|
||||
|
||||
Memory regression tests are designed to detect heap growth and leaks across key
|
||||
CLI scenarios. They are located in the `memory-tests` directory.
|
||||
|
||||
These tests are distinct from standard integration tests because they measure
|
||||
memory usage and compare it against committed baselines.
|
||||
|
||||
### Running memory tests
|
||||
|
||||
Memory tests are not run as part of the default `npm run test` or
|
||||
`npm run test:e2e` commands. They are run nightly in CI but can be run manually:
|
||||
|
||||
```bash
|
||||
npm run test:memory
|
||||
```
|
||||
|
||||
### Updating baselines
|
||||
|
||||
If you intentionally change behavior that affects memory usage, you may need to
|
||||
update the baselines. Set the `UPDATE_MEMORY_BASELINES` environment variable to
|
||||
`true`:
|
||||
|
||||
```bash
|
||||
UPDATE_MEMORY_BASELINES=true npm run test:memory
|
||||
```
|
||||
|
||||
This will run the tests, take median snapshots, and overwrite
|
||||
`memory-tests/baselines.json`. You should review the changes and commit the
|
||||
updated baseline file.
|
||||
|
||||
### How it works
|
||||
|
||||
The harness (`MemoryTestHarness` in `packages/test-utils`):
|
||||
|
||||
- Forces garbage collection multiple times to reduce noise.
|
||||
- Takes median snapshots to filter spikes.
|
||||
- Compares against baselines with a 10% tolerance.
|
||||
- Can analyze sustained leaks across 3 snapshots using `analyzeSnapshots()`.
|
||||
|
||||
## Diagnostics
|
||||
|
||||
The integration test runner provides several options for diagnostics to help
|
||||
|
||||
@@ -346,7 +346,7 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
|
||||
- **`ui.terminalBuffer`** (boolean):
|
||||
- **Description:** Use the new terminal buffer architecture for rendering.
|
||||
- **Default:** `false`
|
||||
- **Default:** `true`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`ui.useBackgroundColor`** (boolean):
|
||||
@@ -1492,10 +1492,9 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
#### `security`
|
||||
|
||||
- **`security.toolSandboxing`** (boolean):
|
||||
- **Description:** Tool-level sandboxing. Isolates individual tools instead of
|
||||
the entire CLI process.
|
||||
- **Description:** Experimental tool-level sandboxing (implementation in
|
||||
progress).
|
||||
- **Default:** `false`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`security.disableYoloMode`** (boolean):
|
||||
- **Description:** Disable YOLO mode, even if enabled by a flag.
|
||||
@@ -1606,12 +1605,6 @@ their corresponding top-level category object in your `settings.json` file.
|
||||
- **Default:** `false`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`experimental.adk.agentSessionInteractiveEnabled`** (boolean):
|
||||
- **Description:** Enable the agent session implementation for the interactive
|
||||
CLI.
|
||||
- **Default:** `false`
|
||||
- **Requires restart:** Yes
|
||||
|
||||
- **`experimental.enableAgents`** (boolean):
|
||||
- **Description:** Enable local and remote subagents.
|
||||
- **Default:** `true`
|
||||
|
||||
@@ -86,14 +86,13 @@ available combinations.
|
||||
|
||||
#### Text Input
|
||||
|
||||
| Command | Action | Keys |
|
||||
| ------------------------------------ | ------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
|
||||
| `input.submit` | Submit the current prompt. | `Enter` |
|
||||
| `input.queueMessage` | Queue the current prompt to be processed after the current task finishes. | `Tab` |
|
||||
| `input.newline` | Insert a newline without submitting. | `Ctrl+Enter`<br />`Cmd/Win+Enter`<br />`Alt+Enter`<br />`Shift+Enter`<br />`Ctrl+J` |
|
||||
| `input.openExternalEditor` | Open the current prompt or the plan in an external editor. | `Ctrl+G` |
|
||||
| `input.deprecatedOpenExternalEditor` | Deprecated command to open external editor. | `Ctrl+X` |
|
||||
| `input.paste` | Paste from the clipboard. | `Ctrl+V`<br />`Cmd/Win+V`<br />`Alt+V` |
|
||||
| Command | Action | Keys |
|
||||
| -------------------------- | ------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
|
||||
| `input.submit` | Submit the current prompt. | `Enter` |
|
||||
| `input.queueMessage` | Queue the current prompt to be processed after the current task finishes. | `Tab` |
|
||||
| `input.newline` | Insert a newline without submitting. | `Ctrl+Enter`<br />`Cmd/Win+Enter`<br />`Alt+Enter`<br />`Shift+Enter`<br />`Ctrl+J` |
|
||||
| `input.openExternalEditor` | Open the current prompt or the plan in an external editor. | `Ctrl+X` |
|
||||
| `input.paste` | Paste from the clipboard. | `Ctrl+V`<br />`Cmd/Win+V`<br />`Alt+V` |
|
||||
|
||||
#### App Controls
|
||||
|
||||
@@ -101,7 +100,7 @@ available combinations.
|
||||
| ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ |
|
||||
| `app.showErrorDetails` | Toggle detailed error information. | `F12` |
|
||||
| `app.showFullTodos` | Toggle the full TODO list. | `Ctrl+T` |
|
||||
| `app.showIdeContextDetail` | Show IDE context details. | `F4` |
|
||||
| `app.showIdeContextDetail` | Show IDE context details. | `Ctrl+G` |
|
||||
| `app.toggleMarkdown` | Toggle Markdown rendering. | `Alt+M` |
|
||||
| `app.toggleCopyMode` | Toggle copy mode when in alternate buffer mode. | `F9` |
|
||||
| `app.toggleMouseMode` | Toggle mouse mode (scrolling and clicking). | `Ctrl+S` |
|
||||
|
||||
@@ -290,7 +290,7 @@ When connecting to an OAuth-enabled server:
|
||||
> OAuth authentication requires that your local machine can:
|
||||
>
|
||||
> - Open a web browser for authentication
|
||||
> - Receive redirects on `http://localhost:<random-port>/oauth/callback` (or a specific port if configured via `redirectUri`)
|
||||
> - Receive redirects on `http://localhost:7777/oauth/callback`
|
||||
|
||||
This feature will not work in:
|
||||
|
||||
@@ -323,8 +323,8 @@ Use the `/mcp auth` command to manage OAuth authentication:
|
||||
if omitted)
|
||||
- **`tokenUrl`** (string): OAuth token endpoint (auto-discovered if omitted)
|
||||
- **`scopes`** (string[]): Required OAuth scopes
|
||||
- **`redirectUri`** (string): Custom redirect URI (defaults to an OS-assigned
|
||||
random port, e.g., `http://localhost:<random-port>/oauth/callback`)
|
||||
- **`redirectUri`** (string): Custom redirect URI (defaults to
|
||||
`http://localhost:7777/oauth/callback`)
|
||||
- **`tokenParamName`** (string): Query parameter name for tokens in SSE URLs
|
||||
- **`audiences`** (string[]): Audiences the token is valid for
|
||||
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { evalTest, TestRig } from './test-helper.js';
|
||||
|
||||
evalTest('USUALLY_PASSES', {
|
||||
name: 'Reproduction: Agent uses Object.create() for cloning/delegation',
|
||||
prompt:
|
||||
'Create a utility function `createScopedConfig(config: Config, additionalDirectories: string[]): Config` in `packages/core/src/config/scoped-config.ts` that returns a new Config instance. This instance should override `getWorkspaceContext()` to include the additional directories, but delegate all other method calls (like `isPathAllowed` or `validatePathAccess`) to the original config. Note that `Config` is a complex class with private state and cannot be easily shallow-copied or reconstructed.',
|
||||
files: {
|
||||
'packages/core/src/config/config.ts': `
|
||||
export class Config {
|
||||
private _internalState = 'secret';
|
||||
constructor(private workspaceContext: any) {}
|
||||
getWorkspaceContext() { return this.workspaceContext; }
|
||||
isPathAllowed(path: string) {
|
||||
return this.getWorkspaceContext().isPathWithinWorkspace(path);
|
||||
}
|
||||
validatePathAccess(path: string) {
|
||||
if (!this.isPathAllowed(path)) return 'Denied';
|
||||
return null;
|
||||
}
|
||||
}`,
|
||||
'packages/core/src/utils/workspaceContext.ts': `
|
||||
export class WorkspaceContext {
|
||||
constructor(private root: string, private additional: string[] = []) {}
|
||||
getDirectories() { return [this.root, ...this.additional]; }
|
||||
isPathWithinWorkspace(path: string) {
|
||||
return this.getDirectories().some(d => path.startsWith(d));
|
||||
}
|
||||
}`,
|
||||
'package.json': JSON.stringify({
|
||||
name: 'test-project',
|
||||
version: '1.0.0',
|
||||
type: 'module',
|
||||
}),
|
||||
},
|
||||
assert: async (rig: TestRig) => {
|
||||
const filePath = 'packages/core/src/config/scoped-config.ts';
|
||||
const content = rig.readFile(filePath);
|
||||
|
||||
if (!content) {
|
||||
throw new Error(`File ${filePath} was not created.`);
|
||||
}
|
||||
|
||||
// Strip comments to avoid false positives.
|
||||
const codeWithoutComments = content.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, '');
|
||||
|
||||
// Ensure that the agent did not use Object.create() in the implementation.
|
||||
// We check for the call pattern specifically using a regex to avoid false positives in comments.
|
||||
const hasObjectCreate = /\bObject\.create\s*\(/.test(codeWithoutComments);
|
||||
if (hasObjectCreate) {
|
||||
throw new Error(
|
||||
'Evaluation Failed: Agent used Object.create() for cloning. ' +
|
||||
'This behavior is forbidden by the project lint rules (no-restricted-syntax). ' +
|
||||
'Implementation found:\n\n' +
|
||||
content,
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -5,8 +5,6 @@
|
||||
*/
|
||||
|
||||
import { describe, expect } from 'vitest';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { evalTest } from './test-helper.js';
|
||||
|
||||
describe('update_topic_behavior', () => {
|
||||
@@ -115,147 +113,4 @@ describe('update_topic_behavior', () => {
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
evalTest('USUALLY_PASSES', {
|
||||
name: 'update_topic should NOT be used for informational coding tasks (Obvious)',
|
||||
approvalMode: 'default',
|
||||
prompt:
|
||||
'Explain the difference between Map and Object in JavaScript and provide a performance-focused code snippet for each.',
|
||||
files: {
|
||||
'.gemini/settings.json': JSON.stringify({
|
||||
experimental: {
|
||||
topicUpdateNarration: true,
|
||||
},
|
||||
}),
|
||||
},
|
||||
assert: async (rig) => {
|
||||
const toolLogs = rig.readToolLogs();
|
||||
const topicCalls = toolLogs.filter(
|
||||
(l) => l.toolRequest.name === UPDATE_TOPIC_TOOL_NAME,
|
||||
);
|
||||
|
||||
expect(
|
||||
topicCalls.length,
|
||||
`Expected 0 update_topic calls for an informational task, but found ${topicCalls.length}`,
|
||||
).toBe(0);
|
||||
},
|
||||
});
|
||||
|
||||
evalTest('USUALLY_PASSES', {
|
||||
name: 'update_topic should NOT be used for surgical symbol searches (Grey Area)',
|
||||
approvalMode: 'default',
|
||||
prompt:
|
||||
"Find the file where the 'UPDATE_TOPIC_TOOL_NAME' constant is defined.",
|
||||
files: {
|
||||
'packages/core/src/tools/tool-names.ts':
|
||||
"export const UPDATE_TOPIC_TOOL_NAME = 'update_topic';",
|
||||
'.gemini/settings.json': JSON.stringify({
|
||||
experimental: {
|
||||
topicUpdateNarration: true,
|
||||
},
|
||||
}),
|
||||
},
|
||||
assert: async (rig) => {
|
||||
const toolLogs = rig.readToolLogs();
|
||||
const topicCalls = toolLogs.filter(
|
||||
(l) => l.toolRequest.name === UPDATE_TOPIC_TOOL_NAME,
|
||||
);
|
||||
|
||||
expect(
|
||||
topicCalls.length,
|
||||
`Expected 0 update_topic calls for a surgical symbol search, but found ${topicCalls.length}`,
|
||||
).toBe(0);
|
||||
},
|
||||
});
|
||||
|
||||
evalTest('USUALLY_PASSES', {
|
||||
name: 'update_topic should be used for medium complexity multi-step tasks',
|
||||
prompt:
|
||||
'Refactor the `users-api` project. Move the routing logic from src/app.ts into a new file src/routes.ts, and update app.ts to use the new routes file.',
|
||||
files: {
|
||||
'package.json': JSON.stringify(
|
||||
{
|
||||
name: 'users-api',
|
||||
version: '1.0.0',
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
'src/app.ts': `
|
||||
import express from 'express';
|
||||
const app = express();
|
||||
|
||||
app.get('/users', (req, res) => {
|
||||
res.json([{id: 1, name: 'Alice'}]);
|
||||
});
|
||||
|
||||
app.post('/users', (req, res) => {
|
||||
res.status(201).send();
|
||||
});
|
||||
|
||||
export default app;
|
||||
`,
|
||||
'.gemini/settings.json': JSON.stringify({
|
||||
experimental: {
|
||||
topicUpdateNarration: true,
|
||||
},
|
||||
}),
|
||||
},
|
||||
assert: async (rig) => {
|
||||
const toolLogs = rig.readToolLogs();
|
||||
const topicCalls = toolLogs.filter(
|
||||
(l) => l.toolRequest.name === UPDATE_TOPIC_TOOL_NAME,
|
||||
);
|
||||
|
||||
// This is a multi-step task (read, create new file, edit old file).
|
||||
// It should clear the bar and use update_topic at least at the start and end.
|
||||
expect(topicCalls.length).toBeGreaterThanOrEqual(2);
|
||||
|
||||
// Verify it actually did the refactoring to ensure it didn't just fail immediately
|
||||
expect(fs.existsSync(path.join(rig.testDir, 'src/routes.ts'))).toBe(true);
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Regression test for a bug where update_topic was called multiple times in a
|
||||
* row. We have seen cases of this occurring in earlier versions of the update_topic
|
||||
* system instruction, prior to https://github.com/google-gemini/gemini-cli/pull/24640.
|
||||
* This test demonstrated that there are cases where it can still occur and validates
|
||||
* the prompt change that improves the behavior.
|
||||
*/
|
||||
evalTest('USUALLY_PASSES', {
|
||||
name: 'update_topic should not be called twice in a row',
|
||||
prompt: `
|
||||
We need to build a C compiler.
|
||||
|
||||
Before you write any code, you must formally declare your strategy.
|
||||
First, declare that you will build a Lexer.
|
||||
Then, immediately realize that is wrong and declare that you will actually build a Parser instead.
|
||||
|
||||
Finally, create 'parser.c'.
|
||||
`,
|
||||
files: {
|
||||
'package.json': JSON.stringify({ name: 'test-project' }),
|
||||
'.gemini/settings.json': JSON.stringify({
|
||||
experimental: {
|
||||
topicUpdateNarration: true,
|
||||
},
|
||||
}),
|
||||
},
|
||||
assert: async (rig) => {
|
||||
const toolLogs = rig.readToolLogs();
|
||||
|
||||
// Check for back-to-back update_topic calls
|
||||
for (let i = 1; i < toolLogs.length; i++) {
|
||||
if (
|
||||
toolLogs[i - 1].toolRequest.name === UPDATE_TOPIC_TOOL_NAME &&
|
||||
toolLogs[i].toolRequest.name === UPDATE_TOPIC_TOOL_NAME
|
||||
) {
|
||||
throw new Error(
|
||||
`Detected back-to-back ${UPDATE_TOPIC_TOOL_NAME} calls at index ${i - 1} and ${i}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"I'll launch two browser agents concurrently to check both repositories."},{"functionCall":{"name":"browser_agent","args":{"task":"Navigate to https://example.com and get the page title"}}},{"functionCall":{"name":"browser_agent","args":{"task":"Navigate to https://example.com and get the page title"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":100,"candidatesTokenCount":50,"totalTokenCount":150}}]}
|
||||
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"navigate_page","args":{"url":"https://example.com"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":100,"candidatesTokenCount":20,"totalTokenCount":120}}]}
|
||||
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"navigate_page","args":{"url":"https://example.com"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":100,"candidatesTokenCount":20,"totalTokenCount":120}}]}
|
||||
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"take_snapshot","args":{}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":150,"candidatesTokenCount":15,"totalTokenCount":165}}]}
|
||||
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"take_snapshot","args":{}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":150,"candidatesTokenCount":15,"totalTokenCount":165}}]}
|
||||
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"complete_task","args":{"result":{"success":true,"summary":"Page title is Example Domain."}}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":200,"candidatesTokenCount":30,"totalTokenCount":230}}]}
|
||||
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"complete_task","args":{"result":{"success":true,"summary":"Page title is Example Domain."}}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":200,"candidatesTokenCount":30,"totalTokenCount":230}}]}
|
||||
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"Both browser agents completed successfully. Agent 1 and Agent 2 both navigated to their respective pages and confirmed the page titles."}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":300,"candidatesTokenCount":40,"totalTokenCount":340}}]}
|
||||
@@ -1,8 +0,0 @@
|
||||
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"I'll browse to example.com twice to verify the content. Let me first check the page title, then check the links on the page."},{"functionCall":{"name":"browser_agent","args":{"task":"Navigate to https://example.com and tell me the page title using the accessibility tree"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":100,"candidatesTokenCount":50,"totalTokenCount":150}}]}
|
||||
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"navigate_page","args":{"url":"https://example.com"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":100,"candidatesTokenCount":30,"totalTokenCount":130}}]}
|
||||
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"take_snapshot","args":{}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":150,"candidatesTokenCount":20,"totalTokenCount":170}}]}
|
||||
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"complete_task","args":{"result":{"success":true,"summary":"Page title is 'Example Domain'."}}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":200,"candidatesTokenCount":40,"totalTokenCount":240}}]}
|
||||
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"The page title is 'Example Domain'. Now let me check the links on the page."},{"functionCall":{"name":"browser_agent","args":{"task":"Take a snapshot of the accessibility tree on the currently open page and tell me about any links"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":200,"candidatesTokenCount":50,"totalTokenCount":250}}]}
|
||||
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"take_snapshot","args":{}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":150,"candidatesTokenCount":20,"totalTokenCount":170}}]}
|
||||
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"complete_task","args":{"result":{"success":true,"summary":"Found a link 'More information...' pointing to iana.org."}}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":200,"candidatesTokenCount":40,"totalTokenCount":240}}]}
|
||||
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"I browsed example.com twice using persistent browser sessions:\n\n1. **First visit**: Page title is 'Example Domain'\n2. **Second visit**: Found a link 'More information...' pointing to iana.org\n\nThe browser stayed open between both visits, confirming persistent session management works correctly."}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":300,"candidatesTokenCount":60,"totalTokenCount":360}}]}
|
||||
@@ -229,51 +229,6 @@ describe.skipIf(!chromeAvailable)('browser-agent', () => {
|
||||
assertModelHasOutput(result);
|
||||
});
|
||||
|
||||
it('should keep browser open across multiple browser_agent invocations', async () => {
|
||||
rig.setup('browser-persistent-session', {
|
||||
fakeResponsesPath: join(
|
||||
__dirname,
|
||||
'browser-agent.persistent-session.responses',
|
||||
),
|
||||
settings: {
|
||||
agents: {
|
||||
overrides: {
|
||||
browser_agent: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
browser: {
|
||||
headless: true,
|
||||
sessionMode: 'isolated',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const result = await rig.run({
|
||||
args: 'Browse to example.com twice: first get the page title, then check for links.',
|
||||
});
|
||||
|
||||
const toolLogs = rig.readToolLogs();
|
||||
const browserCalls = toolLogs.filter(
|
||||
(t) => t.toolRequest.name === 'browser_agent',
|
||||
);
|
||||
|
||||
// Both browser_agent invocations must succeed — if the browser was
|
||||
// incorrectly closed after the first call (regression #24210),
|
||||
// the second call would fail.
|
||||
expect(
|
||||
browserCalls.length,
|
||||
'Expected browser_agent to be called twice',
|
||||
).toBe(2);
|
||||
expect(
|
||||
browserCalls.every((c) => c.toolRequest.success),
|
||||
'Both browser_agent calls should succeed',
|
||||
).toBe(true);
|
||||
|
||||
assertModelHasOutput(result);
|
||||
});
|
||||
|
||||
it('should handle tool confirmation for write_file without crashing', async () => {
|
||||
rig.setup('tool-confirmation', {
|
||||
fakeResponsesPath: join(
|
||||
@@ -307,48 +262,4 @@ describe.skipIf(!chromeAvailable)('browser-agent', () => {
|
||||
|
||||
await run.expectText('successfully written', 15000);
|
||||
});
|
||||
|
||||
it('should handle concurrent browser agents with isolated session mode', async () => {
|
||||
rig.setup('browser-concurrent', {
|
||||
fakeResponsesPath: join(__dirname, 'browser-agent.concurrent.responses'),
|
||||
settings: {
|
||||
agents: {
|
||||
overrides: {
|
||||
browser_agent: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
browser: {
|
||||
headless: true,
|
||||
// Isolated mode supports concurrent browser agents.
|
||||
// Persistent/existing modes reject concurrent calls to prevent
|
||||
// Chrome profile lock conflicts.
|
||||
sessionMode: 'isolated',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const result = await rig.run({
|
||||
args: 'Launch two browser agents concurrently to check example.com',
|
||||
});
|
||||
|
||||
assertModelHasOutput(result);
|
||||
|
||||
const toolLogs = rig.readToolLogs();
|
||||
const browserCalls = toolLogs.filter(
|
||||
(t) => t.toolRequest.name === 'browser_agent',
|
||||
);
|
||||
|
||||
// Both browser_agent invocations should have been called
|
||||
expect(browserCalls.length).toBe(2);
|
||||
|
||||
// Both should complete successfully (no errors)
|
||||
for (const call of browserCalls) {
|
||||
expect(
|
||||
call.toolRequest.success,
|
||||
`browser_agent call failed: ${JSON.stringify(call.toolRequest)}`,
|
||||
).toBe(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { writeFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { writeFileSync, readFileSync } from 'node:fs';
|
||||
import { join, resolve } from 'node:path';
|
||||
import { TestRig } from './test-helper.js';
|
||||
|
||||
// BOM encoders
|
||||
@@ -116,4 +116,21 @@ describe('BOM end-to-end integraion', () => {
|
||||
'BOM_OK UTF-32BE',
|
||||
);
|
||||
});
|
||||
|
||||
it('Can describe a PNG file', async () => {
|
||||
const imagePath = resolve(
|
||||
process.cwd(),
|
||||
'docs/assets/gemini-screenshot.png',
|
||||
);
|
||||
const imageContent = readFileSync(imagePath);
|
||||
const filename = 'gemini-screenshot.png';
|
||||
writeFileSync(join(rig.testDir!, filename), imageContent);
|
||||
const prompt = `What is shown in the image ${filename}?`;
|
||||
const output = await rig.run({ args: prompt });
|
||||
await rig.waitForToolCall('read_file');
|
||||
const lower = output.toLowerCase();
|
||||
// The response is non-deterministic, so we just check for some
|
||||
// keywords that are very likely to be in the response.
|
||||
expect(lower.includes('gemini')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
{
|
||||
"version": 1,
|
||||
"updatedAt": "2026-04-08T01:21:58.770Z",
|
||||
"scenarios": {
|
||||
"multi-turn-conversation": {
|
||||
"heapUsedBytes": 120082704,
|
||||
"heapTotalBytes": 177586176,
|
||||
"rssBytes": 269172736,
|
||||
"timestamp": "2026-04-08T01:21:57.127Z"
|
||||
},
|
||||
"multi-function-call-repo-search": {
|
||||
"heapUsedBytes": 104644984,
|
||||
"heapTotalBytes": 111575040,
|
||||
"rssBytes": 204079104,
|
||||
"timestamp": "2026-04-08T01:21:58.770Z"
|
||||
},
|
||||
"idle-session-startup": {
|
||||
"heapUsedBytes": 119813672,
|
||||
"heapTotalBytes": 177061888,
|
||||
"rssBytes": 267943936,
|
||||
"timestamp": "2026-04-08T01:21:53.855Z"
|
||||
},
|
||||
"simple-prompt-response": {
|
||||
"heapUsedBytes": 119722064,
|
||||
"heapTotalBytes": 177324032,
|
||||
"rssBytes": 268812288,
|
||||
"timestamp": "2026-04-08T01:21:55.491Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { mkdir, readdir, rm } from 'node:fs/promises';
|
||||
import { join, dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { canUseRipgrep } from '../packages/core/src/tools/ripGrep.js';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const rootDir = join(__dirname, '..');
|
||||
const memoryTestsDir = join(rootDir, '.memory-tests');
|
||||
let runDir = '';
|
||||
|
||||
export async function setup() {
|
||||
runDir = join(memoryTestsDir, `${Date.now()}`);
|
||||
await mkdir(runDir, { recursive: true });
|
||||
|
||||
// Set the home directory to the test run directory to avoid conflicts
|
||||
// with the user's local config.
|
||||
process.env['HOME'] = runDir;
|
||||
if (process.platform === 'win32') {
|
||||
process.env['USERPROFILE'] = runDir;
|
||||
}
|
||||
process.env['GEMINI_CONFIG_DIR'] = join(runDir, '.gemini');
|
||||
|
||||
// Download ripgrep to avoid race conditions
|
||||
const available = await canUseRipgrep();
|
||||
if (!available) {
|
||||
throw new Error('Failed to download ripgrep binary');
|
||||
}
|
||||
|
||||
// Clean up old test runs, keeping the latest few for debugging
|
||||
try {
|
||||
const testRuns = await readdir(memoryTestsDir);
|
||||
if (testRuns.length > 3) {
|
||||
const oldRuns = testRuns.sort().slice(0, testRuns.length - 3);
|
||||
await Promise.all(
|
||||
oldRuns.map((oldRun) =>
|
||||
rm(join(memoryTestsDir, oldRun), {
|
||||
recursive: true,
|
||||
force: true,
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error cleaning up old memory test runs:', e);
|
||||
}
|
||||
|
||||
process.env['INTEGRATION_TEST_FILE_DIR'] = runDir;
|
||||
process.env['GEMINI_CLI_INTEGRATION_TEST'] = 'true';
|
||||
process.env['GEMINI_FORCE_FILE_STORAGE'] = 'true';
|
||||
process.env['TELEMETRY_LOG_FILE'] = join(runDir, 'telemetry.log');
|
||||
process.env['VERBOSE'] = process.env['VERBOSE'] ?? 'false';
|
||||
|
||||
console.log(`\nMemory test output directory: ${runDir}`);
|
||||
}
|
||||
|
||||
export async function teardown() {
|
||||
// Cleanup unless KEEP_OUTPUT is set
|
||||
if (process.env['KEEP_OUTPUT'] !== 'true' && runDir) {
|
||||
try {
|
||||
await rm(runDir, { recursive: true, force: true });
|
||||
} catch (e) {
|
||||
console.warn('Failed to clean up memory test directory:', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,185 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, beforeAll, afterAll, afterEach } from 'vitest';
|
||||
import { TestRig, MemoryTestHarness } from '@google/gemini-cli-test-utils';
|
||||
import { join, dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const BASELINES_PATH = join(__dirname, 'baselines.json');
|
||||
const UPDATE_BASELINES = process.env['UPDATE_MEMORY_BASELINES'] === 'true';
|
||||
const TOLERANCE_PERCENT = 10;
|
||||
|
||||
// Fake API key for tests using fake responses
|
||||
const TEST_ENV = { GEMINI_API_KEY: 'fake-memory-test-key' };
|
||||
|
||||
describe('Memory Usage Tests', () => {
|
||||
let harness: MemoryTestHarness;
|
||||
let rig: TestRig;
|
||||
|
||||
beforeAll(() => {
|
||||
harness = new MemoryTestHarness({
|
||||
baselinesPath: BASELINES_PATH,
|
||||
defaultTolerancePercent: TOLERANCE_PERCENT,
|
||||
gcCycles: 3,
|
||||
gcDelayMs: 100,
|
||||
sampleCount: 3,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await rig.cleanup();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
// Generate the summary report after all tests
|
||||
await harness.generateReport();
|
||||
});
|
||||
|
||||
it('idle-session-startup: memory usage within baseline', async () => {
|
||||
rig = new TestRig();
|
||||
rig.setup('memory-idle-startup', {
|
||||
fakeResponsesPath: join(__dirname, 'memory.idle-startup.responses'),
|
||||
});
|
||||
|
||||
const result = await harness.runScenario(
|
||||
'idle-session-startup',
|
||||
async (recordSnapshot) => {
|
||||
await rig.run({
|
||||
args: ['hello'],
|
||||
timeout: 120000,
|
||||
env: TEST_ENV,
|
||||
});
|
||||
|
||||
await recordSnapshot('after-startup');
|
||||
},
|
||||
);
|
||||
|
||||
if (UPDATE_BASELINES) {
|
||||
harness.updateScenarioBaseline(result);
|
||||
console.log(
|
||||
`Updated baseline for idle-session-startup: ${(result.finalHeapUsed / (1024 * 1024)).toFixed(1)} MB`,
|
||||
);
|
||||
} else {
|
||||
harness.assertWithinBaseline(result);
|
||||
}
|
||||
});
|
||||
|
||||
it('simple-prompt-response: memory usage within baseline', async () => {
|
||||
rig = new TestRig();
|
||||
rig.setup('memory-simple-prompt', {
|
||||
fakeResponsesPath: join(__dirname, 'memory.simple-prompt.responses'),
|
||||
});
|
||||
|
||||
const result = await harness.runScenario(
|
||||
'simple-prompt-response',
|
||||
async (recordSnapshot) => {
|
||||
await rig.run({
|
||||
args: ['What is the capital of France?'],
|
||||
timeout: 120000,
|
||||
env: TEST_ENV,
|
||||
});
|
||||
|
||||
await recordSnapshot('after-response');
|
||||
},
|
||||
);
|
||||
|
||||
if (UPDATE_BASELINES) {
|
||||
harness.updateScenarioBaseline(result);
|
||||
console.log(
|
||||
`Updated baseline for simple-prompt-response: ${(result.finalHeapUsed / (1024 * 1024)).toFixed(1)} MB`,
|
||||
);
|
||||
} else {
|
||||
harness.assertWithinBaseline(result);
|
||||
}
|
||||
});
|
||||
|
||||
it('multi-turn-conversation: memory remains stable over turns', async () => {
|
||||
rig = new TestRig();
|
||||
rig.setup('memory-multi-turn', {
|
||||
fakeResponsesPath: join(__dirname, 'memory.multi-turn.responses'),
|
||||
});
|
||||
|
||||
const prompts = [
|
||||
'Hello, what can you help me with?',
|
||||
'Tell me about JavaScript',
|
||||
'How is TypeScript different?',
|
||||
'Can you write a simple TypeScript function?',
|
||||
'What are some TypeScript best practices?',
|
||||
];
|
||||
|
||||
const result = await harness.runScenario(
|
||||
'multi-turn-conversation',
|
||||
async (recordSnapshot) => {
|
||||
// Run through all turns as a piped sequence
|
||||
const stdinContent = prompts.join('\n');
|
||||
await rig.run({
|
||||
stdin: stdinContent,
|
||||
timeout: 120000,
|
||||
env: TEST_ENV,
|
||||
});
|
||||
|
||||
// Take snapshots after the conversation completes
|
||||
await recordSnapshot('after-all-turns');
|
||||
},
|
||||
);
|
||||
|
||||
if (UPDATE_BASELINES) {
|
||||
harness.updateScenarioBaseline(result);
|
||||
console.log(
|
||||
`Updated baseline for multi-turn-conversation: ${(result.finalHeapUsed / (1024 * 1024)).toFixed(1)} MB`,
|
||||
);
|
||||
} else {
|
||||
harness.assertWithinBaseline(result);
|
||||
}
|
||||
});
|
||||
|
||||
it('multi-function-call-repo-search: memory after tool use', async () => {
|
||||
rig = new TestRig();
|
||||
rig.setup('memory-multi-func-call', {
|
||||
fakeResponsesPath: join(
|
||||
__dirname,
|
||||
'memory.multi-function-call.responses',
|
||||
),
|
||||
});
|
||||
|
||||
// Create directories first, then files in the workspace so the tools have targets
|
||||
rig.mkdir('packages/core/src/telemetry');
|
||||
rig.createFile(
|
||||
'packages/core/src/telemetry/memory-monitor.ts',
|
||||
'export class MemoryMonitor { constructor() {} }',
|
||||
);
|
||||
rig.createFile(
|
||||
'packages/core/src/telemetry/metrics.ts',
|
||||
'export function recordMemoryUsage() {}',
|
||||
);
|
||||
|
||||
const result = await harness.runScenario(
|
||||
'multi-function-call-repo-search',
|
||||
async (recordSnapshot) => {
|
||||
await rig.run({
|
||||
args: [
|
||||
'Search this repository for MemoryMonitor and tell me what it does',
|
||||
],
|
||||
timeout: 120000,
|
||||
env: TEST_ENV,
|
||||
});
|
||||
|
||||
await recordSnapshot('after-tool-calls');
|
||||
},
|
||||
);
|
||||
|
||||
if (UPDATE_BASELINES) {
|
||||
harness.updateScenarioBaseline(result);
|
||||
console.log(
|
||||
`Updated baseline for multi-function-call-repo-search: ${(result.finalHeapUsed / (1024 * 1024)).toFixed(1)} MB`,
|
||||
);
|
||||
} else {
|
||||
harness.assertWithinBaseline(result);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,2 +0,0 @@
|
||||
{"method":"generateContent","response":{"candidates":[{"content":{"parts":[{"text":"0"}],"role":"model"},"finishReason":"STOP","index":0}]}}
|
||||
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"Hello! I'm ready to help. What would you like to work on?"}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":5,"candidatesTokenCount":12,"totalTokenCount":17,"promptTokensDetails":[{"modality":"TEXT","tokenCount":5}]}}]}
|
||||
@@ -1,4 +0,0 @@
|
||||
{"method":"generateContent","response":{"candidates":[{"content":{"parts":[{"text":"0"}],"role":"model"},"finishReason":"STOP","index":0}]}}
|
||||
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"I'll search for MemoryMonitor in the repository and analyze what it does."}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":30,"candidatesTokenCount":15,"totalTokenCount":45,"promptTokensDetails":[{"modality":"TEXT","tokenCount":30}]}}]}
|
||||
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"functionCall":{"name":"grep_search","args":{"pattern":"MemoryMonitor","path":".","include_pattern":"*.ts"}}},{"functionCall":{"name":"list_directory","args":{"path":"packages/core/src/telemetry"}}},{"functionCall":{"name":"read_file","args":{"file_path":"packages/core/src/telemetry/memory-monitor.ts"}}}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":30,"candidatesTokenCount":80,"totalTokenCount":110,"promptTokensDetails":[{"modality":"TEXT","tokenCount":30}]}}]}
|
||||
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"I found the memory monitoring code. Here's a summary:\n\nThe `MemoryMonitor` class in `packages/core/src/telemetry/memory-monitor.ts` provides:\n\n1. **Continuous monitoring** via `start()`/`stop()` with configurable intervals\n2. **V8 heap snapshots** using `v8.getHeapStatistics()` and `process.memoryUsage()`\n3. **High-water mark tracking** to detect significant memory growth\n4. **Rate-limited recording** to avoid metric flood\n5. **Activity detection** — only records when user is active\n\nThe class uses a singleton pattern via `initializeMemoryMonitor()` for global access."}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":500,"candidatesTokenCount":120,"totalTokenCount":620,"promptTokensDetails":[{"modality":"TEXT","tokenCount":500}]}}]}
|
||||
@@ -1,10 +0,0 @@
|
||||
{"method":"generateContent","response":{"candidates":[{"content":{"parts":[{"text":"0"}],"role":"model"},"finishReason":"STOP","index":0}]}}
|
||||
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"Hello! I'm ready to help you with your coding tasks. What would you like to work on today?"}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":5,"candidatesTokenCount":18,"totalTokenCount":23,"promptTokensDetails":[{"modality":"TEXT","tokenCount":5}]}}]}
|
||||
{"method":"generateContent","response":{"candidates":[{"content":{"parts":[{"text":"0"}],"role":"model"},"finishReason":"STOP","index":0}]}}
|
||||
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"JavaScript is a high-level, interpreted programming language. It was originally designed for adding interactivity to web pages."}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":25,"candidatesTokenCount":60,"totalTokenCount":85,"promptTokensDetails":[{"modality":"TEXT","tokenCount":25}]}}]}
|
||||
{"method":"generateContent","response":{"candidates":[{"content":{"parts":[{"text":"0"}],"role":"model"},"finishReason":"STOP","index":0}]}}
|
||||
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"TypeScript is a typed superset of JavaScript developed by Microsoft. The main differences from JavaScript are static typing and better tooling."}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":45,"candidatesTokenCount":80,"totalTokenCount":125,"promptTokensDetails":[{"modality":"TEXT","tokenCount":45}]}}]}
|
||||
{"method":"generateContent","response":{"candidates":[{"content":{"parts":[{"text":"0"}],"role":"model"},"finishReason":"STOP","index":0}]}}
|
||||
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"Here is a simple TypeScript function:\n\nfunction greet(name: string): string { return `Hello, ${name}!`; }"}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":60,"candidatesTokenCount":55,"totalTokenCount":115,"promptTokensDetails":[{"modality":"TEXT","tokenCount":60}]}}]}
|
||||
{"method":"generateContent","response":{"candidates":[{"content":{"parts":[{"text":"0"}],"role":"model"},"finishReason":"STOP","index":0}]}}
|
||||
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"Here are 5 key TypeScript best practices: Enable strict mode, prefer interfaces, use union types, leverage type inference, and use readonly."}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":75,"candidatesTokenCount":70,"totalTokenCount":145,"promptTokensDetails":[{"modality":"TEXT","tokenCount":75}]}}]}
|
||||
@@ -1,2 +0,0 @@
|
||||
{"method":"generateContent","response":{"candidates":[{"content":{"parts":[{"text":"0"}],"role":"model"},"finishReason":"STOP","index":0}]}}
|
||||
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"The capital of France is Paris. It has been the capital since the 10th century and is known for iconic landmarks like the Eiffel Tower, the Louvre Museum, and Notre-Dame Cathedral. Paris is also the most populous city in France, with a metropolitan area population of over 12 million people."}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":7,"candidatesTokenCount":55,"totalTokenCount":62,"promptTokensDetails":[{"modality":"TEXT","tokenCount":7}]}}]}
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"noEmit": true,
|
||||
"allowJs": true
|
||||
},
|
||||
"include": ["**/*.ts"],
|
||||
"references": [
|
||||
{ "path": "../packages/core" },
|
||||
{ "path": "../packages/test-utils" }
|
||||
]
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
testTimeout: 600000, // 10 minutes — memory profiling is slow
|
||||
globalSetup: './globalSetup.ts',
|
||||
reporters: ['default'],
|
||||
include: ['**/*.test.ts'],
|
||||
retry: 0, // No retries for memory tests — noise is handled by tolerance
|
||||
fileParallelism: false, // Must run serially to avoid memory interference
|
||||
pool: 'forks', // Use forks pool for --expose-gc support
|
||||
poolOptions: {
|
||||
forks: {
|
||||
singleFork: true, // Single process for accurate per-test memory readings
|
||||
execArgv: ['--expose-gc'], // Enable global.gc() for forced GC
|
||||
},
|
||||
},
|
||||
env: {
|
||||
GEMINI_TEST_TYPE: 'memory',
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -446,8 +446,7 @@
|
||||
"version": "2.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.11.0.tgz",
|
||||
"integrity": "sha512-sBXGT13cpmPR5BMgHE6UEEfEaShh5Ror6rfN3yEK5si7QVrtZg8LEPQb0VVhiLRUslD2yLnXtnRzG035J/mZXQ==",
|
||||
"license": "(Apache-2.0 AND BSD-3-Clause)",
|
||||
"peer": true
|
||||
"license": "(Apache-2.0 AND BSD-3-Clause)"
|
||||
},
|
||||
"node_modules/@bundled-es-modules/cookie": {
|
||||
"version": "2.0.1",
|
||||
@@ -1450,7 +1449,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.13.4.tgz",
|
||||
"integrity": "sha512-GsFaMXCkMqkKIvwCQjCrwH+GHbPKBjhwo/8ZuUkWHqbI73Kky9I+pQltrlT0+MWpedCoosda53lgjYfyEPgxBg==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@grpc/proto-loader": "^0.7.13",
|
||||
"@js-sdsl/ordered-map": "^4.4.2"
|
||||
@@ -2157,7 +2155,6 @@
|
||||
"integrity": "sha512-t54CUOsFMappY1Jbzb7fetWeO0n6K0k/4+/ZpkS+3Joz8I4VcvY9OiEBFRYISqaI2fq5sCiPtAjRDOzVYG8m+Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@octokit/auth-token": "^6.0.0",
|
||||
"@octokit/graphql": "^9.0.2",
|
||||
@@ -2338,7 +2335,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
|
||||
"integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
@@ -2388,7 +2384,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz",
|
||||
"integrity": "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@opentelemetry/semantic-conventions": "^1.29.0"
|
||||
},
|
||||
@@ -2763,7 +2758,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.5.0.tgz",
|
||||
"integrity": "sha512-F8W52ApePshpoSrfsSk1H2yJn9aKjCrbpQF1M9Qii0GHzbfVeFUB+rc3X4aggyZD8x9Gu3Slua+s6krmq6Dt8g==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@opentelemetry/core": "2.5.0",
|
||||
"@opentelemetry/semantic-conventions": "^1.29.0"
|
||||
@@ -2797,7 +2791,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.5.0.tgz",
|
||||
"integrity": "sha512-BeJLtU+f5Gf905cJX9vXFQorAr6TAfK3SPvTFqP+scfIpDQEJfRaGJWta7sJgP+m4dNtBf9y3yvBKVAZZtJQVA==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@opentelemetry/core": "2.5.0",
|
||||
"@opentelemetry/resources": "2.5.0"
|
||||
@@ -2852,7 +2845,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.5.0.tgz",
|
||||
"integrity": "sha512-VzRf8LzotASEyNDUxTdaJ9IRJ1/h692WyArDBInf5puLCjxbICD6XkHgpuudis56EndyS7LYFmtTMny6UABNdQ==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@opentelemetry/core": "2.5.0",
|
||||
"@opentelemetry/resources": "2.5.0",
|
||||
@@ -4089,7 +4081,6 @@
|
||||
"integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
@@ -4364,7 +4355,6 @@
|
||||
"integrity": "sha512-6sMvZePQrnZH2/cJkwRpkT7DxoAWh+g6+GFRK6bV3YQo7ogi3SX5rgF6099r5Q53Ma5qeT7LGmOmuIutF4t3lA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.35.0",
|
||||
"@typescript-eslint/types": "8.35.0",
|
||||
@@ -5238,7 +5228,6 @@
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@@ -5580,12 +5569,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/asciichart": {
|
||||
"version": "1.5.25",
|
||||
"resolved": "https://registry.npmjs.org/asciichart/-/asciichart-1.5.25.tgz",
|
||||
"integrity": "sha512-PNxzXIPPOtWq8T7bgzBtk9cI2lgS4SJZthUHEiQ1aoIc3lNzGfUvIvo9LiAnq26TACo9t1/4qP6KTGAUbzX9Xg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/assertion-error": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
|
||||
@@ -7379,8 +7362,7 @@
|
||||
"version": "0.0.1581282",
|
||||
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1581282.tgz",
|
||||
"integrity": "sha512-nv7iKtNZQshSW2hKzYNr46nM/Cfh5SEvE2oV0/SEGgc9XupIY5ggf84Cz8eJIkBce7S3bmTAauFD6aysMpnqsQ==",
|
||||
"license": "BSD-3-Clause",
|
||||
"peer": true
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/dezalgo": {
|
||||
"version": "1.0.4",
|
||||
@@ -7964,7 +7946,6 @@
|
||||
"integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.2.0",
|
||||
"@eslint-community/regexpp": "^4.12.1",
|
||||
@@ -8482,7 +8463,6 @@
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
|
||||
"integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"accepts": "^2.0.0",
|
||||
"body-parser": "^2.2.1",
|
||||
@@ -9795,7 +9775,6 @@
|
||||
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.7.tgz",
|
||||
"integrity": "sha512-jq9l1DM0zVIvsm3lv9Nw9nlJnMNPOcAtsbsgiUhWcFzPE99Gvo6yRTlszSLLYacMeQ6quHD6hMfId8crVHvexw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=16.9.0"
|
||||
}
|
||||
@@ -10074,7 +10053,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@jrichman/ink/-/ink-6.6.7.tgz",
|
||||
"integrity": "sha512-bDzQLpLzK/dn9Ur/Ku88ZZR9totVcMGrGYAgPHidsAAbe9NKztU1fggj/iu0wRp5g1kBeALb3cfagFGdDxAU1w==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"ansi-escapes": "^7.0.0",
|
||||
"ansi-styles": "^6.2.3",
|
||||
@@ -13848,7 +13826,6 @@
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
|
||||
"integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -13859,7 +13836,6 @@
|
||||
"integrity": "sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"shell-quote": "^1.6.1",
|
||||
"ws": "^7"
|
||||
@@ -16009,7 +15985,6 @@
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -16232,8 +16207,7 @@
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"license": "0BSD",
|
||||
"peer": true
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/tsx": {
|
||||
"version": "4.20.3",
|
||||
@@ -16241,7 +16215,6 @@
|
||||
"integrity": "sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "~0.25.0",
|
||||
"get-tsconfig": "^4.7.5"
|
||||
@@ -16407,7 +16380,6 @@
|
||||
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@@ -16630,7 +16602,6 @@
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.2.2.tgz",
|
||||
"integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
"fdir": "^6.5.0",
|
||||
@@ -16744,7 +16715,6 @@
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -16757,7 +16727,6 @@
|
||||
"resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz",
|
||||
"integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/chai": "^5.2.2",
|
||||
"@vitest/expect": "3.2.4",
|
||||
@@ -17405,7 +17374,6 @@
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
||||
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
@@ -17849,7 +17817,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.3.tgz",
|
||||
"integrity": "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@grpc/proto-loader": "^0.8.0",
|
||||
"@js-sdsl/ordered-map": "^4.4.2"
|
||||
@@ -17953,7 +17920,6 @@
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -18013,7 +17979,6 @@
|
||||
"dependencies": {
|
||||
"@google/gemini-cli-core": "file:../core",
|
||||
"@lydell/node-pty": "1.1.0",
|
||||
"asciichart": "^1.5.25",
|
||||
"strip-ansi": "^7.1.2",
|
||||
"vitest": "^3.2.4"
|
||||
},
|
||||
|
||||
@@ -51,8 +51,6 @@
|
||||
"test:integration:all": "npm run test:integration:sandbox:none && npm run test:integration:sandbox:docker && npm run test:integration:sandbox:podman",
|
||||
"test:integration:flaky": "cross-env RUN_FLAKY_INTEGRATION=1 npm run test:integration:sandbox:none",
|
||||
"test:integration:sandbox:none": "cross-env GEMINI_SANDBOX=false vitest run --root ./integration-tests",
|
||||
"test:memory": "vitest run --root ./memory-tests",
|
||||
"test:memory:update-baselines": "cross-env UPDATE_MEMORY_BASELINES=true vitest run --root ./memory-tests",
|
||||
"test:integration:sandbox:docker": "cross-env GEMINI_SANDBOX=docker npm run build:sandbox && cross-env GEMINI_SANDBOX=docker vitest run --root ./integration-tests",
|
||||
"test:integration:sandbox:podman": "cross-env GEMINI_SANDBOX=podman vitest run --root ./integration-tests",
|
||||
"lint": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192\" eslint . --cache --max-warnings 0",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env node
|
||||
#!/usr/bin/env -S node --no-warnings=DEP0040
|
||||
|
||||
/**
|
||||
* @license
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env node
|
||||
#!/usr/bin/env -S node --no-warnings=DEP0040
|
||||
|
||||
/**
|
||||
* @license
|
||||
|
||||
@@ -26,11 +26,5 @@ describe('CommandHandler', () => {
|
||||
|
||||
const init = parse('/init');
|
||||
expect(init.commandToExecute?.name).toBe('init');
|
||||
|
||||
const about = parse('/about');
|
||||
expect(about.commandToExecute?.name).toBe('about');
|
||||
|
||||
const help = parse('/help');
|
||||
expect(help.commandToExecute?.name).toBe('help');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,8 +10,6 @@ import { MemoryCommand } from './commands/memory.js';
|
||||
import { ExtensionsCommand } from './commands/extensions.js';
|
||||
import { InitCommand } from './commands/init.js';
|
||||
import { RestoreCommand } from './commands/restore.js';
|
||||
import { AboutCommand } from './commands/about.js';
|
||||
import { HelpCommand } from './commands/help.js';
|
||||
|
||||
export class CommandHandler {
|
||||
private registry: CommandRegistry;
|
||||
@@ -26,8 +24,6 @@ export class CommandHandler {
|
||||
registry.register(new ExtensionsCommand());
|
||||
registry.register(new InitCommand());
|
||||
registry.register(new RestoreCommand());
|
||||
registry.register(new AboutCommand());
|
||||
registry.register(new HelpCommand(registry));
|
||||
return registry;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {
|
||||
IdeClient,
|
||||
UserAccountManager,
|
||||
getVersion,
|
||||
} from '@google/gemini-cli-core';
|
||||
import type {
|
||||
Command,
|
||||
CommandContext,
|
||||
CommandExecutionResponse,
|
||||
} from './types.js';
|
||||
import process from 'node:process';
|
||||
|
||||
export class AboutCommand implements Command {
|
||||
readonly name = 'about';
|
||||
readonly description = 'Show version and environment info';
|
||||
|
||||
async execute(
|
||||
context: CommandContext,
|
||||
_args: string[] = [],
|
||||
): Promise<CommandExecutionResponse> {
|
||||
const osVersion = process.platform;
|
||||
let sandboxEnv = 'no sandbox';
|
||||
if (process.env['SANDBOX'] && process.env['SANDBOX'] !== 'sandbox-exec') {
|
||||
sandboxEnv = process.env['SANDBOX'];
|
||||
} else if (process.env['SANDBOX'] === 'sandbox-exec') {
|
||||
sandboxEnv = `sandbox-exec (${
|
||||
process.env['SEATBELT_PROFILE'] || 'unknown'
|
||||
})`;
|
||||
}
|
||||
const modelVersion = context.agentContext.config.getModel() || 'Unknown';
|
||||
const cliVersion = await getVersion();
|
||||
const selectedAuthType =
|
||||
context.settings.merged?.security?.auth?.selectedType ?? '';
|
||||
const gcpProject = process.env['GOOGLE_CLOUD_PROJECT'] || '';
|
||||
const ideClient = await getIdeClientName(context);
|
||||
|
||||
const userAccountManager = new UserAccountManager();
|
||||
const cachedAccount = userAccountManager.getCachedGoogleAccount();
|
||||
const userEmail = cachedAccount ?? 'Unknown';
|
||||
|
||||
const tier = context.agentContext.config.getUserTierName() || 'Unknown';
|
||||
|
||||
const info = [
|
||||
`- Version: ${cliVersion}`,
|
||||
`- OS: ${osVersion}`,
|
||||
`- Sandbox: ${sandboxEnv}`,
|
||||
`- Model: ${modelVersion}`,
|
||||
`- Auth Type: ${selectedAuthType}`,
|
||||
`- GCP Project: ${gcpProject}`,
|
||||
`- IDE Client: ${ideClient}`,
|
||||
`- User Email: ${userEmail}`,
|
||||
`- Tier: ${tier}`,
|
||||
].join('\n');
|
||||
|
||||
return {
|
||||
name: this.name,
|
||||
data: `Gemini CLI Info:\n${info}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async function getIdeClientName(context: CommandContext) {
|
||||
if (!context.agentContext.config.getIdeMode()) {
|
||||
return '';
|
||||
}
|
||||
const ideClient = await IdeClient.getInstance();
|
||||
return ideClient?.getDetectedIdeDisplayName() ?? '';
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { HelpCommand } from './help.js';
|
||||
import { CommandRegistry } from './commandRegistry.js';
|
||||
import type { Command, CommandContext } from './types.js';
|
||||
|
||||
describe('HelpCommand', () => {
|
||||
it('returns formatted help text with sorted commands', async () => {
|
||||
const registry = new CommandRegistry();
|
||||
|
||||
const cmdB: Command = {
|
||||
name: 'bravo',
|
||||
description: 'Bravo command',
|
||||
execute: async () => ({ name: 'bravo', data: '' }),
|
||||
};
|
||||
|
||||
const cmdA: Command = {
|
||||
name: 'alpha',
|
||||
description: 'Alpha command',
|
||||
execute: async () => ({ name: 'alpha', data: '' }),
|
||||
};
|
||||
|
||||
registry.register(cmdB);
|
||||
registry.register(cmdA);
|
||||
|
||||
const helpCommand = new HelpCommand(registry);
|
||||
|
||||
const context = {} as CommandContext;
|
||||
|
||||
const response = await helpCommand.execute(context, []);
|
||||
|
||||
expect(response.name).toBe('help');
|
||||
|
||||
const data = response.data as string;
|
||||
|
||||
expect(data).toContain('Gemini CLI Help:');
|
||||
expect(data).toContain('### Basics');
|
||||
expect(data).toContain('### Commands');
|
||||
|
||||
const lines = data.split('\n');
|
||||
const alphaIndex = lines.findIndex((l) => l.includes('/alpha'));
|
||||
const bravoIndex = lines.findIndex((l) => l.includes('/bravo'));
|
||||
|
||||
expect(alphaIndex).toBeLessThan(bravoIndex);
|
||||
expect(alphaIndex).not.toBe(-1);
|
||||
expect(bravoIndex).not.toBe(-1);
|
||||
});
|
||||
});
|
||||
@@ -1,50 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type {
|
||||
Command,
|
||||
CommandContext,
|
||||
CommandExecutionResponse,
|
||||
} from './types.js';
|
||||
import type { CommandRegistry } from './commandRegistry.js';
|
||||
|
||||
export class HelpCommand implements Command {
|
||||
readonly name = 'help';
|
||||
readonly description = 'Show available commands';
|
||||
|
||||
constructor(private registry: CommandRegistry) {}
|
||||
|
||||
async execute(
|
||||
_context: CommandContext,
|
||||
_args: string[] = [],
|
||||
): Promise<CommandExecutionResponse> {
|
||||
const commands = this.registry
|
||||
.getAllCommands()
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
|
||||
const lines: string[] = [];
|
||||
|
||||
lines.push('Gemini CLI Help:');
|
||||
lines.push('');
|
||||
lines.push('### Basics');
|
||||
lines.push(
|
||||
'- **Add context**: Use `@` to specify files for context (e.g., `@src/myFile.ts`) to target specific files or folders.',
|
||||
);
|
||||
lines.push('');
|
||||
|
||||
lines.push('### Commands');
|
||||
for (const cmd of commands) {
|
||||
if (cmd.description) {
|
||||
lines.push(`- **/${cmd.name}** - ${cmd.description}`);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
name: this.name,
|
||||
data: lines.join('\n'),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -87,7 +87,7 @@ describe('handleValidate', () => {
|
||||
});
|
||||
expect(debugLoggerErrorSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining(
|
||||
'Invalid extension name: "INVALID_NAME". Only letters (a-z, A-Z), numbers (0-9), and dashes (-) are allowed.',
|
||||
'Invalid extension name: "INVALID_NAME". Only letters (a-z, A-Z), numbers (0-9), dashes (-), and dots (.) are allowed. Names must start and end with an alphanumeric character.',
|
||||
),
|
||||
);
|
||||
expect(processSpy).toHaveBeenCalledWith(1);
|
||||
|
||||
@@ -4,16 +4,8 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {
|
||||
vi,
|
||||
describe,
|
||||
it,
|
||||
expect,
|
||||
beforeEach,
|
||||
afterEach,
|
||||
type MockInstance,
|
||||
} from 'vitest';
|
||||
import { type Config } from '@google/gemini-cli-core';
|
||||
import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { coreEvents, type Config } from '@google/gemini-cli-core';
|
||||
import { handleList, listCommand } from './list.js';
|
||||
import { loadSettings, type LoadedSettings } from '../../config/settings.js';
|
||||
import { loadCliConfig } from '../../config/config.js';
|
||||
@@ -40,16 +32,12 @@ vi.mock('../utils.js', () => ({
|
||||
describe('skills list command', () => {
|
||||
const mockLoadSettings = vi.mocked(loadSettings);
|
||||
const mockLoadCliConfig = vi.mocked(loadCliConfig);
|
||||
let stdoutWriteSpy: MockInstance<typeof process.stdout.write>;
|
||||
|
||||
beforeEach(async () => {
|
||||
vi.clearAllMocks();
|
||||
mockLoadSettings.mockReturnValue({
|
||||
merged: {},
|
||||
} as unknown as LoadedSettings);
|
||||
stdoutWriteSpy = vi
|
||||
.spyOn(process.stdout, 'write')
|
||||
.mockImplementation(() => true);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -68,7 +56,10 @@ describe('skills list command', () => {
|
||||
|
||||
await handleList({});
|
||||
|
||||
expect(stdoutWriteSpy).toHaveBeenCalledWith('No skills discovered.\n');
|
||||
expect(coreEvents.emitConsoleLog).toHaveBeenCalledWith(
|
||||
'log',
|
||||
'No skills discovered.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should list all discovered skills', async () => {
|
||||
@@ -96,19 +87,24 @@ describe('skills list command', () => {
|
||||
|
||||
await handleList({});
|
||||
|
||||
expect(stdoutWriteSpy).toHaveBeenCalledWith(
|
||||
chalk.bold('Discovered Agent Skills:') + '\n\n',
|
||||
expect(coreEvents.emitConsoleLog).toHaveBeenCalledWith(
|
||||
'log',
|
||||
chalk.bold('Discovered Agent Skills:'),
|
||||
);
|
||||
expect(stdoutWriteSpy).toHaveBeenCalledWith(
|
||||
expect(coreEvents.emitConsoleLog).toHaveBeenCalledWith(
|
||||
'log',
|
||||
expect.stringContaining('skill1'),
|
||||
);
|
||||
expect(stdoutWriteSpy).toHaveBeenCalledWith(
|
||||
expect(coreEvents.emitConsoleLog).toHaveBeenCalledWith(
|
||||
'log',
|
||||
expect.stringContaining(chalk.green('[Enabled]')),
|
||||
);
|
||||
expect(stdoutWriteSpy).toHaveBeenCalledWith(
|
||||
expect(coreEvents.emitConsoleLog).toHaveBeenCalledWith(
|
||||
'log',
|
||||
expect.stringContaining('skill2'),
|
||||
);
|
||||
expect(stdoutWriteSpy).toHaveBeenCalledWith(
|
||||
expect(coreEvents.emitConsoleLog).toHaveBeenCalledWith(
|
||||
'log',
|
||||
expect.stringContaining(chalk.red('[Disabled]')),
|
||||
);
|
||||
});
|
||||
@@ -139,10 +135,12 @@ describe('skills list command', () => {
|
||||
|
||||
// Default
|
||||
await handleList({ all: false });
|
||||
expect(stdoutWriteSpy).toHaveBeenCalledWith(
|
||||
expect(coreEvents.emitConsoleLog).toHaveBeenCalledWith(
|
||||
'log',
|
||||
expect.stringContaining('regular'),
|
||||
);
|
||||
expect(stdoutWriteSpy).not.toHaveBeenCalledWith(
|
||||
expect(coreEvents.emitConsoleLog).not.toHaveBeenCalledWith(
|
||||
'log',
|
||||
expect.stringContaining('builtin'),
|
||||
);
|
||||
|
||||
@@ -150,13 +148,16 @@ describe('skills list command', () => {
|
||||
|
||||
// With all: true
|
||||
await handleList({ all: true });
|
||||
expect(stdoutWriteSpy).toHaveBeenCalledWith(
|
||||
expect(coreEvents.emitConsoleLog).toHaveBeenCalledWith(
|
||||
'log',
|
||||
expect.stringContaining('regular'),
|
||||
);
|
||||
expect(stdoutWriteSpy).toHaveBeenCalledWith(
|
||||
expect(coreEvents.emitConsoleLog).toHaveBeenCalledWith(
|
||||
'log',
|
||||
expect.stringContaining('builtin'),
|
||||
);
|
||||
expect(stdoutWriteSpy).toHaveBeenCalledWith(
|
||||
expect(coreEvents.emitConsoleLog).toHaveBeenCalledWith(
|
||||
'log',
|
||||
expect.stringContaining(chalk.gray(' [Built-in]')),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
import type { CommandModule } from 'yargs';
|
||||
import { debugLogger } from '@google/gemini-cli-core';
|
||||
import { loadSettings } from '../../config/settings.js';
|
||||
import { loadCliConfig, type CliArgs } from '../../config/config.js';
|
||||
import { exitCli } from '../utils.js';
|
||||
@@ -41,11 +42,12 @@ export async function handleList(args: { all?: boolean }) {
|
||||
});
|
||||
|
||||
if (skills.length === 0) {
|
||||
process.stdout.write('No skills discovered.\n');
|
||||
debugLogger.log('No skills discovered.');
|
||||
return;
|
||||
}
|
||||
|
||||
process.stdout.write(chalk.bold('Discovered Agent Skills:') + '\n\n');
|
||||
debugLogger.log(chalk.bold('Discovered Agent Skills:'));
|
||||
debugLogger.log('');
|
||||
|
||||
for (const skill of skills) {
|
||||
const status = skill.disabled
|
||||
@@ -54,11 +56,10 @@ export async function handleList(args: { all?: boolean }) {
|
||||
|
||||
const builtinSuffix = skill.isBuiltin ? chalk.gray(' [Built-in]') : '';
|
||||
|
||||
process.stdout.write(
|
||||
`${chalk.bold(skill.name)} ${status}${builtinSuffix}\n`,
|
||||
);
|
||||
process.stdout.write(` Description: ${skill.description}\n`);
|
||||
process.stdout.write(` Location: ${skill.location}\n\n`);
|
||||
debugLogger.log(`${chalk.bold(skill.name)} ${status}${builtinSuffix}`);
|
||||
debugLogger.log(` Description: ${skill.description}`);
|
||||
debugLogger.log(` Location: ${skill.location}`);
|
||||
debugLogger.log('');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -938,8 +938,6 @@ export async function loadCliConfig(
|
||||
: undefined,
|
||||
blockedEnvironmentVariables:
|
||||
settings.security?.environmentVariableRedaction?.blocked,
|
||||
allowedEnvironmentVariables:
|
||||
settings.security?.environmentVariableRedaction?.allowed,
|
||||
enableEnvironmentVariableRedaction:
|
||||
settings.security?.environmentVariableRedaction?.enabled,
|
||||
userMemory: memoryContent,
|
||||
|
||||
@@ -11,7 +11,19 @@ import chalk from 'chalk';
|
||||
import { ExtensionEnablementManager } from './extensions/extensionEnablement.js';
|
||||
import { type MergedSettings, SettingScope } from './settings.js';
|
||||
import { createHash, randomUUID } from 'node:crypto';
|
||||
import { loadInstallMetadata, type ExtensionConfig } from './extension.js';
|
||||
import {
|
||||
loadInstallMetadata,
|
||||
loadGeminiConfig,
|
||||
createGeminiExtension,
|
||||
type ExtensionConfig,
|
||||
} from './extension.js';
|
||||
import {
|
||||
findManifest,
|
||||
loadOpenPluginConfig,
|
||||
createOpenPlugin,
|
||||
type OpenPluginConfig,
|
||||
OPEN_PLUGIN_NAME_REGEX,
|
||||
} from './plugin.js';
|
||||
import {
|
||||
isWorkspaceTrusted,
|
||||
loadTrustedFolders,
|
||||
@@ -65,7 +77,6 @@ import { maybeRequestConsentOrFail } from './extensions/consent.js';
|
||||
import { resolveEnvVarsInObject } from '../utils/envVarResolver.js';
|
||||
import { ExtensionStorage } from './extensions/storage.js';
|
||||
import {
|
||||
EXTENSIONS_CONFIG_FILENAME,
|
||||
INSTALL_METADATA_FILENAME,
|
||||
recursivelyHydrateStrings,
|
||||
type JsonObject,
|
||||
@@ -293,6 +304,10 @@ Would you like to attempt to install via "git clone" instead?`,
|
||||
try {
|
||||
newExtensionConfig = await this.loadExtensionConfig(localSourcePath);
|
||||
|
||||
if (!newExtensionConfig) {
|
||||
throw new Error('Failed to load extension configuration');
|
||||
}
|
||||
|
||||
const newExtensionName = newExtensionConfig.name;
|
||||
const previousName = previousExtensionConfig?.name ?? newExtensionName;
|
||||
const previous = this.getExtensions().find(
|
||||
@@ -757,23 +772,59 @@ Would you like to attempt to install via "git clone" instead?`,
|
||||
effectiveExtensionPath = installMetadata.source;
|
||||
}
|
||||
|
||||
try {
|
||||
let config = await this.loadExtensionConfig(effectiveExtensionPath);
|
||||
const manifestInfo = findManifest(effectiveExtensionPath);
|
||||
if (!manifestInfo) {
|
||||
debugLogger.warn(
|
||||
`Warning: Skipping extension in ${effectiveExtensionPath}: No manifest found.`,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
const extensionId = getExtensionId(config, installMetadata);
|
||||
try {
|
||||
// Bifurcate loading based on manifest type
|
||||
if (manifestInfo.type === 'open-plugin') {
|
||||
const config = await loadOpenPluginConfig(
|
||||
manifestInfo.path,
|
||||
effectiveExtensionPath,
|
||||
this.workspaceDir,
|
||||
);
|
||||
validateName(config.name);
|
||||
const extensionId = getExtensionId(config, installMetadata);
|
||||
return await createOpenPlugin(
|
||||
effectiveExtensionPath,
|
||||
manifestInfo.path,
|
||||
this.extensionEnablementManager.isEnabled(
|
||||
config.name,
|
||||
this.workspaceDir,
|
||||
),
|
||||
extensionId,
|
||||
this.workspaceDir,
|
||||
installMetadata,
|
||||
);
|
||||
}
|
||||
|
||||
// Gemini CLI Extension loading path
|
||||
const rawConfig = await loadGeminiConfig(
|
||||
manifestInfo.path,
|
||||
effectiveExtensionPath,
|
||||
this.workspaceDir,
|
||||
);
|
||||
validateName(rawConfig.name);
|
||||
|
||||
const extensionId = getExtensionId(rawConfig, installMetadata);
|
||||
|
||||
let userSettings: Record<string, string> = {};
|
||||
let workspaceSettings: Record<string, string> = {};
|
||||
|
||||
if (this.settings.experimental.extensionConfig) {
|
||||
userSettings = await getScopedEnvContents(
|
||||
config,
|
||||
rawConfig,
|
||||
extensionId,
|
||||
ExtensionSettingScope.USER,
|
||||
);
|
||||
if (isWorkspaceTrusted(this.settings).isTrusted) {
|
||||
workspaceSettings = await getScopedEnvContents(
|
||||
config,
|
||||
rawConfig,
|
||||
extensionId,
|
||||
ExtensionSettingScope.WORKSPACE,
|
||||
this.workspaceDir,
|
||||
@@ -782,7 +833,8 @@ Would you like to attempt to install via "git clone" instead?`,
|
||||
}
|
||||
|
||||
const customEnv = { ...userSettings, ...workspaceSettings };
|
||||
config = resolveEnvVarsInObject(config, customEnv);
|
||||
// config is already hydrated in loadGeminiConfig, but we might need to re-hydrate with customEnv
|
||||
const config = resolveEnvVarsInObject(rawConfig, customEnv);
|
||||
|
||||
const resolvedSettings: ResolvedExtensionSetting[] = [];
|
||||
if (config.settings && this.settings.experimental.extensionConfig) {
|
||||
@@ -874,6 +926,7 @@ Would you like to attempt to install via "git clone" instead?`,
|
||||
|
||||
const hydrationContext: VariableContext = {
|
||||
extensionPath: effectiveExtensionPath,
|
||||
PLUGIN_ROOT: effectiveExtensionPath,
|
||||
workspacePath: this.workspaceDir,
|
||||
'/': path.sep,
|
||||
pathSeparator: path.sep,
|
||||
@@ -957,30 +1010,23 @@ Would you like to attempt to install via "git clone" instead?`,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
name: config.name,
|
||||
version: config.version,
|
||||
path: effectiveExtensionPath,
|
||||
contextFiles,
|
||||
installMetadata,
|
||||
migratedTo: config.migratedTo,
|
||||
mcpServers: config.mcpServers,
|
||||
excludeTools: config.excludeTools,
|
||||
hooks,
|
||||
isActive: this.extensionEnablementManager.isEnabled(
|
||||
return createGeminiExtension(
|
||||
config,
|
||||
effectiveExtensionPath,
|
||||
this.extensionEnablementManager.isEnabled(
|
||||
config.name,
|
||||
this.workspaceDir,
|
||||
),
|
||||
id: getExtensionId(config, installMetadata),
|
||||
settings: config.settings,
|
||||
extensionId,
|
||||
contextFiles,
|
||||
resolvedSettings,
|
||||
installMetadata,
|
||||
hooks,
|
||||
skills,
|
||||
agents: agentLoadResult.agents,
|
||||
themes: config.themes,
|
||||
agentLoadResult.agents,
|
||||
rules,
|
||||
checkers,
|
||||
plan: config.plan,
|
||||
};
|
||||
);
|
||||
} catch (e) {
|
||||
debugLogger.error(
|
||||
`Warning: Skipping extension in ${effectiveExtensionPath}: ${getErrorMessage(
|
||||
@@ -1013,39 +1059,27 @@ Would you like to attempt to install via "git clone" instead?`,
|
||||
}
|
||||
|
||||
async loadExtensionConfig(extensionDir: string): Promise<ExtensionConfig> {
|
||||
const configFilePath = path.join(extensionDir, EXTENSIONS_CONFIG_FILENAME);
|
||||
if (!fs.existsSync(configFilePath)) {
|
||||
throw new Error(`Configuration file not found at ${configFilePath}`);
|
||||
const manifestInfo = findManifest(extensionDir);
|
||||
if (!manifestInfo) {
|
||||
throw new Error(`Configuration file not found in ${extensionDir}`);
|
||||
}
|
||||
try {
|
||||
const configContent = await fs.promises.readFile(configFilePath, 'utf-8');
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
const rawConfig = JSON.parse(configContent) as ExtensionConfig;
|
||||
if (!rawConfig.name || !rawConfig.version) {
|
||||
throw new Error(
|
||||
`Invalid configuration in ${configFilePath}: missing ${!rawConfig.name ? '"name"' : '"version"'}`,
|
||||
);
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
const config = recursivelyHydrateStrings(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
rawConfig as unknown as JsonObject,
|
||||
{
|
||||
extensionPath: extensionDir,
|
||||
workspacePath: this.workspaceDir,
|
||||
'/': path.sep,
|
||||
pathSeparator: path.sep,
|
||||
},
|
||||
) as unknown as ExtensionConfig;
|
||||
|
||||
if (manifestInfo.type === 'open-plugin') {
|
||||
const config = await loadOpenPluginConfig(
|
||||
manifestInfo.path,
|
||||
extensionDir,
|
||||
this.workspaceDir,
|
||||
);
|
||||
validateName(config.name);
|
||||
return config;
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
`Failed to load extension config from ${configFilePath}: ${getErrorMessage(
|
||||
e,
|
||||
)}`,
|
||||
} else {
|
||||
const config = await loadGeminiConfig(
|
||||
manifestInfo.path,
|
||||
extensionDir,
|
||||
this.workspaceDir,
|
||||
);
|
||||
validateName(config.name);
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1111,6 +1145,9 @@ Would you like to attempt to install via "git clone" instead?`,
|
||||
|
||||
const status = workspaceEnabled ? chalk.green('✓') : chalk.red('✗');
|
||||
let output = `${status} ${extension.name} (${extension.version})`;
|
||||
if (extension.manifestType) {
|
||||
output += ` [${extension.manifestType === 'open-plugin' ? 'Open Plugin' : 'Gemini Extension'}]`;
|
||||
}
|
||||
output += `\n ID: ${extension.id}`;
|
||||
output += `\n name: ${hashValue(extension.name)}`;
|
||||
|
||||
@@ -1279,9 +1316,9 @@ function getContextFileNames(config: ExtensionConfig): string[] {
|
||||
}
|
||||
|
||||
function validateName(name: string) {
|
||||
if (!/^[a-zA-Z0-9-]+$/.test(name)) {
|
||||
if (!OPEN_PLUGIN_NAME_REGEX.test(name) && !/^[a-zA-Z0-9-]+$/.test(name)) {
|
||||
throw new Error(
|
||||
`Invalid extension name: "${name}". Only letters (a-z, A-Z), numbers (0-9), and dashes (-) are allowed.`,
|
||||
`Invalid extension name: "${name}". Only letters (a-z, A-Z), numbers (0-9), dashes (-), and dots (.) are allowed. Names must start and end with an alphanumeric character.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1328,7 +1365,7 @@ export async function inferInstallMetadata(
|
||||
}
|
||||
|
||||
export function getExtensionId(
|
||||
config: ExtensionConfig,
|
||||
config: ExtensionConfig | OpenPluginConfig,
|
||||
installMetadata?: ExtensionInstallMetadata,
|
||||
): string {
|
||||
// IDs are created by hashing details of the installation source in order to
|
||||
|
||||
@@ -46,6 +46,7 @@ import {
|
||||
INSTALL_METADATA_FILENAME,
|
||||
} from './extensions/variables.js';
|
||||
import { hashValue, ExtensionManager } from './extension-manager.js';
|
||||
import { loadGeminiConfig, createGeminiExtension } from './extension.js';
|
||||
import { ExtensionStorage } from './extensions/storage.js';
|
||||
import { INSTALL_WARNING_MESSAGE } from './extensions/consent.js';
|
||||
import type { ExtensionSetting } from './extensions/extensionSettings.js';
|
||||
@@ -685,9 +686,7 @@ name = "yolo-checker"
|
||||
expect(extensions).toHaveLength(1);
|
||||
expect(extensions[0].name).toBe('good-ext');
|
||||
expect(consoleSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining(
|
||||
`Warning: Skipping extension in ${badExtDir}: Failed to load extension config from ${badConfigPath}`,
|
||||
),
|
||||
expect.stringContaining(`Warning: Skipping extension in ${badExtDir}:`),
|
||||
);
|
||||
|
||||
consoleSpy.mockRestore();
|
||||
@@ -717,7 +716,7 @@ name = "yolo-checker"
|
||||
expect(extensions[0].name).toBe('good-ext');
|
||||
expect(consoleSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining(
|
||||
`Warning: Skipping extension in ${badExtDir}: Failed to load extension config from ${badConfigPath}: Invalid configuration in ${badConfigPath}: missing "name"`,
|
||||
`Warning: Skipping extension in ${badExtDir}: Invalid gemini-extension.json:`,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1195,14 +1194,13 @@ name = "yolo-checker"
|
||||
it('should throw an error and cleanup if gemini-extension.json is missing', async () => {
|
||||
const sourceExtDir = getRealPath(path.join(tempHomeDir, 'bad-extension'));
|
||||
fs.mkdirSync(sourceExtDir, { recursive: true });
|
||||
const configPath = path.join(sourceExtDir, EXTENSIONS_CONFIG_FILENAME);
|
||||
|
||||
await expect(
|
||||
extensionManager.installOrUpdateExtension({
|
||||
source: sourceExtDir,
|
||||
type: 'local',
|
||||
}),
|
||||
).rejects.toThrow(`Configuration file not found at ${configPath}`);
|
||||
).rejects.toThrow(`Configuration file not found in ${sourceExtDir}`);
|
||||
|
||||
const targetExtDir = path.join(userExtensionsDir, 'bad-extension');
|
||||
expect(fs.existsSync(targetExtDir)).toBe(false);
|
||||
@@ -1219,7 +1217,7 @@ name = "yolo-checker"
|
||||
source: sourceExtDir,
|
||||
type: 'local',
|
||||
}),
|
||||
).rejects.toThrow(`Failed to load extension config from ${configPath}`);
|
||||
).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('should throw an error for missing name in gemini-extension.json', async () => {
|
||||
@@ -1239,9 +1237,7 @@ name = "yolo-checker"
|
||||
source: sourceExtDir,
|
||||
type: 'local',
|
||||
}),
|
||||
).rejects.toThrow(
|
||||
`Invalid configuration in ${configPath}: missing "name"`,
|
||||
);
|
||||
).rejects.toThrow('Invalid gemini-extension.json:');
|
||||
});
|
||||
|
||||
it('should install an extension from a git URL', async () => {
|
||||
@@ -2354,3 +2350,67 @@ function isEnabled(options: { name: string; enabledForPath: string }) {
|
||||
const manager = new ExtensionEnablementManager();
|
||||
return manager.isEnabled(options.name, options.enabledForPath);
|
||||
}
|
||||
|
||||
describe('extension.ts - Gemini CLI Extension Loading', () => {
|
||||
let geminiTempDir: string;
|
||||
let geminiManifestPath: string;
|
||||
|
||||
beforeEach(() => {
|
||||
geminiTempDir = fs.mkdtempSync(
|
||||
path.join(os.tmpdir(), 'gemini-extension-test-'),
|
||||
);
|
||||
geminiManifestPath = path.join(geminiTempDir, 'gemini-extension.json');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (fs.existsSync(geminiTempDir)) {
|
||||
fs.rmSync(geminiTempDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it('should load a valid gemini-extension.json and hydrate PLUGIN_ROOT', async () => {
|
||||
fs.writeFileSync(
|
||||
geminiManifestPath,
|
||||
JSON.stringify({
|
||||
name: 'test-extension',
|
||||
version: '1.0.0',
|
||||
description: 'Uses root: ${PLUGIN_ROOT}',
|
||||
}),
|
||||
);
|
||||
|
||||
const config = await loadGeminiConfig(
|
||||
geminiManifestPath,
|
||||
geminiTempDir,
|
||||
'/tmp/workspace',
|
||||
);
|
||||
|
||||
expect(config.name).toBe('test-extension');
|
||||
expect(config.version).toBe('1.0.0');
|
||||
expect(config.description).toBe(`Uses root: ${geminiTempDir}`);
|
||||
expect(config.manifestType).toBe('gemini');
|
||||
});
|
||||
|
||||
it('should create a GeminiCLIExtension with all fields', () => {
|
||||
const config = {
|
||||
name: 'test-ext',
|
||||
version: '2.0.0',
|
||||
description: 'A test',
|
||||
themes: [],
|
||||
};
|
||||
|
||||
const extension = createGeminiExtension(
|
||||
config,
|
||||
geminiTempDir,
|
||||
true,
|
||||
'ext-id',
|
||||
['GEMINI.md'],
|
||||
[],
|
||||
);
|
||||
|
||||
expect(extension.name).toBe('test-ext');
|
||||
expect(extension.version).toBe('2.0.0');
|
||||
expect(extension.isActive).toBe(true);
|
||||
expect(extension.manifestType).toBe('gemini');
|
||||
expect(extension.contextFiles).toEqual(['GEMINI.md']);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,15 +4,24 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type {
|
||||
MCPServerConfig,
|
||||
ExtensionInstallMetadata,
|
||||
CustomTheme,
|
||||
import {
|
||||
type MCPServerConfig,
|
||||
type ExtensionInstallMetadata,
|
||||
type CustomTheme,
|
||||
type PolicyRule,
|
||||
type SafetyCheckerRule,
|
||||
type GeminiCLIExtension,
|
||||
type ResolvedExtensionSetting,
|
||||
} from '@google/gemini-cli-core';
|
||||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import { INSTALL_METADATA_FILENAME } from './extensions/variables.js';
|
||||
import { z } from 'zod';
|
||||
import type { ExtensionSetting } from './extensions/extensionSettings.js';
|
||||
import {
|
||||
INSTALL_METADATA_FILENAME,
|
||||
recursivelyHydrateStrings,
|
||||
type JsonObject,
|
||||
} from './extensions/variables.js';
|
||||
|
||||
/**
|
||||
* Extension definition as written to disk in gemini-extension.json files.
|
||||
@@ -21,32 +30,53 @@ import type { ExtensionSetting } from './extensions/extensionSettings.js';
|
||||
* outside of the loading process that data needs to be stored on the
|
||||
* GeminiCLIExtension class defined in Core.
|
||||
*/
|
||||
export interface ExtensionConfig {
|
||||
name: string;
|
||||
version: string;
|
||||
mcpServers?: Record<string, MCPServerConfig>;
|
||||
contextFileName?: string | string[];
|
||||
excludeTools?: string[];
|
||||
settings?: ExtensionSetting[];
|
||||
export const geminiExtensionSchema = z.object({
|
||||
name: z.string().trim().min(1),
|
||||
version: z.string().trim().min(1),
|
||||
description: z.string().optional(),
|
||||
author: z
|
||||
.union([
|
||||
z.string(),
|
||||
z.object({
|
||||
name: z.string(),
|
||||
email: z.string().optional(),
|
||||
url: z.string().optional(),
|
||||
}),
|
||||
])
|
||||
.optional(),
|
||||
license: z.string().optional(),
|
||||
mcpServers: z.record(z.custom<MCPServerConfig>()).optional(),
|
||||
contextFileName: z.union([z.string(), z.array(z.string())]).optional(),
|
||||
excludeTools: z.array(z.string()).optional(),
|
||||
settings: z.array(z.custom<ExtensionSetting>()).optional(),
|
||||
/**
|
||||
* Custom themes contributed by this extension.
|
||||
* These themes will be registered when the extension is activated.
|
||||
*/
|
||||
themes?: CustomTheme[];
|
||||
themes: z.array(z.custom<CustomTheme>()).optional(),
|
||||
/**
|
||||
* Planning features configuration contributed by this extension.
|
||||
*/
|
||||
plan?: {
|
||||
/**
|
||||
* The directory where planning artifacts are stored.
|
||||
*/
|
||||
directory?: string;
|
||||
};
|
||||
plan: z
|
||||
.object({
|
||||
directory: z.string().optional(),
|
||||
})
|
||||
.optional(),
|
||||
/**
|
||||
* Used to migrate an extension to a new repository source.
|
||||
*/
|
||||
migratedTo?: string;
|
||||
}
|
||||
migratedTo: z.string().optional(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Internal representation of an extension configuration after being loaded and validated.
|
||||
*/
|
||||
export type ExtensionConfig = z.infer<typeof geminiExtensionSchema> & {
|
||||
manifestType?: 'gemini' | 'open-plugin';
|
||||
keywords?: string[];
|
||||
homepage?: string;
|
||||
repository?: string;
|
||||
};
|
||||
|
||||
export interface ExtensionUpdateInfo {
|
||||
name: string;
|
||||
@@ -67,3 +97,83 @@ export function loadInstallMetadata(
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a Gemini CLI extension manifest.
|
||||
*/
|
||||
export async function loadGeminiConfig(
|
||||
manifestPath: string,
|
||||
extensionDir: string,
|
||||
workspaceDir: string,
|
||||
): Promise<ExtensionConfig> {
|
||||
const content = await fs.promises.readFile(manifestPath, 'utf-8');
|
||||
const json = JSON.parse(content) as unknown;
|
||||
const result = geminiExtensionSchema.safeParse(json);
|
||||
if (!result.success) {
|
||||
throw new Error(`Invalid gemini-extension.json: ${result.error.message}`);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
const rawConfig = result.data as unknown as ExtensionConfig;
|
||||
|
||||
// Hydrate strings with basic context
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
const config = recursivelyHydrateStrings(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
rawConfig as unknown as JsonObject,
|
||||
{
|
||||
extensionPath: extensionDir,
|
||||
PLUGIN_ROOT: extensionDir,
|
||||
workspacePath: workspaceDir,
|
||||
'/': path.sep,
|
||||
pathSeparator: path.sep,
|
||||
},
|
||||
) as unknown as ExtensionConfig;
|
||||
|
||||
config.manifestType = 'gemini';
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory for creating a GeminiCLIExtension from a Gemini config.
|
||||
*/
|
||||
export function createGeminiExtension(
|
||||
config: ExtensionConfig,
|
||||
extensionDir: string,
|
||||
isActive: boolean,
|
||||
id: string,
|
||||
contextFiles: string[],
|
||||
resolvedSettings: ResolvedExtensionSetting[],
|
||||
installMetadata?: ExtensionInstallMetadata,
|
||||
hooks?: GeminiCLIExtension['hooks'],
|
||||
skills?: GeminiCLIExtension['skills'],
|
||||
agents?: GeminiCLIExtension['agents'],
|
||||
rules?: PolicyRule[],
|
||||
checkers?: SafetyCheckerRule[],
|
||||
): GeminiCLIExtension {
|
||||
return {
|
||||
name: config.name,
|
||||
version: config.version,
|
||||
path: extensionDir,
|
||||
isActive,
|
||||
id,
|
||||
installMetadata,
|
||||
manifestType: 'gemini',
|
||||
description: config.description,
|
||||
author: config.author,
|
||||
license: config.license,
|
||||
contextFiles,
|
||||
mcpServers: config.mcpServers,
|
||||
excludeTools: config.excludeTools,
|
||||
settings: config.settings,
|
||||
resolvedSettings,
|
||||
hooks,
|
||||
skills,
|
||||
agents,
|
||||
themes: config.themes,
|
||||
rules,
|
||||
checkers,
|
||||
plan: config.plan,
|
||||
migratedTo: config.migratedTo,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -25,6 +25,10 @@ export const VARIABLE_SCHEMA = {
|
||||
type: 'string',
|
||||
description: 'The path of the extension in the filesystem.',
|
||||
},
|
||||
PLUGIN_ROOT: {
|
||||
type: 'string',
|
||||
description: 'The root path of the plugin (alias for extensionPath).',
|
||||
},
|
||||
workspacePath: {
|
||||
type: 'string',
|
||||
description: 'The absolute path of the current workspace.',
|
||||
|
||||
@@ -20,6 +20,10 @@ const UNMARSHALL_KEY_IGNORE_LIST: Set<string> = new Set<string>([
|
||||
|
||||
export const EXTENSIONS_DIRECTORY_NAME = path.join(GEMINI_DIR, 'extensions');
|
||||
export const EXTENSIONS_CONFIG_FILENAME = 'gemini-extension.json';
|
||||
export const STANDARD_OPEN_PLUGIN_CONFIG_FILENAME = path.join(
|
||||
'.plugin',
|
||||
'plugin.json',
|
||||
);
|
||||
export const INSTALL_METADATA_FILENAME = '.gemini-extension-install.json';
|
||||
export const EXTENSION_SETTINGS_FILENAME = '.env';
|
||||
|
||||
|
||||
@@ -0,0 +1,260 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
import * as fs from 'node:fs';
|
||||
import * as os from 'node:os';
|
||||
import * as path from 'node:path';
|
||||
import { ExtensionManager } from './extension-manager.js';
|
||||
import { createTestMergedSettings } from './settings.js';
|
||||
import { EXTENSIONS_DIRECTORY_NAME } from './extensions/variables.js';
|
||||
|
||||
const mockHomedir = vi.hoisted(() => vi.fn(() => '/tmp/mock-home'));
|
||||
const mockIntegrityManager = vi.hoisted(() => ({
|
||||
verify: vi.fn().mockResolvedValue('verified'),
|
||||
store: vi.fn().mockResolvedValue(undefined),
|
||||
}));
|
||||
|
||||
vi.mock('os', async (importOriginal) => {
|
||||
const mockedOs = await importOriginal<typeof os>();
|
||||
return {
|
||||
...mockedOs,
|
||||
homedir: mockHomedir,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
||||
const actual =
|
||||
await importOriginal<typeof import('@google/gemini-cli-core')>();
|
||||
return {
|
||||
...actual,
|
||||
homedir: mockHomedir,
|
||||
ExtensionIntegrityManager: vi
|
||||
.fn()
|
||||
.mockImplementation(() => mockIntegrityManager),
|
||||
};
|
||||
});
|
||||
|
||||
describe('ExtensionManager - Open Plugin Support', () => {
|
||||
let tempHomeDir: string;
|
||||
let tempWorkspaceDir: string;
|
||||
let userExtensionsDir: string;
|
||||
let extensionManager: ExtensionManager;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
tempHomeDir = fs.mkdtempSync(
|
||||
path.join(os.tmpdir(), 'gemini-cli-test-home-'),
|
||||
);
|
||||
tempWorkspaceDir = fs.mkdtempSync(
|
||||
path.join(tempHomeDir, 'gemini-cli-test-workspace-'),
|
||||
);
|
||||
mockHomedir.mockReturnValue(tempHomeDir);
|
||||
userExtensionsDir = path.join(tempHomeDir, EXTENSIONS_DIRECTORY_NAME);
|
||||
fs.mkdirSync(userExtensionsDir, { recursive: true });
|
||||
|
||||
extensionManager = new ExtensionManager({
|
||||
settings: createTestMergedSettings(),
|
||||
workspaceDir: tempWorkspaceDir,
|
||||
requestConsent: vi.fn().mockResolvedValue(true),
|
||||
requestSetting: null,
|
||||
integrityManager: mockIntegrityManager,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (fs.existsSync(tempHomeDir)) {
|
||||
fs.rmSync(tempHomeDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it('should NOT discover a plugin with plugin.json at root', async () => {
|
||||
const pluginDir = path.join(userExtensionsDir, 'test-plugin');
|
||||
fs.mkdirSync(pluginDir, { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(pluginDir, 'plugin.json'),
|
||||
JSON.stringify({
|
||||
name: 'hello-world',
|
||||
version: '1.0.0',
|
||||
}),
|
||||
);
|
||||
|
||||
const extensions = await extensionManager.loadExtensions();
|
||||
const plugin = extensions.find((ext) => ext.name === 'hello-world');
|
||||
|
||||
expect(plugin).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should discover a plugin with .plugin/plugin.json', async () => {
|
||||
const pluginDir = path.join(userExtensionsDir, 'hidden-plugin-dir');
|
||||
const hiddenDir = path.join(pluginDir, '.plugin');
|
||||
fs.mkdirSync(hiddenDir, { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(hiddenDir, 'plugin.json'),
|
||||
JSON.stringify({
|
||||
name: 'hidden-plugin',
|
||||
version: '2.0.0',
|
||||
}),
|
||||
);
|
||||
|
||||
const extensions = await extensionManager.loadExtensions();
|
||||
const plugin = extensions.find((ext) => ext.name === 'hidden-plugin');
|
||||
|
||||
expect(plugin).toBeDefined();
|
||||
expect(plugin?.version).toBe('2.0.0');
|
||||
expect(plugin?.manifestType).toBe('open-plugin');
|
||||
});
|
||||
|
||||
it('should support PLUGIN_ROOT variable alias in metadata', async () => {
|
||||
const pluginDir = path.join(userExtensionsDir, 'var-plugin');
|
||||
const hiddenDir = path.join(pluginDir, '.plugin');
|
||||
fs.mkdirSync(hiddenDir, { recursive: true });
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(hiddenDir, 'plugin.json'),
|
||||
JSON.stringify({
|
||||
name: 'var-plugin',
|
||||
version: '1.0.0',
|
||||
description: 'Uses root: ${PLUGIN_ROOT}',
|
||||
}),
|
||||
);
|
||||
|
||||
const extensions = await extensionManager.loadExtensions();
|
||||
const plugin = extensions.find((ext) => ext.name === 'var-plugin');
|
||||
|
||||
expect(plugin).toBeDefined();
|
||||
expect(plugin?.description).toBe(`Uses root: ${pluginDir}`);
|
||||
});
|
||||
|
||||
it('should NOT load skills or context files for Open Plugins in v1', async () => {
|
||||
const pluginDir = path.join(userExtensionsDir, 'feature-plugin');
|
||||
const hiddenDir = path.join(pluginDir, '.plugin');
|
||||
fs.mkdirSync(hiddenDir, { recursive: true });
|
||||
const skillsDir = path.join(pluginDir, 'skills', 'test');
|
||||
fs.mkdirSync(skillsDir, { recursive: true });
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(hiddenDir, 'plugin.json'),
|
||||
JSON.stringify({
|
||||
name: 'feature-plugin',
|
||||
version: '1.0.0',
|
||||
}),
|
||||
);
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(skillsDir, 'SKILL.md'),
|
||||
`---
|
||||
name: test-skill
|
||||
description: "Test"
|
||||
---
|
||||
Body`,
|
||||
);
|
||||
|
||||
fs.writeFileSync(path.join(pluginDir, 'GEMINI.md'), '# Context');
|
||||
|
||||
const extensions = await extensionManager.loadExtensions();
|
||||
const plugin = extensions.find((ext) => ext.name === 'feature-plugin');
|
||||
|
||||
expect(plugin).toBeDefined();
|
||||
expect(plugin?.skills).toBeUndefined();
|
||||
expect(plugin?.contextFiles).toEqual([]);
|
||||
});
|
||||
|
||||
it('should prioritize gemini-extension.json over plugin.json', async () => {
|
||||
const pluginDir = path.join(userExtensionsDir, 'dual-manifest-plugin');
|
||||
const hiddenDir = path.join(pluginDir, '.plugin');
|
||||
fs.mkdirSync(hiddenDir, { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(pluginDir, 'gemini-extension.json'),
|
||||
JSON.stringify({
|
||||
name: 'gemini-plugin',
|
||||
version: '1.1.1',
|
||||
}),
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(hiddenDir, 'plugin.json'),
|
||||
JSON.stringify({
|
||||
name: 'open-plugin',
|
||||
version: '2.2.2',
|
||||
}),
|
||||
);
|
||||
|
||||
const extensions = await extensionManager.loadExtensions();
|
||||
const plugin = extensions.find((ext) => ext.name === 'gemini-plugin');
|
||||
|
||||
expect(plugin).toBeDefined();
|
||||
expect(plugin?.version).toBe('1.1.1');
|
||||
expect(plugin?.manifestType).toBe('gemini');
|
||||
|
||||
const openPlugin = extensions.find((ext) => ext.name === 'open-plugin');
|
||||
expect(openPlugin).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should support new metadata and discovery fields with path validation', async () => {
|
||||
const pluginDir = path.join(userExtensionsDir, 'new-spec-plugin');
|
||||
const hiddenDir = path.join(pluginDir, '.plugin');
|
||||
fs.mkdirSync(hiddenDir, { recursive: true });
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(hiddenDir, 'plugin.json'),
|
||||
JSON.stringify({
|
||||
name: 'new-spec-plugin',
|
||||
version: '1.0.0',
|
||||
keywords: ['test', 'plugin'],
|
||||
homepage: 'https://example.com',
|
||||
repository: 'https://github.com/example/plugin',
|
||||
skills: './skills',
|
||||
}),
|
||||
);
|
||||
|
||||
const extensions = await extensionManager.loadExtensions();
|
||||
const plugin = extensions.find((ext) => ext.name === 'new-spec-plugin');
|
||||
|
||||
expect(plugin).toBeDefined();
|
||||
expect(plugin?.keywords).toEqual(['test', 'plugin']);
|
||||
expect(plugin?.homepage).toBe('https://example.com');
|
||||
expect(plugin?.repository).toBe('https://github.com/example/plugin');
|
||||
expect(plugin?.manifestType).toBe('open-plugin');
|
||||
});
|
||||
|
||||
it('should fail validation if discovery path does not start with ./', async () => {
|
||||
const pluginDir = path.join(userExtensionsDir, 'invalid-path-plugin');
|
||||
const hiddenDir = path.join(pluginDir, '.plugin');
|
||||
fs.mkdirSync(hiddenDir, { recursive: true });
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(hiddenDir, 'plugin.json'),
|
||||
JSON.stringify({
|
||||
name: 'invalid-path-plugin',
|
||||
version: '1.0.0',
|
||||
skills: 'skills', // Missing ./
|
||||
}),
|
||||
);
|
||||
|
||||
await expect(
|
||||
extensionManager.loadExtensionConfig(pluginDir),
|
||||
).rejects.toThrow('Invalid plugin.json');
|
||||
});
|
||||
|
||||
it('should fail validation if discovery path contains ../', async () => {
|
||||
const pluginDir = path.join(userExtensionsDir, 'traversal-plugin');
|
||||
const hiddenDir = path.join(pluginDir, '.plugin');
|
||||
fs.mkdirSync(hiddenDir, { recursive: true });
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(hiddenDir, 'plugin.json'),
|
||||
JSON.stringify({
|
||||
name: 'traversal-plugin',
|
||||
version: '1.0.0',
|
||||
skills: './../outside', // Contains ../
|
||||
}),
|
||||
);
|
||||
|
||||
await expect(
|
||||
extensionManager.loadExtensionConfig(pluginDir),
|
||||
).rejects.toThrow('Invalid plugin.json');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,200 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import { z } from 'zod';
|
||||
import type {
|
||||
ExtensionInstallMetadata,
|
||||
GeminiCLIExtension,
|
||||
} from '@google/gemini-cli-core';
|
||||
import {
|
||||
EXTENSIONS_CONFIG_FILENAME,
|
||||
STANDARD_OPEN_PLUGIN_CONFIG_FILENAME,
|
||||
recursivelyHydrateStrings,
|
||||
type JsonObject,
|
||||
} from './extensions/variables.js';
|
||||
import type { ExtensionConfig } from './extension.js';
|
||||
|
||||
/**
|
||||
* Open Plugin manifest (plugin.json) v1.0.0
|
||||
* Based on https://open-plugins.com/plugin-builders/specification
|
||||
*/
|
||||
export interface OpenPluginConfig {
|
||||
name: string;
|
||||
version?: string;
|
||||
description?: string;
|
||||
author?: string | { name: string; email?: string; url?: string };
|
||||
license?: string;
|
||||
keywords?: string[];
|
||||
homepage?: string;
|
||||
repository?: string;
|
||||
// Component fields (parsed but currently ignored during execution per v1 plan)
|
||||
skills?: OpenPluginDiscoveryField;
|
||||
mcpServers?: OpenPluginDiscoveryField | Record<string, unknown>;
|
||||
}
|
||||
|
||||
export type OpenPluginDiscoveryField = string | string[] | { paths: string[] };
|
||||
|
||||
export const OPEN_PLUGIN_NAME_REGEX = /^[a-z0-9.-]{1,64}$/;
|
||||
|
||||
/**
|
||||
* Validates that a discovery path starts with ./ and does not contain ../
|
||||
*/
|
||||
const isValidDiscoveryPath = (p: string) =>
|
||||
p.startsWith('./') && !p.includes('../');
|
||||
|
||||
const discoveryFieldSchema = z.union([
|
||||
z.string().refine(isValidDiscoveryPath, {
|
||||
message: 'Path must start with "./" and cannot contain "../"',
|
||||
}),
|
||||
z.array(
|
||||
z.string().refine(isValidDiscoveryPath, {
|
||||
message: 'Path must start with "./" and cannot contain "../"',
|
||||
}),
|
||||
),
|
||||
z.object({
|
||||
paths: z.array(
|
||||
z.string().refine(isValidDiscoveryPath, {
|
||||
message: 'Path must start with "./" and cannot contain "../"',
|
||||
}),
|
||||
),
|
||||
}),
|
||||
]);
|
||||
|
||||
export const openPluginSchema = z.object({
|
||||
name: z.string().trim().min(1).max(64).regex(OPEN_PLUGIN_NAME_REGEX),
|
||||
version: z.string().trim().optional(),
|
||||
description: z.string().optional(),
|
||||
author: z
|
||||
.union([
|
||||
z.string(),
|
||||
z.object({
|
||||
name: z.string(),
|
||||
email: z.string().optional(),
|
||||
url: z.string().optional(),
|
||||
}),
|
||||
])
|
||||
.optional(),
|
||||
license: z.string().optional(),
|
||||
keywords: z.array(z.string()).optional(),
|
||||
homepage: z.string().url().optional(),
|
||||
repository: z.string().url().optional(),
|
||||
skills: discoveryFieldSchema.optional(),
|
||||
mcpServers: z.union([discoveryFieldSchema, z.record(z.any())]).optional(),
|
||||
});
|
||||
|
||||
export interface ManifestInfo {
|
||||
type: 'gemini' | 'open-plugin';
|
||||
path: string;
|
||||
}
|
||||
|
||||
export function findManifest(extensionDir: string): ManifestInfo | undefined {
|
||||
const geminiPath = path.join(extensionDir, EXTENSIONS_CONFIG_FILENAME);
|
||||
if (fs.existsSync(geminiPath)) {
|
||||
return { type: 'gemini', path: geminiPath };
|
||||
}
|
||||
|
||||
const standardOpenPluginPath = path.join(
|
||||
extensionDir,
|
||||
STANDARD_OPEN_PLUGIN_CONFIG_FILENAME,
|
||||
);
|
||||
if (fs.existsSync(standardOpenPluginPath)) {
|
||||
return { type: 'open-plugin', path: standardOpenPluginPath };
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads an Open Plugin manifest and maps it to ExtensionConfig.
|
||||
*/
|
||||
export async function loadOpenPluginConfig(
|
||||
manifestPath: string,
|
||||
extensionDir: string,
|
||||
workspaceDir: string,
|
||||
): Promise<ExtensionConfig> {
|
||||
const content = await fs.promises.readFile(manifestPath, 'utf-8');
|
||||
const json = JSON.parse(content) as unknown;
|
||||
const result = openPluginSchema.safeParse(json);
|
||||
if (!result.success) {
|
||||
throw new Error(`Invalid plugin.json: ${result.error.message}`);
|
||||
}
|
||||
|
||||
const rawConfig = result.data as OpenPluginConfig;
|
||||
|
||||
// Hydrate metadata fields
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
const hydratedConfig = recursivelyHydrateStrings(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
||||
rawConfig as unknown as JsonObject,
|
||||
{
|
||||
extensionPath: extensionDir,
|
||||
PLUGIN_ROOT: extensionDir,
|
||||
workspacePath: workspaceDir,
|
||||
'/': path.sep,
|
||||
pathSeparator: path.sep,
|
||||
},
|
||||
) as unknown as OpenPluginConfig;
|
||||
|
||||
return {
|
||||
name: hydratedConfig.name,
|
||||
version: hydratedConfig.version ?? '0.0.0',
|
||||
manifestType: 'open-plugin',
|
||||
description: hydratedConfig.description,
|
||||
author: hydratedConfig.author,
|
||||
license: hydratedConfig.license,
|
||||
keywords: hydratedConfig.keywords,
|
||||
homepage: hydratedConfig.homepage,
|
||||
repository: hydratedConfig.repository,
|
||||
// Features are explicitly NOT mapped here for v1 plugins
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a GeminiCLIExtension from an Open Plugin directory.
|
||||
* v1: Does not enable skills, mcp servers, context files, or settings.
|
||||
*/
|
||||
export async function createOpenPlugin(
|
||||
pluginDir: string,
|
||||
manifestPath: string,
|
||||
isActive: boolean,
|
||||
id: string,
|
||||
workspaceDir: string,
|
||||
installMetadata?: ExtensionInstallMetadata,
|
||||
): Promise<GeminiCLIExtension> {
|
||||
// Use loadOpenPluginConfig to get standard mapping
|
||||
const config = await loadOpenPluginConfig(
|
||||
manifestPath,
|
||||
pluginDir,
|
||||
workspaceDir,
|
||||
);
|
||||
|
||||
return {
|
||||
name: config.name,
|
||||
version: config.version,
|
||||
path: pluginDir,
|
||||
isActive,
|
||||
id,
|
||||
installMetadata,
|
||||
manifestType: 'open-plugin',
|
||||
description: config.description,
|
||||
author: config.author,
|
||||
license: config.license,
|
||||
keywords: config.keywords,
|
||||
homepage: config.homepage,
|
||||
repository: config.repository,
|
||||
// v1: Features disabled
|
||||
contextFiles: [],
|
||||
mcpServers: undefined,
|
||||
excludeTools: undefined,
|
||||
settings: undefined,
|
||||
resolvedSettings: undefined,
|
||||
skills: undefined,
|
||||
agents: undefined,
|
||||
themes: undefined,
|
||||
};
|
||||
}
|
||||
@@ -520,8 +520,8 @@ describe('Policy Engine Integration Tests', () => {
|
||||
const readOnlyToolRule = rules.find(
|
||||
(r) => r.toolName === 'glob' && !r.subagent,
|
||||
);
|
||||
// Priority 50 in default tier → 1.05 (Overriding Plan Mode Deny)
|
||||
expect(readOnlyToolRule?.priority).toBeCloseTo(1.05, 5);
|
||||
// Priority 70 in default tier → 1.07 (Overriding Plan Mode Deny)
|
||||
expect(readOnlyToolRule?.priority).toBeCloseTo(1.07, 5);
|
||||
|
||||
// Verify the engine applies these priorities correctly
|
||||
expect(
|
||||
@@ -677,8 +677,8 @@ describe('Policy Engine Integration Tests', () => {
|
||||
expect(server1Rule?.priority).toBe(4.1); // Allowed servers (user tier)
|
||||
|
||||
const globRule = rules.find((r) => r.toolName === 'glob' && !r.subagent);
|
||||
// Priority 50 in default tier → 1.05
|
||||
expect(globRule?.priority).toBeCloseTo(1.05, 5); // Auto-accept read-only
|
||||
// Priority 70 in default tier → 1.07
|
||||
expect(globRule?.priority).toBeCloseTo(1.07, 5); // Auto-accept read-only
|
||||
|
||||
// The PolicyEngine will sort these by priority when it's created
|
||||
const engine = new PolicyEngine(config);
|
||||
|
||||
@@ -757,7 +757,7 @@ const SETTINGS_SCHEMA = {
|
||||
label: 'Terminal Buffer',
|
||||
category: 'UI',
|
||||
requiresRestart: true,
|
||||
default: false,
|
||||
default: true,
|
||||
description: 'Use the new terminal buffer architecture for rendering.',
|
||||
showInDialog: true,
|
||||
},
|
||||
@@ -1711,10 +1711,10 @@ const SETTINGS_SCHEMA = {
|
||||
type: 'boolean',
|
||||
label: 'Tool Sandboxing',
|
||||
category: 'Security',
|
||||
requiresRestart: true,
|
||||
requiresRestart: false,
|
||||
default: false,
|
||||
description:
|
||||
'Tool-level sandboxing. Isolates individual tools instead of the entire CLI process.',
|
||||
'Experimental tool-level sandboxing (implementation in progress).',
|
||||
showInDialog: true,
|
||||
},
|
||||
disableYoloMode: {
|
||||
@@ -1970,16 +1970,6 @@ const SETTINGS_SCHEMA = {
|
||||
description: 'Enable non-interactive agent sessions.',
|
||||
showInDialog: false,
|
||||
},
|
||||
agentSessionInteractiveEnabled: {
|
||||
type: 'boolean',
|
||||
label: 'Interactive Agent Session Enabled',
|
||||
category: 'Experimental',
|
||||
requiresRestart: true,
|
||||
default: false,
|
||||
description:
|
||||
'Enable the agent session implementation for the interactive CLI.',
|
||||
showInDialog: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
enableAgents: {
|
||||
|
||||
@@ -379,30 +379,15 @@ describe('initializeOutputListenersAndFlush', () => {
|
||||
describe('getNodeMemoryArgs', () => {
|
||||
let osTotalMemSpy: MockInstance;
|
||||
let v8GetHeapStatisticsSpy: MockInstance;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
let originalConfig: any;
|
||||
|
||||
beforeEach(() => {
|
||||
osTotalMemSpy = vi.spyOn(os, 'totalmem');
|
||||
v8GetHeapStatisticsSpy = vi.spyOn(v8, 'getHeapStatistics');
|
||||
delete process.env['GEMINI_CLI_NO_RELAUNCH'];
|
||||
|
||||
originalConfig = process.config;
|
||||
Object.defineProperty(process, 'config', {
|
||||
value: {
|
||||
...originalConfig,
|
||||
variables: { ...originalConfig?.variables, v8_enable_sandbox: 1 },
|
||||
},
|
||||
configurable: true,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
Object.defineProperty(process, 'config', {
|
||||
value: originalConfig,
|
||||
configurable: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should return empty array if GEMINI_CLI_NO_RELAUNCH is set', () => {
|
||||
@@ -415,10 +400,8 @@ describe('getNodeMemoryArgs', () => {
|
||||
v8GetHeapStatisticsSpy.mockReturnValue({
|
||||
heap_size_limit: 8 * 1024 * 1024 * 1024, // 8GB
|
||||
});
|
||||
// Target is 50% of 16GB = 8GB. Current is 8GB. Relaunch needed for EPT size only.
|
||||
expect(getNodeMemoryArgs(false)).toEqual([
|
||||
'--max-external-pointer-table-size=268435456',
|
||||
]);
|
||||
// Target is 50% of 16GB = 8GB. Current is 8GB. No relaunch needed.
|
||||
expect(getNodeMemoryArgs(false)).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return memory args if current heap limit is insufficient', () => {
|
||||
@@ -426,11 +409,8 @@ describe('getNodeMemoryArgs', () => {
|
||||
v8GetHeapStatisticsSpy.mockReturnValue({
|
||||
heap_size_limit: 4 * 1024 * 1024 * 1024, // 4GB
|
||||
});
|
||||
// Target is 50% of 16GB = 8GB. Current is 4GB. Relaunch needed for both.
|
||||
expect(getNodeMemoryArgs(false)).toEqual([
|
||||
'--max-external-pointer-table-size=268435456',
|
||||
'--max-old-space-size=8192',
|
||||
]);
|
||||
// Target is 50% of 16GB = 8GB. Current is 4GB. Relaunch needed.
|
||||
expect(getNodeMemoryArgs(false)).toEqual(['--max-old-space-size=8192']);
|
||||
});
|
||||
|
||||
it('should log debug info when isDebugMode is true', () => {
|
||||
|
||||
@@ -111,8 +111,6 @@ export function validateDnsResolutionOrder(
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
const DEFAULT_EPT_SIZE = (256 * 1024 * 1024).toString();
|
||||
|
||||
export function getNodeMemoryArgs(isDebugMode: boolean): string[] {
|
||||
const totalMemoryMB = os.totalmem() / (1024 * 1024);
|
||||
const heapStats = v8.getHeapStatistics();
|
||||
@@ -132,35 +130,16 @@ export function getNodeMemoryArgs(isDebugMode: boolean): string[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
const args: string[] = [];
|
||||
|
||||
// Automatically expand the V8 External Pointer Table to 256MB to prevent
|
||||
// out-of-memory crashes during high native-handle concurrency.
|
||||
// Note: Only supported in specific Node.js versions compiled with V8 Sandbox enabled.
|
||||
const eptFlag = `--max-external-pointer-table-size=${DEFAULT_EPT_SIZE}`;
|
||||
const isV8SandboxEnabled =
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-type-assertion
|
||||
(process.config?.variables as any)?.v8_enable_sandbox === 1;
|
||||
|
||||
if (
|
||||
isV8SandboxEnabled &&
|
||||
!process.execArgv.some((arg) =>
|
||||
arg.startsWith('--max-external-pointer-table-size'),
|
||||
)
|
||||
) {
|
||||
args.push(eptFlag);
|
||||
}
|
||||
|
||||
if (targetMaxOldSpaceSizeInMB > currentMaxOldSpaceSizeMb) {
|
||||
if (isDebugMode) {
|
||||
debugLogger.debug(
|
||||
`Need to relaunch with more memory: ${targetMaxOldSpaceSizeInMB.toFixed(2)} MB`,
|
||||
);
|
||||
}
|
||||
args.push(`--max-old-space-size=${targetMaxOldSpaceSizeInMB}`);
|
||||
return [`--max-old-space-size=${targetMaxOldSpaceSizeInMB}`];
|
||||
}
|
||||
|
||||
return args;
|
||||
return [];
|
||||
}
|
||||
|
||||
export function setupUnhandledRejectionHandler() {
|
||||
|
||||
@@ -156,9 +156,8 @@ export async function startInteractiveUI(
|
||||
useAlternateBuffer || config.getUseTerminalBuffer(),
|
||||
patchConsole: false,
|
||||
alternateBuffer: useAlternateBuffer,
|
||||
renderProcess: config.getUseRenderProcess(),
|
||||
terminalBuffer: config.getUseTerminalBuffer(),
|
||||
renderProcess:
|
||||
config.getUseRenderProcess() && config.getUseTerminalBuffer(),
|
||||
incrementalRendering:
|
||||
settings.merged.ui.incrementalRendering !== false &&
|
||||
useAlternateBuffer &&
|
||||
|
||||
@@ -71,7 +71,6 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
||||
Scheduler: class {
|
||||
schedule = mockSchedulerSchedule;
|
||||
cancelAll = vi.fn();
|
||||
dispose = vi.fn();
|
||||
},
|
||||
isTelemetrySdkInitialized: vi.fn().mockReturnValue(true),
|
||||
ChatRecordingService: MockChatRecordingService,
|
||||
|
||||
@@ -187,7 +187,6 @@ export async function runNonInteractive(
|
||||
};
|
||||
|
||||
let errorToHandle: unknown | undefined;
|
||||
let scheduler: Scheduler | undefined;
|
||||
try {
|
||||
consolePatcher.patch();
|
||||
|
||||
@@ -216,7 +215,7 @@ export async function runNonInteractive(
|
||||
});
|
||||
|
||||
const geminiClient = config.getGeminiClient();
|
||||
scheduler = new Scheduler({
|
||||
const scheduler = new Scheduler({
|
||||
context: config,
|
||||
messageBus: config.getMessageBus(),
|
||||
getPreferredEditor: () => undefined,
|
||||
@@ -529,7 +528,6 @@ export async function runNonInteractive(
|
||||
// Cleanup stdin cancellation before other cleanup
|
||||
cleanupStdinCancellation();
|
||||
|
||||
scheduler?.dispose();
|
||||
consolePatcher.cleanup();
|
||||
coreEvents.off(CoreEvent.UserFeedback, handleUserFeedback);
|
||||
}
|
||||
|
||||
@@ -71,7 +71,6 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
||||
Scheduler: class {
|
||||
schedule = mockSchedulerSchedule;
|
||||
cancelAll = vi.fn();
|
||||
dispose = vi.fn();
|
||||
},
|
||||
isTelemetrySdkInitialized: vi.fn().mockReturnValue(true),
|
||||
ChatRecordingService: MockChatRecordingService,
|
||||
|
||||
@@ -37,7 +37,6 @@ import {
|
||||
LegacyAgentSession,
|
||||
ToolErrorType,
|
||||
geminiPartsToContentParts,
|
||||
debugLogger,
|
||||
} from '@google/gemini-cli-core';
|
||||
|
||||
import type { Part } from '@google/genai';
|
||||
@@ -184,7 +183,6 @@ export async function runNonInteractive({
|
||||
};
|
||||
|
||||
let errorToHandle: unknown | undefined;
|
||||
let scheduler: Scheduler | undefined;
|
||||
let abortSession = () => {};
|
||||
try {
|
||||
consolePatcher.patch();
|
||||
@@ -216,7 +214,7 @@ export async function runNonInteractive({
|
||||
});
|
||||
|
||||
const geminiClient = config.getGeminiClient();
|
||||
scheduler = new Scheduler({
|
||||
const scheduler = new Scheduler({
|
||||
context: config,
|
||||
messageBus: config.getMessageBus(),
|
||||
getPreferredEditor: () => undefined,
|
||||
@@ -601,7 +599,6 @@ export async function runNonInteractive({
|
||||
// Explicitly ignore these non-interactive events
|
||||
break;
|
||||
default:
|
||||
debugLogger.error('Unknown agent event type:', event);
|
||||
event satisfies never;
|
||||
break;
|
||||
}
|
||||
@@ -613,7 +610,6 @@ export async function runNonInteractive({
|
||||
cleanupStdinCancellation();
|
||||
abortController.signal.removeEventListener('abort', abortSession);
|
||||
|
||||
scheduler?.dispose();
|
||||
consolePatcher.cleanup();
|
||||
coreEvents.off(CoreEvent.UserFeedback, handleUserFeedback);
|
||||
}
|
||||
|
||||
@@ -136,7 +136,6 @@ export const createMockConfig = (overrides: Partial<Config> = {}): Config =>
|
||||
getRetryFetchErrors: vi.fn().mockReturnValue(true),
|
||||
getEnableShellOutputEfficiency: vi.fn().mockReturnValue(true),
|
||||
getShellToolInactivityTimeout: vi.fn().mockReturnValue(300000),
|
||||
getRequestTimeoutMs: vi.fn().mockReturnValue(undefined),
|
||||
getShellExecutionConfig: vi.fn().mockReturnValue({
|
||||
sandboxManager: new NoopSandboxManager(),
|
||||
sanitizationConfig: {
|
||||
|
||||
@@ -504,8 +504,6 @@ const baseMockUiState = {
|
||||
history: [],
|
||||
renderMarkdown: true,
|
||||
streamingState: StreamingState.Idle,
|
||||
isConfigInitialized: true,
|
||||
isAuthenticating: false,
|
||||
terminalWidth: 100,
|
||||
terminalHeight: 40,
|
||||
currentModel: 'gemini-pro',
|
||||
@@ -600,9 +598,6 @@ const mockUIActions: UIActions = {
|
||||
clearAccountSuspension: vi.fn(),
|
||||
};
|
||||
|
||||
import { type TextBuffer } from '../ui/components/shared/text-buffer.js';
|
||||
import { InputContext, type InputState } from '../ui/contexts/InputContext.js';
|
||||
|
||||
let capturedOverflowState: OverflowState | undefined;
|
||||
let capturedOverflowActions: OverflowActions | undefined;
|
||||
const ContextCapture: React.FC<{ children: React.ReactNode }> = ({
|
||||
@@ -619,7 +614,6 @@ export const renderWithProviders = async (
|
||||
shellFocus = true,
|
||||
settings = mockSettings,
|
||||
uiState: providedUiState,
|
||||
inputState: providedInputState,
|
||||
width,
|
||||
mouseEventsEnabled = false,
|
||||
config,
|
||||
@@ -631,7 +625,6 @@ export const renderWithProviders = async (
|
||||
shellFocus?: boolean;
|
||||
settings?: LoadedSettings;
|
||||
uiState?: Partial<UIState>;
|
||||
inputState?: Partial<InputState>;
|
||||
width?: number;
|
||||
mouseEventsEnabled?: boolean;
|
||||
config?: Config;
|
||||
@@ -666,18 +659,6 @@ export const renderWithProviders = async (
|
||||
},
|
||||
) as UIState;
|
||||
|
||||
const inputState = {
|
||||
buffer: { text: '' } as unknown as TextBuffer,
|
||||
userMessages: [],
|
||||
shellModeActive: false,
|
||||
showEscapePrompt: false,
|
||||
copyModeEnabled: false,
|
||||
inputWidth: 80,
|
||||
suggestionsWidth: 40,
|
||||
...(providedUiState as unknown as Partial<InputState>),
|
||||
...providedInputState,
|
||||
};
|
||||
|
||||
if (persistentState?.get) {
|
||||
persistentStateMock.get.mockImplementation(persistentState.get);
|
||||
}
|
||||
@@ -727,65 +708,63 @@ export const renderWithProviders = async (
|
||||
<AppContext.Provider value={appState}>
|
||||
<ConfigContext.Provider value={config}>
|
||||
<SettingsContext.Provider value={settings}>
|
||||
<InputContext.Provider value={inputState}>
|
||||
<UIStateContext.Provider value={finalUiState}>
|
||||
<VimModeProvider>
|
||||
<ShellFocusContext.Provider value={shellFocus}>
|
||||
<SessionStatsProvider>
|
||||
<StreamingContext.Provider
|
||||
value={finalUiState.streamingState}
|
||||
>
|
||||
<UIActionsContext.Provider value={finalUIActions}>
|
||||
<OverflowProvider>
|
||||
<ToolActionsProvider
|
||||
config={config}
|
||||
toolCalls={allToolCalls}
|
||||
isExpanded={
|
||||
toolActions?.isExpanded ??
|
||||
vi.fn().mockReturnValue(false)
|
||||
}
|
||||
toggleExpansion={
|
||||
toolActions?.toggleExpansion ?? vi.fn()
|
||||
}
|
||||
toggleAllExpansion={
|
||||
toolActions?.toggleAllExpansion ?? vi.fn()
|
||||
}
|
||||
<UIStateContext.Provider value={finalUiState}>
|
||||
<VimModeProvider>
|
||||
<ShellFocusContext.Provider value={shellFocus}>
|
||||
<SessionStatsProvider>
|
||||
<StreamingContext.Provider
|
||||
value={finalUiState.streamingState}
|
||||
>
|
||||
<UIActionsContext.Provider value={finalUIActions}>
|
||||
<OverflowProvider>
|
||||
<ToolActionsProvider
|
||||
config={config}
|
||||
toolCalls={allToolCalls}
|
||||
isExpanded={
|
||||
toolActions?.isExpanded ??
|
||||
vi.fn().mockReturnValue(false)
|
||||
}
|
||||
toggleExpansion={
|
||||
toolActions?.toggleExpansion ?? vi.fn()
|
||||
}
|
||||
toggleAllExpansion={
|
||||
toolActions?.toggleAllExpansion ?? vi.fn()
|
||||
}
|
||||
>
|
||||
<AskUserActionsProvider
|
||||
request={null}
|
||||
onSubmit={vi.fn()}
|
||||
onCancel={vi.fn()}
|
||||
>
|
||||
<AskUserActionsProvider
|
||||
request={null}
|
||||
onSubmit={vi.fn()}
|
||||
onCancel={vi.fn()}
|
||||
>
|
||||
<KeypressProvider>
|
||||
<MouseProvider
|
||||
mouseEventsEnabled={mouseEventsEnabled}
|
||||
>
|
||||
<TerminalProvider>
|
||||
<ScrollProvider>
|
||||
<ContextCapture>
|
||||
<Box
|
||||
width={terminalWidth}
|
||||
flexShrink={0}
|
||||
flexGrow={0}
|
||||
flexDirection="column"
|
||||
>
|
||||
{comp}
|
||||
</Box>
|
||||
</ContextCapture>
|
||||
</ScrollProvider>
|
||||
</TerminalProvider>
|
||||
</MouseProvider>
|
||||
</KeypressProvider>
|
||||
</AskUserActionsProvider>
|
||||
</ToolActionsProvider>
|
||||
</OverflowProvider>
|
||||
</UIActionsContext.Provider>
|
||||
</StreamingContext.Provider>
|
||||
</SessionStatsProvider>
|
||||
</ShellFocusContext.Provider>
|
||||
</VimModeProvider>
|
||||
</UIStateContext.Provider>
|
||||
</InputContext.Provider>
|
||||
<KeypressProvider>
|
||||
<MouseProvider
|
||||
mouseEventsEnabled={mouseEventsEnabled}
|
||||
>
|
||||
<TerminalProvider>
|
||||
<ScrollProvider>
|
||||
<ContextCapture>
|
||||
<Box
|
||||
width={terminalWidth}
|
||||
flexShrink={0}
|
||||
flexGrow={0}
|
||||
flexDirection="column"
|
||||
>
|
||||
{comp}
|
||||
</Box>
|
||||
</ContextCapture>
|
||||
</ScrollProvider>
|
||||
</TerminalProvider>
|
||||
</MouseProvider>
|
||||
</KeypressProvider>
|
||||
</AskUserActionsProvider>
|
||||
</ToolActionsProvider>
|
||||
</OverflowProvider>
|
||||
</UIActionsContext.Provider>
|
||||
</StreamingContext.Provider>
|
||||
</SessionStatsProvider>
|
||||
</ShellFocusContext.Provider>
|
||||
</VimModeProvider>
|
||||
</UIStateContext.Provider>
|
||||
</SettingsContext.Provider>
|
||||
</ConfigContext.Provider>
|
||||
</AppContext.Provider>
|
||||
|
||||
@@ -122,17 +122,13 @@ vi.mock('ink', async (importOriginal) => {
|
||||
};
|
||||
});
|
||||
|
||||
import { InputContext, type InputState } from './contexts/InputContext.js';
|
||||
|
||||
// Helper component will read the context values provided by AppContainer
|
||||
// so we can assert against them in our tests.
|
||||
let capturedUIState: UIState;
|
||||
let capturedInputState: InputState;
|
||||
let capturedUIActions: UIActions;
|
||||
let capturedOverflowActions: OverflowActions;
|
||||
function TestContextConsumer() {
|
||||
capturedUIState = useContext(UIStateContext)!;
|
||||
capturedInputState = useContext(InputContext)!;
|
||||
capturedUIActions = useContext(UIActionsContext)!;
|
||||
capturedOverflowActions = useOverflowActions()!;
|
||||
return null;
|
||||
@@ -3040,7 +3036,7 @@ describe('AppContainer State Management', () => {
|
||||
});
|
||||
|
||||
const { unmount } = await act(async () => renderAppContainer());
|
||||
expect(capturedInputState.userMessages).toContain('previous message');
|
||||
expect(capturedUIState.userMessages).toContain('previous message');
|
||||
|
||||
const { onCancelSubmit } = extractUseGeminiStreamArgs(
|
||||
mockedUseGeminiStream.mock.lastCall!,
|
||||
@@ -3068,8 +3064,8 @@ describe('AppContainer State Management', () => {
|
||||
const { rerender, unmount } = await act(async () => renderAppContainer());
|
||||
|
||||
// Verify userMessages is populated from inputHistory
|
||||
expect(capturedInputState.userMessages).toContain('first prompt');
|
||||
expect(capturedInputState.userMessages).toContain('second prompt');
|
||||
expect(capturedUIState.userMessages).toContain('first prompt');
|
||||
expect(capturedUIState.userMessages).toContain('second prompt');
|
||||
|
||||
// Clear the conversation history (simulating /clear command)
|
||||
const mockClearItems = vi.fn();
|
||||
@@ -3088,8 +3084,8 @@ describe('AppContainer State Management', () => {
|
||||
|
||||
// Verify that userMessages still contains the input history
|
||||
// (it should not be affected by clearing conversation history)
|
||||
expect(capturedInputState.userMessages).toContain('first prompt');
|
||||
expect(capturedInputState.userMessages).toContain('second prompt');
|
||||
expect(capturedUIState.userMessages).toContain('first prompt');
|
||||
expect(capturedUIState.userMessages).toContain('second prompt');
|
||||
|
||||
unmount();
|
||||
});
|
||||
|
||||
@@ -36,11 +36,9 @@ import {
|
||||
type ConfirmationRequest,
|
||||
type PermissionConfirmationRequest,
|
||||
type QuotaStats,
|
||||
MessageType,
|
||||
StreamingState,
|
||||
type HistoryItemInfo,
|
||||
} from './types.js';
|
||||
import { checkPermissions } from './hooks/atCommandProcessor.js';
|
||||
import { MessageType, StreamingState } from './types.js';
|
||||
import { ToolActionsProvider } from './contexts/ToolActionsContext.js';
|
||||
import { MouseProvider } from './contexts/MouseContext.js';
|
||||
import { ScrollProvider } from './contexts/ScrollProvider.js';
|
||||
@@ -53,7 +51,6 @@ import {
|
||||
type UserTierId,
|
||||
type GeminiUserTier,
|
||||
type UserFeedbackPayload,
|
||||
type HookSystemMessagePayload,
|
||||
type AgentDefinition,
|
||||
type ApprovalMode,
|
||||
IdeClient,
|
||||
@@ -197,8 +194,6 @@ import {
|
||||
} from './hooks/useVisibilityToggle.js';
|
||||
import { useKeyMatchers } from './hooks/useKeyMatchers.js';
|
||||
|
||||
import { InputContext } from './contexts/InputContext.js';
|
||||
|
||||
/**
|
||||
* The fraction of the terminal width to allocate to the shell.
|
||||
* This provides horizontal padding.
|
||||
@@ -2114,19 +2109,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
}
|
||||
};
|
||||
|
||||
const handleHookSystemMessage = (payload: HookSystemMessagePayload) => {
|
||||
historyManager.addItem(
|
||||
{
|
||||
type: MessageType.INFO,
|
||||
text: payload.message,
|
||||
source: payload.hookName,
|
||||
} as HistoryItemInfo,
|
||||
Date.now(),
|
||||
);
|
||||
};
|
||||
|
||||
coreEvents.on(CoreEvent.UserFeedback, handleUserFeedback);
|
||||
coreEvents.on(CoreEvent.HookSystemMessage, handleHookSystemMessage);
|
||||
|
||||
// Flush any messages that happened during startup before this component
|
||||
// mounted.
|
||||
@@ -2134,7 +2117,6 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
|
||||
return () => {
|
||||
coreEvents.off(CoreEvent.UserFeedback, handleUserFeedback);
|
||||
coreEvents.off(CoreEvent.HookSystemMessage, handleHookSystemMessage);
|
||||
};
|
||||
}, [historyManager]);
|
||||
|
||||
@@ -2346,27 +2328,6 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
};
|
||||
}, [config, refreshStatic]);
|
||||
|
||||
const inputState = useMemo(
|
||||
() => ({
|
||||
buffer,
|
||||
userMessages: inputHistory,
|
||||
shellModeActive,
|
||||
showEscapePrompt,
|
||||
copyModeEnabled,
|
||||
inputWidth,
|
||||
suggestionsWidth,
|
||||
}),
|
||||
[
|
||||
buffer,
|
||||
inputHistory,
|
||||
shellModeActive,
|
||||
showEscapePrompt,
|
||||
copyModeEnabled,
|
||||
inputWidth,
|
||||
suggestionsWidth,
|
||||
],
|
||||
);
|
||||
|
||||
const uiState: UIState = useMemo(
|
||||
() => ({
|
||||
history: historyManager.history,
|
||||
@@ -2410,6 +2371,11 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
initError,
|
||||
pendingGeminiHistoryItems,
|
||||
thought,
|
||||
shellModeActive,
|
||||
userMessages: inputHistory,
|
||||
buffer,
|
||||
inputWidth,
|
||||
suggestionsWidth,
|
||||
isInputActive,
|
||||
isResuming,
|
||||
shouldShowIdePrompt,
|
||||
@@ -2425,6 +2391,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
renderMarkdown,
|
||||
ctrlCPressedOnce: ctrlCPressCount >= 1,
|
||||
ctrlDPressedOnce: ctrlDPressCount >= 1,
|
||||
showEscapePrompt,
|
||||
shortcutsHelpVisible,
|
||||
cleanUiDetailsVisible,
|
||||
isFocused,
|
||||
@@ -2476,6 +2443,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
embeddedShellFocused,
|
||||
showDebugProfiler,
|
||||
customDialog,
|
||||
copyModeEnabled,
|
||||
transientMessage,
|
||||
bannerData,
|
||||
bannerVisible,
|
||||
@@ -2530,6 +2498,11 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
initError,
|
||||
pendingGeminiHistoryItems,
|
||||
thought,
|
||||
shellModeActive,
|
||||
inputHistory,
|
||||
buffer,
|
||||
inputWidth,
|
||||
suggestionsWidth,
|
||||
isInputActive,
|
||||
isResuming,
|
||||
shouldShowIdePrompt,
|
||||
@@ -2545,6 +2518,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
renderMarkdown,
|
||||
ctrlCPressCount,
|
||||
ctrlDPressCount,
|
||||
showEscapePrompt,
|
||||
shortcutsHelpVisible,
|
||||
cleanUiDetailsVisible,
|
||||
isFocused,
|
||||
@@ -2596,6 +2570,7 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
customDialog,
|
||||
apiKeyDefaultValue,
|
||||
authState,
|
||||
copyModeEnabled,
|
||||
transientMessage,
|
||||
bannerData,
|
||||
bannerVisible,
|
||||
@@ -2782,34 +2757,32 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
|
||||
return (
|
||||
<UIStateContext.Provider value={uiState}>
|
||||
<InputContext.Provider value={inputState}>
|
||||
<UIActionsContext.Provider value={uiActions}>
|
||||
<ConfigContext.Provider value={config}>
|
||||
<AppContext.Provider
|
||||
value={{
|
||||
version: props.version,
|
||||
startupWarnings: props.startupWarnings || [],
|
||||
}}
|
||||
<UIActionsContext.Provider value={uiActions}>
|
||||
<ConfigContext.Provider value={config}>
|
||||
<AppContext.Provider
|
||||
value={{
|
||||
version: props.version,
|
||||
startupWarnings: props.startupWarnings || [],
|
||||
}}
|
||||
>
|
||||
<ToolActionsProvider
|
||||
config={config}
|
||||
toolCalls={allToolCalls}
|
||||
isExpanded={isExpanded}
|
||||
toggleExpansion={toggleExpansion}
|
||||
toggleAllExpansion={toggleAllExpansion}
|
||||
>
|
||||
<ToolActionsProvider
|
||||
config={config}
|
||||
toolCalls={allToolCalls}
|
||||
isExpanded={isExpanded}
|
||||
toggleExpansion={toggleExpansion}
|
||||
toggleAllExpansion={toggleAllExpansion}
|
||||
>
|
||||
<ShellFocusContext.Provider value={isFocused}>
|
||||
<MouseProvider mouseEventsEnabled={mouseMode}>
|
||||
<ScrollProvider>
|
||||
<App key={`app-${forceRerenderKey}`} />
|
||||
</ScrollProvider>
|
||||
</MouseProvider>
|
||||
</ShellFocusContext.Provider>
|
||||
</ToolActionsProvider>
|
||||
</AppContext.Provider>
|
||||
</ConfigContext.Provider>
|
||||
</UIActionsContext.Provider>
|
||||
</InputContext.Provider>
|
||||
<ShellFocusContext.Provider value={isFocused}>
|
||||
<MouseProvider mouseEventsEnabled={mouseMode}>
|
||||
<ScrollProvider>
|
||||
<App key={`app-${forceRerenderKey}`} />
|
||||
</ScrollProvider>
|
||||
</MouseProvider>
|
||||
</ShellFocusContext.Provider>
|
||||
</ToolActionsProvider>
|
||||
</AppContext.Provider>
|
||||
</ConfigContext.Provider>
|
||||
</UIActionsContext.Provider>
|
||||
</UIStateContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -55,12 +55,6 @@ Footer
|
||||
Gemini CLI v1.2.3
|
||||
|
||||
|
||||
|
||||
Tips for getting started:
|
||||
1. Create GEMINI.md files to customize your interactions
|
||||
2. /help for more information
|
||||
3. Ask coding questions, edit code or run commands
|
||||
4. Be specific for the best results
|
||||
Composer
|
||||
"
|
||||
`;
|
||||
@@ -130,14 +124,13 @@ HistoryItemDisplay
|
||||
│ │
|
||||
│ ? ls list directory │
|
||||
│ │
|
||||
│ ╭──────────────────────────────────────────────────────────────────────────────────────────────╮ │
|
||||
│ │ ls │ │
|
||||
│ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ │
|
||||
│ Allow execution of [ls]? │
|
||||
│ ls │
|
||||
│ Allow execution of: 'ls'? │
|
||||
│ │
|
||||
│ ● 1. Allow once │
|
||||
│ 2. Allow for this session │
|
||||
│ 3. No, suggest changes (esc) │
|
||||
│ │
|
||||
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
|
||||
|
||||
@@ -145,6 +138,7 @@ HistoryItemDisplay
|
||||
|
||||
|
||||
|
||||
|
||||
Notifications
|
||||
|
||||
Composer
|
||||
|
||||
@@ -12,283 +12,253 @@
|
||||
<rect x="351" y="0" width="549" height="17" fill="#141414" />
|
||||
<rect x="0" y="17" width="900" height="17" fill="#141414" />
|
||||
<text x="0" y="19" fill="#000000" textLength="900" lengthAdjust="spacingAndGlyphs">▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄</text>
|
||||
<text x="0" y="53" fill="#333333" textLength="891" lengthAdjust="spacingAndGlyphs">╭─────────────────────────────────────────────────────────────────────────────────────────────────╮</text>
|
||||
<text x="0" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="70" fill="#ffffaf" textLength="54" lengthAdjust="spacingAndGlyphs" font-weight="bold">? Edit</text>
|
||||
<text x="882" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="87" fill="#333333" textLength="855" lengthAdjust="spacingAndGlyphs">╭─────────────────────────────────────────────────────────────────────────────────────────────╮</text>
|
||||
<text x="882" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="104" fill="#afafaf" textLength="414" lengthAdjust="spacingAndGlyphs">... first 42 lines hidden (Ctrl+O to show) ...</text>
|
||||
<text x="864" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="882" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="121" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">43</text>
|
||||
<text x="81" y="121" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
||||
<text x="135" y="121" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line43</text>
|
||||
<text x="189" y="121" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
||||
<text x="216" y="121" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<text x="252" y="121" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="864" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="882" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="138" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">44</text>
|
||||
<text x="81" y="138" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
||||
<text x="135" y="138" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line44</text>
|
||||
<text x="189" y="138" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
||||
<text x="216" y="138" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<text x="252" y="138" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="864" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="882" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="155" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="155" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="155" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">45</text>
|
||||
<text x="81" y="155" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
||||
<text x="135" y="155" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line45</text>
|
||||
<text x="189" y="155" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
||||
<text x="216" y="155" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<text x="252" y="155" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="864" y="155" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="882" y="155" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="172" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">46</text>
|
||||
<text x="81" y="172" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
||||
<text x="135" y="172" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line46</text>
|
||||
<text x="189" y="172" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
||||
<text x="216" y="172" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<text x="252" y="172" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="864" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="882" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="189" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="189" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="189" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">47</text>
|
||||
<text x="81" y="189" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
||||
<text x="135" y="189" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line47</text>
|
||||
<text x="189" y="189" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
||||
<text x="216" y="189" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<text x="252" y="189" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="864" y="189" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="882" y="189" fill="#333333" textLength="18" lengthAdjust="spacingAndGlyphs">│▄</text>
|
||||
<text x="0" y="206" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="206" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="206" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">48</text>
|
||||
<text x="81" y="206" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
||||
<text x="135" y="206" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line48</text>
|
||||
<text x="189" y="206" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
||||
<text x="216" y="206" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<text x="252" y="206" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="864" y="206" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="882" y="206" fill="#333333" textLength="18" lengthAdjust="spacingAndGlyphs">│█</text>
|
||||
<text x="0" y="223" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="223" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="223" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">49</text>
|
||||
<text x="81" y="223" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
||||
<text x="135" y="223" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line49</text>
|
||||
<text x="189" y="223" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
||||
<text x="216" y="223" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<text x="252" y="223" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="864" y="223" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="882" y="223" fill="#333333" textLength="18" lengthAdjust="spacingAndGlyphs">│█</text>
|
||||
<text x="0" y="240" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="240" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="240" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">50</text>
|
||||
<text x="81" y="240" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
||||
<text x="135" y="240" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line50</text>
|
||||
<text x="189" y="240" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
||||
<text x="216" y="240" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<text x="252" y="240" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="864" y="240" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="882" y="240" fill="#333333" textLength="18" lengthAdjust="spacingAndGlyphs">│█</text>
|
||||
<text x="0" y="257" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="257" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="257" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">51</text>
|
||||
<text x="81" y="257" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
||||
<text x="135" y="257" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line51</text>
|
||||
<text x="189" y="257" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
||||
<text x="216" y="257" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<text x="252" y="257" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="864" y="257" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="882" y="257" fill="#333333" textLength="18" lengthAdjust="spacingAndGlyphs">│█</text>
|
||||
<text x="0" y="274" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="274" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="274" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">52</text>
|
||||
<text x="81" y="274" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
||||
<text x="135" y="274" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line52</text>
|
||||
<text x="189" y="274" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
||||
<text x="216" y="274" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<text x="252" y="274" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="864" y="274" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="882" y="274" fill="#333333" textLength="18" lengthAdjust="spacingAndGlyphs">│█</text>
|
||||
<text x="0" y="291" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="291" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="291" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">53</text>
|
||||
<text x="81" y="291" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
||||
<text x="135" y="291" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line53</text>
|
||||
<text x="189" y="291" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
||||
<text x="216" y="291" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<text x="252" y="291" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="864" y="291" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="882" y="291" fill="#333333" textLength="18" lengthAdjust="spacingAndGlyphs">│█</text>
|
||||
<text x="0" y="308" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="308" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="308" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">54</text>
|
||||
<text x="81" y="308" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
||||
<text x="135" y="308" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line54</text>
|
||||
<text x="189" y="308" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
||||
<text x="216" y="308" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<text x="252" y="308" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="864" y="308" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="882" y="308" fill="#333333" textLength="18" lengthAdjust="spacingAndGlyphs">│█</text>
|
||||
<text x="0" y="325" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="325" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="325" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">55</text>
|
||||
<text x="81" y="325" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
||||
<text x="135" y="325" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line55</text>
|
||||
<text x="189" y="325" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
||||
<text x="216" y="325" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<text x="252" y="325" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="864" y="325" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="882" y="325" fill="#333333" textLength="18" lengthAdjust="spacingAndGlyphs">│█</text>
|
||||
<text x="0" y="342" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="342" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="342" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">56</text>
|
||||
<text x="81" y="342" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
||||
<text x="135" y="342" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line56</text>
|
||||
<text x="189" y="342" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
||||
<text x="216" y="342" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<text x="252" y="342" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="864" y="342" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="882" y="342" fill="#333333" textLength="18" lengthAdjust="spacingAndGlyphs">│█</text>
|
||||
<text x="0" y="359" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="359" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="359" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">57</text>
|
||||
<text x="81" y="359" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
||||
<text x="135" y="359" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line57</text>
|
||||
<text x="189" y="359" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
||||
<text x="216" y="359" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<text x="252" y="359" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="864" y="359" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="882" y="359" fill="#333333" textLength="18" lengthAdjust="spacingAndGlyphs">│█</text>
|
||||
<text x="0" y="376" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="376" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="376" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">58</text>
|
||||
<text x="81" y="376" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
||||
<text x="135" y="376" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line58</text>
|
||||
<text x="189" y="376" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
||||
<text x="216" y="376" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<text x="252" y="376" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="864" y="376" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="882" y="376" fill="#333333" textLength="18" lengthAdjust="spacingAndGlyphs">│█</text>
|
||||
<text x="0" y="393" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="393" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="393" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">59</text>
|
||||
<text x="81" y="393" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
||||
<text x="135" y="393" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line59</text>
|
||||
<text x="189" y="393" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
||||
<text x="216" y="393" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<text x="252" y="393" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="864" y="393" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="882" y="393" fill="#333333" textLength="18" lengthAdjust="spacingAndGlyphs">│█</text>
|
||||
<text x="0" y="410" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="410" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="410" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">60</text>
|
||||
<text x="81" y="410" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
||||
<text x="135" y="410" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line60</text>
|
||||
<text x="189" y="410" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
||||
<text x="216" y="410" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<text x="252" y="410" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="864" y="410" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="882" y="410" fill="#333333" textLength="18" lengthAdjust="spacingAndGlyphs">│█</text>
|
||||
<text x="0" y="427" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="427" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="36" y="425" width="18" height="17" fill="#5f0000" />
|
||||
<text x="36" y="427" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">61</text>
|
||||
<text x="0" y="53" fill="#ffffaf" textLength="891" lengthAdjust="spacingAndGlyphs">╭─────────────────────────────────────────────────────────────────────────────────────────────────╮</text>
|
||||
<text x="0" y="70" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="70" fill="#ffffaf" textLength="135" lengthAdjust="spacingAndGlyphs" font-weight="bold">Action Required</text>
|
||||
<text x="882" y="70" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="87" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="882" y="87" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="104" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="104" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">?</text>
|
||||
<text x="45" y="104" fill="#ffffff" textLength="36" lengthAdjust="spacingAndGlyphs" font-weight="bold">Edit</text>
|
||||
<text x="90" y="104" fill="#afafaf" textLength="774" lengthAdjust="spacingAndGlyphs">packages/.../InputPrompt.tsx: return kittyProtocolSupporte... => return kittyProto</text>
|
||||
<text x="864" y="104" fill="#ffffff" textLength="18" lengthAdjust="spacingAndGlyphs">… </text>
|
||||
<text x="882" y="104" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="121" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="882" y="121" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="138" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="138" fill="#afafaf" textLength="414" lengthAdjust="spacingAndGlyphs">... first 44 lines hidden (Ctrl+O to show) ...</text>
|
||||
<text x="882" y="138" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="155" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="155" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">45</text>
|
||||
<text x="63" y="155" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
||||
<text x="117" y="155" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line45</text>
|
||||
<text x="171" y="155" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
||||
<text x="198" y="155" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<text x="234" y="155" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="882" y="155" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="172" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="172" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">46</text>
|
||||
<text x="63" y="172" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
||||
<text x="117" y="172" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line46</text>
|
||||
<text x="171" y="172" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
||||
<text x="198" y="172" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<text x="234" y="172" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="882" y="172" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="189" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="189" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">47</text>
|
||||
<text x="63" y="189" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
||||
<text x="117" y="189" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line47</text>
|
||||
<text x="171" y="189" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
||||
<text x="198" y="189" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<text x="234" y="189" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="882" y="189" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="189" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">▄</text>
|
||||
<text x="0" y="206" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="206" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">48</text>
|
||||
<text x="63" y="206" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
||||
<text x="117" y="206" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line48</text>
|
||||
<text x="171" y="206" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
||||
<text x="198" y="206" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<text x="234" y="206" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="882" y="206" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="206" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">█</text>
|
||||
<text x="0" y="223" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="223" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">49</text>
|
||||
<text x="63" y="223" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
||||
<text x="117" y="223" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line49</text>
|
||||
<text x="171" y="223" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
||||
<text x="198" y="223" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<text x="234" y="223" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="882" y="223" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="223" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">█</text>
|
||||
<text x="0" y="240" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="240" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">50</text>
|
||||
<text x="63" y="240" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
||||
<text x="117" y="240" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line50</text>
|
||||
<text x="171" y="240" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
||||
<text x="198" y="240" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<text x="234" y="240" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="882" y="240" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="240" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">█</text>
|
||||
<text x="0" y="257" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="257" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">51</text>
|
||||
<text x="63" y="257" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
||||
<text x="117" y="257" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line51</text>
|
||||
<text x="171" y="257" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
||||
<text x="198" y="257" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<text x="234" y="257" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="882" y="257" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="257" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">█</text>
|
||||
<text x="0" y="274" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="274" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">52</text>
|
||||
<text x="63" y="274" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
||||
<text x="117" y="274" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line52</text>
|
||||
<text x="171" y="274" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
||||
<text x="198" y="274" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<text x="234" y="274" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="882" y="274" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="274" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">█</text>
|
||||
<text x="0" y="291" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="291" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">53</text>
|
||||
<text x="63" y="291" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
||||
<text x="117" y="291" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line53</text>
|
||||
<text x="171" y="291" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
||||
<text x="198" y="291" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<text x="234" y="291" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="882" y="291" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="291" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">█</text>
|
||||
<text x="0" y="308" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="308" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">54</text>
|
||||
<text x="63" y="308" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
||||
<text x="117" y="308" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line54</text>
|
||||
<text x="171" y="308" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
||||
<text x="198" y="308" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<text x="234" y="308" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="882" y="308" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="308" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">█</text>
|
||||
<text x="0" y="325" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="325" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">55</text>
|
||||
<text x="63" y="325" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
||||
<text x="117" y="325" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line55</text>
|
||||
<text x="171" y="325" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
||||
<text x="198" y="325" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<text x="234" y="325" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="882" y="325" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="325" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">█</text>
|
||||
<text x="0" y="342" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="342" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">56</text>
|
||||
<text x="63" y="342" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
||||
<text x="117" y="342" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line56</text>
|
||||
<text x="171" y="342" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
||||
<text x="198" y="342" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<text x="234" y="342" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="882" y="342" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="342" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">█</text>
|
||||
<text x="0" y="359" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="359" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">57</text>
|
||||
<text x="63" y="359" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
||||
<text x="117" y="359" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line57</text>
|
||||
<text x="171" y="359" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
||||
<text x="198" y="359" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<text x="234" y="359" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="882" y="359" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="359" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">█</text>
|
||||
<text x="0" y="376" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="376" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">58</text>
|
||||
<text x="63" y="376" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
||||
<text x="117" y="376" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line58</text>
|
||||
<text x="171" y="376" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
||||
<text x="198" y="376" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<text x="234" y="376" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="882" y="376" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="376" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">█</text>
|
||||
<text x="0" y="393" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="393" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">59</text>
|
||||
<text x="63" y="393" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
||||
<text x="117" y="393" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line59</text>
|
||||
<text x="171" y="393" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
||||
<text x="198" y="393" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<text x="234" y="393" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="882" y="393" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="393" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">█</text>
|
||||
<text x="0" y="410" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="410" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">60</text>
|
||||
<text x="63" y="410" fill="#e5e5e5" textLength="54" lengthAdjust="spacingAndGlyphs">const </text>
|
||||
<text x="117" y="410" fill="#ffffff" textLength="54" lengthAdjust="spacingAndGlyphs">line60</text>
|
||||
<text x="171" y="410" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs"> = </text>
|
||||
<text x="198" y="410" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<text x="234" y="410" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="882" y="410" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="410" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">█</text>
|
||||
<text x="0" y="427" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="18" y="425" width="18" height="17" fill="#5f0000" />
|
||||
<text x="18" y="427" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">61</text>
|
||||
<rect x="36" y="425" width="9" height="17" fill="#5f0000" />
|
||||
<rect x="45" y="425" width="9" height="17" fill="#5f0000" />
|
||||
<text x="45" y="427" fill="#ff87af" textLength="9" lengthAdjust="spacingAndGlyphs">-</text>
|
||||
<rect x="54" y="425" width="9" height="17" fill="#5f0000" />
|
||||
<rect x="63" y="425" width="9" height="17" fill="#5f0000" />
|
||||
<text x="63" y="427" fill="#ff87af" textLength="9" lengthAdjust="spacingAndGlyphs">-</text>
|
||||
<rect x="72" y="425" width="9" height="17" fill="#5f0000" />
|
||||
<rect x="81" y="425" width="9" height="17" fill="#5f0000" />
|
||||
<rect x="90" y="425" width="54" height="17" fill="#5f0000" />
|
||||
<text x="90" y="427" fill="#0000ee" textLength="54" lengthAdjust="spacingAndGlyphs">return</text>
|
||||
<rect x="144" y="425" width="234" height="17" fill="#5f0000" />
|
||||
<text x="144" y="427" fill="#e5e5e5" textLength="234" lengthAdjust="spacingAndGlyphs"> kittyProtocolSupporte...;</text>
|
||||
<text x="864" y="427" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="882" y="427" fill="#333333" textLength="18" lengthAdjust="spacingAndGlyphs">│█</text>
|
||||
<text x="0" y="444" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="444" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="36" y="442" width="18" height="17" fill="#005f00" />
|
||||
<text x="36" y="444" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">61</text>
|
||||
<rect x="72" y="425" width="54" height="17" fill="#5f0000" />
|
||||
<text x="72" y="427" fill="#0000ee" textLength="54" lengthAdjust="spacingAndGlyphs">return</text>
|
||||
<rect x="126" y="425" width="234" height="17" fill="#5f0000" />
|
||||
<text x="126" y="427" fill="#e5e5e5" textLength="234" lengthAdjust="spacingAndGlyphs"> kittyProtocolSupporte...;</text>
|
||||
<text x="882" y="427" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="427" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">█</text>
|
||||
<text x="0" y="444" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="18" y="442" width="18" height="17" fill="#005f00" />
|
||||
<text x="18" y="444" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">61</text>
|
||||
<rect x="36" y="442" width="9" height="17" fill="#005f00" />
|
||||
<rect x="45" y="442" width="9" height="17" fill="#005f00" />
|
||||
<text x="45" y="444" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">+</text>
|
||||
<rect x="54" y="442" width="9" height="17" fill="#005f00" />
|
||||
<rect x="63" y="442" width="9" height="17" fill="#005f00" />
|
||||
<text x="63" y="444" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">+</text>
|
||||
<rect x="72" y="442" width="9" height="17" fill="#005f00" />
|
||||
<rect x="81" y="442" width="9" height="17" fill="#005f00" />
|
||||
<rect x="90" y="442" width="54" height="17" fill="#005f00" />
|
||||
<text x="90" y="444" fill="#0000ee" textLength="54" lengthAdjust="spacingAndGlyphs">return</text>
|
||||
<rect x="144" y="442" width="234" height="17" fill="#005f00" />
|
||||
<text x="144" y="444" fill="#e5e5e5" textLength="234" lengthAdjust="spacingAndGlyphs"> kittyProtocolSupporte...;</text>
|
||||
<text x="864" y="444" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="882" y="444" fill="#333333" textLength="18" lengthAdjust="spacingAndGlyphs">│█</text>
|
||||
<text x="0" y="461" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="461" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="461" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">62</text>
|
||||
<text x="81" y="461" fill="#e5e5e5" textLength="180" lengthAdjust="spacingAndGlyphs"> buffer: TextBuffer;</text>
|
||||
<text x="864" y="461" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="882" y="461" fill="#333333" textLength="18" lengthAdjust="spacingAndGlyphs">│█</text>
|
||||
<text x="0" y="478" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="478" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="478" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">63</text>
|
||||
<text x="90" y="478" fill="#ffffff" textLength="72" lengthAdjust="spacingAndGlyphs">onSubmit</text>
|
||||
<text x="162" y="478" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs">: (</text>
|
||||
<text x="189" y="478" fill="#ffffff" textLength="45" lengthAdjust="spacingAndGlyphs">value</text>
|
||||
<text x="234" y="478" fill="#e5e5e5" textLength="18" lengthAdjust="spacingAndGlyphs">: </text>
|
||||
<text x="252" y="478" fill="#00cdcd" textLength="54" lengthAdjust="spacingAndGlyphs">string</text>
|
||||
<text x="306" y="478" fill="#e5e5e5" textLength="45" lengthAdjust="spacingAndGlyphs">) => </text>
|
||||
<text x="351" y="478" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">void</text>
|
||||
<text x="387" y="478" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="864" y="478" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="882" y="478" fill="#333333" textLength="18" lengthAdjust="spacingAndGlyphs">│█</text>
|
||||
<text x="0" y="495" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="495" fill="#333333" textLength="855" lengthAdjust="spacingAndGlyphs">╰─────────────────────────────────────────────────────────────────────────────────────────────╯</text>
|
||||
<text x="882" y="495" fill="#333333" textLength="18" lengthAdjust="spacingAndGlyphs">│█</text>
|
||||
<text x="0" y="512" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="512" fill="#ffffff" textLength="162" lengthAdjust="spacingAndGlyphs">Apply this change?</text>
|
||||
<text x="882" y="512" fill="#333333" textLength="18" lengthAdjust="spacingAndGlyphs">│█</text>
|
||||
<text x="0" y="529" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="882" y="529" fill="#333333" textLength="18" lengthAdjust="spacingAndGlyphs">│█</text>
|
||||
<text x="0" y="546" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="18" y="544" width="9" height="17" fill="#001a00" />
|
||||
<text x="18" y="546" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">●</text>
|
||||
<rect x="27" y="544" width="9" height="17" fill="#001a00" />
|
||||
<rect x="36" y="544" width="18" height="17" fill="#001a00" />
|
||||
<text x="36" y="546" fill="#00cd00" textLength="18" lengthAdjust="spacingAndGlyphs">1.</text>
|
||||
<rect x="54" y="544" width="9" height="17" fill="#001a00" />
|
||||
<rect x="63" y="544" width="90" height="17" fill="#001a00" />
|
||||
<text x="63" y="546" fill="#00cd00" textLength="90" lengthAdjust="spacingAndGlyphs">Allow once</text>
|
||||
<rect x="153" y="544" width="603" height="17" fill="#001a00" />
|
||||
<text x="882" y="546" fill="#333333" textLength="18" lengthAdjust="spacingAndGlyphs">│█</text>
|
||||
<text x="0" y="563" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="563" fill="#ffffff" textLength="18" lengthAdjust="spacingAndGlyphs">2.</text>
|
||||
<text x="63" y="563" fill="#ffffff" textLength="198" lengthAdjust="spacingAndGlyphs">Allow for this session</text>
|
||||
<text x="882" y="563" fill="#333333" textLength="18" lengthAdjust="spacingAndGlyphs">│█</text>
|
||||
<text x="0" y="580" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="580" fill="#ffffff" textLength="18" lengthAdjust="spacingAndGlyphs">3.</text>
|
||||
<text x="63" y="580" fill="#ffffff" textLength="387" lengthAdjust="spacingAndGlyphs">Allow for this file in all future sessions </text>
|
||||
<text x="450" y="580" fill="#afafaf" textLength="306" lengthAdjust="spacingAndGlyphs">~/.gemini/policies/auto-saved.toml</text>
|
||||
<text x="882" y="580" fill="#333333" textLength="18" lengthAdjust="spacingAndGlyphs">│█</text>
|
||||
<text x="0" y="597" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="597" fill="#ffffff" textLength="18" lengthAdjust="spacingAndGlyphs">4.</text>
|
||||
<text x="63" y="597" fill="#ffffff" textLength="243" lengthAdjust="spacingAndGlyphs">Modify with external editor</text>
|
||||
<text x="882" y="597" fill="#333333" textLength="18" lengthAdjust="spacingAndGlyphs">│█</text>
|
||||
<text x="0" y="614" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="614" fill="#ffffff" textLength="18" lengthAdjust="spacingAndGlyphs">5.</text>
|
||||
<text x="63" y="614" fill="#ffffff" textLength="225" lengthAdjust="spacingAndGlyphs">No, suggest changes (esc)</text>
|
||||
<text x="882" y="614" fill="#333333" textLength="18" lengthAdjust="spacingAndGlyphs">│█</text>
|
||||
<text x="0" y="631" fill="#333333" textLength="900" lengthAdjust="spacingAndGlyphs">╰─────────────────────────────────────────────────────────────────────────────────────────────────╯█</text>
|
||||
<rect x="72" y="442" width="54" height="17" fill="#005f00" />
|
||||
<text x="72" y="444" fill="#0000ee" textLength="54" lengthAdjust="spacingAndGlyphs">return</text>
|
||||
<rect x="126" y="442" width="234" height="17" fill="#005f00" />
|
||||
<text x="126" y="444" fill="#e5e5e5" textLength="234" lengthAdjust="spacingAndGlyphs"> kittyProtocolSupporte...;</text>
|
||||
<text x="882" y="444" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="444" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">█</text>
|
||||
<text x="0" y="461" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="461" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">62</text>
|
||||
<text x="63" y="461" fill="#e5e5e5" textLength="180" lengthAdjust="spacingAndGlyphs"> buffer: TextBuffer;</text>
|
||||
<text x="882" y="461" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="461" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">█</text>
|
||||
<text x="0" y="478" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="478" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">63</text>
|
||||
<text x="72" y="478" fill="#ffffff" textLength="72" lengthAdjust="spacingAndGlyphs">onSubmit</text>
|
||||
<text x="144" y="478" fill="#e5e5e5" textLength="27" lengthAdjust="spacingAndGlyphs">: (</text>
|
||||
<text x="171" y="478" fill="#ffffff" textLength="45" lengthAdjust="spacingAndGlyphs">value</text>
|
||||
<text x="216" y="478" fill="#e5e5e5" textLength="18" lengthAdjust="spacingAndGlyphs">: </text>
|
||||
<text x="234" y="478" fill="#00cdcd" textLength="54" lengthAdjust="spacingAndGlyphs">string</text>
|
||||
<text x="288" y="478" fill="#e5e5e5" textLength="45" lengthAdjust="spacingAndGlyphs">) => </text>
|
||||
<text x="333" y="478" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">void</text>
|
||||
<text x="369" y="478" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="882" y="478" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="478" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">█</text>
|
||||
<text x="0" y="495" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="495" fill="#ffffff" textLength="162" lengthAdjust="spacingAndGlyphs">Apply this change?</text>
|
||||
<text x="882" y="495" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="495" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">█</text>
|
||||
<text x="0" y="512" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="882" y="512" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="512" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">█</text>
|
||||
<text x="0" y="529" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="18" y="527" width="9" height="17" fill="#001a00" />
|
||||
<text x="18" y="529" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">●</text>
|
||||
<rect x="27" y="527" width="9" height="17" fill="#001a00" />
|
||||
<rect x="36" y="527" width="18" height="17" fill="#001a00" />
|
||||
<text x="36" y="529" fill="#00cd00" textLength="18" lengthAdjust="spacingAndGlyphs">1.</text>
|
||||
<rect x="54" y="527" width="9" height="17" fill="#001a00" />
|
||||
<rect x="63" y="527" width="90" height="17" fill="#001a00" />
|
||||
<text x="63" y="529" fill="#00cd00" textLength="90" lengthAdjust="spacingAndGlyphs">Allow once</text>
|
||||
<rect x="153" y="527" width="288" height="17" fill="#001a00" />
|
||||
<text x="882" y="529" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="529" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">█</text>
|
||||
<text x="0" y="546" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="546" fill="#ffffff" textLength="18" lengthAdjust="spacingAndGlyphs">2.</text>
|
||||
<text x="63" y="546" fill="#ffffff" textLength="198" lengthAdjust="spacingAndGlyphs">Allow for this session</text>
|
||||
<text x="882" y="546" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="546" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">█</text>
|
||||
<text x="0" y="563" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="563" fill="#ffffff" textLength="18" lengthAdjust="spacingAndGlyphs">3.</text>
|
||||
<text x="63" y="563" fill="#ffffff" textLength="378" lengthAdjust="spacingAndGlyphs">Allow for this file in all future sessions</text>
|
||||
<text x="882" y="563" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="563" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">█</text>
|
||||
<text x="0" y="580" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="580" fill="#ffffff" textLength="18" lengthAdjust="spacingAndGlyphs">4.</text>
|
||||
<text x="63" y="580" fill="#ffffff" textLength="243" lengthAdjust="spacingAndGlyphs">Modify with external editor</text>
|
||||
<text x="882" y="580" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="580" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">█</text>
|
||||
<text x="0" y="597" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="597" fill="#ffffff" textLength="18" lengthAdjust="spacingAndGlyphs">5.</text>
|
||||
<text x="63" y="597" fill="#ffffff" textLength="225" lengthAdjust="spacingAndGlyphs">No, suggest changes (esc)</text>
|
||||
<text x="882" y="597" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="597" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">█</text>
|
||||
<text x="0" y="614" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="882" y="614" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="891" y="614" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">█</text>
|
||||
<text x="0" y="631" fill="#ffffaf" textLength="891" lengthAdjust="spacingAndGlyphs">╰─────────────────────────────────────────────────────────────────────────────────────────────────╯</text>
|
||||
<text x="891" y="631" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">█</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 26 KiB |
@@ -5,39 +5,39 @@ exports[`Full Terminal Tool Confirmation Snapshot > renders tool confirmation bo
|
||||
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
|
||||
|
||||
╭─────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ ? Edit │
|
||||
│ ╭─────────────────────────────────────────────────────────────────────────────────────────────╮ │
|
||||
│ │ ... first 42 lines hidden (Ctrl+O to show) ... │ │
|
||||
│ │ 43 const line43 = true; │ │
|
||||
│ │ 44 const line44 = true; │ │
|
||||
│ │ 45 const line45 = true; │ │
|
||||
│ │ 46 const line46 = true; │ │
|
||||
│ │ 47 const line47 = true; │ │▄
|
||||
│ │ 48 const line48 = true; │ │█
|
||||
│ │ 49 const line49 = true; │ │█
|
||||
│ │ 50 const line50 = true; │ │█
|
||||
│ │ 51 const line51 = true; │ │█
|
||||
│ │ 52 const line52 = true; │ │█
|
||||
│ │ 53 const line53 = true; │ │█
|
||||
│ │ 54 const line54 = true; │ │█
|
||||
│ │ 55 const line55 = true; │ │█
|
||||
│ │ 56 const line56 = true; │ │█
|
||||
│ │ 57 const line57 = true; │ │█
|
||||
│ │ 58 const line58 = true; │ │█
|
||||
│ │ 59 const line59 = true; │ │█
|
||||
│ │ 60 const line60 = true; │ │█
|
||||
│ │ 61 - return kittyProtocolSupporte...; │ │█
|
||||
│ │ 61 + return kittyProtocolSupporte...; │ │█
|
||||
│ │ 62 buffer: TextBuffer; │ │█
|
||||
│ │ 63 onSubmit: (value: string) => void; │ │█
|
||||
│ ╰─────────────────────────────────────────────────────────────────────────────────────────────╯ │█
|
||||
│ Action Required │
|
||||
│ │
|
||||
│ ? Edit packages/.../InputPrompt.tsx: return kittyProtocolSupporte... => return kittyProto… │
|
||||
│ │
|
||||
│ ... first 44 lines hidden (Ctrl+O to show) ... │
|
||||
│ 45 const line45 = true; │
|
||||
│ 46 const line46 = true; │
|
||||
│ 47 const line47 = true; │▄
|
||||
│ 48 const line48 = true; │█
|
||||
│ 49 const line49 = true; │█
|
||||
│ 50 const line50 = true; │█
|
||||
│ 51 const line51 = true; │█
|
||||
│ 52 const line52 = true; │█
|
||||
│ 53 const line53 = true; │█
|
||||
│ 54 const line54 = true; │█
|
||||
│ 55 const line55 = true; │█
|
||||
│ 56 const line56 = true; │█
|
||||
│ 57 const line57 = true; │█
|
||||
│ 58 const line58 = true; │█
|
||||
│ 59 const line59 = true; │█
|
||||
│ 60 const line60 = true; │█
|
||||
│ 61 - return kittyProtocolSupporte...; │█
|
||||
│ 61 + return kittyProtocolSupporte...; │█
|
||||
│ 62 buffer: TextBuffer; │█
|
||||
│ 63 onSubmit: (value: string) => void; │█
|
||||
│ Apply this change? │█
|
||||
│ │█
|
||||
│ ● 1. Allow once │█
|
||||
│ 2. Allow for this session │█
|
||||
│ 3. Allow for this file in all future sessions ~/.gemini/policies/auto-saved.toml │█
|
||||
│ 3. Allow for this file in all future sessions │█
|
||||
│ 4. Modify with external editor │█
|
||||
│ 5. No, suggest changes (esc) │█
|
||||
│ │█
|
||||
╰─────────────────────────────────────────────────────────────────────────────────────────────────╯█
|
||||
"
|
||||
`;
|
||||
|
||||
@@ -16,7 +16,6 @@ const createAnsiToken = (overrides: Partial<AnsiToken>): AnsiToken => ({
|
||||
underline: false,
|
||||
dim: false,
|
||||
inverse: false,
|
||||
isUninitialized: false,
|
||||
fg: '#ffffff',
|
||||
bg: '#000000',
|
||||
...overrides,
|
||||
|
||||
@@ -10,20 +10,15 @@ import {
|
||||
} from '../../test-utils/render.js';
|
||||
import type { LoadedSettings } from '../../config/settings.js';
|
||||
import { AppHeader } from './AppHeader.js';
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { makeFakeConfig } from '@google/gemini-cli-core';
|
||||
import crypto from 'node:crypto';
|
||||
import { _clearSessionBannersForTest } from '../hooks/useBanner.js';
|
||||
|
||||
vi.mock('../utils/terminalSetup.js', () => ({
|
||||
getTerminalProgram: () => null,
|
||||
}));
|
||||
|
||||
describe('<AppHeader />', () => {
|
||||
beforeEach(() => {
|
||||
_clearSessionBannersForTest();
|
||||
});
|
||||
|
||||
it('should render the banner with default text', async () => {
|
||||
const uiState = {
|
||||
history: [],
|
||||
|
||||
@@ -59,20 +59,13 @@ const NARROW_TERMINAL_BREAKPOINT = 60;
|
||||
export const AppHeader = ({ version, showDetails = true }: AppHeaderProps) => {
|
||||
const settings = useSettings();
|
||||
const config = useConfig();
|
||||
const {
|
||||
terminalWidth,
|
||||
bannerData,
|
||||
bannerVisible,
|
||||
updateInfo,
|
||||
isConfigInitialized,
|
||||
isAuthenticating,
|
||||
} = useUIState();
|
||||
const { terminalWidth, bannerData, bannerVisible, updateInfo } = useUIState();
|
||||
|
||||
const { bannerText } = useBanner(bannerData);
|
||||
const { showTips } = useTips();
|
||||
|
||||
const authType = config.getContentGeneratorConfig()?.authType;
|
||||
const loggedOut = isConfigInitialized && !isAuthenticating && !authType;
|
||||
const loggedOut = !authType;
|
||||
|
||||
const showHeader = !(
|
||||
settings.merged.ui.hideBanner || config.getScreenReader()
|
||||
|
||||
@@ -245,37 +245,20 @@ const createMockConfig = (overrides = {}): Config =>
|
||||
...overrides,
|
||||
}) as unknown as Config;
|
||||
|
||||
import { InputContext, type InputState } from '../contexts/InputContext.js';
|
||||
|
||||
const renderComposer = async (
|
||||
uiState: UIState,
|
||||
settings = createMockSettings({ ui: {} }),
|
||||
config = createMockConfig(),
|
||||
uiActions = createMockUIActions(),
|
||||
inputStateOverrides: Partial<InputState> = {},
|
||||
) => {
|
||||
const inputState = {
|
||||
buffer: { text: '' } as unknown as TextBuffer,
|
||||
userMessages: [],
|
||||
shellModeActive: false,
|
||||
showEscapePrompt: false,
|
||||
copyModeEnabled: false,
|
||||
inputWidth: 80,
|
||||
suggestionsWidth: 40,
|
||||
...(uiState as unknown as Partial<InputState>),
|
||||
...inputStateOverrides,
|
||||
};
|
||||
|
||||
const result = await render(
|
||||
<ConfigContext.Provider value={config as unknown as Config}>
|
||||
<SettingsContext.Provider value={settings as unknown as LoadedSettings}>
|
||||
<InputContext.Provider value={inputState}>
|
||||
<UIStateContext.Provider value={uiState}>
|
||||
<UIActionsContext.Provider value={uiActions}>
|
||||
<Composer isFocused={true} />
|
||||
</UIActionsContext.Provider>
|
||||
</UIStateContext.Provider>
|
||||
</InputContext.Provider>
|
||||
<UIStateContext.Provider value={uiState}>
|
||||
<UIActionsContext.Provider value={uiActions}>
|
||||
<Composer isFocused={true} />
|
||||
</UIActionsContext.Provider>
|
||||
</UIStateContext.Provider>
|
||||
</SettingsContext.Provider>
|
||||
</ConfigContext.Provider>,
|
||||
);
|
||||
@@ -558,6 +541,7 @@ describe('Composer', () => {
|
||||
const uiState = createMockUIState({
|
||||
ctrlCPressedOnce: false,
|
||||
ctrlDPressedOnce: false,
|
||||
showEscapePrompt: false,
|
||||
});
|
||||
|
||||
const { lastFrame } = await renderComposer(uiState);
|
||||
@@ -647,6 +631,7 @@ describe('Composer', () => {
|
||||
async (mode) => {
|
||||
const uiState = createMockUIState({
|
||||
showApprovalModeIndicator: mode,
|
||||
shellModeActive: false,
|
||||
});
|
||||
|
||||
const { lastFrame } = await renderComposer(uiState);
|
||||
@@ -656,15 +641,11 @@ describe('Composer', () => {
|
||||
);
|
||||
|
||||
it('shows ShellModeIndicator when shell mode is active', async () => {
|
||||
const uiState = createMockUIState();
|
||||
const uiState = createMockUIState({
|
||||
shellModeActive: true,
|
||||
});
|
||||
|
||||
const { lastFrame } = await renderComposer(
|
||||
uiState,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
{ shellModeActive: true },
|
||||
);
|
||||
const { lastFrame } = await renderComposer(uiState);
|
||||
|
||||
expect(lastFrame()).toMatch(/ShellModeIndic[\s\S]*tor/);
|
||||
});
|
||||
@@ -743,16 +724,11 @@ describe('Composer', () => {
|
||||
it('shows Esc rewind prompt in minimal mode without showing full UI', async () => {
|
||||
const uiState = createMockUIState({
|
||||
cleanUiDetailsVisible: false,
|
||||
showEscapePrompt: true,
|
||||
history: [{ id: 1, type: 'user', text: 'msg' }],
|
||||
});
|
||||
|
||||
const { lastFrame } = await renderComposer(
|
||||
uiState,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
{ showEscapePrompt: true },
|
||||
);
|
||||
const { lastFrame } = await renderComposer(uiState);
|
||||
const output = lastFrame();
|
||||
expect(output).toContain('Press Esc again to rewind.');
|
||||
expect(output).not.toContain('ContextSummaryDisplay');
|
||||
@@ -852,16 +828,11 @@ describe('Composer', () => {
|
||||
describe('Shortcuts Hint', () => {
|
||||
it('restores shortcuts hint after 200ms debounce when buffer is empty', async () => {
|
||||
const uiState = createMockUIState({
|
||||
buffer: { text: '' } as unknown as TextBuffer,
|
||||
cleanUiDetailsVisible: false,
|
||||
});
|
||||
|
||||
const { lastFrame } = await renderComposer(
|
||||
uiState,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
{ buffer: { text: '' } as unknown as TextBuffer },
|
||||
);
|
||||
const { lastFrame } = await renderComposer(uiState);
|
||||
|
||||
await act(async () => {
|
||||
await vi.advanceTimersByTimeAsync(250);
|
||||
@@ -874,16 +845,11 @@ describe('Composer', () => {
|
||||
|
||||
it('hides shortcuts hint when text is typed in buffer', async () => {
|
||||
const uiState = createMockUIState({
|
||||
buffer: { text: 'hello' } as unknown as TextBuffer,
|
||||
cleanUiDetailsVisible: false,
|
||||
});
|
||||
|
||||
const { lastFrame } = await renderComposer(
|
||||
uiState,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
{ buffer: { text: 'hello' } as unknown as TextBuffer },
|
||||
);
|
||||
const { lastFrame } = await renderComposer(uiState);
|
||||
|
||||
expect(lastFrame()).not.toContain('press tab twice for more');
|
||||
expect(lastFrame()).not.toContain('? for shortcuts');
|
||||
|
||||
@@ -9,7 +9,6 @@ import { useState, useEffect } from 'react';
|
||||
import { useConfig } from '../contexts/ConfigContext.js';
|
||||
import { useSettings } from '../contexts/SettingsContext.js';
|
||||
import { useUIState } from '../contexts/UIStateContext.js';
|
||||
import { useInputState } from '../contexts/InputContext.js';
|
||||
import { useUIActions } from '../contexts/UIActionsContext.js';
|
||||
import { useVimMode } from '../contexts/VimModeContext.js';
|
||||
import { useAlternateBuffer } from '../hooks/useAlternateBuffer.js';
|
||||
@@ -31,7 +30,6 @@ import { appEvents, AppEvent } from '../../utils/events.js';
|
||||
|
||||
export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
|
||||
const uiState = useUIState();
|
||||
const inputState = useInputState();
|
||||
const uiActions = useUIActions();
|
||||
const settings = useSettings();
|
||||
const config = useConfig();
|
||||
@@ -83,12 +81,12 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
|
||||
return null;
|
||||
}
|
||||
|
||||
const showToast = shouldShowToast(uiState, inputState);
|
||||
const hasToast = shouldShowToast(uiState);
|
||||
const hideUiDetailsForSuggestions =
|
||||
suggestionsVisible && suggestionsPosition === 'above';
|
||||
|
||||
// Mini Mode VIP Flags (Pure Content Triggers)
|
||||
const showMinimalToast = showToast;
|
||||
const showMinimalToast = hasToast;
|
||||
|
||||
return (
|
||||
<Box
|
||||
@@ -143,12 +141,17 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
|
||||
|
||||
{uiState.isInputActive && (
|
||||
<InputPrompt
|
||||
buffer={uiState.buffer}
|
||||
inputWidth={uiState.inputWidth}
|
||||
suggestionsWidth={uiState.suggestionsWidth}
|
||||
onSubmit={uiActions.handleFinalSubmit}
|
||||
userMessages={uiState.userMessages}
|
||||
setBannerVisible={uiActions.setBannerVisible}
|
||||
onClearScreen={uiActions.handleClearScreen}
|
||||
config={config}
|
||||
slashCommands={uiState.slashCommands || []}
|
||||
commandContext={uiState.commandContext}
|
||||
shellModeActive={uiState.shellModeActive}
|
||||
setShellModeActive={uiActions.setShellModeActive}
|
||||
approvalMode={uiState.showApprovalModeIndicator}
|
||||
onEscapePromptChange={uiActions.onEscapePromptChange}
|
||||
@@ -162,7 +165,7 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
|
||||
? vimMode === 'INSERT'
|
||||
? " Press 'Esc' for NORMAL mode."
|
||||
: " Press 'i' for INSERT mode."
|
||||
: inputState.shellModeActive
|
||||
: uiState.shellModeActive
|
||||
? ' Type your shell command'
|
||||
: ' Type your message or @path/to/file'
|
||||
}
|
||||
@@ -170,6 +173,7 @@ export const Composer = ({ isFocused = true }: { isFocused?: boolean }) => {
|
||||
streamingState={uiState.streamingState}
|
||||
suggestionsPosition={suggestionsPosition}
|
||||
onSuggestionsVisibilityChange={setSuggestionsVisible}
|
||||
copyModeEnabled={uiState.copyModeEnabled}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -8,8 +8,6 @@ import type React from 'react';
|
||||
import { Box, Text } from 'ink';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
import { type IdeContext, type MCPServerConfig } from '@google/gemini-cli-core';
|
||||
import { Command } from '../key/keyMatchers.js';
|
||||
import { formatCommand } from '../key/keybindingUtils.js';
|
||||
|
||||
interface ContextSummaryDisplayProps {
|
||||
geminiMdFileCount: number;
|
||||
@@ -51,7 +49,7 @@ export const ContextSummaryDisplay: React.FC<ContextSummaryDisplayProps> = ({
|
||||
}
|
||||
return `${openFileCount} open file${
|
||||
openFileCount > 1 ? 's' : ''
|
||||
} (${formatCommand(Command.SHOW_IDE_CONTEXT_DETAIL)} to view)`;
|
||||
} (ctrl+g to view)`;
|
||||
})();
|
||||
|
||||
const geminiMdText = (() => {
|
||||
|
||||
@@ -4,36 +4,34 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { render } from '../../test-utils/render.js';
|
||||
import { CopyModeWarning } from './CopyModeWarning.js';
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { renderWithProviders } from '../../test-utils/render.js';
|
||||
import { useInputState } from '../contexts/InputContext.js';
|
||||
import { useUIState, type UIState } from '../contexts/UIStateContext.js';
|
||||
|
||||
vi.mock('../contexts/InputContext.js');
|
||||
vi.mock('../contexts/UIStateContext.js');
|
||||
|
||||
describe('CopyModeWarning', () => {
|
||||
const mockUseUIState = vi.mocked(useUIState);
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('renders nothing when copy mode is disabled', async () => {
|
||||
vi.mocked(useInputState).mockReturnValue({
|
||||
mockUseUIState.mockReturnValue({
|
||||
copyModeEnabled: false,
|
||||
} as unknown as ReturnType<typeof useInputState>);
|
||||
const { lastFrame, unmount } = await renderWithProviders(
|
||||
<CopyModeWarning />,
|
||||
);
|
||||
} as unknown as UIState);
|
||||
const { lastFrame, unmount } = await render(<CopyModeWarning />);
|
||||
expect(lastFrame({ allowEmpty: true })).toBe('');
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('renders warning when copy mode is enabled', async () => {
|
||||
vi.mocked(useInputState).mockReturnValue({
|
||||
mockUseUIState.mockReturnValue({
|
||||
copyModeEnabled: true,
|
||||
} as unknown as ReturnType<typeof useInputState>);
|
||||
const { lastFrame, unmount } = await renderWithProviders(
|
||||
<CopyModeWarning />,
|
||||
);
|
||||
} as unknown as UIState);
|
||||
const { lastFrame, unmount } = await render(<CopyModeWarning />);
|
||||
expect(lastFrame()).toContain('In Copy Mode');
|
||||
expect(lastFrame()).toContain('Use Page Up/Down to scroll');
|
||||
expect(lastFrame()).toContain('Press Ctrl+S or any other key to exit');
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
|
||||
import type React from 'react';
|
||||
import { Box, Text } from 'ink';
|
||||
import { useInputState } from '../contexts/InputContext.js';
|
||||
import { useUIState } from '../contexts/UIStateContext.js';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
|
||||
export const CopyModeWarning: React.FC = () => {
|
||||
const { copyModeEnabled } = useInputState();
|
||||
const { copyModeEnabled } = useUIState();
|
||||
|
||||
return (
|
||||
<Box height={1}>
|
||||
|
||||
@@ -169,11 +169,6 @@ Implement a comprehensive authentication system with multiple providers.
|
||||
getUseTerminalBuffer: () => false,
|
||||
} as unknown as import('@google/gemini-cli-core').Config,
|
||||
settings: createMockSettings({ ui: { useAlternateBuffer } }),
|
||||
inputState: {
|
||||
buffer: { text: '' } as never,
|
||||
showEscapePrompt: false,
|
||||
shellModeActive: false,
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
@@ -477,11 +472,6 @@ Implement a comprehensive authentication system with multiple providers.
|
||||
settings: createMockSettings({
|
||||
ui: { useAlternateBuffer: useAlternateBuffer ?? true },
|
||||
}),
|
||||
inputState: {
|
||||
buffer: { text: '' } as never,
|
||||
showEscapePrompt: false,
|
||||
shellModeActive: false,
|
||||
},
|
||||
},
|
||||
),
|
||||
);
|
||||
@@ -587,7 +577,7 @@ Implement a comprehensive authentication system with multiple providers.
|
||||
expect(onFeedback).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('automatically submits feedback when Ctrl+G is used to edit the plan', async () => {
|
||||
it('automatically submits feedback when Ctrl+X is used to edit the plan', async () => {
|
||||
const { stdin, lastFrame } = await act(async () =>
|
||||
renderDialog({ useAlternateBuffer }),
|
||||
);
|
||||
@@ -600,9 +590,9 @@ Implement a comprehensive authentication system with multiple providers.
|
||||
expect(lastFrame()).toContain('Add user authentication');
|
||||
});
|
||||
|
||||
// Press Ctrl+G
|
||||
// Press Ctrl+X
|
||||
await act(async () => {
|
||||
writeKey(stdin, '\x07'); // Ctrl+G
|
||||
writeKey(stdin, '\x18'); // Ctrl+X
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
|
||||
@@ -25,11 +25,6 @@ import { useKeypress } from '../hooks/useKeypress.js';
|
||||
import { Command } from '../key/keyMatchers.js';
|
||||
import { formatCommand } from '../key/keybindingUtils.js';
|
||||
import { useKeyMatchers } from '../hooks/useKeyMatchers.js';
|
||||
import {
|
||||
appEvents,
|
||||
AppEvent,
|
||||
TransientMessageType,
|
||||
} from '../../utils/events.js';
|
||||
|
||||
export interface ExitPlanModeDialogProps {
|
||||
planPath: string;
|
||||
@@ -178,14 +173,6 @@ export const ExitPlanModeDialog: React.FC<ExitPlanModeDialogProps> = ({
|
||||
void handleOpenEditor();
|
||||
return true;
|
||||
}
|
||||
if (keyMatchers[Command.DEPRECATED_OPEN_EXTERNAL_EDITOR](key)) {
|
||||
const cmdKey = formatCommand(Command.OPEN_EXTERNAL_EDITOR);
|
||||
appEvents.emit(AppEvent.TransientMessage, {
|
||||
message: `Use ${cmdKey} to open the external editor.`,
|
||||
type: TransientMessageType.Hint,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
{ isActive: true, priority: true },
|
||||
|
||||
@@ -26,7 +26,6 @@ import { useUIState } from '../contexts/UIStateContext.js';
|
||||
import { useConfig } from '../contexts/ConfigContext.js';
|
||||
import { useSettings } from '../contexts/SettingsContext.js';
|
||||
import { useVimMode } from '../contexts/VimModeContext.js';
|
||||
import { useInputState } from '../contexts/InputContext.js';
|
||||
import {
|
||||
ALL_ITEMS,
|
||||
type FooterItemId,
|
||||
@@ -174,7 +173,6 @@ interface FooterColumn {
|
||||
|
||||
export const Footer: React.FC = () => {
|
||||
const uiState = useUIState();
|
||||
const { copyModeEnabled } = useInputState();
|
||||
const config = useConfig();
|
||||
const settings = useSettings();
|
||||
const { vimEnabled, vimMode } = useVimMode();
|
||||
@@ -367,7 +365,10 @@ export const Footer: React.FC = () => {
|
||||
id,
|
||||
header,
|
||||
() => (
|
||||
<MemoryUsageDisplay color={itemColor} isActive={!copyModeEnabled} />
|
||||
<MemoryUsageDisplay
|
||||
color={itemColor}
|
||||
isActive={!uiState.copyModeEnabled}
|
||||
/>
|
||||
),
|
||||
10,
|
||||
);
|
||||
|
||||
@@ -134,7 +134,6 @@ export const HistoryItemDisplay: React.FC<HistoryItemDisplayProps> = ({
|
||||
<InfoMessage
|
||||
text={itemForDisplay.text}
|
||||
secondaryText={itemForDisplay.secondaryText}
|
||||
source={itemForDisplay.source}
|
||||
icon={itemForDisplay.icon}
|
||||
color={itemForDisplay.color}
|
||||
marginBottom={itemForDisplay.marginBottom}
|
||||
|
||||
@@ -5,24 +5,13 @@
|
||||
*/
|
||||
|
||||
import type React from 'react';
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useState,
|
||||
useRef,
|
||||
useMemo,
|
||||
Fragment,
|
||||
} from 'react';
|
||||
import { useCallback, useEffect, useState, useRef, useMemo } from 'react';
|
||||
import clipboardy from 'clipboardy';
|
||||
import { Box, Text, useStdout, type DOMElement } from 'ink';
|
||||
import { SuggestionsDisplay, MAX_WIDTH } from './SuggestionsDisplay.js';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
import { useInputHistory } from '../hooks/useInputHistory.js';
|
||||
import { escapeAtSymbols } from '../hooks/atCommandProcessor.js';
|
||||
import {
|
||||
ScrollableList,
|
||||
type ScrollableListRef,
|
||||
} from './shared/ScrollableList.js';
|
||||
import { HalfLinePaddedBox } from './shared/HalfLinePaddedBox.js';
|
||||
import {
|
||||
type TextBuffer,
|
||||
@@ -77,7 +66,6 @@ import { getSafeLowColorBackground } from '../themes/color-utils.js';
|
||||
import { isLowColorDepth } from '../utils/terminalUtils.js';
|
||||
import { useShellFocusState } from '../contexts/ShellFocusContext.js';
|
||||
import { useUIState } from '../contexts/UIStateContext.js';
|
||||
import { useInputState } from '../contexts/InputContext.js';
|
||||
import {
|
||||
appEvents,
|
||||
AppEvent,
|
||||
@@ -107,18 +95,19 @@ export function isTerminalPasteTrusted(
|
||||
return kittyProtocolSupported;
|
||||
}
|
||||
|
||||
export type ScrollableItem =
|
||||
| { type: 'visualLine'; lineText: string; absoluteVisualIdx: number }
|
||||
| { type: 'ghostLine'; ghostLine: string; index: number };
|
||||
|
||||
export interface InputPromptProps {
|
||||
buffer: TextBuffer;
|
||||
onSubmit: (value: string) => void;
|
||||
userMessages: readonly string[];
|
||||
onClearScreen: () => void;
|
||||
config: Config;
|
||||
slashCommands: readonly SlashCommand[];
|
||||
commandContext: CommandContext;
|
||||
placeholder?: string;
|
||||
focus?: boolean;
|
||||
inputWidth: number;
|
||||
suggestionsWidth: number;
|
||||
shellModeActive: boolean;
|
||||
setShellModeActive: (value: boolean) => void;
|
||||
approvalMode: ApprovalMode;
|
||||
onEscapePromptChange?: (showPrompt: boolean) => void;
|
||||
@@ -131,6 +120,7 @@ export interface InputPromptProps {
|
||||
onQueueMessage?: (message: string) => void;
|
||||
suggestionsPosition?: 'above' | 'below';
|
||||
setBannerVisible: (visible: boolean) => void;
|
||||
copyModeEnabled?: boolean;
|
||||
}
|
||||
|
||||
// The input content, input container, and input suggestions list may have different widths
|
||||
@@ -201,13 +191,18 @@ export function tryTogglePasteExpansion(buffer: TextBuffer): boolean {
|
||||
}
|
||||
|
||||
export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
buffer,
|
||||
onSubmit,
|
||||
userMessages,
|
||||
onClearScreen,
|
||||
config,
|
||||
slashCommands,
|
||||
commandContext,
|
||||
placeholder = ' Type your message or @path/to/file',
|
||||
focus = true,
|
||||
inputWidth,
|
||||
suggestionsWidth,
|
||||
shellModeActive,
|
||||
setShellModeActive,
|
||||
approvalMode,
|
||||
onEscapePromptChange,
|
||||
@@ -220,16 +215,8 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
onQueueMessage,
|
||||
suggestionsPosition = 'below',
|
||||
setBannerVisible,
|
||||
copyModeEnabled = false,
|
||||
}) => {
|
||||
const inputState = useInputState();
|
||||
const {
|
||||
buffer,
|
||||
userMessages,
|
||||
shellModeActive,
|
||||
copyModeEnabled,
|
||||
inputWidth,
|
||||
suggestionsWidth,
|
||||
} = inputState;
|
||||
const isHelpDismissKey = useIsHelpDismissKey();
|
||||
const keyMatchers = useKeyMatchers();
|
||||
const { stdout } = useStdout();
|
||||
@@ -281,7 +268,6 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
const pasteTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const innerBoxRef = useRef<DOMElement>(null);
|
||||
const hasUserNavigatedSuggestions = useRef(false);
|
||||
const listRef = useRef<ScrollableListRef<ScrollableItem>>(null);
|
||||
|
||||
const [reverseSearchActive, setReverseSearchActive] = useState(false);
|
||||
const [commandSearchActive, setCommandSearchActive] = useState(false);
|
||||
@@ -439,7 +425,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
slashCommands,
|
||||
);
|
||||
if (commandToExecute?.isSafeConcurrent) {
|
||||
handleSubmitAndClear(trimmedMessage);
|
||||
inputHistory.handleSubmit(trimmedMessage);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -457,7 +443,6 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
streamingState,
|
||||
setQueueErrorMessage,
|
||||
slashCommands,
|
||||
handleSubmitAndClear,
|
||||
],
|
||||
);
|
||||
|
||||
@@ -571,10 +556,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
if (isEmbeddedShellFocused) {
|
||||
setEmbeddedShellFocused(false);
|
||||
}
|
||||
const currentScrollTop = Math.round(
|
||||
listRef.current?.getScrollState().scrollTop ?? buffer.visualScrollRow,
|
||||
);
|
||||
const visualRow = currentScrollTop + relY;
|
||||
const visualRow = buffer.visualScrollRow + relY;
|
||||
buffer.moveToVisualPosition(visualRow, relX);
|
||||
},
|
||||
{ isActive: focus },
|
||||
@@ -588,10 +570,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
(_event, relX, relY) => {
|
||||
if (!isAlternateBuffer) return;
|
||||
|
||||
const currentScrollTop = Math.round(
|
||||
listRef.current?.getScrollState().scrollTop ?? buffer.visualScrollRow,
|
||||
);
|
||||
const visualLine = buffer.allVisualLines[currentScrollTop + relY];
|
||||
const visualLine = buffer.viewportVisualLines[relY];
|
||||
if (!visualLine) return;
|
||||
|
||||
// Even if we click past the end of the line, we might want to collapse an expanded paste
|
||||
@@ -599,7 +578,10 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
|
||||
const logicalPos = isPastEndOfLine
|
||||
? null
|
||||
: buffer.getLogicalPositionFromVisual(currentScrollTop + relY, relX);
|
||||
: buffer.getLogicalPositionFromVisual(
|
||||
buffer.visualScrollRow + relY,
|
||||
relX,
|
||||
);
|
||||
|
||||
// Check for paste placeholder (collapsed state)
|
||||
if (logicalPos) {
|
||||
@@ -621,9 +603,7 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
|
||||
// If we didn't click a placeholder to expand, check if we are inside or after
|
||||
// an expanded paste region and collapse it.
|
||||
const visualRow = currentScrollTop + relY;
|
||||
const mapEntry = buffer.visualToLogicalMap[visualRow];
|
||||
const row = mapEntry ? mapEntry[0] : visualRow;
|
||||
const row = buffer.visualScrollRow + relY;
|
||||
const expandedId = buffer.getExpandedPasteAtLine(row);
|
||||
if (expandedId) {
|
||||
buffer.togglePasteExpansion(
|
||||
@@ -1272,15 +1252,6 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
return true;
|
||||
}
|
||||
|
||||
if (keyMatchers[Command.DEPRECATED_OPEN_EXTERNAL_EDITOR](key)) {
|
||||
const cmdKey = formatCommand(Command.OPEN_EXTERNAL_EDITOR);
|
||||
appEvents.emit(AppEvent.TransientMessage, {
|
||||
message: `Use ${cmdKey} to open the external editor.`,
|
||||
type: TransientMessageType.Hint,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ctrl+V for clipboard paste
|
||||
if (keyMatchers[Command.PASTE_CLIPBOARD](key)) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
@@ -1379,8 +1350,10 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
priority: true,
|
||||
});
|
||||
|
||||
const linesToRender = buffer.viewportVisualLines;
|
||||
const [cursorVisualRowAbsolute, cursorVisualColAbsolute] =
|
||||
buffer.visualCursor;
|
||||
const scrollVisualRow = buffer.visualScrollRow;
|
||||
|
||||
const getGhostTextLines = useCallback(() => {
|
||||
if (
|
||||
@@ -1495,155 +1468,6 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
|
||||
const { inlineGhost, additionalLines } = getGhostTextLines();
|
||||
|
||||
const scrollableData = useMemo(() => {
|
||||
const items: ScrollableItem[] = buffer.allVisualLines.map(
|
||||
(lineText, index) => ({
|
||||
type: 'visualLine',
|
||||
lineText,
|
||||
absoluteVisualIdx: index,
|
||||
}),
|
||||
);
|
||||
|
||||
additionalLines.forEach((ghostLine, index) => {
|
||||
items.push({
|
||||
type: 'ghostLine',
|
||||
ghostLine,
|
||||
index,
|
||||
});
|
||||
});
|
||||
|
||||
return items;
|
||||
}, [buffer.allVisualLines, additionalLines]);
|
||||
|
||||
const renderItem = useCallback(
|
||||
({ item }: { item: ScrollableItem; index: number }) => {
|
||||
if (item.type === 'ghostLine') {
|
||||
const padding = Math.max(0, inputWidth - stringWidth(item.ghostLine));
|
||||
return (
|
||||
<Box height={1}>
|
||||
<Text color={theme.text.secondary}>
|
||||
{item.ghostLine}
|
||||
{' '.repeat(padding)}
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
const { lineText, absoluteVisualIdx } = item;
|
||||
// console.log('renderItem called with:', lineText);
|
||||
const mapEntry = buffer.visualToLogicalMap[absoluteVisualIdx];
|
||||
if (!mapEntry) return <Text> </Text>;
|
||||
|
||||
const isOnCursorLine =
|
||||
focus && absoluteVisualIdx === cursorVisualRowAbsolute;
|
||||
const renderedLine: React.ReactNode[] = [];
|
||||
const [logicalLineIdx] = mapEntry;
|
||||
const logicalLine = buffer.lines[logicalLineIdx] || '';
|
||||
const transformations =
|
||||
buffer.transformationsByLine[logicalLineIdx] ?? [];
|
||||
const tokens = parseInputForHighlighting(
|
||||
logicalLine,
|
||||
logicalLineIdx,
|
||||
transformations,
|
||||
...(focus && buffer.cursor[0] === logicalLineIdx
|
||||
? [buffer.cursor[1]]
|
||||
: []),
|
||||
);
|
||||
const visualStartCol =
|
||||
buffer.visualToTransformedMap[absoluteVisualIdx] ?? 0;
|
||||
const visualEndCol = visualStartCol + cpLen(lineText);
|
||||
const segments = parseSegmentsFromTokens(
|
||||
tokens,
|
||||
visualStartCol,
|
||||
visualEndCol,
|
||||
);
|
||||
let charCount = 0;
|
||||
segments.forEach((seg, segIdx) => {
|
||||
const segLen = cpLen(seg.text);
|
||||
let display = seg.text;
|
||||
if (isOnCursorLine) {
|
||||
const relCol = cursorVisualColAbsolute;
|
||||
const segStart = charCount;
|
||||
const segEnd = segStart + segLen;
|
||||
if (relCol >= segStart && relCol < segEnd) {
|
||||
const charToHighlight = cpSlice(
|
||||
display,
|
||||
relCol - segStart,
|
||||
relCol - segStart + 1,
|
||||
);
|
||||
const highlighted = showCursor
|
||||
? chalk.inverse(charToHighlight)
|
||||
: charToHighlight;
|
||||
display =
|
||||
cpSlice(display, 0, relCol - segStart) +
|
||||
highlighted +
|
||||
cpSlice(display, relCol - segStart + 1);
|
||||
}
|
||||
charCount = segEnd;
|
||||
} else {
|
||||
charCount += segLen;
|
||||
}
|
||||
const color =
|
||||
seg.type === 'command' || seg.type === 'file' || seg.type === 'paste'
|
||||
? theme.text.accent
|
||||
: theme.text.primary;
|
||||
renderedLine.push(
|
||||
<Text key={`token-${segIdx}`} color={color}>
|
||||
{display}
|
||||
</Text>,
|
||||
);
|
||||
});
|
||||
|
||||
const currentLineGhost = isOnCursorLine ? inlineGhost : '';
|
||||
if (
|
||||
isOnCursorLine &&
|
||||
cursorVisualColAbsolute === cpLen(lineText) &&
|
||||
!currentLineGhost
|
||||
) {
|
||||
renderedLine.push(
|
||||
<Text key={`cursor-end-${cursorVisualColAbsolute}`}>
|
||||
{showCursor ? chalk.inverse(' ') : ' '}
|
||||
</Text>,
|
||||
);
|
||||
}
|
||||
const showCursorBeforeGhost =
|
||||
focus &&
|
||||
isOnCursorLine &&
|
||||
cursorVisualColAbsolute === cpLen(lineText) &&
|
||||
currentLineGhost;
|
||||
return (
|
||||
<Box height={1}>
|
||||
<Text
|
||||
terminalCursorFocus={showCursor && isOnCursorLine}
|
||||
terminalCursorPosition={cpIndexToOffset(
|
||||
lineText,
|
||||
cursorVisualColAbsolute,
|
||||
)}
|
||||
>
|
||||
{renderedLine}
|
||||
{showCursorBeforeGhost && (showCursor ? chalk.inverse(' ') : ' ')}
|
||||
{currentLineGhost && (
|
||||
<Text color={theme.text.secondary}>{currentLineGhost}</Text>
|
||||
)}
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
},
|
||||
[
|
||||
buffer.visualToLogicalMap,
|
||||
buffer.lines,
|
||||
buffer.transformationsByLine,
|
||||
buffer.cursor,
|
||||
buffer.visualToTransformedMap,
|
||||
focus,
|
||||
cursorVisualRowAbsolute,
|
||||
cursorVisualColAbsolute,
|
||||
showCursor,
|
||||
inlineGhost,
|
||||
inputWidth,
|
||||
],
|
||||
);
|
||||
|
||||
const useBackgroundColor = config.getUseBackgroundColor();
|
||||
const isLowColor = isLowColorDepth();
|
||||
const terminalBg = theme.background.primary || 'black';
|
||||
@@ -1661,46 +1485,6 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
return false;
|
||||
}, [useBackgroundColor, isLowColor, terminalBg]);
|
||||
|
||||
const prevCursorRef = useRef(buffer.visualCursor);
|
||||
const prevTextRef = useRef(buffer.text);
|
||||
|
||||
// Effect to ensure cursor remains visible after interactions
|
||||
useEffect(() => {
|
||||
const cursorChanged = prevCursorRef.current !== buffer.visualCursor;
|
||||
const textChanged = prevTextRef.current !== buffer.text;
|
||||
|
||||
prevCursorRef.current = buffer.visualCursor;
|
||||
prevTextRef.current = buffer.text;
|
||||
|
||||
if (!cursorChanged && !textChanged) return;
|
||||
|
||||
if (!listRef.current || !focus) return;
|
||||
const { scrollTop, innerHeight } = listRef.current.getScrollState();
|
||||
if (innerHeight === 0) return;
|
||||
|
||||
const cursorVisualRow = buffer.visualCursor[0];
|
||||
const actualScrollTop = Math.round(scrollTop);
|
||||
|
||||
// If cursor is out of the currently visible viewport...
|
||||
if (
|
||||
cursorVisualRow < actualScrollTop ||
|
||||
cursorVisualRow >= actualScrollTop + innerHeight
|
||||
) {
|
||||
// Calculate minimal scroll to make it visible
|
||||
let newScrollTop = actualScrollTop;
|
||||
if (cursorVisualRow < actualScrollTop) {
|
||||
newScrollTop = cursorVisualRow;
|
||||
} else if (cursorVisualRow >= actualScrollTop + innerHeight) {
|
||||
newScrollTop = cursorVisualRow - innerHeight + 1;
|
||||
}
|
||||
|
||||
listRef.current.scrollToIndex({ index: newScrollTop });
|
||||
}
|
||||
}, [buffer.visualCursor, buffer.text, focus]);
|
||||
|
||||
const listBackgroundColor =
|
||||
useLineFallback || !useBackgroundColor ? undefined : theme.background.input;
|
||||
|
||||
useEffect(() => {
|
||||
if (onSuggestionsVisibilityChange) {
|
||||
onSuggestionsVisibilityChange(shouldShowSuggestions);
|
||||
@@ -1831,51 +1615,153 @@ export const InputPrompt: React.FC<InputPromptProps> = ({
|
||||
<Text color={theme.text.secondary}>{placeholder}</Text>
|
||||
)
|
||||
) : (
|
||||
<Box
|
||||
flexDirection="column"
|
||||
height={Math.min(buffer.viewportHeight, scrollableData.length)}
|
||||
width="100%"
|
||||
>
|
||||
{isAlternateBuffer ? (
|
||||
<ScrollableList
|
||||
ref={listRef}
|
||||
hasFocus={focus}
|
||||
data={scrollableData}
|
||||
renderItem={renderItem}
|
||||
estimatedItemHeight={() => 1}
|
||||
fixedItemHeight={true}
|
||||
keyExtractor={(item) =>
|
||||
item.type === 'visualLine'
|
||||
? `line-${item.absoluteVisualIdx}`
|
||||
: `ghost-${item.index}`
|
||||
linesToRender
|
||||
.map((lineText: string, visualIdxInRenderedSet: number) => {
|
||||
const absoluteVisualIdx =
|
||||
scrollVisualRow + visualIdxInRenderedSet;
|
||||
const mapEntry = buffer.visualToLogicalMap[absoluteVisualIdx];
|
||||
if (!mapEntry) return null;
|
||||
|
||||
const cursorVisualRow =
|
||||
cursorVisualRowAbsolute - scrollVisualRow;
|
||||
const isOnCursorLine =
|
||||
focus && visualIdxInRenderedSet === cursorVisualRow;
|
||||
|
||||
const renderedLine: React.ReactNode[] = [];
|
||||
|
||||
const [logicalLineIdx] = mapEntry;
|
||||
const logicalLine = buffer.lines[logicalLineIdx] || '';
|
||||
const transformations =
|
||||
buffer.transformationsByLine[logicalLineIdx] ?? [];
|
||||
const tokens = parseInputForHighlighting(
|
||||
logicalLine,
|
||||
logicalLineIdx,
|
||||
transformations,
|
||||
...(focus && buffer.cursor[0] === logicalLineIdx
|
||||
? [buffer.cursor[1]]
|
||||
: []),
|
||||
);
|
||||
const startColInTransformed =
|
||||
buffer.visualToTransformedMap[absoluteVisualIdx] ?? 0;
|
||||
const visualStartCol = startColInTransformed;
|
||||
const visualEndCol = visualStartCol + cpLen(lineText);
|
||||
const segments = parseSegmentsFromTokens(
|
||||
tokens,
|
||||
visualStartCol,
|
||||
visualEndCol,
|
||||
);
|
||||
let charCount = 0;
|
||||
segments.forEach((seg, segIdx) => {
|
||||
const segLen = cpLen(seg.text);
|
||||
let display = seg.text;
|
||||
|
||||
if (isOnCursorLine) {
|
||||
const relativeVisualColForHighlight =
|
||||
cursorVisualColAbsolute;
|
||||
const segStart = charCount;
|
||||
const segEnd = segStart + segLen;
|
||||
if (
|
||||
relativeVisualColForHighlight >= segStart &&
|
||||
relativeVisualColForHighlight < segEnd
|
||||
) {
|
||||
const charToHighlight = cpSlice(
|
||||
display,
|
||||
relativeVisualColForHighlight - segStart,
|
||||
relativeVisualColForHighlight - segStart + 1,
|
||||
);
|
||||
const highlighted = showCursor
|
||||
? chalk.inverse(charToHighlight)
|
||||
: charToHighlight;
|
||||
display =
|
||||
cpSlice(
|
||||
display,
|
||||
0,
|
||||
relativeVisualColForHighlight - segStart,
|
||||
) +
|
||||
highlighted +
|
||||
cpSlice(
|
||||
display,
|
||||
relativeVisualColForHighlight - segStart + 1,
|
||||
);
|
||||
}
|
||||
charCount = segEnd;
|
||||
} else {
|
||||
// Advance the running counter even when not on cursor line
|
||||
charCount += segLen;
|
||||
}
|
||||
width={inputWidth}
|
||||
backgroundColor={listBackgroundColor}
|
||||
containerHeight={Math.min(
|
||||
buffer.viewportHeight,
|
||||
scrollableData.length,
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
scrollableData
|
||||
.slice(
|
||||
buffer.visualScrollRow,
|
||||
buffer.visualScrollRow + buffer.viewportHeight,
|
||||
)
|
||||
.map((item, index) => {
|
||||
const actualIndex = buffer.visualScrollRow + index;
|
||||
const key =
|
||||
item.type === 'visualLine'
|
||||
? `line-${item.absoluteVisualIdx}`
|
||||
: `ghost-${item.index}`;
|
||||
return (
|
||||
<Fragment key={key}>
|
||||
{renderItem({ item, index: actualIndex })}
|
||||
</Fragment>
|
||||
|
||||
const color =
|
||||
seg.type === 'command' ||
|
||||
seg.type === 'file' ||
|
||||
seg.type === 'paste'
|
||||
? theme.text.accent
|
||||
: theme.text.primary;
|
||||
|
||||
renderedLine.push(
|
||||
<Text key={`token-${segIdx}`} color={color}>
|
||||
{display}
|
||||
</Text>,
|
||||
);
|
||||
});
|
||||
|
||||
const currentLineGhost = isOnCursorLine ? inlineGhost : '';
|
||||
if (
|
||||
isOnCursorLine &&
|
||||
cursorVisualColAbsolute === cpLen(lineText)
|
||||
) {
|
||||
if (!currentLineGhost) {
|
||||
renderedLine.push(
|
||||
<Text key={`cursor-end-${cursorVisualColAbsolute}`}>
|
||||
{showCursor ? chalk.inverse(' ') : ' '}
|
||||
</Text>,
|
||||
);
|
||||
})
|
||||
)}
|
||||
</Box>
|
||||
}
|
||||
}
|
||||
|
||||
const showCursorBeforeGhost =
|
||||
focus &&
|
||||
isOnCursorLine &&
|
||||
cursorVisualColAbsolute === cpLen(lineText) &&
|
||||
currentLineGhost;
|
||||
|
||||
return (
|
||||
<Box key={`line-${visualIdxInRenderedSet}`} height={1}>
|
||||
<Text
|
||||
terminalCursorFocus={showCursor && isOnCursorLine}
|
||||
terminalCursorPosition={cpIndexToOffset(
|
||||
lineText,
|
||||
cursorVisualColAbsolute,
|
||||
)}
|
||||
>
|
||||
{renderedLine}
|
||||
{showCursorBeforeGhost &&
|
||||
(showCursor ? chalk.inverse(' ') : ' ')}
|
||||
{currentLineGhost && (
|
||||
<Text color={theme.text.secondary}>
|
||||
{currentLineGhost}
|
||||
</Text>
|
||||
)}
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
})
|
||||
.concat(
|
||||
additionalLines.map((ghostLine, index) => {
|
||||
const padding = Math.max(
|
||||
0,
|
||||
inputWidth - stringWidth(ghostLine),
|
||||
);
|
||||
return (
|
||||
<Text
|
||||
key={`ghost-line-${index}`}
|
||||
color={theme.text.secondary}
|
||||
>
|
||||
{ghostLine}
|
||||
{' '.repeat(padding)}
|
||||
</Text>
|
||||
);
|
||||
}),
|
||||
)
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
@@ -336,10 +336,6 @@ export const MainContent = () => {
|
||||
isAlternateBuffer,
|
||||
]);
|
||||
|
||||
if (!uiState.isConfigInitialized) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isAlternateBufferOrTerminalBuffer) {
|
||||
return scrollableList;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import { describe, it, expect, vi } from 'vitest';
|
||||
import { StatsDisplay } from './StatsDisplay.js';
|
||||
import * as SessionContext from '../contexts/SessionContext.js';
|
||||
import { type SessionMetrics } from '../contexts/SessionContext.js';
|
||||
import { ToolCallDecision, LlmRole } from '@google/gemini-cli-core';
|
||||
import { ToolCallDecision } from '@google/gemini-cli-core';
|
||||
|
||||
// Mock the context to provide controlled data for testing
|
||||
vi.mock('../contexts/SessionContext.js', async (importOriginal) => {
|
||||
@@ -131,66 +131,6 @@ describe('<StatsDisplay />', () => {
|
||||
expect(output).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders role breakdown correctly under models', async () => {
|
||||
const metrics = createTestMetrics({
|
||||
models: {
|
||||
'gemini-2.5-flash': {
|
||||
api: { totalRequests: 10, totalErrors: 0, totalLatencyMs: 10000 },
|
||||
tokens: {
|
||||
input: 1000,
|
||||
prompt: 1200,
|
||||
candidates: 2000,
|
||||
total: 3200,
|
||||
cached: 200,
|
||||
thoughts: 0,
|
||||
tool: 0,
|
||||
},
|
||||
roles: {
|
||||
[LlmRole.MAIN]: {
|
||||
totalRequests: 7,
|
||||
totalErrors: 0,
|
||||
totalLatencyMs: 7000,
|
||||
tokens: {
|
||||
input: 800,
|
||||
prompt: 900,
|
||||
candidates: 1500,
|
||||
total: 2400,
|
||||
cached: 100,
|
||||
thoughts: 0,
|
||||
tool: 0,
|
||||
},
|
||||
},
|
||||
[LlmRole.UTILITY_TOOL]: {
|
||||
totalRequests: 3,
|
||||
totalErrors: 0,
|
||||
totalLatencyMs: 3000,
|
||||
tokens: {
|
||||
input: 200,
|
||||
prompt: 300,
|
||||
candidates: 500,
|
||||
total: 800,
|
||||
cached: 100,
|
||||
thoughts: 0,
|
||||
tool: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { lastFrame } = await renderWithMockedStats(metrics);
|
||||
const output = lastFrame();
|
||||
|
||||
expect(output).toContain('gemini-2.5-flash');
|
||||
expect(output).toContain('10'); // Total requests
|
||||
expect(output).toContain('↳ main');
|
||||
expect(output).toContain('7'); // main requests
|
||||
expect(output).toContain('↳ utility_tool');
|
||||
expect(output).toContain('3'); // tool requests
|
||||
expect(output).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders all sections when all data is present', async () => {
|
||||
const metrics = createTestMetrics({
|
||||
models: {
|
||||
|
||||
@@ -12,7 +12,6 @@ import { formatDuration } from '../utils/formatters.js';
|
||||
import {
|
||||
useSessionStats,
|
||||
type ModelMetrics,
|
||||
type RoleMetrics,
|
||||
} from '../contexts/SessionContext.js';
|
||||
import {
|
||||
getStatusColor,
|
||||
@@ -24,7 +23,6 @@ import {
|
||||
import { computeSessionStats } from '../utils/computeStats.js';
|
||||
import { useSettings } from '../contexts/SettingsContext.js';
|
||||
import type { QuotaStats } from '../types.js';
|
||||
import { LlmRole } from '@google/gemini-cli-core';
|
||||
|
||||
// A more flexible and powerful StatRow component
|
||||
interface StatRowProps {
|
||||
@@ -79,16 +77,6 @@ interface ModelUsageTableProps {
|
||||
models: Record<string, ModelMetrics>;
|
||||
}
|
||||
|
||||
interface ModelRow {
|
||||
name: string;
|
||||
displayName: string;
|
||||
requests: number | string;
|
||||
cachedTokens: string;
|
||||
inputTokens: string;
|
||||
outputTokens: string;
|
||||
isSubRow: boolean;
|
||||
}
|
||||
|
||||
const ModelUsageTable: React.FC<ModelUsageTableProps> = ({ models }) => {
|
||||
const nameWidth = 28;
|
||||
const requestsWidth = 8;
|
||||
@@ -96,46 +84,6 @@ const ModelUsageTable: React.FC<ModelUsageTableProps> = ({ models }) => {
|
||||
const cacheReadsWidth = 14;
|
||||
const outputTokensWidth = 14;
|
||||
|
||||
const rows: ModelRow[] = [];
|
||||
|
||||
Object.entries(models).forEach(([name, metrics]) => {
|
||||
rows.push({
|
||||
name,
|
||||
displayName: name,
|
||||
requests: metrics.api.totalRequests,
|
||||
cachedTokens: metrics.tokens.cached.toLocaleString(),
|
||||
inputTokens: metrics.tokens.prompt.toLocaleString(),
|
||||
outputTokens: metrics.tokens.candidates.toLocaleString(),
|
||||
isSubRow: false,
|
||||
});
|
||||
|
||||
if (metrics.roles) {
|
||||
const roleEntries = Object.entries(metrics.roles).filter(
|
||||
(entry): entry is [string, RoleMetrics] =>
|
||||
entry[1] !== undefined && entry[1].totalRequests > 0,
|
||||
);
|
||||
|
||||
roleEntries.sort(([a], [b]) => {
|
||||
if (a === b) return 0;
|
||||
if (a === LlmRole.MAIN) return -1;
|
||||
if (b === LlmRole.MAIN) return 1;
|
||||
return a.localeCompare(b);
|
||||
});
|
||||
|
||||
roleEntries.forEach(([role, roleMetrics]) => {
|
||||
rows.push({
|
||||
name: `${name}-${role}`,
|
||||
displayName: ` ↳ ${role}`,
|
||||
requests: roleMetrics.totalRequests,
|
||||
cachedTokens: roleMetrics.tokens.cached.toLocaleString(),
|
||||
inputTokens: roleMetrics.tokens.prompt.toLocaleString(),
|
||||
outputTokens: roleMetrics.tokens.candidates.toLocaleString(),
|
||||
isSubRow: true,
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<Box flexDirection="column" marginTop={1}>
|
||||
<Text bold color={theme.text.primary}>
|
||||
@@ -183,42 +131,31 @@ const ModelUsageTable: React.FC<ModelUsageTableProps> = ({ models }) => {
|
||||
</Box>
|
||||
|
||||
{/* Rows */}
|
||||
{rows.map((row) => (
|
||||
<Box key={row.name}>
|
||||
{Object.entries(models).map(([name, modelMetrics]) => (
|
||||
<Box key={name}>
|
||||
<Box width={nameWidth}>
|
||||
<Text
|
||||
color={row.isSubRow ? theme.text.secondary : theme.text.primary}
|
||||
wrap="truncate-end"
|
||||
>
|
||||
{row.displayName}
|
||||
<Text color={theme.text.primary} wrap="truncate-end">
|
||||
{name}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box width={requestsWidth} justifyContent="flex-end">
|
||||
<Text
|
||||
color={row.isSubRow ? theme.text.secondary : theme.text.primary}
|
||||
>
|
||||
{row.requests}
|
||||
<Text color={theme.text.primary}>
|
||||
{modelMetrics.api.totalRequests}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box width={inputTokensWidth} justifyContent="flex-end">
|
||||
<Text
|
||||
color={row.isSubRow ? theme.text.secondary : theme.text.primary}
|
||||
>
|
||||
{row.inputTokens}
|
||||
<Text color={theme.text.primary}>
|
||||
{modelMetrics.tokens.prompt.toLocaleString()}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box width={cacheReadsWidth} justifyContent="flex-end">
|
||||
<Text
|
||||
color={row.isSubRow ? theme.text.secondary : theme.text.primary}
|
||||
>
|
||||
{row.cachedTokens}
|
||||
<Text color={theme.text.primary}>
|
||||
{modelMetrics.tokens.cached.toLocaleString()}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box width={outputTokensWidth} justifyContent="flex-end">
|
||||
<Text
|
||||
color={row.isSubRow ? theme.text.secondary : theme.text.primary}
|
||||
>
|
||||
{row.outputTokens}
|
||||
<Text color={theme.text.primary}>
|
||||
{modelMetrics.tokens.candidates.toLocaleString()}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
@@ -9,7 +9,7 @@ import { StatusRow } from './StatusRow.js';
|
||||
import { describe, it, expect, vi, beforeEach, type Mock } from 'vitest';
|
||||
import { useComposerStatus } from '../hooks/useComposerStatus.js';
|
||||
import { type UIState } from '../contexts/UIStateContext.js';
|
||||
|
||||
import { type TextBuffer } from '../components/shared/text-buffer.js';
|
||||
import { type SessionStatsState } from '../contexts/SessionContext.js';
|
||||
import { type ThoughtSummary } from '../types.js';
|
||||
import { ApprovalMode } from '@google/gemini-cli-core';
|
||||
@@ -29,11 +29,13 @@ describe('<StatusRow />', () => {
|
||||
elapsedTime: 0,
|
||||
currentWittyPhrase: undefined,
|
||||
activeHooks: [],
|
||||
buffer: { text: '' } as unknown as TextBuffer,
|
||||
sessionStats: { lastPromptTokenCount: 0 } as unknown as SessionStatsState,
|
||||
shortcutsHelpVisible: false,
|
||||
contextFileNames: [],
|
||||
showApprovalModeIndicator: ApprovalMode.DEFAULT,
|
||||
allowPlanMode: false,
|
||||
shellModeActive: false,
|
||||
renderMarkdown: true,
|
||||
currentModel: 'gemini-3',
|
||||
};
|
||||
|
||||
@@ -153,8 +153,6 @@ export const StatusNode: React.FC<{
|
||||
);
|
||||
};
|
||||
|
||||
import { useInputState } from '../contexts/InputContext.js';
|
||||
|
||||
export const StatusRow: React.FC<StatusRowProps> = ({
|
||||
showUiDetails,
|
||||
isNarrow,
|
||||
@@ -164,7 +162,6 @@ export const StatusRow: React.FC<StatusRowProps> = ({
|
||||
hasPendingActionRequired,
|
||||
}) => {
|
||||
const uiState = useUIState();
|
||||
const inputState = useInputState();
|
||||
const settings = useSettings();
|
||||
const {
|
||||
isInteractiveShellWaiting,
|
||||
@@ -228,7 +225,7 @@ export const StatusRow: React.FC<StatusRowProps> = ({
|
||||
settings.merged.ui.showShortcutsHint &&
|
||||
!hideUiDetailsForSuggestions &&
|
||||
!hasPendingActionRequired &&
|
||||
inputState.buffer.text.length === 0
|
||||
uiState.buffer.text.length === 0
|
||||
) {
|
||||
return showUiDetails ? '? for shortcuts' : 'press tab twice for more';
|
||||
}
|
||||
@@ -331,7 +328,7 @@ export const StatusRow: React.FC<StatusRowProps> = ({
|
||||
) : isInteractiveShellWaiting ? (
|
||||
<Box width="100%" marginLeft={LAYOUT.INDICATOR_LEFT_MARGIN}>
|
||||
<Text color={theme.status.warning}>
|
||||
{INTERACTIVE_SHELL_WAITING_PHRASE}
|
||||
! Shell awaiting input (Tab to focus)
|
||||
</Text>
|
||||
</Box>
|
||||
) : (
|
||||
@@ -394,14 +391,13 @@ export const StatusRow: React.FC<StatusRowProps> = ({
|
||||
>
|
||||
{showUiDetails ? (
|
||||
<>
|
||||
{!hideUiDetailsForSuggestions &&
|
||||
!inputState.shellModeActive && (
|
||||
<ApprovalModeIndicator
|
||||
approvalMode={uiState.showApprovalModeIndicator}
|
||||
allowPlanMode={uiState.allowPlanMode}
|
||||
/>
|
||||
)}
|
||||
{inputState.shellModeActive && (
|
||||
{!hideUiDetailsForSuggestions && !uiState.shellModeActive && (
|
||||
<ApprovalModeIndicator
|
||||
approvalMode={uiState.showApprovalModeIndicator}
|
||||
allowPlanMode={uiState.allowPlanMode}
|
||||
/>
|
||||
)}
|
||||
{uiState.shellModeActive && (
|
||||
<Box marginLeft={LAYOUT.INDICATOR_LEFT_MARGIN}>
|
||||
<ShellModeIndicator />
|
||||
</Box>
|
||||
|
||||
@@ -9,24 +9,16 @@ import { renderWithProviders } from '../../test-utils/render.js';
|
||||
import { ToastDisplay, shouldShowToast } from './ToastDisplay.js';
|
||||
import { TransientMessageType } from '../../utils/events.js';
|
||||
import { type UIState } from '../contexts/UIStateContext.js';
|
||||
import { type InputState } from '../contexts/InputContext.js';
|
||||
import { type TextBuffer } from './shared/text-buffer.js';
|
||||
import { type HistoryItem } from '../types.js';
|
||||
|
||||
const renderToastDisplay = async (
|
||||
uiState: Partial<UIState> = {},
|
||||
inputState: Partial<InputState> = {},
|
||||
) =>
|
||||
const renderToastDisplay = async (uiState: Partial<UIState> = {}) =>
|
||||
renderWithProviders(<ToastDisplay />, {
|
||||
uiState: {
|
||||
buffer: { text: '' } as TextBuffer,
|
||||
history: [] as HistoryItem[],
|
||||
...uiState,
|
||||
},
|
||||
inputState: {
|
||||
buffer: { text: '' } as TextBuffer,
|
||||
showEscapePrompt: false,
|
||||
...inputState,
|
||||
},
|
||||
});
|
||||
|
||||
describe('ToastDisplay', () => {
|
||||
@@ -35,121 +27,86 @@ describe('ToastDisplay', () => {
|
||||
});
|
||||
|
||||
describe('shouldShowToast', () => {
|
||||
const baseUIState: Partial<UIState> = {
|
||||
const baseState: Partial<UIState> = {
|
||||
ctrlCPressedOnce: false,
|
||||
transientMessage: null,
|
||||
ctrlDPressedOnce: false,
|
||||
showEscapePrompt: false,
|
||||
buffer: { text: '' } as TextBuffer,
|
||||
history: [] as HistoryItem[],
|
||||
queueErrorMessage: null,
|
||||
showIsExpandableHint: false,
|
||||
};
|
||||
|
||||
const baseInputState: Partial<InputState> = {
|
||||
showEscapePrompt: false,
|
||||
buffer: { text: '' } as TextBuffer,
|
||||
};
|
||||
|
||||
it('returns false for default state', () => {
|
||||
expect(
|
||||
shouldShowToast(baseUIState as UIState, baseInputState as InputState),
|
||||
).toBe(false);
|
||||
expect(shouldShowToast(baseState as UIState)).toBe(false);
|
||||
});
|
||||
|
||||
it('returns true when showIsExpandableHint is true', () => {
|
||||
expect(
|
||||
shouldShowToast(
|
||||
{
|
||||
...baseUIState,
|
||||
showIsExpandableHint: true,
|
||||
} as UIState,
|
||||
baseInputState as InputState,
|
||||
),
|
||||
shouldShowToast({
|
||||
...baseState,
|
||||
showIsExpandableHint: true,
|
||||
} as UIState),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('returns true when ctrlCPressedOnce is true', () => {
|
||||
expect(
|
||||
shouldShowToast(
|
||||
{ ...baseUIState, ctrlCPressedOnce: true } as UIState,
|
||||
baseInputState as InputState,
|
||||
),
|
||||
shouldShowToast({ ...baseState, ctrlCPressedOnce: true } as UIState),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('returns true when transientMessage is present', () => {
|
||||
expect(
|
||||
shouldShowToast(
|
||||
{
|
||||
...baseUIState,
|
||||
transientMessage: { text: 'test', type: TransientMessageType.Hint },
|
||||
} as UIState,
|
||||
baseInputState as InputState,
|
||||
),
|
||||
shouldShowToast({
|
||||
...baseState,
|
||||
transientMessage: { text: 'test', type: TransientMessageType.Hint },
|
||||
} as UIState),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('returns true when ctrlDPressedOnce is true', () => {
|
||||
expect(
|
||||
shouldShowToast(
|
||||
{ ...baseUIState, ctrlDPressedOnce: true } as UIState,
|
||||
baseInputState as InputState,
|
||||
),
|
||||
shouldShowToast({ ...baseState, ctrlDPressedOnce: true } as UIState),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('returns true when showEscapePrompt is true and buffer is NOT empty', () => {
|
||||
expect(
|
||||
shouldShowToast(
|
||||
{
|
||||
...baseUIState,
|
||||
} as UIState,
|
||||
{
|
||||
...baseInputState,
|
||||
showEscapePrompt: true,
|
||||
buffer: { text: 'some text' } as TextBuffer,
|
||||
} as InputState,
|
||||
),
|
||||
shouldShowToast({
|
||||
...baseState,
|
||||
showEscapePrompt: true,
|
||||
buffer: { text: 'some text' } as TextBuffer,
|
||||
} as UIState),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('returns true when showEscapePrompt is true and history is NOT empty', () => {
|
||||
expect(
|
||||
shouldShowToast(
|
||||
{
|
||||
...baseUIState,
|
||||
history: [{ id: '1' } as unknown as HistoryItem],
|
||||
} as UIState,
|
||||
{
|
||||
...baseInputState,
|
||||
showEscapePrompt: true,
|
||||
} as InputState,
|
||||
),
|
||||
shouldShowToast({
|
||||
...baseState,
|
||||
showEscapePrompt: true,
|
||||
history: [{ id: '1' } as unknown as HistoryItem],
|
||||
} as UIState),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false when showEscapePrompt is true but buffer and history are empty', () => {
|
||||
expect(
|
||||
shouldShowToast(
|
||||
{
|
||||
...baseUIState,
|
||||
} as UIState,
|
||||
{
|
||||
...baseInputState,
|
||||
showEscapePrompt: true,
|
||||
} as InputState,
|
||||
),
|
||||
shouldShowToast({
|
||||
...baseState,
|
||||
showEscapePrompt: true,
|
||||
} as UIState),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('returns true when queueErrorMessage is present', () => {
|
||||
expect(
|
||||
shouldShowToast(
|
||||
{
|
||||
...baseUIState,
|
||||
queueErrorMessage: 'error',
|
||||
} as UIState,
|
||||
baseInputState as InputState,
|
||||
),
|
||||
shouldShowToast({
|
||||
...baseState,
|
||||
queueErrorMessage: 'error',
|
||||
} as UIState),
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -194,25 +151,18 @@ describe('ToastDisplay', () => {
|
||||
});
|
||||
|
||||
it('renders Escape prompt when buffer is empty', async () => {
|
||||
const { lastFrame } = await renderToastDisplay(
|
||||
{
|
||||
history: [{ id: 1, type: 'user', text: 'test' }] as HistoryItem[],
|
||||
},
|
||||
{
|
||||
showEscapePrompt: true,
|
||||
},
|
||||
);
|
||||
const { lastFrame } = await renderToastDisplay({
|
||||
showEscapePrompt: true,
|
||||
history: [{ id: 1, type: 'user', text: 'test' }] as HistoryItem[],
|
||||
});
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders Escape prompt when buffer is NOT empty', async () => {
|
||||
const { lastFrame } = await renderToastDisplay(
|
||||
{},
|
||||
{
|
||||
showEscapePrompt: true,
|
||||
buffer: { text: 'some text' } as TextBuffer,
|
||||
},
|
||||
);
|
||||
const { lastFrame } = await renderToastDisplay({
|
||||
showEscapePrompt: true,
|
||||
buffer: { text: 'some text' } as TextBuffer,
|
||||
});
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
|
||||
@@ -8,19 +8,15 @@ import type React from 'react';
|
||||
import { Text } from 'ink';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
import { useUIState, type UIState } from '../contexts/UIStateContext.js';
|
||||
import { useInputState, type InputState } from '../contexts/InputContext.js';
|
||||
import { TransientMessageType } from '../../utils/events.js';
|
||||
|
||||
export function shouldShowToast(
|
||||
uiState: UIState,
|
||||
inputState: InputState,
|
||||
): boolean {
|
||||
export function shouldShowToast(uiState: UIState): boolean {
|
||||
return (
|
||||
uiState.ctrlCPressedOnce ||
|
||||
Boolean(uiState.transientMessage) ||
|
||||
uiState.ctrlDPressedOnce ||
|
||||
(inputState.showEscapePrompt &&
|
||||
(inputState.buffer.text.length > 0 || uiState.history.length > 0)) ||
|
||||
(uiState.showEscapePrompt &&
|
||||
(uiState.buffer.text.length > 0 || uiState.history.length > 0)) ||
|
||||
Boolean(uiState.queueErrorMessage) ||
|
||||
uiState.showIsExpandableHint
|
||||
);
|
||||
@@ -28,7 +24,6 @@ export function shouldShowToast(
|
||||
|
||||
export const ToastDisplay: React.FC = () => {
|
||||
const uiState = useUIState();
|
||||
const inputState = useInputState();
|
||||
|
||||
if (uiState.ctrlCPressedOnce) {
|
||||
return (
|
||||
@@ -51,8 +46,8 @@ export const ToastDisplay: React.FC = () => {
|
||||
);
|
||||
}
|
||||
|
||||
if (inputState.showEscapePrompt) {
|
||||
const isPromptEmpty = inputState.buffer.text.length === 0;
|
||||
if (uiState.showEscapePrompt) {
|
||||
const isPromptEmpty = uiState.buffer.text.length === 0;
|
||||
const hasHistory = uiState.history.length > 0;
|
||||
|
||||
if (isPromptEmpty && !hasHistory) {
|
||||
|
||||
@@ -70,7 +70,7 @@ describe('ToolConfirmationQueue', () => {
|
||||
const confirmingTool = {
|
||||
tool: {
|
||||
callId: 'call-1',
|
||||
name: 'run_shell_command',
|
||||
name: 'ls',
|
||||
description: 'list files',
|
||||
status: CoreToolCallStatus.AwaitingApproval,
|
||||
confirmationDetails: {
|
||||
@@ -98,12 +98,15 @@ describe('ToolConfirmationQueue', () => {
|
||||
);
|
||||
|
||||
const output = lastFrame();
|
||||
expect(output).toContain('Action Required');
|
||||
expect(output).toContain('1 of 3');
|
||||
expect(output).toContain('ls'); // Tool name
|
||||
expect(output).toContain('list files'); // Tool description
|
||||
expect(output).toContain('Allow execution of [ls]?');
|
||||
expect(output).toContain("Allow execution of: 'ls'?");
|
||||
expect(output).toMatchSnapshot();
|
||||
|
||||
const stickyHeaderProps = vi.mocked(StickyHeader).mock.calls[0][0];
|
||||
expect(stickyHeaderProps.borderColor).toBe(theme.status.warning);
|
||||
unmount();
|
||||
});
|
||||
|
||||
@@ -180,7 +183,7 @@ describe('ToolConfirmationQueue', () => {
|
||||
// availableContentHeight = Math.max(9 - 6, 4) = 4
|
||||
// MaxSizedBox in ToolConfirmationMessage will use 4
|
||||
// It should show truncation message
|
||||
await waitFor(() => expect(lastFrame()).toContain('48 hidden (Ctrl+O)'));
|
||||
await waitFor(() => expect(lastFrame()).toContain('49 hidden (Ctrl+O)'));
|
||||
expect(lastFrame()).toMatchSnapshot();
|
||||
unmount();
|
||||
});
|
||||
|
||||
@@ -9,11 +9,7 @@ import { Box, Text } from 'ink';
|
||||
import { theme } from '../semantic-colors.js';
|
||||
import { useConfig } from '../contexts/ConfigContext.js';
|
||||
import { ToolConfirmationMessage } from './messages/ToolConfirmationMessage.js';
|
||||
import {
|
||||
isShellTool,
|
||||
ToolStatusIndicator,
|
||||
ToolInfo,
|
||||
} from './messages/ToolShared.js';
|
||||
import { ToolStatusIndicator, ToolInfo } from './messages/ToolShared.js';
|
||||
import { useUIState } from '../contexts/UIStateContext.js';
|
||||
import type { ConfirmingToolState } from '../hooks/useConfirmingTool.js';
|
||||
import { StickyHeader } from './StickyHeader.js';
|
||||
@@ -35,16 +31,6 @@ function getConfirmationHeader(
|
||||
return headers[details.type] ?? 'Action Required';
|
||||
}
|
||||
|
||||
function getConfirmationLabel(
|
||||
toolName: string,
|
||||
details: SerializableConfirmationDetails | undefined,
|
||||
): string {
|
||||
if (details?.type === 'ask_user') return 'Questions';
|
||||
if (details?.type === 'exit_plan_mode') return 'Implementation';
|
||||
if (isShellTool(toolName)) return 'Shell';
|
||||
return toolName;
|
||||
}
|
||||
|
||||
interface ToolConfirmationQueueProps {
|
||||
confirmingTool: ConfirmingToolState;
|
||||
}
|
||||
@@ -72,78 +58,22 @@ export const ToolConfirmationQueue: React.FC<ToolConfirmationQueueProps> = ({
|
||||
? Math.max(uiAvailableHeight, 4)
|
||||
: Math.floor(terminalHeight * 0.5);
|
||||
|
||||
const isShell = isShellTool(tool.name);
|
||||
const isEdit = tool.confirmationDetails?.type === 'edit';
|
||||
|
||||
if (isShell || isEdit) {
|
||||
// Use the new simplified layout for Shell and Edit tools
|
||||
const borderColor = theme.border.default;
|
||||
const availableContentHeight = constrainHeight
|
||||
? Math.max(maxHeight - 3, 4)
|
||||
: undefined;
|
||||
|
||||
const toolLabel = getConfirmationLabel(tool.name, tool.confirmationDetails);
|
||||
|
||||
return (
|
||||
<Box
|
||||
flexDirection="column"
|
||||
width={mainAreaWidth}
|
||||
flexShrink={0}
|
||||
borderStyle="round"
|
||||
borderColor={borderColor}
|
||||
paddingX={1}
|
||||
>
|
||||
{/* Header Line */}
|
||||
<Box justifyContent="space-between" marginBottom={0}>
|
||||
<Box flexDirection="row" flexShrink={1} overflow="hidden">
|
||||
<Text color={theme.status.warning} bold>
|
||||
? {toolLabel}
|
||||
{!isEdit && !!tool.description && ' '}
|
||||
</Text>
|
||||
{!isEdit && !!tool.description && (
|
||||
<Box flexShrink={1} overflow="hidden">
|
||||
<Text color={theme.text.primary} wrap="truncate-end">
|
||||
{tool.description}
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
{total > 1 && (
|
||||
<Text color={theme.text.secondary}>
|
||||
{index} of {total}
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* Interactive Area */}
|
||||
<Box flexDirection="column">
|
||||
<ToolConfirmationMessage
|
||||
callId={tool.callId}
|
||||
confirmationDetails={tool.confirmationDetails}
|
||||
config={config}
|
||||
getPreferredEditor={getPreferredEditor}
|
||||
terminalWidth={mainAreaWidth - 4} // Adjust for parent border/padding
|
||||
availableTerminalHeight={availableContentHeight}
|
||||
toolName={tool.name}
|
||||
isFocused={true}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
// Restore original logic for other tools
|
||||
const isRoutine =
|
||||
tool.confirmationDetails?.type === 'ask_user' ||
|
||||
tool.confirmationDetails?.type === 'exit_plan_mode';
|
||||
const borderColor = isRoutine ? theme.status.success : theme.status.warning;
|
||||
const hideToolIdentity = isRoutine;
|
||||
|
||||
// ToolConfirmationMessage needs to know the height available for its OWN content.
|
||||
// We subtract the lines used by the Queue wrapper:
|
||||
// - 2 lines for the rounded border
|
||||
// - 2 lines for the Header (text + margin)
|
||||
// - 2 lines for Tool Identity (text + margin)
|
||||
const availableContentHeight = constrainHeight
|
||||
? Math.max(maxHeight - (hideToolIdentity ? 4 : 6), 4)
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
const content = (
|
||||
<Box flexDirection="column" width={mainAreaWidth} flexShrink={0}>
|
||||
<StickyHeader
|
||||
width={mainAreaWidth}
|
||||
@@ -192,6 +122,11 @@ export const ToolConfirmationQueue: React.FC<ToolConfirmationQueueProps> = ({
|
||||
paddingX={1}
|
||||
flexDirection="column"
|
||||
>
|
||||
{/* Interactive Area */}
|
||||
{/*
|
||||
Note: We force isFocused={true} because if this component is rendered,
|
||||
it effectively acts as a modal over the shell/composer.
|
||||
*/}
|
||||
<ToolConfirmationMessage
|
||||
callId={tool.callId}
|
||||
confirmationDetails={tool.confirmationDetails}
|
||||
@@ -199,7 +134,6 @@ export const ToolConfirmationQueue: React.FC<ToolConfirmationQueueProps> = ({
|
||||
getPreferredEditor={getPreferredEditor}
|
||||
terminalWidth={mainAreaWidth - 4} // Adjust for parent border/padding
|
||||
availableTerminalHeight={availableContentHeight}
|
||||
toolName={tool.name}
|
||||
isFocused={true}
|
||||
/>
|
||||
</Box>
|
||||
@@ -215,4 +149,6 @@ export const ToolConfirmationQueue: React.FC<ToolConfirmationQueueProps> = ({
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
|
||||
return content;
|
||||
};
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`<ContextSummaryDisplay /> > should not render empty parts 1`] = `
|
||||
" 1 open file (F4 to view)
|
||||
" 1 open file (ctrl+g to view)
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`<ContextSummaryDisplay /> > should render on a single line on a wide screen 1`] = `
|
||||
" 1 open file (F4 to view) · 1 GEMINI.md file · 1 MCP server · 1 skill
|
||||
" 1 open file (ctrl+g to view) · 1 GEMINI.md file · 1 MCP server · 1 skill
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`<ContextSummaryDisplay /> > should render on multiple lines on a narrow screen 1`] = `
|
||||
" 1 open file (F4 to view) · 1 GEMINI.md file · 1 MCP server · 1 skill
|
||||
" 1 open file (ctrl+g to view) · 1 GEMINI.md file · 1 MCP server · 1 skill
|
||||
"
|
||||
`;
|
||||
|
||||
@@ -23,7 +23,7 @@ Files to Modify
|
||||
Approves plan but requires confirmation for each tool
|
||||
3. Type your feedback...
|
||||
|
||||
Enter to select · ↑/↓ to navigate · Ctrl+G to edit plan · Esc to cancel
|
||||
Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
|
||||
"
|
||||
`;
|
||||
|
||||
@@ -50,7 +50,7 @@ Files to Modify
|
||||
Approves plan but requires confirmation for each tool
|
||||
3. Type your feedback...
|
||||
|
||||
Enter to select · ↑/↓ to navigate · Ctrl+G to edit plan · Esc to cancel
|
||||
Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
|
||||
"
|
||||
`;
|
||||
|
||||
@@ -82,7 +82,7 @@ Implementation Steps
|
||||
Approves plan but requires confirmation for each tool
|
||||
3. Type your feedback...
|
||||
|
||||
Enter to select · ↑/↓ to navigate · Ctrl+G to edit plan · Esc to cancel
|
||||
Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
|
||||
"
|
||||
`;
|
||||
|
||||
@@ -109,7 +109,7 @@ Files to Modify
|
||||
Approves plan but requires confirmation for each tool
|
||||
3. Type your feedback...
|
||||
|
||||
Enter to select · ↑/↓ to navigate · Ctrl+G to edit plan · Esc to cancel
|
||||
Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
|
||||
"
|
||||
`;
|
||||
|
||||
@@ -136,7 +136,7 @@ Files to Modify
|
||||
Approves plan but requires confirmation for each tool
|
||||
3. Type your feedback...
|
||||
|
||||
Enter to select · ↑/↓ to navigate · Ctrl+G to edit plan · Esc to cancel
|
||||
Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
|
||||
"
|
||||
`;
|
||||
|
||||
@@ -163,7 +163,7 @@ Files to Modify
|
||||
Approves plan but requires confirmation for each tool
|
||||
3. Type your feedback...
|
||||
|
||||
Enter to select · ↑/↓ to navigate · Ctrl+G to edit plan · Esc to cancel
|
||||
Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
|
||||
"
|
||||
`;
|
||||
|
||||
@@ -216,7 +216,7 @@ Testing Strategy
|
||||
Approves plan but requires confirmation for each tool
|
||||
3. Type your feedback...
|
||||
|
||||
Enter to select · ↑/↓ to navigate · Ctrl+G to edit plan · Esc to cancel
|
||||
Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
|
||||
"
|
||||
`;
|
||||
|
||||
@@ -243,6 +243,6 @@ Files to Modify
|
||||
Approves plan but requires confirmation for each tool
|
||||
3. Type your feedback...
|
||||
|
||||
Enter to select · ↑/↓ to navigate · Ctrl+G to edit plan · Esc to cancel
|
||||
Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel
|
||||
"
|
||||
`;
|
||||
|
||||
@@ -112,7 +112,48 @@ exports[`<HistoryItemDisplay /> > gemini items (alternateBuffer=false) > should
|
||||
|
||||
exports[`<HistoryItemDisplay /> > gemini items (alternateBuffer=false) > should render a truncated gemini item 1`] = `
|
||||
"✦ Example code block:
|
||||
... 42 hidden (Ctrl+O) ...
|
||||
1 Line 1
|
||||
2 Line 2
|
||||
3 Line 3
|
||||
4 Line 4
|
||||
5 Line 5
|
||||
6 Line 6
|
||||
7 Line 7
|
||||
8 Line 8
|
||||
9 Line 9
|
||||
10 Line 10
|
||||
11 Line 11
|
||||
12 Line 12
|
||||
13 Line 13
|
||||
14 Line 14
|
||||
15 Line 15
|
||||
16 Line 16
|
||||
17 Line 17
|
||||
18 Line 18
|
||||
19 Line 19
|
||||
20 Line 20
|
||||
21 Line 21
|
||||
22 Line 22
|
||||
23 Line 23
|
||||
24 Line 24
|
||||
25 Line 25
|
||||
26 Line 26
|
||||
27 Line 27
|
||||
28 Line 28
|
||||
29 Line 29
|
||||
30 Line 30
|
||||
31 Line 31
|
||||
32 Line 32
|
||||
33 Line 33
|
||||
34 Line 34
|
||||
35 Line 35
|
||||
36 Line 36
|
||||
37 Line 37
|
||||
38 Line 38
|
||||
39 Line 39
|
||||
40 Line 40
|
||||
41 Line 41
|
||||
42 Line 42
|
||||
43 Line 43
|
||||
44 Line 44
|
||||
45 Line 45
|
||||
@@ -126,7 +167,48 @@ exports[`<HistoryItemDisplay /> > gemini items (alternateBuffer=false) > should
|
||||
|
||||
exports[`<HistoryItemDisplay /> > gemini items (alternateBuffer=false) > should render a truncated gemini_content item 1`] = `
|
||||
" Example code block:
|
||||
... 42 hidden (Ctrl+O) ...
|
||||
1 Line 1
|
||||
2 Line 2
|
||||
3 Line 3
|
||||
4 Line 4
|
||||
5 Line 5
|
||||
6 Line 6
|
||||
7 Line 7
|
||||
8 Line 8
|
||||
9 Line 9
|
||||
10 Line 10
|
||||
11 Line 11
|
||||
12 Line 12
|
||||
13 Line 13
|
||||
14 Line 14
|
||||
15 Line 15
|
||||
16 Line 16
|
||||
17 Line 17
|
||||
18 Line 18
|
||||
19 Line 19
|
||||
20 Line 20
|
||||
21 Line 21
|
||||
22 Line 22
|
||||
23 Line 23
|
||||
24 Line 24
|
||||
25 Line 25
|
||||
26 Line 26
|
||||
27 Line 27
|
||||
28 Line 28
|
||||
29 Line 29
|
||||
30 Line 30
|
||||
31 Line 31
|
||||
32 Line 32
|
||||
33 Line 33
|
||||
34 Line 34
|
||||
35 Line 35
|
||||
36 Line 36
|
||||
37 Line 37
|
||||
38 Line 38
|
||||
39 Line 39
|
||||
40 Line 40
|
||||
41 Line 41
|
||||
42 Line 42
|
||||
43 Line 43
|
||||
44 Line 44
|
||||
45 Line 45
|
||||
|
||||
@@ -12,7 +12,7 @@ exports[`ShortcutsHelp > renders correctly in 'narrow' mode on 'linux' 1`] = `
|
||||
Ctrl+V paste images
|
||||
Alt+M raw markdown mode
|
||||
Ctrl+R reverse-search history
|
||||
Ctrl+G open external editor
|
||||
Ctrl+X open external editor
|
||||
"
|
||||
`;
|
||||
|
||||
@@ -28,7 +28,7 @@ exports[`ShortcutsHelp > renders correctly in 'narrow' mode on 'mac' 1`] = `
|
||||
Ctrl+V paste images
|
||||
Option+M raw markdown mode
|
||||
Ctrl+R reverse-search history
|
||||
Ctrl+G open external editor
|
||||
Ctrl+X open external editor
|
||||
"
|
||||
`;
|
||||
|
||||
@@ -37,7 +37,7 @@ exports[`ShortcutsHelp > renders correctly in 'wide' mode on 'linux' 1`] = `
|
||||
Shortcuts See /help for more
|
||||
! shell mode Shift+Tab cycle mode Ctrl+V paste images
|
||||
@ select file or folder Ctrl+Y YOLO mode Alt+M raw markdown mode
|
||||
Double Esc clear & rewind Ctrl+R reverse-search history Ctrl+G open external editor
|
||||
Double Esc clear & rewind Ctrl+R reverse-search history Ctrl+X open external editor
|
||||
Tab focus UI
|
||||
"
|
||||
`;
|
||||
@@ -47,7 +47,7 @@ exports[`ShortcutsHelp > renders correctly in 'wide' mode on 'mac' 1`] = `
|
||||
Shortcuts See /help for more
|
||||
! shell mode Shift+Tab cycle mode Ctrl+V paste images
|
||||
@ select file or folder Ctrl+Y YOLO mode Option+M raw markdown mode
|
||||
Double Esc clear & rewind Ctrl+R reverse-search history Ctrl+G open external editor
|
||||
Double Esc clear & rewind Ctrl+R reverse-search history Ctrl+X open external editor
|
||||
Tab focus UI
|
||||
"
|
||||
`;
|
||||
|
||||
@@ -263,32 +263,3 @@ exports[`<StatsDisplay /> > renders only the Performance section in its zero sta
|
||||
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`<StatsDisplay /> > renders role breakdown correctly under models 1`] = `
|
||||
"╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ │
|
||||
│ Session Stats │
|
||||
│ │
|
||||
│ Interaction Summary │
|
||||
│ Session ID: test-session-id │
|
||||
│ Tool Calls: 0 ( ✓ 0 x 0 ) │
|
||||
│ Success Rate: 0.0% │
|
||||
│ │
|
||||
│ Performance │
|
||||
│ Wall Time: 1s │
|
||||
│ Agent Active: 10.0s │
|
||||
│ » API Time: 10.0s (100.0%) │
|
||||
│ » Tool Time: 0s (0.0%) │
|
||||
│ │
|
||||
│ │
|
||||
│ Model Usage │
|
||||
│ Use /model to view model quota information │
|
||||
│ │
|
||||
│ Model Reqs Input Tokens Cache Reads Output Tokens │
|
||||
│ ────────────────────────────────────────────────────────────────────────────────────────────── │
|
||||
│ gemini-2.5-flash 10 1,200 200 2,000 │
|
||||
│ ↳ main 7 900 100 1,500 │
|
||||
│ ↳ utility_tool 3 300 100 500 │
|
||||
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
"
|
||||
`;
|
||||
|
||||
@@ -1,113 +1,130 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="740" height="428" viewBox="0 0 740 428">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="740" height="598" viewBox="0 0 740 598">
|
||||
<style>
|
||||
text { font-family: Consolas, "Courier New", monospace; font-size: 14px; dominant-baseline: text-before-edge; white-space: pre; }
|
||||
</style>
|
||||
<rect width="740" height="428" fill="#000000" />
|
||||
<rect width="740" height="598" fill="#000000" />
|
||||
<g transform="translate(10, 10)">
|
||||
<text x="0" y="2" fill="#333333" textLength="720" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────╮</text>
|
||||
<text x="0" y="19" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="19" fill="#ffffaf" textLength="81" lengthAdjust="spacingAndGlyphs" font-weight="bold">? Shell </text>
|
||||
<text x="99" y="19" fill="#ffffff" textLength="396" lengthAdjust="spacingAndGlyphs">Executes a bash command with a deceptive URL</text>
|
||||
<text x="0" y="2" fill="#ffffaf" textLength="720" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────╮</text>
|
||||
<text x="0" y="19" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="19" fill="#ffffaf" textLength="135" lengthAdjust="spacingAndGlyphs" font-weight="bold">Action Required</text>
|
||||
<text x="648" y="19" fill="#afafaf" textLength="54" lengthAdjust="spacingAndGlyphs">3 of 3</text>
|
||||
<text x="711" y="19" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="36" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="36" fill="#afafaf" textLength="225" lengthAdjust="spacingAndGlyphs">... 6 hidden (Ctrl+O) ...</text>
|
||||
<text x="711" y="36" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="53" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="53" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="53" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="81" y="53" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 44"</text>
|
||||
<text x="693" y="53" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="53" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="70" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="81" y="70" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 45"</text>
|
||||
<text x="693" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="87" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="81" y="87" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 46"</text>
|
||||
<text x="693" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="104" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="81" y="104" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 47"</text>
|
||||
<text x="693" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="121" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="81" y="121" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 48"</text>
|
||||
<text x="693" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="138" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="81" y="138" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 49"</text>
|
||||
<text x="693" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="155" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="155" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="155" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="81" y="155" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 50"</text>
|
||||
<text x="693" y="155" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="155" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="172" fill="#e5e5e5" textLength="189" lengthAdjust="spacingAndGlyphs">curl https://täst.com</text>
|
||||
<text x="693" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="189" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="189" fill="#333333" textLength="684" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────╯</text>
|
||||
<text x="711" y="189" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="206" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="206" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="223" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="223" fill="#ffffaf" textLength="18" lengthAdjust="spacingAndGlyphs">⚠ </text>
|
||||
<text x="45" y="223" fill="#ffffaf" textLength="72" lengthAdjust="spacingAndGlyphs" font-weight="bold">Warning:</text>
|
||||
<text x="117" y="223" fill="#ffffaf" textLength="243" lengthAdjust="spacingAndGlyphs"> Deceptive URL(s) detected:</text>
|
||||
<text x="711" y="223" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="240" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="240" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="257" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="72" y="257" fill="#ffffaf" textLength="81" lengthAdjust="spacingAndGlyphs" font-weight="bold">Original:</text>
|
||||
<text x="162" y="257" fill="#87afff" textLength="153" lengthAdjust="spacingAndGlyphs">https://täst.com/</text>
|
||||
<text x="711" y="257" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="274" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="72" y="274" fill="#ffffaf" textLength="207" lengthAdjust="spacingAndGlyphs" font-weight="bold">Actual Host (Punycode):</text>
|
||||
<text x="288" y="274" fill="#87afff" textLength="216" lengthAdjust="spacingAndGlyphs">https://xn--tst-qla.com/</text>
|
||||
<text x="711" y="274" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="291" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="291" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="308" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="9" y="308" fill="#ffffff" textLength="180" lengthAdjust="spacingAndGlyphs"> Allow execution of </text>
|
||||
<text x="189" y="308" fill="#ffffaf" textLength="54" lengthAdjust="spacingAndGlyphs" font-weight="bold">[echo]</text>
|
||||
<text x="243" y="308" fill="#ffffff" textLength="468" lengthAdjust="spacingAndGlyphs">? </text>
|
||||
<text x="711" y="308" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="325" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="325" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="342" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="18" y="340" width="9" height="17" fill="#001a00" />
|
||||
<text x="18" y="342" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">●</text>
|
||||
<rect x="27" y="340" width="9" height="17" fill="#001a00" />
|
||||
<rect x="36" y="340" width="18" height="17" fill="#001a00" />
|
||||
<text x="36" y="342" fill="#00cd00" textLength="18" lengthAdjust="spacingAndGlyphs">1.</text>
|
||||
<rect x="54" y="340" width="9" height="17" fill="#001a00" />
|
||||
<rect x="63" y="340" width="90" height="17" fill="#001a00" />
|
||||
<text x="63" y="342" fill="#00cd00" textLength="90" lengthAdjust="spacingAndGlyphs">Allow once</text>
|
||||
<rect x="153" y="340" width="135" height="17" fill="#001a00" />
|
||||
<text x="711" y="342" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="359" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="359" fill="#ffffff" textLength="18" lengthAdjust="spacingAndGlyphs">2.</text>
|
||||
<text x="63" y="359" fill="#ffffff" textLength="198" lengthAdjust="spacingAndGlyphs">Allow for this session</text>
|
||||
<text x="711" y="359" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="376" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="376" fill="#ffffff" textLength="18" lengthAdjust="spacingAndGlyphs">3.</text>
|
||||
<text x="63" y="376" fill="#ffffff" textLength="225" lengthAdjust="spacingAndGlyphs">No, suggest changes (esc)</text>
|
||||
<text x="711" y="376" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="393" fill="#333333" textLength="720" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────╯</text>
|
||||
<text x="711" y="19" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="36" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="36" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="53" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="53" fill="#87afff" textLength="9" lengthAdjust="spacingAndGlyphs">?</text>
|
||||
<text x="45" y="53" fill="#ffffff" textLength="153" lengthAdjust="spacingAndGlyphs" font-weight="bold">run_shell_command</text>
|
||||
<text x="207" y="53" fill="#afafaf" textLength="396" lengthAdjust="spacingAndGlyphs">Executes a bash command with a deceptive URL</text>
|
||||
<text x="711" y="53" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="70" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="70" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="87" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="87" fill="#afafaf" textLength="225" lengthAdjust="spacingAndGlyphs">... 6 hidden (Ctrl+O) ...</text>
|
||||
<text x="711" y="87" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="104" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="104" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="63" y="104" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 37"</text>
|
||||
<text x="711" y="104" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="121" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="121" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="63" y="121" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 38"</text>
|
||||
<text x="711" y="121" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="138" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="138" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="63" y="138" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 39"</text>
|
||||
<text x="711" y="138" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="155" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="155" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="63" y="155" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 40"</text>
|
||||
<text x="711" y="155" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="172" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="172" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="63" y="172" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 41"</text>
|
||||
<text x="711" y="172" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="189" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="189" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="63" y="189" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 42"</text>
|
||||
<text x="711" y="189" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="206" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="206" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="63" y="206" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 43"</text>
|
||||
<text x="711" y="206" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="223" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="223" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="63" y="223" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 44"</text>
|
||||
<text x="711" y="223" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="240" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="240" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="63" y="240" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 45"</text>
|
||||
<text x="711" y="240" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="257" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="257" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="63" y="257" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 46"</text>
|
||||
<text x="711" y="257" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="274" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="274" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="63" y="274" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 47"</text>
|
||||
<text x="711" y="274" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="291" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="291" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="63" y="291" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 48"</text>
|
||||
<text x="711" y="291" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="308" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="308" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="63" y="308" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 49"</text>
|
||||
<text x="711" y="308" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="325" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="325" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="63" y="325" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 50"</text>
|
||||
<text x="711" y="325" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="342" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="342" fill="#e5e5e5" textLength="189" lengthAdjust="spacingAndGlyphs">curl https://täst.com</text>
|
||||
<text x="711" y="342" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="359" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="359" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="376" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="376" fill="#ffffaf" textLength="18" lengthAdjust="spacingAndGlyphs">⚠ </text>
|
||||
<text x="45" y="376" fill="#ffffaf" textLength="72" lengthAdjust="spacingAndGlyphs" font-weight="bold">Warning:</text>
|
||||
<text x="117" y="376" fill="#ffffaf" textLength="243" lengthAdjust="spacingAndGlyphs"> Deceptive URL(s) detected:</text>
|
||||
<text x="711" y="376" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="393" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="393" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="410" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="72" y="410" fill="#ffffaf" textLength="81" lengthAdjust="spacingAndGlyphs" font-weight="bold">Original:</text>
|
||||
<text x="162" y="410" fill="#87afff" textLength="153" lengthAdjust="spacingAndGlyphs">https://täst.com/</text>
|
||||
<text x="711" y="410" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="427" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="72" y="427" fill="#ffffaf" textLength="207" lengthAdjust="spacingAndGlyphs" font-weight="bold">Actual Host (Punycode):</text>
|
||||
<text x="288" y="427" fill="#87afff" textLength="216" lengthAdjust="spacingAndGlyphs">https://xn--tst-qla.com/</text>
|
||||
<text x="711" y="427" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="444" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="444" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="461" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="461" fill="#ffffff" textLength="243" lengthAdjust="spacingAndGlyphs">Allow execution of: 'echo'?</text>
|
||||
<text x="711" y="461" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="478" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="478" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="495" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="18" y="493" width="9" height="17" fill="#001a00" />
|
||||
<text x="18" y="495" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">●</text>
|
||||
<rect x="27" y="493" width="9" height="17" fill="#001a00" />
|
||||
<rect x="36" y="493" width="18" height="17" fill="#001a00" />
|
||||
<text x="36" y="495" fill="#00cd00" textLength="18" lengthAdjust="spacingAndGlyphs">1.</text>
|
||||
<rect x="54" y="493" width="9" height="17" fill="#001a00" />
|
||||
<rect x="63" y="493" width="90" height="17" fill="#001a00" />
|
||||
<text x="63" y="495" fill="#00cd00" textLength="90" lengthAdjust="spacingAndGlyphs">Allow once</text>
|
||||
<rect x="153" y="493" width="135" height="17" fill="#001a00" />
|
||||
<text x="711" y="495" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="512" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="512" fill="#ffffff" textLength="18" lengthAdjust="spacingAndGlyphs">2.</text>
|
||||
<text x="63" y="512" fill="#ffffff" textLength="198" lengthAdjust="spacingAndGlyphs">Allow for this session</text>
|
||||
<text x="711" y="512" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="529" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="529" fill="#ffffff" textLength="18" lengthAdjust="spacingAndGlyphs">3.</text>
|
||||
<text x="63" y="529" fill="#ffffff" textLength="225" lengthAdjust="spacingAndGlyphs">No, suggest changes (esc)</text>
|
||||
<text x="711" y="529" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="546" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="546" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="563" fill="#ffffaf" textLength="720" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────╯</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 13 KiB |
@@ -4,540 +4,455 @@
|
||||
</style>
|
||||
<rect width="740" height="683" fill="#000000" />
|
||||
<g transform="translate(10, 10)">
|
||||
<text x="0" y="2" fill="#333333" textLength="720" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────╮</text>
|
||||
<text x="0" y="19" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="19" fill="#ffffaf" textLength="81" lengthAdjust="spacingAndGlyphs" font-weight="bold">? replace</text>
|
||||
<text x="711" y="19" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="36" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="36" fill="#333333" textLength="684" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────╮</text>
|
||||
<text x="711" y="36" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="53" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="53" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="53" fill="#afafaf" textLength="234" lengthAdjust="spacingAndGlyphs">... 13 hidden (Ctrl+O) ...</text>
|
||||
<text x="693" y="53" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="53" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="36" y="68" width="9" height="17" fill="#005f00" />
|
||||
<rect x="45" y="68" width="9" height="17" fill="#005f00" />
|
||||
<text x="45" y="70" fill="#afafaf" textLength="9" lengthAdjust="spacingAndGlyphs">7</text>
|
||||
<rect x="54" y="68" width="9" height="17" fill="#005f00" />
|
||||
<rect x="63" y="68" width="9" height="17" fill="#005f00" />
|
||||
<text x="63" y="70" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">+</text>
|
||||
<rect x="72" y="68" width="9" height="17" fill="#005f00" />
|
||||
<rect x="81" y="68" width="45" height="17" fill="#005f00" />
|
||||
<text x="81" y="70" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||
<rect x="126" y="68" width="108" height="17" fill="#005f00" />
|
||||
<text x="126" y="70" fill="#e5e5e5" textLength="108" lengthAdjust="spacingAndGlyphs"> newLine7 = </text>
|
||||
<rect x="234" y="68" width="36" height="17" fill="#005f00" />
|
||||
<text x="234" y="70" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<rect x="270" y="68" width="9" height="17" fill="#005f00" />
|
||||
<text x="270" y="70" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="693" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="36" y="85" width="9" height="17" fill="#5f0000" />
|
||||
<rect x="45" y="85" width="9" height="17" fill="#5f0000" />
|
||||
<text x="45" y="87" fill="#afafaf" textLength="9" lengthAdjust="spacingAndGlyphs">8</text>
|
||||
<rect x="54" y="85" width="9" height="17" fill="#5f0000" />
|
||||
<rect x="63" y="85" width="9" height="17" fill="#5f0000" />
|
||||
<text x="63" y="87" fill="#ff87af" textLength="9" lengthAdjust="spacingAndGlyphs">-</text>
|
||||
<rect x="72" y="85" width="9" height="17" fill="#5f0000" />
|
||||
<rect x="81" y="85" width="45" height="17" fill="#5f0000" />
|
||||
<text x="81" y="87" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||
<rect x="126" y="85" width="108" height="17" fill="#5f0000" />
|
||||
<text x="126" y="87" fill="#e5e5e5" textLength="108" lengthAdjust="spacingAndGlyphs"> oldLine8 = </text>
|
||||
<rect x="234" y="85" width="36" height="17" fill="#5f0000" />
|
||||
<text x="234" y="87" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<rect x="270" y="85" width="9" height="17" fill="#5f0000" />
|
||||
<text x="270" y="87" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="693" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="2" fill="#ffffaf" textLength="720" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────╮</text>
|
||||
<text x="0" y="19" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="19" fill="#ffffaf" textLength="135" lengthAdjust="spacingAndGlyphs" font-weight="bold">Action Required</text>
|
||||
<text x="711" y="19" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="36" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="36" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="53" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="53" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">?</text>
|
||||
<text x="45" y="53" fill="#ffffff" textLength="63" lengthAdjust="spacingAndGlyphs" font-weight="bold">replace</text>
|
||||
<text x="117" y="53" fill="#afafaf" textLength="234" lengthAdjust="spacingAndGlyphs">Replaces content in a file</text>
|
||||
<text x="711" y="53" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="70" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="70" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="87" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="87" fill="#afafaf" textLength="234" lengthAdjust="spacingAndGlyphs">... 15 hidden (Ctrl+O) ...</text>
|
||||
<text x="711" y="87" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="104" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="18" y="102" width="9" height="17" fill="#005f00" />
|
||||
<rect x="27" y="102" width="9" height="17" fill="#005f00" />
|
||||
<text x="27" y="104" fill="#afafaf" textLength="9" lengthAdjust="spacingAndGlyphs">8</text>
|
||||
<rect x="36" y="102" width="9" height="17" fill="#005f00" />
|
||||
<rect x="45" y="102" width="9" height="17" fill="#005f00" />
|
||||
<text x="45" y="104" fill="#afafaf" textLength="9" lengthAdjust="spacingAndGlyphs">8</text>
|
||||
<text x="45" y="104" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">+</text>
|
||||
<rect x="54" y="102" width="9" height="17" fill="#005f00" />
|
||||
<rect x="63" y="102" width="9" height="17" fill="#005f00" />
|
||||
<text x="63" y="104" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">+</text>
|
||||
<rect x="72" y="102" width="9" height="17" fill="#005f00" />
|
||||
<rect x="81" y="102" width="45" height="17" fill="#005f00" />
|
||||
<text x="81" y="104" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||
<rect x="126" y="102" width="108" height="17" fill="#005f00" />
|
||||
<text x="126" y="104" fill="#e5e5e5" textLength="108" lengthAdjust="spacingAndGlyphs"> newLine8 = </text>
|
||||
<rect x="234" y="102" width="36" height="17" fill="#005f00" />
|
||||
<text x="234" y="104" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<rect x="270" y="102" width="9" height="17" fill="#005f00" />
|
||||
<text x="270" y="104" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="693" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="63" y="102" width="45" height="17" fill="#005f00" />
|
||||
<text x="63" y="104" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||
<rect x="108" y="102" width="108" height="17" fill="#005f00" />
|
||||
<text x="108" y="104" fill="#e5e5e5" textLength="108" lengthAdjust="spacingAndGlyphs"> newLine8 = </text>
|
||||
<rect x="216" y="102" width="36" height="17" fill="#005f00" />
|
||||
<text x="216" y="104" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<rect x="252" y="102" width="9" height="17" fill="#005f00" />
|
||||
<text x="252" y="104" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="711" y="104" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="121" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="18" y="119" width="9" height="17" fill="#5f0000" />
|
||||
<rect x="27" y="119" width="9" height="17" fill="#5f0000" />
|
||||
<text x="27" y="121" fill="#afafaf" textLength="9" lengthAdjust="spacingAndGlyphs">9</text>
|
||||
<rect x="36" y="119" width="9" height="17" fill="#5f0000" />
|
||||
<rect x="45" y="119" width="9" height="17" fill="#5f0000" />
|
||||
<text x="45" y="121" fill="#afafaf" textLength="9" lengthAdjust="spacingAndGlyphs">9</text>
|
||||
<text x="45" y="121" fill="#ff87af" textLength="9" lengthAdjust="spacingAndGlyphs">-</text>
|
||||
<rect x="54" y="119" width="9" height="17" fill="#5f0000" />
|
||||
<rect x="63" y="119" width="9" height="17" fill="#5f0000" />
|
||||
<text x="63" y="121" fill="#ff87af" textLength="9" lengthAdjust="spacingAndGlyphs">-</text>
|
||||
<rect x="72" y="119" width="9" height="17" fill="#5f0000" />
|
||||
<rect x="81" y="119" width="45" height="17" fill="#5f0000" />
|
||||
<text x="81" y="121" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||
<rect x="126" y="119" width="108" height="17" fill="#5f0000" />
|
||||
<text x="126" y="121" fill="#e5e5e5" textLength="108" lengthAdjust="spacingAndGlyphs"> oldLine9 = </text>
|
||||
<rect x="234" y="119" width="36" height="17" fill="#5f0000" />
|
||||
<text x="234" y="121" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<rect x="270" y="119" width="9" height="17" fill="#5f0000" />
|
||||
<text x="270" y="121" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="693" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="63" y="119" width="45" height="17" fill="#5f0000" />
|
||||
<text x="63" y="121" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||
<rect x="108" y="119" width="108" height="17" fill="#5f0000" />
|
||||
<text x="108" y="121" fill="#e5e5e5" textLength="108" lengthAdjust="spacingAndGlyphs"> oldLine9 = </text>
|
||||
<rect x="216" y="119" width="36" height="17" fill="#5f0000" />
|
||||
<text x="216" y="121" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<rect x="252" y="119" width="9" height="17" fill="#5f0000" />
|
||||
<text x="252" y="121" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="711" y="121" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="138" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="18" y="136" width="9" height="17" fill="#005f00" />
|
||||
<rect x="27" y="136" width="9" height="17" fill="#005f00" />
|
||||
<text x="27" y="138" fill="#afafaf" textLength="9" lengthAdjust="spacingAndGlyphs">9</text>
|
||||
<rect x="36" y="136" width="9" height="17" fill="#005f00" />
|
||||
<rect x="45" y="136" width="9" height="17" fill="#005f00" />
|
||||
<text x="45" y="138" fill="#afafaf" textLength="9" lengthAdjust="spacingAndGlyphs">9</text>
|
||||
<text x="45" y="138" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">+</text>
|
||||
<rect x="54" y="136" width="9" height="17" fill="#005f00" />
|
||||
<rect x="63" y="136" width="9" height="17" fill="#005f00" />
|
||||
<text x="63" y="138" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">+</text>
|
||||
<rect x="72" y="136" width="9" height="17" fill="#005f00" />
|
||||
<rect x="81" y="136" width="45" height="17" fill="#005f00" />
|
||||
<text x="81" y="138" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||
<rect x="126" y="136" width="108" height="17" fill="#005f00" />
|
||||
<text x="126" y="138" fill="#e5e5e5" textLength="108" lengthAdjust="spacingAndGlyphs"> newLine9 = </text>
|
||||
<rect x="234" y="136" width="36" height="17" fill="#005f00" />
|
||||
<text x="234" y="138" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<rect x="270" y="136" width="9" height="17" fill="#005f00" />
|
||||
<text x="270" y="138" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="693" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="155" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="155" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="36" y="153" width="18" height="17" fill="#5f0000" />
|
||||
<text x="36" y="155" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">10</text>
|
||||
<rect x="63" y="136" width="45" height="17" fill="#005f00" />
|
||||
<text x="63" y="138" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||
<rect x="108" y="136" width="108" height="17" fill="#005f00" />
|
||||
<text x="108" y="138" fill="#e5e5e5" textLength="108" lengthAdjust="spacingAndGlyphs"> newLine9 = </text>
|
||||
<rect x="216" y="136" width="36" height="17" fill="#005f00" />
|
||||
<text x="216" y="138" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<rect x="252" y="136" width="9" height="17" fill="#005f00" />
|
||||
<text x="252" y="138" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="711" y="138" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="155" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="18" y="153" width="18" height="17" fill="#5f0000" />
|
||||
<text x="18" y="155" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">10</text>
|
||||
<rect x="36" y="153" width="9" height="17" fill="#5f0000" />
|
||||
<rect x="45" y="153" width="9" height="17" fill="#5f0000" />
|
||||
<text x="45" y="155" fill="#ff87af" textLength="9" lengthAdjust="spacingAndGlyphs">-</text>
|
||||
<rect x="54" y="153" width="9" height="17" fill="#5f0000" />
|
||||
<rect x="63" y="153" width="9" height="17" fill="#5f0000" />
|
||||
<text x="63" y="155" fill="#ff87af" textLength="9" lengthAdjust="spacingAndGlyphs">-</text>
|
||||
<rect x="72" y="153" width="9" height="17" fill="#5f0000" />
|
||||
<rect x="81" y="153" width="45" height="17" fill="#5f0000" />
|
||||
<text x="81" y="155" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||
<rect x="126" y="153" width="117" height="17" fill="#5f0000" />
|
||||
<text x="126" y="155" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> oldLine10 = </text>
|
||||
<rect x="243" y="153" width="36" height="17" fill="#5f0000" />
|
||||
<text x="243" y="155" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<rect x="279" y="153" width="9" height="17" fill="#5f0000" />
|
||||
<text x="279" y="155" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="693" y="155" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="155" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="36" y="170" width="18" height="17" fill="#005f00" />
|
||||
<text x="36" y="172" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">10</text>
|
||||
<rect x="63" y="153" width="45" height="17" fill="#5f0000" />
|
||||
<text x="63" y="155" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||
<rect x="108" y="153" width="117" height="17" fill="#5f0000" />
|
||||
<text x="108" y="155" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> oldLine10 = </text>
|
||||
<rect x="225" y="153" width="36" height="17" fill="#5f0000" />
|
||||
<text x="225" y="155" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<rect x="261" y="153" width="9" height="17" fill="#5f0000" />
|
||||
<text x="261" y="155" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="711" y="155" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="172" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="18" y="170" width="18" height="17" fill="#005f00" />
|
||||
<text x="18" y="172" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">10</text>
|
||||
<rect x="36" y="170" width="9" height="17" fill="#005f00" />
|
||||
<rect x="45" y="170" width="9" height="17" fill="#005f00" />
|
||||
<text x="45" y="172" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">+</text>
|
||||
<rect x="54" y="170" width="9" height="17" fill="#005f00" />
|
||||
<rect x="63" y="170" width="9" height="17" fill="#005f00" />
|
||||
<text x="63" y="172" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">+</text>
|
||||
<rect x="72" y="170" width="9" height="17" fill="#005f00" />
|
||||
<rect x="81" y="170" width="45" height="17" fill="#005f00" />
|
||||
<text x="81" y="172" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||
<rect x="126" y="170" width="117" height="17" fill="#005f00" />
|
||||
<text x="126" y="172" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> newLine10 = </text>
|
||||
<rect x="243" y="170" width="36" height="17" fill="#005f00" />
|
||||
<text x="243" y="172" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<rect x="279" y="170" width="9" height="17" fill="#005f00" />
|
||||
<text x="279" y="172" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="693" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="189" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="189" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="36" y="187" width="18" height="17" fill="#5f0000" />
|
||||
<text x="36" y="189" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">11</text>
|
||||
<rect x="63" y="170" width="45" height="17" fill="#005f00" />
|
||||
<text x="63" y="172" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||
<rect x="108" y="170" width="117" height="17" fill="#005f00" />
|
||||
<text x="108" y="172" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> newLine10 = </text>
|
||||
<rect x="225" y="170" width="36" height="17" fill="#005f00" />
|
||||
<text x="225" y="172" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<rect x="261" y="170" width="9" height="17" fill="#005f00" />
|
||||
<text x="261" y="172" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="711" y="172" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="189" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="18" y="187" width="18" height="17" fill="#5f0000" />
|
||||
<text x="18" y="189" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">11</text>
|
||||
<rect x="36" y="187" width="9" height="17" fill="#5f0000" />
|
||||
<rect x="45" y="187" width="9" height="17" fill="#5f0000" />
|
||||
<text x="45" y="189" fill="#ff87af" textLength="9" lengthAdjust="spacingAndGlyphs">-</text>
|
||||
<rect x="54" y="187" width="9" height="17" fill="#5f0000" />
|
||||
<rect x="63" y="187" width="9" height="17" fill="#5f0000" />
|
||||
<text x="63" y="189" fill="#ff87af" textLength="9" lengthAdjust="spacingAndGlyphs">-</text>
|
||||
<rect x="72" y="187" width="9" height="17" fill="#5f0000" />
|
||||
<rect x="81" y="187" width="45" height="17" fill="#5f0000" />
|
||||
<text x="81" y="189" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||
<rect x="126" y="187" width="117" height="17" fill="#5f0000" />
|
||||
<text x="126" y="189" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> oldLine11 = </text>
|
||||
<rect x="243" y="187" width="36" height="17" fill="#5f0000" />
|
||||
<text x="243" y="189" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<rect x="279" y="187" width="9" height="17" fill="#5f0000" />
|
||||
<text x="279" y="189" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="693" y="189" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="189" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="206" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="206" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="36" y="204" width="18" height="17" fill="#005f00" />
|
||||
<text x="36" y="206" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">11</text>
|
||||
<rect x="63" y="187" width="45" height="17" fill="#5f0000" />
|
||||
<text x="63" y="189" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||
<rect x="108" y="187" width="117" height="17" fill="#5f0000" />
|
||||
<text x="108" y="189" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> oldLine11 = </text>
|
||||
<rect x="225" y="187" width="36" height="17" fill="#5f0000" />
|
||||
<text x="225" y="189" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<rect x="261" y="187" width="9" height="17" fill="#5f0000" />
|
||||
<text x="261" y="189" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="711" y="189" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="206" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="18" y="204" width="18" height="17" fill="#005f00" />
|
||||
<text x="18" y="206" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">11</text>
|
||||
<rect x="36" y="204" width="9" height="17" fill="#005f00" />
|
||||
<rect x="45" y="204" width="9" height="17" fill="#005f00" />
|
||||
<text x="45" y="206" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">+</text>
|
||||
<rect x="54" y="204" width="9" height="17" fill="#005f00" />
|
||||
<rect x="63" y="204" width="9" height="17" fill="#005f00" />
|
||||
<text x="63" y="206" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">+</text>
|
||||
<rect x="72" y="204" width="9" height="17" fill="#005f00" />
|
||||
<rect x="81" y="204" width="45" height="17" fill="#005f00" />
|
||||
<text x="81" y="206" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||
<rect x="126" y="204" width="117" height="17" fill="#005f00" />
|
||||
<text x="126" y="206" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> newLine11 = </text>
|
||||
<rect x="243" y="204" width="36" height="17" fill="#005f00" />
|
||||
<text x="243" y="206" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<rect x="279" y="204" width="9" height="17" fill="#005f00" />
|
||||
<text x="279" y="206" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="693" y="206" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="206" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="223" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="223" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="36" y="221" width="18" height="17" fill="#5f0000" />
|
||||
<text x="36" y="223" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">12</text>
|
||||
<rect x="63" y="204" width="45" height="17" fill="#005f00" />
|
||||
<text x="63" y="206" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||
<rect x="108" y="204" width="117" height="17" fill="#005f00" />
|
||||
<text x="108" y="206" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> newLine11 = </text>
|
||||
<rect x="225" y="204" width="36" height="17" fill="#005f00" />
|
||||
<text x="225" y="206" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<rect x="261" y="204" width="9" height="17" fill="#005f00" />
|
||||
<text x="261" y="206" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="711" y="206" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="223" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="18" y="221" width="18" height="17" fill="#5f0000" />
|
||||
<text x="18" y="223" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">12</text>
|
||||
<rect x="36" y="221" width="9" height="17" fill="#5f0000" />
|
||||
<rect x="45" y="221" width="9" height="17" fill="#5f0000" />
|
||||
<text x="45" y="223" fill="#ff87af" textLength="9" lengthAdjust="spacingAndGlyphs">-</text>
|
||||
<rect x="54" y="221" width="9" height="17" fill="#5f0000" />
|
||||
<rect x="63" y="221" width="9" height="17" fill="#5f0000" />
|
||||
<text x="63" y="223" fill="#ff87af" textLength="9" lengthAdjust="spacingAndGlyphs">-</text>
|
||||
<rect x="72" y="221" width="9" height="17" fill="#5f0000" />
|
||||
<rect x="81" y="221" width="45" height="17" fill="#5f0000" />
|
||||
<text x="81" y="223" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||
<rect x="126" y="221" width="117" height="17" fill="#5f0000" />
|
||||
<text x="126" y="223" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> oldLine12 = </text>
|
||||
<rect x="243" y="221" width="36" height="17" fill="#5f0000" />
|
||||
<text x="243" y="223" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<rect x="279" y="221" width="9" height="17" fill="#5f0000" />
|
||||
<text x="279" y="223" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="693" y="223" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="223" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="240" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="240" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="36" y="238" width="18" height="17" fill="#005f00" />
|
||||
<text x="36" y="240" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">12</text>
|
||||
<rect x="63" y="221" width="45" height="17" fill="#5f0000" />
|
||||
<text x="63" y="223" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||
<rect x="108" y="221" width="117" height="17" fill="#5f0000" />
|
||||
<text x="108" y="223" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> oldLine12 = </text>
|
||||
<rect x="225" y="221" width="36" height="17" fill="#5f0000" />
|
||||
<text x="225" y="223" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<rect x="261" y="221" width="9" height="17" fill="#5f0000" />
|
||||
<text x="261" y="223" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="711" y="223" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="240" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="18" y="238" width="18" height="17" fill="#005f00" />
|
||||
<text x="18" y="240" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">12</text>
|
||||
<rect x="36" y="238" width="9" height="17" fill="#005f00" />
|
||||
<rect x="45" y="238" width="9" height="17" fill="#005f00" />
|
||||
<text x="45" y="240" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">+</text>
|
||||
<rect x="54" y="238" width="9" height="17" fill="#005f00" />
|
||||
<rect x="63" y="238" width="9" height="17" fill="#005f00" />
|
||||
<text x="63" y="240" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">+</text>
|
||||
<rect x="72" y="238" width="9" height="17" fill="#005f00" />
|
||||
<rect x="81" y="238" width="45" height="17" fill="#005f00" />
|
||||
<text x="81" y="240" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||
<rect x="126" y="238" width="117" height="17" fill="#005f00" />
|
||||
<text x="126" y="240" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> newLine12 = </text>
|
||||
<rect x="243" y="238" width="36" height="17" fill="#005f00" />
|
||||
<text x="243" y="240" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<rect x="279" y="238" width="9" height="17" fill="#005f00" />
|
||||
<text x="279" y="240" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="693" y="240" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="240" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="257" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="257" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="36" y="255" width="18" height="17" fill="#5f0000" />
|
||||
<text x="36" y="257" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">13</text>
|
||||
<rect x="63" y="238" width="45" height="17" fill="#005f00" />
|
||||
<text x="63" y="240" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||
<rect x="108" y="238" width="117" height="17" fill="#005f00" />
|
||||
<text x="108" y="240" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> newLine12 = </text>
|
||||
<rect x="225" y="238" width="36" height="17" fill="#005f00" />
|
||||
<text x="225" y="240" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<rect x="261" y="238" width="9" height="17" fill="#005f00" />
|
||||
<text x="261" y="240" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="711" y="240" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="257" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="18" y="255" width="18" height="17" fill="#5f0000" />
|
||||
<text x="18" y="257" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">13</text>
|
||||
<rect x="36" y="255" width="9" height="17" fill="#5f0000" />
|
||||
<rect x="45" y="255" width="9" height="17" fill="#5f0000" />
|
||||
<text x="45" y="257" fill="#ff87af" textLength="9" lengthAdjust="spacingAndGlyphs">-</text>
|
||||
<rect x="54" y="255" width="9" height="17" fill="#5f0000" />
|
||||
<rect x="63" y="255" width="9" height="17" fill="#5f0000" />
|
||||
<text x="63" y="257" fill="#ff87af" textLength="9" lengthAdjust="spacingAndGlyphs">-</text>
|
||||
<rect x="72" y="255" width="9" height="17" fill="#5f0000" />
|
||||
<rect x="81" y="255" width="45" height="17" fill="#5f0000" />
|
||||
<text x="81" y="257" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||
<rect x="126" y="255" width="117" height="17" fill="#5f0000" />
|
||||
<text x="126" y="257" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> oldLine13 = </text>
|
||||
<rect x="243" y="255" width="36" height="17" fill="#5f0000" />
|
||||
<text x="243" y="257" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<rect x="279" y="255" width="9" height="17" fill="#5f0000" />
|
||||
<text x="279" y="257" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="693" y="257" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="257" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="274" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="274" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="36" y="272" width="18" height="17" fill="#005f00" />
|
||||
<text x="36" y="274" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">13</text>
|
||||
<rect x="63" y="255" width="45" height="17" fill="#5f0000" />
|
||||
<text x="63" y="257" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||
<rect x="108" y="255" width="117" height="17" fill="#5f0000" />
|
||||
<text x="108" y="257" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> oldLine13 = </text>
|
||||
<rect x="225" y="255" width="36" height="17" fill="#5f0000" />
|
||||
<text x="225" y="257" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<rect x="261" y="255" width="9" height="17" fill="#5f0000" />
|
||||
<text x="261" y="257" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="711" y="257" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="274" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="18" y="272" width="18" height="17" fill="#005f00" />
|
||||
<text x="18" y="274" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">13</text>
|
||||
<rect x="36" y="272" width="9" height="17" fill="#005f00" />
|
||||
<rect x="45" y="272" width="9" height="17" fill="#005f00" />
|
||||
<text x="45" y="274" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">+</text>
|
||||
<rect x="54" y="272" width="9" height="17" fill="#005f00" />
|
||||
<rect x="63" y="272" width="9" height="17" fill="#005f00" />
|
||||
<text x="63" y="274" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">+</text>
|
||||
<rect x="72" y="272" width="9" height="17" fill="#005f00" />
|
||||
<rect x="81" y="272" width="45" height="17" fill="#005f00" />
|
||||
<text x="81" y="274" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||
<rect x="126" y="272" width="117" height="17" fill="#005f00" />
|
||||
<text x="126" y="274" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> newLine13 = </text>
|
||||
<rect x="243" y="272" width="36" height="17" fill="#005f00" />
|
||||
<text x="243" y="274" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<rect x="279" y="272" width="9" height="17" fill="#005f00" />
|
||||
<text x="279" y="274" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="693" y="274" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="274" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="291" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="291" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="36" y="289" width="18" height="17" fill="#5f0000" />
|
||||
<text x="36" y="291" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">14</text>
|
||||
<rect x="63" y="272" width="45" height="17" fill="#005f00" />
|
||||
<text x="63" y="274" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||
<rect x="108" y="272" width="117" height="17" fill="#005f00" />
|
||||
<text x="108" y="274" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> newLine13 = </text>
|
||||
<rect x="225" y="272" width="36" height="17" fill="#005f00" />
|
||||
<text x="225" y="274" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<rect x="261" y="272" width="9" height="17" fill="#005f00" />
|
||||
<text x="261" y="274" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="711" y="274" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="291" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="18" y="289" width="18" height="17" fill="#5f0000" />
|
||||
<text x="18" y="291" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">14</text>
|
||||
<rect x="36" y="289" width="9" height="17" fill="#5f0000" />
|
||||
<rect x="45" y="289" width="9" height="17" fill="#5f0000" />
|
||||
<text x="45" y="291" fill="#ff87af" textLength="9" lengthAdjust="spacingAndGlyphs">-</text>
|
||||
<rect x="54" y="289" width="9" height="17" fill="#5f0000" />
|
||||
<rect x="63" y="289" width="9" height="17" fill="#5f0000" />
|
||||
<text x="63" y="291" fill="#ff87af" textLength="9" lengthAdjust="spacingAndGlyphs">-</text>
|
||||
<rect x="72" y="289" width="9" height="17" fill="#5f0000" />
|
||||
<rect x="81" y="289" width="45" height="17" fill="#5f0000" />
|
||||
<text x="81" y="291" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||
<rect x="126" y="289" width="117" height="17" fill="#5f0000" />
|
||||
<text x="126" y="291" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> oldLine14 = </text>
|
||||
<rect x="243" y="289" width="36" height="17" fill="#5f0000" />
|
||||
<text x="243" y="291" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<rect x="279" y="289" width="9" height="17" fill="#5f0000" />
|
||||
<text x="279" y="291" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="693" y="291" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="291" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="308" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="308" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="36" y="306" width="18" height="17" fill="#005f00" />
|
||||
<text x="36" y="308" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">14</text>
|
||||
<rect x="63" y="289" width="45" height="17" fill="#5f0000" />
|
||||
<text x="63" y="291" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||
<rect x="108" y="289" width="117" height="17" fill="#5f0000" />
|
||||
<text x="108" y="291" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> oldLine14 = </text>
|
||||
<rect x="225" y="289" width="36" height="17" fill="#5f0000" />
|
||||
<text x="225" y="291" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<rect x="261" y="289" width="9" height="17" fill="#5f0000" />
|
||||
<text x="261" y="291" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="711" y="291" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="308" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="18" y="306" width="18" height="17" fill="#005f00" />
|
||||
<text x="18" y="308" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">14</text>
|
||||
<rect x="36" y="306" width="9" height="17" fill="#005f00" />
|
||||
<rect x="45" y="306" width="9" height="17" fill="#005f00" />
|
||||
<text x="45" y="308" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">+</text>
|
||||
<rect x="54" y="306" width="9" height="17" fill="#005f00" />
|
||||
<rect x="63" y="306" width="9" height="17" fill="#005f00" />
|
||||
<text x="63" y="308" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">+</text>
|
||||
<rect x="72" y="306" width="9" height="17" fill="#005f00" />
|
||||
<rect x="81" y="306" width="45" height="17" fill="#005f00" />
|
||||
<text x="81" y="308" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||
<rect x="126" y="306" width="117" height="17" fill="#005f00" />
|
||||
<text x="126" y="308" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> newLine14 = </text>
|
||||
<rect x="243" y="306" width="36" height="17" fill="#005f00" />
|
||||
<text x="243" y="308" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<rect x="279" y="306" width="9" height="17" fill="#005f00" />
|
||||
<text x="279" y="308" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="693" y="308" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="308" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="325" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="325" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="36" y="323" width="18" height="17" fill="#5f0000" />
|
||||
<text x="36" y="325" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">15</text>
|
||||
<rect x="63" y="306" width="45" height="17" fill="#005f00" />
|
||||
<text x="63" y="308" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||
<rect x="108" y="306" width="117" height="17" fill="#005f00" />
|
||||
<text x="108" y="308" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> newLine14 = </text>
|
||||
<rect x="225" y="306" width="36" height="17" fill="#005f00" />
|
||||
<text x="225" y="308" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<rect x="261" y="306" width="9" height="17" fill="#005f00" />
|
||||
<text x="261" y="308" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="711" y="308" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="325" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="18" y="323" width="18" height="17" fill="#5f0000" />
|
||||
<text x="18" y="325" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">15</text>
|
||||
<rect x="36" y="323" width="9" height="17" fill="#5f0000" />
|
||||
<rect x="45" y="323" width="9" height="17" fill="#5f0000" />
|
||||
<text x="45" y="325" fill="#ff87af" textLength="9" lengthAdjust="spacingAndGlyphs">-</text>
|
||||
<rect x="54" y="323" width="9" height="17" fill="#5f0000" />
|
||||
<rect x="63" y="323" width="9" height="17" fill="#5f0000" />
|
||||
<text x="63" y="325" fill="#ff87af" textLength="9" lengthAdjust="spacingAndGlyphs">-</text>
|
||||
<rect x="72" y="323" width="9" height="17" fill="#5f0000" />
|
||||
<rect x="81" y="323" width="45" height="17" fill="#5f0000" />
|
||||
<text x="81" y="325" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||
<rect x="126" y="323" width="117" height="17" fill="#5f0000" />
|
||||
<text x="126" y="325" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> oldLine15 = </text>
|
||||
<rect x="243" y="323" width="36" height="17" fill="#5f0000" />
|
||||
<text x="243" y="325" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<rect x="279" y="323" width="9" height="17" fill="#5f0000" />
|
||||
<text x="279" y="325" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="693" y="325" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="325" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="342" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="342" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="36" y="340" width="18" height="17" fill="#005f00" />
|
||||
<text x="36" y="342" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">15</text>
|
||||
<rect x="63" y="323" width="45" height="17" fill="#5f0000" />
|
||||
<text x="63" y="325" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||
<rect x="108" y="323" width="117" height="17" fill="#5f0000" />
|
||||
<text x="108" y="325" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> oldLine15 = </text>
|
||||
<rect x="225" y="323" width="36" height="17" fill="#5f0000" />
|
||||
<text x="225" y="325" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<rect x="261" y="323" width="9" height="17" fill="#5f0000" />
|
||||
<text x="261" y="325" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="711" y="325" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="342" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="18" y="340" width="18" height="17" fill="#005f00" />
|
||||
<text x="18" y="342" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">15</text>
|
||||
<rect x="36" y="340" width="9" height="17" fill="#005f00" />
|
||||
<rect x="45" y="340" width="9" height="17" fill="#005f00" />
|
||||
<text x="45" y="342" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">+</text>
|
||||
<rect x="54" y="340" width="9" height="17" fill="#005f00" />
|
||||
<rect x="63" y="340" width="9" height="17" fill="#005f00" />
|
||||
<text x="63" y="342" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">+</text>
|
||||
<rect x="72" y="340" width="9" height="17" fill="#005f00" />
|
||||
<rect x="81" y="340" width="45" height="17" fill="#005f00" />
|
||||
<text x="81" y="342" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||
<rect x="126" y="340" width="117" height="17" fill="#005f00" />
|
||||
<text x="126" y="342" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> newLine15 = </text>
|
||||
<rect x="243" y="340" width="36" height="17" fill="#005f00" />
|
||||
<text x="243" y="342" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<rect x="279" y="340" width="9" height="17" fill="#005f00" />
|
||||
<text x="279" y="342" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="693" y="342" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="342" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="359" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="359" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="36" y="357" width="18" height="17" fill="#5f0000" />
|
||||
<text x="36" y="359" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">16</text>
|
||||
<rect x="63" y="340" width="45" height="17" fill="#005f00" />
|
||||
<text x="63" y="342" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||
<rect x="108" y="340" width="117" height="17" fill="#005f00" />
|
||||
<text x="108" y="342" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> newLine15 = </text>
|
||||
<rect x="225" y="340" width="36" height="17" fill="#005f00" />
|
||||
<text x="225" y="342" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<rect x="261" y="340" width="9" height="17" fill="#005f00" />
|
||||
<text x="261" y="342" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="711" y="342" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="359" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="18" y="357" width="18" height="17" fill="#5f0000" />
|
||||
<text x="18" y="359" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">16</text>
|
||||
<rect x="36" y="357" width="9" height="17" fill="#5f0000" />
|
||||
<rect x="45" y="357" width="9" height="17" fill="#5f0000" />
|
||||
<text x="45" y="359" fill="#ff87af" textLength="9" lengthAdjust="spacingAndGlyphs">-</text>
|
||||
<rect x="54" y="357" width="9" height="17" fill="#5f0000" />
|
||||
<rect x="63" y="357" width="9" height="17" fill="#5f0000" />
|
||||
<text x="63" y="359" fill="#ff87af" textLength="9" lengthAdjust="spacingAndGlyphs">-</text>
|
||||
<rect x="72" y="357" width="9" height="17" fill="#5f0000" />
|
||||
<rect x="81" y="357" width="45" height="17" fill="#5f0000" />
|
||||
<text x="81" y="359" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||
<rect x="126" y="357" width="117" height="17" fill="#5f0000" />
|
||||
<text x="126" y="359" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> oldLine16 = </text>
|
||||
<rect x="243" y="357" width="36" height="17" fill="#5f0000" />
|
||||
<text x="243" y="359" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<rect x="279" y="357" width="9" height="17" fill="#5f0000" />
|
||||
<text x="279" y="359" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="693" y="359" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="359" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="376" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="376" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="36" y="374" width="18" height="17" fill="#005f00" />
|
||||
<text x="36" y="376" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">16</text>
|
||||
<rect x="63" y="357" width="45" height="17" fill="#5f0000" />
|
||||
<text x="63" y="359" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||
<rect x="108" y="357" width="117" height="17" fill="#5f0000" />
|
||||
<text x="108" y="359" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> oldLine16 = </text>
|
||||
<rect x="225" y="357" width="36" height="17" fill="#5f0000" />
|
||||
<text x="225" y="359" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<rect x="261" y="357" width="9" height="17" fill="#5f0000" />
|
||||
<text x="261" y="359" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="711" y="359" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="376" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="18" y="374" width="18" height="17" fill="#005f00" />
|
||||
<text x="18" y="376" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">16</text>
|
||||
<rect x="36" y="374" width="9" height="17" fill="#005f00" />
|
||||
<rect x="45" y="374" width="9" height="17" fill="#005f00" />
|
||||
<text x="45" y="376" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">+</text>
|
||||
<rect x="54" y="374" width="9" height="17" fill="#005f00" />
|
||||
<rect x="63" y="374" width="9" height="17" fill="#005f00" />
|
||||
<text x="63" y="376" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">+</text>
|
||||
<rect x="72" y="374" width="9" height="17" fill="#005f00" />
|
||||
<rect x="81" y="374" width="45" height="17" fill="#005f00" />
|
||||
<text x="81" y="376" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||
<rect x="126" y="374" width="117" height="17" fill="#005f00" />
|
||||
<text x="126" y="376" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> newLine16 = </text>
|
||||
<rect x="243" y="374" width="36" height="17" fill="#005f00" />
|
||||
<text x="243" y="376" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<rect x="279" y="374" width="9" height="17" fill="#005f00" />
|
||||
<text x="279" y="376" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="693" y="376" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="376" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="393" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="393" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="36" y="391" width="18" height="17" fill="#5f0000" />
|
||||
<text x="36" y="393" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">17</text>
|
||||
<rect x="63" y="374" width="45" height="17" fill="#005f00" />
|
||||
<text x="63" y="376" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||
<rect x="108" y="374" width="117" height="17" fill="#005f00" />
|
||||
<text x="108" y="376" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> newLine16 = </text>
|
||||
<rect x="225" y="374" width="36" height="17" fill="#005f00" />
|
||||
<text x="225" y="376" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<rect x="261" y="374" width="9" height="17" fill="#005f00" />
|
||||
<text x="261" y="376" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="711" y="376" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="393" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="18" y="391" width="18" height="17" fill="#5f0000" />
|
||||
<text x="18" y="393" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">17</text>
|
||||
<rect x="36" y="391" width="9" height="17" fill="#5f0000" />
|
||||
<rect x="45" y="391" width="9" height="17" fill="#5f0000" />
|
||||
<text x="45" y="393" fill="#ff87af" textLength="9" lengthAdjust="spacingAndGlyphs">-</text>
|
||||
<rect x="54" y="391" width="9" height="17" fill="#5f0000" />
|
||||
<rect x="63" y="391" width="9" height="17" fill="#5f0000" />
|
||||
<text x="63" y="393" fill="#ff87af" textLength="9" lengthAdjust="spacingAndGlyphs">-</text>
|
||||
<rect x="72" y="391" width="9" height="17" fill="#5f0000" />
|
||||
<rect x="81" y="391" width="45" height="17" fill="#5f0000" />
|
||||
<text x="81" y="393" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||
<rect x="126" y="391" width="117" height="17" fill="#5f0000" />
|
||||
<text x="126" y="393" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> oldLine17 = </text>
|
||||
<rect x="243" y="391" width="36" height="17" fill="#5f0000" />
|
||||
<text x="243" y="393" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<rect x="279" y="391" width="9" height="17" fill="#5f0000" />
|
||||
<text x="279" y="393" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="693" y="393" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="393" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="410" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="410" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="36" y="408" width="18" height="17" fill="#005f00" />
|
||||
<text x="36" y="410" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">17</text>
|
||||
<rect x="63" y="391" width="45" height="17" fill="#5f0000" />
|
||||
<text x="63" y="393" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||
<rect x="108" y="391" width="117" height="17" fill="#5f0000" />
|
||||
<text x="108" y="393" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> oldLine17 = </text>
|
||||
<rect x="225" y="391" width="36" height="17" fill="#5f0000" />
|
||||
<text x="225" y="393" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<rect x="261" y="391" width="9" height="17" fill="#5f0000" />
|
||||
<text x="261" y="393" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="711" y="393" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="410" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="18" y="408" width="18" height="17" fill="#005f00" />
|
||||
<text x="18" y="410" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">17</text>
|
||||
<rect x="36" y="408" width="9" height="17" fill="#005f00" />
|
||||
<rect x="45" y="408" width="9" height="17" fill="#005f00" />
|
||||
<text x="45" y="410" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">+</text>
|
||||
<rect x="54" y="408" width="9" height="17" fill="#005f00" />
|
||||
<rect x="63" y="408" width="9" height="17" fill="#005f00" />
|
||||
<text x="63" y="410" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">+</text>
|
||||
<rect x="72" y="408" width="9" height="17" fill="#005f00" />
|
||||
<rect x="81" y="408" width="45" height="17" fill="#005f00" />
|
||||
<text x="81" y="410" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||
<rect x="126" y="408" width="117" height="17" fill="#005f00" />
|
||||
<text x="126" y="410" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> newLine17 = </text>
|
||||
<rect x="243" y="408" width="36" height="17" fill="#005f00" />
|
||||
<text x="243" y="410" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<rect x="279" y="408" width="9" height="17" fill="#005f00" />
|
||||
<text x="279" y="410" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="693" y="410" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="410" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="427" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="427" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="36" y="425" width="18" height="17" fill="#5f0000" />
|
||||
<text x="36" y="427" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">18</text>
|
||||
<rect x="63" y="408" width="45" height="17" fill="#005f00" />
|
||||
<text x="63" y="410" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||
<rect x="108" y="408" width="117" height="17" fill="#005f00" />
|
||||
<text x="108" y="410" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> newLine17 = </text>
|
||||
<rect x="225" y="408" width="36" height="17" fill="#005f00" />
|
||||
<text x="225" y="410" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<rect x="261" y="408" width="9" height="17" fill="#005f00" />
|
||||
<text x="261" y="410" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="711" y="410" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="427" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="18" y="425" width="18" height="17" fill="#5f0000" />
|
||||
<text x="18" y="427" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">18</text>
|
||||
<rect x="36" y="425" width="9" height="17" fill="#5f0000" />
|
||||
<rect x="45" y="425" width="9" height="17" fill="#5f0000" />
|
||||
<text x="45" y="427" fill="#ff87af" textLength="9" lengthAdjust="spacingAndGlyphs">-</text>
|
||||
<rect x="54" y="425" width="9" height="17" fill="#5f0000" />
|
||||
<rect x="63" y="425" width="9" height="17" fill="#5f0000" />
|
||||
<text x="63" y="427" fill="#ff87af" textLength="9" lengthAdjust="spacingAndGlyphs">-</text>
|
||||
<rect x="72" y="425" width="9" height="17" fill="#5f0000" />
|
||||
<rect x="81" y="425" width="45" height="17" fill="#5f0000" />
|
||||
<text x="81" y="427" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||
<rect x="126" y="425" width="117" height="17" fill="#5f0000" />
|
||||
<text x="126" y="427" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> oldLine18 = </text>
|
||||
<rect x="243" y="425" width="36" height="17" fill="#5f0000" />
|
||||
<text x="243" y="427" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<rect x="279" y="425" width="9" height="17" fill="#5f0000" />
|
||||
<text x="279" y="427" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="693" y="427" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="427" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="444" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="444" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="36" y="442" width="18" height="17" fill="#005f00" />
|
||||
<text x="36" y="444" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">18</text>
|
||||
<rect x="63" y="425" width="45" height="17" fill="#5f0000" />
|
||||
<text x="63" y="427" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||
<rect x="108" y="425" width="117" height="17" fill="#5f0000" />
|
||||
<text x="108" y="427" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> oldLine18 = </text>
|
||||
<rect x="225" y="425" width="36" height="17" fill="#5f0000" />
|
||||
<text x="225" y="427" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<rect x="261" y="425" width="9" height="17" fill="#5f0000" />
|
||||
<text x="261" y="427" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="711" y="427" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="444" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="18" y="442" width="18" height="17" fill="#005f00" />
|
||||
<text x="18" y="444" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">18</text>
|
||||
<rect x="36" y="442" width="9" height="17" fill="#005f00" />
|
||||
<rect x="45" y="442" width="9" height="17" fill="#005f00" />
|
||||
<text x="45" y="444" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">+</text>
|
||||
<rect x="54" y="442" width="9" height="17" fill="#005f00" />
|
||||
<rect x="63" y="442" width="9" height="17" fill="#005f00" />
|
||||
<text x="63" y="444" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">+</text>
|
||||
<rect x="72" y="442" width="9" height="17" fill="#005f00" />
|
||||
<rect x="81" y="442" width="45" height="17" fill="#005f00" />
|
||||
<text x="81" y="444" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||
<rect x="126" y="442" width="117" height="17" fill="#005f00" />
|
||||
<text x="126" y="444" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> newLine18 = </text>
|
||||
<rect x="243" y="442" width="36" height="17" fill="#005f00" />
|
||||
<text x="243" y="444" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<rect x="279" y="442" width="9" height="17" fill="#005f00" />
|
||||
<text x="279" y="444" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="693" y="444" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="444" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="461" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="461" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="36" y="459" width="18" height="17" fill="#5f0000" />
|
||||
<text x="36" y="461" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">19</text>
|
||||
<rect x="63" y="442" width="45" height="17" fill="#005f00" />
|
||||
<text x="63" y="444" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||
<rect x="108" y="442" width="117" height="17" fill="#005f00" />
|
||||
<text x="108" y="444" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> newLine18 = </text>
|
||||
<rect x="225" y="442" width="36" height="17" fill="#005f00" />
|
||||
<text x="225" y="444" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<rect x="261" y="442" width="9" height="17" fill="#005f00" />
|
||||
<text x="261" y="444" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="711" y="444" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="461" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="18" y="459" width="18" height="17" fill="#5f0000" />
|
||||
<text x="18" y="461" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">19</text>
|
||||
<rect x="36" y="459" width="9" height="17" fill="#5f0000" />
|
||||
<rect x="45" y="459" width="9" height="17" fill="#5f0000" />
|
||||
<text x="45" y="461" fill="#ff87af" textLength="9" lengthAdjust="spacingAndGlyphs">-</text>
|
||||
<rect x="54" y="459" width="9" height="17" fill="#5f0000" />
|
||||
<rect x="63" y="459" width="9" height="17" fill="#5f0000" />
|
||||
<text x="63" y="461" fill="#ff87af" textLength="9" lengthAdjust="spacingAndGlyphs">-</text>
|
||||
<rect x="72" y="459" width="9" height="17" fill="#5f0000" />
|
||||
<rect x="81" y="459" width="45" height="17" fill="#5f0000" />
|
||||
<text x="81" y="461" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||
<rect x="126" y="459" width="117" height="17" fill="#5f0000" />
|
||||
<text x="126" y="461" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> oldLine19 = </text>
|
||||
<rect x="243" y="459" width="36" height="17" fill="#5f0000" />
|
||||
<text x="243" y="461" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<rect x="279" y="459" width="9" height="17" fill="#5f0000" />
|
||||
<text x="279" y="461" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="693" y="461" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="461" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="478" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="478" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="36" y="476" width="18" height="17" fill="#005f00" />
|
||||
<text x="36" y="478" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">19</text>
|
||||
<rect x="63" y="459" width="45" height="17" fill="#5f0000" />
|
||||
<text x="63" y="461" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||
<rect x="108" y="459" width="117" height="17" fill="#5f0000" />
|
||||
<text x="108" y="461" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> oldLine19 = </text>
|
||||
<rect x="225" y="459" width="36" height="17" fill="#5f0000" />
|
||||
<text x="225" y="461" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<rect x="261" y="459" width="9" height="17" fill="#5f0000" />
|
||||
<text x="261" y="461" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="711" y="461" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="478" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="18" y="476" width="18" height="17" fill="#005f00" />
|
||||
<text x="18" y="478" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">19</text>
|
||||
<rect x="36" y="476" width="9" height="17" fill="#005f00" />
|
||||
<rect x="45" y="476" width="9" height="17" fill="#005f00" />
|
||||
<text x="45" y="478" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">+</text>
|
||||
<rect x="54" y="476" width="9" height="17" fill="#005f00" />
|
||||
<rect x="63" y="476" width="9" height="17" fill="#005f00" />
|
||||
<text x="63" y="478" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">+</text>
|
||||
<rect x="72" y="476" width="9" height="17" fill="#005f00" />
|
||||
<rect x="81" y="476" width="45" height="17" fill="#005f00" />
|
||||
<text x="81" y="478" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||
<rect x="126" y="476" width="117" height="17" fill="#005f00" />
|
||||
<text x="126" y="478" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> newLine19 = </text>
|
||||
<rect x="243" y="476" width="36" height="17" fill="#005f00" />
|
||||
<text x="243" y="478" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<rect x="279" y="476" width="9" height="17" fill="#005f00" />
|
||||
<text x="279" y="478" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="693" y="478" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="478" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="495" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="495" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="36" y="493" width="18" height="17" fill="#5f0000" />
|
||||
<text x="36" y="495" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">20</text>
|
||||
<rect x="63" y="476" width="45" height="17" fill="#005f00" />
|
||||
<text x="63" y="478" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||
<rect x="108" y="476" width="117" height="17" fill="#005f00" />
|
||||
<text x="108" y="478" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> newLine19 = </text>
|
||||
<rect x="225" y="476" width="36" height="17" fill="#005f00" />
|
||||
<text x="225" y="478" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<rect x="261" y="476" width="9" height="17" fill="#005f00" />
|
||||
<text x="261" y="478" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="711" y="478" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="495" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="18" y="493" width="18" height="17" fill="#5f0000" />
|
||||
<text x="18" y="495" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">20</text>
|
||||
<rect x="36" y="493" width="9" height="17" fill="#5f0000" />
|
||||
<rect x="45" y="493" width="9" height="17" fill="#5f0000" />
|
||||
<text x="45" y="495" fill="#ff87af" textLength="9" lengthAdjust="spacingAndGlyphs">-</text>
|
||||
<rect x="54" y="493" width="9" height="17" fill="#5f0000" />
|
||||
<rect x="63" y="493" width="9" height="17" fill="#5f0000" />
|
||||
<text x="63" y="495" fill="#ff87af" textLength="9" lengthAdjust="spacingAndGlyphs">-</text>
|
||||
<rect x="72" y="493" width="9" height="17" fill="#5f0000" />
|
||||
<rect x="81" y="493" width="45" height="17" fill="#5f0000" />
|
||||
<text x="81" y="495" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||
<rect x="126" y="493" width="117" height="17" fill="#5f0000" />
|
||||
<text x="126" y="495" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> oldLine20 = </text>
|
||||
<rect x="243" y="493" width="36" height="17" fill="#5f0000" />
|
||||
<text x="243" y="495" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<rect x="279" y="493" width="9" height="17" fill="#5f0000" />
|
||||
<text x="279" y="495" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="693" y="495" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="495" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="512" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="512" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="36" y="510" width="18" height="17" fill="#005f00" />
|
||||
<text x="36" y="512" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">20</text>
|
||||
<rect x="63" y="493" width="45" height="17" fill="#5f0000" />
|
||||
<text x="63" y="495" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||
<rect x="108" y="493" width="117" height="17" fill="#5f0000" />
|
||||
<text x="108" y="495" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> oldLine20 = </text>
|
||||
<rect x="225" y="493" width="36" height="17" fill="#5f0000" />
|
||||
<text x="225" y="495" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<rect x="261" y="493" width="9" height="17" fill="#5f0000" />
|
||||
<text x="261" y="495" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="711" y="495" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="512" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="18" y="510" width="18" height="17" fill="#005f00" />
|
||||
<text x="18" y="512" fill="#afafaf" textLength="18" lengthAdjust="spacingAndGlyphs">20</text>
|
||||
<rect x="36" y="510" width="9" height="17" fill="#005f00" />
|
||||
<rect x="45" y="510" width="9" height="17" fill="#005f00" />
|
||||
<text x="45" y="512" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">+</text>
|
||||
<rect x="54" y="510" width="9" height="17" fill="#005f00" />
|
||||
<rect x="63" y="510" width="9" height="17" fill="#005f00" />
|
||||
<text x="63" y="512" fill="#d7ffd7" textLength="9" lengthAdjust="spacingAndGlyphs">+</text>
|
||||
<rect x="72" y="510" width="9" height="17" fill="#005f00" />
|
||||
<rect x="81" y="510" width="45" height="17" fill="#005f00" />
|
||||
<text x="81" y="512" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||
<rect x="126" y="510" width="117" height="17" fill="#005f00" />
|
||||
<text x="126" y="512" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> newLine20 = </text>
|
||||
<rect x="243" y="510" width="36" height="17" fill="#005f00" />
|
||||
<text x="243" y="512" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<rect x="279" y="510" width="9" height="17" fill="#005f00" />
|
||||
<text x="279" y="512" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="693" y="512" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="512" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="529" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="529" fill="#333333" textLength="684" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────╯</text>
|
||||
<text x="711" y="529" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="546" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="546" fill="#ffffff" textLength="162" lengthAdjust="spacingAndGlyphs">Apply this change?</text>
|
||||
<text x="711" y="546" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="563" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="563" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="580" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="18" y="578" width="9" height="17" fill="#001a00" />
|
||||
<text x="18" y="580" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">●</text>
|
||||
<rect x="27" y="578" width="9" height="17" fill="#001a00" />
|
||||
<rect x="36" y="578" width="18" height="17" fill="#001a00" />
|
||||
<text x="36" y="580" fill="#00cd00" textLength="18" lengthAdjust="spacingAndGlyphs">1.</text>
|
||||
<rect x="54" y="578" width="9" height="17" fill="#001a00" />
|
||||
<rect x="63" y="578" width="90" height="17" fill="#001a00" />
|
||||
<text x="63" y="580" fill="#00cd00" textLength="90" lengthAdjust="spacingAndGlyphs">Allow once</text>
|
||||
<rect x="153" y="578" width="153" height="17" fill="#001a00" />
|
||||
<text x="711" y="580" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="597" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="597" fill="#ffffff" textLength="18" lengthAdjust="spacingAndGlyphs">2.</text>
|
||||
<text x="63" y="597" fill="#ffffff" textLength="198" lengthAdjust="spacingAndGlyphs">Allow for this session</text>
|
||||
<text x="711" y="597" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="614" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="614" fill="#ffffff" textLength="18" lengthAdjust="spacingAndGlyphs">3.</text>
|
||||
<text x="63" y="614" fill="#ffffff" textLength="243" lengthAdjust="spacingAndGlyphs">Modify with external editor</text>
|
||||
<text x="711" y="614" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="631" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="631" fill="#ffffff" textLength="18" lengthAdjust="spacingAndGlyphs">4.</text>
|
||||
<text x="63" y="631" fill="#ffffff" textLength="225" lengthAdjust="spacingAndGlyphs">No, suggest changes (esc)</text>
|
||||
<text x="711" y="631" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="648" fill="#333333" textLength="720" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────╯</text>
|
||||
<rect x="63" y="510" width="45" height="17" fill="#005f00" />
|
||||
<text x="63" y="512" fill="#0000ee" textLength="45" lengthAdjust="spacingAndGlyphs">const</text>
|
||||
<rect x="108" y="510" width="117" height="17" fill="#005f00" />
|
||||
<text x="108" y="512" fill="#e5e5e5" textLength="117" lengthAdjust="spacingAndGlyphs"> newLine20 = </text>
|
||||
<rect x="225" y="510" width="36" height="17" fill="#005f00" />
|
||||
<text x="225" y="512" fill="#0000ee" textLength="36" lengthAdjust="spacingAndGlyphs">true</text>
|
||||
<rect x="261" y="510" width="9" height="17" fill="#005f00" />
|
||||
<text x="261" y="512" fill="#e5e5e5" textLength="9" lengthAdjust="spacingAndGlyphs">;</text>
|
||||
<text x="711" y="512" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="529" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="529" fill="#ffffff" textLength="162" lengthAdjust="spacingAndGlyphs">Apply this change?</text>
|
||||
<text x="711" y="529" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="546" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="546" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="563" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="18" y="561" width="9" height="17" fill="#001a00" />
|
||||
<text x="18" y="563" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">●</text>
|
||||
<rect x="27" y="561" width="9" height="17" fill="#001a00" />
|
||||
<rect x="36" y="561" width="18" height="17" fill="#001a00" />
|
||||
<text x="36" y="563" fill="#00cd00" textLength="18" lengthAdjust="spacingAndGlyphs">1.</text>
|
||||
<rect x="54" y="561" width="9" height="17" fill="#001a00" />
|
||||
<rect x="63" y="561" width="90" height="17" fill="#001a00" />
|
||||
<text x="63" y="563" fill="#00cd00" textLength="90" lengthAdjust="spacingAndGlyphs">Allow once</text>
|
||||
<rect x="153" y="561" width="153" height="17" fill="#001a00" />
|
||||
<text x="711" y="563" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="580" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="580" fill="#ffffff" textLength="18" lengthAdjust="spacingAndGlyphs">2.</text>
|
||||
<text x="63" y="580" fill="#ffffff" textLength="198" lengthAdjust="spacingAndGlyphs">Allow for this session</text>
|
||||
<text x="711" y="580" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="597" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="597" fill="#ffffff" textLength="18" lengthAdjust="spacingAndGlyphs">3.</text>
|
||||
<text x="63" y="597" fill="#ffffff" textLength="243" lengthAdjust="spacingAndGlyphs">Modify with external editor</text>
|
||||
<text x="711" y="597" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="614" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="614" fill="#ffffff" textLength="18" lengthAdjust="spacingAndGlyphs">4.</text>
|
||||
<text x="63" y="614" fill="#ffffff" textLength="225" lengthAdjust="spacingAndGlyphs">No, suggest changes (esc)</text>
|
||||
<text x="711" y="614" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="631" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="631" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="648" fill="#ffffaf" textLength="720" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────╯</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 38 KiB |
@@ -4,217 +4,153 @@
|
||||
</style>
|
||||
<rect width="740" height="683" fill="#000000" />
|
||||
<g transform="translate(10, 10)">
|
||||
<text x="0" y="2" fill="#333333" textLength="720" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────╮</text>
|
||||
<text x="0" y="19" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="19" fill="#ffffaf" textLength="81" lengthAdjust="spacingAndGlyphs" font-weight="bold">? Shell </text>
|
||||
<text x="99" y="19" fill="#ffffff" textLength="207" lengthAdjust="spacingAndGlyphs">Executes a bash command</text>
|
||||
<text x="0" y="2" fill="#ffffaf" textLength="720" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────────╮</text>
|
||||
<text x="0" y="19" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="19" fill="#ffffaf" textLength="135" lengthAdjust="spacingAndGlyphs" font-weight="bold">Action Required</text>
|
||||
<text x="648" y="19" fill="#afafaf" textLength="54" lengthAdjust="spacingAndGlyphs">2 of 3</text>
|
||||
<text x="711" y="19" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="36" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="36" fill="#333333" textLength="684" lengthAdjust="spacingAndGlyphs">╭──────────────────────────────────────────────────────────────────────────╮</text>
|
||||
<text x="711" y="36" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="53" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="53" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="53" fill="#afafaf" textLength="234" lengthAdjust="spacingAndGlyphs">... 22 hidden (Ctrl+O) ...</text>
|
||||
<text x="693" y="53" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="53" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="70" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="81" y="70" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 23"</text>
|
||||
<text x="693" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="70" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="87" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="81" y="87" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 24"</text>
|
||||
<text x="693" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="87" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="104" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="81" y="104" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 25"</text>
|
||||
<text x="693" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="104" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="121" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="81" y="121" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 26"</text>
|
||||
<text x="693" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="121" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="138" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="81" y="138" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 27"</text>
|
||||
<text x="693" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="138" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="155" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="155" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="155" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="81" y="155" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 28"</text>
|
||||
<text x="693" y="155" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="155" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="172" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="81" y="172" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 29"</text>
|
||||
<text x="693" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="172" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="189" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="189" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="189" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="81" y="189" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 30"</text>
|
||||
<text x="693" y="189" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="189" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="206" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="206" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="206" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="81" y="206" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 31"</text>
|
||||
<text x="693" y="206" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="206" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="223" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="223" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="223" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="81" y="223" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 32"</text>
|
||||
<text x="693" y="223" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="223" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="240" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="240" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="240" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="81" y="240" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 33"</text>
|
||||
<text x="693" y="240" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="240" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="257" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="257" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="257" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="81" y="257" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 34"</text>
|
||||
<text x="693" y="257" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="257" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="274" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="274" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="274" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="81" y="274" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 35"</text>
|
||||
<text x="693" y="274" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="274" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="291" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="291" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="291" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="81" y="291" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 36"</text>
|
||||
<text x="693" y="291" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="291" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="308" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="308" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="308" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="81" y="308" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 37"</text>
|
||||
<text x="693" y="308" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="308" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="325" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="325" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="325" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="81" y="325" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 38"</text>
|
||||
<text x="693" y="325" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="325" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="342" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="342" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="342" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="81" y="342" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 39"</text>
|
||||
<text x="693" y="342" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="342" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="359" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="359" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="359" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="81" y="359" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 40"</text>
|
||||
<text x="693" y="359" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="359" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="376" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="376" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="376" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="81" y="376" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 41"</text>
|
||||
<text x="693" y="376" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="376" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="393" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="393" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="393" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="81" y="393" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 42"</text>
|
||||
<text x="693" y="393" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="393" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="410" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="410" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="410" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="81" y="410" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 43"</text>
|
||||
<text x="693" y="410" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="410" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="427" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="427" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="427" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="81" y="427" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 44"</text>
|
||||
<text x="693" y="427" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="427" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="444" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="444" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="444" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="81" y="444" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 45"</text>
|
||||
<text x="693" y="444" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="444" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="461" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="461" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="461" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="81" y="461" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 46"</text>
|
||||
<text x="693" y="461" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="461" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="478" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="478" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="478" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="81" y="478" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 47"</text>
|
||||
<text x="693" y="478" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="478" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="495" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="495" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="495" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="81" y="495" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 48"</text>
|
||||
<text x="693" y="495" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="495" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="512" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="512" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="512" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="81" y="512" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 49"</text>
|
||||
<text x="693" y="512" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="512" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="529" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="529" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="529" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="81" y="529" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 50"</text>
|
||||
<text x="693" y="529" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="529" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="546" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="546" fill="#333333" textLength="684" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────╯</text>
|
||||
<text x="711" y="546" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="563" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="9" y="563" fill="#ffffff" textLength="180" lengthAdjust="spacingAndGlyphs"> Allow execution of </text>
|
||||
<text x="189" y="563" fill="#ffffaf" textLength="54" lengthAdjust="spacingAndGlyphs" font-weight="bold">[echo]</text>
|
||||
<text x="243" y="563" fill="#ffffff" textLength="468" lengthAdjust="spacingAndGlyphs">? </text>
|
||||
<text x="711" y="563" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="580" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="580" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="597" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="18" y="595" width="9" height="17" fill="#001a00" />
|
||||
<text x="18" y="597" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">●</text>
|
||||
<rect x="27" y="595" width="9" height="17" fill="#001a00" />
|
||||
<rect x="36" y="595" width="18" height="17" fill="#001a00" />
|
||||
<text x="36" y="597" fill="#00cd00" textLength="18" lengthAdjust="spacingAndGlyphs">1.</text>
|
||||
<rect x="54" y="595" width="9" height="17" fill="#001a00" />
|
||||
<rect x="63" y="595" width="90" height="17" fill="#001a00" />
|
||||
<text x="63" y="597" fill="#00cd00" textLength="90" lengthAdjust="spacingAndGlyphs">Allow once</text>
|
||||
<rect x="153" y="595" width="135" height="17" fill="#001a00" />
|
||||
<text x="711" y="597" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="614" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="614" fill="#ffffff" textLength="18" lengthAdjust="spacingAndGlyphs">2.</text>
|
||||
<text x="63" y="614" fill="#ffffff" textLength="198" lengthAdjust="spacingAndGlyphs">Allow for this session</text>
|
||||
<text x="711" y="614" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="631" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="631" fill="#ffffff" textLength="18" lengthAdjust="spacingAndGlyphs">3.</text>
|
||||
<text x="63" y="631" fill="#ffffff" textLength="225" lengthAdjust="spacingAndGlyphs">No, suggest changes (esc)</text>
|
||||
<text x="711" y="631" fill="#333333" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="648" fill="#333333" textLength="720" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────╯</text>
|
||||
<text x="711" y="19" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="36" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="36" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="53" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="53" fill="#87afff" textLength="9" lengthAdjust="spacingAndGlyphs">?</text>
|
||||
<text x="45" y="53" fill="#ffffff" textLength="153" lengthAdjust="spacingAndGlyphs" font-weight="bold">run_shell_command</text>
|
||||
<text x="207" y="53" fill="#afafaf" textLength="207" lengthAdjust="spacingAndGlyphs">Executes a bash command</text>
|
||||
<text x="711" y="53" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="70" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="70" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="87" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="87" fill="#afafaf" textLength="234" lengthAdjust="spacingAndGlyphs">... 24 hidden (Ctrl+O) ...</text>
|
||||
<text x="711" y="87" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="104" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="104" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="63" y="104" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 25"</text>
|
||||
<text x="711" y="104" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="121" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="121" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="63" y="121" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 26"</text>
|
||||
<text x="711" y="121" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="138" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="138" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="63" y="138" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 27"</text>
|
||||
<text x="711" y="138" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="155" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="155" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="63" y="155" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 28"</text>
|
||||
<text x="711" y="155" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="172" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="172" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="63" y="172" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 29"</text>
|
||||
<text x="711" y="172" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="189" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="189" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="63" y="189" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 30"</text>
|
||||
<text x="711" y="189" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="206" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="206" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="63" y="206" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 31"</text>
|
||||
<text x="711" y="206" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="223" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="223" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="63" y="223" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 32"</text>
|
||||
<text x="711" y="223" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="240" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="240" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="63" y="240" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 33"</text>
|
||||
<text x="711" y="240" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="257" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="257" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="63" y="257" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 34"</text>
|
||||
<text x="711" y="257" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="274" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="274" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="63" y="274" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 35"</text>
|
||||
<text x="711" y="274" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="291" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="291" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="63" y="291" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 36"</text>
|
||||
<text x="711" y="291" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="308" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="308" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="63" y="308" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 37"</text>
|
||||
<text x="711" y="308" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="325" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="325" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="63" y="325" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 38"</text>
|
||||
<text x="711" y="325" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="342" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="342" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="63" y="342" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 39"</text>
|
||||
<text x="711" y="342" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="359" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="359" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="63" y="359" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 40"</text>
|
||||
<text x="711" y="359" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="376" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="376" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="63" y="376" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 41"</text>
|
||||
<text x="711" y="376" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="393" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="393" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="63" y="393" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 42"</text>
|
||||
<text x="711" y="393" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="410" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="410" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="63" y="410" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 43"</text>
|
||||
<text x="711" y="410" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="427" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="427" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="63" y="427" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 44"</text>
|
||||
<text x="711" y="427" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="444" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="444" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="63" y="444" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 45"</text>
|
||||
<text x="711" y="444" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="461" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="461" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="63" y="461" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 46"</text>
|
||||
<text x="711" y="461" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="478" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="478" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="63" y="478" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 47"</text>
|
||||
<text x="711" y="478" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="495" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="495" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="63" y="495" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 48"</text>
|
||||
<text x="711" y="495" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="512" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="512" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="63" y="512" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 49"</text>
|
||||
<text x="711" y="512" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="529" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="529" fill="#00cdcd" textLength="36" lengthAdjust="spacingAndGlyphs">echo</text>
|
||||
<text x="63" y="529" fill="#cdcd00" textLength="81" lengthAdjust="spacingAndGlyphs">"Line 50"</text>
|
||||
<text x="711" y="529" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="546" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="18" y="546" fill="#ffffff" textLength="243" lengthAdjust="spacingAndGlyphs">Allow execution of: 'echo'?</text>
|
||||
<text x="711" y="546" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="563" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="563" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="580" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<rect x="18" y="578" width="9" height="17" fill="#001a00" />
|
||||
<text x="18" y="580" fill="#00cd00" textLength="9" lengthAdjust="spacingAndGlyphs">●</text>
|
||||
<rect x="27" y="578" width="9" height="17" fill="#001a00" />
|
||||
<rect x="36" y="578" width="18" height="17" fill="#001a00" />
|
||||
<text x="36" y="580" fill="#00cd00" textLength="18" lengthAdjust="spacingAndGlyphs">1.</text>
|
||||
<rect x="54" y="578" width="9" height="17" fill="#001a00" />
|
||||
<rect x="63" y="578" width="90" height="17" fill="#001a00" />
|
||||
<text x="63" y="580" fill="#00cd00" textLength="90" lengthAdjust="spacingAndGlyphs">Allow once</text>
|
||||
<rect x="153" y="578" width="135" height="17" fill="#001a00" />
|
||||
<text x="711" y="580" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="597" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="597" fill="#ffffff" textLength="18" lengthAdjust="spacingAndGlyphs">2.</text>
|
||||
<text x="63" y="597" fill="#ffffff" textLength="198" lengthAdjust="spacingAndGlyphs">Allow for this session</text>
|
||||
<text x="711" y="597" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="614" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="36" y="614" fill="#ffffff" textLength="18" lengthAdjust="spacingAndGlyphs">3.</text>
|
||||
<text x="63" y="614" fill="#ffffff" textLength="225" lengthAdjust="spacingAndGlyphs">No, suggest changes (esc)</text>
|
||||
<text x="711" y="614" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="631" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="711" y="631" fill="#ffffaf" textLength="9" lengthAdjust="spacingAndGlyphs">│</text>
|
||||
<text x="0" y="648" fill="#ffffaf" textLength="720" lengthAdjust="spacingAndGlyphs">╰──────────────────────────────────────────────────────────────────────────────╯</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 15 KiB |
@@ -2,25 +2,32 @@
|
||||
|
||||
exports[`ToolConfirmationQueue > calculates availableContentHeight based on availableTerminalHeight from UI state 1`] = `
|
||||
"╭──────────────────────────────────────────────────────────────────────────────╮
|
||||
│ ? replace │
|
||||
│ ╭──────────────────────────────────────────────────────────────────────────╮ │
|
||||
│ ╰─... 48 hidden (Ctrl+O) ...───────────────────────────────────────────────╯ │
|
||||
│ Action Required │
|
||||
│ │
|
||||
│ ? replace edit file │
|
||||
│ │
|
||||
│ ... 49 hidden (Ctrl+O) ... │
|
||||
│ 50 line │
|
||||
│ Apply this change? │
|
||||
│ │
|
||||
│ ● 1. Allow once │
|
||||
│ 2. Allow for this session │
|
||||
│ 3. Modify with external editor │
|
||||
│ 4. No, suggest changes (esc) │
|
||||
│ │
|
||||
╰──────────────────────────────────────────────────────────────────────────────╯
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ToolConfirmationQueue > does not render expansion hint when constrainHeight is false 1`] = `
|
||||
"╭──────────────────────────────────────────────────────────────────────────────╮
|
||||
│ ? replace │
|
||||
│ Action Required │
|
||||
│ │
|
||||
│ ? replace edit file │
|
||||
│ │
|
||||
│ ╭──────────────────────────────────────────────────────────────────────────╮ │
|
||||
│ │ │ │
|
||||
│ │ No changes detected. │ │
|
||||
│ │ No changes detected. │ │
|
||||
│ │ │ │
|
||||
│ ╰──────────────────────────────────────────────────────────────────────────╯ │
|
||||
│ Apply this change? │
|
||||
@@ -29,120 +36,131 @@ exports[`ToolConfirmationQueue > does not render expansion hint when constrainHe
|
||||
│ 2. Allow for this session │
|
||||
│ 3. Modify with external editor │
|
||||
│ 4. No, suggest changes (esc) │
|
||||
│ │
|
||||
╰──────────────────────────────────────────────────────────────────────────────╯
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ToolConfirmationQueue > height allocation and layout > should handle security warning height correctly 1`] = `
|
||||
"╭──────────────────────────────────────────────────────────────────────────────╮
|
||||
│ ? Shell Executes a bash command with a deceptive URL 3 of 3 │
|
||||
│ Action Required 3 of 3 │
|
||||
│ │
|
||||
│ ? run_shell_command Executes a bash command with a deceptive URL │
|
||||
│ │
|
||||
│ ... 6 hidden (Ctrl+O) ... │
|
||||
│ │ echo "Line 44" │ │
|
||||
│ │ echo "Line 45" │ │
|
||||
│ │ echo "Line 46" │ │
|
||||
│ │ echo "Line 47" │ │
|
||||
│ │ echo "Line 48" │ │
|
||||
│ │ echo "Line 49" │ │
|
||||
│ │ echo "Line 50" │ │
|
||||
│ │ curl https://täst.com │ │
|
||||
│ ╰──────────────────────────────────────────────────────────────────────────╯ │
|
||||
│ echo "Line 37" │
|
||||
│ echo "Line 38" │
|
||||
│ echo "Line 39" │
|
||||
│ echo "Line 40" │
|
||||
│ echo "Line 41" │
|
||||
│ echo "Line 42" │
|
||||
│ echo "Line 43" │
|
||||
│ echo "Line 44" │
|
||||
│ echo "Line 45" │
|
||||
│ echo "Line 46" │
|
||||
│ echo "Line 47" │
|
||||
│ echo "Line 48" │
|
||||
│ echo "Line 49" │
|
||||
│ echo "Line 50" │
|
||||
│ curl https://täst.com │
|
||||
│ │
|
||||
│ ⚠ Warning: Deceptive URL(s) detected: │
|
||||
│ │
|
||||
│ Original: https://täst.com/ │
|
||||
│ Actual Host (Punycode): https://xn--tst-qla.com/ │
|
||||
│ │
|
||||
│ Allow execution of [echo]? │
|
||||
│ Allow execution of: 'echo'? │
|
||||
│ │
|
||||
│ ● 1. Allow once │
|
||||
│ 2. Allow for this session │
|
||||
│ 3. No, suggest changes (esc) │
|
||||
│ │
|
||||
╰──────────────────────────────────────────────────────────────────────────────╯
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ToolConfirmationQueue > height allocation and layout > should render the full queue wrapper with borders and content for large edit diffs 1`] = `
|
||||
"╭──────────────────────────────────────────────────────────────────────────────╮
|
||||
│ ? replace │
|
||||
│ ╭──────────────────────────────────────────────────────────────────────────╮ │
|
||||
│ │ ... 13 hidden (Ctrl+O) ... │ │
|
||||
│ │ 7 + const newLine7 = true; │ │
|
||||
│ │ 8 - const oldLine8 = true; │ │
|
||||
│ │ 8 + const newLine8 = true; │ │
|
||||
│ │ 9 - const oldLine9 = true; │ │
|
||||
│ │ 9 + const newLine9 = true; │ │
|
||||
│ │ 10 - const oldLine10 = true; │ │
|
||||
│ │ 10 + const newLine10 = true; │ │
|
||||
│ │ 11 - const oldLine11 = true; │ │
|
||||
│ │ 11 + const newLine11 = true; │ │
|
||||
│ │ 12 - const oldLine12 = true; │ │
|
||||
│ │ 12 + const newLine12 = true; │ │
|
||||
│ │ 13 - const oldLine13 = true; │ │
|
||||
│ │ 13 + const newLine13 = true; │ │
|
||||
│ │ 14 - const oldLine14 = true; │ │
|
||||
│ │ 14 + const newLine14 = true; │ │
|
||||
│ │ 15 - const oldLine15 = true; │ │
|
||||
│ │ 15 + const newLine15 = true; │ │
|
||||
│ │ 16 - const oldLine16 = true; │ │
|
||||
│ │ 16 + const newLine16 = true; │ │
|
||||
│ │ 17 - const oldLine17 = true; │ │
|
||||
│ │ 17 + const newLine17 = true; │ │
|
||||
│ │ 18 - const oldLine18 = true; │ │
|
||||
│ │ 18 + const newLine18 = true; │ │
|
||||
│ │ 19 - const oldLine19 = true; │ │
|
||||
│ │ 19 + const newLine19 = true; │ │
|
||||
│ │ 20 - const oldLine20 = true; │ │
|
||||
│ │ 20 + const newLine20 = true; │ │
|
||||
│ ╰──────────────────────────────────────────────────────────────────────────╯ │
|
||||
│ Action Required │
|
||||
│ │
|
||||
│ ? replace Replaces content in a file │
|
||||
│ │
|
||||
│ ... 15 hidden (Ctrl+O) ... │
|
||||
│ 8 + const newLine8 = true; │
|
||||
│ 9 - const oldLine9 = true; │
|
||||
│ 9 + const newLine9 = true; │
|
||||
│ 10 - const oldLine10 = true; │
|
||||
│ 10 + const newLine10 = true; │
|
||||
│ 11 - const oldLine11 = true; │
|
||||
│ 11 + const newLine11 = true; │
|
||||
│ 12 - const oldLine12 = true; │
|
||||
│ 12 + const newLine12 = true; │
|
||||
│ 13 - const oldLine13 = true; │
|
||||
│ 13 + const newLine13 = true; │
|
||||
│ 14 - const oldLine14 = true; │
|
||||
│ 14 + const newLine14 = true; │
|
||||
│ 15 - const oldLine15 = true; │
|
||||
│ 15 + const newLine15 = true; │
|
||||
│ 16 - const oldLine16 = true; │
|
||||
│ 16 + const newLine16 = true; │
|
||||
│ 17 - const oldLine17 = true; │
|
||||
│ 17 + const newLine17 = true; │
|
||||
│ 18 - const oldLine18 = true; │
|
||||
│ 18 + const newLine18 = true; │
|
||||
│ 19 - const oldLine19 = true; │
|
||||
│ 19 + const newLine19 = true; │
|
||||
│ 20 - const oldLine20 = true; │
|
||||
│ 20 + const newLine20 = true; │
|
||||
│ Apply this change? │
|
||||
│ │
|
||||
│ ● 1. Allow once │
|
||||
│ 2. Allow for this session │
|
||||
│ 3. Modify with external editor │
|
||||
│ 4. No, suggest changes (esc) │
|
||||
│ │
|
||||
╰──────────────────────────────────────────────────────────────────────────────╯
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ToolConfirmationQueue > height allocation and layout > should render the full queue wrapper with borders and content for large exec commands 1`] = `
|
||||
"╭──────────────────────────────────────────────────────────────────────────────╮
|
||||
│ ? Shell Executes a bash command 2 of 3 │
|
||||
│ ╭──────────────────────────────────────────────────────────────────────────╮ │
|
||||
│ │ ... 22 hidden (Ctrl+O) ... │ │
|
||||
│ │ echo "Line 23" │ │
|
||||
│ │ echo "Line 24" │ │
|
||||
│ │ echo "Line 25" │ │
|
||||
│ │ echo "Line 26" │ │
|
||||
│ │ echo "Line 27" │ │
|
||||
│ │ echo "Line 28" │ │
|
||||
│ │ echo "Line 29" │ │
|
||||
│ │ echo "Line 30" │ │
|
||||
│ │ echo "Line 31" │ │
|
||||
│ │ echo "Line 32" │ │
|
||||
│ │ echo "Line 33" │ │
|
||||
│ │ echo "Line 34" │ │
|
||||
│ │ echo "Line 35" │ │
|
||||
│ │ echo "Line 36" │ │
|
||||
│ │ echo "Line 37" │ │
|
||||
│ │ echo "Line 38" │ │
|
||||
│ │ echo "Line 39" │ │
|
||||
│ │ echo "Line 40" │ │
|
||||
│ │ echo "Line 41" │ │
|
||||
│ │ echo "Line 42" │ │
|
||||
│ │ echo "Line 43" │ │
|
||||
│ │ echo "Line 44" │ │
|
||||
│ │ echo "Line 45" │ │
|
||||
│ │ echo "Line 46" │ │
|
||||
│ │ echo "Line 47" │ │
|
||||
│ │ echo "Line 48" │ │
|
||||
│ │ echo "Line 49" │ │
|
||||
│ │ echo "Line 50" │ │
|
||||
│ ╰──────────────────────────────────────────────────────────────────────────╯ │
|
||||
│ Allow execution of [echo]? │
|
||||
│ Action Required 2 of 3 │
|
||||
│ │
|
||||
│ ? run_shell_command Executes a bash command │
|
||||
│ │
|
||||
│ ... 24 hidden (Ctrl+O) ... │
|
||||
│ echo "Line 25" │
|
||||
│ echo "Line 26" │
|
||||
│ echo "Line 27" │
|
||||
│ echo "Line 28" │
|
||||
│ echo "Line 29" │
|
||||
│ echo "Line 30" │
|
||||
│ echo "Line 31" │
|
||||
│ echo "Line 32" │
|
||||
│ echo "Line 33" │
|
||||
│ echo "Line 34" │
|
||||
│ echo "Line 35" │
|
||||
│ echo "Line 36" │
|
||||
│ echo "Line 37" │
|
||||
│ echo "Line 38" │
|
||||
│ echo "Line 39" │
|
||||
│ echo "Line 40" │
|
||||
│ echo "Line 41" │
|
||||
│ echo "Line 42" │
|
||||
│ echo "Line 43" │
|
||||
│ echo "Line 44" │
|
||||
│ echo "Line 45" │
|
||||
│ echo "Line 46" │
|
||||
│ echo "Line 47" │
|
||||
│ echo "Line 48" │
|
||||
│ echo "Line 49" │
|
||||
│ echo "Line 50" │
|
||||
│ Allow execution of: 'echo'? │
|
||||
│ │
|
||||
│ ● 1. Allow once │
|
||||
│ 2. Allow for this session │
|
||||
│ 3. No, suggest changes (esc) │
|
||||
│ │
|
||||
╰──────────────────────────────────────────────────────────────────────────────╯
|
||||
"
|
||||
`;
|
||||
@@ -191,22 +209,24 @@ exports[`ToolConfirmationQueue > renders ExitPlanMode tool confirmation with Suc
|
||||
│ Approves plan but requires confirmation for each tool │
|
||||
│ 3. Type your feedback... │
|
||||
│ │
|
||||
│ Enter to select · ↑/↓ to navigate · Ctrl+G to edit plan · Esc to cancel │
|
||||
│ Enter to select · ↑/↓ to navigate · Ctrl+X to edit plan · Esc to cancel │
|
||||
╰──────────────────────────────────────────────────────────────────────────────╯
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`ToolConfirmationQueue > renders the confirming tool with progress indicator 1`] = `
|
||||
"╭──────────────────────────────────────────────────────────────────────────────╮
|
||||
│ ? Shell list files 1 of 3 │
|
||||
│ ╭──────────────────────────────────────────────────────────────────────────╮ │
|
||||
│ │ ls │ │
|
||||
│ ╰──────────────────────────────────────────────────────────────────────────╯ │
|
||||
│ Allow execution of [ls]? │
|
||||
│ Action Required 1 of 3 │
|
||||
│ │
|
||||
│ ? ls list files │
|
||||
│ │
|
||||
│ ls │
|
||||
│ Allow execution of: 'ls'? │
|
||||
│ │
|
||||
│ ● 1. Allow once │
|
||||
│ 2. Allow for this session │
|
||||
│ 3. No, suggest changes (esc) │
|
||||
│ │
|
||||
╰──────────────────────────────────────────────────────────────────────────────╯
|
||||
"
|
||||
`;
|
||||
|
||||
@@ -48,18 +48,15 @@ index 0000000..e69de29
|
||||
},
|
||||
);
|
||||
await waitFor(() =>
|
||||
expect(mockColorizeCode).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
code: 'print("hello world")',
|
||||
language: 'python',
|
||||
availableHeight: undefined,
|
||||
maxWidth: 80,
|
||||
theme: undefined,
|
||||
settings: expect.anything(),
|
||||
disableColor: false,
|
||||
paddingX: 0,
|
||||
}),
|
||||
),
|
||||
expect(mockColorizeCode).toHaveBeenCalledWith({
|
||||
code: 'print("hello world")',
|
||||
language: 'python',
|
||||
availableHeight: undefined,
|
||||
maxWidth: 80,
|
||||
theme: undefined,
|
||||
settings: expect.anything(),
|
||||
disableColor: false,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -86,18 +83,15 @@ index 0000000..e69de29
|
||||
},
|
||||
);
|
||||
await waitFor(() =>
|
||||
expect(mockColorizeCode).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
code: 'some content',
|
||||
language: null,
|
||||
availableHeight: undefined,
|
||||
maxWidth: 80,
|
||||
theme: undefined,
|
||||
settings: expect.anything(),
|
||||
disableColor: false,
|
||||
paddingX: 0,
|
||||
}),
|
||||
),
|
||||
expect(mockColorizeCode).toHaveBeenCalledWith({
|
||||
code: 'some content',
|
||||
language: null,
|
||||
availableHeight: undefined,
|
||||
maxWidth: 80,
|
||||
theme: undefined,
|
||||
settings: expect.anything(),
|
||||
disableColor: false,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -120,18 +114,15 @@ index 0000000..e69de29
|
||||
},
|
||||
);
|
||||
await waitFor(() =>
|
||||
expect(mockColorizeCode).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
code: 'some text content',
|
||||
language: null,
|
||||
availableHeight: undefined,
|
||||
maxWidth: 80,
|
||||
theme: undefined,
|
||||
settings: expect.anything(),
|
||||
disableColor: false,
|
||||
paddingX: 0,
|
||||
}),
|
||||
),
|
||||
expect(mockColorizeCode).toHaveBeenCalledWith({
|
||||
code: 'some text content',
|
||||
language: null,
|
||||
availableHeight: undefined,
|
||||
maxWidth: 80,
|
||||
theme: undefined,
|
||||
settings: expect.anything(),
|
||||
disableColor: false,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -32,7 +32,6 @@ export function parseDiffWithLineNumbers(diffContent: string): DiffLine[] {
|
||||
for (const line of lines) {
|
||||
const hunkMatch = line.match(hunkHeaderRegex);
|
||||
if (hunkMatch) {
|
||||
currentOldLine = parseInt(hunkMatch[1], 10);
|
||||
currentOldLine = parseInt(hunkMatch[1], 10);
|
||||
currentNewLine = parseInt(hunkMatch[2], 10);
|
||||
inHunk = true;
|
||||
@@ -90,7 +89,6 @@ interface DiffRendererProps {
|
||||
terminalWidth: number;
|
||||
theme?: Theme;
|
||||
disableColor?: boolean;
|
||||
paddingX?: number;
|
||||
}
|
||||
|
||||
const DEFAULT_TAB_WIDTH = 4; // Spaces per tab for normalization
|
||||
@@ -103,7 +101,6 @@ export const DiffRenderer: React.FC<DiffRendererProps> = ({
|
||||
terminalWidth,
|
||||
theme,
|
||||
disableColor = false,
|
||||
paddingX = 0,
|
||||
}) => {
|
||||
const settings = useSettings();
|
||||
|
||||
@@ -125,7 +122,11 @@ export const DiffRenderer: React.FC<DiffRendererProps> = ({
|
||||
|
||||
if (parsedLines.length === 0) {
|
||||
return (
|
||||
<Box padding={1}>
|
||||
<Box
|
||||
borderStyle="round"
|
||||
borderColor={semanticTheme.border.default}
|
||||
padding={1}
|
||||
>
|
||||
<Text dimColor>No changes detected.</Text>
|
||||
</Box>
|
||||
);
|
||||
@@ -161,14 +162,12 @@ export const DiffRenderer: React.FC<DiffRendererProps> = ({
|
||||
theme,
|
||||
settings,
|
||||
disableColor,
|
||||
paddingX,
|
||||
});
|
||||
} else {
|
||||
const key = filename ? `diff-box-${filename}` : undefined;
|
||||
|
||||
return (
|
||||
<MaxSizedBox
|
||||
paddingX={paddingX}
|
||||
maxHeight={availableTerminalHeight}
|
||||
maxWidth={terminalWidth}
|
||||
key={key}
|
||||
@@ -195,7 +194,6 @@ export const DiffRenderer: React.FC<DiffRendererProps> = ({
|
||||
settings,
|
||||
tabWidth,
|
||||
disableColor,
|
||||
paddingX,
|
||||
]);
|
||||
|
||||
return renderedOutput;
|
||||
@@ -241,7 +239,12 @@ export const renderDiffLines = ({
|
||||
|
||||
if (displayableLines.length === 0) {
|
||||
return [
|
||||
<Box key="no-changes" padding={1}>
|
||||
<Box
|
||||
key="no-changes"
|
||||
borderStyle="round"
|
||||
borderColor={semanticTheme.border.default}
|
||||
padding={1}
|
||||
>
|
||||
<Text dimColor>No changes detected.</Text>
|
||||
</Box>,
|
||||
];
|
||||
|
||||
@@ -12,7 +12,6 @@ import { RenderInline } from '../../utils/InlineMarkdownRenderer.js';
|
||||
interface InfoMessageProps {
|
||||
text: string;
|
||||
secondaryText?: string;
|
||||
source?: string;
|
||||
icon?: string;
|
||||
color?: string;
|
||||
marginBottom?: number;
|
||||
@@ -21,7 +20,6 @@ interface InfoMessageProps {
|
||||
export const InfoMessage: React.FC<InfoMessageProps> = ({
|
||||
text,
|
||||
secondaryText,
|
||||
source,
|
||||
icon,
|
||||
color,
|
||||
marginBottom,
|
||||
@@ -42,9 +40,6 @@ export const InfoMessage: React.FC<InfoMessageProps> = ({
|
||||
{index === text.split('\n').length - 1 && secondaryText && (
|
||||
<Text color={theme.text.secondary}> {secondaryText}</Text>
|
||||
)}
|
||||
{index === text.split('\n').length - 1 && source && (
|
||||
<Text color={theme.text.secondary}> [{source}]</Text>
|
||||
)}
|
||||
</Text>
|
||||
))}
|
||||
</Box>
|
||||
|
||||
@@ -42,7 +42,6 @@ describe('ToolConfirmationMessage Redirection', () => {
|
||||
getPreferredEditor={vi.fn()}
|
||||
availableTerminalHeight={30}
|
||||
terminalWidth={100}
|
||||
toolName="shell"
|
||||
/>,
|
||||
);
|
||||
|
||||
|
||||
@@ -293,8 +293,8 @@ describe('<ShellToolMessage />', () => {
|
||||
await waitUntilReady();
|
||||
const frame = lastFrame();
|
||||
// Since it's Executing, it might still constrain to ACTIVE_SHELL_MAX_LINES (10)
|
||||
// Actually let's just assert on the behaviour that happens right now (which is 100 lines because we removed the terminalBuffer check)
|
||||
expect(frame.match(/Line \d+/g)?.length).toBe(100);
|
||||
// Actually let's just assert on the behaviour that happens right now (which is 10 lines)
|
||||
expect(frame.match(/Line \d+/g)?.length).toBe(10);
|
||||
unmount();
|
||||
});
|
||||
|
||||
|
||||
@@ -62,7 +62,6 @@ describe('ToolConfirmationMessage', () => {
|
||||
getPreferredEditor={vi.fn()}
|
||||
availableTerminalHeight={30}
|
||||
terminalWidth={80}
|
||||
toolName="shell"
|
||||
/>,
|
||||
);
|
||||
|
||||
@@ -89,7 +88,6 @@ describe('ToolConfirmationMessage', () => {
|
||||
getPreferredEditor={vi.fn()}
|
||||
availableTerminalHeight={30}
|
||||
terminalWidth={80}
|
||||
toolName="shell"
|
||||
/>,
|
||||
);
|
||||
|
||||
@@ -113,7 +111,6 @@ describe('ToolConfirmationMessage', () => {
|
||||
getPreferredEditor={vi.fn()}
|
||||
availableTerminalHeight={30}
|
||||
terminalWidth={80}
|
||||
toolName="shell"
|
||||
/>,
|
||||
);
|
||||
|
||||
@@ -143,7 +140,6 @@ describe('ToolConfirmationMessage', () => {
|
||||
getPreferredEditor={vi.fn()}
|
||||
availableTerminalHeight={30}
|
||||
terminalWidth={80}
|
||||
toolName="shell"
|
||||
/>,
|
||||
);
|
||||
|
||||
@@ -173,7 +169,6 @@ describe('ToolConfirmationMessage', () => {
|
||||
getPreferredEditor={vi.fn()}
|
||||
availableTerminalHeight={30}
|
||||
terminalWidth={80}
|
||||
toolName="shell"
|
||||
/>,
|
||||
);
|
||||
|
||||
@@ -202,7 +197,6 @@ describe('ToolConfirmationMessage', () => {
|
||||
getPreferredEditor={vi.fn()}
|
||||
availableTerminalHeight={30}
|
||||
terminalWidth={80}
|
||||
toolName="shell"
|
||||
/>,
|
||||
);
|
||||
|
||||
@@ -231,7 +225,6 @@ describe('ToolConfirmationMessage', () => {
|
||||
getPreferredEditor={vi.fn()}
|
||||
availableTerminalHeight={30}
|
||||
terminalWidth={80}
|
||||
toolName="shell"
|
||||
/>,
|
||||
);
|
||||
|
||||
@@ -260,7 +253,6 @@ describe('ToolConfirmationMessage', () => {
|
||||
getPreferredEditor={vi.fn()}
|
||||
availableTerminalHeight={30}
|
||||
terminalWidth={80}
|
||||
toolName="shell"
|
||||
/>,
|
||||
);
|
||||
await result.waitUntilReady();
|
||||
@@ -346,7 +338,6 @@ describe('ToolConfirmationMessage', () => {
|
||||
getPreferredEditor={vi.fn()}
|
||||
availableTerminalHeight={30}
|
||||
terminalWidth={80}
|
||||
toolName="shell"
|
||||
/>,
|
||||
);
|
||||
|
||||
@@ -370,7 +361,6 @@ describe('ToolConfirmationMessage', () => {
|
||||
getPreferredEditor={vi.fn()}
|
||||
availableTerminalHeight={30}
|
||||
terminalWidth={80}
|
||||
toolName="shell"
|
||||
/>,
|
||||
);
|
||||
|
||||
@@ -406,7 +396,6 @@ describe('ToolConfirmationMessage', () => {
|
||||
getPreferredEditor={vi.fn()}
|
||||
availableTerminalHeight={30}
|
||||
terminalWidth={80}
|
||||
toolName="shell"
|
||||
/>,
|
||||
{
|
||||
settings: createMockSettings({
|
||||
@@ -434,7 +423,6 @@ describe('ToolConfirmationMessage', () => {
|
||||
getPreferredEditor={vi.fn()}
|
||||
availableTerminalHeight={30}
|
||||
terminalWidth={80}
|
||||
toolName="shell"
|
||||
/>,
|
||||
{
|
||||
settings: createMockSettings({
|
||||
@@ -486,7 +474,6 @@ describe('ToolConfirmationMessage', () => {
|
||||
getPreferredEditor={vi.fn()}
|
||||
availableTerminalHeight={30}
|
||||
terminalWidth={80}
|
||||
toolName="shell"
|
||||
/>,
|
||||
);
|
||||
|
||||
@@ -518,7 +505,6 @@ describe('ToolConfirmationMessage', () => {
|
||||
getPreferredEditor={vi.fn()}
|
||||
availableTerminalHeight={30}
|
||||
terminalWidth={80}
|
||||
toolName="shell"
|
||||
/>,
|
||||
);
|
||||
|
||||
@@ -550,7 +536,6 @@ describe('ToolConfirmationMessage', () => {
|
||||
getPreferredEditor={vi.fn()}
|
||||
availableTerminalHeight={30}
|
||||
terminalWidth={80}
|
||||
toolName="shell"
|
||||
/>,
|
||||
);
|
||||
|
||||
@@ -577,7 +562,6 @@ describe('ToolConfirmationMessage', () => {
|
||||
getPreferredEditor={vi.fn()}
|
||||
availableTerminalHeight={30}
|
||||
terminalWidth={80}
|
||||
toolName="shell"
|
||||
/>,
|
||||
);
|
||||
|
||||
@@ -623,7 +607,6 @@ describe('ToolConfirmationMessage', () => {
|
||||
getPreferredEditor={vi.fn()}
|
||||
availableTerminalHeight={30}
|
||||
terminalWidth={80}
|
||||
toolName="shell"
|
||||
/>,
|
||||
);
|
||||
|
||||
@@ -655,7 +638,6 @@ describe('ToolConfirmationMessage', () => {
|
||||
getPreferredEditor={vi.fn()}
|
||||
availableTerminalHeight={30}
|
||||
terminalWidth={80}
|
||||
toolName="shell"
|
||||
/>,
|
||||
);
|
||||
|
||||
@@ -690,14 +672,13 @@ describe('ToolConfirmationMessage', () => {
|
||||
getPreferredEditor={vi.fn()}
|
||||
availableTerminalHeight={40}
|
||||
terminalWidth={80}
|
||||
toolName="shell"
|
||||
/>,
|
||||
);
|
||||
await waitUntilReady();
|
||||
|
||||
const outputLines = lastFrame().split('\n');
|
||||
// Should use the entire terminal height
|
||||
expect(outputLines.length).toBe(40);
|
||||
// Should use the entire terminal height minus 1 line for the "Press Ctrl+O to show more lines" hint
|
||||
expect(outputLines.length).toBe(39);
|
||||
|
||||
await expect({ lastFrame, generateSvg }).toMatchSvgSnapshot();
|
||||
unmount();
|
||||
@@ -731,14 +712,13 @@ describe('ToolConfirmationMessage', () => {
|
||||
getPreferredEditor={vi.fn()}
|
||||
availableTerminalHeight={40}
|
||||
terminalWidth={80}
|
||||
toolName="shell"
|
||||
/>,
|
||||
);
|
||||
await waitUntilReady();
|
||||
|
||||
const outputLines = lastFrame().split('\n');
|
||||
// Should use the entire terminal height
|
||||
expect(outputLines.length).toBe(40);
|
||||
// Should use the entire terminal height minus 1 line for the "Press Ctrl+O to show more lines" hint
|
||||
expect(outputLines.length).toBe(39);
|
||||
|
||||
await expect({ lastFrame, generateSvg }).toMatchSvgSnapshot();
|
||||
unmount();
|
||||
@@ -781,7 +761,6 @@ describe('ToolConfirmationMessage', () => {
|
||||
getPreferredEditor={vi.fn()}
|
||||
availableTerminalHeight={30}
|
||||
terminalWidth={80}
|
||||
toolName="shell"
|
||||
/>,
|
||||
);
|
||||
|
||||
|
||||