Skip to content

cli: add Go CLI (subtext binary + @fullstory/subtext-cli npm wrapper)#75

Merged
jurassix merged 17 commits into
mainfrom
clint/subtext-cli
May 29, 2026
Merged

cli: add Go CLI (subtext binary + @fullstory/subtext-cli npm wrapper)#75
jurassix merged 17 commits into
mainfrom
clint/subtext-cli

Conversation

@jurassix
Copy link
Copy Markdown
Collaborator

What's in this PR

This adds a standalone Go CLI to the subtext repo under cli/. It replaces the previous internal-only Bazel build with a standard Go module, ships binaries via GoReleaser, and wraps them for npx consumption via @fullstory/subtext-cli.

The source was ported from the fullstory/mn monorepo (subtext/sightmap-upload-cmd@68b9093), which itself incorporates two completed feature branches:

  • cli-format-flag--format json/text flag, Go-native reverse tunnel client
  • subtext/sightmap-upload-cmdsubtext sightmap upload command for uploading .sightmap/ definitions to a live session

No code logic was changed during the port — only import paths and build infrastructure.


What the CLI does

subtext is a terminal interface to the Subtext MCP server. It lets you drive the same tools an AI agent uses — live browser sessions, comments, proof documents, sightmap uploads — directly from a shell or CI script.

subtext live    connect | view navigate | view screenshot | act click | …
subtext comment list | add | reply | resolve
subtext doc     create | update | attach | close
subtext tunnel  connect | disconnect | status
subtext artifact upload | url
subtext sightmap upload
subtext auth    whoami

Structure

cli/
  cmd/subtext/main.go         entry point
  internal/
    auth/                     API key resolution
    cli/                      cobra commands (namespaces, call dispatch, render)
    config/                   config file (~/.config/subtext/config.yaml)
    mcpclient/                JSON-RPC MCP client + endpoint logic
    sightmap/                 .sightmap/ walker + HTTP upload
    tunnel/                   Go-native yamux reverse tunnel client
    fstesting/                local Ok/Equals/Assert test helpers (replaces internal mn package)
  npm/
    package.json              @fullstory/subtext-cli
    install.js                postinstall: downloads binary from GitHub Releases
    bin/subtext.js            thin spawnSync wrapper
  .goreleaser.yml             builds darwin/linux/windows × amd64/arm64
  RELEASING.md                step-by-step release guide
  README.md                   end-user install + usage docs

Distribution

Channel How
npx @fullstory/subtext-cli npm postinstall downloads binary from GitHub Releases
npm install -g @fullstory/subtext-cli same
go install github.com/fullstorydev/subtext/cli/cmd/subtext@latest standard Go toolchain
Download binary GitHub Releases page

A new GitHub Actions workflow (.github/workflows/release-cli.yml) fires on cli-v* tags, runs tests, and calls GoReleaser to publish the GitHub Release. The npm publish step is gated behind a PUBLISH_NPM=true repo variable (pending @fullstory npm scope provisioning).


Before the first release

See cli/RELEASING.md for the full runbook. The two things that need to exist before tagging cli-v0.1.0:

  1. NPM_TOKEN secret + PUBLISH_NPM=true repo variable (once @fullstory scope is provisioned)
  2. That's it — homebrew tap omitted intentionally until the tap repo exists

Test plan

  • go build ./... — clean
  • go test ./... — all 6 packages pass
  • GOOS=windows go build ./... — cross-compiles cleanly (Unix-specific tunnel code excluded via build tag)
  • goreleaser release --snapshot --clean --skip=publish — all 6 OS/arch archives produced, subtext version prints the injected version string

jurassix added 8 commits May 28, 2026 11:18
Copies the Go subtext CLI from the fullstory/mn monorepo.
Source: projects/fullstory/go/src/fs/services/lidar/main/subtext/
Branch: subtext/sightmap-upload-cmd at 68b9093217

Includes the --format flag, Go-native tunnel client, and sightmap
upload command. BUILD.bazel files removed; main.go moved to
cmd/subtext/main.go per Go conventions. No code edits in this commit.
Replace all monorepo-internal import paths:
- fs/services/lidar/main/subtext/internal/* -> github.com/fullstory/subtext/cli/internal/*
- fs/fstesting -> github.com/fullstory/subtext/cli/internal/fstesting
- go.mod: module github.com/fullstory/subtext/cli
- internal/fstesting: local Ok/Equals/Assert helpers replacing fs/fstesting
- go mod tidy resolves cobra, yaml.v2, gorilla/websocket, hashicorp/yamux

go build ./... and go test ./... both pass (6 test packages).
.goreleaser.yml builds subtext for darwin/linux/windows x amd64/arm64.
Triggered by cli-v* tags; posts GitHub release + homebrew tap update.
npm publish gated behind PUBLISH_NPM repo variable.
cli/npm/ contains:
- package.json: name @fullstory/subtext-cli, bin subtext
- install.js: postinstall script downloads binary from GitHub releases
  (SUBTEXT_SKIP_DOWNLOAD=1 bypasses download before first tag exists)
- bin/subtext.js: thin spawnSync wrapper around the vendored binary

npm publish in the release workflow is gated behind PUBLISH_NPM repo variable.
Replace the internal Bazel-build README with an end-user-focused doc.
Covers install (npx/npm/brew/go install/binary), quickstart, command
table, calling conventions, auth, config, env vars, and sightmap upload.
- Rename all github.com/fullstory -> github.com/fullstorydev throughout
  (matches the actual remote: github.com/fullstorydev/subtext)
- cli/internal/cli.Version var: injected via goreleaser ldflags so
  'subtext version' prints the real release tag, not 'dev'
- tunnel.go: add //go:build !windows; add tunnel_windows.go stub
  (syscall.Kill / Setsid are not available on Windows)
- .goreleaser.yml: fix archive format fields (formats: [] array),
  update ldflags to inject Version
- cli/.gitignore: exclude dist/
- cli/RELEASING.md: step-by-step release guide for the team

goreleaser snapshot builds all 6 OS/arch archives cleanly.
go test ./... passes on all 6 packages.
Comment thread .github/workflows/release-cli.yml
Comment thread cli/internal/sightmap/upload.go
Comment thread cli/internal/tunnel/transport.go
jurassix added 9 commits May 28, 2026 11:53
- tunnel/transport.go: discard io.Copy returns in bidirectional proxy goroutines
- cli/tunnel.go: discard os.Unsetenv returns in daemon cleanup
- test files: discard returns from Close, WriteString, json.Encode in test handlers

golangci-lint: 0 issues. go test -race: all 6 packages pass.
- release-cli.yml: remove NPM_TOKEN echo interpolation; use setup-node
  registry-url so NODE_AUTH_TOKEN handles .npmrc automatically
- release-cli.yml/RELEASING.md: switch tags from cli-v to cli/v so the
  Go module proxy can resolve go install ...@vX.Y.Z for nested module
- release-cli.yml: add validation step that fails the job when the git
  tag version disagrees with cli/npm/package.json
- install.js: update releaseTag to cli/v prefix to match new tag scheme
- install.js: guard early-exit with X_OK access check so a non-executable
  binary triggers a fresh download instead of silently failing at runtime
- install.js: wrap unlinkSync in error handler with try/catch so ENOENT
  on a never-created file doesn't swallow the original network error
- call.go: pass cmd.Context() to GetTool/CallTool so Ctrl-C cancels
  in-flight MCP requests
- sightmap.go: same context fix for Upload
- mcpclient/client.go: drain response body before close on all error
  paths so connections return to the pool on auth failures
- tunnel_windows.go: register connect/disconnect/status subcommands on
  Windows so help and shell completion show the full command surface
- auth/resolve.go: implement SECRET_SUBTEXT_API_KEY and FULLSTORY_API_KEY
  lookups that were documented but never read; update tests
- auth.go: runWhoami already held cmd; switch tools/list call
- call.go: thread ctx through runCallHelp so --help on a tool name
  is also cancellable
- tunnel.go: runTunnelForeground now takes a parent context so the
  signal.NotifyContext chains off the cobra command context instead
  of context.Background()

The daemon re-exec path (runTunnelDaemon) has no cobra cmd and
correctly keeps context.Background() as the signal-context base.
@jurassix jurassix merged commit 496ab3e into main May 29, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant