@skippercorp/skipper 1.0.1 → 1.0.3

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/README.md CHANGED
@@ -1,27 +1,157 @@
1
- # @skippercorp/skipper
1
+ <p align="center">
2
+ <img src="docs/skipper-logo.png" alt="Skipper logo" width="220" />
3
+ </p>
2
4
 
3
- Published npm package: `@skippercorp/skipper`.
4
- CLI command name stays `skipper`.
5
+ <h1 align="center">@skippercorp/skipper</h1>
5
6
 
6
- Install dependencies:
7
+ <p align="center">
8
+ Fast local worktree flow + GitHub event automation in one CLI.
9
+ </p>
10
+
11
+ <p align="center">
12
+ <a href="#quick-ramp-up">Quick ramp-up</a>
13
+ <span> | </span>
14
+ <a href="#command-cheat-sheet">Command cheat sheet</a>
15
+ <span> | </span>
16
+ <a href="#aws-worker-flow">AWS worker flow</a>
17
+ <span> | </span>
18
+ <a href="#development">Development</a>
19
+ </p>
20
+
21
+ Published package: `@skippercorp/skipper`
22
+ CLI command: `skipper`
23
+
24
+ ## Why skipper
25
+
26
+ - Keep repo cloning, worktree setup, and tmux session switching fast.
27
+ - Run prompt-based repo automation locally or in AWS.
28
+ - Wire GitHub webhooks to worker definitions in `.skipper/worker/*.ts`.
29
+
30
+ ## Quick ramp-up
31
+
32
+ ### Prerequisites
33
+
34
+ - `bun`
35
+ - `git`
36
+ - `gh` (GitHub CLI)
37
+ - `fzf`
38
+ - `tmux`
39
+ - For AWS commands: valid AWS credentials/profile with CloudFormation, ECS, EventBridge, S3, and IAM access.
40
+
41
+ ### Install
42
+
43
+ Run without global install:
7
44
 
8
45
  ```bash
9
- bun install
46
+ bunx @skippercorp/skipper hello
10
47
  ```
11
48
 
12
- Run locally:
49
+ Or install globally:
13
50
 
14
51
  ```bash
15
- bun run cli
52
+ bun add -g @skippercorp/skipper
53
+ skipper hello
54
+ ```
55
+
56
+ ### Local workflow in 60 seconds
57
+
58
+ ```bash
59
+ # 1) Clone a repo into ~/.local/share/github/<repo>
60
+ skipper clone owner/repo
61
+
62
+ # 2) Create/attach a worktree and jump into tmux
63
+ skipper a
64
+
65
+ # 3) Pull latest + run prompt automation in selected repo
66
+ skipper run "fix flaky tests and update CI"
67
+
68
+ # 4) Remove a worktree and its tmux session
69
+ skipper rm
70
+ ```
71
+
72
+ Skipper manages:
73
+
74
+ - Repositories: `~/.local/share/github/<repo>`
75
+ - Worktrees: `~/.local/share/skipper/worktree/<repo>/<worktree>`
76
+ - Tmux sessions: `<repo>-<worktree>`
77
+
78
+ ## Command cheat sheet
79
+
80
+ | Command | What it does |
81
+ | --- | --- |
82
+ | `skipper clone <owner/repo-or-url>` | Clones repo using `gh` into `~/.local/share/github` |
83
+ | `skipper a` | Selects repo/worktree with `fzf`, creates if missing, then attaches tmux |
84
+ | `skipper rm [--force]` | Removes selected worktree and kills matching tmux session (`--force` discards local worktree changes) |
85
+ | `skipper run "<prompt>"` | Selects repo, pulls latest, runs `opencode run` |
86
+ | `skipper aws bootstrap ...` | Deploys shared AWS ingress stack + optional GitHub webhook |
87
+ | `skipper aws deploy ...` | Deploys repository-scoped subscription stack |
88
+ | `skipper aws run "<prompt>"` | Starts ECS task from bootstrap stack for prompt execution |
89
+
90
+ ## AWS worker flow
91
+
92
+ Use this when you want GitHub events to trigger remote agent runs.
93
+
94
+ ### 1) Add worker definitions
95
+
96
+ Create `.skipper/worker/review.ts`:
97
+
98
+ ```ts
99
+ export default {
100
+ metadata: {
101
+ id: "review",
102
+ type: "code-review",
103
+ description: "Review new pull requests",
104
+ enabled: true,
105
+ version: "1",
106
+ },
107
+ triggers: [
108
+ {
109
+ provider: "github",
110
+ event: "pull_request",
111
+ actions: ["opened", "reopened", "ready_for_review", "synchronize"],
112
+ },
113
+ ],
114
+ runtime: {
115
+ mode: "comment-only",
116
+ allowPush: false,
117
+ prompt: "Review this pull request. Focus on correctness and regression risk.",
118
+ },
119
+ };
120
+ ```
121
+
122
+ ### 2) Bootstrap shared infrastructure (once per service/env)
123
+
124
+ ```bash
125
+ skipper aws bootstrap myservice sandbox --github-repo owner/repo
126
+ ```
127
+
128
+ ### 3) Deploy repo-scoped subscription stack
129
+
130
+ ```bash
131
+ skipper aws deploy myservice sandbox --github-repo owner/repo
16
132
  ```
17
133
 
18
- Validate source standards:
134
+ ### 4) Trigger one-off remote run
19
135
 
20
136
  ```bash
137
+ skipper aws run --service myservice --env sandbox --wait "triage new issues"
138
+ ```
139
+
140
+ Useful safety flags:
141
+
142
+ - `--dry-run-template` for `aws bootstrap` and `aws deploy`
143
+ - `--dry-run` for `aws run`
144
+
145
+ ## Development
146
+
147
+ ```bash
148
+ bun install
149
+ bun run cli
21
150
  bun run lint
151
+ bun test
22
152
  ```
23
153
 
24
- Run e2e issue subscription verification (creates temp issue, waits for ECS task, verifies issue fetch logs, then closes issue + stops task):
154
+ Run e2e issue subscription verification:
25
155
 
26
156
  ```bash
27
157
  SKIPPER_E2E_GITHUB_REPO=blntrsz/skipper SKIPPER_E2E_REGION=eu-central-1 bun run test:e2e:issue-subscription
@@ -29,7 +159,7 @@ SKIPPER_E2E_GITHUB_REPO=blntrsz/skipper SKIPPER_E2E_REGION=eu-central-1 bun run
29
159
 
30
160
  ## Release flow (Changesets)
31
161
 
32
- 1. Add a release note: `bun run changeset`
162
+ 1. Add release note: `bun run changeset`
33
163
  2. Check pending releases: `bunx changeset status`
34
- 3. Push to `main`: `.github/workflows/release.yml` opens/updates a version PR
164
+ 3. Push to `main`: `.github/workflows/release.yml` opens/updates version PR
35
165
  4. Merge version PR: workflow publishes to npm with `NPM_TOKEN`
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "@skippercorp/skipper",
4
- "version": "1.0.1",
4
+ "version": "1.0.3",
5
5
  "type": "module",
6
6
  "private": false,
7
7
  "publishConfig": {
@@ -12,7 +12,7 @@
12
12
  "!src/**/*.test.ts"
13
13
  ],
14
14
  "bin": {
15
- "skipper": "./src/index.ts"
15
+ "sk": "./src/index.ts"
16
16
  },
17
17
  "scripts": {
18
18
  "cli": "bun run ./src/index.ts",
package/src/command/rm.ts CHANGED
@@ -11,6 +11,10 @@ type WorktreeRef = {
11
11
  path: string;
12
12
  };
13
13
 
14
+ type RemoveCommandOptions = {
15
+ force?: boolean;
16
+ };
17
+
14
18
  /**
15
19
  * Register remove worktree command.
16
20
  *
@@ -21,6 +25,7 @@ export function registerRemoveCommand(program: Command): void {
21
25
  program
22
26
  .command("rm")
23
27
  .description("Remove a worktree (repo+worktree)")
28
+ .option("-f, --force", "Force remove worktree with uncommitted changes")
24
29
  .action(runRemoveCommand);
25
30
  }
26
31
 
@@ -30,7 +35,8 @@ export function registerRemoveCommand(program: Command): void {
30
35
  * @since 1.0.0
31
36
  * @category Worktree
32
37
  */
33
- async function runRemoveCommand(): Promise<void> {
38
+ async function runRemoveCommand(options: RemoveCommandOptions = {}): Promise<void> {
39
+ const force = options.force === true;
34
40
  const worktreeBaseDir = `${process.env.HOME}/.local/share/skipper/worktree`;
35
41
  const allWorktrees = await collectWorktrees(worktreeBaseDir);
36
42
  assertNonEmpty(allWorktrees, "No worktrees found");
@@ -39,7 +45,7 @@ async function runRemoveCommand(): Promise<void> {
39
45
  console.log("No worktree selected");
40
46
  process.exit(0);
41
47
  }
42
- await removeWorktree(selected);
48
+ await removeWorktree(selected, force);
43
49
  }
44
50
 
45
51
  /**
@@ -85,13 +91,13 @@ async function selectWorktree(worktrees: WorktreeRef[]): Promise<WorktreeRef | u
85
91
  * @since 1.0.0
86
92
  * @category Worktree
87
93
  */
88
- async function removeWorktree(target: WorktreeRef): Promise<void> {
94
+ async function removeWorktree(target: WorktreeRef, force: boolean): Promise<void> {
89
95
  const githubDir = `${process.env.HOME}/.local/share/github`;
90
96
  const repoPath = `${githubDir}/${target.repo}`;
91
97
  const sessionName = `${target.repo}-${target.worktree}`;
92
98
  const name = `${target.repo}/${target.worktree}`;
93
99
  console.log(`Removing worktree: ${name}`);
94
- await removeGitWorktree(repoPath, target.path);
100
+ await removeGitWorktree(repoPath, target.path, force);
95
101
  await Bun.$`rm -rf ${target.path}`;
96
102
  if (await tmuxSessionExists(sessionName)) {
97
103
  await Bun.$`tmux kill-session -t ${sessionName}`;
@@ -106,11 +112,18 @@ async function removeWorktree(target: WorktreeRef): Promise<void> {
106
112
  * @since 1.0.0
107
113
  * @category Worktree
108
114
  */
109
- async function removeGitWorktree(repoPath: string, worktreePath: string): Promise<void> {
110
- const result = await Bun.$`git -C ${repoPath} worktree remove ${worktreePath}`.nothrow();
115
+ async function removeGitWorktree(
116
+ repoPath: string,
117
+ worktreePath: string,
118
+ force: boolean,
119
+ ): Promise<void> {
120
+ const result = force
121
+ ? await Bun.$`git -C ${repoPath} worktree remove --force ${worktreePath}`.nothrow()
122
+ : await Bun.$`git -C ${repoPath} worktree remove ${worktreePath}`.nothrow();
111
123
  if (result.exitCode === 0) return;
112
124
  const stderr = result.stderr.toString().trim();
113
- const fallback = `git worktree remove failed with code ${result.exitCode}`;
125
+ const command = force ? "git worktree remove --force" : "git worktree remove";
126
+ const fallback = `${command} failed with code ${result.exitCode}`;
114
127
  throw new Error(stderr || fallback);
115
128
  }
116
129