lobster-farmer-cli 0.1.1 → 0.1.2

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.
@@ -2,7 +2,7 @@
2
2
 
3
3
  const { spawn, spawnSync } = require("node:child_process");
4
4
  const { existsSync, rmSync, mkdirSync, writeFileSync, readFileSync, openSync } = require("node:fs");
5
- const { homedir } = require("node:os");
5
+ const { homedir, networkInterfaces } = require("node:os");
6
6
  const { resolve } = require("node:path");
7
7
 
8
8
  const entry = resolve(__dirname, "../dist/index.js");
@@ -12,6 +12,7 @@ const dataDir = resolve(homedir(), ".lobster-farmer");
12
12
  const dbBase = resolve(dataDir, "data.sqlite");
13
13
  const pidFile = resolve(dataDir, "lobster-farmer.pid");
14
14
  const logFile = resolve(dataDir, "lobster-farmer.log");
15
+ const portFile = resolve(dataDir, "lobster-farmer.port");
15
16
  const defaultPort = "18990";
16
17
 
17
18
  function printHelp() {
@@ -157,6 +158,7 @@ function resetData() {
157
158
  removeIfExists(`${dbBase}-wal`);
158
159
  removeIfExists(pidFile);
159
160
  removeIfExists(logFile);
161
+ removeIfExists(portFile);
160
162
  console.log("SQLite data reset complete.");
161
163
  }
162
164
 
@@ -206,20 +208,115 @@ function getRunningPid() {
206
208
  }
207
209
 
208
210
  removeIfExists(pidFile);
211
+ removeIfExists(portFile);
209
212
  return null;
210
213
  }
211
214
 
215
+ function readActivePort() {
216
+ if (!existsSync(portFile)) {
217
+ return null;
218
+ }
219
+
220
+ const value = readFileSync(portFile, "utf8").trim();
221
+ const numeric = Number(value);
222
+ if (!Number.isInteger(numeric) || numeric <= 0 || numeric > 65535) {
223
+ return null;
224
+ }
225
+ return String(numeric);
226
+ }
227
+
228
+ function writeActivePort(port) {
229
+ writeFileSync(portFile, `${port}\n`);
230
+ }
231
+
232
+ function accessUrl(port) {
233
+ return `http://localhost:${port}`;
234
+ }
235
+
236
+ function isPrivateLanIpv4(address) {
237
+ const parts = address.split(".").map((part) => Number(part));
238
+ if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) {
239
+ return false;
240
+ }
241
+
242
+ const [a, b] = parts;
243
+ if (a === 10) {
244
+ return true;
245
+ }
246
+ if (a === 172 && b >= 16 && b <= 31) {
247
+ return true;
248
+ }
249
+ if (a === 192 && b === 168) {
250
+ return true;
251
+ }
252
+ if (a === 100 && b >= 64 && b <= 127) {
253
+ return true;
254
+ }
255
+
256
+ return false;
257
+ }
258
+
259
+ function lanAccessUrls(port) {
260
+ const nets = networkInterfaces();
261
+ const urls = [];
262
+ const seen = new Set();
263
+
264
+ for (const interfaces of Object.values(nets)) {
265
+ if (!Array.isArray(interfaces)) {
266
+ continue;
267
+ }
268
+
269
+ for (const iface of interfaces) {
270
+ if (!iface || iface.internal) {
271
+ continue;
272
+ }
273
+
274
+ const family = typeof iface.family === "string" ? iface.family : iface.family === 4 ? "IPv4" : "";
275
+ if (family !== "IPv4") {
276
+ continue;
277
+ }
278
+
279
+ if (!iface.address || !isPrivateLanIpv4(iface.address)) {
280
+ continue;
281
+ }
282
+
283
+ const url = `http://${iface.address}:${port}`;
284
+ if (seen.has(url)) {
285
+ continue;
286
+ }
287
+ seen.add(url);
288
+ urls.push(url);
289
+ }
290
+ }
291
+
292
+ return urls;
293
+ }
294
+
295
+ function printAccessUrls(port) {
296
+ console.log(`url: ${accessUrl(port)}`);
297
+
298
+ const lanUrls = lanAccessUrls(port);
299
+ if (lanUrls.length > 0) {
300
+ console.log("lan:");
301
+ for (const url of lanUrls) {
302
+ console.log(` - ${url}`);
303
+ }
304
+ }
305
+ }
306
+
212
307
  function startServer(port, foreground) {
213
308
  ensureBuild();
214
309
 
215
310
  mkdirSync(dataDir, { recursive: true });
311
+ const resolvedPort = resolvePort(port);
216
312
 
217
313
  const env = { ...process.env };
218
- if (port) {
219
- env.PORT = port;
220
- }
314
+ env.PORT = resolvedPort;
221
315
 
222
316
  if (foreground) {
317
+ writeActivePort(resolvedPort);
318
+ printAccessUrls(resolvedPort);
319
+
223
320
  const child = spawn(process.execPath, [entry], {
224
321
  stdio: "inherit",
225
322
  env
@@ -231,6 +328,7 @@ function startServer(port, foreground) {
231
328
  });
232
329
 
233
330
  child.on("exit", (code, signal) => {
331
+ removeIfExists(portFile);
234
332
  if (signal) {
235
333
  process.kill(process.pid, signal);
236
334
  return;
@@ -243,7 +341,9 @@ function startServer(port, foreground) {
243
341
 
244
342
  const existingPid = getRunningPid();
245
343
  if (existingPid) {
344
+ const runningPort = readActivePort() || resolvedPort;
246
345
  console.log(`Lobster Farmer already running (pid: ${existingPid})`);
346
+ printAccessUrls(runningPort);
247
347
  return;
248
348
  }
249
349
 
@@ -257,8 +357,10 @@ function startServer(port, foreground) {
257
357
  child.unref();
258
358
 
259
359
  writeFileSync(pidFile, String(child.pid));
360
+ writeActivePort(resolvedPort);
260
361
  console.log(`Lobster Farmer started in background.`);
261
362
  console.log(`pid: ${child.pid}`);
363
+ printAccessUrls(resolvedPort);
262
364
  console.log(`log: ${logFile}`);
263
365
  }
264
366
 
@@ -277,6 +379,7 @@ function stopServer() {
277
379
  }
278
380
 
279
381
  removeIfExists(pidFile);
382
+ removeIfExists(portFile);
280
383
  console.log(`Lobster Farmer stopped (pid: ${pid}).`);
281
384
  }
282
385
 
@@ -288,6 +391,8 @@ function showStatus() {
288
391
  }
289
392
 
290
393
  console.log(`Lobster Farmer status: running (pid: ${pid})`);
394
+ const runningPort = readActivePort() || defaultPort;
395
+ printAccessUrls(runningPort);
291
396
  console.log(`log: ${logFile}`);
292
397
  }
293
398
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lobster-farmer-cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "private": false,
5
5
  "description": "Lobster Farmer CLI for OpenClaw auto-feeding and lobster growth simulation",
6
6
  "license": "MIT",
@@ -22,6 +22,12 @@
22
22
  "access": "public"
23
23
  },
24
24
  "packageManager": "pnpm@10.14.0",
25
+ "pnpm": {
26
+ "onlyBuiltDependencies": [
27
+ "better-sqlite3",
28
+ "esbuild"
29
+ ]
30
+ },
25
31
  "scripts": {
26
32
  "build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.json",
27
33
  "dev": "tsx watch server/index.ts",