Initial public release of Warp.

Repo-Sync-Origin: warpdotdev/warp-internal@12af1d983b
This commit is contained in:
David Stern
2026-04-28 08:43:33 -05:00
commit 0dbd3d567a
4982 changed files with 1431549 additions and 0 deletions
@@ -0,0 +1,308 @@
#!/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())
@@ -0,0 +1,75 @@
#!/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())
@@ -0,0 +1,365 @@
#!/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()