@way_marks/cli 0.4.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,213 @@
1
+ # Waymark
2
+
3
+ **Control what AI agents can do in your codebase.**
4
+
5
+ Waymark sits between your team and any AI agent.
6
+ Every file action is intercepted, logged, and checked
7
+ against your policies before it executes.
8
+ Dangerous commands are blocked. Sensitive paths
9
+ require human approval. Everything is reversible.
10
+
11
+ ---
12
+
13
+ ## The Problem
14
+
15
+ AI agents like Claude Code are powerful.
16
+ They can also write to your .env, run rm -rf,
17
+ or modify your database schema without asking.
18
+
19
+ You find out after it happens.
20
+
21
+ ## The Solution
22
+
23
+ Waymark intercepts every action before it runs:
24
+
25
+ | Agent tries to... | Waymark does... |
26
+ |----------------------------|----------------------------------------|
27
+ | Write to .env | Blocks it instantly. Logged. |
28
+ | Run rm -rf | Blocks it instantly. Logged. |
29
+ | Pipe curl to bash | Blocks it instantly. Logged. |
30
+ | Modify src/db/schema.ts | Holds it. Asks for your approval. |
31
+ | Write to src/ | Allows it. Logged with full rollback. |
32
+ | Read any file | Logged with path and content snapshot. |
33
+
34
+ ---
35
+
36
+ ## Install
37
+
38
+ ```bash
39
+ cd your-project
40
+ npx @way_marks/cli init
41
+ npx @way_marks/cli start
42
+ ```
43
+
44
+ Restart Claude Code. Done.
45
+ Waymark is now active in this project.
46
+
47
+ ---
48
+
49
+ ## How It Works
50
+
51
+ ```
52
+ Your Prompt
53
+
54
+ Claude Code
55
+
56
+ Waymark MCP Server ← intercepts here
57
+
58
+ Policy Engine
59
+
60
+ allowed → executes + logged
61
+ blocked → stopped + logged
62
+ pending → held + approval required
63
+
64
+ Dashboard: http://localhost:3001
65
+ ```
66
+
67
+ ---
68
+
69
+ ## Dashboard
70
+
71
+ Open **http://localhost:3001** after running
72
+ `npx @way_marks/cli start`.
73
+
74
+ - See every agent action in real time
75
+ - Approve or reject pending actions
76
+ - Roll back any write with one click
77
+ - Filter by allowed / blocked / pending
78
+
79
+ ---
80
+
81
+ ## Configuration
82
+
83
+ Edit `waymark.config.json` in your project root:
84
+
85
+ ```json
86
+ {
87
+ "policies": {
88
+ "allowedPaths": [
89
+ "./src/**",
90
+ "./data/**",
91
+ "./README.md"
92
+ ],
93
+ "blockedPaths": [
94
+ "./.env",
95
+ "./.env.*",
96
+ "./package-lock.json",
97
+ "/etc/**"
98
+ ],
99
+ "blockedCommands": [
100
+ "rm -rf",
101
+ "DROP TABLE",
102
+ "regex:\\|\\s*bash",
103
+ "regex:\\$\\(curl"
104
+ ],
105
+ "requireApproval": [
106
+ "./src/db/**",
107
+ "./waymark.config.json"
108
+ ]
109
+ }
110
+ }
111
+ ```
112
+
113
+ ### Policy Rules
114
+
115
+ **allowedPaths** — Agent can read and write these.
116
+ Supports glob patterns.
117
+
118
+ **blockedPaths** — Agent can never touch these.
119
+ Takes priority over allowedPaths.
120
+
121
+ **blockedCommands** — Bash commands containing
122
+ these strings are blocked. Prefix with `regex:`
123
+ for pattern matching.
124
+
125
+ **requireApproval** — Actions on these paths are
126
+ held until a human approves from the dashboard.
127
+
128
+ ---
129
+
130
+ ## CLI Commands
131
+
132
+ ```bash
133
+ npx @way_marks/cli init # Set up Waymark in current project
134
+ npx @way_marks/cli start # Start dashboard + MCP server (background)
135
+ npx @way_marks/cli stop # Stop the running servers
136
+ npx @way_marks/cli status # Check if server is running
137
+ npx @way_marks/cli logs # View recent actions in terminal
138
+ npx @way_marks/cli logs --pending # Show only pending actions
139
+ npx @way_marks/cli logs --blocked # Show only blocked actions
140
+ ```
141
+
142
+ ---
143
+
144
+ ## Slack Notifications
145
+
146
+ Get notified when an agent action needs approval:
147
+
148
+ ```bash
149
+ # Add to .env in your project
150
+ WAYMARK_SLACK_WEBHOOK_URL=https://hooks.slack.com/...
151
+ WAYMARK_SLACK_CHANNEL=#engineering
152
+ WAYMARK_BASE_URL=http://localhost:3001
153
+ ```
154
+
155
+ Create a Slack webhook at:
156
+ api.slack.com/apps → Incoming Webhooks
157
+
158
+ ---
159
+
160
+ ## Works With
161
+
162
+ - **Claude Code** — native MCP integration,
163
+ zero configuration after init
164
+ - **Any MCP-compatible agent** — register
165
+ the Waymark MCP server in your agent config
166
+ - More integrations coming
167
+
168
+ ---
169
+
170
+ ## Requirements
171
+
172
+ - Node.js 18 or higher
173
+ - Claude Code (for MCP integration)
174
+ - macOS or Linux (Windows support coming)
175
+
176
+ ---
177
+
178
+ ## Roadmap
179
+
180
+ - [ ] Team approval routing
181
+ (assign approvals to specific teammates)
182
+ - [ ] Session-level rollback
183
+ (undo an entire agent run at once)
184
+ - [ ] CLI agent wrapping
185
+ (waymark run <any-agent-command>)
186
+ - [ ] Proxy mode
187
+ (drop-in for any OpenAI-compatible agent)
188
+ - [ ] Email notifications
189
+ - [ ] Windows support
190
+
191
+ ---
192
+
193
+ ## Contributing
194
+
195
+ Waymark is MIT licensed and open to contributions.
196
+
197
+ 1. Fork the repo
198
+ 2. Create a feature branch
199
+ 3. Open a pull request
200
+
201
+ Please open an issue before starting large changes.
202
+
203
+ ---
204
+
205
+ ## License
206
+
207
+ MIT — see [LICENSE](LICENSE)
208
+
209
+ ---
210
+
211
+ Built for developers who want to use AI agents
212
+ seriously — without giving them unsupervised
213
+ access to production systems.
@@ -0,0 +1,325 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.run = run;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const os = __importStar(require("os"));
40
+ const readline = __importStar(require("readline"));
41
+ const child_process_1 = require("child_process");
42
+ const DEFAULT_CONFIG = {
43
+ version: '1',
44
+ policies: {
45
+ allowedPaths: [
46
+ './src/**',
47
+ './data/**',
48
+ './README.md',
49
+ './CLAUDE.md'
50
+ ],
51
+ blockedPaths: [
52
+ './.env',
53
+ './.env.*',
54
+ './package-lock.json',
55
+ '/etc/**',
56
+ '/usr/**'
57
+ ],
58
+ blockedCommands: [
59
+ 'rm -rf',
60
+ 'DROP TABLE',
61
+ 'DROP DATABASE',
62
+ 'chmod 777',
63
+ 'regex:\\|\\s*bash',
64
+ 'regex:\\|\\s*sh\\b',
65
+ 'regex:\\$\\(curl',
66
+ 'regex:\\$\\(wget',
67
+ 'wget '
68
+ ],
69
+ requireApproval: [
70
+ './src/db/**',
71
+ './waymark.config.json'
72
+ ],
73
+ maxBashOutputBytes: 10000
74
+ }
75
+ };
76
+ const WAYMARK_MARKER = '<!-- waymark -->';
77
+ function kebabCase(str) {
78
+ return str
79
+ .toLowerCase()
80
+ .replace(/[\s_]+/g, '-')
81
+ .replace(/[^a-z0-9-]/g, '')
82
+ .replace(/-+/g, '-')
83
+ .replace(/^-|-$/g, '');
84
+ }
85
+ function generateClaudeMd(projectName, port) {
86
+ return `---
87
+ # ⚠️ WAYMARK ACTIVE — MANDATORY INSTRUCTIONS
88
+
89
+ ## You are operating under Waymark agent control
90
+
91
+ Waymark is active in this project (${projectName}).
92
+ All file and shell operations MUST go through
93
+ Waymark MCP tools. No exceptions.
94
+
95
+ ## MANDATORY tool usage
96
+
97
+ You MUST replace ALL built-in file and shell
98
+ operations with Waymark MCP equivalents:
99
+
100
+ | Operation | Built-in (DO NOT USE) | Waymark (ALWAYS USE) |
101
+ |------------------------|-----------------------|----------------------|
102
+ | Write or edit a file | write_file | mcp__waymark-${projectName}__write_file |
103
+ | Read a file | read_file | mcp__waymark-${projectName}__read_file |
104
+ | Run shell commands | bash / run_command | mcp__waymark-${projectName}__bash |
105
+
106
+ This applies to ALL tasks in this project —
107
+ refactoring, creating files, running tests,
108
+ installing packages, git operations, everything.
109
+
110
+ ## What Waymark does with each action
111
+
112
+ - Allowed paths → executes normally, logged
113
+ - Blocked paths → stopped, you will see why
114
+ - Sensitive paths → held for human approval,
115
+ do not retry, tell the user to check dashboard
116
+
117
+ ## If a Waymark tool returns a block decision
118
+
119
+ Accept the block. Do not attempt to work around it
120
+ using built-in tools. Tell the user what was blocked
121
+ and why. Suggest they update waymark.config.json
122
+ if the block was unintended.
123
+
124
+ ## If Waymark MCP tools are unavailable
125
+
126
+ Do NOT fall back to built-in file tools.
127
+ Stop and tell the user:
128
+
129
+ "Waymark MCP server is not running for this project.
130
+ Please run in your terminal:
131
+ npx @way_marks/cli start
132
+
133
+ Then restart Claude Code and try again."
134
+
135
+ ## Dashboard
136
+
137
+ All actions are visible at: http://localhost:${port}
138
+ Approve pending actions there.
139
+ Roll back any write there.
140
+
141
+ ## This file was generated by Waymark
142
+ Do not delete or modify this file.
143
+ It controls how Claude Code behaves in this project.
144
+ ---
145
+ `;
146
+ }
147
+ function prompt(question) {
148
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
149
+ return new Promise(resolve => {
150
+ rl.question(question, answer => { rl.close(); resolve(answer.trim()); });
151
+ });
152
+ }
153
+ function getClaudeDesktopConfigPath() {
154
+ const platform = process.platform;
155
+ if (platform === 'darwin') {
156
+ return path.join(os.homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
157
+ }
158
+ else if (platform === 'win32') {
159
+ return path.join(process.env.APPDATA || os.homedir(), 'Claude', 'claude_desktop_config.json');
160
+ }
161
+ else {
162
+ return path.join(os.homedir(), '.config', 'Claude', 'claude_desktop_config.json');
163
+ }
164
+ }
165
+ function resolveServerBin() {
166
+ try {
167
+ return require.resolve('@way_marks/server/dist/mcp/server.js');
168
+ }
169
+ catch {
170
+ return path.resolve(__dirname, '../../../server/dist/mcp/server.js');
171
+ }
172
+ }
173
+ async function run() {
174
+ const projectRoot = process.cwd();
175
+ const projectName = kebabCase(path.basename(projectRoot));
176
+ const mcpKey = `waymark-${projectName}`;
177
+ const dbPath = path.join(projectRoot, '.waymark', 'waymark.db');
178
+ const defaultPort = 3001;
179
+ console.log(`Initializing Waymark in: ${projectRoot}`);
180
+ // Step 1 — Detect project
181
+ const hasPackageJson = fs.existsSync(path.join(projectRoot, 'package.json'));
182
+ const hasGit = fs.existsSync(path.join(projectRoot, '.git'));
183
+ if (!hasPackageJson && !hasGit) {
184
+ console.warn('Warning: No package.json or .git found. Continuing anyway.');
185
+ }
186
+ // Step 2 — Install @way_marks/server (skip if already resolvable or in monorepo)
187
+ let serverBin;
188
+ try {
189
+ serverBin = require.resolve('@way_marks/server/dist/mcp/server.js');
190
+ console.log('✓ @way_marks/server already installed');
191
+ }
192
+ catch {
193
+ const monorepoFallback = path.resolve(__dirname, '../../../server/dist/mcp/server.js');
194
+ if (fs.existsSync(monorepoFallback)) {
195
+ serverBin = monorepoFallback;
196
+ console.log('✓ Using local @way_marks/server (monorepo)');
197
+ }
198
+ else {
199
+ console.log('Installing @way_marks/server...');
200
+ const result = (0, child_process_1.spawnSync)('npm', ['install', '--save-dev', '@way_marks/server'], {
201
+ stdio: 'inherit',
202
+ cwd: projectRoot
203
+ });
204
+ if (result.status !== 0) {
205
+ console.error('Failed to install @way_marks/server');
206
+ process.exit(1);
207
+ }
208
+ serverBin = resolveServerBin();
209
+ }
210
+ }
211
+ // Step 3 — Create waymark.config.json
212
+ const configPath = path.join(projectRoot, 'waymark.config.json');
213
+ if (fs.existsSync(configPath)) {
214
+ const answer = await prompt('waymark.config.json exists. Overwrite? (y/N) ');
215
+ if (answer.toLowerCase() !== 'y') {
216
+ console.log('Keeping existing waymark.config.json');
217
+ }
218
+ else {
219
+ fs.writeFileSync(configPath, JSON.stringify(DEFAULT_CONFIG, null, 2) + '\n');
220
+ console.log('✓ Created waymark.config.json');
221
+ }
222
+ }
223
+ else {
224
+ fs.writeFileSync(configPath, JSON.stringify(DEFAULT_CONFIG, null, 2) + '\n');
225
+ console.log('✓ Created waymark.config.json');
226
+ }
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');
234
+ }
235
+ else {
236
+ fs.appendFileSync(claudeMdPath, `\n${WAYMARK_MARKER}\n${claudeMdContent}`);
237
+ console.log('✓ Appended Waymark section to CLAUDE.md');
238
+ }
239
+ }
240
+ else {
241
+ fs.writeFileSync(claudeMdPath, `${WAYMARK_MARKER}\n${claudeMdContent}`);
242
+ console.log('✓ Created CLAUDE.md — Claude Code will now use Waymark automatically');
243
+ }
244
+ // Step 5 — Update .gitignore
245
+ const gitignorePath = path.join(projectRoot, '.gitignore');
246
+ const gitignoreContent = fs.existsSync(gitignorePath)
247
+ ? fs.readFileSync(gitignorePath, 'utf8')
248
+ : '';
249
+ const linesToAdd = ['.waymark/', 'waymark.db', 'data/waymark.db']
250
+ .filter(line => !gitignoreContent.includes(line));
251
+ if (linesToAdd.length > 0) {
252
+ const section = '\n# Waymark\n' + linesToAdd.join('\n') + '\n';
253
+ fs.appendFileSync(gitignorePath, section);
254
+ console.log('✓ Updated .gitignore');
255
+ }
256
+ else {
257
+ console.log('✓ .gitignore already up to date');
258
+ }
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}`);
282
+ }
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`);
294
+ }
295
+ catch (err) {
296
+ console.warn(`Warning: Could not update .mcp.json: ${err.message}`);
297
+ }
298
+ // Step 7 — Success summary
299
+ const col = 43;
300
+ const pad = (s) => s + ' '.repeat(Math.max(0, col - 2 - s.length));
301
+ console.log('');
302
+ console.log('┌' + '─'.repeat(col) + '┐');
303
+ console.log(`│ ${pad('✅ Waymark initialized')} │`);
304
+ console.log(`│ ${pad('')} │`);
305
+ console.log(`│ ${pad(`Project: ${projectName}`)} │`);
306
+ console.log(`│ ${pad('Database: .waymark/waymark.db')} │`);
307
+ console.log(`│ ${pad(`MCP key: ${mcpKey}`)} │`);
308
+ console.log(`│ ${pad('')} │`);
309
+ console.log(`│ ${pad('Files created:')} │`);
310
+ console.log(`│ ${pad(' waymark.config.json')} │`);
311
+ console.log(`│ ${pad(' CLAUDE.md')} │`);
312
+ console.log(`│ ${pad(' .waymark/ (gitignored)')} │`);
313
+ 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)')} │`);
320
+ 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')} │`);
324
+ console.log('└' + '─'.repeat(col) + '┘');
325
+ }
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.run = run;
4
+ const BASE = 'http://localhost:3001';
5
+ function parseIso(iso) {
6
+ const normalized = iso.includes('T') ? iso : iso.replace(' ', 'T');
7
+ const withZ = normalized.endsWith('Z') ? normalized : normalized + 'Z';
8
+ return new Date(withZ);
9
+ }
10
+ function relativeTime(iso) {
11
+ const diffMs = Date.now() - parseIso(iso).getTime();
12
+ const mins = Math.floor(diffMs / 60000);
13
+ if (mins < 1)
14
+ return 'just now';
15
+ if (mins < 60)
16
+ return `${mins}m ago`;
17
+ const hours = Math.floor(mins / 60);
18
+ if (hours < 24)
19
+ return `${hours}h ago`;
20
+ return `${Math.floor(hours / 24)}d ago`;
21
+ }
22
+ function truncate(str, len) {
23
+ if (!str)
24
+ return '—';
25
+ const s = str.split('/').pop() || str;
26
+ return s.length > len ? s.slice(0, len - 1) + '…' : s;
27
+ }
28
+ function pad(str, len) {
29
+ return str.padEnd(len).slice(0, len);
30
+ }
31
+ async function run() {
32
+ const args = process.argv.slice(3);
33
+ const limitIdx = args.indexOf('--limit');
34
+ const limit = limitIdx !== -1 ? parseInt(args[limitIdx + 1], 10) || 20 : 20;
35
+ const pendingOnly = args.includes('--pending');
36
+ const blockedOnly = args.includes('--blocked');
37
+ let rows;
38
+ try {
39
+ const res = await fetch(`${BASE}/api/actions`);
40
+ if (!res.ok)
41
+ throw new Error('Bad response');
42
+ rows = await res.json();
43
+ }
44
+ catch {
45
+ console.log('Waymark is not running. Start with: npx @way_marks/cli start');
46
+ return;
47
+ }
48
+ if (pendingOnly)
49
+ rows = rows.filter((r) => r.status === 'pending');
50
+ if (blockedOnly)
51
+ rows = rows.filter((r) => r.status === 'blocked');
52
+ rows = rows.slice(0, limit);
53
+ if (rows.length === 0) {
54
+ console.log('No actions found.');
55
+ return;
56
+ }
57
+ console.log(pad('Time', 10) + ' ' +
58
+ pad('Tool', 12) + ' ' +
59
+ pad('Path / Command', 40) + ' ' +
60
+ pad('Decision', 10) + ' ' +
61
+ 'Status');
62
+ console.log('─'.repeat(86));
63
+ for (const r of rows) {
64
+ const target = r.target_path
65
+ ? truncate(r.target_path, 40)
66
+ : (() => {
67
+ try {
68
+ return truncate(JSON.parse(r.input_payload || '{}').command || '—', 40);
69
+ }
70
+ catch {
71
+ return '—';
72
+ }
73
+ })();
74
+ console.log(pad(relativeTime(r.created_at), 10) + ' ' +
75
+ pad(r.tool_name, 12) + ' ' +
76
+ pad(target, 40) + ' ' +
77
+ pad(r.decision || '—', 10) + ' ' +
78
+ r.status);
79
+ }
80
+ }
@@ -0,0 +1,168 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.run = run;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const net = __importStar(require("net"));
40
+ const child_process_1 = require("child_process");
41
+ function resolveServerBin(name) {
42
+ const file = name === 'mcp' ? 'mcp/server.js' : 'api/server.js';
43
+ try {
44
+ return require.resolve(`@way_marks/server/dist/${file}`);
45
+ }
46
+ catch {
47
+ return path.resolve(__dirname, `../../../server/dist/${file}`);
48
+ }
49
+ }
50
+ function openBrowser(url) {
51
+ try {
52
+ const cmd = process.platform === 'darwin' ? 'open'
53
+ : process.platform === 'win32' ? 'start'
54
+ : 'xdg-open';
55
+ (0, child_process_1.execSync)(`${cmd} ${url}`, { stdio: 'ignore' });
56
+ }
57
+ catch {
58
+ // ignore — browser open is best-effort
59
+ }
60
+ }
61
+ function isAlive(pid) {
62
+ try {
63
+ process.kill(pid, 0);
64
+ return true;
65
+ }
66
+ catch {
67
+ return false;
68
+ }
69
+ }
70
+ function kebabCase(str) {
71
+ return str
72
+ .toLowerCase()
73
+ .replace(/[\s_]+/g, '-')
74
+ .replace(/[^a-z0-9-]/g, '')
75
+ .replace(/-+/g, '-')
76
+ .replace(/^-|-$/g, '');
77
+ }
78
+ 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));
91
+ });
92
+ });
93
+ }
94
+ async function run() {
95
+ const projectRoot = process.cwd();
96
+ const configPath = path.join(projectRoot, 'waymark.config.json');
97
+ const waymarkDir = path.join(projectRoot, '.waymark');
98
+ const pidFile = path.join(waymarkDir, 'waymark.pid');
99
+ if (!fs.existsSync(configPath)) {
100
+ console.error('waymark.config.json not found. Run: npx @way_marks/cli init');
101
+ process.exit(1);
102
+ }
103
+ // Guard: already running
104
+ if (fs.existsSync(pidFile)) {
105
+ try {
106
+ const saved = JSON.parse(fs.readFileSync(pidFile, 'utf8'));
107
+ if (isAlive(saved.api) || isAlive(saved.mcp)) {
108
+ const port = saved.port || 3001;
109
+ console.log('Waymark is already running.');
110
+ console.log(`Dashboard: http://localhost:${port}`);
111
+ console.log('Run "npx @way_marks/cli stop" to stop it.');
112
+ process.exit(0);
113
+ }
114
+ }
115
+ catch {
116
+ // stale/corrupt PID file — continue to start
117
+ }
118
+ }
119
+ const port = await findAvailablePort(3001);
120
+ const dbPath = path.join(projectRoot, '.waymark', 'waymark.db');
121
+ const projectName = kebabCase(path.basename(projectRoot));
122
+ const nodeBin = process.execPath;
123
+ const mcpBin = resolveServerBin('mcp');
124
+ const apiBin = resolveServerBin('api');
125
+ const env = {
126
+ ...process.env,
127
+ WAYMARK_PROJECT_ROOT: projectRoot,
128
+ WAYMARK_DB_PATH: dbPath,
129
+ WAYMARK_PORT: String(port),
130
+ };
131
+ const apiProc = (0, child_process_1.spawn)(nodeBin, [apiBin], {
132
+ env,
133
+ stdio: 'ignore',
134
+ detached: true
135
+ });
136
+ const mcpProc = (0, child_process_1.spawn)(nodeBin, [
137
+ mcpBin,
138
+ '--project-root', projectRoot,
139
+ '--db-path', dbPath,
140
+ '--port', String(port),
141
+ ], {
142
+ env,
143
+ stdio: 'ignore',
144
+ detached: true
145
+ });
146
+ apiProc.unref();
147
+ mcpProc.unref();
148
+ // Ensure .waymark directory exists
149
+ if (!fs.existsSync(waymarkDir))
150
+ fs.mkdirSync(waymarkDir, { recursive: true });
151
+ // Write .waymark/config.json
152
+ fs.writeFileSync(path.join(waymarkDir, 'config.json'), JSON.stringify({ port, projectRoot, projectName, startedAt: new Date().toISOString() }, null, 2) + '\n');
153
+ // Write PID file
154
+ fs.writeFileSync(pidFile, JSON.stringify({
155
+ api: apiProc.pid,
156
+ mcp: mcpProc.pid,
157
+ port,
158
+ startedAt: new Date().toISOString()
159
+ }, null, 2) + '\n');
160
+ // Open browser after short delay for server startup
161
+ setTimeout(() => {
162
+ openBrowser(`http://localhost:${port}`);
163
+ console.log('Waymark started (background)');
164
+ console.log(`Dashboard: http://localhost:${port}`);
165
+ console.log('MCP server: active (stdio)');
166
+ console.log('Run "npx @way_marks/cli stop" to stop.');
167
+ }, 1500);
168
+ }
@@ -0,0 +1,89 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.run = run;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ async function run() {
40
+ const projectRoot = process.cwd();
41
+ const configPath = path.join(projectRoot, '.waymark', 'config.json');
42
+ if (!fs.existsSync(configPath)) {
43
+ console.log('Waymark not initialized in this directory.');
44
+ console.log('Run: npx @way_marks/cli init');
45
+ return;
46
+ }
47
+ let waymarkConfig;
48
+ try {
49
+ waymarkConfig = JSON.parse(fs.readFileSync(configPath, 'utf8'));
50
+ }
51
+ catch {
52
+ console.log('Waymark config is corrupt. Re-run: npx @way_marks/cli init');
53
+ return;
54
+ }
55
+ const { port, projectName, startedAt } = waymarkConfig;
56
+ const mcpKey = `waymark-${projectName}`;
57
+ const base = `http://localhost:${port}`;
58
+ // Check if server is running (2s timeout)
59
+ let running = false;
60
+ let pending = 0;
61
+ try {
62
+ const controller = new AbortController();
63
+ const timer = setTimeout(() => controller.abort(), 2000);
64
+ const res = await fetch(`${base}/api/actions?count=true`, { signal: controller.signal });
65
+ clearTimeout(timer);
66
+ if (res.ok) {
67
+ running = true;
68
+ const data = await res.json();
69
+ pending = data.count;
70
+ }
71
+ }
72
+ catch { /* not running */ }
73
+ console.log('Waymark — Project Status');
74
+ console.log('─'.repeat(35));
75
+ console.log(`Project: ${projectName}`);
76
+ console.log(`Root: ${projectRoot}`);
77
+ console.log(`Database: .waymark/waymark.db`);
78
+ console.log(`Port: ${port}`);
79
+ console.log(`Dashboard: ${base}`);
80
+ console.log(`MCP key: ${mcpKey}`);
81
+ console.log('─'.repeat(35));
82
+ console.log(`Server: ${running ? 'running ✅' : 'not running ❌'}`);
83
+ if (running)
84
+ console.log(`Pending: ${pending} actions`);
85
+ if (!running)
86
+ console.log(`Start with: npx @way_marks/cli start`);
87
+ if (startedAt)
88
+ console.log(`Started: ${startedAt}`);
89
+ }
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.run = run;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ function tryKill(pid) {
40
+ try {
41
+ process.kill(pid, 'SIGTERM');
42
+ return true;
43
+ }
44
+ catch {
45
+ return false;
46
+ }
47
+ }
48
+ function run() {
49
+ const pidFile = path.join(process.cwd(), '.waymark', 'waymark.pid');
50
+ if (!fs.existsSync(pidFile)) {
51
+ console.log('Waymark is not running.');
52
+ return;
53
+ }
54
+ let saved;
55
+ try {
56
+ saved = JSON.parse(fs.readFileSync(pidFile, 'utf8'));
57
+ }
58
+ catch {
59
+ fs.unlinkSync(pidFile);
60
+ console.log('Waymark is not running.');
61
+ return;
62
+ }
63
+ const killedApi = tryKill(saved.api);
64
+ const killedMcp = tryKill(saved.mcp);
65
+ try {
66
+ fs.unlinkSync(pidFile);
67
+ }
68
+ catch { /* already gone */ }
69
+ if (killedApi || killedMcp) {
70
+ console.log('Waymark stopped.');
71
+ }
72
+ else {
73
+ console.log('Waymark was not running (stale PID file removed).');
74
+ }
75
+ }
package/dist/index.js ADDED
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ const command = process.argv[2];
4
+ switch (command) {
5
+ case 'init':
6
+ require('./commands/init').run();
7
+ break;
8
+ case 'start':
9
+ require('./commands/start').run();
10
+ break;
11
+ case 'stop':
12
+ require('./commands/stop').run();
13
+ break;
14
+ case 'status':
15
+ require('./commands/status').run();
16
+ break;
17
+ case 'logs':
18
+ require('./commands/logs').run();
19
+ break;
20
+ default:
21
+ console.log('Usage: npx @way_marks/cli <init|start|stop|status|logs>');
22
+ console.log('');
23
+ console.log('Commands:');
24
+ console.log(' init Set up Waymark in the current project');
25
+ console.log(' start Start the Waymark dashboard and MCP server');
26
+ console.log(' stop Stop the running Waymark servers');
27
+ console.log(' status Show current Waymark status and pending count');
28
+ console.log(' logs Show recent action log');
29
+ process.exit(command ? 1 : 0);
30
+ }
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@way_marks/cli",
3
+ "version": "0.4.0",
4
+ "description": "Control what AI agents can do in your codebase",
5
+ "author": "Waymark <hello@waymarks.dev>",
6
+ "license": "MIT",
7
+ "homepage": "https://github.com/waymarks/waymark",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/waymarks/waymark.git"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/waymarks/waymark/issues"
14
+ },
15
+ "keywords": [
16
+ "ai-agents",
17
+ "claude-code",
18
+ "mcp",
19
+ "security",
20
+ "developer-tools",
21
+ "llm",
22
+ "agent-control"
23
+ ],
24
+ "bin": {
25
+ "waymark": "./dist/index.js"
26
+ },
27
+ "files": [
28
+ "dist/**"
29
+ ],
30
+ "scripts": {
31
+ "build": "tsc"
32
+ },
33
+ "dependencies": {
34
+ "@way_marks/server": "0.4.0"
35
+ },
36
+ "devDependencies": {
37
+ "@types/node": "^20.11.5",
38
+ "typescript": "^5.3.3"
39
+ }
40
+ }