viveworker 0.2.0 → 0.3.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.
@@ -27,7 +27,22 @@ const defaultLabel = "io.viveworker.app";
27
27
  const defaultTlsDir = path.join(defaultConfigDir, "tls");
28
28
  const defaultServerPort = 8810;
29
29
 
30
- const cli = parseArgs(process.argv.slice(2));
30
+ const rawArgs = process.argv.slice(2);
31
+
32
+ // `viveworker moltbook <cmd> ...` bypasses the strict parseArgs so that
33
+ // free-form flags like `--text "hello"` reach the Moltbook CLI untouched.
34
+ if (rawArgs[0] === "moltbook") {
35
+ const { runMoltbookCli } = await import("./moltbook-cli.mjs");
36
+ try {
37
+ await runMoltbookCli(rawArgs.slice(1));
38
+ process.exit(0);
39
+ } catch (error) {
40
+ console.error(error.message || String(error));
41
+ process.exit(1);
42
+ }
43
+ }
44
+
45
+ const cli = parseArgs(rawArgs);
31
46
 
32
47
  try {
33
48
  await main(cli);
@@ -300,6 +315,254 @@ async function runSetup(cliOptions) {
300
315
  console.log("");
301
316
  console.log(t(locale, "cli.setup.claudeHooksSkipped"));
302
317
  }
318
+
319
+ if (cliOptions.moltbook) {
320
+ try {
321
+ await installMoltbookWatcher({ cliOptions, sessionSecret, port });
322
+ } catch (error) {
323
+ console.log("");
324
+ console.log(`Moltbook watcher install failed: ${error.message}`);
325
+ }
326
+ }
327
+
328
+ if (cliOptions.autoScoutUninstall) {
329
+ await uninstallMoltbookScout();
330
+ } else if (cliOptions.autoScout) {
331
+ try {
332
+ await installMoltbookScout({ cliOptions });
333
+ } catch (error) {
334
+ console.log("");
335
+ console.log(`Moltbook auto-scout install failed: ${error.message}`);
336
+ }
337
+ }
338
+ }
339
+
340
+ async function detectScoutHarness(preferred) {
341
+ const { spawn } = await import("node:child_process");
342
+ const which = (cmd) =>
343
+ new Promise((resolve) => {
344
+ const p = spawn("command", ["-v", cmd], { shell: "/bin/bash", stdio: ["ignore", "pipe", "ignore"] });
345
+ let out = "";
346
+ p.stdout.on("data", (d) => (out += d.toString()));
347
+ p.on("exit", (code) => resolve(code === 0 ? out.trim() : ""));
348
+ p.on("error", () => resolve(""));
349
+ });
350
+ if (preferred === "codex") return { kind: "codex", bin: (await which("codex")) || "codex" };
351
+ if (preferred === "claude") return { kind: "claude", bin: (await which("claude")) || "claude" };
352
+ if (preferred === "manual") return { kind: "manual", bin: "" };
353
+ const codex = await which("codex");
354
+ if (codex) return { kind: "codex", bin: codex };
355
+ const claude = await which("claude");
356
+ if (claude) return { kind: "claude", bin: claude };
357
+ return { kind: "manual", bin: "" };
358
+ }
359
+
360
+ async function uninstallMoltbookScout() {
361
+ const plistPath = path.join(os.homedir(), "Library", "LaunchAgents", `${"com.viveworker.moltbook-scout"}.plist`);
362
+ const { spawn } = await import("node:child_process");
363
+ await new Promise((resolve) => {
364
+ const p = spawn("launchctl", ["bootout", `gui/${process.getuid()}`, plistPath], { stdio: "ignore" });
365
+ p.on("exit", () => resolve());
366
+ p.on("error", () => resolve());
367
+ });
368
+ try {
369
+ await fs.unlink(plistPath);
370
+ console.log("");
371
+ console.log(`Moltbook auto-scout uninstalled (removed ${plistPath}).`);
372
+ } catch {
373
+ console.log("");
374
+ console.log(`No auto-scout plist found at ${plistPath}.`);
375
+ }
376
+ }
377
+
378
+ async function installMoltbookScout({ cliOptions }) {
379
+ const moltbookEnvFile = path.join(os.homedir(), ".viveworker", "moltbook.env");
380
+ try {
381
+ await fs.access(moltbookEnvFile);
382
+ } catch {
383
+ throw new Error(
384
+ `${moltbookEnvFile} not found. Run --moltbook --moltbook-api-key ... --moltbook-agent-id ... first.`
385
+ );
386
+ }
387
+ const harness = await detectScoutHarness(cliOptions.autoScoutHarness || "auto");
388
+ const interval = Number(cliOptions.autoScoutInterval) || 120;
389
+ const scoutRunScript = path.join(packageRoot, "scripts", "moltbook-scout-run.sh");
390
+ const viveworkerJs = path.join(packageRoot, "scripts", "viveworker.mjs");
391
+ const submoltsFlag = cliOptions.autoScoutSubmolts
392
+ ? ` --submolts ${cliOptions.autoScoutSubmolts}`
393
+ : "";
394
+ const maxDailyFlag = cliOptions.autoScoutMaxDaily
395
+ ? ` --max-daily ${cliOptions.autoScoutMaxDaily}`
396
+ : "";
397
+
398
+ const personaPath = path.join(os.homedir(), ".viveworker", "moltbook-persona.md");
399
+ let hasPersona = false;
400
+ try {
401
+ const personaText = await fs.readFile(personaPath, "utf8");
402
+ hasPersona = Boolean(personaText.trim());
403
+ } catch {
404
+ // no persona file
405
+ }
406
+
407
+ // Ensure node (and harness CLI) is on PATH inside launchd's minimal env.
408
+ const nodeBinDir = path.dirname(process.execPath);
409
+ const pathPrefix = `export PATH="${nodeBinDir}:$PATH"`;
410
+ const autoScript = path.join(packageRoot, "scripts", "moltbook-scout-auto.sh");
411
+
412
+ let inner;
413
+ if (harness.kind === "manual") {
414
+ // Manual fallback: just run scout and log the candidate. No drafting.
415
+ inner =
416
+ `${pathPrefix}; set -a; . "${moltbookEnvFile}"; set +a; cd "${packageRoot}" && ` +
417
+ `"${scoutRunScript}"${submoltsFlag}${maxDailyFlag} || true`;
418
+ } else {
419
+ // Use the auto script which runs scout (no LLM), then drafts via harness, then proposes.
420
+ const envVars = [
421
+ `SCOUT_HARNESS=${harness.kind}`,
422
+ submoltsFlag ? `SCOUT_FLAGS="${submoltsFlag.trim()}${maxDailyFlag}"` : maxDailyFlag ? `SCOUT_FLAGS="${maxDailyFlag.trim()}"` : "",
423
+ ].filter(Boolean).join(" ");
424
+ inner =
425
+ `${pathPrefix}; set -a; . "${moltbookEnvFile}"; set +a; ${envVars ? envVars + " " : ""}` +
426
+ `"${autoScript}"`;
427
+ }
428
+
429
+ const userShell = process.env.SHELL || "/bin/zsh";
430
+ const plistPath = path.join(os.homedir(), "Library", "LaunchAgents", `${"com.viveworker.moltbook-scout"}.plist`);
431
+ const plistBody = `<?xml version="1.0" encoding="UTF-8"?>
432
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
433
+ <plist version="1.0">
434
+ <dict>
435
+ <key>Label</key><string>${"com.viveworker.moltbook-scout"}</string>
436
+ <key>ProgramArguments</key>
437
+ <array>
438
+ <string>${escapeXml(userShell)}</string>
439
+ <string>-lc</string>
440
+ <string>${escapeXml(inner)}</string>
441
+ </array>
442
+ <key>StartInterval</key><integer>${interval}</integer>
443
+ <key>RunAtLoad</key><false/>
444
+ <key>StandardOutPath</key><string>/tmp/viveworker-moltbook-scout.out.log</string>
445
+ <key>StandardErrorPath</key><string>/tmp/viveworker-moltbook-scout.err.log</string>
446
+ <key>ProcessType</key><string>Background</string>
447
+ </dict>
448
+ </plist>
449
+ `;
450
+ await fs.mkdir(path.dirname(plistPath), { recursive: true });
451
+ await fs.writeFile(plistPath, plistBody, "utf8");
452
+
453
+ const { spawn } = await import("node:child_process");
454
+ await new Promise((resolve) => {
455
+ const p = spawn("launchctl", ["bootout", `gui/${process.getuid()}`, plistPath], { stdio: "ignore" });
456
+ p.on("exit", () => resolve());
457
+ p.on("error", () => resolve());
458
+ });
459
+ try {
460
+ await new Promise((resolve, reject) => {
461
+ const p = spawn("launchctl", ["bootstrap", `gui/${process.getuid()}`, plistPath], { stdio: "ignore" });
462
+ p.on("exit", (code) => (code === 0 ? resolve() : reject(new Error(`launchctl bootstrap exited ${code}`))));
463
+ p.on("error", reject);
464
+ });
465
+ } catch (error) {
466
+ console.log("");
467
+ console.log(`Moltbook scout plist written but launchctl bootstrap failed: ${error.message}`);
468
+ console.log(`Run manually: launchctl bootstrap gui/$(id -u) ${plistPath}`);
469
+ }
470
+
471
+ console.log("");
472
+ console.log(`Moltbook auto-scout installed (${harness.kind}).`);
473
+ console.log(` plist: ${plistPath}`);
474
+ console.log(` interval: ${interval}s`);
475
+ if (harness.kind === "manual") {
476
+ console.log(
477
+ ` note: Neither 'codex' nor 'claude' CLI was found. The scheduled task will only run scout and print candidates to the log. Install Codex CLI (npm i -g @openai/codex) or Claude Code CLI to enable automated drafting.`
478
+ );
479
+ } else {
480
+ console.log(` harness: ${harness.bin}`);
481
+ }
482
+ console.log(` logs: /tmp/viveworker-moltbook-scout.{out,err}.log`);
483
+ if (!hasPersona) {
484
+ console.log(
485
+ ` tip: No persona file found. Run 'npx viveworker moltbook persona init' to describe your agent — draft quality improves a lot.`
486
+ );
487
+ } else {
488
+ console.log(` persona: ${personaPath}`);
489
+ }
490
+ }
491
+
492
+ async function installMoltbookWatcher({ cliOptions, sessionSecret, port }) {
493
+ if (!cliOptions.moltbookApiKey || !cliOptions.moltbookAgentId) {
494
+ throw new Error(
495
+ "--moltbook requires --moltbook-api-key and --moltbook-agent-id"
496
+ );
497
+ }
498
+ const moltbookDir = path.join(os.homedir(), ".viveworker");
499
+ const moltbookEnvFile = path.join(moltbookDir, "moltbook.env");
500
+ await fs.mkdir(moltbookDir, { recursive: true });
501
+ const envLines = [
502
+ `MOLTBOOK_API_KEY=${cliOptions.moltbookApiKey}`,
503
+ `MOLTBOOK_AGENT_ID=${cliOptions.moltbookAgentId}`,
504
+ cliOptions.moltbookAgentName ? `MOLTBOOK_AGENT_NAME=${cliOptions.moltbookAgentName}` : "",
505
+ `VIVEWORKER_HOOK_SECRET=${sessionSecret}`,
506
+ `VIVEWORKER_BASE_URL=https://127.0.0.1:${port}`,
507
+ "",
508
+ ].filter((line) => line !== null);
509
+ await fs.writeFile(moltbookEnvFile, envLines.join("\n"), { mode: 0o600 });
510
+ await fs.chmod(moltbookEnvFile, 0o600);
511
+
512
+ const plistLabel = "com.viveworker.moltbook-watcher";
513
+ const plistPath = path.join(os.homedir(), "Library", "LaunchAgents", `${plistLabel}.plist`);
514
+ const watcherScript = path.join(packageRoot, "scripts", "moltbook-watcher.mjs");
515
+ const nodePath = process.execPath;
516
+ const watcherShell = process.env.SHELL || "/bin/zsh";
517
+ const watcherNodeDir = path.dirname(nodePath);
518
+ const plistBody = `<?xml version="1.0" encoding="UTF-8"?>
519
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
520
+ <plist version="1.0">
521
+ <dict>
522
+ <key>Label</key><string>${plistLabel}</string>
523
+ <key>ProgramArguments</key>
524
+ <array>
525
+ <string>${escapeXml(watcherShell)}</string>
526
+ <string>-lc</string>
527
+ <string>export PATH="${watcherNodeDir}:$PATH"; set -a; . "${moltbookEnvFile}"; set +a; exec "${nodePath}" "${watcherScript}"</string>
528
+ </array>
529
+ <key>RunAtLoad</key><true/>
530
+ <key>KeepAlive</key><dict><key>SuccessfulExit</key><false/><key>Crashed</key><true/></dict>
531
+ <key>ThrottleInterval</key><integer>30</integer>
532
+ <key>StandardOutPath</key><string>/tmp/viveworker-moltbook-watcher.out.log</string>
533
+ <key>StandardErrorPath</key><string>/tmp/viveworker-moltbook-watcher.err.log</string>
534
+ <key>ProcessType</key><string>Background</string>
535
+ </dict>
536
+ </plist>
537
+ `;
538
+ await fs.mkdir(path.dirname(plistPath), { recursive: true });
539
+ await fs.writeFile(plistPath, plistBody, "utf8");
540
+
541
+ // Reload the agent if launchctl is available
542
+ try {
543
+ const { spawn } = await import("node:child_process");
544
+ await new Promise((resolve) => {
545
+ const unload = spawn("launchctl", ["unload", plistPath], { stdio: "ignore" });
546
+ unload.on("exit", () => resolve());
547
+ unload.on("error", () => resolve());
548
+ });
549
+ await new Promise((resolve, reject) => {
550
+ const load = spawn("launchctl", ["load", plistPath], { stdio: "ignore" });
551
+ load.on("exit", (code) => (code === 0 ? resolve() : reject(new Error(`launchctl load exited ${code}`))));
552
+ load.on("error", reject);
553
+ });
554
+ } catch (error) {
555
+ console.log("");
556
+ console.log(`Moltbook watcher plist written but launchctl load failed: ${error.message}`);
557
+ console.log(`Run manually: launchctl load ${plistPath}`);
558
+ return;
559
+ }
560
+
561
+ console.log("");
562
+ console.log(`Moltbook watcher installed.`);
563
+ console.log(` env: ${moltbookEnvFile}`);
564
+ console.log(` plist: ${plistPath}`);
565
+ console.log(` logs: /tmp/viveworker-moltbook-watcher.{out,err}.log`);
303
566
  }
304
567
 
305
568
  async function installClaudeHooks({ envFile, claudeSettingsFile, sessionSecret }) {
@@ -601,6 +864,16 @@ function parseArgs(argv) {
601
864
  locale: "",
602
865
  mkcertTrustStores: "",
603
866
  claudeSettingsFile: "",
867
+ moltbook: false,
868
+ moltbookApiKey: "",
869
+ moltbookAgentId: "",
870
+ moltbookAgentName: "",
871
+ autoScout: false,
872
+ autoScoutUninstall: false,
873
+ autoScoutInterval: 120,
874
+ autoScoutHarness: "auto",
875
+ autoScoutSubmolts: "",
876
+ autoScoutMaxDaily: 0,
604
877
  };
605
878
 
606
879
  if (argv[0] && !argv[0].startsWith("-")) {
@@ -682,6 +955,35 @@ function parseArgs(argv) {
682
955
  } else if (arg === "--claude-settings-file") {
683
956
  parsed.claudeSettingsFile = next;
684
957
  index += 1;
958
+ } else if (arg === "--moltbook") {
959
+ parsed.moltbook = true;
960
+ } else if (arg === "--moltbook-api-key") {
961
+ parsed.moltbook = true;
962
+ parsed.moltbookApiKey = next;
963
+ index += 1;
964
+ } else if (arg === "--moltbook-agent-id") {
965
+ parsed.moltbook = true;
966
+ parsed.moltbookAgentId = next;
967
+ index += 1;
968
+ } else if (arg === "--moltbook-agent-name") {
969
+ parsed.moltbookAgentName = next;
970
+ index += 1;
971
+ } else if (arg === "--auto-scout") {
972
+ parsed.autoScout = true;
973
+ } else if (arg === "--auto-scout-uninstall") {
974
+ parsed.autoScoutUninstall = true;
975
+ } else if (arg === "--auto-scout-interval") {
976
+ parsed.autoScoutInterval = Number(next) || 120;
977
+ index += 1;
978
+ } else if (arg === "--auto-scout-harness") {
979
+ parsed.autoScoutHarness = next;
980
+ index += 1;
981
+ } else if (arg === "--auto-scout-submolts") {
982
+ parsed.autoScoutSubmolts = next;
983
+ index += 1;
984
+ } else if (arg === "--auto-scout-max-daily") {
985
+ parsed.autoScoutMaxDaily = Number(next) || 0;
986
+ index += 1;
685
987
  } else if (arg === "--help" || arg === "-h") {
686
988
  parsed.command = "help";
687
989
  } else {
package/web/app.css CHANGED
@@ -1932,7 +1932,7 @@ code {
1932
1932
 
1933
1933
  .detail-page-copy--approval {
1934
1934
  margin: 0.78rem 0 0.48rem;
1935
- padding: 0.06rem 0;
1935
+ padding: 0.25rem 0.4rem;
1936
1936
  display: grid;
1937
1937
  gap: 0;
1938
1938
  line-height: 1.24;
@@ -2259,6 +2259,7 @@ code {
2259
2259
 
2260
2260
  .reply-composer__description {
2261
2261
  margin: 0;
2262
+ padding: 0.75rem 1rem;
2262
2263
  }
2263
2264
 
2264
2265
  .reply-composer__form {
@@ -2266,6 +2267,75 @@ code {
2266
2267
  gap: 0.8rem;
2267
2268
  }
2268
2269
 
2270
+ .reply-composer__context {
2271
+ margin: 0.4rem 0;
2272
+ padding: 0.6rem 0.75rem;
2273
+ border-radius: 12px;
2274
+ background: rgba(255, 255, 255, 0.03);
2275
+ border: 1px solid var(--line);
2276
+ font-size: 0.9rem;
2277
+ }
2278
+ .reply-composer__context summary {
2279
+ cursor: pointer;
2280
+ color: var(--muted);
2281
+ }
2282
+ .reply-composer__context-body {
2283
+ margin-top: 0.5rem;
2284
+ line-height: 1.55;
2285
+ color: var(--text);
2286
+ }
2287
+ .reply-composer__intent {
2288
+ margin: 0.5rem 0;
2289
+ padding: 0.6rem 0.75rem;
2290
+ border-radius: 12px;
2291
+ background: rgba(110, 168, 255, 0.08);
2292
+ border: 1px solid rgba(110, 168, 255, 0.25);
2293
+ }
2294
+ .reply-composer__intent p {
2295
+ margin: 0.35rem 0 0;
2296
+ line-height: 1.5;
2297
+ }
2298
+ .reply-composer__author-meta {
2299
+ margin: 0.1rem 0 0.4rem;
2300
+ font-size: 0.85rem;
2301
+ }
2302
+ .reply-composer__textarea {
2303
+ width: 100%;
2304
+ min-height: 14rem;
2305
+ padding: 0.85rem 0.95rem;
2306
+ border-radius: 14px;
2307
+ border: 1px solid var(--line);
2308
+ background: rgba(7, 12, 16, 0.96);
2309
+ color: var(--text);
2310
+ font: inherit;
2311
+ font-size: 1rem;
2312
+ line-height: 1.55;
2313
+ resize: vertical;
2314
+ }
2315
+
2316
+ .reply-composer__textarea:focus {
2317
+ outline: none;
2318
+ border-color: var(--accent, #6ea8ff);
2319
+ }
2320
+
2321
+ .reply-field--title {
2322
+ margin-bottom: 0.5rem;
2323
+ }
2324
+ .reply-field--title .reply-field__input {
2325
+ min-height: auto;
2326
+ padding: 0.65rem 0.85rem;
2327
+ border: 1px solid var(--line);
2328
+ border-radius: 12px;
2329
+ background: rgba(7, 12, 16, 0.96);
2330
+ font-size: 1.05rem;
2331
+ font-weight: 700;
2332
+ }
2333
+
2334
+ .reply-composer__title-display {
2335
+ margin: 0.25rem 0 0.5rem;
2336
+ font-size: 1.05rem;
2337
+ }
2338
+
2269
2339
  .reply-field {
2270
2340
  gap: 0.5rem;
2271
2341
  }
@@ -3347,6 +3417,10 @@ button[data-action-url][aria-busy="true"]:not(.is-loading) {
3347
3417
  color: #f5a45a;
3348
3418
  background: rgba(245, 164, 90, 0.1);
3349
3419
  }
3420
+ .provider-badge--moltbook {
3421
+ color: #e07a5f;
3422
+ background: rgba(224, 122, 95, 0.12);
3423
+ }
3350
3424
 
3351
3425
  .provider-filter {
3352
3426
  display: inline-flex;