@seanpropapp/cli 0.1.0-beta.1
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 +21 -0
- package/README.md +64 -0
- package/dist/commands/autostart.d.ts +42 -0
- package/dist/commands/autostart.js +195 -0
- package/dist/commands/autostart.js.map +1 -0
- package/dist/commands/bridge.d.ts +54 -0
- package/dist/commands/bridge.js +145 -0
- package/dist/commands/bridge.js.map +1 -0
- package/dist/commands/connect.d.ts +56 -0
- package/dist/commands/connect.js +213 -0
- package/dist/commands/connect.js.map +1 -0
- package/dist/commands/doctor.d.ts +24 -0
- package/dist/commands/doctor.js +200 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/install-claude.d.ts +22 -0
- package/dist/commands/install-claude.js +56 -0
- package/dist/commands/install-claude.js.map +1 -0
- package/dist/commands/mcp.d.ts +5 -0
- package/dist/commands/mcp.js +23 -0
- package/dist/commands/mcp.js.map +1 -0
- package/dist/commands/pair-url.d.ts +12 -0
- package/dist/commands/pair-url.js +23 -0
- package/dist/commands/pair-url.js.map +1 -0
- package/dist/commands/pair.d.ts +12 -0
- package/dist/commands/pair.js +24 -0
- package/dist/commands/pair.js.map +1 -0
- package/dist/commands/prompt.d.ts +5 -0
- package/dist/commands/prompt.js +24 -0
- package/dist/commands/prompt.js.map +1 -0
- package/dist/commands/telemetry-cmd.d.ts +8 -0
- package/dist/commands/telemetry-cmd.js +55 -0
- package/dist/commands/telemetry-cmd.js.map +1 -0
- package/dist/config.d.ts +63 -0
- package/dist/config.js +77 -0
- package/dist/config.js.map +1 -0
- package/dist/http/auth-middleware.d.ts +9 -0
- package/dist/http/auth-middleware.js +29 -0
- package/dist/http/auth-middleware.js.map +1 -0
- package/dist/http/chat-completions.d.ts +48 -0
- package/dist/http/chat-completions.js +117 -0
- package/dist/http/chat-completions.js.map +1 -0
- package/dist/http/cors.d.ts +9 -0
- package/dist/http/cors.js +35 -0
- package/dist/http/cors.js.map +1 -0
- package/dist/http/handshake.d.ts +43 -0
- package/dist/http/handshake.js +28 -0
- package/dist/http/handshake.js.map +1 -0
- package/dist/http/messages-endpoint.d.ts +67 -0
- package/dist/http/messages-endpoint.js +95 -0
- package/dist/http/messages-endpoint.js.map +1 -0
- package/dist/http/server.d.ts +37 -0
- package/dist/http/server.js +83 -0
- package/dist/http/server.js.map +1 -0
- package/dist/http/sse.d.ts +35 -0
- package/dist/http/sse.js +72 -0
- package/dist/http/sse.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +219 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/manifest.d.ts +16 -0
- package/dist/mcp/manifest.js +88 -0
- package/dist/mcp/manifest.js.map +1 -0
- package/dist/mcp/server.d.ts +25 -0
- package/dist/mcp/server.js +166 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/providers/base.d.ts +91 -0
- package/dist/providers/base.js +27 -0
- package/dist/providers/base.js.map +1 -0
- package/dist/providers/claude.d.ts +22 -0
- package/dist/providers/claude.js +157 -0
- package/dist/providers/claude.js.map +1 -0
- package/dist/providers/codex.d.ts +38 -0
- package/dist/providers/codex.js +179 -0
- package/dist/providers/codex.js.map +1 -0
- package/dist/providers/detect-util.d.ts +17 -0
- package/dist/providers/detect-util.js +68 -0
- package/dist/providers/detect-util.js.map +1 -0
- package/dist/providers/index.d.ts +14 -0
- package/dist/providers/index.js +21 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/telemetry.d.ts +68 -0
- package/dist/telemetry.js +118 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/version.d.ts +9 -0
- package/dist/version.js +10 -0
- package/dist/version.js.map +1 -0
- package/package.json +62 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sean O'Neill
|
|
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,64 @@
|
|
|
1
|
+
# @seanpropapp/cli
|
|
2
|
+
|
|
3
|
+
Run [SeanPropApp](https://prop.seanoneill.com) proposition analyses on your existing Claude Pro or ChatGPT Plus subscription. No API key, no extra cost.
|
|
4
|
+
|
|
5
|
+
## Quick start
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npx @seanpropapp/cli connect
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
That's it. Click the link that appears, confirm the device in your browser, and your SeanPropApp workspace is now powered by your AI subscription.
|
|
12
|
+
|
|
13
|
+
Time to hello world: about 3 minutes on a fresh machine (Claude CLI install included). Reconnects on a paired machine take a couple of seconds.
|
|
14
|
+
|
|
15
|
+
## What this is
|
|
16
|
+
|
|
17
|
+
A small open-source CLI that runs locally on your computer and lets the SeanPropApp browser app send LLM requests to your Claude or ChatGPT subscription without an API key. Your prompts and your provider responses stay between your computer and your AI provider; this repo only ships the bridge code.
|
|
18
|
+
|
|
19
|
+
## What you need
|
|
20
|
+
|
|
21
|
+
- Node 18 or later (run `node --version`).
|
|
22
|
+
- A Claude Pro subscription OR a ChatGPT Plus subscription.
|
|
23
|
+
- The Claude CLI or Codex CLI installed. `connect` will guide you through this on first run.
|
|
24
|
+
|
|
25
|
+
## Commands
|
|
26
|
+
|
|
27
|
+
| Command | What it does |
|
|
28
|
+
|---------------------|-------------------------------------------------------------|
|
|
29
|
+
| `connect` | Start everything and pair with your browser. Use this first.|
|
|
30
|
+
| `bridge` | Run the bridge server explicitly. |
|
|
31
|
+
| `pair` | Generate a new pair URL. |
|
|
32
|
+
| `mcp` | Run as an MCP stdio server (Claude Desktop, Cursor). |
|
|
33
|
+
| `doctor` | Self-diagnostic with actionable suggestions. |
|
|
34
|
+
| `autostart install` | Install an OS-native supervisor so the bridge starts at login. See [docs/autostart-macos.md](./docs/autostart-macos.md), [docs/autostart-linux.md](./docs/autostart-linux.md), [docs/autostart-windows.md](./docs/autostart-windows.md). |
|
|
35
|
+
| `telemetry` | `enable`, `disable`, or `status`. Default is off. |
|
|
36
|
+
|
|
37
|
+
Global flags: `--config <path>`, `--quiet`, `--json`, `--verbose`, `--no-telemetry`.
|
|
38
|
+
|
|
39
|
+
## How it works
|
|
40
|
+
|
|
41
|
+
The CLI runs a small HTTP server on `127.0.0.1` (default port `17492`, falling back through `17500`). The SeanPropApp browser workspace sends LLM requests to that local URL, attaching a Bearer pair token that lives only on your machine. The CLI in turn shells out to your installed Claude CLI or Codex CLI, which runs against your subscription.
|
|
42
|
+
|
|
43
|
+
The bridge accepts requests only from `https://prop.seanoneill.com` (and `http://localhost:3000` for development); every other Origin is rejected with 403.
|
|
44
|
+
|
|
45
|
+
## Trust signals
|
|
46
|
+
|
|
47
|
+
- **Source code:** 100% in this repository. Review the bridge HTTP server, providers, and command code before installing.
|
|
48
|
+
- **License:** MIT.
|
|
49
|
+
- **npm provenance:** every release is published with `--provenance` (see [.github/workflows/publish.yml](./.github/workflows/publish.yml)).
|
|
50
|
+
- **Telemetry:** opt-in only. See [TELEMETRY.md](./TELEMETRY.md).
|
|
51
|
+
- **Security disclosure:** [SECURITY.md](./SECURITY.md) (mailto: security@seanoneill.com).
|
|
52
|
+
- **Files in the npm package:** run `npm pack --dry-run` to see exactly what we ship. Currently `dist/`, `README.md`, `LICENSE`.
|
|
53
|
+
|
|
54
|
+
## Status
|
|
55
|
+
|
|
56
|
+
v0.1.0-beta.1. The connect/bridge/pair/mcp/doctor/autostart/telemetry commands are live. CI + npm-provenance publish are set up. The workspace-side TTHW dashboard (TX8) ships with v1.4.0 of the proposition-app workspace; until then, telemetry events land but are not visualized in-app.
|
|
57
|
+
|
|
58
|
+
## Contributing
|
|
59
|
+
|
|
60
|
+
See [CONTRIBUTING.md](./CONTRIBUTING.md). Issues and pull requests welcome at [github.com/seanomich/seanpropapp-cli](https://github.com/seanomich/seanpropapp-cli).
|
|
61
|
+
|
|
62
|
+
## License
|
|
63
|
+
|
|
64
|
+
MIT. See [LICENSE](./LICENSE).
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `seanpropapp autostart install|uninstall`.
|
|
3
|
+
*
|
|
4
|
+
* macOS: writes a LaunchAgent plist + bootstraps it via `launchctl`.
|
|
5
|
+
* Linux: writes a user systemd unit + enables it via `systemctl --user`.
|
|
6
|
+
* Windows: prints the schtasks command (full Task Scheduler XML is deferred
|
|
7
|
+
* to v1.4.1; the printed command works for users who run it themselves).
|
|
8
|
+
*
|
|
9
|
+
* The command targets `seanpropapp bridge --foreground` so the OS supervisor
|
|
10
|
+
* keeps the bridge alive across crashes / reboots.
|
|
11
|
+
*/
|
|
12
|
+
import { spawn } from "node:child_process";
|
|
13
|
+
import { promises as fs } from "node:fs";
|
|
14
|
+
export interface AutostartOptions {
|
|
15
|
+
/** macOS / Linux / Windows; defaults to process.platform. */
|
|
16
|
+
platform?: NodeJS.Platform;
|
|
17
|
+
/** Override the binary the supervisor invokes (used by tests). */
|
|
18
|
+
binPath?: string;
|
|
19
|
+
/** Override fs write/read (used by tests). */
|
|
20
|
+
fsImpl?: typeof fs;
|
|
21
|
+
/** Override spawn (used by tests). */
|
|
22
|
+
spawnFn?: typeof spawn;
|
|
23
|
+
/** Override stdout writer (used by tests). */
|
|
24
|
+
stdout?: (line: string) => void;
|
|
25
|
+
/** Override stderr writer (used by tests). */
|
|
26
|
+
stderr?: (line: string) => void;
|
|
27
|
+
/** Override $HOME (used by tests). */
|
|
28
|
+
home?: string;
|
|
29
|
+
/** Skip the launchctl / systemctl subprocess; useful in tests + dry-run. */
|
|
30
|
+
dryRun?: boolean;
|
|
31
|
+
}
|
|
32
|
+
export type AutostartResult = {
|
|
33
|
+
ok: true;
|
|
34
|
+
action: "install" | "uninstall";
|
|
35
|
+
path?: string;
|
|
36
|
+
manual?: string[];
|
|
37
|
+
} | {
|
|
38
|
+
ok: false;
|
|
39
|
+
reason: string;
|
|
40
|
+
};
|
|
41
|
+
export declare function installAutostart(opts?: AutostartOptions): Promise<AutostartResult>;
|
|
42
|
+
export declare function uninstallAutostart(opts?: AutostartOptions): Promise<AutostartResult>;
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `seanpropapp autostart install|uninstall`.
|
|
3
|
+
*
|
|
4
|
+
* macOS: writes a LaunchAgent plist + bootstraps it via `launchctl`.
|
|
5
|
+
* Linux: writes a user systemd unit + enables it via `systemctl --user`.
|
|
6
|
+
* Windows: prints the schtasks command (full Task Scheduler XML is deferred
|
|
7
|
+
* to v1.4.1; the printed command works for users who run it themselves).
|
|
8
|
+
*
|
|
9
|
+
* The command targets `seanpropapp bridge --foreground` so the OS supervisor
|
|
10
|
+
* keeps the bridge alive across crashes / reboots.
|
|
11
|
+
*/
|
|
12
|
+
import { spawn } from "node:child_process";
|
|
13
|
+
import { promises as fs } from "node:fs";
|
|
14
|
+
import os from "node:os";
|
|
15
|
+
import path from "node:path";
|
|
16
|
+
const LAUNCHD_LABEL = "com.seanpropapp.bridge";
|
|
17
|
+
const SYSTEMD_UNIT = "seanpropapp-bridge.service";
|
|
18
|
+
function resolveBinPath(opts) {
|
|
19
|
+
if (opts.binPath)
|
|
20
|
+
return opts.binPath;
|
|
21
|
+
// Default: try to find seanpropapp on PATH. Fall back to a placeholder the
|
|
22
|
+
// user must edit (this happens when someone runs autostart from a dev
|
|
23
|
+
// checkout that hasn't been npm-linked).
|
|
24
|
+
return "seanpropapp";
|
|
25
|
+
}
|
|
26
|
+
function macPlist(binPath) {
|
|
27
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
28
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
29
|
+
<plist version="1.0">
|
|
30
|
+
<dict>
|
|
31
|
+
<key>Label</key>
|
|
32
|
+
<string>${LAUNCHD_LABEL}</string>
|
|
33
|
+
<key>ProgramArguments</key>
|
|
34
|
+
<array>
|
|
35
|
+
<string>${binPath}</string>
|
|
36
|
+
<string>bridge</string>
|
|
37
|
+
<string>--foreground</string>
|
|
38
|
+
</array>
|
|
39
|
+
<key>RunAtLoad</key>
|
|
40
|
+
<true/>
|
|
41
|
+
<key>KeepAlive</key>
|
|
42
|
+
<true/>
|
|
43
|
+
<key>StandardOutPath</key>
|
|
44
|
+
<string>/tmp/seanpropapp-bridge.out.log</string>
|
|
45
|
+
<key>StandardErrorPath</key>
|
|
46
|
+
<string>/tmp/seanpropapp-bridge.err.log</string>
|
|
47
|
+
</dict>
|
|
48
|
+
</plist>
|
|
49
|
+
`;
|
|
50
|
+
}
|
|
51
|
+
function systemdUnit(binPath) {
|
|
52
|
+
return `[Unit]
|
|
53
|
+
Description=SeanPropApp local bridge
|
|
54
|
+
After=network.target
|
|
55
|
+
|
|
56
|
+
[Service]
|
|
57
|
+
ExecStart=${binPath} bridge --foreground
|
|
58
|
+
Restart=always
|
|
59
|
+
RestartSec=5
|
|
60
|
+
|
|
61
|
+
[Install]
|
|
62
|
+
WantedBy=default.target
|
|
63
|
+
`;
|
|
64
|
+
}
|
|
65
|
+
async function runCmd(bin, args, spawnFn) {
|
|
66
|
+
return new Promise((resolve) => {
|
|
67
|
+
const child = spawnFn(bin, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
68
|
+
let stderr = "";
|
|
69
|
+
child.stderr?.on("data", (chunk) => {
|
|
70
|
+
stderr += typeof chunk === "string" ? chunk : chunk.toString();
|
|
71
|
+
});
|
|
72
|
+
child.once("close", (code) => resolve({ code, stderr }));
|
|
73
|
+
child.once("error", (err) => resolve({ code: -1, stderr: err.message }));
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
export async function installAutostart(opts = {}) {
|
|
77
|
+
const platform = opts.platform ?? process.platform;
|
|
78
|
+
const fsImpl = opts.fsImpl ?? fs;
|
|
79
|
+
const spawnFn = opts.spawnFn ?? spawn;
|
|
80
|
+
const out = opts.stdout ?? ((s) => process.stdout.write(s));
|
|
81
|
+
const err = opts.stderr ?? ((s) => process.stderr.write(s));
|
|
82
|
+
const home = opts.home ?? os.homedir();
|
|
83
|
+
const bin = resolveBinPath(opts);
|
|
84
|
+
if (platform === "darwin") {
|
|
85
|
+
const plistDir = path.join(home, "Library", "LaunchAgents");
|
|
86
|
+
const plistPath = path.join(plistDir, `${LAUNCHD_LABEL}.plist`);
|
|
87
|
+
try {
|
|
88
|
+
await fsImpl.mkdir(plistDir, { recursive: true });
|
|
89
|
+
await fsImpl.writeFile(plistPath, macPlist(bin), { mode: 0o644 });
|
|
90
|
+
}
|
|
91
|
+
catch (e) {
|
|
92
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
93
|
+
err(`Could not write ${plistPath}: ${msg}\n`);
|
|
94
|
+
return { ok: false, reason: msg };
|
|
95
|
+
}
|
|
96
|
+
out(`Wrote LaunchAgent: ${plistPath}\n`);
|
|
97
|
+
if (opts.dryRun) {
|
|
98
|
+
return { ok: true, action: "install", path: plistPath };
|
|
99
|
+
}
|
|
100
|
+
const uid = process.getuid?.() ?? 0;
|
|
101
|
+
const cmd = await runCmd("launchctl", ["bootstrap", `gui/${uid}`, plistPath], spawnFn);
|
|
102
|
+
if (cmd.code !== 0) {
|
|
103
|
+
err(`launchctl bootstrap exited ${cmd.code}: ${cmd.stderr.trim() || "(no stderr)"}\n` +
|
|
104
|
+
`If you see "service already loaded", run \`seanpropapp autostart uninstall\` first.\n`);
|
|
105
|
+
return { ok: false, reason: cmd.stderr || `exit ${cmd.code}` };
|
|
106
|
+
}
|
|
107
|
+
out(`Bootstrapped LaunchAgent ${LAUNCHD_LABEL}.\n`);
|
|
108
|
+
return { ok: true, action: "install", path: plistPath };
|
|
109
|
+
}
|
|
110
|
+
if (platform === "linux") {
|
|
111
|
+
const unitDir = path.join(home, ".config", "systemd", "user");
|
|
112
|
+
const unitPath = path.join(unitDir, SYSTEMD_UNIT);
|
|
113
|
+
try {
|
|
114
|
+
await fsImpl.mkdir(unitDir, { recursive: true });
|
|
115
|
+
await fsImpl.writeFile(unitPath, systemdUnit(bin), { mode: 0o644 });
|
|
116
|
+
}
|
|
117
|
+
catch (e) {
|
|
118
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
119
|
+
err(`Could not write ${unitPath}: ${msg}\n`);
|
|
120
|
+
return { ok: false, reason: msg };
|
|
121
|
+
}
|
|
122
|
+
out(`Wrote systemd unit: ${unitPath}\n`);
|
|
123
|
+
if (opts.dryRun) {
|
|
124
|
+
return { ok: true, action: "install", path: unitPath };
|
|
125
|
+
}
|
|
126
|
+
const enable = await runCmd("systemctl", ["--user", "enable", "--now", SYSTEMD_UNIT], spawnFn);
|
|
127
|
+
if (enable.code !== 0) {
|
|
128
|
+
err(`systemctl --user exited ${enable.code}: ${enable.stderr.trim() || "(no stderr)"}\n` +
|
|
129
|
+
`If you're on a server without lingering enabled, run: \`sudo loginctl enable-linger $USER\`\n`);
|
|
130
|
+
return { ok: false, reason: enable.stderr || `exit ${enable.code}` };
|
|
131
|
+
}
|
|
132
|
+
out(`Enabled ${SYSTEMD_UNIT}.\n`);
|
|
133
|
+
return { ok: true, action: "install", path: unitPath };
|
|
134
|
+
}
|
|
135
|
+
if (platform === "win32") {
|
|
136
|
+
// TODO v1.4.1: emit a Task Scheduler XML and import it via `schtasks /create /XML`.
|
|
137
|
+
const cmd = `schtasks /create /SC ONLOGON /RL HIGHEST /TN "SeanPropApp Bridge" /TR "${bin} bridge --foreground"`;
|
|
138
|
+
out("Windows full auto-install is deferred to v1.4.1. Run this once to register the task:\n\n " +
|
|
139
|
+
cmd +
|
|
140
|
+
"\n\nOr open Task Scheduler and create a new task pointing at the same command.\n");
|
|
141
|
+
return { ok: true, action: "install", manual: [cmd] };
|
|
142
|
+
}
|
|
143
|
+
err(`Unsupported platform: ${platform}\n`);
|
|
144
|
+
return { ok: false, reason: `unsupported platform ${platform}` };
|
|
145
|
+
}
|
|
146
|
+
export async function uninstallAutostart(opts = {}) {
|
|
147
|
+
const platform = opts.platform ?? process.platform;
|
|
148
|
+
const fsImpl = opts.fsImpl ?? fs;
|
|
149
|
+
const spawnFn = opts.spawnFn ?? spawn;
|
|
150
|
+
const out = opts.stdout ?? ((s) => process.stdout.write(s));
|
|
151
|
+
const err = opts.stderr ?? ((s) => process.stderr.write(s));
|
|
152
|
+
const home = opts.home ?? os.homedir();
|
|
153
|
+
if (platform === "darwin") {
|
|
154
|
+
const plistPath = path.join(home, "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
|
|
155
|
+
if (!opts.dryRun) {
|
|
156
|
+
const uid = process.getuid?.() ?? 0;
|
|
157
|
+
const cmd = await runCmd("launchctl", ["bootout", `gui/${uid}/${LAUNCHD_LABEL}`], spawnFn);
|
|
158
|
+
if (cmd.code !== 0 && !/No such process/i.test(cmd.stderr)) {
|
|
159
|
+
err(`launchctl bootout warning (continuing): ${cmd.stderr.trim()}\n`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
try {
|
|
163
|
+
await fsImpl.unlink(plistPath);
|
|
164
|
+
out(`Removed ${plistPath}\n`);
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
out(`No plist at ${plistPath}; nothing to remove.\n`);
|
|
168
|
+
}
|
|
169
|
+
return { ok: true, action: "uninstall", path: plistPath };
|
|
170
|
+
}
|
|
171
|
+
if (platform === "linux") {
|
|
172
|
+
const unitPath = path.join(home, ".config", "systemd", "user", SYSTEMD_UNIT);
|
|
173
|
+
if (!opts.dryRun) {
|
|
174
|
+
await runCmd("systemctl", ["--user", "disable", "--now", SYSTEMD_UNIT], spawnFn);
|
|
175
|
+
}
|
|
176
|
+
try {
|
|
177
|
+
await fsImpl.unlink(unitPath);
|
|
178
|
+
out(`Removed ${unitPath}\n`);
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
out(`No unit at ${unitPath}; nothing to remove.\n`);
|
|
182
|
+
}
|
|
183
|
+
return { ok: true, action: "uninstall", path: unitPath };
|
|
184
|
+
}
|
|
185
|
+
if (platform === "win32") {
|
|
186
|
+
const cmd = 'schtasks /delete /TN "SeanPropApp Bridge" /F';
|
|
187
|
+
out("Windows uninstall is deferred to v1.4.1. Run this once to remove the task:\n\n " +
|
|
188
|
+
cmd +
|
|
189
|
+
"\n");
|
|
190
|
+
return { ok: true, action: "uninstall", manual: [cmd] };
|
|
191
|
+
}
|
|
192
|
+
err(`Unsupported platform: ${platform}\n`);
|
|
193
|
+
return { ok: false, reason: `unsupported platform ${platform}` };
|
|
194
|
+
}
|
|
195
|
+
//# sourceMappingURL=autostart.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"autostart.js","sourceRoot":"","sources":["../../src/commands/autostart.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export interface BridgeRunOptions {
|
|
2
|
+
port?: number;
|
|
3
|
+
foreground?: boolean;
|
|
4
|
+
/** When true (the default), reuse a token from config; otherwise generate. */
|
|
5
|
+
reuseToken?: boolean;
|
|
6
|
+
/** Override config dir (used by tests). */
|
|
7
|
+
configDir?: string;
|
|
8
|
+
/** Pre-supplied token (used by `connect` which generates it first). */
|
|
9
|
+
token?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Foreground bridge: bind the HTTP server and stay alive until interrupted.
|
|
13
|
+
*
|
|
14
|
+
* Listens for SIGHUP and reloads the pair token from disk so `seanpropapp pair`
|
|
15
|
+
* can rotate the token without restarting the bridge. SIGINT / SIGTERM trigger
|
|
16
|
+
* a clean shutdown.
|
|
17
|
+
*/
|
|
18
|
+
export declare function runBridgeForeground(opts: BridgeRunOptions): Promise<void>;
|
|
19
|
+
/**
|
|
20
|
+
* Poll the bridge until it responds, or the timeout elapses. Returns true
|
|
21
|
+
* when reachable.
|
|
22
|
+
*/
|
|
23
|
+
export declare function waitForBridge(port: number, token: string, opts?: {
|
|
24
|
+
timeoutMs?: number;
|
|
25
|
+
pollMs?: number;
|
|
26
|
+
fetchImpl?: typeof fetch;
|
|
27
|
+
nowFn?: () => number;
|
|
28
|
+
sleepFn?: (ms: number) => Promise<void>;
|
|
29
|
+
}): Promise<boolean>;
|
|
30
|
+
/**
|
|
31
|
+
* Spawn the current CLI binary in foreground mode as a detached child so the
|
|
32
|
+
* parent process can return immediately. Used by `connect`.
|
|
33
|
+
*
|
|
34
|
+
* After spawning, polls /v1/handshake every 100ms for up to 3s to confirm
|
|
35
|
+
* the child actually bound the port. Lane C-Core's probe-bind-close-then-spawn
|
|
36
|
+
* dance has a sub-second race window we close here.
|
|
37
|
+
*
|
|
38
|
+
* Returns the child's PID + whether the post-spawn handshake succeeded.
|
|
39
|
+
* If `requireHealthy` is true (the default), throws when the handshake never
|
|
40
|
+
* comes up so the caller can fail fast with a clear error.
|
|
41
|
+
*/
|
|
42
|
+
export declare function spawnBackgroundBridge(opts: {
|
|
43
|
+
port?: number;
|
|
44
|
+
token: string;
|
|
45
|
+
configDir?: string;
|
|
46
|
+
requireHealthy?: boolean;
|
|
47
|
+
/** Test seam: skip the post-spawn poll entirely. */
|
|
48
|
+
skipHealthcheck?: boolean;
|
|
49
|
+
/** Test seam: override fetch used by the post-spawn poll. */
|
|
50
|
+
fetchImpl?: typeof fetch;
|
|
51
|
+
}): Promise<{
|
|
52
|
+
pid: number | undefined;
|
|
53
|
+
healthy: boolean;
|
|
54
|
+
}>;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { startServer } from "../http/server.js";
|
|
5
|
+
import { generatePairToken } from "./pair-url.js";
|
|
6
|
+
import { loadConfig, updateConfig, getConfigPath } from "../config.js";
|
|
7
|
+
const HEALTHCHECK_TIMEOUT_MS = 3_000;
|
|
8
|
+
const HEALTHCHECK_POLL_MS = 100;
|
|
9
|
+
/**
|
|
10
|
+
* Foreground bridge: bind the HTTP server and stay alive until interrupted.
|
|
11
|
+
*
|
|
12
|
+
* Listens for SIGHUP and reloads the pair token from disk so `seanpropapp pair`
|
|
13
|
+
* can rotate the token without restarting the bridge. SIGINT / SIGTERM trigger
|
|
14
|
+
* a clean shutdown.
|
|
15
|
+
*/
|
|
16
|
+
export async function runBridgeForeground(opts) {
|
|
17
|
+
const cfg = await loadConfig(opts.configDir);
|
|
18
|
+
let currentToken = opts.token ??
|
|
19
|
+
(opts.reuseToken && cfg.pair_token ? cfg.pair_token : generatePairToken());
|
|
20
|
+
let currentPairedAt = cfg.paired_at ?? null;
|
|
21
|
+
const running = await startServer({
|
|
22
|
+
token: () => currentToken,
|
|
23
|
+
port: opts.port,
|
|
24
|
+
pairedAt: () => currentPairedAt,
|
|
25
|
+
});
|
|
26
|
+
await updateConfig({
|
|
27
|
+
pair_token: currentToken,
|
|
28
|
+
bridge_url: running.url,
|
|
29
|
+
bridge_port: running.port,
|
|
30
|
+
}, opts.configDir);
|
|
31
|
+
process.stdout.write(`Bridge ready on port ${running.port}\n`);
|
|
32
|
+
process.stdout.write(`URL: ${running.url}\n`);
|
|
33
|
+
process.stdout.write(`Config: ${getConfigPath(opts.configDir)}\n`);
|
|
34
|
+
process.stdout.write("Press Ctrl-C to stop.\n");
|
|
35
|
+
const reloadOnSighup = async () => {
|
|
36
|
+
try {
|
|
37
|
+
const next = await loadConfig(opts.configDir);
|
|
38
|
+
if (next.pair_token && next.pair_token !== currentToken) {
|
|
39
|
+
currentToken = next.pair_token;
|
|
40
|
+
process.stdout.write("SIGHUP: reloaded pair token from config.\n");
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
process.stdout.write("SIGHUP: no token change in config; bridge token unchanged.\n");
|
|
44
|
+
}
|
|
45
|
+
if (next.paired_at && next.paired_at !== currentPairedAt) {
|
|
46
|
+
currentPairedAt = next.paired_at;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
process.stderr.write(`SIGHUP: failed to reload config: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
process.on("SIGHUP", reloadOnSighup);
|
|
54
|
+
// Keep alive until SIGINT / SIGTERM.
|
|
55
|
+
await new Promise((resolve) => {
|
|
56
|
+
const shutdown = async () => {
|
|
57
|
+
process.off("SIGHUP", reloadOnSighup);
|
|
58
|
+
await running.close();
|
|
59
|
+
resolve();
|
|
60
|
+
};
|
|
61
|
+
process.once("SIGINT", shutdown);
|
|
62
|
+
process.once("SIGTERM", shutdown);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Probe the bridge's /v1/handshake endpoint, returning true if the server
|
|
67
|
+
* is reachable. Used by the post-spawn health check.
|
|
68
|
+
*/
|
|
69
|
+
async function pingHandshake(port, token, fetchImpl = fetch) {
|
|
70
|
+
try {
|
|
71
|
+
const res = await fetchImpl(`http://127.0.0.1:${port}/v1/handshake`, {
|
|
72
|
+
method: "GET",
|
|
73
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
74
|
+
});
|
|
75
|
+
return res.ok || res.status === 401; // 401 still means "server is up".
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Poll the bridge until it responds, or the timeout elapses. Returns true
|
|
83
|
+
* when reachable.
|
|
84
|
+
*/
|
|
85
|
+
export async function waitForBridge(port, token, opts = {}) {
|
|
86
|
+
const now = opts.nowFn ?? (() => Date.now());
|
|
87
|
+
const sleep = opts.sleepFn ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
88
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
89
|
+
const timeout = opts.timeoutMs ?? HEALTHCHECK_TIMEOUT_MS;
|
|
90
|
+
const poll = opts.pollMs ?? HEALTHCHECK_POLL_MS;
|
|
91
|
+
const deadline = now() + timeout;
|
|
92
|
+
while (now() < deadline) {
|
|
93
|
+
if (await pingHandshake(port, token, fetchImpl))
|
|
94
|
+
return true;
|
|
95
|
+
await sleep(poll);
|
|
96
|
+
}
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Spawn the current CLI binary in foreground mode as a detached child so the
|
|
101
|
+
* parent process can return immediately. Used by `connect`.
|
|
102
|
+
*
|
|
103
|
+
* After spawning, polls /v1/handshake every 100ms for up to 3s to confirm
|
|
104
|
+
* the child actually bound the port. Lane C-Core's probe-bind-close-then-spawn
|
|
105
|
+
* dance has a sub-second race window we close here.
|
|
106
|
+
*
|
|
107
|
+
* Returns the child's PID + whether the post-spawn handshake succeeded.
|
|
108
|
+
* If `requireHealthy` is true (the default), throws when the handshake never
|
|
109
|
+
* comes up so the caller can fail fast with a clear error.
|
|
110
|
+
*/
|
|
111
|
+
export async function spawnBackgroundBridge(opts) {
|
|
112
|
+
// Resolve the path to our own CLI entrypoint. When installed via npm, this
|
|
113
|
+
// file lives at dist/commands/bridge.js, and the entry binary is dist/index.js.
|
|
114
|
+
const here = fileURLToPath(import.meta.url);
|
|
115
|
+
const entry = path.resolve(path.dirname(here), "..", "index.js");
|
|
116
|
+
const args = ["bridge", "--foreground", "--no-token-rotation"];
|
|
117
|
+
if (opts.port)
|
|
118
|
+
args.push("--port", String(opts.port));
|
|
119
|
+
if (opts.configDir)
|
|
120
|
+
args.push("--config", opts.configDir);
|
|
121
|
+
const child = spawn(process.execPath, [entry, ...args], {
|
|
122
|
+
detached: true,
|
|
123
|
+
stdio: "ignore",
|
|
124
|
+
env: {
|
|
125
|
+
...process.env,
|
|
126
|
+
SEANPROPAPP_PRESEED_TOKEN: opts.token,
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
child.unref();
|
|
130
|
+
if (opts.skipHealthcheck || !opts.port) {
|
|
131
|
+
// Without a known port, the parent can't probe; the caller (connect.ts)
|
|
132
|
+
// bound + closed the port itself, so it can fall back to its own check.
|
|
133
|
+
return { pid: child.pid, healthy: false };
|
|
134
|
+
}
|
|
135
|
+
const healthOpts = {};
|
|
136
|
+
if (opts.fetchImpl)
|
|
137
|
+
healthOpts.fetchImpl = opts.fetchImpl;
|
|
138
|
+
const healthy = await waitForBridge(opts.port, opts.token, healthOpts);
|
|
139
|
+
if (!healthy && opts.requireHealthy !== false) {
|
|
140
|
+
throw new Error(`Bridge process spawned (pid ${child.pid ?? "unknown"}) but did not become reachable on port ${opts.port} within ${HEALTHCHECK_TIMEOUT_MS}ms. ` +
|
|
141
|
+
"Run `seanpropapp doctor` to inspect, or try `seanpropapp bridge --foreground` to see the bridge's own stderr.");
|
|
142
|
+
}
|
|
143
|
+
return { pid: child.pid, healthy };
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=bridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bridge.js","sourceRoot":"","sources":["../../src/commands/bridge.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { Provider } from "../providers/base.js";
|
|
2
|
+
export interface ConnectOptions {
|
|
3
|
+
/** When true, the bridge runs in this process instead of being detached. */
|
|
4
|
+
noBridgeFork?: boolean;
|
|
5
|
+
/** Override the requested initial port. */
|
|
6
|
+
port?: number;
|
|
7
|
+
/** Override config dir. */
|
|
8
|
+
configDir?: string;
|
|
9
|
+
/**
|
|
10
|
+
* Test seam: skip Y/N prompt + install. Always treats Claude as detected.
|
|
11
|
+
* Tests use this so they don't depend on terminal input.
|
|
12
|
+
*/
|
|
13
|
+
skipInstallPrompt?: boolean;
|
|
14
|
+
/** Test seam: skip the browser launch. */
|
|
15
|
+
skipBrowserOpen?: boolean;
|
|
16
|
+
/** Test seam: provider override for detection in unit tests. */
|
|
17
|
+
providers?: {
|
|
18
|
+
claude?: Provider;
|
|
19
|
+
codex?: Provider;
|
|
20
|
+
};
|
|
21
|
+
/** Stdout writer override for tests. */
|
|
22
|
+
stdout?: (line: string) => void;
|
|
23
|
+
/** Stderr writer override for tests. */
|
|
24
|
+
stderr?: (line: string) => void;
|
|
25
|
+
/**
|
|
26
|
+
* Test seam: skip the handshake wait loop and pretend the user paired
|
|
27
|
+
* immediately at this timestamp.
|
|
28
|
+
*/
|
|
29
|
+
fakePairedAt?: string;
|
|
30
|
+
/** When true, suppress telemetry emit for this run (--no-telemetry). */
|
|
31
|
+
noTelemetry?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Test seam: skip the post-spawn bridge healthcheck. Production code never
|
|
34
|
+
* sets this; only the connect.test.ts happy-path test does so it can run
|
|
35
|
+
* without an actual `dist/` build to spawn.
|
|
36
|
+
*/
|
|
37
|
+
skipBridgeHealthcheck?: boolean;
|
|
38
|
+
}
|
|
39
|
+
export interface ConnectResult {
|
|
40
|
+
success: boolean;
|
|
41
|
+
elapsedSeconds: number;
|
|
42
|
+
bridgePort?: number;
|
|
43
|
+
pairUrl?: string;
|
|
44
|
+
reason?: string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Persona A entry point. Runs the full first-time flow:
|
|
48
|
+
* 1. Detect Claude CLI; inline-guide install if missing.
|
|
49
|
+
* 2. Generate pair token.
|
|
50
|
+
* 3. Start bridge (background by default).
|
|
51
|
+
* 4. Print clickable + plain-text pair URL (TX13).
|
|
52
|
+
* 5. Open browser.
|
|
53
|
+
* 6. Poll config for paired_at (set by bridge once /pair confirms).
|
|
54
|
+
* 7. Print elapsed time + sample-analysis CTA on success (TX6).
|
|
55
|
+
*/
|
|
56
|
+
export declare function runConnect(opts?: ConnectOptions): Promise<ConnectResult>;
|