botsync 0.1.0

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 ADDED
@@ -0,0 +1,37 @@
1
+ # botsync
2
+
3
+ P2P file sync for AI agents. Two commands. No server.
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ # On machine A (your agent):
9
+ npx botsync init
10
+ # Share the passphrase with machine B
11
+
12
+ # On machine B (you):
13
+ npx botsync join <passphrase>
14
+
15
+ # That's it. ~/sync/ is now shared.
16
+ ```
17
+
18
+ ## Folder Convention
19
+
20
+ | Folder | Purpose |
21
+ |--------|---------|
22
+ | `shared/` | Bidirectional — both sides read and write |
23
+ | `deliverables/` | Agent drops outputs here for human review |
24
+ | `inbox/` | Human drops files here for agent to process |
25
+
26
+ ## Commands
27
+
28
+ ```bash
29
+ botsync init # Initialize and start syncing
30
+ botsync join <passphrase> # Connect to another botsync instance
31
+ botsync status # Show sync status
32
+ botsync stop # Stop the sync daemon
33
+ ```
34
+
35
+ ## How It Works
36
+
37
+ botsync wraps [Syncthing](https://syncthing.net/) — a battle-tested, open-source P2P sync engine. No central server, no cloud accounts, no configuration files to edit. Just a passphrase that encodes everything needed to connect two machines.
package/dist/cli.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * cli.ts — Entry point for the botsync CLI.
4
+ *
5
+ * Routes commands to their handlers. The shebang line makes it
6
+ * executable as `npx botsync` or a globally installed CLI.
7
+ */
8
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,102 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ /**
4
+ * cli.ts — Entry point for the botsync CLI.
5
+ *
6
+ * Routes commands to their handlers. The shebang line makes it
7
+ * executable as `npx botsync` or a globally installed CLI.
8
+ */
9
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ var desc = Object.getOwnPropertyDescriptor(m, k);
12
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
13
+ desc = { enumerable: true, get: function() { return m[k]; } };
14
+ }
15
+ Object.defineProperty(o, k2, desc);
16
+ }) : (function(o, m, k, k2) {
17
+ if (k2 === undefined) k2 = k;
18
+ o[k2] = m[k];
19
+ }));
20
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
21
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
22
+ }) : function(o, v) {
23
+ o["default"] = v;
24
+ });
25
+ var __importStar = (this && this.__importStar) || (function () {
26
+ var ownKeys = function(o) {
27
+ ownKeys = Object.getOwnPropertyNames || function (o) {
28
+ var ar = [];
29
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
30
+ return ar;
31
+ };
32
+ return ownKeys(o);
33
+ };
34
+ return function (mod) {
35
+ if (mod && mod.__esModule) return mod;
36
+ var result = {};
37
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
38
+ __setModuleDefault(result, mod);
39
+ return result;
40
+ };
41
+ })();
42
+ Object.defineProperty(exports, "__esModule", { value: true });
43
+ const commander_1 = require("commander");
44
+ const init_js_1 = require("./commands/init.js");
45
+ const join_js_1 = require("./commands/join.js");
46
+ const status_js_1 = require("./commands/status.js");
47
+ const stop_js_1 = require("./commands/stop.js");
48
+ const ui = __importStar(require("./ui.js"));
49
+ const program = new commander_1.Command();
50
+ program
51
+ .name("botsync")
52
+ .description("P2P file sync for AI agents.")
53
+ .version("0.1.0");
54
+ program
55
+ .command("init")
56
+ .description("Initialize botsync and start syncing. Prints a passphrase for pairing.")
57
+ .action(async () => {
58
+ try {
59
+ await (0, init_js_1.init)();
60
+ }
61
+ catch (err) {
62
+ ui.error(err instanceof Error ? err.message : String(err));
63
+ process.exit(1);
64
+ }
65
+ });
66
+ program
67
+ .command("join <passphrase>")
68
+ .description("Connect to another botsync instance using a passphrase.")
69
+ .action(async (passphrase) => {
70
+ try {
71
+ await (0, join_js_1.join)(passphrase);
72
+ }
73
+ catch (err) {
74
+ ui.error(err instanceof Error ? err.message : String(err));
75
+ process.exit(1);
76
+ }
77
+ });
78
+ program
79
+ .command("status")
80
+ .description("Show sync status — peers, folders, sync progress.")
81
+ .action(async () => {
82
+ try {
83
+ await (0, status_js_1.status)();
84
+ }
85
+ catch (err) {
86
+ ui.error(err instanceof Error ? err.message : String(err));
87
+ process.exit(1);
88
+ }
89
+ });
90
+ program
91
+ .command("stop")
92
+ .description("Stop the botsync daemon.")
93
+ .action(async () => {
94
+ try {
95
+ await (0, stop_js_1.stop)();
96
+ }
97
+ catch (err) {
98
+ ui.error(err instanceof Error ? err.message : String(err));
99
+ process.exit(1);
100
+ }
101
+ });
102
+ program.parse();
@@ -0,0 +1,13 @@
1
+ /**
2
+ * init.ts — The `botsync init` command.
3
+ *
4
+ * This is the "machine A" side of the pairing flow. It:
5
+ * 1. Creates the ~/sync/ directory structure
6
+ * 2. Downloads Syncthing if needed
7
+ * 3. Generates config with sane defaults
8
+ * 4. Starts the daemon
9
+ * 5. Waits for it to come online
10
+ * 6. Produces a passphrase that encodes everything "machine B" needs to connect
11
+ * 7. Polls for the joining device and auto-accepts it
12
+ */
13
+ export declare function init(): Promise<void>;
@@ -0,0 +1,151 @@
1
+ "use strict";
2
+ /**
3
+ * init.ts — The `botsync init` command.
4
+ *
5
+ * This is the "machine A" side of the pairing flow. It:
6
+ * 1. Creates the ~/sync/ directory structure
7
+ * 2. Downloads Syncthing if needed
8
+ * 3. Generates config with sane defaults
9
+ * 4. Starts the daemon
10
+ * 5. Waits for it to come online
11
+ * 6. Produces a passphrase that encodes everything "machine B" needs to connect
12
+ * 7. Polls for the joining device and auto-accepts it
13
+ */
14
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ var desc = Object.getOwnPropertyDescriptor(m, k);
17
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
18
+ desc = { enumerable: true, get: function() { return m[k]; } };
19
+ }
20
+ Object.defineProperty(o, k2, desc);
21
+ }) : (function(o, m, k, k2) {
22
+ if (k2 === undefined) k2 = k;
23
+ o[k2] = m[k];
24
+ }));
25
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
26
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
27
+ }) : function(o, v) {
28
+ o["default"] = v;
29
+ });
30
+ var __importStar = (this && this.__importStar) || (function () {
31
+ var ownKeys = function(o) {
32
+ ownKeys = Object.getOwnPropertyNames || function (o) {
33
+ var ar = [];
34
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
35
+ return ar;
36
+ };
37
+ return ownKeys(o);
38
+ };
39
+ return function (mod) {
40
+ if (mod && mod.__esModule) return mod;
41
+ var result = {};
42
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
43
+ __setModuleDefault(result, mod);
44
+ return result;
45
+ };
46
+ })();
47
+ Object.defineProperty(exports, "__esModule", { value: true });
48
+ exports.init = init;
49
+ const fs_1 = require("fs");
50
+ const crypto_1 = require("crypto");
51
+ const config_js_1 = require("../config.js");
52
+ const syncthing_js_1 = require("../syncthing.js");
53
+ const passphrase_js_1 = require("../passphrase.js");
54
+ const ui = __importStar(require("../ui.js"));
55
+ /**
56
+ * Pick a random port in the ephemeral range for the Syncthing REST API.
57
+ * We avoid 8384 (Syncthing's default) to not conflict with any existing
58
+ * Syncthing installation the user might have.
59
+ */
60
+ function randomPort() {
61
+ return 27000 + Math.floor(Math.random() * 10000);
62
+ }
63
+ async function init() {
64
+ ui.header();
65
+ // Step 1: Create directory structure
66
+ for (const folder of config_js_1.FOLDERS) {
67
+ (0, fs_1.mkdirSync)(folder.path, { recursive: true });
68
+ }
69
+ (0, fs_1.mkdirSync)(config_js_1.BOTSYNC_DIR, { recursive: true });
70
+ (0, fs_1.mkdirSync)(config_js_1.SYNCTHING_CONFIG_DIR, { recursive: true });
71
+ ui.stepDone("Sync folders created");
72
+ // Step 2: Download Syncthing binary (skips if already present)
73
+ await (0, syncthing_js_1.downloadSyncthing)();
74
+ ui.stepDone("Syncthing ready");
75
+ // Step 3: Generate config
76
+ const apiKey = (0, crypto_1.randomUUID)();
77
+ const apiPort = randomPort();
78
+ const configXml = (0, syncthing_js_1.generateConfig)(apiKey, apiPort);
79
+ (0, fs_1.writeFileSync)(`${config_js_1.SYNCTHING_CONFIG_DIR}/config.xml`, configXml);
80
+ (0, config_js_1.writeConfig)({ apiKey, apiPort });
81
+ // Step 4: Start the daemon
82
+ const pid = (0, syncthing_js_1.startDaemon)();
83
+ ui.stepDone(`Daemon started (PID ${pid})`);
84
+ // Step 5: Wait for Syncthing to be ready and get our device ID
85
+ const spin = ui.spinner("Starting Syncthing...");
86
+ await (0, syncthing_js_1.waitForStart)();
87
+ spin.succeed();
88
+ const deviceId = await (0, syncthing_js_1.getDeviceId)();
89
+ (0, config_js_1.writeConfig)({ apiKey, apiPort, deviceId });
90
+ // Step 6: Register with relay and display the code
91
+ ui.gap();
92
+ const { code, isRelay } = await (0, passphrase_js_1.createCode)({
93
+ deviceId,
94
+ folders: config_js_1.FOLDERS.map((f) => f.id),
95
+ });
96
+ if (isRelay) {
97
+ ui.passphraseBox(code, `npx botsync join ${code}`);
98
+ ui.info("Code expires in 10 minutes.");
99
+ }
100
+ else {
101
+ ui.info("Relay unavailable — using offline passphrase:");
102
+ ui.gap();
103
+ ui.passphraseBox(code, `npx botsync join ${code.substring(0, 20)}...`);
104
+ }
105
+ ui.gap();
106
+ // Step 7: Wait for the joining device to connect and auto-accept it
107
+ const peerSpin = ui.spinner("Waiting for peer...");
108
+ const accepted = await waitForPeer(apiKey, apiPort, peerSpin);
109
+ if (accepted) {
110
+ peerSpin.stop();
111
+ ui.paired(accepted);
112
+ }
113
+ }
114
+ /**
115
+ * Poll for pending device connections and auto-accept the first one.
116
+ * After accepting the device, share all botsync folders with it.
117
+ *
118
+ * Times out after 5 minutes — if nobody joins by then, they can still
119
+ * join later (they'll just show up as pending until the user restarts
120
+ * init or manually approves via the API).
121
+ */
122
+ async function waitForPeer(apiKey, apiPort, spin, timeoutMs = 300_000) {
123
+ const start = Date.now();
124
+ const pollInterval = 2000;
125
+ while (Date.now() - start < timeoutMs) {
126
+ try {
127
+ const pending = await (0, syncthing_js_1.apiCall)("GET", "/rest/cluster/pending/devices");
128
+ const deviceIds = Object.keys(pending);
129
+ if (deviceIds.length > 0) {
130
+ const peerId = deviceIds[0];
131
+ spin.text = `Accepting ${peerId.substring(0, 7)}...`;
132
+ // Add the device to our config
133
+ await (0, syncthing_js_1.addDevice)(peerId);
134
+ // Share all folders with it
135
+ for (const folder of config_js_1.FOLDERS) {
136
+ await (0, syncthing_js_1.addDeviceToFolder)(folder.id, peerId);
137
+ }
138
+ return peerId;
139
+ }
140
+ }
141
+ catch {
142
+ // API might hiccup during config changes — ignore and retry
143
+ }
144
+ await new Promise((r) => setTimeout(r, pollInterval));
145
+ }
146
+ spin.stop();
147
+ ui.info("No peer connected within 5 minutes.");
148
+ ui.info("The passphrase is still valid — join anytime, then restart init.");
149
+ ui.gap();
150
+ return null;
151
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * join.ts — The `botsync join <passphrase>` command.
3
+ *
4
+ * This is the "machine B" side of the pairing flow. It:
5
+ * 1. Decodes the passphrase to get the init side's device ID + folder list
6
+ * 2. Creates the local directory structure
7
+ * 3. Downloads Syncthing if needed
8
+ * 4. Generates config
9
+ * 5. Starts the daemon
10
+ * 6. Adds the init side as a known device
11
+ * 7. Shares all folders with the init side
12
+ */
13
+ export declare function join(passphrase: string): Promise<void>;
@@ -0,0 +1,106 @@
1
+ "use strict";
2
+ /**
3
+ * join.ts — The `botsync join <passphrase>` command.
4
+ *
5
+ * This is the "machine B" side of the pairing flow. It:
6
+ * 1. Decodes the passphrase to get the init side's device ID + folder list
7
+ * 2. Creates the local directory structure
8
+ * 3. Downloads Syncthing if needed
9
+ * 4. Generates config
10
+ * 5. Starts the daemon
11
+ * 6. Adds the init side as a known device
12
+ * 7. Shares all folders with the init side
13
+ */
14
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ var desc = Object.getOwnPropertyDescriptor(m, k);
17
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
18
+ desc = { enumerable: true, get: function() { return m[k]; } };
19
+ }
20
+ Object.defineProperty(o, k2, desc);
21
+ }) : (function(o, m, k, k2) {
22
+ if (k2 === undefined) k2 = k;
23
+ o[k2] = m[k];
24
+ }));
25
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
26
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
27
+ }) : function(o, v) {
28
+ o["default"] = v;
29
+ });
30
+ var __importStar = (this && this.__importStar) || (function () {
31
+ var ownKeys = function(o) {
32
+ ownKeys = Object.getOwnPropertyNames || function (o) {
33
+ var ar = [];
34
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
35
+ return ar;
36
+ };
37
+ return ownKeys(o);
38
+ };
39
+ return function (mod) {
40
+ if (mod && mod.__esModule) return mod;
41
+ var result = {};
42
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
43
+ __setModuleDefault(result, mod);
44
+ return result;
45
+ };
46
+ })();
47
+ Object.defineProperty(exports, "__esModule", { value: true });
48
+ exports.join = join;
49
+ const fs_1 = require("fs");
50
+ const crypto_1 = require("crypto");
51
+ const config_js_1 = require("../config.js");
52
+ const syncthing_js_1 = require("../syncthing.js");
53
+ const passphrase_js_1 = require("../passphrase.js");
54
+ const ui = __importStar(require("../ui.js"));
55
+ async function join(passphrase) {
56
+ ui.header();
57
+ // Resolve the code — tries relay first, falls back to base58
58
+ const spin0 = ui.spinner("Resolving pairing code...");
59
+ let remoteId;
60
+ let folders;
61
+ try {
62
+ const data = await (0, passphrase_js_1.resolveCode)(passphrase);
63
+ remoteId = data.deviceId;
64
+ folders = data.folders;
65
+ spin0.succeed();
66
+ }
67
+ catch (err) {
68
+ spin0.fail();
69
+ ui.error(err instanceof Error ? err.message : "Failed to resolve code");
70
+ process.exit(1);
71
+ }
72
+ const short = remoteId.substring(0, 7);
73
+ ui.info(`Connecting to ${short}...`);
74
+ ui.gap();
75
+ // First time? Set up everything
76
+ const existing = (0, config_js_1.readConfig)();
77
+ if (!existing) {
78
+ for (const folder of config_js_1.FOLDERS) {
79
+ (0, fs_1.mkdirSync)(folder.path, { recursive: true });
80
+ }
81
+ (0, fs_1.mkdirSync)(config_js_1.BOTSYNC_DIR, { recursive: true });
82
+ (0, fs_1.mkdirSync)(config_js_1.SYNCTHING_CONFIG_DIR, { recursive: true });
83
+ await (0, syncthing_js_1.downloadSyncthing)();
84
+ ui.stepDone("Syncthing ready");
85
+ const apiKey = (0, crypto_1.randomUUID)();
86
+ const apiPort = 27000 + Math.floor(Math.random() * 10000);
87
+ const configXml = (0, syncthing_js_1.generateConfig)(apiKey, apiPort);
88
+ (0, fs_1.writeFileSync)(`${config_js_1.SYNCTHING_CONFIG_DIR}/config.xml`, configXml);
89
+ (0, config_js_1.writeConfig)({ apiKey, apiPort });
90
+ const pid = (0, syncthing_js_1.startDaemon)();
91
+ ui.stepDone(`Daemon started (PID ${pid})`);
92
+ const spin = ui.spinner("Starting Syncthing...");
93
+ await (0, syncthing_js_1.waitForStart)();
94
+ spin.succeed();
95
+ const myId = await (0, syncthing_js_1.getDeviceId)();
96
+ (0, config_js_1.writeConfig)({ apiKey, apiPort, deviceId: myId });
97
+ }
98
+ // Add the remote device and share folders
99
+ const spin2 = ui.spinner("Pairing...");
100
+ await (0, syncthing_js_1.addDevice)(remoteId);
101
+ for (const folderId of folders) {
102
+ await (0, syncthing_js_1.addDeviceToFolder)(folderId, remoteId);
103
+ }
104
+ spin2.stop();
105
+ ui.connected(remoteId);
106
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * status.ts — The `botsync status` command.
3
+ *
4
+ * Shows connected peers, device ID, and per-folder sync state.
5
+ */
6
+ export declare function status(): Promise<void>;
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ /**
3
+ * status.ts — The `botsync status` command.
4
+ *
5
+ * Shows connected peers, device ID, and per-folder sync state.
6
+ */
7
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
8
+ if (k2 === undefined) k2 = k;
9
+ var desc = Object.getOwnPropertyDescriptor(m, k);
10
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
11
+ desc = { enumerable: true, get: function() { return m[k]; } };
12
+ }
13
+ Object.defineProperty(o, k2, desc);
14
+ }) : (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ o[k2] = m[k];
17
+ }));
18
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
19
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
20
+ }) : function(o, v) {
21
+ o["default"] = v;
22
+ });
23
+ var __importStar = (this && this.__importStar) || (function () {
24
+ var ownKeys = function(o) {
25
+ ownKeys = Object.getOwnPropertyNames || function (o) {
26
+ var ar = [];
27
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
28
+ return ar;
29
+ };
30
+ return ownKeys(o);
31
+ };
32
+ return function (mod) {
33
+ if (mod && mod.__esModule) return mod;
34
+ var result = {};
35
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
36
+ __setModuleDefault(result, mod);
37
+ return result;
38
+ };
39
+ })();
40
+ Object.defineProperty(exports, "__esModule", { value: true });
41
+ exports.status = status;
42
+ const config_js_1 = require("../config.js");
43
+ const syncthing_js_1 = require("../syncthing.js");
44
+ const ui = __importStar(require("../ui.js"));
45
+ async function status() {
46
+ const config = (0, config_js_1.readConfig)();
47
+ if (!config) {
48
+ ui.notRunning();
49
+ return;
50
+ }
51
+ // Check if daemon is actually running
52
+ try {
53
+ await (0, syncthing_js_1.apiCall)("GET", "/rest/system/status");
54
+ }
55
+ catch {
56
+ ui.notRunning();
57
+ return;
58
+ }
59
+ // Get connections
60
+ const connections = await (0, syncthing_js_1.apiCall)("GET", "/rest/system/connections");
61
+ const peers = Object.values(connections.connections).filter((c) => c.connected).length;
62
+ // Get folder statuses
63
+ const folders = [];
64
+ for (const f of config_js_1.FOLDERS) {
65
+ try {
66
+ const s = await (0, syncthing_js_1.apiCall)("GET", `/rest/db/status?folder=${f.id}`);
67
+ const synced = s.state === "idle" && s.needFiles === 0;
68
+ const state = synced ? "idle" : s.state;
69
+ folders.push({
70
+ name: f.id.replace("botsync-", ""),
71
+ synced,
72
+ state,
73
+ lastChange: s.stateChanged,
74
+ });
75
+ }
76
+ catch {
77
+ folders.push({ name: f.id.replace("botsync-", ""), synced: false, state: "unknown" });
78
+ }
79
+ }
80
+ ui.statusTable(peers, config.deviceId || "unknown", folders);
81
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * stop.ts — The `botsync stop` command.
3
+ *
4
+ * Stops the Syncthing daemon.
5
+ */
6
+ export declare function stop(): Promise<void>;
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ /**
3
+ * stop.ts — The `botsync stop` command.
4
+ *
5
+ * Stops the Syncthing daemon.
6
+ */
7
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
8
+ if (k2 === undefined) k2 = k;
9
+ var desc = Object.getOwnPropertyDescriptor(m, k);
10
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
11
+ desc = { enumerable: true, get: function() { return m[k]; } };
12
+ }
13
+ Object.defineProperty(o, k2, desc);
14
+ }) : (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ o[k2] = m[k];
17
+ }));
18
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
19
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
20
+ }) : function(o, v) {
21
+ o["default"] = v;
22
+ });
23
+ var __importStar = (this && this.__importStar) || (function () {
24
+ var ownKeys = function(o) {
25
+ ownKeys = Object.getOwnPropertyNames || function (o) {
26
+ var ar = [];
27
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
28
+ return ar;
29
+ };
30
+ return ownKeys(o);
31
+ };
32
+ return function (mod) {
33
+ if (mod && mod.__esModule) return mod;
34
+ var result = {};
35
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
36
+ __setModuleDefault(result, mod);
37
+ return result;
38
+ };
39
+ })();
40
+ Object.defineProperty(exports, "__esModule", { value: true });
41
+ exports.stop = stop;
42
+ const syncthing_js_1 = require("../syncthing.js");
43
+ const ui = __importStar(require("../ui.js"));
44
+ async function stop() {
45
+ const killed = (0, syncthing_js_1.stopDaemon)();
46
+ if (killed) {
47
+ ui.stopped();
48
+ }
49
+ else {
50
+ ui.notRunning();
51
+ }
52
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * config.ts — Central configuration and path management for botsync.
3
+ *
4
+ * All paths are derived from the user's home directory. The sync root is ~/sync/,
5
+ * and botsync's internal state lives in ~/sync/.botsync/. We also store the
6
+ * syncthing binary in ~/.botsync/bin/ (outside the sync dir so it doesn't get synced).
7
+ *
8
+ * config.json is the runtime state file — it stores the API key, port, device ID,
9
+ * and PID so that all commands can talk to the running Syncthing instance.
10
+ */
11
+ export declare const SYNC_DIR: string;
12
+ export declare const BOTSYNC_DIR: string;
13
+ export declare const SYNCTHING_CONFIG_DIR: string;
14
+ export declare const SYNCTHING_BIN_DIR: string;
15
+ export declare const SYNCTHING_BIN: string;
16
+ export declare const CONFIG_FILE: string;
17
+ export declare const PID_FILE: string;
18
+ export declare const FOLDERS: {
19
+ id: string;
20
+ path: string;
21
+ }[];
22
+ /** Runtime config shape — everything we need to talk to Syncthing */
23
+ export interface BotsyncConfig {
24
+ apiKey: string;
25
+ apiPort: number;
26
+ deviceId?: string;
27
+ }
28
+ /** Read the config file, or return null if it doesn't exist */
29
+ export declare function readConfig(): BotsyncConfig | null;
30
+ /** Write config to disk. Creates parent dirs if needed. */
31
+ export declare function writeConfig(config: BotsyncConfig): void;
package/dist/config.js ADDED
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ /**
3
+ * config.ts — Central configuration and path management for botsync.
4
+ *
5
+ * All paths are derived from the user's home directory. The sync root is ~/sync/,
6
+ * and botsync's internal state lives in ~/sync/.botsync/. We also store the
7
+ * syncthing binary in ~/.botsync/bin/ (outside the sync dir so it doesn't get synced).
8
+ *
9
+ * config.json is the runtime state file — it stores the API key, port, device ID,
10
+ * and PID so that all commands can talk to the running Syncthing instance.
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.FOLDERS = exports.PID_FILE = exports.CONFIG_FILE = exports.SYNCTHING_BIN = exports.SYNCTHING_BIN_DIR = exports.SYNCTHING_CONFIG_DIR = exports.BOTSYNC_DIR = exports.SYNC_DIR = void 0;
14
+ exports.readConfig = readConfig;
15
+ exports.writeConfig = writeConfig;
16
+ const fs_1 = require("fs");
17
+ const path_1 = require("path");
18
+ const os_1 = require("os");
19
+ // BOTSYNC_ROOT env var overrides the default ~/sync/ location.
20
+ // Useful for testing without touching a production sync folder.
21
+ const root = process.env.BOTSYNC_ROOT || (0, path_1.join)((0, os_1.homedir)(), "sync");
22
+ // Where synced files live — the user-facing directory
23
+ exports.SYNC_DIR = root;
24
+ // Internal botsync state — inside sync dir but gitignored by Syncthing
25
+ exports.BOTSYNC_DIR = (0, path_1.join)(exports.SYNC_DIR, ".botsync");
26
+ // Syncthing config/data lives here (inside .botsync so it's co-located)
27
+ exports.SYNCTHING_CONFIG_DIR = (0, path_1.join)(exports.BOTSYNC_DIR, "syncthing");
28
+ // Where we store the syncthing binary — outside sync dir to avoid syncing a binary
29
+ exports.SYNCTHING_BIN_DIR = (0, path_1.join)((0, os_1.homedir)(), ".botsync", "bin");
30
+ exports.SYNCTHING_BIN = (0, path_1.join)(exports.SYNCTHING_BIN_DIR, "syncthing");
31
+ // Runtime config file — API key, port, device ID, PID
32
+ exports.CONFIG_FILE = (0, path_1.join)(exports.BOTSYNC_DIR, "config.json");
33
+ // PID file for the daemon
34
+ exports.PID_FILE = (0, path_1.join)(exports.BOTSYNC_DIR, "daemon.pid");
35
+ // The three standard sync folders
36
+ exports.FOLDERS = [
37
+ { id: "botsync-shared", path: (0, path_1.join)(exports.SYNC_DIR, "shared") },
38
+ { id: "botsync-deliverables", path: (0, path_1.join)(exports.SYNC_DIR, "deliverables") },
39
+ { id: "botsync-inbox", path: (0, path_1.join)(exports.SYNC_DIR, "inbox") },
40
+ ];
41
+ /** Read the config file, or return null if it doesn't exist */
42
+ function readConfig() {
43
+ try {
44
+ const raw = (0, fs_1.readFileSync)(exports.CONFIG_FILE, "utf-8");
45
+ return JSON.parse(raw);
46
+ }
47
+ catch {
48
+ return null;
49
+ }
50
+ }
51
+ /** Write config to disk. Creates parent dirs if needed. */
52
+ function writeConfig(config) {
53
+ (0, fs_1.mkdirSync)(exports.BOTSYNC_DIR, { recursive: true });
54
+ (0, fs_1.writeFileSync)(exports.CONFIG_FILE, JSON.stringify(config, null, 2));
55
+ }