Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .codex/skills/luotsi-agent/references/agent-loop.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ Prefer exact text and structured selectors:
"command": "tap_text",
"text": "Files",
"text_match": "exact",
"resource_id": "com.elotouch.home:id/tvAppName",
"resource_id": "com.example.app:id/itemTitle",
"class_name": "android.widget.TextView"
}
```
Expand Down
26 changes: 26 additions & 0 deletions .github/release-notes/stable.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
## What changed for agent builders

- Luotsi now has a stable first-minute output loop for agents: `command -> structured output -> artifact root -> replay command -> next action`.
- `inspect` and `view --json` keep JSONL session semantics explicit, while one-shot commands keep the `luotsi-command.v1` envelope contract for scripts and CI.
- Failed runs can start with `luotsi replay packet --artifacts <artifact-root>` to write `run-summary.json` and `run-summary.md`, then use `replay packet --check` as the packet validation gate.
- The repo includes reusable agent guidance and parser examples for extracting the next command from envelopes, JSONL logs, and persisted run-summary packets.

## What changed for engineers and CI

- The CLI is packaged as self-contained release archives for Windows, Linux, macOS x64, and macOS arm64, with installer scripts, SHA-256 checksums, and release asset attestations.
- First-run setup is centered on `luotsi quickstart`, `luotsi doctor`, and `luotsi doctor --fix` so adb readiness, device selection, live-view prerequisites, helper APK presence, and FFmpeg view extras are visible before deeper workflows.
- Scenario runs, shared-lab device claims, JUnit/JSON reports, replay artifacts, failure capsules, replay graphs, and artifact packages now share the same evidence-first handoff model.
- Live view sharing is intended for trusted lab or development networks only; `--share-bind` uses raw TCP today and does not provide TLS or authentication.

## Open this first

- Docs hub: https://digablesolutions.github.io/luotsi/docs/
- First five minutes: https://digablesolutions.github.io/luotsi/docs/getting-started/first-five-minutes/
- AI agent workflows: https://digablesolutions.github.io/luotsi/docs/core-workflows/ai-agent-workflows/
- Installation: https://digablesolutions.github.io/luotsi/docs/getting-started/installation/
- Replay and artifacts: https://digablesolutions.github.io/luotsi/docs/core-workflows/replay-and-artifacts/

## Output and replay handoff

- For source-tree validation, run `luotsi help output` to see the JSON envelope, JSONL session, artifact root, and replay mental model.
- When a release note mentions failed CI runs, agent handoffs, or artifact packets, point the first follow-up to `luotsi replay packet --artifacts <artifact-root>` and the validation gate to `luotsi replay packet --artifacts <artifact-root> --check` so evaluators get `run-summary.json`, `run-summary.md`, the At a Glance summary, primary failure, recommended next action, and the 60-second checklist. Use `luotsi replay open --artifacts <artifact-root> --dry-run` when a human needs the replay front door without launching a browser.
2 changes: 1 addition & 1 deletion Luotsi.Cli.Tests/CommandEnvelopeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public async Task RunAsync_Help_Command_Writes_Command_Topic()
Assert.Empty(console.OutputLines);
Assert.Single(console.ErrorLines);
Assert.Contains("Luotsi help: view", console.ErrorLines[0], StringComparison.Ordinal);
Assert.Contains("luotsi view --device <adb serial>", console.ErrorLines[0], StringComparison.Ordinal);
Assert.Contains("luotsi view (--device <adb serial> | --join-share <host:port> | --last)", console.ErrorLines[0], StringComparison.Ordinal);
}

[Fact]
Expand Down
38 changes: 37 additions & 1 deletion Luotsi.Cli.Tests/TutorialDocumentationTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Diagnostics;
using System.IO.Compression;
using System.Linq;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Xml.Linq;
Expand Down Expand Up @@ -672,7 +673,7 @@ public void Website_Documentation_Documents_Portable_Ci_Workflow_Contract()
[Fact]
public void Portable_Ci_Bash_Script_Preserves_Run_Failure_And_Appends_Checked_Run_Summary()
{
if (!TryFindExecutable("bash", out var bash))
if (!TryFindPortableBash(out var bash))
{
return;
}
Expand Down Expand Up @@ -1469,6 +1470,41 @@ private static bool TryFindExecutable(string firstCandidate, string secondCandid
private static bool TryFindExecutable(string candidate, out string executable) =>
TryFindExecutable([candidate], out executable);

private static bool TryFindPortableBash(out string executable)
{
if (OperatingSystem.IsWindows())
{
var portableBash = EnumerateWindowsBashCandidates()
.Where(static candidate => RunProcessForProbe(candidate, ["--version"]).ExitCode == 0)
.FirstOrDefault();

if (!string.IsNullOrEmpty(portableBash))
{
executable = portableBash;
return true;
}
}

return TryFindExecutable("bash", out executable);
}

private static IEnumerable<string> EnumerateWindowsBashCandidates()
{
var programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
if (!string.IsNullOrWhiteSpace(programFiles))
{
yield return Path.Join(programFiles, "Git", "bin", "bash.exe");
yield return Path.Join(programFiles, "Git", "usr", "bin", "bash.exe");
}

var programFilesX86 = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86);
if (!string.IsNullOrWhiteSpace(programFilesX86))
{
yield return Path.Join(programFilesX86, "Git", "bin", "bash.exe");
yield return Path.Join(programFilesX86, "Git", "usr", "bin", "bash.exe");
}
}

private static bool TryFindExecutable(IReadOnlyList<string> candidates, out string executable)
{
foreach (var candidate in candidates)
Expand Down
12 changes: 10 additions & 2 deletions Luotsi.Cli/Cli/Help.cs
Original file line number Diff line number Diff line change
Expand Up @@ -682,16 +682,19 @@ final command envelope.
Luotsi help: view

Usage:
luotsi view --device <adb serial> [--profile <name>] [--preset safe|balanced|high-quality|low-latency]
luotsi view (--device <adb serial> | --join-share <host:port> | --last)
[--profile <name>] [--preset safe|balanced|high-quality|low-latency]
[--capture-backend auto|screenrecord|mediaprojection] [--decoder ffmpeg|wmf]
[--read-only] [--headless] [--record <file>]
[--share-bind <host:port>] [--read-only] [--headless] [--record <file>]
[-o|--output human|json|jsonl] [--json] [--quiet]
luotsi view setup --device <adb serial> [--dry-run]
luotsi view-doctor --device <adb serial> [--fix]
luotsi reconnect [--profile <name>] [--device <adb serial> | --join-share <host:port>]

Examples:
luotsi view --device emulator-5554
luotsi view --join-share 192.168.0.10:9000
luotsi view --last
luotsi view setup --device emulator-5554 --capture-backend mediaprojection
luotsi view-doctor --device emulator-5554 --fix

Expand All @@ -707,6 +710,11 @@ startup fails.
--quiet to print only diagnostics and errors. Session events are still
written to the artifact timeline either way.

Sharing:
--share-bind exposes the live stream over raw TCP for trusted lab/dev
networks. The relay does not provide TLS or authentication today, so do not
bind it to untrusted networks.

Failure modes:
If startup fails, Luotsi tries to print one actionable next command such as
view setup, view-doctor --fix, or choosing another decoder/capture backend.
Expand Down
Loading
Loading