Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
2149a46
feat: LLM-powered branch naming from task description
linletian Mar 23, 2026
7c5e2bb
style: apply gofmt formatting
linletian Mar 23, 2026
a8bffa3
feat: LLM branch naming with customizable protocol and manual branch …
linletian Mar 23, 2026
9ff465c
Merge pull request #27 from linletian/feature/llm-task-to-branch
linletian Mar 23, 2026
04433a3
Merge origin/develop into fix/chinese-ime-shift-symbol-fullwidth
linletian Mar 24, 2026
b7bbb78
docs: add Chinese IME shift symbol fullwidth analysis
linletian Mar 25, 2026
9a0addb
build: upgrade xterm.js to v6.0.0 with custom scrollbar styles
linletian Mar 26, 2026
1c9d343
build: add npm integrity verification and source maps for xterm.js
linletian Mar 26, 2026
b9a4a84
Merge pull request #28 from linletian/fix/chinese-ime-shift-symbol-fu…
linletian Mar 26, 2026
3be73be
feat: split Changes panel into Staged/Unstaged accordion sections
linletian May 14, 2026
0cfdf71
feat: remove instance archive/purge, allow delete directly on stop
linletian May 14, 2026
575dd82
Merge branch 'develop' into feature/optimization
linletian May 15, 2026
f2653ff
refactor: overhaul LLM config — remove openai_compatible, add reasoni…
linletian May 15, 2026
eb4a738
refactor: remove labels feature from data model, API, and UI
linletian May 15, 2026
d1208f9
fix: update thinking field to use object format for API compatibility
linletian May 15, 2026
c8fbe9d
style: fix gofmt alignment in state.go
linletian May 15, 2026
cb561aa
Merge pull request #29 from linletian/feature/optimization
linletian May 15, 2026
fe1655e
feat: prioritize main/develop branches in Base Ref dropdown sorting
linletian May 15, 2026
032ec49
Merge pull request #30 from linletian/feature/optimization
linletian May 15, 2026
2ae4d78
fix: add WebSocket connection timeout and improve reconnect handling
linletian May 15, 2026
31d37f8
fix: update test to match new WebSocket reconnect delay (5000ms)
linletian May 15, 2026
5f047c1
Merge pull request #31 from linletian/feature/optimization
linletian May 15, 2026
42a836f
fix: pass git repo Root to instance Manager for MainWorktreeID resolu…
linletian May 15, 2026
8276399
Merge pull request #32 from linletian/feature/optimization
linletian May 15, 2026
356ba16
feat: add tags config directory open and default tags support
linletian May 15, 2026
7614437
Merge pull request #33 from linletian/feature/optimization
linletian May 15, 2026
b64fb3a
fix: make git diff runner injectable to fix flaky status test
linletian May 15, 2026
2d64e7a
feat: show untracked files in unstaged section with line counts and U…
linletian May 17, 2026
dff8da5
Merge pull request #34 from linletian/feature/optimization
linletian May 21, 2026
4d2deac
docs: add Portal Dashboard implementation plan
linletian May 22, 2026
d0eef64
docs: add Portal Dashboard task breakdown
linletian May 22, 2026
593ac90
docs: add Task 0 for project README updates (文档优先)
linletian May 22, 2026
f368be7
docs: add step 0 for README updates to implementation order
linletian May 22, 2026
6cfb41b
docs: expand Task 0 to include PRD, ARCHITECTURE, and API doc updates
linletian May 22, 2026
4cddc7f
docs: Task 0 — update project documentation for Portal Dashboard
linletian May 22, 2026
391fc9a
feat: Task 1 — global auth config (Load/Save auth.json)
linletian May 22, 2026
f471137
feat: Task 2 — CLI portal port flag and config auth commands
linletian May 22, 2026
82d7f36
feat: Task 3 — app config extension, server.json read-merge-write wit…
linletian May 22, 2026
45dcbb1
feat(app): Task 4 - withAuth middleware改造
linletian May 22, 2026
974903f
feat(portal): Task 5 — portal core (claimer, registration, CSRF, clea…
linletian May 22, 2026
02d2a1c
feat(portal): Task 6 — HTTP endpoints & dashboard HTML
linletian May 22, 2026
7663d8a
feat(portal): Task 7 — reverse proxy with bidirectional WebSocket
linletian May 22, 2026
b5a1300
feat(portal): Task 8 — Tailscale Serve 自动管理
linletian May 22, 2026
093d050
portal: implement Task 9 - Go generate CSP hash auto-computation
linletian May 22, 2026
56cd577
feat(portal): Task 10 — Portal lifecycle integration
linletian May 22, 2026
ccf7bdf
feat(portal): Task 11 — comprehensive tests (concurrency, end-to-end,…
linletian May 23, 2026
8ad710c
docs, portal: mark tailscale serve as disabled (macOS CLI bug) ; hard…
linletian May 23, 2026
a85beab
feat: auto-generate auth token when not configured
linletian May 23, 2026
a61c1d9
feat: improve remote access functionality
linletian May 24, 2026
a6f50c8
refactor(portal): remove reverse proxy, use direct links; instances b…
linletian May 24, 2026
fc2f0ba
feat: improve remote access and add tests
linletian May 24, 2026
f8af74e
feat: improve remote access UI and functionality
linletian May 24, 2026
d960677
fix: gofmt all Go files
linletian May 24, 2026
3815b88
fix: replace hardcoded port with config, improve browser shortcut fee…
linletian May 24, 2026
ad6cd71
Merge pull request #35 from linletian/feature/improve-remote-access
linletian May 24, 2026
0adb5cb
fix(ui): unify modal titlebar styles and improve dark mode adaptation
linletian May 24, 2026
5ae525a
Merge pull request #36 from linletian/fix/fix-ui
linletian May 24, 2026
a0bb067
feat: add mw config regen command and per-request auth token reload
linletian May 25, 2026
84a8bfe
docs: sync PRD/API/architecture/readme with actual code implementation
linletian May 25, 2026
2303d22
fix: add SetPathForTest to TestWithAuth_LoopbackBypass to use isolate…
linletian May 25, 2026
8574e26
Merge pull request #37 from linletian/feature/improve-remote-access
linletian May 25, 2026
42040b6
chore(release): prepare v0.3.0
linletian May 25, 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
10 changes: 5 additions & 5 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
- Run server (inside the target repo): `go run ./cmd/myworktree -listen 127.0.0.1:0`
- Run full test suite: `go test ./...`
- Run one package: `go test ./internal/worktree -v`
- Run one test: `go test ./internal/app -run TestNormalizeLabels -v`
- Run one test: `go test ./internal/app -run TestClientIP -v`
- Lint/format check used by CI: `test -z "$(gofmt -l .)"`

## High-level architecture
Expand Down Expand Up @@ -36,10 +36,10 @@ Important behavior notes:
## Key conventions
- **Run location matters**: the server is started by the default command (e.g. `myworktree -listen ...`) and should be executed *inside the target git repo* you want to manage, because repo detection and the per-project data dir derive from CWD.
- **Branch naming**:
- Default: managed worktrees are created on branches like `mwt/<slug>`.
- Custom grouping: if the task description is in the form `<group>/<name>` (e.g. `feature/auth`), the branch will be `<group>/<name>` (no extra prefix).
- `slugify()` is best-effort ASCII, max length ~48; if empty it falls back to `worktree`.
- If the target branch already exists, suffix `-2`, `-3`, ... is appended to `<name>`.
- Branch name is whatever the user provides in the form (manually entered or LLM-generated).
- LLM mode: calls LLM to convert task description into a branch name (e.g. `fix/auth-bug`).
- Custom input: when user directly enters a branch name, it is used as-is.
- No automatic prefix is added (previously `mwt/`).
- **Strict deletion**: `worktree delete` refuses to delete if `git status --porcelain` is non-empty (includes untracked files).
- **State storage discipline**:
- Persisted state lives in `state.json` via `store.FileStore` and is protected with `flock` + atomic rename.
Expand Down
84 changes: 80 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,11 @@ jobs:
target="myworktree_${VERSION}_darwin_${GOARCH}"
dist_dir="dist/${target}"
mkdir -p "${dist_dir}"
ldflags="-s -w -X myworktree/internal/version.Version=${VERSION} -X myworktree/internal/version.Commit=${COMMIT} -X myworktree/internal/version.BuildDate=${build_date}"
GOOS="${GOOS}" GOARCH="${GOARCH}" go build -trimpath -ldflags "${ldflags}" -o "${dist_dir}/myworktree" ./cmd/myworktree
GOOS="${GOOS}" GOARCH="${GOARCH}" go build -trimpath -ldflags "${ldflags}" -o "${dist_dir}/mw" ./cmd/mw
# Only strip debug symbols (-s), keep symbol table (-w removed)
# to preserve macOS code signing compatibility
ldflags="-s -X myworktree/internal/version.Version=${VERSION} -X myworktree/internal/version.Commit=${COMMIT} -X myworktree/internal/version.BuildDate=${build_date}"
CGO_ENABLED=0 GOOS="${GOOS}" GOARCH="${GOARCH}" go build -trimpath -ldflags "${ldflags}" -o "${dist_dir}/myworktree" ./cmd/myworktree
CGO_ENABLED=0 GOOS="${GOOS}" GOARCH="${GOARCH}" go build -trimpath -ldflags "${ldflags}" -o "${dist_dir}/mw" ./cmd/mw
cp README.md LICENSE CHANGELOG.md "${dist_dir}/"
tar -C dist -czf "dist/${target}.tar.gz" "${target}"

Expand All @@ -52,9 +54,83 @@ jobs:
path: dist/*.tar.gz
if-no-files-found: error

codesign-notarize:
runs-on: macos-latest
needs: build-darwin
if: ${{ vars.APPLE_ENABLE_CODESIGN == 'true' }}
strategy:
fail-fast: false
matrix:
goarch:
- amd64
- arm64
steps:
- name: Download archive
uses: actions/download-artifact@v4
with:
name: release-darwin-${{ matrix.goarch }}
path: dist

- name: Extract and codesign
env:
APPLE_DEVELOPER_ID_CERT_BASE64: ${{ secrets.APPLE_DEVELOPER_ID_CERT_BASE64 }}
APPLE_DEVELOPER_ID_CERT_PASSWORD: ${{ secrets.APPLE_DEVELOPER_ID_CERT_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
run: |
set -euo pipefail
archive=$(ls dist/*.tar.gz)
basename=$(basename "${archive}" .tar.gz)
mkdir -p dist/extracted
tar xzf "${archive}" -C dist/extracted

# Import Apple Developer signing certificate
KEYCHAIN="build.keychain"
security create-keychain -p "" "${KEYCHAIN}"
security default-keychain -s "${KEYCHAIN}"
security unlock-keychain -p "" "${KEYCHAIN}"
security set-keychain-settings -lut 21600 "${KEYCHAIN}"
echo "${APPLE_DEVELOPER_ID_CERT_BASE64}" | base64 -d > cert.p12
security import cert.p12 -k "${KEYCHAIN}" -P "${APPLE_DEVELOPER_ID_CERT_PASSWORD}" -T /usr/bin/codesign -T /usr/bin/pkgbuild
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "" "${KEYCHAIN}"

for binary in myworktree mw; do
# Sign with hardened runtime (required for notarization)
codesign --force --options runtime --timestamp --sign "${APPLE_TEAM_ID}" \
"dist/extracted/${basename}/${binary}"
done

# Notarize each binary
for binary in myworktree mw; do
zip_path="dist/${binary}.zip"
ditto -c -k --keepParent "dist/extracted/${basename}/${binary}" "${zip_path}"
xcrun notarytool submit "${zip_path}" \
--apple-id "${APPLE_ID}" \
--team-id "${APPLE_TEAM_ID}" \
--password "${APPLE_APP_SPECIFIC_PASSWORD}" \
--wait
xcrun stapler staple "dist/extracted/${basename}/${binary}"
done

# Re-pack the signed archive
tar czf "${archive}" -C dist/extracted "${basename}"

# Clean up keychain
security delete-keychain "${KEYCHAIN}"

- name: Upload signed archive
uses: actions/upload-artifact@v4
with:
name: release-darwin-${{ matrix.goarch }}
path: dist/*.tar.gz
if-no-files-found: error
overwrite: true

publish:
runs-on: ubuntu-latest
needs: build-darwin
needs: [build-darwin, codesign-notarize]
if: ${{ !failure() }}
steps:
- name: Download archives
uses: actions/download-artifact@v4
Expand Down
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,27 @@
# Changelog

## v0.3.0

Release focused on remote collaboration, build robustness, and Apple Silicon reliability.

Highlights:
- Added **Portal Dashboard** — a shared entry port with auto-discovery of running instances across repos, global auth token (HttpOnly Cookie, CSRF protection), and Tailscale serve readiness.
- Improved **remote access** — instances and portal now bind to `0.0.0.0` by default, auth token auto-generates on first run, and the login flow supports CSRF-protected forms for non-loopback clients.
- Integrated **LLM-powered branch naming** — configurable protocol (OpenAI / Anthropic), with reasoning split support and a manual override option.
- Upgraded terminal shell with **xterm.js v6.0.0** and fixed Chinese IME shift-symbol fullwidth issues.
- Enhanced the **Changes panel** with separate Staged / Unstaged accordion sections, untracked file tracking, and per-file diff stats.
- Streamlined instance lifecycle — instances can be deleted directly on stop (no archive step), with per-worktree tab reordering via optimistic locking.

Build hardening for Apple Silicon:
- Release builds now set `CGO_ENABLED=0` to guarantee pure-Go cross-compilation from Linux to Darwin.
- Removed `-w` linker flag to preserve macOS code-signing compatibility.
- Added optional macOS codesign + notarization job (enabled via repository variables/secrets) to resolve Gatekeeper blocking on Apple Silicon Macs.
- Users who still encounter "no response" on Apple Silicon can run `xattr -d com.apple.quarantine ./mw` to clear the download quarantine attribute.

Documentation and validation:
- Expanded API, architecture, and PRD docs to cover the Portal Dashboard, remote access flow, CSRF protection, and auto-auth generation.
- Release packaging continues to publish Darwin `amd64` / `arm64` archives plus SHA256 checksums via the tag-triggered GitHub Actions workflow, with an optional codesign job.

## v0.2.0

Feature release focused on workspace visibility, terminal continuity, and day-to-day usability improvements.
Expand Down
2 changes: 1 addition & 1 deletion NOTICE
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,5 @@ yusufpapurcu/wmi
Copyright (c) 2013 Stack Exchange
MIT License

xterm.js (bundled)
xterm.js v6.0.0
MIT License
73 changes: 67 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,15 @@ myworktree is a thin management layer that:
## Features (MVP)
- Create/list/import/delete managed worktrees (strict delete: refuses if dirty)
- Start/list/stop managed instances per worktree via **Tag** templates
- Instance restart support (keeps worktree/tag-or-command/labels and links old/new instance records)
- Optional instance labels (`k=v`) with UI filtering/search
- Instance restart support (keeps worktree/tag-or-command and links old/new instance records)
- Web UI can be closed/reopened; instances keep running; WebSocket Web TTY is default (with SSE/HTTP fallback)
- UI shows transport status (`websocket/sse/polling`) and provides WS reconnect action
- Startup reconcile: stale persisted `running` instances are auto-marked `stopped` after mw restart
- Optional built-in HTTPS (`--tls-cert/--tls-key`) and token auth for non-loopback
- Stored backlog redaction for common secrets (e.g. `sk-...`)
- MCP tool endpoints (`/api/mcp/tools`, `/api/mcp/call`)
- Portal Dashboard with shared entry port, auto-discovery of running instances across repos
- Global auth token (HttpOnly Cookie, CSRF protection, tailscale serve integration)

## Requirements
- macOS 12+ (other platforms are not validated yet)
Expand Down Expand Up @@ -76,6 +77,12 @@ Start from `v0.2.0` or newer for public release binaries. The earlier `v0.1.0` G
Each release archive contains `mw`, `myworktree`, `README.md`, `LICENSE`, and `CHANGELOG.md`.
If there is no prerelease/release asset yet, or you need a platform we do not publish, follow the source build steps below.

**Apple Silicon troubleshooting:** macOS may quarantine downloaded binaries and silently prevent execution (Gatekeeper). If the binary does not respond or shows "cannot be opened":
```bash
xattr -d com.apple.quarantine ./mw ./myworktree
```
Or open **System Settings → Privacy & Security** and click "Allow Anyway" for the blocked binaries.

### Build & install

```bash
Expand Down Expand Up @@ -138,6 +145,12 @@ mw
When startup succeeds, `mw` opens the web page automatically at the serving URL by default.
`myworktree` prints the URL without opening a browser unless you pass `-open=true`.

When Portal is enabled, the startup output includes:
```
[portal] Portal dashboard at: http://0.0.0.0:12345/
[portal] Tailscale URL: https://my-machine.tail-scale.ts.net/
```

myworktree uses the **current working directory** to detect the target repo (git root) and derives an isolated per-project data dir from it, so you can manage other projects by running the same binary in a different repo directory.

By default, newly created worktrees are placed next to your repo:
Expand All @@ -164,6 +177,18 @@ myworktree instance start --worktree <worktreeId> --cmd "echo hello && ls"
myworktree instance start --worktree <worktreeId> # starts an interactive shell instance
myworktree instance list
myworktree instance stop <instanceId>

# config (global auth token)
mw config # interactive guided setup (set/view/clear/regen token)
mw config set-auth # set token directly
mw config get-auth # view token (masked)
mw config clear-auth # clear token
mw config regen # regenerate token (with confirmation)

# start with remote access & Portal (IPv6 is explicitly disabled)
mw start --listen 0.0.0.0:0 # LAN access, auto-inherits global token
mw start --listen 0.0.0.0:0 --portal-port 12346 # custom Portal port
mw start --listen 0.0.0.0:0 --portal-port 0 # disable Portal
```

Note: command starts are executed inside the instance shell, and you can continue sending input to the same running instance from the UI.
Expand Down Expand Up @@ -212,10 +237,46 @@ The workflow verifies `gofmt`, runs `go test ./...`, and builds both binaries on
Tagged releases (`v*`) run `.github/workflows/release.yml`, which produces darwin `amd64` / `arm64` archives plus SHA256 checksums.

## Remote access
- Default: binds to loopback only.
- If you listen on a non-loopback address, you must set `--auth`.
- For HTTPS, provide `--tls-cert` and `--tls-key`.
- `?token=<token>` works for simple clients, but prefer `Authorization: Bearer <token>` to avoid leaving tokens in browser history or shell history.

### Global Token

Configure a global auth token once, and all instances automatically inherit it:

```bash
mw config
# → Interactive guided setup: [1] set token [2] view token [3] clear token [4] regen token [q] quit
# Token stored in ~/.config/myworktree/auth.json (0600 permissions)
```

When `--auth` is not provided and no token exists in `auth.json`, the CLI **automatically generates a random 32-character hex token** and persists it. This ensures instances always have authentication enabled by default. Instance-level `--auth` override takes precedence over the global token.

### Portal Dashboard

`mw start --listen 0.0.0.0:0` starts a **Portal Dashboard** on port `12345` (configurable via `--portal-port`). The dashboard:

- Lists all running instances across repos with auto-discovery
- Click an instance to jump to its Web UI (directly via instance port — Portal reverse proxy `/s/<repo-hash>/` is planned but not yet implemented)
- Uses **HttpOnly Cookie** (`mw_token`) for authentication — token never appears in URL or JS
- **CSRF protection** via double-submit cookie pattern on login/logout endpoints
- Cookie has 24-hour **sliding expiration** (refreshed on each auth-successful request)

Set `--portal-port 0` to disable the Portal.

### Tailscale HTTPS

> **Note**: Automatic `tailscale serve` configuration is **currently disabled** due to a Tailscale 1.98 CLI bug on macOS where `tailscale serve --bg` reports success but does not actually configure the proxy. The relevant code exists in `internal/portal/portal.go` but is not wired into the production code paths. Users can still securely access Portal via Tailscale IP (`http://100.x.x.x:12345`) over the WireGuard tunnel. Automatic `tailscale serve` management will be re-enabled once Tailscale fixes the upstream bug.

### Network Security

| Access Path | Protocol | Encryption Layer |
|-------------|----------|-----------------|
| Instance direct (local/LAN IP) | `http://192.168.1.18:PORT` → instance | None (LAN only) |
| Instance direct (Tailscale IP) | `http://100.x.x.x:PORT` → instance | WireGuard tunnel |
| Dashboard + proxy (local/LAN) | `http://host:12345` → proxy `http://127.0.0.1:PORT` | None (LAN only) |
| Dashboard + proxy (Tailscale IP) | `http://100.x.x.x:12345` → proxy `http://127.0.0.1:PORT` | WireGuard tunnel |
| `tailscale serve` domain | `https://machine.ts.net` → proxy `http://127.0.0.1:PORT` | Let's Encrypt TLS + WireGuard |

> **Note**: Tailscale's WireGuard tunnel provides network-layer encryption. Application-layer HTTPS is only used when accessing via `tailscale serve` domain (Let's Encrypt certificate).

## License
MIT. See [LICENSE](./LICENSE).
Loading