Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
89158cc
Keep the exit record of a process that exits before WAIT
mgasner Sep 28, 2026
0a48a38
Speak the YAS byte stream on the Unix socket transports
mgasner Sep 28, 2026
dbd0862
Emit Node-loadable ESM from core, react and solid
mgasner Sep 28, 2026
394b643
Call the wasm init export through the loaded module
mgasner Sep 28, 2026
54aab99
Track the release in core's @yas-run/browser peer range
mgasner Sep 28, 2026
7171605
Document direct as the touchMode default in React and Solid views
mgasner Sep 28, 2026
7870c88
Resolve target names from the home server's remotes catalogue
mgasner Sep 28, 2026
cd80c67
Stop creating yas.remotes when reading it
mgasner Sep 28, 2026
7d427d9
Return empty output for a sequence cursor past the end
mgasner Sep 28, 2026
a76205d
Document OUTPUT behavior for a cursor past the end
mgasner Sep 28, 2026
8522323
Size terminal OUTPUT collection limits for the result framing
mgasner Sep 28, 2026
a7f5a6d
Make `yas terminal output --wait` time out with 124
mgasner Sep 28, 2026
687e438
Tolerate a closed stdout in terminal commands
mgasner Sep 28, 2026
f77d828
Refuse `yas terminal send` to an exited or unknown terminal
mgasner Sep 28, 2026
c1b9050
Fix terminal restart, server env flags, and missing-program status
mgasner Sep 28, 2026
e9997c7
Print output of extension attempts that finish before ext run follows…
mgasner Sep 28, 2026
a527d51
Document ext run output, trap, and name-retention behavior
mgasner Sep 28, 2026
efdf9a0
Describe ext manage --from as a registry URL
mgasner Sep 28, 2026
2c81944
Let a plain-text clipboard get read any offered plain-text variant
mgasner Sep 28, 2026
0379371
Make surface capture and record flags match what the server does
mgasner Sep 28, 2026
e7afe8d
Honor YAS_AUDIO=0 and YAS_AUDIO_BITRATE when PipeWire runs for media
mgasner Sep 28, 2026
3b9a755
Label help overlay keys by what they do
mgasner Sep 28, 2026
cfe5488
Refuse --on and --hub for yas open instead of ignoring them
mgasner Sep 28, 2026
46a5604
Deliver a Client DISCONNECT reason to the disconnected client
mgasner Sep 28, 2026
d639104
git: send the first real state as the WATCH snapshot
mgasner Sep 28, 2026
4301d9c
git: text PATCH between index and worktree
mgasner Sep 28, 2026
697c73a
cli: git show without a path, and client-relative --repo
mgasner Sep 28, 2026
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
17 changes: 11 additions & 6 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,8 @@ server's own KV store — see below.
attached backend workspace; they are never transferred by the edge.

Special key: `yas.target = <uri-or-name>` — sets the default for non-browser
CLI commands. Browsers discover Relay routes from the home server and keep the
CLI commands. A name is resolved against the home server's catalogue (below)
on each connection. Browsers discover Relay routes from the home server and keep the
active route set in the attached backend workspace.

### The Relay catalogue — the `remotes` KV key
Expand All @@ -252,11 +253,15 @@ two catalogues rather than one shared home-directory file; and KV already has
watching, compare-and-swap and a client-facing family, so editing a remote
needed no transport of its own.

Managed with `yas remote add/remove/toggle/list`, which now reach the target
server rather than this machine's home directory — `yas --on dev remote add`
edits `dev`'s catalogue — and from the browser's Remotes panel. A server that
Managed with `yas remote add/remove/toggle/list`, which now reach a server
rather than this machine's home directory: the home server by default (not
`yas.target`), or the `--on` server, so `yas --on dev remote add` edits `dev`'s
catalogue. Also managed from the browser's Remotes panel. A server that
finds no `remotes` key at startup imports a pre-KV `yas.remotes` file once, if
there is one.
there is one. The CLI resolves a bare target name (`--on NAME`, `YAS_TARGET`,
`yas.target`) by reading this key from the home server (`YAS_SOCK`, else the
default local instance) and dialing the stored URI itself; it no longer reads
the file.

**The stored URIs carry credentials, and every client of the server can read
them**: a `share:` passphrase, an `ssh:` host reference. That is the trade for
Expand Down Expand Up @@ -318,7 +323,7 @@ All yas components share a common URI vocabulary for addressing yas server insta
| `share:passphrase` | CLI, `yas.remotes` | Native read-only WebRTC share |
| `share:passphrase?hub=URL` | `yas.remotes` | WebRTC via custom hub URL |
| `proxy:uri` | CLI (`--on`) | Explicitly route through yas proxy-daemon |
| `name` | CLI (`--on`), yas.conf | Named remote from yas.remotes |
| `name` | CLI (`--on`), yas.conf | Named remote on the home server |

Set `YAS_PROXY=0` to bypass proxy routing and connect directly for `ssh:`,
`tcp:`, `ws:`, `wss:`, and `wt:` URIs.
Expand Down
27 changes: 22 additions & 5 deletions EMBEDDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,12 @@ You can also run a `@yas-run/core` client **server-side** (Node/Bun/Deno) to dri
local `yas server` over its unix-domain socket — e.g. to script terminals or run
headless commands. The non-browser building blocks live under the
`@yas-run/core/node` subpath (kept out of the package root so `node:net` and
runtime globals never leak into browser bundles):
runtime globals never leak into browser bundles). The packages are plain ES
modules; install `@yas-run/core` with its matching `@yas-run/browser` peer:

```bash
npm install @yas-run/core @yas-run/browser
```

```ts
import { YasWorkspace, exitCodeFromStatus, nullLogger } from "@yas-run/core";
Expand All @@ -315,6 +320,17 @@ const workspace = new YasWorkspace({
connections: [{ id: "default", transport }],
});

// Families are negotiated after the HELLO round trip; wait before creating.
await new Promise<void>((resolve) => {
const check = () => {
if (!workspace.getSnapshot().ready) return;
unsubscribe();
resolve();
};
const unsubscribe = workspace.subscribe(check);
check();
});

const session = await workspace.createSession({
connectionId: "default",
rows: 24,
Expand All @@ -323,10 +339,11 @@ const session = await workspace.createSession({
});
```

The unix transport speaks yas's framing protocol (4-byte little-endian
length-prefixed frames) for you — there is no need to re-implement the wire
format. `BunUnixSocketTransport` and `DenoUnixSocketTransport` are the
runtime-native equivalents.
The unix transport carries the YAS byte stream (the preface, then 4-byte
little-endian length-prefixed frames) for you; there is no need to
re-implement the wire format. `BunUnixSocketTransport` and
`DenoUnixSocketTransport` are the runtime-native equivalents; Deno needs
`--allow-read --allow-write` for the socket path.

### Exit status

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,7 @@ Without any of the above, the compositor falls back to CPU rendering and softwar
| `wireplumber` | Session manager (optional, started if available) |
| `xwayland-satellite` | X11 applications (optional, started if available) |

Audio is disabled automatically when PipeWire is not installed or `libpipewire-0.3.so.0` is not resolvable via `ld.so` (set `LD_LIBRARY_PATH` if you have it in a non-default location), or explicitly with `YAS_AUDIO=0`.
Audio is disabled automatically when PipeWire is not installed or `libpipewire-0.3.so.0` is not resolvable via `ld.so` (set `LD_LIBRARY_PATH` if you have it in a non-default location); the server then prints one `[audio] desktop audio unavailable` line naming the reason (`-v` for details). `YAS_AUDIO=0` stops desktop audio from being offered or streamed to viewers, even when PipeWire still runs for camera, microphone, or portals. `YAS_AUDIO_BITRATE` (bits/sec, default 64000) is the Opus bitrate viewers get when they leave the bitrate at the server's default.

X11 applications run through `xwayland-satellite`, which yas starts once per session when the binary is on `PATH` and exports the resulting `DISPLAY` to every terminal. Wayland stays the first choice for anything that speaks it; X11 is the fallback behind it. Without the binary, sessions are Wayland-only and no `DISPLAY` is exported. Set `YAS_XWAYLAND=0` to opt out.

Expand Down
2 changes: 1 addition & 1 deletion SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ description: >
yas is a terminal multiplexer and experimental headless Wayland compositor. Every terminal can run both CLI programs (via PTYs) and GUI applications (via the built-in compositor). Surfaces are video-encoded and streamed to browsers; the CLI gives programmatic control over both terminals and graphical windows.

Everything the CLI does works locally or against a remote over one wire —
`--on ssh:host`, `--on share:passphrase`, or a named remote — including the
`--on ssh:host`, `--on share:passphrase`, or a named remote from `yas remote` — including the
filesystem, git, and language-server commands. Beyond terminals it can:

- **Files** — `yas fs cat|find|grep|write|sync|mkdir|mv|rm|ln`. `grep`
Expand Down
9 changes: 9 additions & 0 deletions bin/prepare-release
Original file line number Diff line number Diff line change
Expand Up @@ -287,9 +287,13 @@ node - "$target_version" "${js_manifests[@]}" <<'NODE'
const fs = require("fs");
const [version, ...manifests] = process.argv.slice(2);

// @yas-run/browser is built from crates/browser outside the pnpm workspace,
// so `workspace:*` cannot stamp its published range; it tracks the release.
for (const manifest of manifests) {
const pkg = JSON.parse(fs.readFileSync(manifest, "utf8"));
pkg.version = version;
if (pkg.peerDependencies?.["@yas-run/browser"] !== undefined)
pkg.peerDependencies["@yas-run/browser"] = `^${version}`;
fs.writeFileSync(manifest, `${JSON.stringify(pkg, null, 2)}\n`);
}
NODE
Expand Down Expand Up @@ -341,6 +345,11 @@ for (const manifest of manifests) {
console.error(`release: failed to update ${manifest} to version ${expected}`);
process.exit(1);
}
const browser = pkg.peerDependencies?.["@yas-run/browser"];
if (browser !== undefined && browser !== `^${expected}`) {
console.error(`release: failed to update ${manifest} @yas-run/browser peer to ^${expected}`);
process.exit(1);
}
}
NODE

Expand Down
7 changes: 7 additions & 0 deletions crates/alacritty-driver/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -924,6 +924,13 @@ impl TerminalDriver {
out.evicted = true;
}
out.start_seq = seq;
// A cursor past the end of the output reads nothing and resumes from
// the end, so a result's start never lies after its next cursor.
if (seq, out.start_col) > (out.next_seq, out.next_col) {
out.start_seq = out.next_seq;
out.start_col = out.next_col;
return out;
}
if seq > end_inclusive {
return out;
}
Expand Down
23 changes: 23 additions & 0 deletions crates/alacritty-driver/tests/seq_cursor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -295,3 +295,26 @@ fn growing_with_no_history_does_not_invent_rotation() {
assert_eq!(named_lines(&d, 3), before);
assert_eq!(d.cursor_seq(), cursor);
}

#[test]
fn a_cursor_past_the_end_reads_nothing_and_resumes_at_the_end() {
let mut d = TerminalDriver::new(10, 40, 1000);
feed_lines(&mut d, 0..3);
d.process(b"prompt> ");
let (seq, col) = d.cursor_seq();

for (from_seq, from_col) in [(seq, col + 5), (seq + 2, 0), (seq + 1000, 7)] {
let read = d.seq_text(from_seq, from_col, None, 64 * 1024);
assert_eq!(read.text, "");
assert!(!read.truncated);
assert_eq!((read.start_seq, read.start_col), (seq, col));
assert_eq!((read.next_seq, read.next_col), (seq, col));
}

let bounded = d.seq_text(seq + 50, 0, Some(seq), 64 * 1024);
assert_eq!(bounded.text, "");
assert_eq!(
(bounded.start_seq, bounded.start_col),
(bounded.next_seq, bounded.next_col)
);
}
Loading
Loading