offwatch 0.5.12 → 0.5.13
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 +132 -178
- package/bin/offwatch.js +6 -7
- package/lib/downloader.js +112 -0
- package/package.json +18 -11
- package/postinstall.js +21 -0
- package/src/__tests__/agent-jwt-env.test.ts +0 -79
- package/src/__tests__/allowed-hostname.test.ts +0 -80
- package/src/__tests__/auth-command-registration.test.ts +0 -16
- package/src/__tests__/board-auth.test.ts +0 -53
- package/src/__tests__/common.test.ts +0 -98
- package/src/__tests__/company-delete.test.ts +0 -95
- package/src/__tests__/company-import-export-e2e.test.ts +0 -502
- package/src/__tests__/company-import-url.test.ts +0 -74
- package/src/__tests__/company-import-zip.test.ts +0 -44
- package/src/__tests__/company.test.ts +0 -599
- package/src/__tests__/context.test.ts +0 -70
- package/src/__tests__/data-dir.test.ts +0 -79
- package/src/__tests__/doctor.test.ts +0 -102
- package/src/__tests__/feedback.test.ts +0 -177
- package/src/__tests__/helpers/embedded-postgres.ts +0 -6
- package/src/__tests__/helpers/zip.ts +0 -87
- package/src/__tests__/home-paths.test.ts +0 -44
- package/src/__tests__/http.test.ts +0 -106
- package/src/__tests__/network-bind.test.ts +0 -62
- package/src/__tests__/onboard.test.ts +0 -166
- package/src/__tests__/routines.test.ts +0 -249
- package/src/__tests__/telemetry.test.ts +0 -117
- package/src/__tests__/worktree-merge-history.test.ts +0 -492
- package/src/__tests__/worktree.test.ts +0 -982
- package/src/adapters/http/format-event.ts +0 -4
- package/src/adapters/http/index.ts +0 -7
- package/src/adapters/index.ts +0 -2
- package/src/adapters/process/format-event.ts +0 -4
- package/src/adapters/process/index.ts +0 -7
- package/src/adapters/registry.ts +0 -63
- package/src/checks/agent-jwt-secret-check.ts +0 -40
- package/src/checks/config-check.ts +0 -33
- package/src/checks/database-check.ts +0 -59
- package/src/checks/deployment-auth-check.ts +0 -88
- package/src/checks/index.ts +0 -18
- package/src/checks/llm-check.ts +0 -82
- package/src/checks/log-check.ts +0 -30
- package/src/checks/path-resolver.ts +0 -1
- package/src/checks/port-check.ts +0 -24
- package/src/checks/secrets-check.ts +0 -146
- package/src/checks/storage-check.ts +0 -51
- package/src/client/board-auth.ts +0 -282
- package/src/client/command-label.ts +0 -4
- package/src/client/context.ts +0 -175
- package/src/client/http.ts +0 -255
- package/src/commands/allowed-hostname.ts +0 -40
- package/src/commands/auth-bootstrap-ceo.ts +0 -138
- package/src/commands/client/activity.ts +0 -71
- package/src/commands/client/agent.ts +0 -315
- package/src/commands/client/approval.ts +0 -259
- package/src/commands/client/auth.ts +0 -113
- package/src/commands/client/common.ts +0 -221
- package/src/commands/client/company.ts +0 -1578
- package/src/commands/client/context.ts +0 -125
- package/src/commands/client/dashboard.ts +0 -34
- package/src/commands/client/feedback.ts +0 -645
- package/src/commands/client/issue.ts +0 -411
- package/src/commands/client/plugin.ts +0 -374
- package/src/commands/client/zip.ts +0 -129
- package/src/commands/configure.ts +0 -201
- package/src/commands/db-backup.ts +0 -102
- package/src/commands/doctor.ts +0 -203
- package/src/commands/env.ts +0 -411
- package/src/commands/heartbeat-run.ts +0 -344
- package/src/commands/onboard.ts +0 -692
- package/src/commands/routines.ts +0 -352
- package/src/commands/run.ts +0 -216
- package/src/commands/worktree-lib.ts +0 -279
- package/src/commands/worktree-merge-history-lib.ts +0 -764
- package/src/commands/worktree.ts +0 -2876
- package/src/config/data-dir.ts +0 -48
- package/src/config/env.ts +0 -125
- package/src/config/home.ts +0 -80
- package/src/config/hostnames.ts +0 -26
- package/src/config/schema.ts +0 -30
- package/src/config/secrets-key.ts +0 -48
- package/src/config/server-bind.ts +0 -183
- package/src/config/store.ts +0 -120
- package/src/index.ts +0 -182
- package/src/prompts/database.ts +0 -157
- package/src/prompts/llm.ts +0 -43
- package/src/prompts/logging.ts +0 -37
- package/src/prompts/secrets.ts +0 -99
- package/src/prompts/server.ts +0 -221
- package/src/prompts/storage.ts +0 -146
- package/src/telemetry.ts +0 -49
- package/src/utils/banner.ts +0 -24
- package/src/utils/net.ts +0 -18
- package/src/utils/path-resolver.ts +0 -25
- package/src/version.ts +0 -10
package/README.md
CHANGED
|
@@ -1,178 +1,132 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
## Quickstart
|
|
134
|
-
|
|
135
|
-
```bash
|
|
136
|
-
npm install -g offwatch
|
|
137
|
-
offwatch --help
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
Or run directly:
|
|
141
|
-
|
|
142
|
-
```bash
|
|
143
|
-
npx offwatch --help
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
<br/>
|
|
147
|
-
|
|
148
|
-
## CLI Commands
|
|
149
|
-
|
|
150
|
-
```bash
|
|
151
|
-
offwatch worktree create # Create a new isolated worktree for agent tasks
|
|
152
|
-
offwatch worktree list # List all worktrees
|
|
153
|
-
offwatch worktree sync # Sync issues to worktree
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
<br/>
|
|
157
|
-
|
|
158
|
-
## Development
|
|
159
|
-
|
|
160
|
-
```bash
|
|
161
|
-
pnpm install
|
|
162
|
-
pnpm build
|
|
163
|
-
pnpm dev
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
<br/>
|
|
167
|
-
|
|
168
|
-
## License
|
|
169
|
-
|
|
170
|
-
MIT © 2026 Offwatch
|
|
171
|
-
|
|
172
|
-
<br/>
|
|
173
|
-
|
|
174
|
-
---
|
|
175
|
-
|
|
176
|
-
<p align="center">
|
|
177
|
-
<sub>Open source under MIT. Built for developers who want to automate work, not babysit agents.</sub>
|
|
178
|
-
</p>
|
|
1
|
+
# OffWatch
|
|
2
|
+
|
|
3
|
+
**Your issues. Your agents. They ship while you're off-watch.**
|
|
4
|
+
|
|
5
|
+
OffWatch is a self-hosted server that connects your issue tracker to a fleet of AI coding agents. Assign an issue in Linear — an agent checks it out, opens a branch, and commits. You review and merge.
|
|
6
|
+
|
|
7
|
+
No babysitting. No tab management. Work lands in your repo.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## How it works
|
|
12
|
+
|
|
13
|
+
| Step | What happens |
|
|
14
|
+
| ---- | ------------ |
|
|
15
|
+
| **01** | An issue is assigned to you in Linear |
|
|
16
|
+
| **02** | OffWatch syncs it and makes it available to your agents |
|
|
17
|
+
| **03** | An agent checks it out atomically, works the issue, and commits |
|
|
18
|
+
| **04** | You get a summary in the issue thread and review the PR |
|
|
19
|
+
|
|
20
|
+
Agents wake on a schedule and on events — new assignments, @-mentions, approval resolutions. When there's nothing to do, they sleep.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Features
|
|
25
|
+
|
|
26
|
+
**Issue tracker sync** — Linear adapter included. Issues sync in, completions sync back. Tracker-agnostic by design; additional adapters can be added.
|
|
27
|
+
|
|
28
|
+
**Atomic checkout** — one agent per issue, enforced. No duplicate work, no conflicts.
|
|
29
|
+
|
|
30
|
+
**Bring your own agent** — Claude Code, Codex, Cursor, OpenClaw, any HTTP endpoint, any local process. If it can receive a heartbeat, it works.
|
|
31
|
+
|
|
32
|
+
**Heartbeat protocol** — agents wake on schedule or on trigger, do their work, and sleep. Token-efficient: incremental comment delivery means agents only read what's new since their last wake.
|
|
33
|
+
|
|
34
|
+
**Approval queue** — agents can propose issues to your tracker before creating them. You approve or reject from the OffWatch UI.
|
|
35
|
+
|
|
36
|
+
**Multiple workspaces** — one deployment, multiple codebases. Each workspace has its own agents, projects, and tracker config.
|
|
37
|
+
|
|
38
|
+
**Skills** — inject context and workflow instructions into agents at runtime. Share skills across agents.
|
|
39
|
+
|
|
40
|
+
**Plugins** — extend OffWatch with custom tracing, knowledge bases, queues, and more via the plugin SDK.
|
|
41
|
+
|
|
42
|
+
**Activity log** — every agent action is recorded. Full audit trail, always.
|
|
43
|
+
|
|
44
|
+
**Mobile-ready UI** — manage your agents from anywhere.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## OffWatch is right for you if
|
|
49
|
+
|
|
50
|
+
- You want issues from your tracker to become commits without manual intervention
|
|
51
|
+
- You run multiple AI coding agents and lose track of who is doing what
|
|
52
|
+
- You want agents running 24/7 but still want to review their work before it merges
|
|
53
|
+
- You want a persistent session layer so agents don't restart from scratch every time
|
|
54
|
+
- You're hitting large token bills and want a leaner heartbeat layer
|
|
55
|
+
|
|
56
|
+
## OffWatch is not right for you if
|
|
57
|
+
|
|
58
|
+
- You want a chatbot or prompt manager — agents have jobs, not chat windows
|
|
59
|
+
- You want a drag-and-drop workflow builder — OffWatch models agents working issues, not pipelines
|
|
60
|
+
- You have one agent — this is for fleets
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Quickstart
|
|
65
|
+
|
|
66
|
+
Open source. Self-hosted. No account required.
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
git clone https://github.com/triss-smith/offwatch.git
|
|
70
|
+
cd offwatch
|
|
71
|
+
pnpm install
|
|
72
|
+
pnpm dev
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
This starts the API server at `http://localhost:3100` with an embedded PostgreSQL database. No setup required.
|
|
76
|
+
|
|
77
|
+
> **Requirements:** Node.js 20+, pnpm 9.15+
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Configuration
|
|
82
|
+
|
|
83
|
+
On first run, create a workspace and configure your issue tracker:
|
|
84
|
+
|
|
85
|
+
1. Open `http://localhost:3100` and create a workspace
|
|
86
|
+
2. Go to **Settings → Issue Tracker** and connect Linear (API key + team)
|
|
87
|
+
3. Add an agent and point it at your adapter of choice
|
|
88
|
+
4. Assign an issue to yourself in Linear and watch it appear
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Development
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
pnpm dev # Full dev (API + UI, watch mode)
|
|
96
|
+
pnpm dev:server # Server only
|
|
97
|
+
pnpm build # Build all
|
|
98
|
+
pnpm typecheck # Type checking
|
|
99
|
+
pnpm test:run # Run tests
|
|
100
|
+
pnpm db:generate # Generate DB migration
|
|
101
|
+
pnpm db:migrate # Apply migrations
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Roadmap
|
|
107
|
+
|
|
108
|
+
- ✅ Issue tracker adapter system (Linear)
|
|
109
|
+
- ✅ Atomic issue checkout
|
|
110
|
+
- ✅ Heartbeat context compaction
|
|
111
|
+
- ✅ Skills manager
|
|
112
|
+
- ✅ Scheduled routines
|
|
113
|
+
- ✅ Approval queue
|
|
114
|
+
- ✅ Plugin system
|
|
115
|
+
- ✅ Multiple workspaces
|
|
116
|
+
- ⚪ GitHub Issues adapter
|
|
117
|
+
- ⚪ Git worktree isolation per checkout
|
|
118
|
+
- ⚪ Security audit routine
|
|
119
|
+
- ⚪ Desktop app
|
|
120
|
+
- ⚪ Additional tracker adapters
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## Contributing
|
|
125
|
+
|
|
126
|
+
Contributions welcome. Open an issue to discuss before sending large PRs.
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## License
|
|
131
|
+
|
|
132
|
+
MIT © 2026 OffWatch
|
package/bin/offwatch.js
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { loadCLIBinPath } from "../lib/downloader.js";
|
|
2
3
|
import { spawn } from "child_process";
|
|
3
|
-
import { dirname
|
|
4
|
+
import { dirname } from "path";
|
|
4
5
|
import { fileURLToPath } from "url";
|
|
5
6
|
|
|
6
7
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
|
-
const srcDir = resolve(__dirname, "..", "src");
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
});
|
|
9
|
+
const binPath = await loadCLIBinPath(__dirname);
|
|
10
|
+
|
|
11
|
+
// Run the downloaded CLI binary
|
|
12
|
+
spawn("node", [binPath], { stdio: "inherit" });
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import got from "got";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import fsa from "fs-extra";
|
|
4
|
+
import * as os from "node:os";
|
|
5
|
+
import tar from "tar";
|
|
6
|
+
import stream from "node:stream";
|
|
7
|
+
import { promisify } from "node:util";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import { dirname } from "path";
|
|
10
|
+
import { fileURLToPath } from "url";
|
|
11
|
+
|
|
12
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const debugMode = process.env.DEBUG != null;
|
|
14
|
+
const pipeline = promisify(stream.pipeline);
|
|
15
|
+
|
|
16
|
+
const pkg = JSON.parse(fs.readFileSync(__dirname + "/../package.json", "utf8"));
|
|
17
|
+
const CLI_FILENAME = "offwatch";
|
|
18
|
+
|
|
19
|
+
const logDebug = (message) => {
|
|
20
|
+
if (debugMode) {
|
|
21
|
+
console.debug(`[DEBUG] ${message}`);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const getPlatform = () => {
|
|
26
|
+
const platform = os.platform();
|
|
27
|
+
if (platform === "win32") return "win32-x64";
|
|
28
|
+
if (platform === "darwin") return "darwin-x64";
|
|
29
|
+
return "linux-x64";
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const downloadRelease = async (versionDir) => {
|
|
33
|
+
logDebug(`downloadRelease(${versionDir})`);
|
|
34
|
+
const version = pkg.version;
|
|
35
|
+
|
|
36
|
+
const releases = (await got
|
|
37
|
+
.get(`https://api.github.com/repos/triss-smith/offwatch/releases`)
|
|
38
|
+
.json());
|
|
39
|
+
|
|
40
|
+
// Find the latest release that matches our major.minor
|
|
41
|
+
const currentParts = extractVersionParts(version);
|
|
42
|
+
const matchingRelease = releases
|
|
43
|
+
.filter((release) => {
|
|
44
|
+
const releaseVersion = extractVersionParts(release.tag_name);
|
|
45
|
+
return (releaseVersion.major === currentParts.major &&
|
|
46
|
+
releaseVersion.minor === currentParts.minor);
|
|
47
|
+
})
|
|
48
|
+
.sort((a, b) => {
|
|
49
|
+
return Number(extractVersionParts(b.tag_name).patch) >
|
|
50
|
+
Number(extractVersionParts(a.tag_name).patch)
|
|
51
|
+
? 1
|
|
52
|
+
: -1;
|
|
53
|
+
})[0] || releases[0];
|
|
54
|
+
|
|
55
|
+
if (!matchingRelease) {
|
|
56
|
+
throw new Error(`No releases found for ${currentParts.major}.${currentParts.minor}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const platform = getPlatform();
|
|
60
|
+
logDebug(`Looking for platform: ${platform} in release ${matchingRelease.tag_name}`);
|
|
61
|
+
|
|
62
|
+
const asset = matchingRelease.assets.find((asset) => {
|
|
63
|
+
return asset.name.includes(platform);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
if (!asset) {
|
|
67
|
+
throw new Error(`${platform} is not currently supported. Check GitHub releases for available platforms.`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
fsa.mkdirpSync(versionDir);
|
|
72
|
+
} catch (e) { }
|
|
73
|
+
|
|
74
|
+
const ext = asset.name.endsWith(".js") ? ".js" : "";
|
|
75
|
+
const filePath = path.join(versionDir, CLI_FILENAME + ext);
|
|
76
|
+
const tempPath = path.join(versionDir, asset.name);
|
|
77
|
+
|
|
78
|
+
logDebug(`Downloading ${asset.browser_download_url}`);
|
|
79
|
+
|
|
80
|
+
await pipeline(got.stream(asset.browser_download_url), fs.createWriteStream(tempPath));
|
|
81
|
+
|
|
82
|
+
if (ext) {
|
|
83
|
+
// For .js files, rename to expected name
|
|
84
|
+
fs.renameSync(tempPath, filePath);
|
|
85
|
+
} else {
|
|
86
|
+
// For compressed files, decompress
|
|
87
|
+
await pipeline(fs.createReadStream(tempPath), tar.x({
|
|
88
|
+
C: versionDir,
|
|
89
|
+
strip: 1,
|
|
90
|
+
}));
|
|
91
|
+
fs.unlinkSync(tempPath);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export const loadCLIBinPath = async (cwd) => {
|
|
96
|
+
const versionDir = path.join(cwd, pkg.version);
|
|
97
|
+
const ext = process.platform === "win32" ? ".js" : "";
|
|
98
|
+
const binPath = path.join(versionDir, CLI_FILENAME + ext);
|
|
99
|
+
logDebug(`loadCLIBinPath: ${binPath}`);
|
|
100
|
+
|
|
101
|
+
if (!fs.existsSync(binPath)) {
|
|
102
|
+
await downloadRelease(versionDir);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
logDebug(`returning ${binPath}`);
|
|
106
|
+
return binPath;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const extractVersionParts = (version) => {
|
|
110
|
+
const [major, minor, patch] = version.replace(/^v/, "").split(".");
|
|
111
|
+
return { major, minor, patch };
|
|
112
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "offwatch",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.13",
|
|
4
4
|
"description": "Offwatch — orchestrate AI agent teams to automate dev work",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -24,20 +24,27 @@
|
|
|
24
24
|
"url": "https://github.com/triss-smith/offwatch/issues"
|
|
25
25
|
},
|
|
26
26
|
"files": [
|
|
27
|
-
"src",
|
|
28
27
|
"bin",
|
|
29
|
-
"lib"
|
|
28
|
+
"lib",
|
|
29
|
+
"postinstall.js"
|
|
30
30
|
],
|
|
31
|
-
"
|
|
32
|
-
"
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=20"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"
|
|
36
|
-
"commander": "^13.
|
|
37
|
-
"
|
|
35
|
+
"@clack/prompts": "^0.7.0",
|
|
36
|
+
"commander": "^13.0.0",
|
|
37
|
+
"dotenv": "^16.4.5",
|
|
38
|
+
"drizzle-orm": "^0.38.0",
|
|
39
|
+
"embedded-postgres": "^18.1.0-beta.16",
|
|
40
|
+
"fs-extra": "^10.1.0",
|
|
41
|
+
"got": "^14.0.0",
|
|
38
42
|
"picocolors": "^1.1.1",
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
43
|
+
"postgres": "^3.4.5",
|
|
44
|
+
"superjson": "^2.2.1",
|
|
45
|
+
"tar": "^6.2.0",
|
|
46
|
+
"undici": "^6.21.0",
|
|
47
|
+
"ws": "^8.19.0",
|
|
48
|
+
"zod": "^3.24.0"
|
|
42
49
|
}
|
|
43
50
|
}
|
package/postinstall.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { loadCLIBinPath } from "./lib/downloader.js";
|
|
2
|
+
import { dirname } from "path";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
|
+
|
|
5
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
|
|
7
|
+
// Skip download in monorepo development
|
|
8
|
+
if (process.cwd().includes("node_modules")) {
|
|
9
|
+
if (process.env.DEBUG) {
|
|
10
|
+
console.debug(`[DEBUG] downloading CLI binary`);
|
|
11
|
+
}
|
|
12
|
+
loadCLIBinPath(__dirname).then(
|
|
13
|
+
() => {
|
|
14
|
+
process.exit(0);
|
|
15
|
+
},
|
|
16
|
+
(err) => {
|
|
17
|
+
console.error(err);
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import os from "node:os";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
5
|
-
import {
|
|
6
|
-
ensureAgentJwtSecret,
|
|
7
|
-
mergePaperclipEnvEntries,
|
|
8
|
-
readAgentJwtSecretFromEnv,
|
|
9
|
-
readPaperclipEnvEntries,
|
|
10
|
-
resolveAgentJwtEnvFile,
|
|
11
|
-
} from "../config/env.js";
|
|
12
|
-
import { agentJwtSecretCheck } from "../checks/agent-jwt-secret-check.js";
|
|
13
|
-
|
|
14
|
-
const ORIGINAL_ENV = { ...process.env };
|
|
15
|
-
|
|
16
|
-
function tempConfigPath(): string {
|
|
17
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-jwt-env-"));
|
|
18
|
-
const configDir = path.join(dir, "custom");
|
|
19
|
-
fs.mkdirSync(configDir, { recursive: true });
|
|
20
|
-
return path.join(configDir, "config.json");
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
describe("agent jwt env helpers", () => {
|
|
24
|
-
beforeEach(() => {
|
|
25
|
-
process.env = { ...ORIGINAL_ENV };
|
|
26
|
-
delete process.env.PAPERCLIP_AGENT_JWT_SECRET;
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
afterEach(() => {
|
|
30
|
-
process.env = { ...ORIGINAL_ENV };
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it("writes .env next to explicit config path", () => {
|
|
34
|
-
const configPath = tempConfigPath();
|
|
35
|
-
const result = ensureAgentJwtSecret(configPath);
|
|
36
|
-
|
|
37
|
-
expect(result.created).toBe(true);
|
|
38
|
-
|
|
39
|
-
const envPath = resolveAgentJwtEnvFile(configPath);
|
|
40
|
-
expect(fs.existsSync(envPath)).toBe(true);
|
|
41
|
-
const contents = fs.readFileSync(envPath, "utf-8");
|
|
42
|
-
expect(contents).toContain("PAPERCLIP_AGENT_JWT_SECRET=");
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it("loads secret from .env next to explicit config path", () => {
|
|
46
|
-
const configPath = tempConfigPath();
|
|
47
|
-
const envPath = resolveAgentJwtEnvFile(configPath);
|
|
48
|
-
fs.writeFileSync(envPath, "PAPERCLIP_AGENT_JWT_SECRET=test-secret\n", { mode: 0o600 });
|
|
49
|
-
|
|
50
|
-
const loaded = readAgentJwtSecretFromEnv(configPath);
|
|
51
|
-
expect(loaded).toBe("test-secret");
|
|
52
|
-
expect(process.env.PAPERCLIP_AGENT_JWT_SECRET).toBe("test-secret");
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it("doctor check passes when secret exists in adjacent .env", () => {
|
|
56
|
-
const configPath = tempConfigPath();
|
|
57
|
-
const envPath = resolveAgentJwtEnvFile(configPath);
|
|
58
|
-
fs.writeFileSync(envPath, "PAPERCLIP_AGENT_JWT_SECRET=check-secret\n", { mode: 0o600 });
|
|
59
|
-
|
|
60
|
-
const result = agentJwtSecretCheck(configPath);
|
|
61
|
-
expect(result.status).toBe("pass");
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it("quotes hash-prefixed env values so dotenv round-trips them", () => {
|
|
65
|
-
const configPath = tempConfigPath();
|
|
66
|
-
const envPath = resolveAgentJwtEnvFile(configPath);
|
|
67
|
-
|
|
68
|
-
mergePaperclipEnvEntries(
|
|
69
|
-
{
|
|
70
|
-
PAPERCLIP_WORKTREE_COLOR: "#439edb",
|
|
71
|
-
},
|
|
72
|
-
envPath,
|
|
73
|
-
);
|
|
74
|
-
|
|
75
|
-
const contents = fs.readFileSync(envPath, "utf-8");
|
|
76
|
-
expect(contents).toContain('PAPERCLIP_WORKTREE_COLOR="#439edb"');
|
|
77
|
-
expect(readPaperclipEnvEntries(envPath).PAPERCLIP_WORKTREE_COLOR).toBe("#439edb");
|
|
78
|
-
});
|
|
79
|
-
});
|