diff --git a/.github/workflows/windows-distribution.yml b/.github/workflows/windows-distribution.yml index d501036..d69d868 100644 --- a/.github/workflows/windows-distribution.yml +++ b/.github/workflows/windows-distribution.yml @@ -46,7 +46,7 @@ jobs: shell: pwsh run: | $version = & .\dist\interly.exe --version - if ($LASTEXITCODE -ne 0 -or $version -ne "Interly 0.5.1") { + if ($LASTEXITCODE -ne 0 -or $version -ne "Interly 0.6.0") { throw "Standalone executable smoke test failed: $version" } @@ -56,16 +56,16 @@ jobs: - name: Build Windows installer shell: pwsh run: | - & "${env:ProgramFiles(x86)}\Inno Setup 6\ISCC.exe" "/DMyAppVersion=0.5.1" packaging\interly.iss + & "${env:ProgramFiles(x86)}\Inno Setup 6\ISCC.exe" "/DMyAppVersion=0.6.0" packaging\interly.iss if ($LASTEXITCODE -ne 0) { throw "Inno Setup failed." } - name: Generate WinGet manifests shell: pwsh run: | $sha = (Get-FileHash .\dist\InterlySetup-x64.exe -Algorithm SHA256).Hash - $url = "https://github.com/interlinkglobal/Interly/releases/download/v0.5.1/InterlySetup-x64.exe" - python packaging\render_winget.py --version 0.5.1 --installer-url $url --sha256 $sha --output dist\winget - Compress-Archive -Path dist\winget\* -DestinationPath dist\Interly-0.5.1-winget-manifests.zip + $url = "https://github.com/interlinkglobal/Interly/releases/download/v0.6.0/InterlySetup-x64.exe" + python packaging\render_winget.py --version 0.6.0 --installer-url $url --sha256 $sha --output dist\winget + Compress-Archive -Path dist\winget\* -DestinationPath dist\Interly-0.6.0-winget-manifests.zip - name: Upload build artifact uses: actions/upload-artifact@v4 @@ -75,7 +75,7 @@ jobs: dist/interly.exe dist/interlink.exe dist/InterlySetup-x64.exe - dist/Interly-0.5.1-winget-manifests.zip + dist/Interly-0.6.0-winget-manifests.zip - name: Publish GitHub release if: startsWith(github.ref, 'refs/tags/v') @@ -86,4 +86,4 @@ jobs: dist/interly.exe dist/interlink.exe dist/InterlySetup-x64.exe - dist/Interly-0.5.1-winget-manifests.zip + dist/Interly-0.6.0-winget-manifests.zip diff --git a/README.md b/README.md index e35ca9c..cb43d10 100644 --- a/README.md +++ b/README.md @@ -26,16 +26,16 @@ Git, Node.js, or npm on the user's computer. ### Development and fallback installer -For branch testing, development installations, or recovery when WinGet is unavailable, open -Command Prompt or PowerShell and run: +For development installations or recovery when WinGet is unavailable, open Command Prompt or +PowerShell and run: ```cmd -powershell -NoProfile -ExecutionPolicy Bypass -Command "irm 'https://raw.githubusercontent.com/interlinkglobal/Interly/agent/next-ten-roadmap/install.ps1' | iex" +powershell -NoProfile -ExecutionPolicy Bypass -Command "irm 'https://raw.githubusercontent.com/interlinkglobal/Interly/main/install.ps1' | iex" ``` This fallback installer detects suitable Python and Git installations, installs either when necessary, -installs and configures pipx, installs Interly, verifies the command, and launches it. Node.js is -not required. It remains separate from the standalone WinGet distribution. +installs and configures pipx, installs the current `main` branch, verifies the command, and launches +Interly. Node.js is not required. It remains separate from the standalone WinGet distribution. On first launch, Interly asks for a [Groq API key](https://console.groq.com/keys). Input is hidden, validated before saving, and stored under the current Windows user's application-data diff --git a/RELEASE_NOTES_0.6.0.md b/RELEASE_NOTES_0.6.0.md new file mode 100644 index 0000000..c879702 --- /dev/null +++ b/RELEASE_NOTES_0.6.0.md @@ -0,0 +1,72 @@ +# Interly 0.6.0 + +Interly 0.6.0 is the first release where the agent can inspect and interact with the visible Windows desktop while using a stronger host-side governance layer. + +## Highlights + +### Execution governance +- Add session dry-run mode with `dry-run`, `dry-run on`, and `dry-run off`. +- Add persistent per-tool permission policies with `prompt`, `allow`, and `deny` modes. +- Add multi-step plan presentation before complex work. +- Add one-request scoped plan approval limited to the tools and scopes shown in the plan. +- Keep process termination, logout, restart, shutdown, and other destructive Windows actions individually confirmed. +- Add persistent privacy-aware JSONL action audit logs with sensitive text and URL secrets redacted. + +### Desktop perception and interaction +- List visible top-level Windows windows with exact handles, PIDs, titles, and rectangles. +- Focus, minimise, maximise, restore, move, and resize exact windows after revalidation. +- Capture the full virtual desktop or a selected window to PNG. +- Add bundled OCR with detected text and bounding boxes. +- Inspect visible foreground controls through Windows UI Automation without activating them. +- Add guarded generic mouse movement, clicking, double-clicking, and bounded scrolling. +- Add guarded generic keyboard typing and bounded key combinations. +- Add separately approved Windows clipboard reads and writes. + +### Memory and reusable work +- Add persistent conversation memory with explicit retention controls. +- Add approved personal facts and preferences memory. +- Add memory inspection, export, and clear controls. +- Add reusable named workflows. +- Add the beta `make-memory` command for simple local note capture. + +### Developer workflows +- Add repository inspection. +- Add bounded repository command execution for tests, linters, builds, and development-server workflows. + +### Distribution and updates +- Bump the package, CLI, standalone executable, installer, and WinGet manifest version to 0.6.0. +- Include the desktop OCR/UI Automation dependencies in normal developer requirements. +- Make the fallback pipx installer install from `main` instead of the old roadmap branch. +- Make pipx update checks follow `main` as well. +- Keep `interly` as the canonical command while retaining `interlink` as a compatibility alias. +- Generate 0.6.0 Windows executable, installer, and WinGet manifest artifacts through the existing Windows distribution workflow. + +## Verified roadmap state + +Interly is currently at **88/101 verified roadmap items**. + +## Still not included + +- Recycle Bin file deletion +- Structured PDF, Word, Excel, and PowerPoint understanding +- Malware scanning and quarantine for downloads +- Scheduled tasks, reminders, and monitors +- Structured log monitoring +- Token, cost, latency, and request-count reporting +- Volume and brightness controls +- Speech input and output +- Signed Windows release artifacts +- External 1.0 security review + +## Release procedure + +After the 0.6.0 release-prep PR is merged and the site deployment is complete: + +1. Create tag `v0.6.0` on the current `main` commit. +2. The Windows distribution workflow will build and publish: + - `interly.exe` + - `interlink.exe` + - `InterlySetup-x64.exe` + - `Interly-0.6.0-winget-manifests.zip` +3. Confirm the GitHub Release contains the installer SHA-256 digest. +4. Install or run `update` from an existing standalone Interly installation and verify `interly --version` reports `Interly 0.6.0`. diff --git a/install.ps1 b/install.ps1 index 3e97526..0e00fc5 100644 --- a/install.ps1 +++ b/install.ps1 @@ -1,7 +1,7 @@ $ErrorActionPreference = "Stop" Set-StrictMode -Version Latest -$InterlySpec = "git+https://github.com/interlinkglobal/Interly.git@agent/next-ten-roadmap" +$InterlySpec = "git+https://github.com/interlinkglobal/Interly.git@main" $PythonVersion = "3.13.14" function Write-Step([string]$Message) { diff --git a/packaging/interly.iss b/packaging/interly.iss index 47d5aba..a438023 100644 --- a/packaging/interly.iss +++ b/packaging/interly.iss @@ -1,5 +1,5 @@ #ifndef MyAppVersion - #define MyAppVersion "0.5.1" + #define MyAppVersion "0.6.0" #endif #define MyAppName "Interly" diff --git a/packaging/render_winget.py b/packaging/render_winget.py index 318e12a..cbdb46b 100644 --- a/packaging/render_winget.py +++ b/packaging/render_winget.py @@ -51,8 +51,9 @@ def render(version: str, installer_url: str, sha256: str, output: Path) -> list[ - silentWithProgress UpgradeBehavior: install Commands: + - interly - interlink -ReleaseDate: 2026-08-04 +ReleaseDate: 2026-08-07 Installers: - Architecture: x64 InstallerUrl: {installer_url} diff --git a/pyproject.toml b/pyproject.toml index 5de79b5..c4d4058 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "interly" -version = "0.5.1" +version = "0.6.0" description = "A permission-aware Windows computer agent powered by Groq" readme = "README.md" license = "MIT" diff --git a/requirements.txt b/requirements.txt index 3016e7c..97e556e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,14 @@ beautifulsoup4==4.15.0 groq==1.6.0 +onnxruntime==1.27.0 +pillow==12.3.0 playwright==1.62.0 psutil==7.2.2 pynput==1.8.2 pydantic-settings==2.14.2 python-dotenv==1.2.2 +rapidocr==3.9.2 +uiautomation==2.0.29 # Development and verification pytest==9.1.1 ruff==0.16.1 diff --git a/src/computer_agent/__init__.py b/src/computer_agent/__init__.py index f1a9caa..102c7a7 100644 --- a/src/computer_agent/__init__.py +++ b/src/computer_agent/__init__.py @@ -1,3 +1,3 @@ """Local computer agent package.""" -__version__ = "0.5.1" +__version__ = "0.6.0" diff --git a/src/computer_agent/updater.py b/src/computer_agent/updater.py index 4abf9b3..bceb35c 100644 --- a/src/computer_agent/updater.py +++ b/src/computer_agent/updater.py @@ -15,7 +15,7 @@ PACKAGE_NAME = "interly" WINGET_PACKAGE_ID = "InterlinkGlobal.Interly" -UPDATE_BRANCH = "agent/next-ten-roadmap" +UPDATE_BRANCH = "main" UPDATE_REF_URL = ( "https://api.github.com/repos/interlinkglobal/Interly/git/ref/heads/" f"{UPDATE_BRANCH}" @@ -42,7 +42,7 @@ def installed_commit() -> str | None: def repository_commit() -> str: - """Return the current commit at Interly's development update branch.""" + """Return the current commit at Interly's update branch.""" response = httpx.get( UPDATE_REF_URL, headers={"Accept": "application/vnd.github+json", "User-Agent": "Interly updater"}, @@ -91,7 +91,7 @@ def update_interly() -> str: previous = current[:8] if current else "unknown" return ( f"Interly updated from {previous} to {latest[:8]}.\n" - "Type exit, then run interlink again to use the new version." + "Type exit, then run interly again to use the new version." ) diff --git a/tests/test_cli.py b/tests/test_cli.py index f948bcd..c664271 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -33,4 +33,4 @@ def test_working_groq_key_is_validated_before_save(groq: object, save: object) - def test_version_flag_exits_before_setup(capsys: object) -> None: main() - assert "Interly 0.5.1" in capsys.readouterr().out + assert "Interly 0.6.0" in capsys.readouterr().out diff --git a/tests/test_distribution.py b/tests/test_distribution.py index 02ebd20..55ba199 100644 --- a/tests/test_distribution.py +++ b/tests/test_distribution.py @@ -16,14 +16,17 @@ def load_renderer() -> object: def test_winget_renderer_creates_valid_release_manifests(tmp_path: Path) -> None: renderer = load_renderer() sha256 = "ab" * 32 - url = "https://github.com/interlinkglobal/Interly/releases/download/v0.5.1/InterlySetup-x64.exe" + url = "https://github.com/interlinkglobal/Interly/releases/download/v0.6.0/InterlySetup-x64.exe" - paths = renderer.render("0.5.1", url, sha256, tmp_path) + paths = renderer.render("0.6.0", url, sha256, tmp_path) assert len(paths) == 3 installer = (tmp_path / "InterlinkGlobal.Interly.installer.yaml").read_text() assert "PackageIdentifier: InterlinkGlobal.Interly" in installer + assert "PackageVersion: 0.6.0" in installer assert "InstallerType: inno" in installer + assert "Commands:\n - interly\n - interlink" in installer + assert "ReleaseDate: 2026-08-07" in installer assert f"InstallerUrl: {url}" in installer assert f"InstallerSha256: {sha256.upper()}" in installer @@ -33,8 +36,10 @@ def test_distribution_files_keep_standalone_and_fallback_routes() -> None: readme = (ROOT / "README.md").read_text() assert "pyinstaller" in workflow.casefold() + assert "Interly 0.6.0" in workflow assert "dist/interly.exe" in workflow assert "dist/interlink.exe" in workflow assert "InterlySetup-x64.exe" in workflow + assert "Interly-0.6.0-winget-manifests.zip" in workflow assert "winget install --id InterlinkGlobal.Interly" in readme assert "install.ps1" in readme diff --git a/tests/test_installer.py b/tests/test_installer.py index b12e8ad..b9dc279 100644 --- a/tests/test_installer.py +++ b/tests/test_installer.py @@ -13,7 +13,8 @@ def test_windows_installer_bootstraps_required_components() -> None: assert "-m pip install --user --upgrade pipx" in script assert "-m pipx ensurepath" in script assert "-m pipx install --force $InterlySpec" in script - assert "agent/next-ten-roadmap" in script + assert "Interly.git@main" in script + assert "agent/next-ten-roadmap" not in script assert "& $interlyPath" in script @@ -21,6 +22,6 @@ def test_readme_exposes_one_cmd_compatible_install_command() -> None: readme = (INSTALLER.parent / "README.md").read_text(encoding="utf-8") assert "powershell -NoProfile -ExecutionPolicy Bypass" in readme - assert "install.ps1' | iex" in readme + assert "Interly/main/install.ps1' | iex" in readme assert "Node.js" in readme assert "not required" in readme diff --git a/tests/test_updater.py b/tests/test_updater.py index f7da19f..a4c8273 100644 --- a/tests/test_updater.py +++ b/tests/test_updater.py @@ -4,6 +4,7 @@ from unittest.mock import patch from computer_agent.updater import ( + UPDATE_BRANCH, digest_file, installed_commit, latest_release_installer, @@ -12,6 +13,10 @@ ) +def test_pipx_update_branch_is_main() -> None: + assert UPDATE_BRANCH == "main" + + @patch("computer_agent.updater.distribution") def test_installed_commit_reads_pip_vcs_metadata(metadata: object) -> None: metadata.return_value.read_text.return_value = json.dumps( @@ -45,6 +50,7 @@ def test_new_commit_upgrades_existing_pipx_installation( result = update_interly() assert "updated from" in result + assert "run interly again" in result run.assert_called_once_with( ["C:/Python/Scripts/pipx.exe", "upgrade", "interly"], capture_output=True, @@ -96,8 +102,8 @@ def test_standalone_falls_back_to_verified_release_when_winget_cannot_find_packa ) -> None: run.return_value = SimpleNamespace(returncode=1, stdout="", stderr="No package found") release.return_value = ( - "0.5.2", - "https://github.com/interlinkglobal/Interly/releases/download/v0.5.2/InterlySetup-x64.exe", + "0.6.1", + "https://github.com/interlinkglobal/Interly/releases/download/v0.6.1/InterlySetup-x64.exe", "ab" * 32, ) download.return_value = "C:/Temp/InterlySetup-update-x64.exe" @@ -105,6 +111,7 @@ def test_standalone_falls_back_to_verified_release_when_winget_cannot_find_packa result = update_standalone() assert "downloaded, verified" in result + assert "run interly again" in result download.assert_called_once() popen.assert_called_once_with( [ @@ -120,13 +127,13 @@ def test_standalone_falls_back_to_verified_release_when_winget_cannot_find_packa @patch("computer_agent.updater.httpx.get") def test_latest_release_requires_official_installer_and_sha256(get: object) -> None: get.return_value.json.return_value = { - "tag_name": "v0.5.1", + "tag_name": "v0.6.0", "assets": [ { "name": "InterlySetup-x64.exe", "browser_download_url": ( "https://github.com/interlinkglobal/Interly/releases/download/" - "v0.5.1/InterlySetup-x64.exe" + "v0.6.0/InterlySetup-x64.exe" ), "digest": f"sha256:{'ab' * 32}", } @@ -134,8 +141,8 @@ def test_latest_release_requires_official_installer_and_sha256(get: object) -> N } assert latest_release_installer() == ( - "0.5.1", - "https://github.com/interlinkglobal/Interly/releases/download/v0.5.1/InterlySetup-x64.exe", + "0.6.0", + "https://github.com/interlinkglobal/Interly/releases/download/v0.6.0/InterlySetup-x64.exe", "ab" * 32, )