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
6 changes: 6 additions & 0 deletions LOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,3 +151,9 @@ rerun while the npm namespace permission remains unchanged.
## BUI-938 CLI release

The user clarified the deployment boundary: publish a normal stable CLI containing BUI-938 and the latest Factory tools; Account and Web remain on dev. Remove the temporary CLI dev-prerelease workflow changes. The combined CLI passes 974 tests, types, lint/format and package checks. The existing demo's registration and free fake-data read both passed through the published candidate. Promote the CLI only through its normal main release workflow; do not promote Unity/Account or Personal Server main. Demo commands select VANA_ENV=dev. BUI-945 is not a prerequisite for own-app free reads.

## Approved five-screen onboarding delivery

Finish and ship only the approved welcome, searchable 12-row source picker, missing-source name, connector decision and guide handoff. Start from v0.46.0; preserve its public Factory 0.0.2 workflow and BUI-938. Do not merge #106's funding or collection-readiness work. Interactive standalone installation opens bare `vana` via the controlling terminal; automated installs skip prompts. Skills are bundled without installing into agent harnesses; the advertised optional setup uses existing explicit skill selection. Proof must exercise a fresh installer and all five actual terminal screens, including clean exit with guide visible.

Proof: fresh piped standalone install in a disposable home reached welcome, source picker, Ctrl+O source-name input, Readwise connector decision and canonical Factory guide, then exited 0 with output retained. The first test reproduced the remaining forced-help entry override before repair. Full validation passes 976 tests; CLI preflight uses browser/network-blocked fixtures and passes. Packaging checks require the bundled current Factory guide. Structured Codex closeout review is clean at P0 (0.96). Funding/app commands, Factory adapter and the current Create Connector instructions have no diff from v0.46.0. Delivery is the scoped onboarding PR and normal stable release; #106's funding work stays unmerged.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ Or install the standalone binary, which is signed and notarized on macOS:

```bash
curl -fsSL https://raw.githubusercontent.com/vana-com/vana-cli/main/install/install.sh | sh
vana status
```

At a human terminal, installation opens the welcome menu. Choose **Collect or manage my data** to search sources; press Ctrl+O for a missing source and follow the connector guide. Run `vana` to reopen the menu. Automated installs remain prompt-free.

Windows uses `install/install.ps1`. The Homebrew tap (`vana-com/tap`) is
no longer updated and ships a months-old canary, so use one of the routes
above instead.
Expand Down
5 changes: 4 additions & 1 deletion install/install.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,10 @@ try {
Write-Host ""
Write-Host "Installed vana to $WrapperPath"
Write-Host "Next step:"
Write-Host " vana status"
Write-Host " vana"
if (-not [Console]::IsInputRedirected -and -not [Console]::IsOutputRedirected -and -not $env:CI -and -not $env:AGENT) {
& $WrapperPath
}
}
finally {
if (Test-Path $TempDir) {
Expand Down
11 changes: 10 additions & 1 deletion install/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -150,4 +150,13 @@ esac

echo ""
echo "Next step:"
echo " vana status"
echo " vana"

# curl | sh consumes stdin; human interaction must use the controlling terminal.
if [ -t 1 ] && [ -z "${CI:-}" ] && [ -z "${AGENT:-}" ]; then
if [ -t 0 ]; then
"$BIN_DIR/vana" || true
elif [ -r /dev/tty ]; then
"$BIN_DIR/vana" </dev/tty || true
fi
fi
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@
"node": "^22.23.4 || ^24.22.1 || >=26.12.1"
},
"dependencies": {
"@inquirer/core": "11.1.7",
"@inquirer/prompts": "8.3.0",
"@inquirer/search": "^4.1.6",
"@modelcontextprotocol/sdk": "^1.27.1",
Expand Down
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions scripts/assert-pack-contents.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { spawnSync } from "node:child_process";

const requiredPaths = [
"dist/cli/bin.js",
"dist/skills/bundled/registry.json",
"dist/skills/bundled/create-connector/SKILL.md",
"dist/cli/index.js",
"dist/runtime/managed-playwright.js",
"dist/vendor/pdpp-connector-manager/index.mjs",
Expand Down
2 changes: 2 additions & 0 deletions scripts/assert-sea-artifact.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ await assertExists(
const requiredDirectoryEntries = [
binaryName,
"app/sea-entry.cjs",
"app/dist/skills/bundled/registry.json",
"app/dist/skills/bundled/create-connector/SKILL.md",
"app/package.json",
"app/dist/cli/bin.js",
"app/dist/cli/main.js",
Expand Down
14 changes: 14 additions & 0 deletions scripts/copy-vendor.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,17 @@ for (const relative of ASSET_DIRS) {
await fs.rm(destination, { recursive: true, force: true });
await fs.cp(source, destination, { recursive: true });
}

// Both npm and SEA ship dist/, including the public skills for offline setup.
await fs.rm(path.join(repoRoot, "dist/skills/bundled"), {
recursive: true,
force: true,
});
await fs.cp(
path.join(repoRoot, "skills"),
path.join(repoRoot, "dist/skills/bundled"),
{
recursive: true,
force: true,
},
);
6 changes: 6 additions & 0 deletions scripts/test-install-unix.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,9 @@ sh "$ROOT_DIR/install/install.sh"
PATH="$HOME_DIR/bin:$PATH" HOME="$HOME_DIR" "$HOME_DIR/bin/vana" status --json >/dev/null

echo "Unix installer smoke test passed"

python3 "$ROOT_DIR/scripts/test-onboarding.py" \
--installer "$ROOT_DIR/install/install.sh" \
--version test-release \
--release-base-url "file://$WORK_DIR/local-release" \
--evidence "$WORK_DIR/onboarding-proof.json"
142 changes: 142 additions & 0 deletions scripts/test-onboarding.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
#!/usr/bin/env python3
"""Exercise the real piped Unix installer and five onboarding screens in a PTY.

Use --release-base-url file:///... --version candidate for a local SEA release;
by default this downloads and tests the latest published release. No account,
provider, browser, grant or payment is used. State lives in a disposable home.
"""
import argparse
import errno
import fcntl
import json
import os
from pathlib import Path
import pty
import re
import select
import shlex
import signal
import struct
import tempfile
import termios
import time

parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--installer", default="install/install.sh")
parser.add_argument("--release-base-url")
parser.add_argument("--version")
parser.add_argument("--evidence", default="/tmp/vana-onboarding-proof.json")
args = parser.parse_args()
ansi = re.compile(r"\x1b\[[0-?]*[ -/]*[@-~]|\x1b\][^\x07]*(?:\x07|\x1b\\)")


class Terminal:
def __init__(self, command, env):
self.pid, self.fd = pty.fork()
if self.pid == 0:
os.execvpe("sh", ["sh", "-c", command], env)
fcntl.ioctl(self.fd, termios.TIOCSWINSZ, struct.pack("HHHH", 44, 120, 0, 0))
self.output = ""
self.offset = 0
self.closed = False
self.reaped = False

def expect(self, text, timeout=45):
deadline = time.monotonic() + timeout
while text not in ansi.sub("", self.output[self.offset:]):
if self.closed or time.monotonic() >= deadline:
raise AssertionError(f"Did not see {text!r}:\n{ansi.sub('', self.output)[-6000:]}")
ready, _, _ = select.select([self.fd], [], [], 0.2)
if ready:
try:
chunk = os.read(self.fd, 65536)
except OSError as error:
if error.errno != errno.EIO:
raise
chunk = b""
if not chunk:
self.closed = True
self.output += chunk.decode(errors="replace")
return ansi.sub("", self.output[self.offset:])

def send(self, data):
self.offset = len(self.output)
os.write(self.fd, data)

def finish(self):
deadline = time.monotonic() + 10
while time.monotonic() < deadline:
pid, status = os.waitpid(self.pid, os.WNOHANG)
if pid:
self.reaped = True
assert os.waitstatus_to_exitcode(status) == 0, self.output[-3000:]
return
# Drain pending terminal output so the process can finish writing.
if not self.closed and select.select([self.fd], [], [], 0.1)[0]:
try:
chunk = os.read(self.fd, 65536)
self.output += chunk.decode(errors="replace")
self.closed = not chunk
except OSError as error:
if error.errno != errno.EIO:
raise
self.closed = True
raise AssertionError("CLI did not exit after the guide")

def close(self):
if not self.reaped:
os.kill(self.pid, signal.SIGTERM)
os.waitpid(self.pid, 0)
os.close(self.fd)


proof = {}
with tempfile.TemporaryDirectory(prefix="vana-onboarding-") as directory:
home = Path(directory)
env = dict(os.environ)
for key in ("CI", "AGENT", "VANA_DETACHED", "VANA_HOME", "VANA_APP_ROOT", "VANA_SKILLS_DIR", "DATA_CONNECTORS_DIR"):
env.pop(key, None)
env.update(HOME=str(home), VANA_HOME=str(home / ".vana"),
VANA_INSTALL_ROOT=str(home / "release"), VANA_INSTALL_BIN_DIR=str(home / "bin"),
TERM="xterm-256color", VANA_TELEMETRY_DISABLED="1", VANA_NO_UPDATE_NOTIFIER="1")
if args.version:
env["VANA_VERSION"] = args.version
if args.release_base_url:
env["VANA_RELEASE_BASE_URL"] = args.release_base_url
installer = shlex.quote(str(Path(args.installer).resolve()))
terminal = Terminal(f"cd {shlex.quote(directory)} && cat {installer} | sh", env)
try:
welcome = terminal.expect("Up/Down: move | Enter: choose | Ctrl+C: exit", 180)
for label in ("[OK] Vana installed.", "Collect or manage my data", "Use or share data",
"Build or test a connector", "Manage my Personal Server",
"Agent skills are included. No installation needed.", "vana skills setup"):
assert label in welcome, label
assert welcome.index("Manage my Personal Server") < welcome.index("Help: vana --help")
proof["1-welcome-from-install"] = welcome
terminal.send(b"\r")
terminal.expect("Ctrl+O: Source not listed | Ctrl+C: exit")
proof["2-source-picker"] = terminal.expect("Amazon")
terminal.send(b"\x0f")
proof["3-source-name"] = terminal.expect("Enter: continue | Ctrl+C: exit")
assert "Which source are you looking for?" in proof["3-source-name"]
terminal.send(b"Readwise")
terminal.expect("Name or website: Readwise")
terminal.send(b"\r")
decision = terminal.expect("Choose another source")
assert "Readwise isn't in Vana's source list." in decision
assert "A connector is needed to collect your highlights." in decision
assert "Build a Readwise connector" in decision
proof["4-connector-decision"] = decision
terminal.send(b"\r")
proof["5-guide"] = terminal.expect("Follow the guide yourself, or share it with your agent.")
assert "https://github.com/vana-com/vana-cli/blob/main/skills/create-connector/SKILL.md" in proof["5-guide"]
terminal.finish()
# Discovery must not install agent skills or require sign-in.
assert not (home / ".agents/skills").exists()
assert not (home / ".claude/skills").exists()
proof["exit"] = 0
print("PASS: fresh piped install -> welcome -> picker -> source name -> connector choice -> guide -> exit 0")
finally:
proof["raw-terminal"] = terminal.output
Path(args.evidence).write_text(json.dumps(proof, indent=2))
terminal.close()
Loading
Loading