@way_marks/cli 0.7.0 → 0.9.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.
@@ -40,7 +40,8 @@ const os = __importStar(require("os"));
40
40
  const readline = __importStar(require("readline"));
41
41
  const child_process_1 = require("child_process");
42
42
  const DEFAULT_CONFIG = {
43
- version: '1',
43
+ version: '2',
44
+ platforms: ['claude'],
44
45
  policies: {
45
46
  allowedPaths: [
46
47
  './src/**',
@@ -170,6 +171,38 @@ function resolveServerBin() {
170
171
  return path.resolve(__dirname, '../../../server/dist/mcp/server.js');
171
172
  }
172
173
  }
174
+ async function selectPlatforms() {
175
+ console.log('');
176
+ console.log('┌─ Platform Selection ──────────────────────────────────┐');
177
+ console.log('│ Which AI platform(s) will you use with Waymark? │');
178
+ console.log('│ │');
179
+ console.log('│ 1. Claude Desktop / Claude Code (RECOMMENDED) ✓ │');
180
+ console.log('│ → Best experience. Full Waymark features. │');
181
+ console.log('│ │');
182
+ console.log('│ 2. GitHub Copilot CLI (EXPERIMENTAL) ⚠ │');
183
+ console.log('│ → Terminal support. CLI-only (no VSCode). │');
184
+ console.log('│ │');
185
+ console.log('│ 3. Both (Claude + GitHub Copilot CLI) │');
186
+ console.log('│ → Setup for both. Easy to switch. │');
187
+ console.log('└───────────────────────────────────────────────────────┘');
188
+ console.log('');
189
+ while (true) {
190
+ const answer = await prompt('Enter choice (1-3) [default: 1]: ');
191
+ const choice = answer || '1';
192
+ if (choice === '1') {
193
+ return ['claude'];
194
+ }
195
+ else if (choice === '2') {
196
+ return ['copilot-cli'];
197
+ }
198
+ else if (choice === '3') {
199
+ return ['claude', 'copilot-cli'];
200
+ }
201
+ else {
202
+ console.log('Invalid choice. Please enter 1, 2, or 3.');
203
+ }
204
+ }
205
+ }
173
206
  async function run() {
174
207
  const projectRoot = process.cwd();
175
208
  const projectName = kebabCase(path.basename(projectRoot));
@@ -183,6 +216,8 @@ async function run() {
183
216
  if (!hasPackageJson && !hasGit) {
184
217
  console.warn('Warning: No package.json or .git found. Continuing anyway.');
185
218
  }
219
+ // Step 1b — Select platforms (NEW in Phase 5)
220
+ const selectedPlatforms = await selectPlatforms();
186
221
  // Step 2 — Install @way_marks/server (skip if already resolvable or in monorepo)
187
222
  let serverBin;
188
223
  try {
@@ -208,38 +243,44 @@ async function run() {
208
243
  serverBin = resolveServerBin();
209
244
  }
210
245
  }
211
- // Step 3 — Create waymark.config.json
246
+ // Step 3 — Create waymark.config.json with selected platforms
212
247
  const configPath = path.join(projectRoot, 'waymark.config.json');
248
+ const platformConfig = { ...DEFAULT_CONFIG, platforms: selectedPlatforms };
213
249
  if (fs.existsSync(configPath)) {
214
250
  const answer = await prompt('waymark.config.json exists. Overwrite? (y/N) ');
215
251
  if (answer.toLowerCase() !== 'y') {
216
252
  console.log('Keeping existing waymark.config.json');
217
253
  }
218
254
  else {
219
- fs.writeFileSync(configPath, JSON.stringify(DEFAULT_CONFIG, null, 2) + '\n');
220
- console.log('✓ Created waymark.config.json');
255
+ fs.writeFileSync(configPath, JSON.stringify(platformConfig, null, 2) + '\n');
256
+ console.log('✓ Created waymark.config.json with platforms:', selectedPlatforms.join(', '));
221
257
  }
222
258
  }
223
259
  else {
224
- fs.writeFileSync(configPath, JSON.stringify(DEFAULT_CONFIG, null, 2) + '\n');
225
- console.log('✓ Created waymark.config.json');
260
+ fs.writeFileSync(configPath, JSON.stringify(platformConfig, null, 2) + '\n');
261
+ console.log('✓ Created waymark.config.json with platforms:', selectedPlatforms.join(', '));
226
262
  }
227
- // Step 4 — Create/append CLAUDE.md
228
- const claudeMdPath = path.join(projectRoot, 'CLAUDE.md');
229
- const claudeMdContent = generateClaudeMd(projectName, defaultPort);
230
- if (fs.existsSync(claudeMdPath)) {
231
- const existing = fs.readFileSync(claudeMdPath, 'utf8');
232
- if (existing.includes(WAYMARK_MARKER)) {
233
- console.log('✓ CLAUDE.md already has Waymark section');
263
+ // Step 4 — Create/append CLAUDE.md (only if Claude selected)
264
+ if (selectedPlatforms.includes('claude')) {
265
+ const claudeMdPath = path.join(projectRoot, 'CLAUDE.md');
266
+ const claudeMdContent = generateClaudeMd(projectName, defaultPort);
267
+ if (fs.existsSync(claudeMdPath)) {
268
+ const existing = fs.readFileSync(claudeMdPath, 'utf8');
269
+ if (existing.includes(WAYMARK_MARKER)) {
270
+ console.log('✓ CLAUDE.md already has Waymark section');
271
+ }
272
+ else {
273
+ fs.appendFileSync(claudeMdPath, `\n${WAYMARK_MARKER}\n${claudeMdContent}`);
274
+ console.log('✓ Appended Waymark section to CLAUDE.md');
275
+ }
234
276
  }
235
277
  else {
236
- fs.appendFileSync(claudeMdPath, `\n${WAYMARK_MARKER}\n${claudeMdContent}`);
237
- console.log('✓ Appended Waymark section to CLAUDE.md');
278
+ fs.writeFileSync(claudeMdPath, `${WAYMARK_MARKER}\n${claudeMdContent}`);
279
+ console.log('✓ Created CLAUDE.md Claude Code will now use Waymark automatically');
238
280
  }
239
281
  }
240
282
  else {
241
- fs.writeFileSync(claudeMdPath, `${WAYMARK_MARKER}\n${claudeMdContent}`);
242
- console.log('✓ Created CLAUDE.md — Claude Code will now use Waymark automatically');
283
+ console.log('⊘ Skipping CLAUDE.md (Claude not selected)');
243
284
  }
244
285
  // Step 5 — Update .gitignore
245
286
  const gitignorePath = path.join(projectRoot, '.gitignore');
@@ -256,47 +297,66 @@ async function run() {
256
297
  else {
257
298
  console.log('✓ .gitignore already up to date');
258
299
  }
259
- // Step 6 — Register MCP in both Claude configs
260
- const nodeBin = process.execPath;
261
- const mcpEntry = {
262
- command: nodeBin,
263
- args: [serverBin, '--project-root', projectRoot, '--db-path', dbPath]
264
- };
265
- // Claude Desktop config — add/update this project's entry only
266
- const desktopConfigPath = getClaudeDesktopConfigPath();
267
- try {
268
- const desktopDir = path.dirname(desktopConfigPath);
269
- if (!fs.existsSync(desktopDir))
270
- fs.mkdirSync(desktopDir, { recursive: true });
271
- const desktopConfig = fs.existsSync(desktopConfigPath)
272
- ? JSON.parse(fs.readFileSync(desktopConfigPath, 'utf8'))
273
- : { mcpServers: {} };
274
- if (!desktopConfig.mcpServers)
275
- desktopConfig.mcpServers = {};
276
- desktopConfig.mcpServers[mcpKey] = mcpEntry;
277
- fs.writeFileSync(desktopConfigPath, JSON.stringify(desktopConfig, null, 2) + '\n');
278
- console.log(`✓ Registered MCP server "${mcpKey}" in Claude Desktop config`);
279
- }
280
- catch (err) {
281
- console.warn(`Warning: Could not update Claude Desktop config: ${err.message}`);
300
+ // Step 6 — Register MCP in both Claude configs (only if Claude selected)
301
+ if (selectedPlatforms.includes('claude')) {
302
+ const nodeBin = process.execPath;
303
+ const mcpEntry = {
304
+ command: nodeBin,
305
+ args: [serverBin, '--project-root', projectRoot, '--db-path', dbPath]
306
+ };
307
+ // Claude Desktop config — add/update this project's entry only
308
+ const desktopConfigPath = getClaudeDesktopConfigPath();
309
+ try {
310
+ const desktopDir = path.dirname(desktopConfigPath);
311
+ if (!fs.existsSync(desktopDir))
312
+ fs.mkdirSync(desktopDir, { recursive: true });
313
+ const desktopConfig = fs.existsSync(desktopConfigPath)
314
+ ? JSON.parse(fs.readFileSync(desktopConfigPath, 'utf8'))
315
+ : { mcpServers: {} };
316
+ if (!desktopConfig.mcpServers)
317
+ desktopConfig.mcpServers = {};
318
+ desktopConfig.mcpServers[mcpKey] = mcpEntry;
319
+ fs.writeFileSync(desktopConfigPath, JSON.stringify(desktopConfig, null, 2) + '\n');
320
+ console.log(`✓ Registered MCP server "${mcpKey}" in Claude Desktop config`);
321
+ }
322
+ catch (err) {
323
+ console.warn(`Warning: Could not update Claude Desktop config: ${err.message}`);
324
+ }
325
+ // .mcp.json (Claude Code project-level)
326
+ const mcpJsonPath = path.join(projectRoot, '.mcp.json');
327
+ try {
328
+ const mcpJson = fs.existsSync(mcpJsonPath)
329
+ ? JSON.parse(fs.readFileSync(mcpJsonPath, 'utf8'))
330
+ : { mcpServers: {} };
331
+ if (!mcpJson.mcpServers)
332
+ mcpJson.mcpServers = {};
333
+ mcpJson.mcpServers[mcpKey] = { type: 'stdio', ...mcpEntry, cwd: projectRoot };
334
+ fs.writeFileSync(mcpJsonPath, JSON.stringify(mcpJson, null, 2) + '\n');
335
+ console.log(`✓ Created/updated .mcp.json for Claude Code`);
336
+ }
337
+ catch (err) {
338
+ console.warn(`Warning: Could not update .mcp.json: ${err.message}`);
339
+ }
282
340
  }
283
- // .mcp.json (Claude Code project-level)
284
- const mcpJsonPath = path.join(projectRoot, '.mcp.json');
285
- try {
286
- const mcpJson = fs.existsSync(mcpJsonPath)
287
- ? JSON.parse(fs.readFileSync(mcpJsonPath, 'utf8'))
288
- : { mcpServers: {} };
289
- if (!mcpJson.mcpServers)
290
- mcpJson.mcpServers = {};
291
- mcpJson.mcpServers[mcpKey] = { type: 'stdio', ...mcpEntry, cwd: projectRoot };
292
- fs.writeFileSync(mcpJsonPath, JSON.stringify(mcpJson, null, 2) + '\n');
293
- console.log(`✓ Created/updated .mcp.json for Claude Code`);
341
+ else {
342
+ console.log('⊘ Skipping MCP registration (Claude not selected)');
294
343
  }
295
- catch (err) {
296
- console.warn(`Warning: Could not update .mcp.json: ${err.message}`);
344
+ // Step 6b — Show Copilot CLI instructions (if Copilot CLI selected)
345
+ if (selectedPlatforms.includes('copilot-cli')) {
346
+ console.log('');
347
+ console.log('📋 GitHub Copilot CLI Setup');
348
+ console.log('─'.repeat(50));
349
+ console.log('⚠️ Copilot CLI support requires manual setup.');
350
+ console.log('See COPILOT_CLI.md for step-by-step instructions.');
351
+ console.log('');
352
+ console.log('Quick start:');
353
+ console.log('1. Find copilot binary: which copilot');
354
+ console.log('2. Run: npx @way_marks/cli init-copilot-wrapper');
355
+ console.log('3. Test: copilot --version');
356
+ console.log('');
297
357
  }
298
- // Step 7 — Success summary
299
- const col = 43;
358
+ // Step 7 — Success summary (updated for platform selection)
359
+ const col = 50;
300
360
  const pad = (s) => s + ' '.repeat(Math.max(0, col - 2 - s.length));
301
361
  console.log('');
302
362
  console.log('┌' + '─'.repeat(col) + '┐');
@@ -304,22 +364,35 @@ async function run() {
304
364
  console.log(`│ ${pad('')} │`);
305
365
  console.log(`│ ${pad(`Project: ${projectName}`)} │`);
306
366
  console.log(`│ ${pad('Database: .waymark/waymark.db')} │`);
307
- console.log(`│ ${pad(`MCP key: ${mcpKey}`)} │`);
367
+ console.log(`│ ${pad(`Platforms: ${selectedPlatforms.join(', ')}`)} │`);
308
368
  console.log(`│ ${pad('')} │`);
309
369
  console.log(`│ ${pad('Files created:')} │`);
310
- console.log(`│ ${pad(' waymark.config.json')} │`);
311
- console.log(`│ ${pad(' CLAUDE.md')} │`);
370
+ console.log(`│ ${pad(' waymark.config.json (with platforms)')} │`);
371
+ if (selectedPlatforms.includes('claude')) {
372
+ console.log(`│ ${pad(' CLAUDE.md')} │`);
373
+ }
374
+ if (selectedPlatforms.includes('copilot-cli')) {
375
+ console.log(`│ ${pad(' (see COPILOT_CLI.md setup)')} │`);
376
+ }
312
377
  console.log(`│ ${pad(' .waymark/ (gitignored)')} │`);
313
378
  console.log(`│ ${pad('')} │`);
314
- console.log(`│ ${pad('Next steps:')} │`);
315
- console.log(`│ ${pad('1. Run: npx @way_marks/cli start')} │`);
316
- console.log(`│ ${pad('2. Restart Claude Code')} │`);
317
- console.log(`│ ${pad('3. Open this project in Claude')} │`);
318
- console.log(`│ ${pad('4. Dashboard: http://localhost:3001')} │`);
319
- console.log(`│ ${pad(' (port may differ if 3001 is taken)')} │`);
379
+ if (selectedPlatforms.includes('claude')) {
380
+ console.log(`│ ${pad('Next steps (Claude):')} │`);
381
+ console.log(`│ ${pad('1. Run: npx @way_marks/cli start')} │`);
382
+ console.log(`│ ${pad('2. Restart Claude Code')} │`);
383
+ console.log(`│ ${pad('3. Open project in Claude')} │`);
384
+ console.log(`│ ${pad('4. Dashboard: http://localhost:3001')} │`);
385
+ }
386
+ if (selectedPlatforms.includes('copilot-cli')) {
387
+ if (selectedPlatforms.includes('claude')) {
388
+ console.log(`│ ${pad('')} │`);
389
+ }
390
+ console.log(`│ ${pad('Next steps (Copilot CLI):')} │`);
391
+ console.log(`│ ${pad('1. See COPILOT_CLI.md for wrapper setup')} │`);
392
+ }
320
393
  console.log(`│ ${pad('')} │`);
321
- console.log(`│ ${pad('Waymark is now always-on in this')} │`);
322
- console.log(`│ ${pad('project. Claude will use Waymark')} │`);
323
- console.log(`│ ${pad('tools automatically via CLAUDE.md')} │`);
394
+ console.log(`│ ${pad('For more info:')} │`);
395
+ console.log(`│ ${pad(' README.md Feature overview')} │`);
396
+ console.log(`│ ${pad(' README_PLATFORMS.md Platform support')} │`);
324
397
  console.log('└' + '─'.repeat(col) + '┘');
325
398
  }
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ /**
3
+ * waymark list — List all registered Waymark projects
4
+ *
5
+ * Shows project name, port, status, uptime, and user.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.run = run;
9
+ const registry_1 = require("../registry");
10
+ function run() {
11
+ (0, registry_1.cleanupStaleEntries)();
12
+ const projects = (0, registry_1.listProjects)();
13
+ if (projects.length === 0) {
14
+ console.log('No Waymark projects registered.');
15
+ console.log('Run: waymark init && waymark start');
16
+ return;
17
+ }
18
+ // Format output
19
+ console.log('\n📋 Waymark Projects');
20
+ console.log('═══════════════════════════════════════════════════════════════\n');
21
+ for (const p of projects) {
22
+ const statusEmoji = p.status === 'running' ? '🟢' : p.status === 'paused' ? '⏸️ ' : '🔴';
23
+ const started = new Date(p.startedAt);
24
+ const uptime = p.status === 'running'
25
+ ? Math.floor((Date.now() - started.getTime()) / 1000)
26
+ : 0;
27
+ const uptimeStr = uptime > 3600
28
+ ? `${Math.floor(uptime / 3600)}h ${Math.floor((uptime % 3600) / 60)}m`
29
+ : `${Math.floor(uptime / 60)}m`;
30
+ console.log(`${statusEmoji} ${p.projectName}`);
31
+ console.log(` ID: ${p.id}`);
32
+ console.log(` Port: http://localhost:${p.port}`);
33
+ console.log(` Status: ${p.status}${p.status === 'running' ? ` (${uptimeStr})` : ''}`);
34
+ console.log(` User: ${p.user}@${p.hostname}`);
35
+ console.log(` Path: ${p.projectRoot}`);
36
+ console.log('');
37
+ }
38
+ const running = projects.filter(p => p.status === 'running').length;
39
+ const paused = projects.filter(p => p.status === 'paused').length;
40
+ const stopped = projects.filter(p => p.status === 'stopped').length;
41
+ console.log(`Summary: ${running} running, ${paused} paused, ${stopped} stopped`);
42
+ console.log('═══════════════════════════════════════════════════════════════\n');
43
+ if (running === 0) {
44
+ console.log('💡 Tip: Run "waymark open PROJECT_NAME" to start a project');
45
+ }
46
+ }
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ /**
3
+ * waymark open — Open a project dashboard in browser
4
+ *
5
+ * Usage: waymark open PROJECT_NAME
6
+ *
7
+ * If the project is not running, starts it first.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.run = run;
11
+ const child_process_1 = require("child_process");
12
+ const registry_1 = require("../registry");
13
+ function openBrowser(url) {
14
+ try {
15
+ const cmd = process.platform === 'darwin' ? 'open'
16
+ : process.platform === 'win32' ? 'start'
17
+ : 'xdg-open';
18
+ (0, child_process_1.execSync)(`${cmd} ${url}`, { stdio: 'ignore' });
19
+ }
20
+ catch {
21
+ console.log(`Open this URL in your browser: ${url}`);
22
+ }
23
+ }
24
+ function run() {
25
+ const projectName = process.argv[3];
26
+ if (!projectName) {
27
+ console.error('Usage: waymark open PROJECT_NAME');
28
+ console.error('');
29
+ console.error('Example:');
30
+ console.error(' waymark open my-app');
31
+ console.error('');
32
+ console.error('Registered projects:');
33
+ (0, registry_1.cleanupStaleEntries)();
34
+ const projects = (0, registry_1.listProjects)('running');
35
+ if (projects.length > 0) {
36
+ projects.forEach(p => {
37
+ console.error(` - ${p.id} (port ${p.port})`);
38
+ });
39
+ }
40
+ else {
41
+ console.error(' (none running — run: waymark start)');
42
+ }
43
+ process.exit(1);
44
+ }
45
+ (0, registry_1.cleanupStaleEntries)();
46
+ const project = (0, registry_1.getProject)(projectName);
47
+ if (!project) {
48
+ console.error(`Project not found: ${projectName}`);
49
+ console.error('');
50
+ console.error('Available projects:');
51
+ const all = (0, registry_1.listProjects)();
52
+ if (all.length > 0) {
53
+ all.forEach(p => {
54
+ const status = p.status === 'running' ? '🟢' : '🔴';
55
+ console.error(` ${status} ${p.id} (port ${p.port})`);
56
+ });
57
+ }
58
+ else {
59
+ console.error(' (none registered)');
60
+ }
61
+ process.exit(1);
62
+ }
63
+ if (project.status !== 'running') {
64
+ console.log(`Project "${projectName}" is ${project.status}. Starting...`);
65
+ // Try to start it by running `waymark start` in its directory
66
+ const cwd = process.cwd();
67
+ try {
68
+ process.chdir(project.projectRoot);
69
+ (0, child_process_1.execSync)('npx @way_marks/cli start', { stdio: 'inherit' });
70
+ }
71
+ finally {
72
+ process.chdir(cwd);
73
+ }
74
+ }
75
+ const url = `http://localhost:${project.port}`;
76
+ console.log(`Opening dashboard: ${url}`);
77
+ openBrowser(url);
78
+ }
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ /**
3
+ * waymark pause — Pause a project (keep port allocation, but mark as paused)
4
+ *
5
+ * Usage: waymark pause [PROJECT_NAME]
6
+ *
7
+ * If PROJECT_NAME is omitted, pauses the current project.
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
+ exports.run = run;
44
+ const fs = __importStar(require("fs"));
45
+ const path = __importStar(require("path"));
46
+ const registry_1 = require("../registry");
47
+ function run() {
48
+ const projectName = process.argv[3];
49
+ const projectRoot = process.cwd();
50
+ const configPath = path.join(projectRoot, '.waymark', 'config.json');
51
+ if (!fs.existsSync(configPath)) {
52
+ console.error('No .waymark/config.json found. Run: waymark init');
53
+ process.exit(1);
54
+ }
55
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
56
+ const name = projectName || config.projectName || path.basename(projectRoot);
57
+ try {
58
+ const project = (0, registry_1.getProject)(name);
59
+ if (!project) {
60
+ console.error(`Project not found: ${name}`);
61
+ console.error('Hint: Use "waymark list" to see registered projects');
62
+ process.exit(1);
63
+ }
64
+ if (project.status === 'paused') {
65
+ console.log(`Project "${name}" is already paused.`);
66
+ process.exit(0);
67
+ }
68
+ (0, registry_1.updateProjectStatus)(name, 'paused');
69
+ console.log(`✓ Paused project: ${name}`);
70
+ console.log(` Port allocated: ${project.port} (reserved for this project)`);
71
+ console.log(` Resume with: waymark resume ${name}`);
72
+ }
73
+ catch (err) {
74
+ console.error(`Failed to pause project: ${err.message}`);
75
+ process.exit(1);
76
+ }
77
+ }
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ /**
3
+ * waymark resume — Resume a paused project
4
+ *
5
+ * Usage: waymark resume [PROJECT_NAME]
6
+ *
7
+ * If PROJECT_NAME is omitted, resumes the current project.
8
+ * The project keeps its previously allocated port.
9
+ */
10
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ var desc = Object.getOwnPropertyDescriptor(m, k);
13
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
14
+ desc = { enumerable: true, get: function() { return m[k]; } };
15
+ }
16
+ Object.defineProperty(o, k2, desc);
17
+ }) : (function(o, m, k, k2) {
18
+ if (k2 === undefined) k2 = k;
19
+ o[k2] = m[k];
20
+ }));
21
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
22
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
23
+ }) : function(o, v) {
24
+ o["default"] = v;
25
+ });
26
+ var __importStar = (this && this.__importStar) || (function () {
27
+ var ownKeys = function(o) {
28
+ ownKeys = Object.getOwnPropertyNames || function (o) {
29
+ var ar = [];
30
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
31
+ return ar;
32
+ };
33
+ return ownKeys(o);
34
+ };
35
+ return function (mod) {
36
+ if (mod && mod.__esModule) return mod;
37
+ var result = {};
38
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
39
+ __setModuleDefault(result, mod);
40
+ return result;
41
+ };
42
+ })();
43
+ Object.defineProperty(exports, "__esModule", { value: true });
44
+ exports.run = run;
45
+ const fs = __importStar(require("fs"));
46
+ const path = __importStar(require("path"));
47
+ const registry_1 = require("../registry");
48
+ function run() {
49
+ const projectName = process.argv[3];
50
+ const projectRoot = process.cwd();
51
+ const configPath = path.join(projectRoot, '.waymark', 'config.json');
52
+ if (!fs.existsSync(configPath)) {
53
+ console.error('No .waymark/config.json found. Run: waymark init');
54
+ process.exit(1);
55
+ }
56
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
57
+ const name = projectName || config.projectName || path.basename(projectRoot);
58
+ try {
59
+ const project = (0, registry_1.getProject)(name);
60
+ if (!project) {
61
+ console.error(`Project not found: ${name}`);
62
+ console.error('Hint: Use "waymark list" to see registered projects');
63
+ process.exit(1);
64
+ }
65
+ if (project.status !== 'paused') {
66
+ console.error(`Project "${name}" is not paused (current status: ${project.status})`);
67
+ process.exit(1);
68
+ }
69
+ (0, registry_1.updateProjectStatus)(name, 'running');
70
+ console.log(`✓ Resumed project: ${name}`);
71
+ console.log(` Dashboard: http://localhost:${project.port}`);
72
+ }
73
+ catch (err) {
74
+ console.error(`Failed to resume project: ${err.message}`);
75
+ process.exit(1);
76
+ }
77
+ }
@@ -38,6 +38,7 @@ const fs = __importStar(require("fs"));
38
38
  const path = __importStar(require("path"));
39
39
  const net = __importStar(require("net"));
40
40
  const child_process_1 = require("child_process");
41
+ const registry_1 = require("../registry");
41
42
  function resolveServerBin(name) {
42
43
  const file = name === 'mcp' ? 'mcp/server.js' : 'api/server.js';
43
44
  try {
@@ -76,20 +77,27 @@ function kebabCase(str) {
76
77
  .replace(/^-|-$/g, '');
77
78
  }
78
79
  function findAvailablePort(preferred) {
79
- return new Promise((resolve) => {
80
- const server = net.createServer();
81
- server.listen(preferred, () => {
82
- const port = server.address().port;
83
- server.close(() => resolve(port));
84
- });
85
- server.on('error', () => {
86
- if (preferred >= 3010) {
87
- console.error('No available ports found between 3001-3010. Stop other Waymark projects first.');
88
- process.exit(1);
89
- }
90
- resolve(findAvailablePort(preferred + 1));
80
+ // Try registry first (Phase 2+)
81
+ try {
82
+ return Promise.resolve((0, registry_1.findAvailablePort)(preferred));
83
+ }
84
+ catch {
85
+ // Fallback: old logic (Phase 1 compatibility)
86
+ return new Promise((resolve) => {
87
+ const server = net.createServer();
88
+ server.listen(preferred, () => {
89
+ const port = server.address().port;
90
+ server.close(() => resolve(port));
91
+ });
92
+ server.on('error', () => {
93
+ if (preferred >= 4000) {
94
+ console.error('No available ports found between 3001-4000. Stop other Waymark projects first.');
95
+ process.exit(1);
96
+ }
97
+ resolve(findAvailablePort(preferred + 1));
98
+ });
91
99
  });
92
- });
100
+ }
93
101
  }
94
102
  async function run() {
95
103
  const projectRoot = process.cwd();
@@ -157,6 +165,25 @@ async function run() {
157
165
  port,
158
166
  startedAt: new Date().toISOString()
159
167
  }, null, 2) + '\n');
168
+ // Register in global registry (Phase 2+)
169
+ try {
170
+ (0, registry_1.registerProject)({
171
+ id: projectName,
172
+ projectRoot,
173
+ projectName,
174
+ port,
175
+ mcp_pid: mcpProc.pid,
176
+ api_pid: apiProc.pid,
177
+ status: 'running',
178
+ startedAt: new Date().toISOString(),
179
+ hostname: require('os').hostname(),
180
+ user: process.env.USER || 'unknown',
181
+ });
182
+ }
183
+ catch (err) {
184
+ console.warn('Warning: failed to register in global registry:', err instanceof Error ? err.message : String(err));
185
+ // Continue anyway — registry is optional (backward compat)
186
+ }
160
187
  // Open browser after short delay for server startup
161
188
  setTimeout(() => {
162
189
  openBrowser(`http://localhost:${port}`);
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.run = run;
37
37
  const fs = __importStar(require("fs"));
38
38
  const path = __importStar(require("path"));
39
+ const registry_1 = require("../registry");
39
40
  function tryKill(pid) {
40
41
  try {
41
42
  process.kill(pid, 'SIGTERM');
@@ -66,6 +67,17 @@ function run() {
66
67
  fs.unlinkSync(pidFile);
67
68
  }
68
69
  catch { /* already gone */ }
70
+ // Unregister from global registry and release port (Phase 2+ & Phase 4)
71
+ try {
72
+ const project = (0, registry_1.findProjectByPath)(process.cwd());
73
+ if (project) {
74
+ (0, registry_1.releasePort)(project.id); // Phase 4: Release port for reuse
75
+ (0, registry_1.unregisterProject)(project.id);
76
+ }
77
+ }
78
+ catch (err) {
79
+ // ignore — registry cleanup is optional
80
+ }
69
81
  if (killedApi || killedMcp) {
70
82
  console.log('Waymark stopped.');
71
83
  }
package/dist/index.js CHANGED
@@ -11,20 +11,36 @@ switch (command) {
11
11
  case 'stop':
12
12
  require('./commands/stop').run();
13
13
  break;
14
+ case 'pause':
15
+ require('./commands/pause').run();
16
+ break;
17
+ case 'resume':
18
+ require('./commands/resume').run();
19
+ break;
14
20
  case 'status':
15
21
  require('./commands/status').run();
16
22
  break;
17
23
  case 'logs':
18
24
  require('./commands/logs').run();
19
25
  break;
26
+ case 'list':
27
+ require('./commands/list').run();
28
+ break;
29
+ case 'open':
30
+ require('./commands/open').run();
31
+ break;
20
32
  default:
21
- console.log('Usage: npx @way_marks/cli <init|start|stop|status|logs>');
33
+ console.log('Usage: npx @way_marks/cli <init|start|stop|pause|resume|status|logs|list|open>');
22
34
  console.log('');
23
35
  console.log('Commands:');
24
36
  console.log(' init Set up Waymark in the current project');
25
37
  console.log(' start Start the Waymark dashboard and MCP server');
26
38
  console.log(' stop Stop the running Waymark servers');
39
+ console.log(' pause Pause a project (keep port allocated)');
40
+ console.log(' resume Resume a paused project');
27
41
  console.log(' status Show current Waymark status and pending count');
28
42
  console.log(' logs Show recent action log');
43
+ console.log(' list List all registered Waymark projects');
44
+ console.log(' open Open a project dashboard or start it');
29
45
  process.exit(command ? 1 : 0);
30
46
  }
@@ -0,0 +1,275 @@
1
+ "use strict";
2
+ /**
3
+ * Waymark Project Registry
4
+ *
5
+ * Central registry for managing active Waymark projects.
6
+ * Stored at ~/.waymark/registry.json
7
+ *
8
+ * This enables:
9
+ * - `waymark list` — enumerate all active projects
10
+ * - `waymark open PROJECT` — quickly switch between projects
11
+ * - Port broker — central allocation system (Phase 2 future)
12
+ * - Project lifecycle tracking
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.getRegistry = getRegistry;
49
+ exports.registerProject = registerProject;
50
+ exports.unregisterProject = unregisterProject;
51
+ exports.updateProjectStatus = updateProjectStatus;
52
+ exports.releasePort = releasePort;
53
+ exports.getProject = getProject;
54
+ exports.findProjectByPath = findProjectByPath;
55
+ exports.listProjects = listProjects;
56
+ exports.findAvailablePort = findAvailablePort;
57
+ exports.cleanupStaleEntries = cleanupStaleEntries;
58
+ exports.garbageCollectRegistry = garbageCollectRegistry;
59
+ const fs = __importStar(require("fs"));
60
+ const path = __importStar(require("path"));
61
+ const os = __importStar(require("os"));
62
+ const REGISTRY_DIR = path.join(os.homedir(), '.waymark');
63
+ const REGISTRY_PATH = path.join(REGISTRY_DIR, 'registry.json');
64
+ /**
65
+ * Ensure registry directory and file exist
66
+ */
67
+ function ensureRegistry() {
68
+ if (!fs.existsSync(REGISTRY_DIR)) {
69
+ fs.mkdirSync(REGISTRY_DIR, { recursive: true });
70
+ }
71
+ if (!fs.existsSync(REGISTRY_PATH)) {
72
+ const empty = {
73
+ version: 1,
74
+ projects: {},
75
+ releasedPorts: [],
76
+ lastUpdated: new Date().toISOString(),
77
+ };
78
+ fs.writeFileSync(REGISTRY_PATH, JSON.stringify(empty, null, 2) + '\n');
79
+ return empty;
80
+ }
81
+ try {
82
+ const reg = JSON.parse(fs.readFileSync(REGISTRY_PATH, 'utf8'));
83
+ // Ensure releasedPorts exists for Phase 4
84
+ if (!reg.releasedPorts) {
85
+ reg.releasedPorts = [];
86
+ }
87
+ return reg;
88
+ }
89
+ catch (err) {
90
+ throw new Error(`Failed to read registry: ${err instanceof Error ? err.message : String(err)}`);
91
+ }
92
+ }
93
+ /**
94
+ * Get the current registry
95
+ */
96
+ function getRegistry() {
97
+ return ensureRegistry();
98
+ }
99
+ /**
100
+ * Register a project (called on `waymark start`)
101
+ */
102
+ function registerProject(entry) {
103
+ const registry = ensureRegistry();
104
+ entry.startedAt = entry.startedAt || new Date().toISOString();
105
+ entry.status = entry.status || 'running';
106
+ entry.hostname = entry.hostname || os.hostname();
107
+ entry.user = entry.user || process.env.USER || 'unknown';
108
+ registry.projects[entry.id] = entry;
109
+ registry.lastUpdated = new Date().toISOString();
110
+ fs.writeFileSync(REGISTRY_PATH, JSON.stringify(registry, null, 2) + '\n');
111
+ }
112
+ /**
113
+ * Unregister a project (called on `waymark stop`)
114
+ */
115
+ function unregisterProject(id) {
116
+ const registry = ensureRegistry();
117
+ delete registry.projects[id];
118
+ registry.lastUpdated = new Date().toISOString();
119
+ fs.writeFileSync(REGISTRY_PATH, JSON.stringify(registry, null, 2) + '\n');
120
+ }
121
+ /**
122
+ * Update project status
123
+ */
124
+ function updateProjectStatus(id, status) {
125
+ const registry = ensureRegistry();
126
+ const entry = registry.projects[id];
127
+ if (!entry) {
128
+ throw new Error(`Project not found: ${id}`);
129
+ }
130
+ entry.status = status;
131
+ if (status === 'stopped') {
132
+ entry.stoppedAt = new Date().toISOString();
133
+ }
134
+ else if (status === 'paused') {
135
+ entry.pausedAt = new Date().toISOString();
136
+ }
137
+ registry.lastUpdated = new Date().toISOString();
138
+ fs.writeFileSync(REGISTRY_PATH, JSON.stringify(registry, null, 2) + '\n');
139
+ }
140
+ /**
141
+ * Phase 4: Release a port when project stops (enables reuse)
142
+ */
143
+ function releasePort(projectId) {
144
+ const registry = ensureRegistry();
145
+ const entry = registry.projects[projectId];
146
+ if (!entry) {
147
+ return; // Project not found, nothing to do
148
+ }
149
+ // Add port to released queue for reuse
150
+ if (!registry.releasedPorts) {
151
+ registry.releasedPorts = [];
152
+ }
153
+ registry.releasedPorts.push(entry.port);
154
+ registry.lastUpdated = new Date().toISOString();
155
+ fs.writeFileSync(REGISTRY_PATH, JSON.stringify(registry, null, 2) + '\n');
156
+ }
157
+ /**
158
+ * Get a project by ID
159
+ */
160
+ function getProject(id) {
161
+ const registry = ensureRegistry();
162
+ return registry.projects[id] || null;
163
+ }
164
+ /**
165
+ * Find a project by directory path
166
+ */
167
+ function findProjectByPath(projectRoot) {
168
+ const registry = ensureRegistry();
169
+ const normalized = path.resolve(projectRoot);
170
+ for (const entry of Object.values(registry.projects)) {
171
+ if (path.resolve(entry.projectRoot) === normalized) {
172
+ return entry;
173
+ }
174
+ }
175
+ return null;
176
+ }
177
+ /**
178
+ * List all projects
179
+ */
180
+ function listProjects(filter) {
181
+ const registry = ensureRegistry();
182
+ const entries = Object.values(registry.projects);
183
+ if (filter) {
184
+ return entries.filter(e => e.status === filter);
185
+ }
186
+ return entries;
187
+ }
188
+ /**
189
+ * Find available port in the 3001-4000 range
190
+ * (Phase 2 future: will be replaced by central port broker)
191
+ */
192
+ function findAvailablePort(preferred = 3001) {
193
+ const registry = ensureRegistry();
194
+ const usedPorts = new Set(Object.values(registry.projects)
195
+ .filter(p => p.status === 'running')
196
+ .map(p => p.port));
197
+ // Phase 4: Check if we have a released port to reuse first
198
+ if (registry.releasedPorts && registry.releasedPorts.length > 0) {
199
+ const releasedPort = registry.releasedPorts.shift();
200
+ if (releasedPort && !usedPorts.has(releasedPort)) {
201
+ // Save updated registry with shifted port
202
+ registry.lastUpdated = new Date().toISOString();
203
+ fs.writeFileSync(REGISTRY_PATH, JSON.stringify(registry, null, 2) + '\n');
204
+ return releasedPort;
205
+ }
206
+ }
207
+ // Check if preferred is available
208
+ if (!usedPorts.has(preferred)) {
209
+ return preferred;
210
+ }
211
+ // Find next available
212
+ for (let port = 3001; port <= 4000; port++) {
213
+ if (!usedPorts.has(port)) {
214
+ return port;
215
+ }
216
+ }
217
+ throw new Error('No available ports in range 3001-4000');
218
+ }
219
+ /**
220
+ * Clean up stale entries (processes that are dead but not unregistered)
221
+ * Called periodically by waymark list/status commands
222
+ */
223
+ function cleanupStaleEntries() {
224
+ const registry = ensureRegistry();
225
+ let changed = false;
226
+ for (const [id, entry] of Object.entries(registry.projects)) {
227
+ if (entry.status === 'running' && entry.mcp_pid) {
228
+ try {
229
+ process.kill(entry.mcp_pid, 0); // Check if process exists
230
+ }
231
+ catch {
232
+ // Process is dead — mark as stopped
233
+ entry.status = 'stopped';
234
+ entry.stoppedAt = new Date().toISOString();
235
+ changed = true;
236
+ }
237
+ }
238
+ }
239
+ if (changed) {
240
+ registry.lastUpdated = new Date().toISOString();
241
+ fs.writeFileSync(REGISTRY_PATH, JSON.stringify(registry, null, 2) + '\n');
242
+ }
243
+ }
244
+ /**
245
+ * Phase 4: Garbage collect stale entries (cleanup old stopped projects)
246
+ * Removes entries older than daysOld that are marked as stopped
247
+ */
248
+ function garbageCollectRegistry(daysOld = 7) {
249
+ const registry = ensureRegistry();
250
+ const cutoffTime = new Date();
251
+ cutoffTime.setDate(cutoffTime.getDate() - daysOld);
252
+ let removed = 0;
253
+ const idsToRemove = [];
254
+ for (const [id, entry] of Object.entries(registry.projects)) {
255
+ if (entry.status === 'stopped' && entry.stoppedAt) {
256
+ const stoppedTime = new Date(entry.stoppedAt);
257
+ if (stoppedTime < cutoffTime) {
258
+ idsToRemove.push(id);
259
+ }
260
+ }
261
+ }
262
+ for (const id of idsToRemove) {
263
+ delete registry.projects[id];
264
+ removed++;
265
+ }
266
+ if (removed > 0) {
267
+ // Clean up released ports queue (keep only last 20)
268
+ if (registry.releasedPorts && registry.releasedPorts.length > 20) {
269
+ registry.releasedPorts = registry.releasedPorts.slice(-20);
270
+ }
271
+ registry.lastUpdated = new Date().toISOString();
272
+ fs.writeFileSync(REGISTRY_PATH, JSON.stringify(registry, null, 2) + '\n');
273
+ }
274
+ return removed;
275
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@way_marks/cli",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "description": "Control what AI agents can do in your codebase",
5
5
  "author": "Waymark <hello@waymarks.dev>",
6
6
  "license": "MIT",