Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 14 additions & 8 deletions .github/workflows/windows-distribution.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,15 @@ jobs:
working-directory: packaging
run: pyinstaller --clean --noconfirm --distpath ../dist --workpath ../build interlink.spec

- name: Add temporary interlink compatibility alias
shell: pwsh
run: Copy-Item .\dist\interly.exe .\dist\interlink.exe

- name: Smoke test executable
shell: pwsh
run: |
$version = & .\dist\interlink.exe --version
if ($LASTEXITCODE -ne 0 -or $version -ne "Interly 0.5.0") {
$version = & .\dist\interly.exe --version
if ($LASTEXITCODE -ne 0 -or $version -ne "Interly 0.5.1") {
throw "Standalone executable smoke test failed: $version"
}

Expand All @@ -52,32 +56,34 @@ jobs:
- name: Build Windows installer
shell: pwsh
run: |
& "${env:ProgramFiles(x86)}\Inno Setup 6\ISCC.exe" "/DMyAppVersion=0.5.0" packaging\interly.iss
& "${env:ProgramFiles(x86)}\Inno Setup 6\ISCC.exe" "/DMyAppVersion=0.5.1" 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.0/InterlySetup-x64.exe"
python packaging\render_winget.py --version 0.5.0 --installer-url $url --sha256 $sha --output dist\winget
Compress-Archive -Path dist\winget\* -DestinationPath dist\Interly-0.5.0-winget-manifests.zip
$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

- name: Upload build artifact
uses: actions/upload-artifact@v4
with:
name: Interly-Windows-x64
path: |
dist/interly.exe
dist/interlink.exe
dist/InterlySetup-x64.exe
dist/Interly-0.5.0-winget-manifests.zip
dist/Interly-0.5.1-winget-manifests.zip

- name: Publish GitHub release
if: startsWith(github.ref, 'refs/tags/v')
uses: softprops/action-gh-release@v2
with:
generate_release_notes: true
files: |
dist/interly.exe
dist/interlink.exe
dist/InterlySetup-x64.exe
dist/Interly-0.5.0-winget-manifests.zip
dist/Interly-0.5.1-winget-manifests.zip
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ winget install --id InterlinkGlobal.Interly --exact
Then launch Interly from any Command Prompt:

```cmd
interlink
interly
```

The WinGet package contains a standalone Windows executable and does not require Python, pipx,
Expand Down Expand Up @@ -48,9 +48,9 @@ Upgrade later with:
update
```

Run `update` at any Interly `You:` prompt. Interly compares the installed Git commit with its
development branch, upgrades the existing pipx installation only when needed, and asks you to
restart Interly after a successful update.
Run `update` at any Interly `You:` prompt. Standalone installations try WinGet first and fall
back to a SHA-256-verified GitHub Release installer while catalogue publication is pending.
Development installations upgrade through pipx. Restart Interly after a successful update.

## Current capabilities

Expand Down
18 changes: 9 additions & 9 deletions install.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -198,21 +198,21 @@ if ($LASTEXITCODE -ne 0) {
throw "Interly installation failed."
}

$interlinkPath = $null
$interlink = Get-Command interlink -ErrorAction SilentlyContinue
if (-not $interlink) {
$interlyPath = $null
$interly = Get-Command interly -ErrorAction SilentlyContinue
if (-not $interly) {
$pipxBin = & $pythonCommand @pythonArguments -m pipx environment --value PIPX_BIN_DIR
$candidate = Join-Path $pipxBin.Trim() "interlink.exe"
$candidate = Join-Path $pipxBin.Trim() "interly.exe"
if (Test-Path $candidate) {
$interlinkPath = $candidate
$interlyPath = $candidate
}
}
else {
$interlinkPath = $interlink.Source
$interlyPath = $interly.Source
}
if (-not $interlinkPath) {
throw "Interly installed, but interlink.exe could not be found. Open a new terminal and run interlink."
if (-not $interlyPath) {
throw "Interly installed, but interly.exe could not be found. Open a new terminal and run interly."
}

Write-Step "Installation verified. Launching Interly"
& $interlinkPath
& $interlyPath
2 changes: 1 addition & 1 deletion packaging/interlink.spec
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ exe = EXE(
a.binaries,
a.datas,
[],
name="interlink",
name="interly",
debug=False,
bootloader_ignore_signals=False,
strip=False,
Expand Down
5 changes: 3 additions & 2 deletions packaging/interly.iss
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
#ifndef MyAppVersion
#define MyAppVersion "0.5.0"
#define MyAppVersion "0.5.1"
#endif

#define MyAppName "Interly"
#define MyAppPublisher "Interlink Global Technologies"
#define MyAppExeName "interlink.exe"
#define MyAppExeName "interly.exe"

[Setup]
AppId={{6CB4E41F-F5A4-4F9D-B05F-C0565EBD99E7}
Expand All @@ -27,6 +27,7 @@ ChangesEnvironment=yes
UninstallDisplayIcon={app}\{#MyAppExeName}

[Files]
Source: "..\dist\interly.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "..\dist\interlink.exe"; DestDir: "{app}"; Flags: ignoreversion

[Icons]
Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "interly"
version = "0.5.0"
version = "0.5.1"
description = "A permission-aware Windows computer agent powered by Groq"
readme = "README.md"
license = "MIT"
Expand Down Expand Up @@ -32,6 +32,7 @@ distribution = ["pyinstaller==6.21.0"]

[project.scripts]
brocode = "computer_agent.__main__:main"
interly = "computer_agent.__main__:main"
interlink = "computer_agent.__main__:main"

[project.urls]
Expand Down
2 changes: 1 addition & 1 deletion src/computer_agent/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""Local computer agent package."""

__version__ = "0.5.0"
__version__ = "0.5.1"
30 changes: 29 additions & 1 deletion src/computer_agent/chat.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""The Stage 1 terminal conversation loop."""

from collections.abc import Callable
from time import monotonic
from typing import Any

from playwright.sync_api import Error as PlaywrightError
Expand Down Expand Up @@ -38,6 +39,8 @@
"browser_read_page": 5,
}
MAX_MODEL_ROUNDS_PER_MESSAGE = 12
SET_FREE_COMMAND = "set-free"
MAX_SET_FREE_MINUTES = 30


def run_chat(
Expand All @@ -51,6 +54,7 @@ def run_chat(
"""Chat until the user exits, then return the conversation history."""
messages: list[dict[str, Any]] = []
session_approvals: set[str] = set()
free_until: float | None = None
write_output("Interlink is ready. Type 'exit' to stop.")

while True:
Expand Down Expand Up @@ -84,6 +88,26 @@ def run_chat(
write_output(update_interly())
continue

if user_text.casefold().startswith(f"{SET_FREE_COMMAND} "):
value = user_text[len(SET_FREE_COMMAND) :].strip()
try:
minutes = int(value)
if minutes < 0 or minutes > MAX_SET_FREE_MINUTES:
raise ValueError
except ValueError:
write_output("Usage: set-free <1-30 whole minutes>, or set-free 0 to disable.")
continue
if minutes == 0:
free_until = None
write_output("Automatic command approval disabled.")
else:
free_until = monotonic() + minutes * 60
write_output(
f"Automatic command approval enabled for {minutes} minute"
f"{'s' if minutes != 1 else ''}. Emergency stop remains active."
)
continue

if not user_text:
continue
if emergency_stop and emergency_stop.requested():
Expand Down Expand Up @@ -144,7 +168,11 @@ def run_chat(
if warning:
write_output(warning)
approval_group = SESSION_APPROVAL_GROUPS.get(request.name)
if approval_group in session_approvals:
free_active = free_until is not None and monotonic() < free_until
if free_until is not None and not free_active:
free_until = None
write_output("Automatic command approval period ended; prompts restored.")
if free_active or approval_group in session_approvals:
approved = True
else:
if request.name in SENSITIVE_LOCAL_TOOLS:
Expand Down
Loading
Loading