@way_marks/server 3.0.0 → 4.0.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.
@@ -60,6 +60,42 @@ function getRegistryProjects() {
60
60
  return [];
61
61
  }
62
62
  }
63
+ function readRegistry() {
64
+ if (!fs.existsSync(registryPath))
65
+ return { projects: {}, releasedPorts: [] };
66
+ try {
67
+ const r = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
68
+ return { projects: r.projects || {}, releasedPorts: r.releasedPorts || [], lastUpdated: r.lastUpdated };
69
+ }
70
+ catch {
71
+ return { projects: {}, releasedPorts: [] };
72
+ }
73
+ }
74
+ function writeRegistry(reg) {
75
+ reg.lastUpdated = new Date().toISOString();
76
+ fs.writeFileSync(registryPath, JSON.stringify({ version: 1, ...reg }, null, 2) + '\n');
77
+ }
78
+ function mutateRegistryEntry(id, mutator) {
79
+ const reg = readRegistry();
80
+ const entry = reg.projects[id];
81
+ if (!entry)
82
+ return null;
83
+ mutator(entry);
84
+ reg.projects[id] = entry;
85
+ writeRegistry(reg);
86
+ return entry;
87
+ }
88
+ function tryKill(pid) {
89
+ if (!pid)
90
+ return false;
91
+ try {
92
+ process.kill(pid, 'SIGTERM');
93
+ return true;
94
+ }
95
+ catch {
96
+ return false;
97
+ }
98
+ }
63
99
  // Phase 4: Garbage collection for registry
64
100
  function garbageCollectRegistryFile() {
65
101
  try {
@@ -94,9 +130,26 @@ function garbageCollectRegistryFile() {
94
130
  }
95
131
  }
96
132
  const app = (0, express_1.default)();
97
- const PORT = parseInt(process.env.WAYMARK_PORT || '3001', 10);
133
+ // Fallback only `waymark start` always passes WAYMARK_PORT explicitly.
134
+ // 47000 is the new default range (47000-47999); 3001 was the legacy default.
135
+ const PORT = parseInt(process.env.WAYMARK_PORT || '47000', 10);
98
136
  app.use(express_1.default.json());
99
137
  app.use(express_1.default.urlencoded({ extended: true }));
138
+ // Same-machine peer CORS for the Hub view: another Waymark dashboard on a
139
+ // different localhost port may probe this server's /api/* (e.g. /api/stats,
140
+ // /api/hub/*). Allow it without opening up arbitrary remote origins.
141
+ app.use((req, res, next) => {
142
+ const origin = req.headers.origin;
143
+ if (typeof origin === 'string' && /^http:\/\/(localhost|127\.0\.0\.1):\d+$/.test(origin)) {
144
+ res.setHeader('Access-Control-Allow-Origin', origin);
145
+ res.setHeader('Vary', 'Origin');
146
+ res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,OPTIONS');
147
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
148
+ if (req.method === 'OPTIONS')
149
+ return res.sendStatus(204);
150
+ }
151
+ next();
152
+ });
100
153
  // Serve UI — path works for both ts-node (src/api/) and compiled (dist/api/).
101
154
  const UI_DIR = path.resolve(__dirname, '../../src/ui-dist');
102
155
  const UI_INDEX = path.join(UI_DIR, 'index.html');
@@ -447,15 +500,22 @@ app.get('/api/config', (req, res) => {
447
500
  res.status(500).json({ error: err.message });
448
501
  }
449
502
  });
450
- // GET /api/project — returns project metadata from .waymark/config.json
451
- app.get('/api/project', (req, res) => {
503
+ // GET /api/project — returns live project metadata for the running server.
504
+ // Source of truth: .waymark/config.json (written by `waymark start`).
505
+ // Falls back to env-only state when running stand-alone (no .waymark/ yet).
506
+ app.get('/api/project', (_req, res) => {
452
507
  try {
453
- const configPath = path.join(process.env.WAYMARK_PROJECT_ROOT || process.cwd(), '.waymark', 'config.json');
508
+ const projectRoot = process.env.WAYMARK_PROJECT_ROOT || process.cwd();
509
+ const configPath = path.join(projectRoot, '.waymark', 'config.json');
454
510
  if (!fs.existsSync(configPath)) {
455
- return res.json({ projectName: null, port: PORT });
511
+ return res.json({ projectName: null, port: PORT, projectRoot });
456
512
  }
457
513
  const cfg = JSON.parse(fs.readFileSync(configPath, 'utf8'));
458
- res.json({ projectName: cfg.projectName || null, port: cfg.port || PORT });
514
+ res.json({
515
+ projectName: cfg.projectName || null,
516
+ port: cfg.port || PORT,
517
+ projectRoot: cfg.projectRoot || projectRoot,
518
+ });
459
519
  }
460
520
  catch (err) {
461
521
  res.status(500).json({ error: err.message });
@@ -482,6 +542,86 @@ app.post('/api/registry/cleanup', (req, res) => {
482
542
  }
483
543
  });
484
544
  // ============================================================================
545
+ // Hub: cross-project mutations driven from any peer's dashboard.
546
+ // All write to ~/.waymark/registry.json the same way the CLI does.
547
+ // ============================================================================
548
+ // POST /api/hub/projects/:id/pause — flip status to paused (port retained)
549
+ app.post('/api/hub/projects/:id/pause', (req, res) => {
550
+ try {
551
+ const { id } = req.params;
552
+ const updated = mutateRegistryEntry(id, (e) => {
553
+ e.status = 'paused';
554
+ e.pausedAt = new Date().toISOString();
555
+ });
556
+ if (!updated)
557
+ return res.status(404).json({ error: `Project not found: ${id}` });
558
+ res.json({ success: true, project: updated });
559
+ }
560
+ catch (err) {
561
+ res.status(500).json({ error: err.message });
562
+ }
563
+ });
564
+ // POST /api/hub/projects/:id/resume — flip status back to running
565
+ app.post('/api/hub/projects/:id/resume', (req, res) => {
566
+ try {
567
+ const { id } = req.params;
568
+ const updated = mutateRegistryEntry(id, (e) => {
569
+ e.status = 'running';
570
+ delete e.pausedAt;
571
+ });
572
+ if (!updated)
573
+ return res.status(404).json({ error: `Project not found: ${id}` });
574
+ res.json({ success: true, project: updated });
575
+ }
576
+ catch (err) {
577
+ res.status(500).json({ error: err.message });
578
+ }
579
+ });
580
+ // POST /api/hub/projects/:id/stop — SIGTERM the peer's mcp+api, mark stopped,
581
+ // release the port for reuse. Mirrors `waymark stop` behaviour without needing
582
+ // the user to cd into the other project.
583
+ app.post('/api/hub/projects/:id/stop', (req, res) => {
584
+ try {
585
+ const { id } = req.params;
586
+ const reg = readRegistry();
587
+ const entry = reg.projects[id];
588
+ if (!entry)
589
+ return res.status(404).json({ error: `Project not found: ${id}` });
590
+ const killedApi = tryKill(entry.api_pid);
591
+ const killedMcp = tryKill(entry.mcp_pid);
592
+ entry.status = 'stopped';
593
+ entry.stoppedAt = new Date().toISOString();
594
+ reg.projects[id] = entry;
595
+ // Release port for reuse (mirrors registry.releasePort behaviour).
596
+ if (entry.port && Array.isArray(reg.releasedPorts)) {
597
+ reg.releasedPorts.push(entry.port);
598
+ }
599
+ else {
600
+ reg.releasedPorts = [entry.port];
601
+ }
602
+ writeRegistry(reg);
603
+ res.json({
604
+ success: true,
605
+ project: entry,
606
+ killed: { api: killedApi, mcp: killedMcp },
607
+ message: killedApi || killedMcp ? `Stopped ${id}.` : `${id} was not running (registry cleaned).`,
608
+ });
609
+ }
610
+ catch (err) {
611
+ res.status(500).json({ error: err.message });
612
+ }
613
+ });
614
+ // POST /api/hub/gc — alias of /api/registry/cleanup; convenient from the Hub UI.
615
+ app.post('/api/hub/gc', (_req, res) => {
616
+ try {
617
+ const removed = garbageCollectRegistryFile();
618
+ res.json({ success: true, removed, message: `Garbage collected ${removed} stale entries` });
619
+ }
620
+ catch (err) {
621
+ res.status(500).json({ error: err.message });
622
+ }
623
+ });
624
+ // ============================================================================
485
625
  // PHASE 2: Team Approval Routing Endpoints
486
626
  // ============================================================================
487
627
  // GET /api/team/members — list all team members
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@way_marks/server",
3
- "version": "3.0.0",
3
+ "version": "4.0.0",
4
4
  "description": "Waymark MCP server and dashboard",
5
5
  "author": "Waymark <hello@waymarks.dev>",
6
6
  "license": "MIT",