first pass of merging in warp (doesn't build)
This commit is contained in:
+1
-1
@@ -32,7 +32,7 @@ Code syntax is a surface area in Figma for codebase translation context. You can
|
||||
|
||||
### Scope
|
||||
|
||||
`variable.scopes: VariableScope[]` specifies which properties in Figma the variable can be used for. This is important when you create and when you use variables. **Always set specific scopes rather than leaving the default `ALL_SCOPES`** — it pollutes every property picker with irrelevant tokens. The more specific the better. For the canonical scope-to-use-case mapping, see [token-creation.md § Variable Scopes — Complete Reference Table](../../figma-generate-library/references/token-creation.md).
|
||||
`variable.scopes: VariableScope[]` specifies which properties in Figma the variable can be used for. This is important when you create and when you use variables. **Always set specific scopes rather than leaving the default `ALL_SCOPES`** — it pollutes every property picker with irrelevant tokens. The more specific the better. For the canonical scope-to-use-case mapping, see [token-creation.md § Variable Scopes — Complete Reference Table](../../../figma-generate-library/references/token-creation.md).
|
||||
|
||||
Common scope values:
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
name: add-mcp-server
|
||||
name: agent-add-mcp
|
||||
description: Use this skill when helping users add MCP servers to their Warp configuration.
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
---
|
||||
name: change-keybinding
|
||||
description: Customize Warp keyboard shortcuts (keybindings, keymappings) by editing the user's keybindings.yaml file. Use when the user asks to remap a key combination, rebind an action, change a shortcut, or remove a default keybinding (e.g. "change ctrl+space to ctrl+s", "rebind the command palette to cmd+p", "remove the default for X").
|
||||
---
|
||||
|
||||
# change-keybinding
|
||||
|
||||
Use this skill when the user wants to remap, rebind, or remove a Warp keyboard shortcut.
|
||||
|
||||
## Keybindings file
|
||||
|
||||
User customizations live at:
|
||||
|
||||
```
|
||||
{{keybindings_file_path}}
|
||||
```
|
||||
|
||||
This is the exact path Warp reads at launch — it is platform- and channel-specific (e.g. under `~/.warp*/` on macOS, under XDG config dirs like `~/.config/warp-terminal/` on Linux, and under `%LocalAppData%` on Windows). Use this path verbatim — do not infer a different one from the user's home directory layout. Create the file (and any missing parent directories) if it does not exist.
|
||||
|
||||
## File format
|
||||
|
||||
A flat YAML map of `action_name` → `key_trigger`. Action names contain a colon, so they **must be quoted**:
|
||||
|
||||
```yaml
|
||||
"workspace:toggle_ai_assistant": ctrl-s
|
||||
"editor_view:delete_all_left": cmd-shift-A
|
||||
"workspace:toggle_command_palette": none
|
||||
```
|
||||
|
||||
## Keystroke encoding rules
|
||||
|
||||
Triggers use Warp's normalized form — get this exactly right or the binding silently fails to load.
|
||||
|
||||
- **Modifiers** (in this order when combined): `ctrl-alt-shift-cmd-meta-`. Cross-platform alias: `cmdorctrl-` (becomes `cmd` on macOS, `ctrl` elsewhere).
|
||||
- **Letter casing**: applies only to single-letter keys. Without `shift`, the letter is lowercase (`ctrl-s`). With `shift`, the letter is **uppercase** (`shift-A`, never `shift-a`). Mixing them is invalid.
|
||||
- **Special keys**: `space`, `enter`, `escape`, `tab`, `backspace`, `delete`, `insert`, `up`, `down`, `left`, `right`, `home`, `end`, `pageup`, `pagedown`, `f1`–`f20`, `numpadenter`. Always lowercase, even with `shift` (`ctrl-shift-space`, `shift-tab` — never `ctrl-shift-SPACE`). Use the literal word `space` — not `" "`.
|
||||
- **Punctuation** is the bare character: `cmd-=`, `cmd-,`, `cmdorctrl-/`.
|
||||
- **Remove a default binding**: set the value to the literal string `none`. The action becomes unbound.
|
||||
|
||||
Translate user phrasing into this form: `Ctrl+S` → `ctrl-s`, `Cmd+Shift+P` → `cmd-shift-P`, `Ctrl+Space` → `ctrl-space`.
|
||||
|
||||
## Identifying the action
|
||||
|
||||
Defaults are compiled into Warp and are **not** discoverable from the keybindings file on disk. There is no catalog the agent can consult to map a description or current shortcut to an action name. Pick the right strategy based on how the user described the change:
|
||||
|
||||
1. **By action name** ("set workspace:toggle_command_palette to cmd-p"): the user already gave you the name — write it directly.
|
||||
|
||||
2. **By description or current key combo** ("rebind the command palette to cmd-p", "change ctrl+space to ctrl+s"): you don't have the action name and cannot reliably guess it. Do not invent one. Direct the user to the **keybindings editor** (`workspace:show_keybinding_settings`, default `cmd-ctrl-k` on macOS; **Settings → Keyboard Shortcuts** on other platforms) — they can search by description or current shortcut there and either edit the binding in place or share the canonical `namespace:action_name` so you can write it.
|
||||
|
||||
## Workflow
|
||||
|
||||
1. Determine which action to remap and the new trigger (see "Identifying the action").
|
||||
2. Read the existing keybindings file at `{{keybindings_file_path}}` if present. **Preserve every existing entry** — only add or update the one you're changing.
|
||||
3. Write the file (creating parent directories if necessary). Make sure the action key is quoted and the value is normalized (see encoding rules).
|
||||
4. Tell the user that **Warp must be restarted** for the change to take effect — the keybindings file is loaded only at app launch, unlike `settings.toml` which hot-reloads. They can quit with `cmd-Q` (macOS) or the equivalent on their platform and reopen Warp.
|
||||
|
||||
## Examples
|
||||
|
||||
Remap an existing custom binding by old trigger:
|
||||
|
||||
```yaml
|
||||
# before
|
||||
"workspace:toggle_ai_assistant": ctrl-space
|
||||
# after
|
||||
"workspace:toggle_ai_assistant": ctrl-s
|
||||
```
|
||||
|
||||
Remove a default shortcut:
|
||||
|
||||
```yaml
|
||||
"workspace:toggle_keybindings_page": none
|
||||
```
|
||||
|
||||
Shift combined with a special key (note the special key stays lowercase):
|
||||
|
||||
```yaml
|
||||
"workspace:toggle_ai_assistant": ctrl-shift-space
|
||||
```
|
||||
|
||||
Cross-platform binding using the `cmdorctrl-` alias (resolves to `cmd` on macOS, `ctrl` elsewhere):
|
||||
|
||||
```yaml
|
||||
"workspace:toggle_command_palette": cmdorctrl-shift-P
|
||||
```
|
||||
@@ -227,7 +227,7 @@ Once all runs are done:
|
||||
|
||||
2. **Aggregate into benchmark** — run the aggregation script from the skill-creator directory:
|
||||
```bash
|
||||
python -m scripts.aggregate_benchmark <workspace>/iteration-N --skill-name <name>
|
||||
python {{skill_dir}}/scripts/aggregate_benchmark.py <workspace>/iteration-N --skill-name <name>
|
||||
```
|
||||
This produces `benchmark.json` and `benchmark.md` with pass_rate, time, and tokens for each configuration, with mean ± stddev and the delta. If generating benchmark.json manually, see `references/schemas.md` for the exact schema the viewer expects.
|
||||
Put each with_skill version before its baseline counterpart.
|
||||
@@ -236,7 +236,7 @@ Put each with_skill version before its baseline counterpart.
|
||||
|
||||
4. **Launch the viewer** with both qualitative outputs and quantitative data:
|
||||
```bash
|
||||
nohup python <skill-creator-path>/eval-viewer/generate_review.py \
|
||||
nohup python {{skill_dir}}/eval-viewer/generate_review.py \
|
||||
<workspace>/iteration-N \
|
||||
--skill-name "my-skill" \
|
||||
--benchmark <workspace>/iteration-N/benchmark.json \
|
||||
|
||||
@@ -1,240 +0,0 @@
|
||||
---
|
||||
name: feedback
|
||||
description: Turn rough feedback about the Warp app into a filed GitHub issue or duplicate-issue response for `warpdotdev/warp`. Use when the user shares a Warp bug report, regression, confusing UX note, feature gap, or short complaint and wants it clarified into reproduction steps, expected vs actual behavior, concrete impact, and grounded source references from local Warp repos when available.
|
||||
---
|
||||
|
||||
# Feedback
|
||||
|
||||
Turn rough Warp app feedback into a crisp filed issue or duplicate-issue response for `warpdotdev/warp`.
|
||||
|
||||
Treat Warp client, Warp app, Warp terminal, and Warp UX feedback as `warpdotdev/warp` unless the user clearly asks for a different destination.
|
||||
|
||||
## Overview
|
||||
- Use the `gh` CLI to search for and fetch code from `warpdotdev/warp` when product or implementation context would improve the report.
|
||||
- If those repos are not available, draft the issue from the user's report alone rather than blocking on more context.
|
||||
- This skill is strictly for issue filing and duplicate detection. Never modify code, generate patches, propose implementation diffs, or open a pull request as part of this workflow.
|
||||
- If you cannot file an issue, say so explicitly in the response instead of attempting another side effect.
|
||||
- The helper script applies the `in-app-feedback` label to filed issues for tracking.
|
||||
|
||||
## Code access boundaries
|
||||
|
||||
This skill runs in environments where Warp source code may be present in the current working directory or on disk. The following rules apply unconditionally regardless of what source code is visible locally:
|
||||
|
||||
- **Never write, edit, or delete any source file.** Do not use Edit, Write, or any tool that modifies files on disk, even if asked to do so as part of filing feedback or "while you're in the code."
|
||||
- **Never create or modify any git artifact.** Do not stage files, create commits, create branches, produce patches, or modify any git state.
|
||||
- **Local source code is read-only context at most.** You may read local Warp source files (e.g., with Read or grep) only to find concrete file paths, symbol names, or setting names that would make a source reference more precise. Never read local files to produce a code fix or diff.
|
||||
- **Prefer `gh` CLI for code lookups.** Use `gh` to search and fetch code from `warpdotdev/warp` rather than reading the local checkout when both are available.
|
||||
- **The presence of local source code is not an invitation to fix it.** Observing that you are inside a Warp source directory changes nothing about the permitted outputs of this skill: issue filed, duplicate found, or explicit refusal.
|
||||
|
||||
Load the bundled reference files only when relevant:
|
||||
- platform and OS-version resolution, plus operating-system-specific behavior: `references/platforms.md`
|
||||
- logs and crash artifacts: `references/logs.md`
|
||||
- output calibration examples: `references/examples.md`
|
||||
|
||||
## Workflow
|
||||
|
||||
### 1. Confirm scope and classify the report
|
||||
|
||||
- This skill only handles feedback about the Warp product that could plausibly be addressed by a code or docs change to the Warp client, server, or SDKs. Before drafting anything, verify the request is in scope.
|
||||
- **Decline and exit the skill (do not call the helper script) when the report is clearly out of scope.** Out-of-scope categories include, but are not limited to:
|
||||
- Account, billing, subscription, plan, credits, refund, or invoice questions.
|
||||
- Login, authentication, SSO, password, or session-expiry problems.
|
||||
- Requests to contact human support, sales, or legal.
|
||||
- General venting, praise, or commentary with no actionable product signal.
|
||||
- Questions about third-party tools or the user's own shell, machine, or network configuration that are not about Warp's behavior.
|
||||
- Anything the user explicitly says is not about Warp, or that they just want to talk through.
|
||||
- When you decline, respond in one or two sentences that (a) say you won't file an issue, (b) name the reason in plain language, and (c) point the user at the right channel: account/billing/support concerns go to the in-app Help menu or `support@warp.dev`, community discussion goes to the Warp Slack community, and security reports go to `security@warp.dev`. Do not apologize performatively and do not offer to retry the same flow.
|
||||
- Only if the request is in scope, classify it as `bug`, `regression`, `ux issue`, or `feature request` before drafting.
|
||||
|
||||
### 2. Ask only for missing facts that materially improve the draft
|
||||
|
||||
- Before drafting, decide whether the report already contains the minimum actionable information: what the user was doing, what they expected, and what happened (for bugs, regressions, and UX issues) or what they want to be able to do and why (for feature requests). If any of those pieces is missing, run a single focused clarifying round.
|
||||
- Use the `ask_user_question` tool for that round. Ask 3-4 high-value multiple-choice questions in a single call, focused on user experience and expectations: what the user was trying to do, what felt confusing or broken, what they expected to happen instead, where in the product they hit the issue, and how much it blocked them.
|
||||
- Follow the tool guidance where possible: only ask when necessary, do not add labels like `Select One` or `Select All that Apply`, and if fixed options are too limiting, include an `Other` option. If the user skips a question, proceed with your best judgment on what they did answer.
|
||||
- **Run at most one clarifying round.** If after that round the minimum actionable information is still missing, decline to file rather than drafting a weak issue. Tell the user in one or two sentences exactly which specifics would unblock a future report (for example: "A short description of what you were doing when it happened and what you expected instead would let us turn this into an actionable bug report."). Do not file a placeholder issue just to close the loop.
|
||||
- For bugs and regressions, first read `references/platforms.md` and try to resolve Warp version and operating system from the bundled version metadata and available context. Ask for reproduction steps only when they are not already clear, and for regressions in particular only when the flow is not readily available from the report or supporting context.
|
||||
- For crashes, startup failures, rendering bugs, sync issues, or hard-to-reproduce regressions, ask for logs or crash artifacts only when they are likely to help. Read `references/logs.md` only when needed.
|
||||
- If operating system version, Warp version, or operating-system-specific behavior is relevant, read `references/platforms.md` and follow the bundled metadata guidance there yourself when possible. Ask the user only if you still cannot determine the necessary platform details.
|
||||
|
||||
### 3. Check whether the feature or capability is already supported
|
||||
|
||||
- Before concluding that something is missing from Warp (feature requests, "it doesn't do X" complaints, "I wish it could Y" asks, or any UX complaint that could be explained by an existing setting or workflow), you **must** consult the docs first.
|
||||
- Call the `search_warp_documentation` tool with the user's own phrasing. If the first query is vague or returns nothing actionable, try one shorter variant that keeps the same user-visible problem.
|
||||
- If the search returns a clear match, respond with a concise, direct answer that cites the docs page (title + URL) and explains how the existing functionality addresses the user's ask. Do not file an issue and do not invoke the helper script.
|
||||
- If the search returns an ambiguous or partial match, briefly summarize what does exist and ask one clarifying question about whether that satisfies the user's intent before deciding whether to file.
|
||||
- If the search turns up nothing relevant, proceed to step 4. Do not invent workarounds, and do not imply a feature is missing when the docs already answer the question.
|
||||
- Docs-first checking applies primarily to feature-request-shaped reports. For reproducible bugs and regressions, skip ahead to step 4 unless docs would clarify whether the current behavior is intended.
|
||||
|
||||
### 4. Ground the report in product and code context when helpful
|
||||
|
||||
- Search the `warpdotdev/warp` repo via the `gh` CLI for matching product language, expected workflows, setting names, or UX intent when that context would make the draft more actionable.
|
||||
- Search the `warpdotdev/warp` repo via the `gh` CLI for matching components, settings surfaces, feature flags, and likely code paths when implementation context would help triage.
|
||||
- Add source references only when they point to real files, symbols, settings names, or spec text that plausibly relate to the feedback.
|
||||
- Never invent a root cause just to make the report sound complete.
|
||||
|
||||
### 5. Draft the issue
|
||||
|
||||
- Keep the title concrete and user-visible.
|
||||
- Rewrite rough notes into a polished issue body with the shared section structure below.
|
||||
- Preserve the user's meaning while making the report easier for an engineer to act on.
|
||||
- If the exact reproduction steps are still uncertain, write the best-supported scenario and call out what is still unknown.
|
||||
- Make the title specific enough that it can be used as the primary duplicate-detection query.
|
||||
|
||||
### 6. Handle image attachments, if present
|
||||
|
||||
- If the user's query includes one or more image attachments visible to you as multimodal context, apply the rules in this step in addition to the normal drafting workflow.
|
||||
- Incorporate what you can see in each image into the drafted issue body. At minimum, describe the relevant visual content in prose in the `Problem`, `Actual behavior`, or similar section so the report remains coherent even if the images do not end up attached to the filed issue.
|
||||
- In the `Artifacts` section, emit one entry per attached image, in the order you encountered them. Each entry must include both a **caption** describing what the screenshot depicts and a drop-target placeholder, so the issue body still conveys what each screenshot was meant to show even if the user never uploads the image. Use this format per image, numbered starting at 1:
|
||||
|
||||
```md
|
||||
**Screenshot 1:** <one-sentence caption describing what this screenshot shows, written from what you saw in multimodal context>.
|
||||
_Paste screenshot 1 here_
|
||||
```
|
||||
|
||||
Captions should be concrete (for example, "Agent footer with the Send button misaligned below the text input") rather than generic ("a screenshot" or "the bug"). Do not invent details that aren't visible; if an image is ambiguous or unreadable, say so in the caption.
|
||||
- When invoking the helper script (see the `Output` section below), pass `--use browser` instead of `--use gh`. This makes the script skip `gh issue create` and open the prefilled new-issue page in the browser so the user can upload images through GitHub's native drag-and-drop.
|
||||
- In your final response, explicitly instruct the user to paste or drag each attached image into the body at the corresponding `_Paste screenshot N here_` line(s) and then submit the issue. Reference the count of attached images so the user knows how many to paste. Do not claim the issue has been filed until the user submits.
|
||||
- If the user's query has no image attachments, do not add captions or placeholders, pass `--use gh` as usual, and do not add drag-and-drop instructions to your final response.
|
||||
|
||||
### 7. Check for likely duplicates before filing
|
||||
|
||||
- Before invoking `scripts/file_feedback_issue.py`, search issues in `warpdotdev/warp` for likely title matches using the drafted title as the primary query.
|
||||
- Use a lightweight title-based check only. Prefer precision over recall, and do not run a broad semantic fishing expedition.
|
||||
- Start with the exact drafted title. If the exact title returns no clear title match, try one shorter normalized variant that removes filler words while preserving the same user-visible problem.
|
||||
- A suitable command is:
|
||||
|
||||
```bash
|
||||
GH_PAGER=cat gh issue list \
|
||||
--repo warpdotdev/warp \
|
||||
--state all \
|
||||
--limit 10 \
|
||||
--search "<title> in:title" \
|
||||
--json number,title,url,state
|
||||
```
|
||||
|
||||
- Treat a result as a duplicate candidate only when the existing issue title clearly refers to the same underlying problem or request.
|
||||
- If you find a clear title match, do not file a new issue. Respond by pointing the user to the existing issue and explain briefly why it appears to match.
|
||||
- If no clear title match is found, proceed to file the new issue.
|
||||
|
||||
## Issue Structure
|
||||
Use these sections in order when they apply:
|
||||
|
||||
- Summary
|
||||
- Problem
|
||||
- Reproduction steps or desired workflow
|
||||
- Artifacts
|
||||
- Warp version
|
||||
- Operating system
|
||||
For bugs, regressions, and UX issues, also include:
|
||||
|
||||
- Expected behavior
|
||||
- Actual behavior
|
||||
|
||||
If you found grounded repo evidence, append:
|
||||
|
||||
- Possible source references
|
||||
|
||||
Section rules:
|
||||
- If a required field is unknown, say `Unknown`.
|
||||
- If an optional section does not apply, omit it.
|
||||
- If no artifacts are attached, say `None attached`.
|
||||
- For feature requests, the `Problem` section can describe the current friction or missing capability, and the `Reproduction steps or desired workflow` section can describe the desired flow instead of literal repro steps.
|
||||
- For feature requests, `Expected behavior` and `Actual behavior` are usually omitted unless they genuinely clarify the request.
|
||||
|
||||
## Source Reference Rules
|
||||
|
||||
- Prefer concrete file paths, symbols, settings names, and spec headings over broad guesses like "probably in the terminal code."
|
||||
- Cite spec references when they clarify the intended workflow, wording, or product expectation.
|
||||
- Cite implementation references when they localize the likely surface area or affected component.
|
||||
- Keep each reference brief and explain why it is relevant.
|
||||
- Omit references that are speculative, weakly related, or only tangentially connected.
|
||||
|
||||
## Writing Rules
|
||||
|
||||
- Turn vague feedback into specific behavior without changing the user's meaning.
|
||||
- Convert fuzzy narratives into numbered reproduction steps when the sequence can be inferred responsibly.
|
||||
- Call out severity, frequency, and regression context when available.
|
||||
- Be explicit about uncertainty or missing details instead of smoothing them over.
|
||||
- Do not mention this skill, private internal discussion, or speculative implementation theories in the issue body.
|
||||
- Do not pad the issue with source references unless they genuinely improve debugging or triage.
|
||||
- Never treat this workflow as permission to implement a fix. This skill may only file an issue, decline to file, or point to an existing issue.
|
||||
- Never suggest code changes, patches, or implementation diffs in the issue body, in your response, or as a follow-up action — even informally or "as a starting point." If the user asks for a fix, decline and redirect them to the filed issue.
|
||||
- **Refuse to file in three situations:** (1) the report is out of scope per step 1, (2) the minimum actionable information is still missing after the single clarifying round in step 2, or (3) step 3 found a clear docs match for what the user is asking about. In each case, explain the decision in one or two plain sentences and do not call the helper script. Never imply a feature is missing when docs already answer the question, and never file a placeholder issue just to acknowledge the user.
|
||||
|
||||
## Output
|
||||
|
||||
Use the bundled helper script `scripts/file_feedback_issue.py` to file the issue in `warpdotdev/warp` instead of calling `gh` directly. The script requires a `--use` flag that selects the filing method explicitly:
|
||||
|
||||
- `--use gh`: creates the issue with `gh issue create`. Requires `gh` to be installed and authenticated for `github.com`. Prints a `created` result with `issue_url` on success, or `unavailable` when `gh` is missing or unauthenticated. Does not silently fall back to the browser.
|
||||
- `--use browser`: opens the prefilled new-issue page in the browser so the user can upload image attachments via GitHub's web UI. Prints a `browser_opened` result on success. If the browser cannot be opened, automatically falls back to `gh issue create` and prints a `created` result with `browser_unavailable: true`; if both are unavailable, prints `failed`. Use this whenever the user attached one or more images to the query.
|
||||
- The script always targets `warpdotdev/warp` on `github.com`.
|
||||
|
||||
Write the final body to a temporary UTF-8 file and pass the final title directly as an argument. When the user has no image attachments:
|
||||
|
||||
```bash
|
||||
python3 scripts/file_feedback_issue.py \
|
||||
--use gh \
|
||||
--title "<title>" \
|
||||
--body-file <body-file>
|
||||
```
|
||||
|
||||
When the user has one or more image attachments:
|
||||
|
||||
```bash
|
||||
# Opens the prefilled new-issue page in the browser so the user can drop
|
||||
# their images into the issue body via GitHub's web UI.
|
||||
python3 scripts/file_feedback_issue.py \
|
||||
--use browser \
|
||||
--title "<title>" \
|
||||
--body-file <body-file>
|
||||
```
|
||||
|
||||
The title and body should be structured as follows:
|
||||
|
||||
Issue title: `<title>`
|
||||
|
||||
Issue body:
|
||||
|
||||
```md
|
||||
<!-- warp-feedback-skill:v1 -->
|
||||
## Summary
|
||||
...
|
||||
|
||||
## Problem
|
||||
...
|
||||
<!-- Include these sections for bugs, regressions, and UX issues when applicable. -->
|
||||
|
||||
## Expected behavior
|
||||
...
|
||||
|
||||
## Actual behavior
|
||||
...
|
||||
|
||||
## Reproduction steps or desired workflow
|
||||
1. ...
|
||||
|
||||
## Artifacts
|
||||
...
|
||||
|
||||
## Warp version
|
||||
...
|
||||
|
||||
## Operating system
|
||||
...
|
||||
|
||||
<!-- Omit this section if there are no grounded references. -->
|
||||
## Possible source references
|
||||
- path or symbol: why it may be relevant
|
||||
```
|
||||
|
||||
After completing the duplicate-check and filing workflow:
|
||||
- If the duplicate-check step finds an existing matching issue, respond with the existing issue link and a brief summary (2–4 sentences max) explaining that a likely duplicate already exists, including the matching title, whether that issue is open or closed, and why it appears to match. Do not create another issue.
|
||||
|
||||
- If the JSON result has `status: "created"` and `browser_unavailable: true`, explicitly tell the user that the browser could not be opened (include the `message` field from the result) and that the issue was filed programmatically with the available text contents. Make clear that image attachments were not uploaded to the issue. Then provide the issue link and a brief summary (3–5 sentences max) of what was filed.
|
||||
- If the JSON result has `status: "created"` (without `browser_unavailable`), respond with only the created issue link and a brief summary (3–5 sentences max) of what was filed: the classification, the core problem, and any notable missing details.
|
||||
- If the JSON result has `status: "unavailable"`, say that you could not file the issue because the GitHub CLI was not installed or authenticated, and include the returned message.
|
||||
- If the JSON result has `status: "browser_opened"` (seen when `--use browser` was used for image-bearing feedback), do not claim the issue has been filed. Pass the returned `message` to the user, and explicitly instruct them to paste or drag each attached image into the placeholder line(s) in the issue body and then submit the issue. When the result includes a `body` field (the drafted body was too long to prefill in the URL), surface that body so the user can paste it into the issue form before attaching images.
|
||||
- If the JSON result has `status: "failed"`, say that you could not file the issue because the filing flow failed, and include the returned `error` or `gh_error` message. When `--use browser` was used and filing failed (meaning both the browser and the `gh` CLI fallback were unavailable), make it explicit that image attachments were not handed off and no issue was filed.
|
||||
- If issue filing is not possible for any other reason, say explicitly that no issue was filed.
|
||||
|
||||
Read `references/examples.md` only if you need a compact example of the expected polish level.
|
||||
@@ -1,39 +0,0 @@
|
||||
# Examples
|
||||
|
||||
Use these only to calibrate tone and structure. Do not copy them mechanically.
|
||||
|
||||
## Example 1: Bug
|
||||
|
||||
Rough feedback:
|
||||
|
||||
`Warp loses my selected theme after restart on macOS. I changed it twice and it keeps going back.`
|
||||
|
||||
Draft shape:
|
||||
|
||||
- classification: `bug`
|
||||
- title: `Theme selection resets after restarting Warp on macOS`
|
||||
- missing details: Warp version, macOS version, whether the issue reproduces every restart
|
||||
|
||||
Key body moves:
|
||||
|
||||
- turn the complaint into a concrete restart flow
|
||||
- state the expected persisted theme behavior
|
||||
- mark unknown version details as `Unknown`
|
||||
|
||||
## Example 2: Feature Request
|
||||
|
||||
Rough feedback:
|
||||
|
||||
`The command palette is too hard to scan. I want better grouping for settings-related actions.`
|
||||
|
||||
Draft shape:
|
||||
|
||||
- classification: `feature request`
|
||||
- title: `Improve Command Palette grouping for settings-related actions`
|
||||
- missing details: concrete commands that are hardest to find, screenshots if the user has them
|
||||
|
||||
Key body moves:
|
||||
|
||||
- describe the current friction instead of inventing reproduction steps
|
||||
- focus the request on the desired workflow and user impact
|
||||
- draft immediately unless a specific missing fact blocks clarity
|
||||
@@ -1,33 +0,0 @@
|
||||
# Log And Crash Artifact Guidance
|
||||
|
||||
Use this only for crashes, startup failures, rendering bugs, sync issues, or hard-to-reproduce regressions.
|
||||
|
||||
- Ask for logs only when they are likely to improve the report.
|
||||
- Note in the issue that logs or crash reports were attached, but do not claim they contain console input or output.
|
||||
- In the `Artifacts` section, mention the exact file names or bundles that were attached.
|
||||
|
||||
macOS paths and commands:
|
||||
|
||||
- Logs live under `~/Library/Logs/`
|
||||
- Stable app logs are typically `~/Library/Logs/warp.log*`
|
||||
- Preview app logs are typically `~/Library/Logs/warp_preview.log*`
|
||||
- Stable zip command: `zip -j ~/Desktop/warp-logs.zip ~/Library/Logs/warp.log*`
|
||||
- Preview zip command: `zip -j ~/Desktop/warp_preview-logs.zip ~/Library/Logs/warp_preview.log*`
|
||||
- If Warp still opens, the user can search `View Warp Logs` in the Command Palette
|
||||
- Crash reports may also exist under `~/Library/Logs/DiagnosticReports/` as Warp `.ips` files
|
||||
|
||||
Linux paths:
|
||||
|
||||
- Logs live under Warp's state directory.
|
||||
- Stable app logs are typically `~/.local/state/warp-terminal/warp.log*`
|
||||
- Preview app logs are typically `~/.local/state/warp-terminal-preview/warp_preview.log*`
|
||||
- If the exact channel is unclear, ask the user to open the nearest `warp*.log*` files under `~/.local/state/`
|
||||
|
||||
Windows paths:
|
||||
|
||||
- Logs live under Warp's local app data state directory.
|
||||
- Stable app logs are typically `%LOCALAPPDATA%\warp\Warp\data\logs\warp.log*`
|
||||
- Preview app logs are typically `%LOCALAPPDATA%\warp\WarpPreview\data\logs\warp_preview.log*`
|
||||
- If the exact channel is unclear, ask the user to look under `%LOCALAPPDATA%\warp\` for the relevant `Warp*` folder and attach the matching `warp*.log*` files from its `data\logs\` directory
|
||||
|
||||
If no artifacts are available, say so plainly instead of implying they were checked.
|
||||
@@ -1,29 +0,0 @@
|
||||
# Platform Guidance
|
||||
|
||||
Use this only when the operating system, Warp version, or operating-system-specific behavior is relevant and missing.
|
||||
|
||||
- Resolve the execution surface first: packaged native Warp app or web session.
|
||||
- Prefer the bundled helper scripts and metadata files over prose or ad hoc shell inspection.
|
||||
- If the user already gave a sufficiently specific OS version or Warp version, do not ask again.
|
||||
- Include both OS name and version in the `Operating system` section when available.
|
||||
- Include the `Warp version` section when available, and note when the report is about a web session rather than a packaged native install.
|
||||
|
||||
## Operating system
|
||||
|
||||
Resolve the OS from the machine where the reported behavior actually happens. Do not substitute the OS of a different host, container, or remote target unless that is where the bug occurs.
|
||||
Run the bundled helper script when you need to resolve OS name and version:
|
||||
|
||||
```bash
|
||||
python3 scripts/resolve_platform.py
|
||||
```
|
||||
|
||||
Use the script output directly when filling `Operating system`. Ask the user only if Python is unavailable or the output still does not identify the relevant environment precisely enough.
|
||||
|
||||
## Warp version
|
||||
|
||||
For packaged native Warp installs, read the bundled version metadata file directly:
|
||||
The bundled version metadata file lives at `../../metadata/version.json` relative to the skill root. Read its `warp_version` field and use that value directly.
|
||||
|
||||
Use the file contents directly when filling `Warp version`. Ask the user only if Python is unavailable, the bundled metadata file is missing or unreadable, or the report is about a browser or web session rather than a packaged native install.
|
||||
|
||||
- Browser or web session with no local Warp executable: use the version or build identifier from the session URL or surrounding session metadata when present. If there is no concrete version string, record that it was a web session and leave `Warp version` as `Unknown` rather than guessing.
|
||||
@@ -1,308 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import platform
|
||||
import urllib.parse
|
||||
import webbrowser
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
DEFAULT_REPO = "warpdotdev/warp"
|
||||
DEFAULT_HOSTNAME = "github.com"
|
||||
FEEDBACK_LABEL = "in-app-feedback"
|
||||
|
||||
# GitHub's new-issue page accepts a prefilled title and body via query
|
||||
# parameters, but browsers and intermediate servers commonly cap URLs around
|
||||
# 8 KB. Keep a conservative threshold that leaves headroom for the base URL
|
||||
# and percent-encoding overhead.
|
||||
MAX_PREFILL_URL_LENGTH = 8000
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(
|
||||
description=(
|
||||
"File a GitHub issue in warpdotdev/warp. The caller must choose "
|
||||
"the filing method via --use: `gh` to create the issue directly with the "
|
||||
"gh CLI, or `browser` to open the prefilled new-issue page in the browser "
|
||||
"(used when attachments require manual upload via GitHub's web UI)."
|
||||
)
|
||||
)
|
||||
parser.add_argument(
|
||||
"--use",
|
||||
dest="use_method",
|
||||
required=True,
|
||||
choices=["gh", "browser"],
|
||||
help=(
|
||||
"Filing method. `gh` creates the issue via the gh CLI (requires gh to be "
|
||||
"installed and authenticated). `browser` opens the prefilled new-issue "
|
||||
"page in the user's browser so attachments can be uploaded via GitHub's "
|
||||
"web UI."
|
||||
),
|
||||
)
|
||||
|
||||
parser.add_argument("--title", required=True, help="Issue title.")
|
||||
parser.add_argument(
|
||||
"--body-file",
|
||||
type=Path,
|
||||
required=True,
|
||||
help="Path to a UTF-8 file containing the issue body.",
|
||||
)
|
||||
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def read_text(path: Path, field_name: str) -> str:
|
||||
|
||||
try:
|
||||
return path.read_text(encoding="utf-8")
|
||||
except OSError as exc:
|
||||
raise SystemExit(f"Failed to read {field_name} from {path}: {exc}") from exc
|
||||
|
||||
|
||||
def normalize_title(title: str) -> str:
|
||||
normalized_title = " ".join(title.splitlines()).strip()
|
||||
if not normalized_title:
|
||||
raise SystemExit("Issue title must not be empty.")
|
||||
return normalized_title
|
||||
|
||||
|
||||
def normalize_body(body: str) -> str:
|
||||
normalized_body = body.rstrip("\n")
|
||||
if not normalized_body.strip():
|
||||
raise SystemExit("Issue body must not be empty.")
|
||||
return normalized_body
|
||||
|
||||
|
||||
def gh_path_if_authenticated() -> str | None:
|
||||
gh_path = shutil.which("gh")
|
||||
if gh_path is None:
|
||||
return None
|
||||
|
||||
auth_result = subprocess.run(
|
||||
[gh_path, "auth", "status", "--hostname", DEFAULT_HOSTNAME],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
if auth_result.returncode != 0:
|
||||
return None
|
||||
|
||||
return gh_path
|
||||
|
||||
|
||||
def create_issue_with_gh(
|
||||
gh_path: str,
|
||||
title: str,
|
||||
body: str,
|
||||
) -> tuple[str | None, str | None]:
|
||||
command = [
|
||||
gh_path, "issue", "create",
|
||||
"--repo", DEFAULT_REPO,
|
||||
"--title", title,
|
||||
"--body", body,
|
||||
"--label", FEEDBACK_LABEL,
|
||||
]
|
||||
|
||||
result = subprocess.run(command, capture_output=True, text=True, check=False)
|
||||
if result.returncode != 0:
|
||||
error_output = "\n".join(part for part in [result.stdout.strip(), result.stderr.strip()] if part).strip()
|
||||
return None, error_output or "gh issue create failed"
|
||||
|
||||
issue_url = result.stdout.strip().splitlines()[-1].strip()
|
||||
if not issue_url:
|
||||
return None, "gh issue create succeeded but did not return an issue URL"
|
||||
|
||||
return issue_url, None
|
||||
|
||||
|
||||
def build_new_issue_url(title: str, body: str | None) -> str:
|
||||
"""Build a GitHub new-issue URL with the provided title and optional body prefilled."""
|
||||
base = f"https://{DEFAULT_HOSTNAME}/{DEFAULT_REPO}/issues/new"
|
||||
params: list[tuple[str, str]] = [("title", title)]
|
||||
if body is not None:
|
||||
params.append(("body", body))
|
||||
return f"{base}?{urllib.parse.urlencode(params, quote_via=urllib.parse.quote)}"
|
||||
|
||||
|
||||
def browser_is_available() -> tuple[bool, str | None]:
|
||||
"""Return whether opening a browser is likely to succeed on this system.
|
||||
|
||||
On macOS and Windows a GUI session is effectively always present for the
|
||||
user running this script. On Linux and other unix-likes ``webbrowser.open``
|
||||
can return True without actually opening a browser when no display server is
|
||||
running (for example, in a headless SSH session), so we require ``DISPLAY``
|
||||
or ``WAYLAND_DISPLAY`` to be set.
|
||||
"""
|
||||
system = platform.system()
|
||||
if system in ("Darwin", "Windows"):
|
||||
return True, None
|
||||
if os.environ.get("DISPLAY") or os.environ.get("WAYLAND_DISPLAY"):
|
||||
return True, None
|
||||
return (
|
||||
False,
|
||||
(
|
||||
"No graphical display is available (neither DISPLAY nor WAYLAND_DISPLAY "
|
||||
"is set), so the GitHub new-issue page cannot be opened in a browser."
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def open_in_browser(url: str) -> bool:
|
||||
try:
|
||||
return webbrowser.open(url, new=2)
|
||||
except webbrowser.Error:
|
||||
return False
|
||||
|
||||
|
||||
def fallback_to_browser(title: str, body: str) -> int:
|
||||
"""Open the GitHub new-issue page with a prefilled title (and body when it fits).
|
||||
|
||||
Intended for the caller-selected ``--use browser`` path, which the feedback skill
|
||||
uses when image attachments need to be uploaded via GitHub's web UI. When the full
|
||||
prefill URL would exceed ``MAX_PREFILL_URL_LENGTH``, the body is omitted from the
|
||||
URL and surfaced in the JSON result under a ``body`` field so the caller can tell
|
||||
the user to paste it into the issue form manually.
|
||||
|
||||
When the browser cannot be opened, the function attempts a ``gh issue create``
|
||||
fallback so the issue is still filed with the available text contents. The returned
|
||||
payload includes ``browser_unavailable: true`` when that fallback is used, so the
|
||||
caller can inform the user that the browser could not be opened and images were not
|
||||
uploaded. If both the browser and ``gh`` are unavailable, filing fails.
|
||||
"""
|
||||
full_url = build_new_issue_url(title, body)
|
||||
body_fits_in_url = len(full_url) <= MAX_PREFILL_URL_LENGTH
|
||||
url_to_open = full_url if body_fits_in_url else build_new_issue_url(title, None)
|
||||
|
||||
browser_available, browser_unavailable_reason = browser_is_available()
|
||||
|
||||
base_payload: dict[str, object] = {
|
||||
"method": "browser",
|
||||
"repo": DEFAULT_REPO,
|
||||
"url": url_to_open,
|
||||
}
|
||||
if not body_fits_in_url:
|
||||
# Surface the body so the caller can instruct the user to paste it in.
|
||||
base_payload["body"] = body
|
||||
|
||||
browser_failure_reason: str | None = None
|
||||
if not browser_available:
|
||||
browser_failure_reason = browser_unavailable_reason or "No display available."
|
||||
elif not open_in_browser(url_to_open):
|
||||
browser_failure_reason = "Unable to open a web browser for the prefilled new-issue page."
|
||||
|
||||
if browser_failure_reason is not None:
|
||||
# Browser unavailable — try gh CLI as a fallback so the issue is still filed.
|
||||
gh_path = gh_path_if_authenticated()
|
||||
if gh_path is not None:
|
||||
issue_url, _gh_error = create_issue_with_gh(gh_path, title, body)
|
||||
if issue_url is not None:
|
||||
print_result({
|
||||
"status": "created",
|
||||
"method": "gh",
|
||||
"repo": DEFAULT_REPO,
|
||||
"issue_url": issue_url,
|
||||
"browser_unavailable": True,
|
||||
"message": (
|
||||
f"{browser_failure_reason} "
|
||||
"The issue was filed programmatically with the available text contents. "
|
||||
"Image attachments were not uploaded."
|
||||
),
|
||||
})
|
||||
return 0
|
||||
|
||||
base_payload["status"] = "failed"
|
||||
base_payload["error"] = (
|
||||
browser_failure_reason
|
||||
+ " Image attachments could not be handed off through the browser flow; "
|
||||
"no issue has been filed."
|
||||
)
|
||||
print_result(base_payload)
|
||||
return 1
|
||||
|
||||
# The browser path is only used by the skill when image attachments are
|
||||
# present, so the user-facing message always references pasting/dropping them.
|
||||
if body_fits_in_url:
|
||||
message = (
|
||||
"Opened the GitHub new-issue page in your browser with the title and body prefilled. "
|
||||
"Paste or drag your attached screenshot(s) into the body at the placeholder line(s), "
|
||||
"then submit the issue."
|
||||
)
|
||||
else:
|
||||
message = (
|
||||
"Opened the GitHub new-issue page in your browser with the title prefilled. "
|
||||
"The drafted body was too long to include in the URL; paste the body (returned in "
|
||||
"this result's `body` field) into the issue form first, then paste or drag your "
|
||||
"attached screenshot(s) into the placeholder line(s), then submit."
|
||||
)
|
||||
|
||||
base_payload["status"] = "browser_opened"
|
||||
base_payload["message"] = message
|
||||
print_result(base_payload)
|
||||
return 0
|
||||
|
||||
|
||||
def print_result(payload: dict[str, object]) -> None:
|
||||
json.dump(payload, sys.stdout)
|
||||
sys.stdout.write("\n")
|
||||
|
||||
|
||||
def file_with_gh(title: str, body: str) -> int:
|
||||
"""File the issue via the gh CLI. Returns status `unavailable` when gh isn't
|
||||
installed or not authenticated; the caller is responsible for choosing a
|
||||
different --use method in that case (no automatic fallback happens here).
|
||||
"""
|
||||
gh_path = gh_path_if_authenticated()
|
||||
if gh_path is None:
|
||||
print_result(
|
||||
{
|
||||
"status": "unavailable",
|
||||
"method": "gh",
|
||||
"repo": DEFAULT_REPO,
|
||||
"message": f"GitHub CLI is not installed or not authenticated for {DEFAULT_HOSTNAME}.",
|
||||
}
|
||||
)
|
||||
return 0
|
||||
|
||||
issue_url, gh_error = create_issue_with_gh(gh_path, title, body)
|
||||
if issue_url is not None:
|
||||
print_result(
|
||||
{
|
||||
"status": "created",
|
||||
"method": "gh",
|
||||
"repo": DEFAULT_REPO,
|
||||
"issue_url": issue_url,
|
||||
}
|
||||
)
|
||||
return 0
|
||||
|
||||
print_result(
|
||||
{
|
||||
"status": "failed",
|
||||
"method": "gh",
|
||||
"repo": DEFAULT_REPO,
|
||||
"gh_error": gh_error,
|
||||
}
|
||||
)
|
||||
return 1
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
|
||||
title = normalize_title(args.title)
|
||||
body = normalize_body(read_text(args.body_file, "body"))
|
||||
|
||||
if args.use_method == "browser":
|
||||
return fallback_to_browser(title, body)
|
||||
# argparse enforces choices=["gh", "browser"], so the remaining case is "gh".
|
||||
return file_with_gh(title, body)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -1,75 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
import platform
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
def linux_info() -> dict[str, str]:
|
||||
fields: dict[str, str] = {}
|
||||
os_release = Path("/etc/os-release")
|
||||
if os_release.exists():
|
||||
for line in os_release.read_text(encoding="utf-8", errors="replace").splitlines():
|
||||
if "=" not in line or line.startswith("#"):
|
||||
continue
|
||||
key, value = line.split("=", 1)
|
||||
fields[key] = value.strip().strip(chr(34))
|
||||
|
||||
return {
|
||||
"os": fields.get("PRETTY_NAME") or fields.get("NAME") or "Linux",
|
||||
"os_version": fields.get("VERSION_ID") or fields.get("VERSION") or platform.release(),
|
||||
"kernel": platform.release(),
|
||||
}
|
||||
|
||||
|
||||
def mac_info() -> dict[str, str]:
|
||||
build = None
|
||||
result = subprocess.run(
|
||||
["sw_vers", "-buildVersion"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
if result.returncode == 0:
|
||||
build = result.stdout.strip() or None
|
||||
|
||||
return {
|
||||
"os": "macOS",
|
||||
"os_version": platform.mac_ver()[0] or None,
|
||||
"os_build": build,
|
||||
}
|
||||
|
||||
|
||||
def windows_info() -> dict[str, str]:
|
||||
version = sys.getwindowsversion()
|
||||
return {
|
||||
"os": "Windows",
|
||||
"os_version": f"{version.major}.{version.minor}.{version.build}",
|
||||
}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
system = platform.system()
|
||||
if system == "Darwin":
|
||||
info = mac_info()
|
||||
elif system == "Linux":
|
||||
info = linux_info()
|
||||
elif system == "Windows":
|
||||
info = windows_info()
|
||||
else:
|
||||
info = {
|
||||
"os": system or "Unknown",
|
||||
"os_version": platform.version() or None,
|
||||
}
|
||||
|
||||
json.dump({k: v for k, v in info.items() if v}, sys.stdout, sort_keys=True)
|
||||
sys.stdout.write("\n")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -1,365 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Unit tests for file_feedback_issue.py.
|
||||
|
||||
Run with:
|
||||
python3 resources/channel-gated-skills/dogfood/feedback/scripts/test_file_feedback_issue.py
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import importlib.util
|
||||
import io
|
||||
import json
|
||||
import sys
|
||||
import unittest
|
||||
import urllib.parse
|
||||
from contextlib import redirect_stdout
|
||||
from pathlib import Path
|
||||
from unittest import mock
|
||||
|
||||
|
||||
SCRIPT_DIR = Path(__file__).resolve().parent
|
||||
MODULE_PATH = SCRIPT_DIR / "file_feedback_issue.py"
|
||||
|
||||
|
||||
def load_module():
|
||||
spec = importlib.util.spec_from_file_location("file_feedback_issue", MODULE_PATH)
|
||||
assert spec is not None and spec.loader is not None
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
ffi = load_module()
|
||||
|
||||
|
||||
class BuildNewIssueUrlTests(unittest.TestCase):
|
||||
def test_url_includes_repo_and_title(self):
|
||||
url = ffi.build_new_issue_url("Hello world", None)
|
||||
self.assertTrue(url.startswith(f"https://{ffi.DEFAULT_HOSTNAME}/{ffi.DEFAULT_REPO}/issues/new?"))
|
||||
parsed = urllib.parse.urlparse(url)
|
||||
qs = urllib.parse.parse_qs(parsed.query)
|
||||
self.assertEqual(qs["title"], ["Hello world"])
|
||||
self.assertNotIn("body", qs)
|
||||
|
||||
def test_url_includes_body_when_provided(self):
|
||||
url = ffi.build_new_issue_url("T", "body text with spaces & symbols?")
|
||||
parsed = urllib.parse.urlparse(url)
|
||||
qs = urllib.parse.parse_qs(parsed.query)
|
||||
self.assertEqual(qs["title"], ["T"])
|
||||
self.assertEqual(qs["body"], ["body text with spaces & symbols?"])
|
||||
|
||||
def test_special_characters_are_percent_encoded(self):
|
||||
url = ffi.build_new_issue_url("crash: `foo`", "<script>alert(1)</script>")
|
||||
# Spaces should be %20, not +, because we use quote_via=quote.
|
||||
self.assertIn("%20", url)
|
||||
self.assertNotIn("+", url.split("?", 1)[1])
|
||||
# Angle brackets and backticks are percent-encoded.
|
||||
self.assertIn("%3C", url)
|
||||
self.assertIn("%3E", url)
|
||||
|
||||
|
||||
class FallbackToBrowserTests(unittest.TestCase):
|
||||
def _run_fallback(
|
||||
self,
|
||||
title,
|
||||
body,
|
||||
open_ok=True,
|
||||
browser_available=(True, None),
|
||||
gh_path=None,
|
||||
gh_create_result=(None, "gh not configured"),
|
||||
):
|
||||
with mock.patch.object(
|
||||
ffi, "open_in_browser", return_value=open_ok
|
||||
) as open_mock, mock.patch.object(
|
||||
ffi, "browser_is_available", return_value=browser_available
|
||||
), mock.patch.object(
|
||||
ffi, "gh_path_if_authenticated", return_value=gh_path
|
||||
), mock.patch.object(
|
||||
ffi, "create_issue_with_gh", return_value=gh_create_result
|
||||
):
|
||||
buf = io.StringIO()
|
||||
with redirect_stdout(buf):
|
||||
rc = ffi.fallback_to_browser(title, body)
|
||||
payload = json.loads(buf.getvalue().strip())
|
||||
return rc, payload, open_mock
|
||||
|
||||
def test_short_body_is_embedded_in_url(self):
|
||||
rc, payload, open_mock = self._run_fallback("t", "small body")
|
||||
self.assertEqual(rc, 0)
|
||||
self.assertEqual(payload["status"], "browser_opened")
|
||||
self.assertEqual(payload["method"], "browser")
|
||||
# Short bodies fit in the URL; no `body` field is surfaced separately.
|
||||
self.assertNotIn("body", payload)
|
||||
open_mock.assert_called_once()
|
||||
called_url = open_mock.call_args.args[0]
|
||||
parsed = urllib.parse.urlparse(called_url)
|
||||
qs = urllib.parse.parse_qs(parsed.query)
|
||||
self.assertEqual(qs["title"], ["t"])
|
||||
self.assertEqual(qs["body"], ["small body"])
|
||||
|
||||
def test_long_body_surfaces_body_in_payload_and_opens_title_only_url(self):
|
||||
huge_body = "A" * (ffi.MAX_PREFILL_URL_LENGTH + 500)
|
||||
rc, payload, open_mock = self._run_fallback("t", huge_body)
|
||||
self.assertEqual(rc, 0)
|
||||
self.assertEqual(payload["status"], "browser_opened")
|
||||
# Long bodies are returned in the payload so the caller can instruct
|
||||
# the user to paste them; no clipboard copy happens.
|
||||
self.assertEqual(payload["body"], huge_body)
|
||||
called_url = open_mock.call_args.args[0]
|
||||
parsed = urllib.parse.urlparse(called_url)
|
||||
qs = urllib.parse.parse_qs(parsed.query)
|
||||
self.assertEqual(qs["title"], ["t"])
|
||||
self.assertNotIn("body", qs)
|
||||
|
||||
def test_browser_failure_falls_back_to_gh_when_available(self):
|
||||
rc, payload, _ = self._run_fallback(
|
||||
"t",
|
||||
"small body",
|
||||
open_ok=False,
|
||||
gh_path="/usr/bin/gh",
|
||||
gh_create_result=("https://github.com/warpdotdev/warp/issues/7", None),
|
||||
)
|
||||
self.assertEqual(rc, 0)
|
||||
self.assertEqual(payload["status"], "created")
|
||||
self.assertEqual(payload["method"], "gh")
|
||||
self.assertTrue(payload["issue_url"].endswith("/issues/7"))
|
||||
self.assertTrue(payload.get("browser_unavailable"))
|
||||
self.assertIn("attachment", payload["message"].lower())
|
||||
|
||||
def test_browser_failure_reports_failed_when_gh_also_unavailable(self):
|
||||
rc, payload, open_mock = self._run_fallback("t", "small body", open_ok=False)
|
||||
# Filing failed, so exit code must be non-zero for shell callers that
|
||||
# check `$?` to distinguish "issue filed" from "issue not filed".
|
||||
self.assertEqual(rc, 1)
|
||||
self.assertEqual(payload["status"], "failed")
|
||||
self.assertEqual(payload["method"], "browser")
|
||||
self.assertIn("url", payload)
|
||||
# Failure messaging must acknowledge attachments so the user understands
|
||||
# why the image workflow couldn't complete.
|
||||
self.assertIn("attachment", payload["error"].lower())
|
||||
|
||||
def test_browser_unavailable_falls_back_to_gh_when_available(self):
|
||||
rc, payload, open_mock = self._run_fallback(
|
||||
"t",
|
||||
"small body",
|
||||
browser_available=(False, "No DISPLAY"),
|
||||
gh_path="/usr/bin/gh",
|
||||
gh_create_result=("https://github.com/warpdotdev/warp/issues/8", None),
|
||||
)
|
||||
self.assertEqual(rc, 0)
|
||||
self.assertEqual(payload["status"], "created")
|
||||
self.assertEqual(payload["method"], "gh")
|
||||
self.assertTrue(payload["issue_url"].endswith("/issues/8"))
|
||||
self.assertTrue(payload.get("browser_unavailable"))
|
||||
self.assertIn("No DISPLAY", payload["message"])
|
||||
self.assertIn("attachment", payload["message"].lower())
|
||||
open_mock.assert_not_called()
|
||||
|
||||
def test_browser_unavailable_reports_failed_when_gh_also_unavailable(self):
|
||||
rc, payload, open_mock = self._run_fallback(
|
||||
"t",
|
||||
"small body",
|
||||
browser_available=(False, "No DISPLAY"),
|
||||
)
|
||||
self.assertEqual(rc, 1)
|
||||
self.assertEqual(payload["status"], "failed")
|
||||
self.assertEqual(payload["method"], "browser")
|
||||
self.assertIn("No DISPLAY", payload["error"])
|
||||
self.assertIn("attachment", payload["error"].lower())
|
||||
open_mock.assert_not_called()
|
||||
|
||||
|
||||
class BrowserIsAvailableTests(unittest.TestCase):
|
||||
def test_darwin_is_always_available(self):
|
||||
with mock.patch.object(ffi.platform, "system", return_value="Darwin"), \
|
||||
mock.patch.dict(ffi.os.environ, {}, clear=True):
|
||||
ok, reason = ffi.browser_is_available()
|
||||
self.assertTrue(ok)
|
||||
self.assertIsNone(reason)
|
||||
|
||||
def test_windows_is_always_available(self):
|
||||
with mock.patch.object(ffi.platform, "system", return_value="Windows"), \
|
||||
mock.patch.dict(ffi.os.environ, {}, clear=True):
|
||||
ok, reason = ffi.browser_is_available()
|
||||
self.assertTrue(ok)
|
||||
self.assertIsNone(reason)
|
||||
|
||||
def test_linux_without_display_is_unavailable(self):
|
||||
with mock.patch.object(ffi.platform, "system", return_value="Linux"), \
|
||||
mock.patch.dict(ffi.os.environ, {}, clear=True):
|
||||
ok, reason = ffi.browser_is_available()
|
||||
self.assertFalse(ok)
|
||||
self.assertIsNotNone(reason)
|
||||
|
||||
def test_linux_with_display_is_available(self):
|
||||
with mock.patch.object(ffi.platform, "system", return_value="Linux"), \
|
||||
mock.patch.dict(ffi.os.environ, {"DISPLAY": ":0"}, clear=True):
|
||||
ok, reason = ffi.browser_is_available()
|
||||
self.assertTrue(ok)
|
||||
self.assertIsNone(reason)
|
||||
|
||||
def test_linux_with_wayland_display_is_available(self):
|
||||
with mock.patch.object(ffi.platform, "system", return_value="Linux"), \
|
||||
mock.patch.dict(ffi.os.environ, {"WAYLAND_DISPLAY": "wayland-0"}, clear=True):
|
||||
ok, reason = ffi.browser_is_available()
|
||||
self.assertTrue(ok)
|
||||
self.assertIsNone(reason)
|
||||
|
||||
|
||||
class FileWithGhTests(unittest.TestCase):
|
||||
def _run(self, **mocks):
|
||||
patches = []
|
||||
for attr, value in mocks.items():
|
||||
patches.append(mock.patch.object(ffi, attr, return_value=value))
|
||||
buf = io.StringIO()
|
||||
with contextlib.ExitStack() as stack:
|
||||
for p in patches:
|
||||
stack.enter_context(p)
|
||||
with redirect_stdout(buf):
|
||||
rc = ffi.file_with_gh("hello", "body")
|
||||
return rc, json.loads(buf.getvalue().strip())
|
||||
|
||||
def test_reports_unavailable_when_gh_missing(self):
|
||||
rc, payload = self._run(gh_path_if_authenticated=None)
|
||||
self.assertEqual(rc, 0)
|
||||
self.assertEqual(payload["status"], "unavailable")
|
||||
self.assertEqual(payload["method"], "gh")
|
||||
self.assertIn("message", payload)
|
||||
|
||||
def test_creates_when_gh_available(self):
|
||||
rc, payload = self._run(
|
||||
gh_path_if_authenticated="/usr/bin/gh",
|
||||
create_issue_with_gh=(
|
||||
"https://github.com/warpdotdev/warp/issues/42",
|
||||
None,
|
||||
),
|
||||
)
|
||||
self.assertEqual(rc, 0)
|
||||
self.assertEqual(payload["status"], "created")
|
||||
self.assertEqual(payload["method"], "gh")
|
||||
self.assertTrue(payload["issue_url"].endswith("/issues/42"))
|
||||
|
||||
def test_reports_failed_when_gh_create_fails(self):
|
||||
rc, payload = self._run(
|
||||
gh_path_if_authenticated="/usr/bin/gh",
|
||||
create_issue_with_gh=(None, "gh failed to create issue"),
|
||||
)
|
||||
# Filing failed, so exit code must be non-zero.
|
||||
self.assertEqual(rc, 1)
|
||||
self.assertEqual(payload["status"], "failed")
|
||||
self.assertEqual(payload["method"], "gh")
|
||||
self.assertEqual(payload["gh_error"], "gh failed to create issue")
|
||||
|
||||
|
||||
class MainTests(unittest.TestCase):
|
||||
"""Tests for the --use dispatch in main().
|
||||
|
||||
The caller must pick a filing method explicitly; main() does not fall back
|
||||
between the two paths.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.body_file = SCRIPT_DIR / "_tmp_body.txt"
|
||||
self.body_file.write_text("my body", encoding="utf-8")
|
||||
|
||||
def tearDown(self):
|
||||
self.body_file.unlink(missing_ok=True)
|
||||
|
||||
def _run_main(self, argv_extras):
|
||||
argv = [
|
||||
"file_feedback_issue.py",
|
||||
"--title",
|
||||
"hello",
|
||||
"--body-file",
|
||||
str(self.body_file),
|
||||
*argv_extras,
|
||||
]
|
||||
buf = io.StringIO()
|
||||
with mock.patch.object(sys, "argv", argv):
|
||||
with redirect_stdout(buf):
|
||||
rc = ffi.main()
|
||||
return rc, json.loads(buf.getvalue().strip())
|
||||
|
||||
def test_use_gh_creates_when_gh_available(self):
|
||||
with mock.patch.object(
|
||||
ffi, "gh_path_if_authenticated", return_value="/usr/bin/gh"
|
||||
), mock.patch.object(
|
||||
ffi,
|
||||
"create_issue_with_gh",
|
||||
return_value=(
|
||||
"https://github.com/warpdotdev/warp/issues/999",
|
||||
None,
|
||||
),
|
||||
):
|
||||
rc, payload = self._run_main(["--use", "gh"])
|
||||
self.assertEqual(rc, 0)
|
||||
self.assertEqual(payload["status"], "created")
|
||||
self.assertEqual(payload["method"], "gh")
|
||||
self.assertTrue(payload["issue_url"].endswith("/issues/999"))
|
||||
|
||||
def test_use_gh_reports_unavailable_and_does_not_open_browser(self):
|
||||
with mock.patch.object(
|
||||
ffi, "gh_path_if_authenticated", return_value=None
|
||||
), mock.patch.object(ffi, "open_in_browser", return_value=True) as open_mock:
|
||||
rc, payload = self._run_main(["--use", "gh"])
|
||||
self.assertEqual(rc, 0)
|
||||
self.assertEqual(payload["status"], "unavailable")
|
||||
self.assertEqual(payload["method"], "gh")
|
||||
# Critical invariant: --use gh must not silently fall back to the browser.
|
||||
open_mock.assert_not_called()
|
||||
|
||||
def test_use_browser_does_not_touch_gh(self):
|
||||
with mock.patch.object(
|
||||
ffi, "gh_path_if_authenticated"
|
||||
) as gh_mock, mock.patch.object(
|
||||
ffi, "create_issue_with_gh"
|
||||
) as create_mock, mock.patch.object(
|
||||
ffi, "browser_is_available", return_value=(True, None)
|
||||
), mock.patch.object(ffi, "open_in_browser", return_value=True) as open_mock:
|
||||
rc, payload = self._run_main(["--use", "browser"])
|
||||
self.assertEqual(rc, 0)
|
||||
self.assertEqual(payload["status"], "browser_opened")
|
||||
self.assertEqual(payload["method"], "browser")
|
||||
open_mock.assert_called_once()
|
||||
gh_mock.assert_not_called()
|
||||
create_mock.assert_not_called()
|
||||
|
||||
def test_missing_use_flag_exits_with_argparse_error(self):
|
||||
stderr = io.StringIO()
|
||||
with mock.patch.object(
|
||||
sys,
|
||||
"argv",
|
||||
[
|
||||
"file_feedback_issue.py",
|
||||
"--title",
|
||||
"hello",
|
||||
"--body-file",
|
||||
str(self.body_file),
|
||||
],
|
||||
), mock.patch.object(sys, "stderr", stderr), contextlib.suppress(SystemExit):
|
||||
ffi.main()
|
||||
self.assertIn("--use", stderr.getvalue())
|
||||
|
||||
def test_invalid_use_value_is_rejected(self):
|
||||
stderr = io.StringIO()
|
||||
with mock.patch.object(
|
||||
sys,
|
||||
"argv",
|
||||
[
|
||||
"file_feedback_issue.py",
|
||||
"--use",
|
||||
"carrier-pigeon",
|
||||
"--title",
|
||||
"hello",
|
||||
"--body-file",
|
||||
str(self.body_file),
|
||||
],
|
||||
), mock.patch.object(sys, "stderr", stderr), contextlib.suppress(SystemExit):
|
||||
ffi.main()
|
||||
self.assertIn("invalid choice", stderr.getvalue())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -33,7 +33,7 @@ grep -i "font" {{settings_schema_path}}
|
||||
Once you have a candidate key name, run the bundled script to get the **full dotted path**, the setting's properties, and any parent context. This is critical — the schema has multiple sections with similar names (e.g. several `input` keys), so never assume the nesting from grep output alone.
|
||||
|
||||
```sh
|
||||
python3 <skill_dir>/scripts/find_setting.py {{settings_schema_path}} <key_name>
|
||||
python3 {{skill_dir}}/scripts/find_setting.py {{settings_schema_path}} <key_name>
|
||||
```
|
||||
|
||||
The output gives you the unambiguous full path (e.g. `properties.appearance.properties.input.properties.input_mode`) and the setting's full definition including valid values.
|
||||
|
||||
@@ -0,0 +1,285 @@
|
||||
---
|
||||
name: oz-platform
|
||||
description: Use Warp's REST API and command line to run, configure, and inspect Oz cloud agents
|
||||
---
|
||||
|
||||
# oz-platform
|
||||
|
||||
Use the Oz REST API and CLI to:
|
||||
* Spawn cloud agents
|
||||
* Get the status of a cloud agent
|
||||
* Schedule cloud agents to run repeatedly
|
||||
* Create and manage the environments in which cloud agents run
|
||||
* Provide secrets for cloud agents to use
|
||||
|
||||
## Command Line
|
||||
|
||||
The Oz CLI is installed as `{{warp_cli_binary_name}}`. To get help output, use `{{warp_cli_binary_name}} help` or `{{warp_cli_binary_name}} help <subcommand>`.
|
||||
Prefer `--output-format text` to review the response, or `--output-format json` to parse fields with `jq`.
|
||||
You can find more information at https://docs.warp.dev/reference/cli.
|
||||
|
||||
The most important commands are:
|
||||
* `{{warp_cli_binary_name}} agent run-cloud`: Spawn a new cloud agent. You can configure the prompt, model, environment, and other settings.
|
||||
* `{{warp_cli_binary_name}} run list` and `{{warp_cli_binary_name}} run get <run-id>`: List all cloud agent runs, and get details about a particular run.
|
||||
* `{{warp_cli_binary_name}} environment list` and `{{warp_cli_binary_name}} environment get`: List available environments, and get more information about a particular environment.
|
||||
* `{{warp_cli_binary_name}} schedule list` and `{{warp_cli_binary_name}} schedule get`: List scheduled tasks with most recent runs, and get more information about a particular scheduled run.
|
||||
|
||||
Most subcommands support the `--output-format json` flag to produce JSON output, which you can pipe into `jq` or other commands.
|
||||
|
||||
### Examples
|
||||
|
||||
Start a cloud agent, and then monitor its status:
|
||||
|
||||
```sh
|
||||
$ {{warp_cli_binary_name}} agent run-cloud --prompt "Update the login error to be more specific" --environment UA17BXYZ
|
||||
# ...
|
||||
Spawned agent with run ID: 5972cca4-a410-42af-930a-e56bc23e07ac
|
||||
```
|
||||
|
||||
```sh
|
||||
$ {{warp_cli_binary_name}} run get 5972cca4-a410-42af-930a-e56bc23e07ac
|
||||
# ...
|
||||
```
|
||||
|
||||
Schedule an agent to summarize feedback every day at 8am UTC:
|
||||
|
||||
```sh
|
||||
$ {{warp_cli_binary_name}} schedule create --cron "0 8 * * *" \
|
||||
--name "GitHub issue summary" \
|
||||
--prompt "Collect all feedback from new GitHub issues and provide a summary report" \
|
||||
--environment UA17BXYZ
|
||||
```
|
||||
|
||||
List and inspect scheduled agents:
|
||||
|
||||
```sh
|
||||
$ {{warp_cli_binary_name}} schedule list
|
||||
$ {{warp_cli_binary_name}} schedule get <schedule-id>
|
||||
```
|
||||
|
||||
Create a secret for cloud agents to use:
|
||||
|
||||
```sh
|
||||
$ {{warp_cli_binary_name}} secret create JIRA_API_KEY --team --value-file jira_key.txt --description "API key to access Jira"
|
||||
```
|
||||
|
||||
## REST API
|
||||
|
||||
Oz has a REST API for starting and inspecting cloud agents.
|
||||
|
||||
All API requests require authentication using an API key. The user can generate API keys in their Warp settings, on the `Platform` page (accessible via `{{warp_url_scheme}}://settings/platform`).
|
||||
|
||||
You can find the full OpenAPI specification here: https://docs.warp.dev/reference/api-and-sdk
|
||||
|
||||
### TypeScript / JavaScript SDK
|
||||
|
||||
The TypeScript SDK is available via NPM. It is fully async, and works with Node, Bun, and Deno.
|
||||
|
||||
* Package link: https://www.npmjs.com/package/oz-agent-sdk
|
||||
* Source Code: https://github.com/warpdotdev/oz-sdk-typescript
|
||||
* API reference: https://raw.githubusercontent.com/warpdotdev/oz-sdk-typescript/HEAD/api.md
|
||||
|
||||
### Python SDK
|
||||
|
||||
The Python SDK is available from PyPi. It can be used synchronously or asynchronously.
|
||||
|
||||
* Package link: https://pypi.org/project/oz-agent-sdk/
|
||||
* Source Code: https://github.com/warpdotdev/oz-sdk-python
|
||||
* API reference: https://raw.githubusercontent.com/warpdotdev/oz-sdk-python/refs/heads/main/api.md
|
||||
|
||||
### API Examples
|
||||
|
||||
```sh
|
||||
curl -L -X POST {{warp_server_url}}/api/v1/agent/run \
|
||||
--header 'Authorization: Bearer YOUR_API_KEY' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data '{
|
||||
"prompt": "Update the login error to be more specific",
|
||||
"config": {
|
||||
"environment_id": "UA17BXYZ"
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
```sh
|
||||
curl -L -X GET {{warp_server_url}}/api/v1/agent/runs/5972cca4-a410-42af-930a-e56bc23e07ac \
|
||||
--header 'Authorization: Bearer YOUR_API_KEY' \
|
||||
--header 'Content-Type: application/json'
|
||||
```
|
||||
|
||||
## GitHub Actions Integration
|
||||
|
||||
You can trigger Oz cloud agents from GitHub Actions workflows. This enables automation like:
|
||||
* Triaging issues when they're created or labeled
|
||||
* Running checks on pull requests
|
||||
* Responding to CI events or deployment triggers
|
||||
|
||||
Use GitHub Actions when the trigger itself lives in GitHub: An event like an issue being opened, a PR being labeled, a push, or a CI workflow completing.
|
||||
|
||||
For periodic/recurring work, prefer `{{warp_cli_binary_name}} schedule create` to enhance scheduled run tracking with the Oz platform.
|
||||
|
||||
The agent will have access to the `gh` CLI to communicate back to the repository. Prefer prompting the agent to use `gh` vs. requiring the agent to respond with structured output for the GitHub workflow to parse.
|
||||
|
||||
### Action Setup
|
||||
|
||||
Use `warpdotdev/oz-agent-action@main` in your workflow. Required inputs:
|
||||
* `prompt`: The task description for the agent
|
||||
* `warp_api_key`: API key (store in GitHub secrets, e.g., `${{ secrets.WARP_API_KEY }}`)
|
||||
* `profile`: Optional agent profile identifier (can use repo variable, e.g., `${{ vars.WARP_AGENT_PROFILE || '' }}`)
|
||||
|
||||
The action outputs `agent_output` with the agent's response.
|
||||
|
||||
### Minimal Workflow Example
|
||||
|
||||
```yaml
|
||||
name: Run Oz Agent
|
||||
on:
|
||||
issues:
|
||||
types: [opened, labeled]
|
||||
|
||||
jobs:
|
||||
agent:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: warpdotdev/oz-agent-action@main
|
||||
id: agent
|
||||
with:
|
||||
prompt: |
|
||||
Analyze the GitHub issue and provide a summary.
|
||||
Issue: ${{ github.event.issue.title }}
|
||||
${{ github.event.issue.body }}
|
||||
|
||||
Respond to the issue with a comment containing your summary using the `gh` CLI.
|
||||
warp_api_key: ${{ secrets.WARP_API_KEY }}
|
||||
profile: ${{ vars.WARP_AGENT_PROFILE || '' }}
|
||||
- name: Use Agent Output
|
||||
run: echo "${{ steps.agent.outputs.agent_output }}"
|
||||
```
|
||||
|
||||
### Common Patterns
|
||||
|
||||
**Conditional steps**: Use `if: steps.agent.outputs.agent_output` to branch on agent results.
|
||||
|
||||
**Templating**: Use `actions/github-script@v7` to construct dynamic prompts from issue templates, repo context, or code.
|
||||
|
||||
**Error handling**: Check action success with `if: success()` or `if: failure()`.
|
||||
|
||||
**Git operations**: The action runs with checked-out code and Git credentials, so agents can commit and push changes.
|
||||
|
||||
|
||||
## Environments
|
||||
|
||||
All cloud agents run in an environment. The environment defines:
|
||||
* Which programs are preinstalled for the agent (based on a Docker image)
|
||||
* The Git repositories to check out before the agent starts
|
||||
* Setup commands to run, such as `npm install` or `cargo fetch`
|
||||
|
||||
You should almost always run cloud agents in an environment. Otherwise, they may not have the necessary code or tools available.
|
||||
|
||||
Cloud agents run in a sandbox, so they _can_ install additional programs into their environment. They also have Git credentials to create PRs and push branches.
|
||||
|
||||
Cloud environments DO NOT store secret values, like API keys. Use the `{{warp_cli_binary_name}} secret` commands instead.
|
||||
|
||||
## Using Third-Party Coding CLIs
|
||||
|
||||
Oz environments support running third-party coding agent CLIs such as Claude Code, Codex, Gemini CLI, Amp, Copilot CLI, and OpenCode. The `-agents` tagged variants of prebuilt Oz Docker images (e.g. `warpdotdev/dev-rust:1.85-agents`) come with the most popular CLIs preinstalled. Base tags (without `-agents`) do not include coding agent CLIs.
|
||||
|
||||
For detailed per-CLI documentation (installation, authentication, non-interactive flags, and artifact reporting), see [references/third-party-clis.md](./references/third-party-clis.md).
|
||||
|
||||
### For Interactive Agents: Launching Cloud Agents with Third-Party CLIs
|
||||
|
||||
When you are an interactive agent launching a cloud agent to use a third-party CLI:
|
||||
|
||||
1. **Environment Selection**: First, ask the user which environment to use. Present the public `-agents` image options from [warpdotdev/oz-dev-environments](https://github.com/warpdotdev/oz-dev-environments):
|
||||
- `warpdotdev/dev-base:latest-agents`
|
||||
- `warpdotdev/dev-go:1.23-agents`
|
||||
- `warpdotdev/dev-rust:1.83-agents`
|
||||
- `warpdotdev/dev-rust:1.85-agents`
|
||||
- `warpdotdev/dev-java:21-agents`
|
||||
- `warpdotdev/dev-dotnet:8.0-agents`
|
||||
- `warpdotdev/dev-ruby:3.3-agents`
|
||||
- `warpdotdev/dev-web:latest-agents`
|
||||
- `warpdotdev/dev-full:latest-agents`
|
||||
|
||||
Also ask if they want to use an existing environment instead. To list their environments:
|
||||
```sh
|
||||
{{warp_cli_binary_name}} environment list --output-format text
|
||||
```
|
||||
|
||||
If they choose a public image without an existing environment, create one with `{{warp_cli_binary_name}} environment create ...`
|
||||
|
||||
2. **Prompt Construction**: Construct a simple prompt that delegates CLI invocation to the cloud agent:
|
||||
```sh
|
||||
{{warp_cli_binary_name}} agent run-cloud \
|
||||
--environment <ENV_ID> \
|
||||
--prompt 'Read the oz-platform skill for instructions on using [CLI name] to solve: <task description>'
|
||||
```
|
||||
|
||||
**Do not** include the CLI command syntax in the prompt. The cloud agent will read the oz-platform skill and follow those instructions.
|
||||
|
||||
**Example**:
|
||||
```sh
|
||||
$ {{warp_cli_binary_name}} agent run-cloud \
|
||||
--environment <ENV_ID> \
|
||||
--prompt 'Read the oz-platform skill for instructions on using Claude Code to solve: Summarize the architecture of this project'
|
||||
```
|
||||
|
||||
### For Cloud Agents: Invoking Third-Party CLIs
|
||||
|
||||
When you are a cloud agent instructed to use a third-party CLI:
|
||||
|
||||
1. **Environment**: You are already running in an environment with the CLI preinstalled (if it's in an `-agents` image).
|
||||
|
||||
2. **Authentication**: API keys are available as environment variables (e.g. `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY`). These were configured as Oz secrets by the user.
|
||||
|
||||
3. **Task Delegation (IMPORTANT)**: The user's task should be completed **entirely by the third-party CLI**. Do NOT use Warp's built-in tools to complete the task yourself:
|
||||
- Do NOT use `edit_files`, `read_files`, `grep`, `codebase_semantic_search`, or other Warp coding tools to perform the user's task
|
||||
- The third-party CLI should do all the coding, file editing, searching, and analysis work
|
||||
- Your role is to:
|
||||
- Set up the CLI (e.g., authenticate if needed)
|
||||
- Construct the prompt for the CLI with the user's task
|
||||
- Run the CLI and monitor its execution
|
||||
- Debug any issues with the CLI itself
|
||||
- Report artifacts back to Warp (see below)
|
||||
|
||||
4. **CLI Invocation**: Read [references/third-party-clis.md](./references/third-party-clis.md) for detailed instructions on:
|
||||
- Non-interactive mode flags for each CLI (e.g. `claude -p`, `codex exec`, `gemini -p`)
|
||||
- Authentication setup steps if needed (e.g. Codex requires `printenv OPENAI_API_KEY | codex login --with-api-key`)
|
||||
- Useful flags and options
|
||||
- Example commands
|
||||
|
||||
5. **Artifact Reporting**: When the third-party CLI creates a PR, parse its output for the PR URL and branch name, then call `report_pr` to register the artifact in the Warp UI.
|
||||
|
||||
**Example workflow**:
|
||||
```sh
|
||||
# 1. Read this skill and references/third-party-clis.md to understand CLI usage
|
||||
|
||||
# 2. Set up authentication if needed (e.g., for Codex)
|
||||
# For Claude Code, ANTHROPIC_API_KEY is already available
|
||||
|
||||
# 3. Run the CLI with the user's task - let it do ALL the work
|
||||
$ claude -p "Summarize the architecture of this project"
|
||||
|
||||
# 4. If a PR was created, parse the CLI output and report the artifact
|
||||
# Example: report_pr(pr_url="https://github.com/...", branch="feature-branch")
|
||||
```
|
||||
|
||||
**What NOT to do**:
|
||||
```sh
|
||||
# ❌ Don't read files yourself to help the CLI
|
||||
$ read_files ...
|
||||
|
||||
# ❌ Don't search the codebase yourself
|
||||
$ grep ...
|
||||
|
||||
# ❌ Don't edit files yourself
|
||||
$ edit_files ...
|
||||
|
||||
# ✅ Instead, let the third-party CLI handle everything
|
||||
$ claude -p "Complete the entire task: <user's task>"
|
||||
```
|
||||
@@ -12,7 +12,7 @@ Fetch all review comments from the current branch's GitHub PR and display them v
|
||||
1. Run the bundled script (must be inside a git repo with an open PR on the current branch).
|
||||
Use `do_not_summarize_output: true` when running this shell command so the JSON output is not truncated.
|
||||
```bash
|
||||
python3 <skill_dir>/scripts/fetch_github_review_comments.py
|
||||
python3 {{skill_dir}}/scripts/fetch_github_review_comments.py
|
||||
```
|
||||
The script prints JSON to stdout.
|
||||
If the script fails to fetch comments, run the fallback `gh` commands instead.
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
---
|
||||
name: warpctrl
|
||||
description: Control and inspect the currently running local Warp application with the warpctrl CLI. Use this skill whenever the user asks the agent to manipulate Warp's own windows, tabs, panes, sessions, input buffer, themes, or UI surfaces; open a file in Warp; inspect local Warp state; or explain how to invoke Warp Control manually.
|
||||
---
|
||||
|
||||
# Warp Control
|
||||
|
||||
Use `{{warpctrl_binary_name}}` to inspect or control the already-running local Warp application that provided this skill. The command name and wrapper path in this skill are injected for the current Warp channel, so do not inspect running processes or guess which channel is active.
|
||||
|
||||
Prefer `{{warpctrl_binary_name}}` when the requested action changes Warp itself rather than the user's project or operating system. Examples include creating a Warp tab, splitting a pane, staging text in Warp's input, opening Warp settings, or focusing a Warp window.
|
||||
|
||||
## How to invoke Warp Control
|
||||
|
||||
Warp Control is bundled into the Warp application. It is not a separate standalone binary; it is a hidden control mode served by the running Warp process.
|
||||
|
||||
- Command for the current Warp channel: `{{warpctrl_binary_name}}`
|
||||
- Bundled wrapper for the current Warp channel: `{{warpctrl_wrapper_path}}`
|
||||
- Optional PATH symlink: `/usr/local/bin/{{warpctrl_binary_name}}`
|
||||
|
||||
### Ensure the command is available
|
||||
|
||||
Before invoking Warp Control for the first time in a task, prefer the shortest available path and avoid unnecessary setup research:
|
||||
|
||||
1. If `command -v {{warpctrl_binary_name}}` succeeds, use `{{warpctrl_binary_name}}` for the rest of the task. Do not inspect the bundled wrapper or verify the symlink unless a later command fails.
|
||||
2. If `command -v {{warpctrl_binary_name}}` fails, verify that `{{warpctrl_wrapper_path}}` exists and is executable. If it is missing, tell the user that this Warp build does not contain the expected wrapper and stop.
|
||||
3. Inspect `/usr/local/bin/{{warpctrl_binary_name}}`. Treat setup as complete only when it is a symlink that resolves to the exact `{{warpctrl_wrapper_path}}` bundled wrapper.
|
||||
4. If the expected symlink is missing, broken, or points elsewhere, use the `ask_user_question` tool to ask whether the user wants to install it at `/usr/local/bin/{{warpctrl_binary_name}}` pointing to `{{warpctrl_wrapper_path}}`. Offer **Install command** as the recommended option and **Not now** as the alternative. Do not create or replace a symlink without an affirmative response.
|
||||
5. After approval, create or update only the expected symlink by running `ln -sf "{{warpctrl_wrapper_path}}" "/usr/local/bin/{{warpctrl_binary_name}}"`. Try without elevation first. If macOS permissions prevent the change, run that same command through `osascript` with administrator privileges; never request or expose the user's password directly.
|
||||
6. Verify the result with `command -v {{warpctrl_binary_name}}`, `readlink /usr/local/bin/{{warpctrl_binary_name}}`, and `{{warpctrl_binary_name}} app version`.
|
||||
|
||||
If the user chooses **Not now**, do not create the symlink. Use the bundled wrapper at `{{warpctrl_wrapper_path}}` directly for the current task.
|
||||
|
||||
The Warp UI also exposes **Install Warp Control CLI command** and **Uninstall Warp Control CLI command** in the Command Palette and an install control under **Settings > Scripting**.
|
||||
|
||||
## Workflow
|
||||
|
||||
Always prefer discovering commands from `{{warpctrl_binary_name}}` itself rather than guessing or inventing them. The CLI provides full help and an action catalog that is the authoritative source of truth for what the installed build supports.
|
||||
|
||||
### Execute serially and validate results
|
||||
|
||||
Run Warp Control commands serially. Never dispatch multiple `{{warpctrl_binary_name}}` commands through parallel shell-tool calls, even when the commands appear independent. They act on the same running app and may change the active target or the terminal context used to execute and observe later commands. For multi-step requests, prefer one shell-tool call that chains commands sequentially, or issue separate shell-tool calls one at a time.
|
||||
|
||||
After an action that creates, activates, navigates, or focuses a window, tab, pane, session, or surface, do not assume the active target is unchanged. Use explicit selectors for later commands when exact targeting matters, or rerun `{{warpctrl_binary_name}} app active` before continuing.
|
||||
|
||||
Validate that each result corresponds to the command that was invoked. If output describes a different action, reports an unexpected instance or channel, or otherwise conflicts with the request, stop and rerun `{{warpctrl_binary_name}} instance list` serially before retrying. Do not report success until the requested final state has been verified when a corresponding `list`, `inspect`, or `get` command is available.
|
||||
|
||||
### Route by intent
|
||||
|
||||
Before discovering commands, route the request to the narrowest matching top-level group:
|
||||
|
||||
1. Requests to open, show, view, or toggle a named Warp UI destination, panel, picker, or settings page use `surface`. Convert natural-language names to kebab case, such as "Warp Drive" to `warp-drive` and "code review" to `code-review`. Prefer `surface <name> open` when the requested final state is open. Use `surface list` or `surface help` when the destination or supported verb is unknown. Do not infer an internal action name for a UI destination.
|
||||
2. Requests about windows, tabs, panes, or sessions use the matching `window`, `tab`, `pane`, or `session` group.
|
||||
3. Requests to stage or inspect editor input use `input`.
|
||||
4. Requests to open a file in Warp use `file`.
|
||||
5. Requests about themes, appearance, settings, or keybindings use the matching `theme`, `appearance`, `setting`, or `keybinding` group.
|
||||
6. Use the generic `action` catalog only when no dedicated CLI group matches. Internal or catalog action names are not guaranteed to be reachable as standalone parser commands.
|
||||
|
||||
1. Discover running Warp instances from the current Warp channel:
|
||||
|
||||
```sh
|
||||
{{warpctrl_binary_name}} instance list
|
||||
```
|
||||
|
||||
2. If exactly one same-channel instance is running, commands select it automatically. If multiple same-channel instances are running, select one explicitly with `--instance <instance_id>` or `--pid <pid>`.
|
||||
|
||||
3. Discover the exact command and parameters from the routed group instead of guessing. This is the preferred source of truth for the available command surface:
|
||||
|
||||
```sh
|
||||
{{warpctrl_binary_name}} help
|
||||
{{warpctrl_binary_name}} <group> help
|
||||
{{warpctrl_binary_name}} <group> <command> --help
|
||||
```
|
||||
|
||||
Only when no dedicated group matches, inspect the generic action catalog:
|
||||
|
||||
```sh
|
||||
{{warpctrl_binary_name}} action list
|
||||
{{warpctrl_binary_name}} action inspect <action.name>
|
||||
```
|
||||
|
||||
4. Inspect the active target chain or list the relevant targets before changing them:
|
||||
|
||||
```sh
|
||||
{{warpctrl_binary_name}} app active
|
||||
{{warpctrl_binary_name}} window list
|
||||
{{warpctrl_binary_name}} tab list
|
||||
{{warpctrl_binary_name}} pane list
|
||||
{{warpctrl_binary_name}} session list
|
||||
```
|
||||
|
||||
5. Invoke the narrowest action that satisfies the request, then verify the result with the corresponding `list`, `inspect`, or `get` command when useful.
|
||||
|
||||
## Common actions
|
||||
|
||||
These are frequently used commands that are safe to invoke directly. For less common commands, route by intent and use `{{warpctrl_binary_name}} <group> help` or `{{warpctrl_binary_name}} <group> <command> --help` to discover the exact syntax supported by the running build. Inspect the generic action catalog only when no dedicated group matches.
|
||||
|
||||
```sh
|
||||
# Create and manage tabs and panes
|
||||
{{warpctrl_binary_name}} tab create
|
||||
{{warpctrl_binary_name}} tab create --type agent
|
||||
{{warpctrl_binary_name}} tab rename "server logs"
|
||||
{{warpctrl_binary_name}} pane split --direction right
|
||||
{{warpctrl_binary_name}} pane navigate --direction next
|
||||
|
||||
# Stage text in Warp's input without submitting it
|
||||
{{warpctrl_binary_name}} input insert "git status"
|
||||
{{warpctrl_binary_name}} input replace "cargo test"
|
||||
|
||||
# Open or toggle Warp UI surfaces
|
||||
{{warpctrl_binary_name}} surface list
|
||||
{{warpctrl_binary_name}} surface settings open
|
||||
{{warpctrl_binary_name}} surface command-palette open --query "theme"
|
||||
{{warpctrl_binary_name}} surface command-search open
|
||||
{{warpctrl_binary_name}} surface theme-picker open
|
||||
{{warpctrl_binary_name}} surface keybindings open
|
||||
{{warpctrl_binary_name}} surface warp-drive open
|
||||
{{warpctrl_binary_name}} surface resource-center toggle
|
||||
{{warpctrl_binary_name}} surface ai-assistant toggle
|
||||
{{warpctrl_binary_name}} surface project-explorer open
|
||||
{{warpctrl_binary_name}} surface global-search open
|
||||
{{warpctrl_binary_name}} surface conversation-list open
|
||||
{{warpctrl_binary_name}} surface code-review open
|
||||
{{warpctrl_binary_name}} surface left-panel toggle
|
||||
{{warpctrl_binary_name}} surface right-panel toggle
|
||||
{{warpctrl_binary_name}} surface vertical-tabs open
|
||||
{{warpctrl_binary_name}} surface agent-management open
|
||||
|
||||
# Open a file in Warp
|
||||
{{warpctrl_binary_name}} file open ./src/main.rs --line 42
|
||||
|
||||
# Inspect and update supported state
|
||||
{{warpctrl_binary_name}} theme get
|
||||
{{warpctrl_binary_name}} theme set "Dracula"
|
||||
{{warpctrl_binary_name}} appearance get
|
||||
{{warpctrl_binary_name}} setting list
|
||||
{{warpctrl_binary_name}} keybinding list
|
||||
```
|
||||
|
||||
Add `--output-format json` when structured output is easier to consume:
|
||||
|
||||
```sh
|
||||
{{warpctrl_binary_name}} --output-format json tab list
|
||||
```
|
||||
|
||||
## Targeting
|
||||
|
||||
Target selectors can be combined when the action supports their scope:
|
||||
|
||||
- Instance: `--instance <instance_id>` or `--pid <pid>`
|
||||
- Window: `--window <id>`, `--window-index <n>`, or `--window-title <exact-title>`
|
||||
- Tab: `--tab <id>`, `--tab-index <n>`, or `--tab-title <exact-title>`
|
||||
- Pane: `--pane <id>` or `--pane-index <n>`
|
||||
- Session: `--session <id>`
|
||||
|
||||
Use IDs returned by `list`, `inspect`, or `app active` when exact targeting matters. If a selector is omitted, most scoped actions operate on the active target. Prefer explicit selectors when more than one target could reasonably match the user's request.
|
||||
|
||||
Use `surface list` before a walkthrough or multi-step UI workflow. It reports both available and unavailable destinations with stable names and reasons. The direct `surface ... open` commands are idempotent; use them instead of toggle commands when the final open state matters. `surface list` accepts `--instance` or `--pid` for process selection but rejects window, tab, pane, and session selectors.
|
||||
|
||||
## Safety and limitations
|
||||
|
||||
- Invoke close actions only when the user explicitly asks to close something. Close actions flow through normal Warp close behavior and may trigger existing app warnings.
|
||||
- `input insert` and `input replace` only stage text. Warp Control intentionally does not provide an action that submits or runs the input.
|
||||
- Do not invent unsupported commands. Use the matching group's `help` first, then use `action list` or `action inspect` only when no dedicated group matches.
|
||||
- Warp Control affects only a running local Warp application owned by the same user. It does not control remote or cloud Warp instances.
|
||||
- Each channel-specific Warp Control CLI lists and targets only Warp instances from its own channel.
|
||||
- On Windows, local-control publication is disabled until authenticated broker transport is supported.
|
||||
|
||||
## Manual setup and troubleshooting
|
||||
|
||||
Warp Control availability depends on the build channel and the **Settings > Scripting** toggle. The local-control mode defaults to enabled on internal dogfood builds (e.g., WarpDev) and disabled on public channels (Stable, Preview, OSS). On any channel, the final gate is the **Settings > Scripting** toggle. The installed `{{warpctrl_binary_name}}` wrapper invokes the matching channel-specific Warp executable.
|
||||
|
||||
If `{{warpctrl_binary_name}} instance list` is empty, confirm that a compatible same-channel Warp app is running and Scripting is enabled. If a command reports multiple instances, rerun it with `--instance <instance_id>`.
|
||||
|
||||
If the symlink is not on `PATH`, follow the confirmation-gated setup flow in **How to invoke Warp Control** or use `{{warpctrl_wrapper_path}}` directly.
|
||||
@@ -19,7 +19,7 @@ channel-gated-skills/
|
||||
```
|
||||
|
||||
> **Stable-ready skills** do not belong here. Place them in the always-bundled
|
||||
> `resources/skills/` directory instead. The build script will error if a
|
||||
> `resources/bundled/skills/` directory instead. The build script will error if a
|
||||
> `stable/` directory exists under `channel-gated-skills/`.
|
||||
|
||||
## Progressive gating
|
||||
@@ -31,7 +31,7 @@ Gating is **progressive**: earlier gates include all skills from later gates.
|
||||
| `local` | `dogfood` | `dogfood/` + `preview/` |
|
||||
| `dev` | `dogfood` | `dogfood/` + `preview/` |
|
||||
| `preview` | `preview` | `preview/` |
|
||||
| `stable` | — | *(none — use resources/skills/)* |
|
||||
| `stable` | — | *(none — use resources/bundled/skills/)* |
|
||||
|
||||
A skill placed in `preview/` is bundled on **all** non-stable builds
|
||||
(dogfood, preview). A skill placed in `dogfood/` is bundled on dogfood
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
---
|
||||
name: test-warp-ui
|
||||
description: >
|
||||
Guides testing Warp UI features and changes using the computer use tool.
|
||||
Use this skill only when the computer_use tool is available to the agent.
|
||||
Covers launching Warp and verifying UI behavior.
|
||||
user-invocable: false
|
||||
---
|
||||
|
||||
# Computer Use for Warp UI Testing
|
||||
|
||||
Use the `computer_use` tool to visually test that Warp looks and behaves as intended after UI changes.
|
||||
|
||||
## Running Warp
|
||||
|
||||
Launch Warp from the repository root. The exact command depends on which environment variable holds the API key:
|
||||
|
||||
- If `WARP_API_KEY` is already set, omit the flag entirely — the `--api-key` flag is bound to `WARP_API_KEY`, so Warp reads it automatically:
|
||||
|
||||
```bash
|
||||
cargo run --bin warp
|
||||
```
|
||||
|
||||
- If the key is in `STAGING_USER_WARP_API_KEY` instead, pass it explicitly via the flag:
|
||||
|
||||
```bash
|
||||
cargo run --bin warp -- --api-key $STAGING_USER_WARP_API_KEY
|
||||
```
|
||||
|
||||
Always pass `--bin warp` explicitly. That target builds the internal (dogfood) channel, which is the only channel that honors `--api-key` for the GUI app. A plain `cargo run` builds the OSS channel, which ignores the key and falls back to interactive onboarding.
|
||||
|
||||
Authenticating this way starts the app directly without interactive login prompts.
|
||||
|
||||
Initial builds may take several minutes; subsequent incremental builds are faster.
|
||||
|
||||
### Verify the launch is authenticated
|
||||
|
||||
After launching, confirm both of the following before testing:
|
||||
|
||||
- Warp is **authenticated** — it opens straight to the terminal, NOT the logged-out onboarding/sign-in screen.
|
||||
- The `cargo run` stderr/terminal output does **not** contain the substring `provided but IGNORED`.
|
||||
|
||||
If that warning appears (or the app is logged out), the wrong binary/channel was launched — stop and relaunch with `cargo run --bin warp`.
|
||||
|
||||
## Testing Workflow
|
||||
|
||||
### 1. Hardcode or Mock Data (When Needed)
|
||||
|
||||
If you just need to verify that a specific UI looks correct, it can be useful to hardcode or mock data so the UI state is immediately reachable without navigating a full flow. This is optional — skip this step when testing end-to-end flows that should work naturally.
|
||||
|
||||
Examples of when to hardcode:
|
||||
|
||||
- **Conditional UI**: The feature only appears under certain conditions (e.g., a specific setting, a non-empty data set, an active subscription) — hardcode the condition so the UI always appears.
|
||||
- **Feature flags**: The feature is behind a flag that isn't enabled yet — enable it directly.
|
||||
- **Error states**: You want to test error handling UI — hardcode error responses or failure conditions.
|
||||
|
||||
Keep mocked changes minimal and focused — only change what's necessary to reach the UI state under test.
|
||||
|
||||
### 2. Invoke Computer Use
|
||||
|
||||
Call the `computer_use` tool with a task description that includes:
|
||||
|
||||
- The command to build and launch Warp from the repo root: `cargo run --bin warp` when `WARP_API_KEY` is set in the environment, or `cargo run --bin warp -- --api-key $STAGING_USER_WARP_API_KEY` when the key is in `STAGING_USER_WARP_API_KEY` instead
|
||||
- Step-by-step instructions for navigating to the UI being tested
|
||||
- **Specific observations to report**: describe exactly what elements, text, colors, layout, or states the tool should observe and describe back
|
||||
- Do **not** include expected values in the task — the tool should report what it sees, not judge correctness
|
||||
|
||||
### 3. Verify Results
|
||||
|
||||
Compare the observations returned by `computer_use` against your expectations. If the UI doesn't match, investigate and adjust the code or mocks accordingly.
|
||||
|
||||
## Tips
|
||||
|
||||
- **Be specific in task descriptions**: Instead of "check if the dialog looks right," say "open Settings, click the General tab, and describe the text and layout of the first section."
|
||||
- **Test one thing at a time**: Focused tests are easier to debug when observations don't match expectations.
|
||||
- **Build before invoking**: Always confirm the build succeeds before calling `computer_use`. The tool cannot fix build errors.
|
||||
@@ -0,0 +1,79 @@
|
||||
---
|
||||
name: verify-ui-change-in-cloud
|
||||
description: >
|
||||
Invoke this automatically after completing any user-facing client change, ONLY in non-sandboxed environments and local environments.
|
||||
Verifies changes by spawning a cloud agent with computer use to test Warp.
|
||||
Triggers a cloud agent that runs the test-warp-ui skill.
|
||||
user-invocable: true
|
||||
---
|
||||
|
||||
# Verify UI Change in Cloud
|
||||
|
||||
After making a user-facing client change, use this workflow to verify it by spawning a cloud agent with computer use capabilities. This applies to any change that affects what the user sees or experiences in the running app — not just visual/UI changes, but also startup behavior, config handling, migration flows, and other client-side logic.
|
||||
|
||||
## Workflow
|
||||
|
||||
### 1. Push Your Changes
|
||||
|
||||
The cloud agent runs in a fresh environment that clones the repo. Your changes must be pushed to a branch so the cloud agent can access them.
|
||||
|
||||
### 2. Detect the Repository
|
||||
|
||||
Before spawning the cloud agent, detect which repository you are running in. Check the Git remote URL to determine the repo:
|
||||
|
||||
```bash
|
||||
git remote get-url origin
|
||||
```
|
||||
|
||||
Verify the remote URL contains `warpdotdev/warp`. If it does not, warn the user that this skill only supports the warp repository and stop.
|
||||
|
||||
The environment ID for the warp Dev Environment is `SVhg783GBFQHk1OfdPfFU9`.
|
||||
|
||||
### 3. Spawn the Cloud Agent
|
||||
|
||||
Use the `run_agents` tool to spawn a remote cloud agent. A single-child batch (one entry in `agent_run_configs`) is valid.
|
||||
|
||||
- `summary`: a brief declarative explanation, e.g. `"Spawning a cloud agent with computer use to verify the UI change."`
|
||||
- `base_prompt`: include an instruction to read and follow the `test-warp-ui` skill, followed by the verification task (see the next section)
|
||||
- `remote.environment_id`: `SVhg783GBFQHk1OfdPfFU9`
|
||||
- `remote.computer_use_enabled`: `true`
|
||||
- `agent_run_configs`: a single entry with `name` set to a short display name such as `"verify-ui-change"`. The per-agent `prompt` can be empty since `base_prompt` covers the task.
|
||||
|
||||
The `test-warp-ui` skill is bundled, so the cloud agent has it automatically. Tell the agent to invoke it by name in the `base_prompt` (e.g. "Read and follow the test-warp-ui skill.").
|
||||
|
||||
### 4. Write an Effective Prompt
|
||||
|
||||
The prompt should tell the cloud agent:
|
||||
- Which element, flow, or behavior to test
|
||||
- What hardcoding or mocking is needed (see below and the test-warp-ui skill for details on sandbox constraints)
|
||||
- What filesystem or app state to pre-seed before launching (e.g., creating directories, writing config files)
|
||||
- What specific observations to report back
|
||||
|
||||
**Example prompts:**
|
||||
|
||||
```
|
||||
I changed the settings dialog header to use a larger font and blue color.
|
||||
Hardcode the settings dialog to open on launch, then describe the header text,
|
||||
font size relative to other text, and color.
|
||||
```
|
||||
|
||||
```
|
||||
I added a migration that symlinks config from ~/.warp into ~/.warp-preview on first launch.
|
||||
The migration is gated on Channel::Preview. Before building, hardcode the migration to run
|
||||
regardless of channel by removing the channel check. Also create a fake ~/.warp directory
|
||||
with test files. After launching Warp, verify the symlinks were created in ~/.warp-preview.
|
||||
```
|
||||
|
||||
### Hardcoding to reach the code path under test
|
||||
|
||||
The cloud agent builds Warp with `cargo run`, which may not match the exact runtime conditions of your change (e.g., different channel, missing feature flags, absent preconditions). When this happens, instruct the cloud agent to temporarily hardcode the code so the build exercises the path you need to test. Common examples:
|
||||
|
||||
- **Gated code paths**: If the change is behind a channel check, feature flag, or experiment, tell the agent to remove or bypass the gate before building.
|
||||
- **Pre-existing state**: If the change depends on filesystem state that wouldn't exist in a clean environment (e.g., a config directory from a prior install), tell the agent to create it before launching.
|
||||
- **Startup behavior**: If the change affects something that only happens on first launch or migration, make sure the agent sets up the preconditions that trigger it.
|
||||
|
||||
Be explicit in the prompt about what to hardcode and why — the cloud agent won't infer this on its own.
|
||||
|
||||
### 5. Surface the Cloud Agent Link
|
||||
|
||||
No extra surfacing step is needed — the Warp client displays the cloud agent run automatically.
|
||||
@@ -80,16 +80,38 @@ Qh6Vm3FbRCkKqx2FzF6lCwA=
|
||||
SIGNINGKEY
|
||||
|
||||
# If our package repository hasn't been configured yet, set it up.
|
||||
#
|
||||
# apt supports two source-list formats: the legacy one-line `.list` format and
|
||||
# the RFC 822-style `.sources` (DEB822) format. Newer apt (e.g. Ubuntu 26.04's
|
||||
# `apt modernize-sources`) converts `.list` files to `.sources`. If we only
|
||||
# check for `.list` here, a post-modernization upgrade ends up writing a fresh
|
||||
# `.list` alongside the converted `.sources`, producing duplicate-source
|
||||
# warnings on every `apt update` (#10011).
|
||||
#
|
||||
# Behavior:
|
||||
# - If neither `.sources` nor `.list` exists, write `.sources` (the modern
|
||||
# format the reporter recommended; supported by every apt version Warp's
|
||||
# packages target).
|
||||
# - If either format already exists, leave it alone. We don't migrate a
|
||||
# legacy `.list` to `.sources` here — that's the user's tooling's job
|
||||
# (`apt modernize-sources` handles it cleanly), and overwriting their
|
||||
# potentially-edited file would be hostile.
|
||||
if [ -d "$APT_SOURCE_LIST_DIR" ]; then
|
||||
APT_SOURCE_LIST="${APT_SOURCE_LIST_DIR}@@REPO_NAME@@.list"
|
||||
if [ ! -f "$APT_SOURCE_LIST" ]; then
|
||||
APT_SOURCE_LIST_LEGACY="${APT_SOURCE_LIST_DIR}@@REPO_NAME@@.list"
|
||||
APT_SOURCE_LIST_DEB822="${APT_SOURCE_LIST_DIR}@@REPO_NAME@@.sources"
|
||||
if [ ! -f "$APT_SOURCE_LIST_LEGACY" ] && [ ! -f "$APT_SOURCE_LIST_DEB822" ]; then
|
||||
# If the source list directory exists but our repository isn't
|
||||
# configured within it, install our repo.
|
||||
cat > "$APT_SOURCE_LIST" <<EOF
|
||||
# configured in either format, install our repo.
|
||||
cat > "$APT_SOURCE_LIST_DEB822" <<EOF
|
||||
### THIS FILE IS AUTOMATICALLY CONFIGURED ###
|
||||
# You may comment out this entry, but other modifications to the file may be lost.
|
||||
|
||||
deb [arch=@@ARCH@@ signed-by=$SIGNING_KEY_PATH] https://releases.warp.dev/linux/deb @@CHANNEL@@ main
|
||||
Types: deb
|
||||
URIs: https://releases.warp.dev/linux/deb
|
||||
Suites: @@CHANNEL@@
|
||||
Components: main
|
||||
Architectures: @@ARCH@@
|
||||
Signed-By: $SIGNING_KEY_PATH
|
||||
EOF
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -14,6 +14,10 @@ eval $("$APT_CONFIG" shell APT_TRUSTED_KEYRING_DIR 'Dir::Etc::trustedparts/d')
|
||||
rm -f "${APT_TRUSTED_KEYRING_DIR}@@REPO_NAME@@.gpg"
|
||||
|
||||
# Use apt-config to determine the location of the source list config directory,
|
||||
# then delete our entry.
|
||||
# then delete our entry. We may have written either the legacy `.list` format
|
||||
# (older installs) or the modern `.sources` (DEB822) format introduced in the
|
||||
# postinst, so remove both filenames to ensure a clean purge regardless of
|
||||
# which format was last written. `rm -f` is silent on missing files.
|
||||
eval $("$APT_CONFIG" shell APT_SOURCE_LIST_DIR 'Dir::Etc::sourceparts/d')
|
||||
rm -f "${APT_SOURCE_LIST_DIR}@@REPO_NAME@@.list"
|
||||
rm -f "${APT_SOURCE_LIST_DIR}@@REPO_NAME@@.sources"
|
||||
|
||||
Reference in New Issue
Block a user