@valescoagency/runway 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Valesco Agency
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,199 @@
1
+ # runway
2
+
3
+ A small CLI for two jobs: **scaffold** a target repo for autonomous
4
+ coding-agent runs, then **drain** a Linear queue against it. Wraps
5
+ [Sandcastle](https://github.com/mattpocock/sandcastle) (Claude Code
6
+ inside Docker), [varlock](https://varlock.dev) + 1Password for
7
+ zero-secrets-at-rest, and the `gh` CLI for PR creation.
8
+
9
+ ## Five commands
10
+
11
+ | | |
12
+ |---|---|
13
+ | `runway doctor` | Read-only preflight diagnostic: host tooling, env vars, repo state, and the agent docker image. Use when something stopped working and you want a sanity report. `--json` for CI / scripted health checks. |
14
+ | `runway init` | Scaffold the cwd repo for runway: write `.sandcastle/Dockerfile` + (tier 2) `.env.schema` with op:// references. Run **once per target repo**. |
15
+ | `runway run` | Drain a Linear queue. For each `Todo` issue: branch, agent works, sub-agent reviews, PR opens (or `needs-human` label). Run **whenever you want a batch of work done**. |
16
+ | `runway upgrade` | Update the runway CLI itself: `git pull` the local clone, `pnpm install`, typecheck. `--check` for a dry-run, `--force` to override dirty/branch refusals. |
17
+ | `runway upgrade-repo` | Re-render the cwd repo's runway scaffold against the current vendored templates. Use after a runway version bump that changed the Dockerfile or template shape — `init` writes them, `upgrade-repo` keeps them current without re-prompting for op:// values. |
18
+
19
+ `runway --help` for the full usage; `runway <cmd> --help` per command.
20
+
21
+ `runway init` and `runway upgrade-repo` are siblings: `init` is the
22
+ first-time scaffold (you provide the op:// vault/items), `upgrade-repo`
23
+ is the re-render (extracts those values from the existing `.env.schema`
24
+ so you never re-type them). Override the extracted values via
25
+ `--op-vault=…`, `--anthropic-item=…`, `--gh-token-item=…` if needed,
26
+ or use `--check` for a CI dry-run that exits 1 on drift.
27
+
28
+ ## What runway is and isn't
29
+
30
+ | It is | It isn't |
31
+ |---|---|
32
+ | A small Node CLI that orchestrates Sandcastle runs from a Linear queue | A sandbox or agent runtime — Sandcastle does that |
33
+ | The replacement for the AFK label-handler / preflight / contracts pipeline | A governance layer; trust comes from your review of the PR, not gates |
34
+ | Repo-agnostic — runs in whatever cwd you launch it from | Multi-repo (yet) — one repo per `runway run` invocation |
35
+ | Sequential | Parallel (yet) |
36
+
37
+ ## Architecture
38
+
39
+ ```
40
+ Linear (Todo, team=VA)
41
+ ↓ poll
42
+ runway (this CLI, on your Mac, run from inside the target repo)
43
+ ↓ for each issue
44
+ │ sandcastle.run({ agent: claudeCode, sandbox: docker, cwd: process.cwd(), ... })
45
+ │ → branch agent/<issue-id>, commits, tests
46
+
47
+ │ sandcastle.run({ ..., prompt: review template })
48
+ │ → REVIEW: APPROVED | REVIEW: REJECTED — <reason>
49
+
50
+ ├── approved → git push → gh pr create → Linear "In Review"
51
+ └── rejected → Linear label "needs-human", comment with reason
52
+ ↓ next issue
53
+ ```
54
+
55
+ ## Prerequisites
56
+
57
+ - macOS or Linux
58
+ - Docker Desktop (or Podman)
59
+ - Node 22+
60
+ - `gh` CLI authenticated against the org that hosts your target repo
61
+ - Linear API key with read+write on the team you're targeting
62
+ - Anthropic API key (set in the **target repo's** `.sandcastle/.env`,
63
+ not in runway's env — Sandcastle reads it)
64
+
65
+ ## One-time setup per target repo
66
+
67
+ ```bash
68
+ cd /path/to/your/repo
69
+ runway init \
70
+ --op-vault=runway \
71
+ --anthropic-item=anthropic-api-key \
72
+ --gh-token-item=gh-token
73
+ ```
74
+
75
+ (No `--op-account` — runway uses 1Password service-account auth
76
+ (`OP_SERVICE_ACCOUNT_TOKEN`) exclusively, and the token already
77
+ encodes the tenant. `op://` URIs runway writes are
78
+ `op://<vault>/<item>`, not `op://<account>/<vault>/<item>`.)
79
+
80
+ This runs `npx sandcastle init`, patches the generated `.sandcastle/Dockerfile`
81
+ to bake in `varlock` + the 1Password CLI + a `claude` shim, scaffolds
82
+ `.env.schema` at the repo root with op:// references to your 1Password vault,
83
+ and deletes `.sandcastle/.env` so no secrets sit at rest. Review the diff,
84
+ commit on a feature branch, open a PR.
85
+
86
+ Pass `--tier=1` if you want the plain Sandcastle setup with `.sandcastle/.env`
87
+ and no varlock (faster but secrets land on disk).
88
+
89
+ Architecture walkthrough: [`docs/secrets-with-varlock.md`](docs/secrets-with-varlock.md).
90
+
91
+ ## Secrets — recommended: varlock + 1Password
92
+
93
+ If you don't want any secret sitting at rest in any `.env` file,
94
+ runway integrates [varlock](https://varlock.dev). Two layers (host +
95
+ in-container), both opt-in. Full walk-through:
96
+ [`docs/secrets-with-varlock.md`](docs/secrets-with-varlock.md).
97
+
98
+ TL;DR for the host layer:
99
+
100
+ ```bash
101
+ varlock run --schema /path/to/runway/.env.schema -- runway --max 3
102
+ ```
103
+
104
+ Without varlock, runway falls back to plain `process.env` and
105
+ sandcastle reads `.sandcastle/.env` per its docs.
106
+
107
+ ## Install
108
+
109
+ ```bash
110
+ pnpm install -g @valescoagency/runway # or npm i -g, yarn global add
111
+ ```
112
+
113
+ Export runway's own env (in your shell rc, or wherever you keep API keys):
114
+
115
+ ```bash
116
+ export LINEAR_API_KEY=lin_api_...
117
+ # Optional overrides:
118
+ # export RUNWAY_LINEAR_TEAM=VA
119
+ # export RUNWAY_READY_STATUS="Todo"
120
+ # export RUNWAY_IN_PROGRESS_STATUS="In Progress"
121
+ # export RUNWAY_IN_REVIEW_STATUS="In Review"
122
+ # export RUNWAY_HITL_LABEL="needs-human"
123
+ # export RUNWAY_MAX_ITERATIONS=5
124
+ ```
125
+
126
+ ### From source (development)
127
+
128
+ ```bash
129
+ git clone git@github.com:ValescoAgency/runway.git
130
+ cd runway
131
+ pnpm install
132
+ pnpm build # compiles src/ → dist/
133
+ pnpm link --global # so `runway` is on your $PATH
134
+ ```
135
+
136
+ `pnpm dev -- <args>` runs the TypeScript source via `tsx` without building, useful while iterating on runway itself.
137
+
138
+ ## Usage
139
+
140
+ ```bash
141
+ cd /path/to/the/repo/you/want/agents/working/on
142
+ runway run # drain the entire ready queue
143
+ runway run --max 3 # process at most 3 issues then exit
144
+ runway --help
145
+ ```
146
+
147
+ `runway` (no subcommand) is an alias for `runway run` for back-compat.
148
+
149
+ The CLI exits with 0 even if some issues hit HITL or errored — those
150
+ are normal outcomes. Check Linear for the `needs-human` label and the
151
+ per-issue comments for what happened.
152
+
153
+ ## Linear conventions
154
+
155
+ Runway picks up issues that are:
156
+
157
+ - in team `RUNWAY_LINEAR_TEAM` (default `VA`)
158
+ - in workflow state `RUNWAY_READY_STATUS` (default `Todo`)
159
+
160
+ It transitions them through:
161
+
162
+ - `In Progress` while the agent is running
163
+ - `In Review` when the PR opens
164
+ - (label `needs-human`) if the agent or reviewer can't finish
165
+
166
+ These names are configurable per env var; the queries match by name so
167
+ your Linear workspace's actual state names need to line up with what
168
+ you set.
169
+
170
+ ## Sub-agent review
171
+
172
+ Every implementation run is followed by a fresh Sandcastle run with
173
+ [`prompts/review.md`](prompts/review.md). The reviewer reads the diff
174
+ and the issue body and outputs one of:
175
+
176
+ ```
177
+ REVIEW: APPROVED
178
+ REVIEW: REJECTED — <one-line reason>
179
+ ```
180
+
181
+ If approved, runway pushes the branch and opens the PR. If rejected,
182
+ the issue gets the HITL label and a comment with the reviewer's reason.
183
+
184
+ The reviewer is intentionally adversarial — its job is to find reasons
185
+ NOT to ship, not to rubber-stamp.
186
+
187
+ ## What's deliberately missing in v1
188
+
189
+ - Parallel runs (one issue at a time)
190
+ - Multi-repo dispatch from a single command (cd into each repo)
191
+ - Vercel Sandbox provider (Docker only — swap to `vercel()` later)
192
+ - DAG / dependency awareness across issues
193
+ - Auto-rebase / merge of approved PRs (you do that)
194
+
195
+ These are tractable, just not v1.
196
+
197
+ ## Status
198
+
199
+ v0.1 — scaffold complete, untested against a live queue.
package/dist/cli.js ADDED
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env node
2
+ import { doctorCommand, printDoctorUsage } from "./commands/doctor.js";
3
+ import { initCommand, printInitUsage } from "./commands/init.js";
4
+ import { runCommand, printRunUsage } from "./commands/run.js";
5
+ import { upgradeCommand, printUpgradeUsage } from "./commands/upgrade.js";
6
+ import { upgradeRepoCommand, printUpgradeRepoUsage, } from "./commands/upgrade-repo.js";
7
+ const SUBCOMMANDS = [
8
+ {
9
+ name: "doctor",
10
+ summary: "Read-only preflight: tooling, env, repo state, agent image.",
11
+ run: doctorCommand,
12
+ help: printDoctorUsage,
13
+ },
14
+ {
15
+ name: "init",
16
+ summary: "Scaffold a target repo (sandcastle init + varlock layer).",
17
+ run: initCommand,
18
+ help: printInitUsage,
19
+ },
20
+ {
21
+ name: "run",
22
+ summary: "Drain a Linear queue against the cwd repo (default verb).",
23
+ run: runCommand,
24
+ help: printRunUsage,
25
+ },
26
+ {
27
+ name: "upgrade",
28
+ summary: "Update the runway CLI itself (git pull + pnpm install).",
29
+ run: upgradeCommand,
30
+ help: printUpgradeUsage,
31
+ },
32
+ {
33
+ name: "upgrade-repo",
34
+ summary: "Refresh a target repo's runway scaffold against current templates.",
35
+ run: upgradeRepoCommand,
36
+ help: printUpgradeRepoUsage,
37
+ },
38
+ ];
39
+ function printRootUsage() {
40
+ console.log(`runway — Linear-driven coding agent orchestrator
41
+
42
+ USAGE
43
+ runway <command> [options]
44
+
45
+ COMMANDS`);
46
+ for (const cmd of SUBCOMMANDS) {
47
+ console.log(` ${cmd.name.padEnd(13)} ${cmd.summary}`);
48
+ }
49
+ console.log(`
50
+ Run \`runway <command> --help\` for command-specific options.
51
+
52
+ GLOBAL
53
+ --help, -h Show this help.
54
+ --version, -V Show version.
55
+ `);
56
+ }
57
+ async function main() {
58
+ const argv = process.argv.slice(2);
59
+ if (argv.length === 0 || argv[0] === "--help" || argv[0] === "-h") {
60
+ printRootUsage();
61
+ process.exit(0);
62
+ }
63
+ if (argv[0] === "--version" || argv[0] === "-V") {
64
+ // Read package.json at runtime — single source of truth.
65
+ const { readFile } = await import("node:fs/promises");
66
+ const { fileURLToPath } = await import("node:url");
67
+ const { dirname, join } = await import("node:path");
68
+ const here = dirname(fileURLToPath(import.meta.url));
69
+ const pkg = JSON.parse(await readFile(join(here, "..", "package.json"), "utf8"));
70
+ console.log(pkg.version);
71
+ process.exit(0);
72
+ }
73
+ // First arg may be a subcommand name. If it isn't, treat the whole
74
+ // argv as `run <argv>` (back-compat: `runway --max 3` still works).
75
+ const first = argv[0];
76
+ const matched = SUBCOMMANDS.find((c) => c.name === first);
77
+ if (matched) {
78
+ await matched.run(argv.slice(1));
79
+ }
80
+ else {
81
+ // Implicit `run` — back-compat for old invocations.
82
+ await runCommand(argv);
83
+ }
84
+ }
85
+ main().catch((err) => {
86
+ console.error("[runway] fatal:", err instanceof Error ? err.message : err);
87
+ process.exit(1);
88
+ });