@bulletproof-sh/ctrl-daemon 0.0.5 → 0.0.7
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 +18 -2
- package/dist/index.js +76 -10
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @bulletproof-sh/ctrl-daemon
|
|
2
2
|
|
|
3
|
-
WebSocket daemon for [Ctrl / Cubicles](https://bulletproof.sh) — watches Claude Code sessions on disk and broadcasts live agent state to connected web clients.
|
|
3
|
+
WebSocket daemon for [Ctrl / Cubicles](https://ctrl.bulletproof.sh) — watches Claude Code sessions on disk and broadcasts live agent state to connected web clients.
|
|
4
4
|
|
|
5
5
|
## Requirements
|
|
6
6
|
|
|
@@ -30,6 +30,22 @@ npx @bulletproof-sh/ctrl-daemon --port 3001 --host 127.0.0.1
|
|
|
30
30
|
|
|
31
31
|
Without `--project-dir`, the daemon scans `~/.claude/projects/` and watches every session it finds there.
|
|
32
32
|
|
|
33
|
+
## Sharing your office
|
|
34
|
+
|
|
35
|
+
Run the daemon and an [ngrok](https://ngrok.com) tunnel in parallel to share a live view with anyone:
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
# Terminal 1 — start the daemon
|
|
39
|
+
npx @bulletproof-sh/ctrl-daemon
|
|
40
|
+
|
|
41
|
+
# Terminal 2 — expose it publicly
|
|
42
|
+
ngrok http 3001
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Then open [bulletproof.sh](https://bulletproof.sh), go to **Settings → Share Office** and copy the generated link. Anyone who opens it will see your agents in real time — no auth or setup required on their end.
|
|
46
|
+
|
|
47
|
+
> **Note:** Free ngrok URLs rotate each time you restart the tunnel. A paid ngrok plan gives you a stable domain.
|
|
48
|
+
|
|
33
49
|
## WebSocket API
|
|
34
50
|
|
|
35
51
|
Connect to `ws://localhost:3001/ws`. The daemon broadcasts JSON messages whenever agent state changes — the same message format used by the VS Code extension's internal webview protocol.
|
|
@@ -42,7 +58,7 @@ The [Ctrl web app](https://ctrl.bulletproof.sh) connects here by default. You ca
|
|
|
42
58
|
|---|---|
|
|
43
59
|
| `CLAUDE_HOME` | Override the Claude home directory (default: `~/.claude`) |
|
|
44
60
|
|
|
45
|
-
|
|
61
|
+
Crash reports use PostHog analytics.
|
|
46
62
|
|
|
47
63
|
## Auto-update
|
|
48
64
|
|
package/dist/index.js
CHANGED
|
@@ -4245,6 +4245,70 @@ async function shutdownAnalytics() {
|
|
|
4245
4245
|
await client.shutdown();
|
|
4246
4246
|
}
|
|
4247
4247
|
|
|
4248
|
+
// src/banner.ts
|
|
4249
|
+
var BG = "\x1B[92m";
|
|
4250
|
+
var CY = "\x1B[36m";
|
|
4251
|
+
var YL = "\x1B[33m";
|
|
4252
|
+
var DM = "\x1B[2m";
|
|
4253
|
+
var BD = "\x1B[1m";
|
|
4254
|
+
var RS = "\x1B[0m";
|
|
4255
|
+
var LOGO_LINES = [
|
|
4256
|
+
" \u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 ",
|
|
4257
|
+
" \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 ",
|
|
4258
|
+
" \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 ",
|
|
4259
|
+
" \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 ",
|
|
4260
|
+
" \u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557",
|
|
4261
|
+
" \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D"
|
|
4262
|
+
];
|
|
4263
|
+
function printBanner() {
|
|
4264
|
+
process.stdout.write(`
|
|
4265
|
+
`);
|
|
4266
|
+
for (const line of LOGO_LINES) {
|
|
4267
|
+
process.stdout.write(`${BD}${BG}${line}${RS}
|
|
4268
|
+
`);
|
|
4269
|
+
}
|
|
4270
|
+
process.stdout.write(`
|
|
4271
|
+
`);
|
|
4272
|
+
}
|
|
4273
|
+
var SPINNER_FRAMES = [
|
|
4274
|
+
"\u280B",
|
|
4275
|
+
"\u2819",
|
|
4276
|
+
"\u2839",
|
|
4277
|
+
"\u2838",
|
|
4278
|
+
"\u283C",
|
|
4279
|
+
"\u2834",
|
|
4280
|
+
"\u2826",
|
|
4281
|
+
"\u2827",
|
|
4282
|
+
"\u2807",
|
|
4283
|
+
"\u280F"
|
|
4284
|
+
];
|
|
4285
|
+
function startSpinner(text) {
|
|
4286
|
+
let i = 0;
|
|
4287
|
+
const clearWidth = text.length + 4;
|
|
4288
|
+
const timer = setInterval(() => {
|
|
4289
|
+
process.stdout.write(`\r${CY}${SPINNER_FRAMES[i++ % SPINNER_FRAMES.length]}${RS} ${text}`);
|
|
4290
|
+
}, 80);
|
|
4291
|
+
return () => {
|
|
4292
|
+
clearInterval(timer);
|
|
4293
|
+
process.stdout.write(`\r${" ".repeat(clearWidth)}\r`);
|
|
4294
|
+
};
|
|
4295
|
+
}
|
|
4296
|
+
function printReady(port, version2, updateMsg) {
|
|
4297
|
+
const versionStr = version2 ? ` ${DM}v${version2}${RS}` : "";
|
|
4298
|
+
if (updateMsg) {
|
|
4299
|
+
process.stdout.write(`
|
|
4300
|
+
${YL}${updateMsg}${RS}
|
|
4301
|
+
|
|
4302
|
+
`);
|
|
4303
|
+
}
|
|
4304
|
+
console.log(` ${BG}\u2713${RS} WebSocket server on :${port}${versionStr}`);
|
|
4305
|
+
console.log(` ${BG}\u2713${RS} Watching Claude sessions`);
|
|
4306
|
+
console.log("");
|
|
4307
|
+
console.log(` ${DM}Share: run \`ngrok http ${port}\`, then open Settings \u2192 Share${RS}`);
|
|
4308
|
+
console.log(` ${DM}Issues: https://github.com/bulletproof-sh/ctrl${RS}`);
|
|
4309
|
+
console.log("");
|
|
4310
|
+
}
|
|
4311
|
+
|
|
4248
4312
|
// src/projectScanner.ts
|
|
4249
4313
|
import * as fs2 from "fs";
|
|
4250
4314
|
import * as path2 from "path";
|
|
@@ -4889,13 +4953,11 @@ async function checkForUpdate() {
|
|
|
4889
4953
|
getLatestVersion()
|
|
4890
4954
|
]);
|
|
4891
4955
|
if (!current || !latest)
|
|
4892
|
-
return;
|
|
4956
|
+
return null;
|
|
4893
4957
|
if (!isNewer(current, latest))
|
|
4894
|
-
return;
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
` + ` Run: npx ${PACKAGE_NAME}@latest
|
|
4898
|
-
`);
|
|
4958
|
+
return null;
|
|
4959
|
+
return ` \u2B06 Update available: v${current} \u2192 v${latest}
|
|
4960
|
+
` + ` Run: npx ${PACKAGE_NAME}@latest`;
|
|
4899
4961
|
}
|
|
4900
4962
|
|
|
4901
4963
|
// src/index.ts
|
|
@@ -4935,8 +4997,14 @@ function resolveProjectsRoot(claudeHome) {
|
|
|
4935
4997
|
return path3.join(home, "projects");
|
|
4936
4998
|
}
|
|
4937
4999
|
async function main() {
|
|
5000
|
+
printBanner();
|
|
5001
|
+
const stopSpinner = startSpinner("Starting\u2026");
|
|
4938
5002
|
const analyticsConfig = initAnalytics();
|
|
4939
|
-
const [version2] = await Promise.all([
|
|
5003
|
+
const [version2, updateMsg] = await Promise.all([
|
|
5004
|
+
getCurrentVersion(),
|
|
5005
|
+
checkForUpdate()
|
|
5006
|
+
]);
|
|
5007
|
+
stopSpinner();
|
|
4940
5008
|
process.on("uncaughtException", async (err) => {
|
|
4941
5009
|
console.error("[ctrl-daemon] Uncaught exception:", err);
|
|
4942
5010
|
trackException(err, { ...systemInfo, crash_type: "uncaughtException" });
|
|
@@ -4956,11 +5024,8 @@ async function main() {
|
|
|
4956
5024
|
if (projectDir) {
|
|
4957
5025
|
const dir = resolveProjectDir(projectDir, claudeHome);
|
|
4958
5026
|
scanDirs = [dir];
|
|
4959
|
-
console.log(`[ctrl-daemon] Project dir: ${projectDir}`);
|
|
4960
|
-
console.log(`[ctrl-daemon] Session dir: ${dir}`);
|
|
4961
5027
|
} else {
|
|
4962
5028
|
const projectsRoot = resolveProjectsRoot(claudeHome);
|
|
4963
|
-
console.log(`[ctrl-daemon] Watching all projects in: ${projectsRoot}`);
|
|
4964
5029
|
scanDirs = [projectsRoot];
|
|
4965
5030
|
}
|
|
4966
5031
|
const agents = new Map;
|
|
@@ -4969,6 +5034,7 @@ async function main() {
|
|
|
4969
5034
|
const waitingTimers = new Map;
|
|
4970
5035
|
const permissionTimers = new Map;
|
|
4971
5036
|
const server = createServer({ port, host, agents });
|
|
5037
|
+
printReady(port, version2, updateMsg);
|
|
4972
5038
|
trackEvent("daemon_started", {
|
|
4973
5039
|
version: version2 ?? "unknown",
|
|
4974
5040
|
port,
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bulletproof-sh/ctrl-daemon",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"description": "WebSocket daemon for ctrl — watches Claude Code sessions and broadcasts agent state",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"license": "
|
|
6
|
+
"license": "BUSL-1.1",
|
|
7
7
|
"bin": {
|
|
8
8
|
"ctrl-daemon": "bin/ctrl-daemon.js"
|
|
9
9
|
},
|