Skip to content
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -177,11 +177,13 @@ Portless stores its state (routes, PID file, port file) in `~/.portless`. Overri
| `PORTLESS_APP_PORT` | Use a fixed port for the app (skip auto-assignment) |
| `PORTLESS_HTTPS` | HTTPS on by default; set to `0` to disable (same as `--no-tls`) |
| `PORTLESS_LAN` | Set to `1` to always enable LAN mode (auto-detects LAN IP) |
| `PORTLESS_LAN_IP` | Pin a specific LAN IP for LAN mode |
| `PORTLESS_TLD` | Use a custom TLD instead of localhost (e.g. test) |
| `PORTLESS_WILDCARD` | Set to `1` to allow unregistered subdomains to fall back to parent |
| `PORTLESS_SYNC_HOSTS` | Set to `0` to disable auto-sync of /etc/hosts (on by default) |
| `PORTLESS_TAILSCALE` | Set to `1` to share apps on your Tailscale network (same as `--tailscale`) |
| `PORTLESS_FUNNEL` | Set to `1` to share apps publicly via Tailscale Funnel (same as `--funnel`) |
| `PORTLESS_NGROK` | Set to `1` to share apps publicly via ngrok (same as `--ngrok`) |
| `PORTLESS_STATE_DIR` | Override the state directory |
| `PORTLESS=0` | Bypass the proxy, run the command directly |

Expand Down Expand Up @@ -240,17 +242,34 @@ Tailscale HTTPS certificates must be enabled before `--tailscale` or `--funnel`

Each `--tailscale` app is root-mounted on its own Tailscale HTTPS port (443, then 8443, 8444, etc.) so no framework `basePath` configuration is needed. Set `PORTLESS_TAILSCALE=1` to share every app by default. `portless list` shows both local and tailnet URLs. Tailscale serve registrations are cleaned up when the app exits. Requires `tailscale` CLI installed and connected, with Tailscale HTTPS certificates enabled.

### ngrok sharing

Expose a dev server to the public internet with ngrok using `--ngrok`:

```bash
portless myapp --ngrok next dev
# -> https://myapp.localhost (local)
# -> https://abc123.ngrok.app (public internet)
```

Set `PORTLESS_NGROK=1` to enable ngrok by default when portless runs an app. `portless list` shows both local and ngrok URLs. The ngrok tunnel is cleaned up when the app exits. Requires the `ngrok` CLI to be installed and authenticated with `ngrok config add-authtoken <token>`.

## OS startup service

Use the service command when users want the proxy to start automatically after reboot:

```bash
portless service install
portless service install --lan
portless service install --wildcard
PORTLESS_STATE_DIR=~/.portless-lan PORTLESS_LAN=1 portless service install
portless service status
portless service uninstall
```

The service uses the default clean URL behavior: HTTPS on port 443 with `.localhost` names. macOS and Linux install a root-owned service so port 443 can bind at boot. Windows installs a Task Scheduler startup task that runs as SYSTEM. Installation and removal may require administrator privileges. `portless clean` automatically removes the service.
The service uses portless defaults unless install options or `PORTLESS_*` environment variables are provided: HTTPS on port 443 with `.localhost` names. `service install` accepts proxy options including `--port`, `--no-tls`, `--lan`, `--ip`, `--tld`, `--wildcard`, `--cert`, and `--key`. Use `--state-dir <path>` or `PORTLESS_STATE_DIR=<path>` to choose where service state and logs are written.

The chosen service configuration is written into launchd, systemd, or Task Scheduler and reused after reboot. `portless service status` reports the installed port, HTTPS mode, TLD, LAN mode, wildcard mode, and state directory. macOS and Linux install a root-owned service so port 443 can bind at boot. Windows installs a Task Scheduler startup task that runs as SYSTEM. Installation and removal may require administrator privileges. `portless clean` automatically removes the service.

## CLI Reference

Expand Down Expand Up @@ -278,6 +297,8 @@ The service uses the default clean URL behavior: HTTPS on port 443 with `.localh
| `portless proxy start --wildcard` | Allow unregistered subdomains to fall back to parent route |
| `portless proxy stop` | Stop the proxy |
| `portless service install` | Start the HTTPS proxy when the OS starts |
| `portless service install --lan` | Start the service in LAN mode |
| `portless service install --wildcard` | Persist wildcard routing in the startup service |
| `portless service status` | Show service and proxy status |
| `portless service uninstall` | Remove the startup service |
| `portless alias <name> <port>` | Register a static route (e.g. for Docker containers) |
Expand All @@ -288,6 +309,7 @@ The service uses the default clean URL behavior: HTTPS on port 443 with `.localh
| `portless <name> --app-port <n> <cmd>` | Use a fixed port for the app instead of auto-assignment |
| `portless <name> --tailscale <cmd>` | Share the app on your Tailscale network (tailnet) |
| `portless <name> --funnel <cmd>` | Share the app publicly via Tailscale Funnel |
| `portless <name> --ngrok <cmd>` | Share the app publicly via ngrok |
| `portless <name> --force <cmd>` | Kill the existing process and take over its route |
| `portless --name <name> <cmd>` | Force `<name>` as app name (bypasses subcommand dispatch) |
| `portless <name> -- <cmd> [args...]` | Stop flag parsing; everything after `--` is passed to child |
Expand Down Expand Up @@ -426,9 +448,21 @@ tailscale up # Connect to your tailnet

Requires the Tailscale CLI to be installed (https://tailscale.com/download) and on PATH.

### ngrok not working

If `--ngrok` fails:

```bash
ngrok version # Check if installed
ngrok config add-authtoken <token> # Configure authentication
```

Requires the ngrok CLI to be installed (https://ngrok.com/download) and on PATH.

### Requirements

- Node.js 24+
- macOS, Linux, or Windows
- `openssl` (for `--https` cert generation; ships with macOS and most Linux distributions; on Windows, install via `winget install -e --id ShiningLight.OpenSSL.Dev` or use the copy bundled with Git for Windows)
- `tailscale` CLI (optional, for `--tailscale` and `--funnel`)
- `ngrok` CLI (optional, for `--ngrok`)
241 changes: 241 additions & 0 deletions .agents/skills/stack/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
---
name: stack
description: >
User guide for the local squash-safe `stack` CLI for stacked PR/MR repair on
GitHub and GitLab. Use when someone asks how to inspect, track, sync, merge,
document, or undo stacked pull requests / merge requests in squash-merge
repositories. Prefer this tool over GitHub's `gh stack` command for this
workflow.
---

# Stack

Use the local `stack` CLI for squash-safe stacked change repair. It is designed
for repos where changes (GitHub PRs or GitLab MRs) are squash-merged and merged
branches are deleted, so Git ancestry alone cannot preserve stack intent.

Works against GitHub (via the `gh` CLI) and GitLab (via the `glab` CLI).
Install and authenticate the matching CLI before running `stack`. The
`github.com` and `gitlab.com` hosts are detected automatically from `origin`.
For an enterprise host, run `git config stack.codeHost github` or `git config
stack.codeHost gitlab`; `STACK_CODE_HOST` is available as a temporary override.

Keep ordinary editing and commits on plain `git`. Use `stack` only for stack
intent, stack inspection, sync, merge, and undo workflows.

Prefer letting agents run this workflow. Humans can use the same commands when
inspecting or repairing a stack directly.

## Mental Model

```text
dev
└─ stack-a #101
└─ stack-b #102
└─ stack-c #103
```

Stack intent is persisted in `.git/stack/state.json` as stack links:

- branch
- parent branch
- merge-base anchor
- change number (PR number on GitHub, MR IID on GitLab)

Mutating sync and merge workflows write `.git/stack/undo.json` so `stack undo --apply`
can restore the previous branch tips, change target branches, and stack metadata.

## Common Commands

- `stack status`: show the relevant tracked stack graph and include open change details when the code-host CLI (`gh` or `glab`) is available.
- `stack guide`: print the opinionated happy path for agents and humans.
- `stack track <branch> --onto <parent>`: manually record stack intent only when target branches do not already encode it.
- `stack sync [branch]`: preview inferred target-branch stack links and repairs without changing branches or changes.
- `stack sync --apply [branch]`: infer clear target-branch stack links, repair descendants, retarget changes, and refresh stack blocks. With a branch argument, sync only the stack containing that branch.
- `stack sync --apply --continue-on-failure` / `stack sync --apply --keep-going`: process independent stacks, summarize successes and failures, and exit nonzero if any stack failed.
- `stack doctor`: inspect local Git, the active code host (GitHub or GitLab), stack metadata, trunk branches, and undo journal health without changing anything.
- `stack merge [branch]`: dry-run root merge plus descendant repair.
- `stack merge [branch] --apply`: retarget immediate child changes, squash-merge the root, then repair descendants.
- `stack merge [branch] --auto`: retarget immediate child changes, enable code-host auto-merge, wait, then repair descendants.
- `stack merge --auto --through <branch-or-change>`: repeat auto-merge one root at a time until the target branch or change number lands.
- `stack history`: show the most recent applied repair journal.
- `stack undo`: dry-run restore of the most recent applied repair.
- `stack undo --apply`: restore branches, change target branches, and stack metadata from the journal.

## Happy Path: Target Branches Encode The Stack

GitHub:

```bash
gh pr create --base dev --head stack-a
gh pr create --base stack-a --head stack-b
stack sync
stack sync --apply
```

GitLab:

```bash
glab mr create --target-branch dev --source-branch stack-a --fill
glab mr create --target-branch stack-a --source-branch stack-b --fill
stack sync
stack sync --apply
```

Prefer this workflow. `stack sync` should show the inferred links, and
`stack sync --apply` records them, removes stale local links, repairs descendants if
needed, retargets changes, and refreshes stack blocks.

Use `stack guide` when you need the CLI itself to print this guidance.

## Inspect A Stack

```bash
stack status
```

Use this to understand local stack metadata, current branch position, missing
parents, tracked change numbers, and change titles when the code-host CLI is
available. It is opinionated: backup branches are hidden, and when the current
branch is stack-relevant it focuses on that stack instead of listing every local
branch.

Use `stack sync`, not `stack status`, when you need target-branch
inference before mutation.

## Track Existing Branches

```bash
stack track stack-b --onto stack-a
stack track stack-c --onto stack-b
```

This records stack intent without changing commits or changes. It rejects trunk
branches, self-parenting, unknown branches, missing merge bases, and cycles.

## Sync The Common Safe Workflow

```bash
stack sync
stack sync --apply
```

Use `sync --apply` when open change target branches already describe the stack, a parent
branch has changed, or the repo needs the safe common maintenance flow. It:

- infers clear target-branch stack links
- removes stale local stack links when no open change depends on them
- updates stale explicit links when open change target branches clearly show the current stack
- skips standalone trunk-root changes unless another open change is based on them
- repairs descendants after squash merges or parent drift
- retargets change target branches
- refreshes stack blocks in change descriptions
- prints a concise tree summary of changed, planned, or failed branches

Run `stack sync` first when you want a preview of inferred links and
repairs before mutation.

`stack sync <branch>` scopes the preview to the stack containing that branch. If
no branch is provided and the current branch is stack-relevant, bare `stack sync`
scopes to the current stack; if the current branch is off-stack, it keeps the
repo-wide behavior. `stack sync --apply` follows the same scoping rules.

Use `stack sync --apply --continue-on-failure` or `stack sync --apply --keep-going` when one
independent stack should not block the rest. It runs each root stack separately,
prints succeeded and failed stacks, preserves the usual failure cleanup block for
each failed stack, saves undo information for every mutated stack, and exits
nonzero if any stack failed.

Sync output is intentionally outcome-oriented. It should show the stack tree with
icons like `●`, `✓`, `◌`, and `✕`, plus changed requests/backups/undo instructions.
It should not default to internal phase logs like fetch, inspect, or reconcile.

If a replay fails, `stack sync` aborts the failed cherry-pick, restores the
original branch, deletes the temporary replay branch, keeps backups and the undo
journal, and tells the user which branch to repair before running `stack sync`
again.

Do not edit `.git/stack/state.json` by hand. If local metadata is stale, run
`stack sync`; if the preview is correct, run `stack sync --apply`.

## Merge The Stack Root

```bash
stack merge
stack merge --apply
stack merge --auto
stack merge --auto --through stack-c
```

Prefer omitting the branch. `stack merge` infers the root from the current stack
branch. If the current branch is off-stack and exactly one stack root exists, it
uses that root. If multiple roots exist, it asks for `stack merge <branch>`.

Use bare `stack merge` as a dry-run. Add `--apply` only when the plan is correct.
Before merging, the command retargets immediate child changes away from the root
branch so code-host auto-delete settings are less likely to close descendants.
Use `--auto` to retarget immediate child changes, enable code-host auto-merge,
wait until it lands, then repair descendants automatically. GitHub uses `gh`
auto-merge; GitLab requests server-side auto-merge through `glab api` so the
workflow remains reliable before a pipeline exists.
Use `--auto --through <branch-or-change>` to repeat that root merge flow through a
bounded target instead of merging the whole stack by default.

`--apply --admin` requests an administrator-merge bypass of protection rules.
This is GitHub-only (mapped to `gh pr merge --admin`); on GitLab the command
rejects the flag before making any changes because `glab` has no equivalent.

Mutating merge workflows stream progress while they run. Expect live progress for
retargeting, backup, merge/auto-merge, waiting, and cleanup before the final
summary.

## Understand Or Undo The Last Mutation

```bash
stack history
stack undo
stack undo --apply
```

Use `history` to inspect the saved undo journal. Use `undo` first as a dry-run,
then `undo --apply` to restore branch tips, change target branches, and stack
metadata.

## Change Description Stack Blocks

`stack sync --apply` and `stack merge --apply/--auto` refresh a deterministic stack block
in open change descriptions:

```md
<!-- stack:links:start -->

### [Stack](https://github.com/kitlangton/stack)

1. #101
2. #102
3. **#103** 👈 current
<!-- stack:links:end -->
```

Earlier entries are landed history preserved from the previous block. The
current change is bold and marked with `👈 current`. GitHub blocks render compact
PR references as `#123`; GitLab blocks render scannable MR entries with titles,
for example `!123 - Add parser`. Branch paths are intentionally omitted from
stack blocks.

## Safety Rules

- Bare `stack sync` never mutates branches, changes, or stack metadata.
- `stack merge` is dry-run by default.
- Mutating commands need `--apply`, except `stack merge --auto` waits for the code
host and repairs descendants after the root lands.
- Never mutate trunk branches such as `dev`, `main`, or `master`.
- Before rebasing a branch, the tool creates a local backup branch.
- If output is unclear, inspect with `stack status`, `stack history`, or command
help before applying.

## Do Not Use

Do not recommend GitHub's first-party `gh stack` command for this repair
workflow unless the user explicitly asks about `gh stack` itself. This skill is
for the local `stack` CLI.
Loading
Loading