@strideops/bridge 0.1.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/LICENSES/cortex-os.txt +21 -0
- package/README.md +98 -0
- package/dist/autostart-77BPPTEG.js +78 -0
- package/dist/autostart-77BPPTEG.js.map +1 -0
- package/dist/chunk-GBLMB3XB.js +95 -0
- package/dist/chunk-GBLMB3XB.js.map +1 -0
- package/dist/cli.js +1023 -0
- package/dist/cli.js.map +1 -0
- package/package.json +32 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Cortext LLC
|
|
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,98 @@
|
|
|
1
|
+
# @strideops/bridge
|
|
2
|
+
|
|
3
|
+
The Stride Bridge daemon lets StrideOps "Build" agents run on your local machine. Agents dispatched from the StrideOps cloud are executed locally via Claude Code CLI, giving them access to your filesystem, local tools, and private network resources.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- Node.js >= 20
|
|
8
|
+
- [Claude Code CLI](https://docs.anthropic.com/claude-code) installed and authenticated (`claude` on PATH)
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install -g @strideops/bridge
|
|
14
|
+
# or
|
|
15
|
+
npx @strideops/bridge connect <CODE>
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
### 1. Connect to your StrideOps workspace
|
|
21
|
+
|
|
22
|
+
Get a pairing code from StrideOps > Build > Bridges > New Bridge, then run:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
stride-bridge connect <PAIRING_CODE>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
This saves credentials to `~/.stride-bridge/config.json` and registers your machine.
|
|
29
|
+
|
|
30
|
+
### 2. Start the daemon
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
stride-bridge start
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The daemon polls for work items dispatched to your bridge and executes them locally using Claude Code CLI. Press Ctrl+C to stop gracefully.
|
|
37
|
+
|
|
38
|
+
### 3. Check status
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
stride-bridge status
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Prints the current configuration, last known state, and whether the StrideOps server is reachable.
|
|
45
|
+
|
|
46
|
+
### 4. Run diagnostics
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
stride-bridge doctor
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Checks all prerequisites:
|
|
53
|
+
- Node.js version (must be >= 20)
|
|
54
|
+
- Claude CLI on PATH and authenticated
|
|
55
|
+
- Config file valid and present
|
|
56
|
+
- StrideOps server reachable
|
|
57
|
+
- Local workspace directory writable
|
|
58
|
+
|
|
59
|
+
## Commands
|
|
60
|
+
|
|
61
|
+
| Command | Description |
|
|
62
|
+
|---------|-------------|
|
|
63
|
+
| `stride-bridge connect <CODE> [--api-url <url>]` | Pair this machine with StrideOps using a pairing code |
|
|
64
|
+
| `stride-bridge start` | Start the daemon (foreground; use a process manager for background) |
|
|
65
|
+
| `stride-bridge status` | Print config and connectivity status |
|
|
66
|
+
| `stride-bridge doctor` | Run prerequisite checks |
|
|
67
|
+
|
|
68
|
+
### Options
|
|
69
|
+
|
|
70
|
+
- `--api-url <url>` — Override the StrideOps API base URL (default: `https://app.strideops.ai`)
|
|
71
|
+
|
|
72
|
+
## Configuration
|
|
73
|
+
|
|
74
|
+
Config is stored at `~/.stride-bridge/config.json` (chmod 600 on POSIX). It is written atomically to prevent corruption. Do not edit it by hand; use `stride-bridge connect` to re-pair.
|
|
75
|
+
|
|
76
|
+
## Running as a Background Service
|
|
77
|
+
|
|
78
|
+
For persistent operation, use your OS process manager:
|
|
79
|
+
|
|
80
|
+
**macOS (launchd)** — create a plist in `~/Library/LaunchAgents/`.
|
|
81
|
+
|
|
82
|
+
**Linux (systemd)** — create a user service unit.
|
|
83
|
+
|
|
84
|
+
**Windows (Task Scheduler)** — create a task that runs `stride-bridge start` on login.
|
|
85
|
+
|
|
86
|
+
## Security
|
|
87
|
+
|
|
88
|
+
- The bridge token is stored locally and never logged.
|
|
89
|
+
- Hook callbacks to StrideOps are signed with a per-agent HMAC secret (SHA-256).
|
|
90
|
+
- All HTTP requests use a 10-second timeout.
|
|
91
|
+
- Hook scripts are fail-open: if StrideOps is unreachable, the agent continues running.
|
|
92
|
+
|
|
93
|
+
## Uninstalling
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
npm uninstall -g @strideops/bridge
|
|
97
|
+
rm -rf ~/.stride-bridge
|
|
98
|
+
```
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { BRIDGE_DIR, log, logError } from './chunk-GBLMB3XB.js';
|
|
3
|
+
import { execFileSync } from 'child_process';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
|
|
7
|
+
var TASK_NAME = "StrideBridge";
|
|
8
|
+
function cliEntryPath() {
|
|
9
|
+
return fileURLToPath(import.meta.url);
|
|
10
|
+
}
|
|
11
|
+
function runSchtasks(args) {
|
|
12
|
+
return execFileSync("schtasks", args, { encoding: "utf-8" });
|
|
13
|
+
}
|
|
14
|
+
function autostartInstall() {
|
|
15
|
+
if (process.platform !== "win32") {
|
|
16
|
+
printPosixInstructions();
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const logFile = join(BRIDGE_DIR, "daemon.log");
|
|
20
|
+
const command = `cmd /c ""${process.execPath}" "${cliEntryPath()}" start >> "${logFile}" 2>&1"`;
|
|
21
|
+
try {
|
|
22
|
+
runSchtasks([
|
|
23
|
+
"/Create",
|
|
24
|
+
"/F",
|
|
25
|
+
// overwrite if exists
|
|
26
|
+
"/TN",
|
|
27
|
+
TASK_NAME,
|
|
28
|
+
"/TR",
|
|
29
|
+
command,
|
|
30
|
+
"/SC",
|
|
31
|
+
"ONLOGON",
|
|
32
|
+
"/RL",
|
|
33
|
+
"LIMITED"
|
|
34
|
+
]);
|
|
35
|
+
log(`Task Scheduler task "${TASK_NAME}" installed \u2014 the daemon starts at logon.`);
|
|
36
|
+
log(`Logs: ${logFile}`);
|
|
37
|
+
log(`Start it now without rebooting: schtasks /Run /TN ${TASK_NAME}`);
|
|
38
|
+
} catch (err) {
|
|
39
|
+
logError("Failed to create scheduled task (try an elevated terminal)", err);
|
|
40
|
+
process.exitCode = 1;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function autostartRemove() {
|
|
44
|
+
if (process.platform !== "win32") {
|
|
45
|
+
log("Autostart install/remove is Windows-only; on macOS/Linux manage your launchd/systemd unit directly.");
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
runSchtasks(["/Delete", "/F", "/TN", TASK_NAME]);
|
|
50
|
+
log(`Task Scheduler task "${TASK_NAME}" removed.`);
|
|
51
|
+
} catch (err) {
|
|
52
|
+
logError(`Failed to remove task "${TASK_NAME}" (was it installed?)`, err);
|
|
53
|
+
process.exitCode = 1;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function autostartStatus() {
|
|
57
|
+
if (process.platform !== "win32") {
|
|
58
|
+
printPosixInstructions();
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
const out = runSchtasks(["/Query", "/TN", TASK_NAME]);
|
|
63
|
+
log(`Task "${TASK_NAME}" is installed:`);
|
|
64
|
+
process.stdout.write(out);
|
|
65
|
+
} catch {
|
|
66
|
+
log(`Task "${TASK_NAME}" is not installed. Run: stride-bridge autostart install`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function printPosixInstructions() {
|
|
70
|
+
log("Autostart auto-install is currently Windows-only. On macOS/Linux:");
|
|
71
|
+
log(` macOS \u2014 create a LaunchAgent running: ${process.execPath} ${cliEntryPath()} start`);
|
|
72
|
+
log(` Linux \u2014 create a systemd user unit: ExecStart=${process.execPath} ${cliEntryPath()} start`);
|
|
73
|
+
log(" then: systemctl --user enable --now stride-bridge");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export { autostartInstall, autostartRemove, autostartStatus };
|
|
77
|
+
//# sourceMappingURL=autostart-77BPPTEG.js.map
|
|
78
|
+
//# sourceMappingURL=autostart-77BPPTEG.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/autostart.ts"],"names":[],"mappings":"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","file":"autostart-77BPPTEG.js","sourcesContent":["/**\n * autostart.ts — Survive reboots (Phase 2).\n *\n * Windows: registers a Task Scheduler task that runs `stride-bridge start`\n * at user logon (the cortex-os Windows lesson: `pm2 startup` is broken on\n * Windows; a logon task in user context is the reliable path).\n * macOS/Linux: prints the launchd/systemd instructions instead — most\n * customers are Windows-first, and a wrong unit file is worse than a recipe.\n */\n\nimport { execFileSync } from \"node:child_process\"\nimport { join } from \"node:path\"\nimport { fileURLToPath } from \"node:url\"\nimport { BRIDGE_DIR } from \"./config.js\"\nimport { log, logError } from \"./log.js\"\n\nconst TASK_NAME = \"StrideBridge\"\n\nfunction cliEntryPath(): string {\n // dist/cli.js — this module is bundled into it, so resolve self.\n return fileURLToPath(import.meta.url)\n}\n\nfunction runSchtasks(args: string[]): string {\n return execFileSync(\"schtasks\", args, { encoding: \"utf-8\" })\n}\n\nexport function autostartInstall(): void {\n if (process.platform !== \"win32\") {\n printPosixInstructions()\n return\n }\n const logFile = join(BRIDGE_DIR, \"daemon.log\")\n // cmd wrapper so stdout/stderr land in a log file the user can read.\n const command = `cmd /c \"\"${process.execPath}\" \"${cliEntryPath()}\" start >> \"${logFile}\" 2>&1\"`\n try {\n runSchtasks([\n \"/Create\",\n \"/F\", // overwrite if exists\n \"/TN\", TASK_NAME,\n \"/TR\", command,\n \"/SC\", \"ONLOGON\",\n \"/RL\", \"LIMITED\",\n ])\n log(`Task Scheduler task \"${TASK_NAME}\" installed — the daemon starts at logon.`)\n log(`Logs: ${logFile}`)\n log(`Start it now without rebooting: schtasks /Run /TN ${TASK_NAME}`)\n } catch (err) {\n logError(\"Failed to create scheduled task (try an elevated terminal)\", err)\n process.exitCode = 1\n }\n}\n\nexport function autostartRemove(): void {\n if (process.platform !== \"win32\") {\n log(\"Autostart install/remove is Windows-only; on macOS/Linux manage your launchd/systemd unit directly.\")\n return\n }\n try {\n runSchtasks([\"/Delete\", \"/F\", \"/TN\", TASK_NAME])\n log(`Task Scheduler task \"${TASK_NAME}\" removed.`)\n } catch (err) {\n logError(`Failed to remove task \"${TASK_NAME}\" (was it installed?)`, err)\n process.exitCode = 1\n }\n}\n\nexport function autostartStatus(): void {\n if (process.platform !== \"win32\") {\n printPosixInstructions()\n return\n }\n try {\n const out = runSchtasks([\"/Query\", \"/TN\", TASK_NAME])\n log(`Task \"${TASK_NAME}\" is installed:`)\n process.stdout.write(out)\n } catch {\n log(`Task \"${TASK_NAME}\" is not installed. Run: stride-bridge autostart install`)\n }\n}\n\nfunction printPosixInstructions(): void {\n log(\"Autostart auto-install is currently Windows-only. On macOS/Linux:\")\n log(` macOS — create a LaunchAgent running: ${process.execPath} ${cliEntryPath()} start`)\n log(` Linux — create a systemd user unit: ExecStart=${process.execPath} ${cliEntryPath()} start`)\n log(\" then: systemctl --user enable --now stride-bridge\")\n}\n"]}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { join, dirname } from 'path';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
import { existsSync, chmodSync, readFileSync, mkdirSync, copyFileSync, writeFileSync, renameSync, unlinkSync } from 'fs';
|
|
5
|
+
import { randomBytes } from 'crypto';
|
|
6
|
+
|
|
7
|
+
function stripBom(s) {
|
|
8
|
+
return s.charCodeAt(0) === 65279 ? s.slice(1) : s;
|
|
9
|
+
}
|
|
10
|
+
function readFileTextStripped(path) {
|
|
11
|
+
return stripBom(readFileSync(path, "utf-8"));
|
|
12
|
+
}
|
|
13
|
+
function atomicWriteSync(filePath, data, keepBak = false) {
|
|
14
|
+
const dir = dirname(filePath);
|
|
15
|
+
mkdirSync(dir, { recursive: true });
|
|
16
|
+
if (keepBak && existsSync(filePath)) {
|
|
17
|
+
try {
|
|
18
|
+
copyFileSync(filePath, filePath + ".bak");
|
|
19
|
+
} catch {
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const tmpPath = join(dir, `.tmp.${randomBytes(6).toString("hex")}`);
|
|
23
|
+
try {
|
|
24
|
+
writeFileSync(tmpPath, data, { encoding: "utf-8", mode: 384 });
|
|
25
|
+
renameSync(tmpPath, filePath);
|
|
26
|
+
} catch (err) {
|
|
27
|
+
try {
|
|
28
|
+
unlinkSync(tmpPath);
|
|
29
|
+
} catch {
|
|
30
|
+
}
|
|
31
|
+
throw err;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function readJsonSafe(filePath) {
|
|
35
|
+
if (!existsSync(filePath)) return null;
|
|
36
|
+
try {
|
|
37
|
+
const text = readFileTextStripped(filePath);
|
|
38
|
+
return JSON.parse(text);
|
|
39
|
+
} catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function writeJsonAtomic(filePath, value) {
|
|
44
|
+
atomicWriteSync(filePath, JSON.stringify(value, null, 2));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// src/config.ts
|
|
48
|
+
var BRIDGE_DIR = join(homedir(), ".stride-bridge");
|
|
49
|
+
var CONFIG_PATH = join(BRIDGE_DIR, "config.json");
|
|
50
|
+
var PENDING_REPORTS_PATH = join(BRIDGE_DIR, "pending-reports.json");
|
|
51
|
+
var AGENTS_DIR = join(BRIDGE_DIR, "agents");
|
|
52
|
+
function readConfig() {
|
|
53
|
+
return readJsonSafe(CONFIG_PATH);
|
|
54
|
+
}
|
|
55
|
+
function writeConfig(config) {
|
|
56
|
+
writeJsonAtomic(CONFIG_PATH, config);
|
|
57
|
+
if (process.platform !== "win32") {
|
|
58
|
+
try {
|
|
59
|
+
chmodSync(CONFIG_PATH, 384);
|
|
60
|
+
} catch {
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function requireConfig() {
|
|
65
|
+
const config = readConfig();
|
|
66
|
+
if (!config) {
|
|
67
|
+
console.error(
|
|
68
|
+
"[stride-bridge] Not connected. Run `stride-bridge connect <PAIRING_CODE>` first."
|
|
69
|
+
);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
return config;
|
|
73
|
+
}
|
|
74
|
+
function isConfigured() {
|
|
75
|
+
return existsSync(CONFIG_PATH);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// src/log.ts
|
|
79
|
+
function log(message) {
|
|
80
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
81
|
+
console.log(`[stride-bridge] ${ts} ${message}`);
|
|
82
|
+
}
|
|
83
|
+
function logError(message, err) {
|
|
84
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
85
|
+
const detail = err instanceof Error ? err.message : err !== void 0 ? String(err) : "";
|
|
86
|
+
console.error(`[stride-bridge] ${ts} ERROR ${message}${detail ? ": " + detail : ""}`);
|
|
87
|
+
}
|
|
88
|
+
function logWarn(message) {
|
|
89
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
90
|
+
console.warn(`[stride-bridge] ${ts} WARN ${message}`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export { AGENTS_DIR, BRIDGE_DIR, CONFIG_PATH, PENDING_REPORTS_PATH, isConfigured, log, logError, logWarn, readConfig, readJsonSafe, requireConfig, writeConfig, writeJsonAtomic };
|
|
94
|
+
//# sourceMappingURL=chunk-GBLMB3XB.js.map
|
|
95
|
+
//# sourceMappingURL=chunk-GBLMB3XB.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/state.ts","../src/config.ts","../src/log.ts"],"names":["join","existsSync"],"mappings":"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","file":"chunk-GBLMB3XB.js","sourcesContent":["/**\n * state.ts — BOM-safe file reading + atomic JSON write utilities.\n *\n * BOM stripping ported from cortex-os/src/utils/strip-bom.ts (MIT, Cortext LLC).\n * Atomic write ported from cortex-os/src/utils/atomic.ts (MIT, Cortext LLC).\n *\n * Windows PowerShell Out-File / Set-Content and many editors write UTF-8 BOM\n * (EF BB BF / U+FEFF). JSON.parse and ^ anchored regex silently fail on BOM\n * bytes. Always strip before parsing any user-editable file.\n */\n\nimport {\n readFileSync,\n writeFileSync,\n renameSync,\n mkdirSync,\n existsSync,\n copyFileSync,\n unlinkSync,\n} from \"node:fs\"\nimport { dirname, join } from \"node:path\"\nimport { randomBytes } from \"node:crypto\"\n\n// ---------------------------------------------------------------------------\n// BOM handling (ported from cortex-os strip-bom.ts)\n// ---------------------------------------------------------------------------\n\n/**\n * Strip a leading UTF-8 BOM (U+FEFF) if present.\n * Fast path: single charCodeAt(0) check; no allocation when no BOM.\n */\nexport function stripBom(s: string): string {\n return s.charCodeAt(0) === 0xfeff ? s.slice(1) : s\n}\n\n/**\n * Read a UTF-8 text file and strip any leading BOM.\n * Use this for all config / JSON files that may have been written by Windows tools.\n */\nexport function readFileTextStripped(path: string): string {\n return stripBom(readFileSync(path, \"utf-8\"))\n}\n\n// ---------------------------------------------------------------------------\n// Atomic write (ported from cortex-os atomic.ts)\n// ---------------------------------------------------------------------------\n\n/**\n * Atomically write data to filePath by writing to a temp file first, then\n * renaming. rename() is atomic on the same filesystem.\n *\n * When keepBak is true, the current file is copied to <filePath>.bak before\n * overwriting. Best-effort — backup failures never block the main write.\n */\nexport function atomicWriteSync(\n filePath: string,\n data: string,\n keepBak = false,\n): void {\n const dir = dirname(filePath)\n mkdirSync(dir, { recursive: true })\n\n if (keepBak && existsSync(filePath)) {\n try {\n copyFileSync(filePath, filePath + \".bak\")\n } catch {\n // Ignore backup errors — do not block the main write.\n }\n }\n\n const tmpPath = join(dir, `.tmp.${randomBytes(6).toString(\"hex\")}`)\n try {\n writeFileSync(tmpPath, data, { encoding: \"utf-8\", mode: 0o600 })\n renameSync(tmpPath, filePath)\n } catch (err) {\n try {\n unlinkSync(tmpPath)\n } catch {\n // Ignore cleanup errors.\n }\n throw err\n }\n}\n\n// ---------------------------------------------------------------------------\n// JSON helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Read and parse a JSON file, stripping BOM first.\n * Returns null if the file does not exist or cannot be parsed.\n */\nexport function readJsonSafe<T>(filePath: string): T | null {\n if (!existsSync(filePath)) return null\n try {\n const text = readFileTextStripped(filePath)\n return JSON.parse(text) as T\n } catch {\n return null\n }\n}\n\n/**\n * Atomically write a value as pretty-printed JSON.\n */\nexport function writeJsonAtomic(filePath: string, value: unknown): void {\n atomicWriteSync(filePath, JSON.stringify(value, null, 2))\n}\n","/**\n * config.ts — Bridge configuration: read/write ~/.stride-bridge/config.json.\n *\n * The config file is written atomically and chmod 600 on POSIX to prevent\n * other users from reading the bridge token.\n */\n\nimport { join } from \"node:path\"\nimport { homedir } from \"node:os\"\nimport { chmodSync, existsSync } from \"node:fs\"\nimport { readJsonSafe, writeJsonAtomic } from \"./state.js\"\n\nexport const BRIDGE_DIR = join(homedir(), \".stride-bridge\")\nexport const CONFIG_PATH = join(BRIDGE_DIR, \"config.json\")\nexport const PENDING_REPORTS_PATH = join(BRIDGE_DIR, \"pending-reports.json\")\nexport const AGENTS_DIR = join(BRIDGE_DIR, \"agents\")\n\nexport interface BridgeConfig {\n apiBaseUrl: string\n /** Opaque bearer token issued by the server during pairing. Never log. */\n bridgeToken: string\n bridgeId: string\n orgId: string\n bridgeName: string\n /**\n * How the agent authenticates with Anthropic.\n * \"claude_login\" = uses the local Claude Code OAuth session (Phase 1).\n * \"org_api_key\" = org-provided key injected into env (TODO Phase 2).\n */\n authMode: \"claude_login\" | \"org_api_key\"\n}\n\n/**\n * Read the config from disk. Returns null if not yet configured.\n */\nexport function readConfig(): BridgeConfig | null {\n return readJsonSafe<BridgeConfig>(CONFIG_PATH)\n}\n\n/**\n * Persist config atomically. On POSIX, chmod 600 after write so only the\n * current user can read the bridge token.\n */\nexport function writeConfig(config: BridgeConfig): void {\n writeJsonAtomic(CONFIG_PATH, config)\n if (process.platform !== \"win32\") {\n try {\n chmodSync(CONFIG_PATH, 0o600)\n } catch {\n // Non-fatal — best effort.\n }\n }\n}\n\n/**\n * Require a valid config or exit with a clear error message.\n */\nexport function requireConfig(): BridgeConfig {\n const config = readConfig()\n if (!config) {\n console.error(\n \"[stride-bridge] Not connected. Run `stride-bridge connect <PAIRING_CODE>` first.\",\n )\n process.exit(1)\n }\n return config\n}\n\n/**\n * Check whether a config file exists (does not validate contents).\n */\nexport function isConfigured(): boolean {\n return existsSync(CONFIG_PATH)\n}\n","/**\n * log.ts — Minimal structured logging with timestamps.\n *\n * All log lines are prefixed with \"[stride-bridge] <ISO timestamp>\" so they\n * are easy to grep in process manager output. Secrets (bridgeToken,\n * hookSecret) must NEVER be passed to these functions.\n */\n\nexport function log(message: string): void {\n const ts = new Date().toISOString()\n console.log(`[stride-bridge] ${ts} ${message}`)\n}\n\nexport function logError(message: string, err?: unknown): void {\n const ts = new Date().toISOString()\n const detail =\n err instanceof Error ? err.message : err !== undefined ? String(err) : \"\"\n console.error(`[stride-bridge] ${ts} ERROR ${message}${detail ? \": \" + detail : \"\"}`)\n}\n\nexport function logWarn(message: string): void {\n const ts = new Date().toISOString()\n console.warn(`[stride-bridge] ${ts} WARN ${message}`)\n}\n"]}
|