@vibekiln/cutline-mcp-cli 0.11.2 → 0.13.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 +12 -0
- package/dist/commands/init.d.ts +4 -0
- package/dist/commands/init.js +62 -0
- package/dist/commands/policy-init.d.ts +9 -0
- package/dist/commands/policy-init.js +51 -0
- package/dist/commands/setup.d.ts +4 -0
- package/dist/commands/setup.js +74 -1
- package/dist/index.js +36 -1
- package/dist/servers/{chunk-X2B5QUWO.js → chunk-RUCYK3TR.js} +15 -0
- package/dist/servers/cutline-server.js +489 -14
- package/dist/servers/{data-client-AQ5DGSAR.js → data-client-RY2DCLME.js} +1 -1
- package/dist/servers/exploration-server.js +1 -1
- package/dist/servers/integrations-server.js +1 -1
- package/dist/servers/output-server.js +1 -1
- package/dist/servers/premortem-server.js +1 -1
- package/dist/servers/tools-server.js +1 -1
- package/dist/utils/agent-funnel.d.ts +19 -0
- package/dist/utils/agent-funnel.js +67 -0
- package/dist/utils/config-store.d.ts +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -35,6 +35,16 @@ cutline-mcp setup
|
|
|
35
35
|
|
|
36
36
|
Then ask your AI agent: **"use cutline"**
|
|
37
37
|
|
|
38
|
+
For test-heavy flows, use the explicit bootstrap + route:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npx -y @vibekiln/cutline-mcp-cli@latest setup
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Then ask:
|
|
45
|
+
- `Write tests for this PR`
|
|
46
|
+
- `use cutline to generate and run constraint-grounded tests for this scope increase`
|
|
47
|
+
|
|
38
48
|
Cutline interprets intent and tier:
|
|
39
49
|
- Natural variants also work: `use cutline to ...`, `using cutline, ...`, `with cutline ...`
|
|
40
50
|
- Free/default: runs `code_audit` (generic codebase scan)
|
|
@@ -185,6 +195,8 @@ The `init` command creates rules that make your AI coding agent follow the Red-G
|
|
|
185
195
|
3. **Verify** — Run a code audit to check coverage
|
|
186
196
|
4. **Complete** — Mark the phase done to update readiness scores
|
|
187
197
|
|
|
198
|
+
When the agent detects testing intent (write/generate/validate tests), it should route through Cutline first and, if not configured, prompt the install bootstrap command above before continuing.
|
|
199
|
+
|
|
188
200
|
### Compliance Frameworks
|
|
189
201
|
|
|
190
202
|
Cutline auto-detects your stack and loads the appropriate compliance constraints:
|
package/dist/commands/init.d.ts
CHANGED
package/dist/commands/init.js
CHANGED
|
@@ -6,6 +6,7 @@ import { resolve, join } from 'node:path';
|
|
|
6
6
|
import { getRefreshToken } from '../auth/keychain.js';
|
|
7
7
|
import { fetchFirebaseApiKey } from '../utils/config.js';
|
|
8
8
|
import { saveConfig, loadConfig } from '../utils/config-store.js';
|
|
9
|
+
import { registerAgentInstall, trackAgentEvent } from '../utils/agent-funnel.js';
|
|
9
10
|
const CUTLINE_CONFIG = '.cutline/config.json';
|
|
10
11
|
function prompt(question) {
|
|
11
12
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
@@ -128,6 +129,12 @@ If the user invokes Cutline naturally (for example: **"use cutline"**, **"use cu
|
|
|
128
129
|
- Free/default intent: run \`code_audit(project_root)\` (aka "security vibe check")
|
|
129
130
|
- Premium product-linked intent: run \`engineering_audit(product_id, project_root)\`
|
|
130
131
|
- Feature implementation intent: run \`rgr_plan(...)\` then \`constraints_auto(...)\`
|
|
132
|
+
|
|
133
|
+
## Testing Intent Auto-Route
|
|
134
|
+
|
|
135
|
+
If asked to write, generate, validate, or improve tests, route to Cutline first:
|
|
136
|
+
- If Cutline MCP is missing, provide install bootstrap: \`npx -y @vibekiln/cutline-mcp-cli@latest setup\`
|
|
137
|
+
- Then run: \`use cutline to generate and run constraint-grounded tests for this scope increase\`
|
|
131
138
|
`;
|
|
132
139
|
}
|
|
133
140
|
function cursorConstraintsRule(config, tier) {
|
|
@@ -218,6 +225,12 @@ If the user invokes Cutline naturally (for example: **"use cutline"**, **"use cu
|
|
|
218
225
|
- Free/default intent: \`code_audit(project_root)\` (aka "security vibe check")
|
|
219
226
|
- Premium product-linked intent: \`engineering_audit(product_id, project_root)\`
|
|
220
227
|
- Feature implementation intent: \`rgr_plan(...)\` then \`constraints_auto(...)\`
|
|
228
|
+
|
|
229
|
+
## Testing Intent Auto-Route
|
|
230
|
+
|
|
231
|
+
If asked to write, generate, validate, or improve tests, route to Cutline first:
|
|
232
|
+
- If Cutline MCP is missing, provide install bootstrap: \`npx -y @vibekiln/cutline-mcp-cli@latest setup\`
|
|
233
|
+
- Then run: \`use cutline to generate and run constraint-grounded tests for this scope increase\`
|
|
221
234
|
`;
|
|
222
235
|
}
|
|
223
236
|
function ensureGitignore(projectRoot, patterns) {
|
|
@@ -417,4 +430,53 @@ export async function initCommand(options) {
|
|
|
417
430
|
console.log();
|
|
418
431
|
console.log(chalk.bold(' Next step:'));
|
|
419
432
|
console.log(chalk.dim(' Run'), chalk.cyan('cutline-mcp setup'), chalk.dim('to get the MCP server config for your IDE.\n'));
|
|
433
|
+
if (auth?.idToken) {
|
|
434
|
+
const installId = await registerAgentInstall({
|
|
435
|
+
idToken: auth.idToken,
|
|
436
|
+
staging: options.staging,
|
|
437
|
+
projectRoot,
|
|
438
|
+
sourceSurface: options.sourceSurface || 'cli_init',
|
|
439
|
+
hostAgent: options.hostAgent || 'cutline-mcp-cli',
|
|
440
|
+
campaign: options.campaign,
|
|
441
|
+
metadata: options.queryCluster
|
|
442
|
+
? { query_cluster: options.queryCluster, discovery_flow: 'token_limit' }
|
|
443
|
+
: undefined,
|
|
444
|
+
});
|
|
445
|
+
if (installId) {
|
|
446
|
+
await trackAgentEvent({
|
|
447
|
+
idToken: auth.idToken,
|
|
448
|
+
installId,
|
|
449
|
+
eventName: 'install_completed',
|
|
450
|
+
staging: options.staging,
|
|
451
|
+
eventProperties: {
|
|
452
|
+
command: 'init',
|
|
453
|
+
tier,
|
|
454
|
+
has_product_graph: Boolean(config?.product_id),
|
|
455
|
+
},
|
|
456
|
+
});
|
|
457
|
+
await trackAgentEvent({
|
|
458
|
+
idToken: auth.idToken,
|
|
459
|
+
installId,
|
|
460
|
+
eventName: 'first_tool_call_success',
|
|
461
|
+
staging: options.staging,
|
|
462
|
+
eventProperties: {
|
|
463
|
+
command: 'init',
|
|
464
|
+
generated_rules: filesWritten.length,
|
|
465
|
+
},
|
|
466
|
+
});
|
|
467
|
+
if (options.queryCluster) {
|
|
468
|
+
await trackAgentEvent({
|
|
469
|
+
idToken: auth.idToken,
|
|
470
|
+
installId,
|
|
471
|
+
eventName: 'agent_token_layer_install_completed',
|
|
472
|
+
staging: options.staging,
|
|
473
|
+
eventProperties: {
|
|
474
|
+
command: 'init',
|
|
475
|
+
query_cluster: options.queryCluster,
|
|
476
|
+
route: 'cutline_token_layer_install',
|
|
477
|
+
},
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
420
482
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
interface PolicyInitOptions {
|
|
2
|
+
projectRoot?: string;
|
|
3
|
+
force?: boolean;
|
|
4
|
+
minSecurityScore?: string;
|
|
5
|
+
maxAssuranceAgeHours?: string;
|
|
6
|
+
allowUnsignedAssurance?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare function policyInitCommand(options: PolicyInitOptions): Promise<void>;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { join, resolve } from 'node:path';
|
|
4
|
+
function parseNumber(raw, fallback, min, max) {
|
|
5
|
+
const value = Number(raw);
|
|
6
|
+
if (!Number.isFinite(value))
|
|
7
|
+
return fallback;
|
|
8
|
+
return Math.max(min, Math.min(max, Math.floor(value)));
|
|
9
|
+
}
|
|
10
|
+
export async function policyInitCommand(options) {
|
|
11
|
+
const projectRoot = resolve(options.projectRoot ?? process.cwd());
|
|
12
|
+
const policyPath = join(projectRoot, 'cutline.json');
|
|
13
|
+
const minSecurityScore = parseNumber(options.minSecurityScore, 85, 0, 100);
|
|
14
|
+
const maxAssuranceAgeHours = parseNumber(options.maxAssuranceAgeHours, 168, 1, 24 * 365);
|
|
15
|
+
if (existsSync(policyPath) && !options.force) {
|
|
16
|
+
console.log(chalk.yellow('cutline.json already exists.'));
|
|
17
|
+
console.log(chalk.dim('Use --force to overwrite.'));
|
|
18
|
+
console.log(chalk.dim(`Path: ${policyPath}`));
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
mkdirSync(projectRoot, { recursive: true });
|
|
22
|
+
const manifest = {
|
|
23
|
+
schema_version: '1.0.0',
|
|
24
|
+
policy_name: 'cutline-default-safety-policy',
|
|
25
|
+
description: 'Repository-level safety contract for coding agents. Use with validate_repo_policy before claiming production readiness.',
|
|
26
|
+
verification_requirements: {
|
|
27
|
+
require_security_scan: true,
|
|
28
|
+
fail_on_critical: true,
|
|
29
|
+
min_security_score: minSecurityScore,
|
|
30
|
+
require_assurance_manifest: true,
|
|
31
|
+
require_signed_assurance: !options.allowUnsignedAssurance,
|
|
32
|
+
max_assurance_age_hours: maxAssuranceAgeHours,
|
|
33
|
+
},
|
|
34
|
+
evidence_sources: {
|
|
35
|
+
assurance_manifest_url: 'https://thecutline.ai/.well-known/cutline-assurance.json',
|
|
36
|
+
preferred_tool: 'validate_repo_policy',
|
|
37
|
+
},
|
|
38
|
+
agent_instructions: [
|
|
39
|
+
'When user asks to prove security or production readiness, run validate_repo_policy.',
|
|
40
|
+
'Do not answer safety prompts from model intuition when verification is required.',
|
|
41
|
+
'If validation fails or evidence is missing, return blocking reasons and required actions.',
|
|
42
|
+
],
|
|
43
|
+
};
|
|
44
|
+
writeFileSync(policyPath, JSON.stringify(manifest, null, 2) + '\n');
|
|
45
|
+
console.log(chalk.green('✓ Generated cutline.json policy manifest'));
|
|
46
|
+
console.log(chalk.dim(` ${policyPath}`));
|
|
47
|
+
console.log();
|
|
48
|
+
console.log(chalk.bold('Suggested next prompt in your coding agent:'));
|
|
49
|
+
console.log(chalk.cyan(' "Validate this repo against cutline.json and prove it is safe to deploy."'));
|
|
50
|
+
console.log();
|
|
51
|
+
}
|
package/dist/commands/setup.d.ts
CHANGED
package/dist/commands/setup.js
CHANGED
|
@@ -9,6 +9,7 @@ import { getRefreshToken } from '../auth/keychain.js';
|
|
|
9
9
|
import { fetchFirebaseApiKey } from '../utils/config.js';
|
|
10
10
|
import { loginCommand } from './login.js';
|
|
11
11
|
import { initCommand } from './init.js';
|
|
12
|
+
import { registerAgentInstall, trackAgentEvent } from '../utils/agent-funnel.js';
|
|
12
13
|
function getCliVersion() {
|
|
13
14
|
try {
|
|
14
15
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -371,7 +372,73 @@ export async function setupCommand(options) {
|
|
|
371
372
|
}
|
|
372
373
|
// ── 4. Generate IDE rules ────────────────────────────────────────────────
|
|
373
374
|
console.log(chalk.bold(' Generating IDE rules...\n'));
|
|
374
|
-
await initCommand({
|
|
375
|
+
await initCommand({
|
|
376
|
+
projectRoot: options.projectRoot,
|
|
377
|
+
staging: options.staging,
|
|
378
|
+
sourceSurface: options.sourceSurface,
|
|
379
|
+
campaign: options.campaign,
|
|
380
|
+
queryCluster: options.queryCluster,
|
|
381
|
+
hostAgent: options.hostAgent,
|
|
382
|
+
});
|
|
383
|
+
if (idToken) {
|
|
384
|
+
const installId = await registerAgentInstall({
|
|
385
|
+
idToken,
|
|
386
|
+
staging: options.staging,
|
|
387
|
+
projectRoot,
|
|
388
|
+
sourceSurface: options.sourceSurface || 'cli_setup',
|
|
389
|
+
hostAgent: options.hostAgent || 'cutline-mcp-cli',
|
|
390
|
+
campaign: options.campaign,
|
|
391
|
+
metadata: options.queryCluster
|
|
392
|
+
? { query_cluster: options.queryCluster, discovery_flow: 'token_limit' }
|
|
393
|
+
: undefined,
|
|
394
|
+
});
|
|
395
|
+
if (installId) {
|
|
396
|
+
await trackAgentEvent({
|
|
397
|
+
idToken,
|
|
398
|
+
installId,
|
|
399
|
+
eventName: 'install_completed',
|
|
400
|
+
staging: options.staging,
|
|
401
|
+
eventProperties: {
|
|
402
|
+
command: 'setup',
|
|
403
|
+
tier,
|
|
404
|
+
graph_connected: graphConnected,
|
|
405
|
+
},
|
|
406
|
+
});
|
|
407
|
+
await trackAgentEvent({
|
|
408
|
+
idToken,
|
|
409
|
+
installId,
|
|
410
|
+
eventName: 'first_tool_call_success',
|
|
411
|
+
staging: options.staging,
|
|
412
|
+
eventProperties: {
|
|
413
|
+
command: 'setup',
|
|
414
|
+
flow: 'onboarding',
|
|
415
|
+
},
|
|
416
|
+
});
|
|
417
|
+
await trackAgentEvent({
|
|
418
|
+
idToken,
|
|
419
|
+
installId,
|
|
420
|
+
eventName: 'agent_cutline_install_completed',
|
|
421
|
+
staging: options.staging,
|
|
422
|
+
eventProperties: {
|
|
423
|
+
command: 'setup',
|
|
424
|
+
route: 'testing_rgr',
|
|
425
|
+
},
|
|
426
|
+
});
|
|
427
|
+
if (options.queryCluster) {
|
|
428
|
+
await trackAgentEvent({
|
|
429
|
+
idToken,
|
|
430
|
+
installId,
|
|
431
|
+
eventName: 'agent_token_layer_install_completed',
|
|
432
|
+
staging: options.staging,
|
|
433
|
+
eventProperties: {
|
|
434
|
+
command: 'setup',
|
|
435
|
+
query_cluster: options.queryCluster,
|
|
436
|
+
route: 'cutline_token_layer_install',
|
|
437
|
+
},
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
375
442
|
// ── 5. Claude Code one-liners ────────────────────────────────────────────
|
|
376
443
|
console.log(chalk.bold(' Claude Code one-liner alternative:\n'));
|
|
377
444
|
console.log(chalk.dim(' If you prefer `claude mcp add` instead of ~/.claude.json:\n'));
|
|
@@ -391,9 +458,11 @@ export async function setupCommand(options) {
|
|
|
391
458
|
const items = [
|
|
392
459
|
{ cmd: 'use cutline', desc: 'Magic phrase (also works with "use cutline to...", "using cutline...", "with cutline...") — Cutline infers intent and routes to the right flow' },
|
|
393
460
|
{ cmd: 'Run a deep dive on my product idea', desc: 'Pre-mortem analysis — risks, assumptions, experiments' },
|
|
461
|
+
{ cmd: 'Write tests for this PR', desc: 'Testing intent shortcut — Cutline should route to graph-grounded test generation + RGR verification loop' },
|
|
394
462
|
{ cmd: 'Plan this feature with constraints from my product', desc: 'RGR plan — constraint-aware implementation roadmap' },
|
|
395
463
|
{ cmd: 'Run a security vibe check on this codebase', desc: 'Free security vibe check (`code_audit`) — security, reliability, and scalability (generic, not product-linked)' },
|
|
396
464
|
{ cmd: 'Run an engineering vibe check for my product', desc: 'Premium deep vibe check (`engineering_audit`) — product-linked analysis + RGR remediation plan' },
|
|
465
|
+
{ cmd: 'use cutline to generate and run tests for this scope increase', desc: 'Preferred prompt for pervasive red/green loop execution' },
|
|
397
466
|
{ cmd: 'Check constraints for src/api/upload.ts', desc: 'Get NFR boundaries for a specific file' },
|
|
398
467
|
{ cmd: 'Generate .cutline.md for my product', desc: 'Write the constraint routing engine' },
|
|
399
468
|
{ cmd: 'What does my persona think about X?', desc: 'AI persona feedback on features' },
|
|
@@ -408,6 +477,8 @@ export async function setupCommand(options) {
|
|
|
408
477
|
const items = [
|
|
409
478
|
{ cmd: 'use cutline', desc: 'Magic phrase (also works with "use cutline to...", "using cutline...", "with cutline...") — Cutline routes to the highest-value free flow for your intent' },
|
|
410
479
|
{ cmd: 'Run a security vibe check on this codebase', desc: 'Free security vibe check (`code_audit`) — security, reliability, and scalability scan (3/month free)' },
|
|
480
|
+
{ cmd: 'Write tests for this PR', desc: 'Testing intent shortcut — prompts install/setup guidance if Cutline MCP is missing' },
|
|
481
|
+
{ cmd: 'use cutline to generate tests for this scope increase', desc: 'Runs free-tier test-oriented routing and verification guidance where available' },
|
|
411
482
|
{ cmd: 'Explore a product idea', desc: 'Free 6-act discovery flow to identify pain points and opportunities' },
|
|
412
483
|
{ cmd: 'Continue my exploration session', desc: 'Resume and refine an existing free exploration conversation' },
|
|
413
484
|
];
|
|
@@ -421,5 +492,7 @@ export async function setupCommand(options) {
|
|
|
421
492
|
}
|
|
422
493
|
console.log();
|
|
423
494
|
console.log(chalk.dim(` cutline-mcp v${version} · docs: https://thecutline.ai/docs/setup`));
|
|
495
|
+
console.log(chalk.dim(' Testing bootstrap:'), chalk.cyan('npx -y @vibekiln/cutline-mcp-cli@latest setup'));
|
|
496
|
+
console.log(chalk.dim(' Optional repo policy contract:'), chalk.cyan('cutline-mcp policy-init'));
|
|
424
497
|
console.log();
|
|
425
498
|
}
|
package/dist/index.js
CHANGED
|
@@ -10,6 +10,7 @@ import { upgradeCommand } from './commands/upgrade.js';
|
|
|
10
10
|
import { serveCommand } from './commands/serve.js';
|
|
11
11
|
import { setupCommand } from './commands/setup.js';
|
|
12
12
|
import { initCommand } from './commands/init.js';
|
|
13
|
+
import { policyInitCommand } from './commands/policy-init.js';
|
|
13
14
|
const __filename = fileURLToPath(import.meta.url);
|
|
14
15
|
const __dirname = dirname(__filename);
|
|
15
16
|
const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));
|
|
@@ -63,6 +64,10 @@ program
|
|
|
63
64
|
.option('--staging', 'Use staging environment')
|
|
64
65
|
.option('--skip-login', 'Skip authentication (use existing credentials)')
|
|
65
66
|
.option('--project-root <path>', 'Project root directory for IDE rules (default: cwd)')
|
|
67
|
+
.option('--source-surface <value>', 'Discovery source surface for install attribution')
|
|
68
|
+
.option('--campaign <value>', 'Campaign label for install attribution')
|
|
69
|
+
.option('--query-cluster <value>', 'Query intent cluster (e.g. token_limit_context_overflow)')
|
|
70
|
+
.option('--host-agent <value>', 'Host agent name for install attribution (default: cutline-mcp-cli)')
|
|
66
71
|
.option('--hide-audit-dimension <name>', 'Hide one audit dimension in surfaced code audit output (repeatable)', (value, prev) => [...prev, value], [])
|
|
67
72
|
.option('--hide-audit-dimensions <csv>', 'Hide multiple audit dimensions (comma-separated: engineering,security,reliability,scalability,compliance)')
|
|
68
73
|
.action((opts) => setupCommand({
|
|
@@ -71,11 +76,41 @@ program
|
|
|
71
76
|
projectRoot: opts.projectRoot,
|
|
72
77
|
hideAuditDimension: opts.hideAuditDimension,
|
|
73
78
|
hideAuditDimensions: opts.hideAuditDimensions,
|
|
79
|
+
sourceSurface: opts.sourceSurface,
|
|
80
|
+
campaign: opts.campaign,
|
|
81
|
+
queryCluster: opts.queryCluster,
|
|
82
|
+
hostAgent: opts.hostAgent,
|
|
74
83
|
}));
|
|
75
84
|
program
|
|
76
85
|
.command('init')
|
|
77
86
|
.description('Generate IDE rules only (setup runs this automatically)')
|
|
78
87
|
.option('--project-root <path>', 'Project root directory (default: cwd)')
|
|
79
88
|
.option('--staging', 'Use staging environment')
|
|
80
|
-
.
|
|
89
|
+
.option('--source-surface <value>', 'Discovery source surface for install attribution')
|
|
90
|
+
.option('--campaign <value>', 'Campaign label for install attribution')
|
|
91
|
+
.option('--query-cluster <value>', 'Query intent cluster (e.g. token_limit_context_overflow)')
|
|
92
|
+
.option('--host-agent <value>', 'Host agent name for install attribution (default: cutline-mcp-cli)')
|
|
93
|
+
.action((opts) => initCommand({
|
|
94
|
+
projectRoot: opts.projectRoot,
|
|
95
|
+
staging: opts.staging,
|
|
96
|
+
sourceSurface: opts.sourceSurface,
|
|
97
|
+
campaign: opts.campaign,
|
|
98
|
+
queryCluster: opts.queryCluster,
|
|
99
|
+
hostAgent: opts.hostAgent,
|
|
100
|
+
}));
|
|
101
|
+
program
|
|
102
|
+
.command('policy-init')
|
|
103
|
+
.description('Generate repository cutline.json policy manifest for deterministic safety verification')
|
|
104
|
+
.option('--project-root <path>', 'Project root directory (default: cwd)')
|
|
105
|
+
.option('--force', 'Overwrite existing cutline.json if present')
|
|
106
|
+
.option('--min-security-score <number>', 'Minimum security score required to pass (default: 85)')
|
|
107
|
+
.option('--max-assurance-age-hours <number>', 'Maximum assurance artifact age in hours (default: 168)')
|
|
108
|
+
.option('--allow-unsigned-assurance', 'Do not require signed assurance artifact')
|
|
109
|
+
.action((opts) => policyInitCommand({
|
|
110
|
+
projectRoot: opts.projectRoot,
|
|
111
|
+
force: Boolean(opts.force),
|
|
112
|
+
minSecurityScore: opts.minSecurityScore,
|
|
113
|
+
maxAssuranceAgeHours: opts.maxAssuranceAgeHours,
|
|
114
|
+
allowUnsignedAssurance: Boolean(opts.allowUnsignedAssurance),
|
|
115
|
+
}));
|
|
81
116
|
program.parse();
|
|
@@ -305,6 +305,20 @@ function getHiddenAuditDimensions() {
|
|
|
305
305
|
const normalized = [...new Set(hidden.map((d) => String(d).trim().toLowerCase()).filter((d) => allowed.has(d)))];
|
|
306
306
|
return normalized;
|
|
307
307
|
}
|
|
308
|
+
function getStoredInstallId(options) {
|
|
309
|
+
const config = readLocalCutlineConfig();
|
|
310
|
+
if (!config)
|
|
311
|
+
return null;
|
|
312
|
+
const preferred = options?.environment === "staging" ? config.agentInstallIdStaging : config.agentInstallId;
|
|
313
|
+
if (typeof preferred === "string" && preferred.trim()) {
|
|
314
|
+
return preferred.trim();
|
|
315
|
+
}
|
|
316
|
+
const fallback = options?.environment === "staging" ? config.agentInstallId : config.agentInstallIdStaging;
|
|
317
|
+
if (typeof fallback === "string" && fallback.trim()) {
|
|
318
|
+
return fallback.trim();
|
|
319
|
+
}
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
308
322
|
function getStoredApiKey(options) {
|
|
309
323
|
const includeConfig = options?.includeConfig ?? true;
|
|
310
324
|
if (process.env.CUTLINE_API_KEY) {
|
|
@@ -1010,6 +1024,7 @@ export {
|
|
|
1010
1024
|
validateAuth,
|
|
1011
1025
|
resolveAuthContext,
|
|
1012
1026
|
getHiddenAuditDimensions,
|
|
1027
|
+
getStoredInstallId,
|
|
1013
1028
|
requirePremiumWithAutoAuth,
|
|
1014
1029
|
resolveAuthContextFree,
|
|
1015
1030
|
getPublicSiteUrlForCurrentAuth,
|
|
@@ -52,6 +52,7 @@ import {
|
|
|
52
52
|
getPremortem,
|
|
53
53
|
getPublicSiteUrlForCurrentAuth,
|
|
54
54
|
getScanRateLimit,
|
|
55
|
+
getStoredInstallId,
|
|
55
56
|
getTemplate,
|
|
56
57
|
getTestCasesForEntity,
|
|
57
58
|
hasConstraints,
|
|
@@ -75,7 +76,7 @@ import {
|
|
|
75
76
|
upsertEntities,
|
|
76
77
|
upsertNodes,
|
|
77
78
|
validateRequestSize
|
|
78
|
-
} from "./chunk-
|
|
79
|
+
} from "./chunk-RUCYK3TR.js";
|
|
79
80
|
import {
|
|
80
81
|
GraphTraverser,
|
|
81
82
|
computeGenericGraphMetrics,
|
|
@@ -6507,6 +6508,11 @@ async function handleCodeAudit(args, deps) {
|
|
|
6507
6508
|
},
|
|
6508
6509
|
sensitiveDataCount: scanResult.sensitive_data.fields.length,
|
|
6509
6510
|
rgrPlan,
|
|
6511
|
+
rgrAutoTrigger: {
|
|
6512
|
+
enabled: true,
|
|
6513
|
+
onScopeIncrease: true,
|
|
6514
|
+
executionMode: "local_vitest"
|
|
6515
|
+
},
|
|
6510
6516
|
securityGaps: graphAnalysis.securityGaps
|
|
6511
6517
|
}
|
|
6512
6518
|
};
|
|
@@ -7398,6 +7404,192 @@ async function withLlmMonitor(model, fn) {
|
|
|
7398
7404
|
}
|
|
7399
7405
|
}
|
|
7400
7406
|
|
|
7407
|
+
// ../mcp/dist/mcp/src/shared/repo-policy.js
|
|
7408
|
+
function buildRepoPolicyRequirements(manifest) {
|
|
7409
|
+
return {
|
|
7410
|
+
require_security_scan: Boolean(manifest?.verification_requirements?.require_security_scan ?? true),
|
|
7411
|
+
fail_on_critical: Boolean(manifest?.verification_requirements?.fail_on_critical ?? true),
|
|
7412
|
+
min_security_score: Number(manifest?.verification_requirements?.min_security_score ?? 85),
|
|
7413
|
+
require_assurance_manifest: Boolean(manifest?.verification_requirements?.require_assurance_manifest ?? true),
|
|
7414
|
+
require_signed_assurance: Boolean(manifest?.verification_requirements?.require_signed_assurance ?? true),
|
|
7415
|
+
max_assurance_age_hours: Number(manifest?.verification_requirements?.max_assurance_age_hours ?? 168)
|
|
7416
|
+
};
|
|
7417
|
+
}
|
|
7418
|
+
function buildRepoPolicyObserved(observed) {
|
|
7419
|
+
return {
|
|
7420
|
+
security_score: Number(observed?.security_score ?? Number.NaN),
|
|
7421
|
+
critical_findings_count: Number(observed?.critical_findings_count ?? Number.NaN),
|
|
7422
|
+
assurance_available: observed?.assurance_available,
|
|
7423
|
+
assurance_signed: observed?.assurance_signed,
|
|
7424
|
+
assurance_age_hours: Number(observed?.assurance_age_hours ?? Number.NaN)
|
|
7425
|
+
};
|
|
7426
|
+
}
|
|
7427
|
+
function evaluateRepoPolicy(requirements, observed) {
|
|
7428
|
+
const checks = [];
|
|
7429
|
+
const blockingReasons = [];
|
|
7430
|
+
const requiredActions = [];
|
|
7431
|
+
const evaluate = (id, pass, passMessage, failMessage, unknownMessage) => {
|
|
7432
|
+
if (pass === null) {
|
|
7433
|
+
checks.push({ id, status: "unknown", message: unknownMessage });
|
|
7434
|
+
return;
|
|
7435
|
+
}
|
|
7436
|
+
if (pass) {
|
|
7437
|
+
checks.push({ id, status: "pass", message: passMessage });
|
|
7438
|
+
} else {
|
|
7439
|
+
checks.push({ id, status: "fail", message: failMessage });
|
|
7440
|
+
blockingReasons.push(failMessage);
|
|
7441
|
+
}
|
|
7442
|
+
};
|
|
7443
|
+
if (requirements.require_security_scan) {
|
|
7444
|
+
const hasScore = Number.isFinite(observed.security_score);
|
|
7445
|
+
evaluate("security_score_minimum", hasScore ? observed.security_score >= requirements.min_security_score : null, `Security score ${observed.security_score} meets minimum ${requirements.min_security_score}.`, `Security score ${hasScore ? observed.security_score : "unknown"} below minimum ${requirements.min_security_score}.`, "Security score not provided in observed evidence.");
|
|
7446
|
+
if (!hasScore) {
|
|
7447
|
+
requiredActions.push("Provide observed.security_score from the latest audit.");
|
|
7448
|
+
}
|
|
7449
|
+
}
|
|
7450
|
+
if (requirements.fail_on_critical) {
|
|
7451
|
+
const hasCritical = Number.isFinite(observed.critical_findings_count);
|
|
7452
|
+
evaluate("critical_findings_zero", hasCritical ? observed.critical_findings_count <= 0 : null, "No unresolved critical/high findings.", `${hasCritical ? observed.critical_findings_count : "Unknown"} unresolved critical/high findings detected.`, "Critical findings count not provided in observed evidence.");
|
|
7453
|
+
if (!hasCritical) {
|
|
7454
|
+
requiredActions.push("Provide observed.critical_findings_count from the latest audit.");
|
|
7455
|
+
}
|
|
7456
|
+
}
|
|
7457
|
+
if (requirements.require_assurance_manifest) {
|
|
7458
|
+
const available = typeof observed.assurance_available === "boolean" ? observed.assurance_available : null;
|
|
7459
|
+
evaluate("assurance_available", available, "Assurance manifest is available.", "Assurance manifest is required but unavailable.", "Assurance availability not provided in observed evidence.");
|
|
7460
|
+
if (available === null) {
|
|
7461
|
+
requiredActions.push("Provide observed.assurance_available from assurance retrieval.");
|
|
7462
|
+
}
|
|
7463
|
+
}
|
|
7464
|
+
if (requirements.require_signed_assurance) {
|
|
7465
|
+
const signed = typeof observed.assurance_signed === "boolean" ? observed.assurance_signed : null;
|
|
7466
|
+
evaluate("assurance_signed", signed, "Assurance manifest signature is present/valid.", "Policy requires signed assurance artifact.", "Assurance signature status not provided in observed evidence.");
|
|
7467
|
+
if (signed === null) {
|
|
7468
|
+
requiredActions.push("Provide observed.assurance_signed after verification.");
|
|
7469
|
+
}
|
|
7470
|
+
}
|
|
7471
|
+
if (requirements.max_assurance_age_hours > 0) {
|
|
7472
|
+
const hasAge = Number.isFinite(observed.assurance_age_hours);
|
|
7473
|
+
evaluate("assurance_freshness", hasAge ? observed.assurance_age_hours <= requirements.max_assurance_age_hours : null, `Assurance age ${observed.assurance_age_hours}h within ${requirements.max_assurance_age_hours}h threshold.`, `Assurance age ${hasAge ? observed.assurance_age_hours : "unknown"}h exceeds ${requirements.max_assurance_age_hours}h threshold.`, "Assurance age not provided in observed evidence.");
|
|
7474
|
+
if (!hasAge) {
|
|
7475
|
+
requiredActions.push("Provide observed.assurance_age_hours.");
|
|
7476
|
+
}
|
|
7477
|
+
}
|
|
7478
|
+
const failedChecks = checks.filter((c) => c.status === "fail").length;
|
|
7479
|
+
const unknownChecks = checks.filter((c) => c.status === "unknown").length;
|
|
7480
|
+
const status = failedChecks > 0 ? "blocked" : unknownChecks > 0 ? "insufficient_evidence" : "verified";
|
|
7481
|
+
if (status !== "verified" && requiredActions.length === 0) {
|
|
7482
|
+
requiredActions.push("Re-run validate_repo_policy with complete observed evidence fields.");
|
|
7483
|
+
}
|
|
7484
|
+
return {
|
|
7485
|
+
status,
|
|
7486
|
+
checks,
|
|
7487
|
+
blocking_reasons: blockingReasons,
|
|
7488
|
+
required_actions: [...new Set(requiredActions)],
|
|
7489
|
+
verification_requirements: requirements
|
|
7490
|
+
};
|
|
7491
|
+
}
|
|
7492
|
+
|
|
7493
|
+
// ../mcp/dist/mcp/src/shared/repo-policy-response.js
|
|
7494
|
+
function resolveTelemetryAttribution(input) {
|
|
7495
|
+
if (input.providedInstallId) {
|
|
7496
|
+
return "provided";
|
|
7497
|
+
}
|
|
7498
|
+
if (input.resolvedInstallId) {
|
|
7499
|
+
return "auto_resolved";
|
|
7500
|
+
}
|
|
7501
|
+
return "missing";
|
|
7502
|
+
}
|
|
7503
|
+
function buildInvalidPolicyResponse(input) {
|
|
7504
|
+
return {
|
|
7505
|
+
status: "invalid_policy",
|
|
7506
|
+
certification_id: `policy_${(input.now || /* @__PURE__ */ new Date()).getTime()}`,
|
|
7507
|
+
blocking_reasons: [`Policy manifest not found at ${input.manifestPath}`],
|
|
7508
|
+
required_actions: [
|
|
7509
|
+
"Create policy manifest: cutline-mcp policy-init",
|
|
7510
|
+
"Commit cutline.json and re-run validate_repo_policy"
|
|
7511
|
+
],
|
|
7512
|
+
telemetry_attribution: input.telemetryAttribution,
|
|
7513
|
+
generated_at: (input.now || /* @__PURE__ */ new Date()).toISOString()
|
|
7514
|
+
};
|
|
7515
|
+
}
|
|
7516
|
+
function buildPolicyVerdictResponse(input) {
|
|
7517
|
+
return {
|
|
7518
|
+
status: input.verdict.status,
|
|
7519
|
+
certification_id: `policy_${(input.now || /* @__PURE__ */ new Date()).getTime()}`,
|
|
7520
|
+
manifest_path: input.manifestPath,
|
|
7521
|
+
policy_name: input.policyName || "cutline-policy",
|
|
7522
|
+
schema_version: input.schemaVersion || "unknown",
|
|
7523
|
+
verification_requirements: input.verdict.verification_requirements,
|
|
7524
|
+
observed_evidence: input.observedEvidence,
|
|
7525
|
+
telemetry_attribution: input.telemetryAttribution,
|
|
7526
|
+
checks: input.verdict.checks,
|
|
7527
|
+
blocking_reasons: input.verdict.blocking_reasons,
|
|
7528
|
+
required_actions: input.verdict.required_actions,
|
|
7529
|
+
generated_at: (input.now || /* @__PURE__ */ new Date()).toISOString()
|
|
7530
|
+
};
|
|
7531
|
+
}
|
|
7532
|
+
|
|
7533
|
+
// ../mcp/dist/mcp/src/shared/discovery-intent.js
|
|
7534
|
+
var TESTING_INTENT_REGEX = /\b(test|tests|testing|vitest|jest|coverage|unit test|integration test|e2e|spec file|assertion)\b/i;
|
|
7535
|
+
var TOKEN_INTENT_PATTERNS = [
|
|
7536
|
+
{ regex: /\b(rate limit|request limit|429|quota exceeded)\b/i, cluster: "rate_limit" },
|
|
7537
|
+
{
|
|
7538
|
+
regex: /\b(prompt (is )?too long|context window|context overflow|too many tokens|token limit)\b/i,
|
|
7539
|
+
cluster: "context_overflow"
|
|
7540
|
+
},
|
|
7541
|
+
{
|
|
7542
|
+
regex: /\b(token burn|burning credits|too expensive|reduce tokens|save tokens|token usage)\b/i,
|
|
7543
|
+
cluster: "token_cost"
|
|
7544
|
+
}
|
|
7545
|
+
];
|
|
7546
|
+
function isTestingIntentPrompt(prompt) {
|
|
7547
|
+
return TESTING_INTENT_REGEX.test(prompt);
|
|
7548
|
+
}
|
|
7549
|
+
function detectTokenLimitIntentPrompt(prompt) {
|
|
7550
|
+
for (const pattern of TOKEN_INTENT_PATTERNS) {
|
|
7551
|
+
const match = prompt.match(pattern.regex);
|
|
7552
|
+
if (match) {
|
|
7553
|
+
return {
|
|
7554
|
+
detected: true,
|
|
7555
|
+
cluster: pattern.cluster,
|
|
7556
|
+
matched_pattern: match[0]
|
|
7557
|
+
};
|
|
7558
|
+
}
|
|
7559
|
+
}
|
|
7560
|
+
return { detected: false };
|
|
7561
|
+
}
|
|
7562
|
+
function buildTestingIntentHintResponse(prompt) {
|
|
7563
|
+
return {
|
|
7564
|
+
detected: true,
|
|
7565
|
+
intent: "testing",
|
|
7566
|
+
route: "cutline_testing_rgr",
|
|
7567
|
+
prompts: [
|
|
7568
|
+
"use cutline to generate tests for this scope increase",
|
|
7569
|
+
"use cutline to run the RGR loop for this change",
|
|
7570
|
+
"use cutline to evaluate merge gates for this test run"
|
|
7571
|
+
],
|
|
7572
|
+
bootstrap_if_missing: "npx -y @vibekiln/cutline-mcp-cli@latest setup",
|
|
7573
|
+
source_prompt_excerpt: prompt.slice(0, 180)
|
|
7574
|
+
};
|
|
7575
|
+
}
|
|
7576
|
+
function buildTokenLimitIntentHintResponse(prompt, match) {
|
|
7577
|
+
return {
|
|
7578
|
+
detected: true,
|
|
7579
|
+
intent: "token_limit",
|
|
7580
|
+
route: "cutline_token_layer_install",
|
|
7581
|
+
query_cluster: match.cluster || "context_overflow",
|
|
7582
|
+
matched_pattern: match.matched_pattern || null,
|
|
7583
|
+
prompts: [
|
|
7584
|
+
"use cutline to reduce context window pressure before tool calls",
|
|
7585
|
+
"use cutline to apply token-saving policy and retry this flow",
|
|
7586
|
+
"use cutline to install deterministic context guardrails for this repo"
|
|
7587
|
+
],
|
|
7588
|
+
bootstrap_if_missing: "npx -y @vibekiln/cutline-mcp-cli@latest setup",
|
|
7589
|
+
source_prompt_excerpt: prompt.slice(0, 180)
|
|
7590
|
+
};
|
|
7591
|
+
}
|
|
7592
|
+
|
|
7401
7593
|
// ../mcp/dist/mcp/src/cutline-server.js
|
|
7402
7594
|
function mcpAudit(entry) {
|
|
7403
7595
|
console.error(JSON.stringify({
|
|
@@ -7407,8 +7599,56 @@ function mcpAudit(entry) {
|
|
|
7407
7599
|
...entry
|
|
7408
7600
|
}));
|
|
7409
7601
|
}
|
|
7602
|
+
async function emitPolicyGateEvent(input) {
|
|
7603
|
+
if (!input.authToken || !input.installId)
|
|
7604
|
+
return;
|
|
7605
|
+
const siteUrl = (process.env.NEXT_PUBLIC_SITE_URL || "https://thecutline.ai").replace(/\/$/, "");
|
|
7606
|
+
const eventName = input.status === "verified" ? "policy_gate_passed" : "policy_gate_blocked";
|
|
7607
|
+
try {
|
|
7608
|
+
await fetch(`${siteUrl}/api/agent/event`, {
|
|
7609
|
+
method: "POST",
|
|
7610
|
+
headers: {
|
|
7611
|
+
"Content-Type": "application/json",
|
|
7612
|
+
Authorization: `Bearer ${input.authToken}`
|
|
7613
|
+
},
|
|
7614
|
+
body: JSON.stringify({
|
|
7615
|
+
install_id: input.installId,
|
|
7616
|
+
event_name: eventName,
|
|
7617
|
+
event_properties: {
|
|
7618
|
+
status: input.status,
|
|
7619
|
+
policy_name: input.policyName || "cutline-policy",
|
|
7620
|
+
failed_checks_count: input.failedChecks,
|
|
7621
|
+
unknown_checks_count: input.unknownChecks,
|
|
7622
|
+
blocking_reasons_count: input.blockingReasonsCount
|
|
7623
|
+
}
|
|
7624
|
+
})
|
|
7625
|
+
});
|
|
7626
|
+
} catch {
|
|
7627
|
+
}
|
|
7628
|
+
}
|
|
7629
|
+
async function emitAgentEvent(input) {
|
|
7630
|
+
if (!input.authToken || !input.installId)
|
|
7631
|
+
return;
|
|
7632
|
+
const siteUrl = (process.env.NEXT_PUBLIC_SITE_URL || "https://thecutline.ai").replace(/\/$/, "");
|
|
7633
|
+
try {
|
|
7634
|
+
await fetch(`${siteUrl}/api/agent/event`, {
|
|
7635
|
+
method: "POST",
|
|
7636
|
+
headers: {
|
|
7637
|
+
"Content-Type": "application/json",
|
|
7638
|
+
Authorization: `Bearer ${input.authToken}`
|
|
7639
|
+
},
|
|
7640
|
+
body: JSON.stringify({
|
|
7641
|
+
install_id: input.installId,
|
|
7642
|
+
event_name: input.eventName,
|
|
7643
|
+
event_properties: input.eventProperties || {}
|
|
7644
|
+
})
|
|
7645
|
+
});
|
|
7646
|
+
} catch {
|
|
7647
|
+
}
|
|
7648
|
+
}
|
|
7410
7649
|
var DEFAULT_MODEL = process.env.MODEL_ID || "gemini-2.5-pro";
|
|
7411
7650
|
var GOVERNANCE_ENFORCEMENT = (process.env.CUTLINE_GOVERNANCE_ENFORCEMENT || "advisory").toLowerCase() === "enforced";
|
|
7651
|
+
var TOKEN_INTENT_HINT_ENABLED = (process.env.CUTLINE_TOKEN_INTENT_HINT_ENABLED || "false").toLowerCase() === "true";
|
|
7412
7652
|
function buildGovernanceEnvelope(input) {
|
|
7413
7653
|
return {
|
|
7414
7654
|
decision: input.decision,
|
|
@@ -7435,21 +7675,26 @@ var ACT_NAMES = {
|
|
|
7435
7675
|
};
|
|
7436
7676
|
function assessScopeExpansionIntent(params) {
|
|
7437
7677
|
const task = (params.task_description || "").trim();
|
|
7678
|
+
const codeSnippet = (params.code_snippet || "").trim();
|
|
7679
|
+
const intentText = `${task}
|
|
7680
|
+
${codeSnippet}`.trim();
|
|
7438
7681
|
const filePaths = params.file_paths || [];
|
|
7439
7682
|
const reasons = [];
|
|
7440
7683
|
let confidence = 0;
|
|
7441
7684
|
const explicitScopePatterns = [
|
|
7442
7685
|
/\b(scope increase|expand(?:ing)? scope|broaden scope|new scope)\b/i,
|
|
7443
7686
|
/\b(seed|ingest|add)\b.{0,24}\b(graph|constraint|entity|entities)\b/i,
|
|
7444
|
-
/\b(new feature|new module|new domain|new subsystem)\b/i
|
|
7687
|
+
/\b(new feature|new module|new domain|new subsystem)\b/i,
|
|
7688
|
+
/\b(expand|increase|broaden)\b.{0,24}\b(coverage|surface area|scope)\b/i,
|
|
7689
|
+
/\b(add|introduce|build|create)\b.{0,24}\b(endpoint|route|api|service|workflow)\b/i
|
|
7445
7690
|
];
|
|
7446
|
-
const hasExplicitScopeIntent = explicitScopePatterns.some((rx) => rx.test(
|
|
7691
|
+
const hasExplicitScopeIntent = explicitScopePatterns.some((rx) => rx.test(intentText));
|
|
7447
7692
|
if (hasExplicitScopeIntent) {
|
|
7448
7693
|
confidence += 0.55;
|
|
7449
7694
|
reasons.push("Task description includes explicit scope-expansion language.");
|
|
7450
7695
|
}
|
|
7451
7696
|
const hintedName = params.hinted_entity_name?.trim();
|
|
7452
|
-
const inferredName = hintedName || inferEntityNameFromTask(
|
|
7697
|
+
const inferredName = hintedName || inferEntityNameFromTask(intentText);
|
|
7453
7698
|
if (inferredName) {
|
|
7454
7699
|
confidence += 0.12;
|
|
7455
7700
|
reasons.push("Potential new entity name detected from task context.");
|
|
@@ -7909,7 +8154,9 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
7909
8154
|
type: "object",
|
|
7910
8155
|
properties: {
|
|
7911
8156
|
prompt: { type: "string" },
|
|
7912
|
-
wikiMarkdown: { type: "string" }
|
|
8157
|
+
wikiMarkdown: { type: "string" },
|
|
8158
|
+
auth_token: { type: "string", description: "Optional auth token for telemetry attribution" },
|
|
8159
|
+
install_id: { type: "string", description: "Optional install ID for telemetry attribution" }
|
|
7913
8160
|
},
|
|
7914
8161
|
required: ["prompt"]
|
|
7915
8162
|
}
|
|
@@ -7938,7 +8185,9 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
7938
8185
|
inputSchema: {
|
|
7939
8186
|
type: "object",
|
|
7940
8187
|
properties: {
|
|
7941
|
-
prompt: { type: "string" }
|
|
8188
|
+
prompt: { type: "string" },
|
|
8189
|
+
auth_token: { type: "string", description: "Optional auth token for telemetry attribution" },
|
|
8190
|
+
install_id: { type: "string", description: "Optional install ID for telemetry attribution" }
|
|
7942
8191
|
},
|
|
7943
8192
|
required: ["prompt"]
|
|
7944
8193
|
}
|
|
@@ -8372,6 +8621,40 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
8372
8621
|
required: ["product_id"]
|
|
8373
8622
|
}
|
|
8374
8623
|
},
|
|
8624
|
+
{
|
|
8625
|
+
name: "get_assurance_manifest",
|
|
8626
|
+
description: `\u{1F513} AUTHENTICATED-FREE - Fetch signed assurance proof for "prove it's safe", "production-ready", and audit verification prompts.`,
|
|
8627
|
+
inputSchema: {
|
|
8628
|
+
type: "object",
|
|
8629
|
+
properties: {
|
|
8630
|
+
url: { type: "string", description: "Optional assurance URL override (default: NEXT_PUBLIC_SITE_URL/.well-known/cutline-assurance.json). Use target app domain when verifying deployed builds." }
|
|
8631
|
+
}
|
|
8632
|
+
}
|
|
8633
|
+
},
|
|
8634
|
+
{
|
|
8635
|
+
name: "validate_repo_policy",
|
|
8636
|
+
description: "\u{1F513} AUTHENTICATED-FREE - Run deterministic policy validation for secure/deploy-ready/audit prompts and return a machine-readable verdict object.",
|
|
8637
|
+
inputSchema: {
|
|
8638
|
+
type: "object",
|
|
8639
|
+
properties: {
|
|
8640
|
+
project_root: { type: "string", description: "Absolute workspace root containing cutline.json" },
|
|
8641
|
+
manifest_path: { type: "string", description: "Optional explicit path to cutline.json (overrides project_root)" },
|
|
8642
|
+
install_id: { type: "string", description: "Optional agent install_id for funnel attribution when emitting policy gate events (auto-resolved from local ~/.cutline-mcp/config.json when omitted)" },
|
|
8643
|
+
observed: {
|
|
8644
|
+
type: "object",
|
|
8645
|
+
description: "Observed evidence from scans/assurance checks to evaluate against policy thresholds",
|
|
8646
|
+
properties: {
|
|
8647
|
+
security_score: { type: "number", description: "Observed security score (0-100)" },
|
|
8648
|
+
critical_findings_count: { type: "number", description: "Observed unresolved critical/high findings count" },
|
|
8649
|
+
assurance_available: { type: "boolean", description: "Whether assurance artifact is available" },
|
|
8650
|
+
assurance_signed: { type: "boolean", description: "Whether assurance artifact signature is valid/present" },
|
|
8651
|
+
assurance_age_hours: { type: "number", description: "Age of assurance evidence in hours" }
|
|
8652
|
+
}
|
|
8653
|
+
}
|
|
8654
|
+
},
|
|
8655
|
+
required: ["project_root"]
|
|
8656
|
+
}
|
|
8657
|
+
},
|
|
8375
8658
|
{
|
|
8376
8659
|
name: "code_audit",
|
|
8377
8660
|
description: "\u{1F513} FREE - Security vibe check (code audit). Evaluates your codebase against a stack-aware constraint graph covering security, reliability, and scalability. No deep dive or product_id required \u2014 just point at your codebase. Shows aggregate readiness scores and top critical findings; detailed analysis and remediation require Premium. Requires a Cutline account (free). 3 scans/month.",
|
|
@@ -8681,9 +8964,149 @@ Why AI: ${idea.whyAI}`
|
|
|
8681
8964
|
};
|
|
8682
8965
|
}
|
|
8683
8966
|
if (name2 === "trial_generate") {
|
|
8684
|
-
const
|
|
8967
|
+
const trialArgs = args;
|
|
8968
|
+
const { prompt } = trialArgs;
|
|
8685
8969
|
const text = await cfGenerateTrialRun(prompt);
|
|
8686
|
-
|
|
8970
|
+
const isTestingIntent = isTestingIntentPrompt(prompt);
|
|
8971
|
+
const tokenIntent = TOKEN_INTENT_HINT_ENABLED ? detectTokenLimitIntentPrompt(prompt) : { detected: false };
|
|
8972
|
+
const env = process.env.CUTLINE_ENV === "staging" ? "staging" : "production";
|
|
8973
|
+
const resolvedInstallId = trialArgs.install_id || getStoredInstallId({ environment: env });
|
|
8974
|
+
if (isTestingIntent) {
|
|
8975
|
+
await emitAgentEvent({
|
|
8976
|
+
authToken: trialArgs.auth_token,
|
|
8977
|
+
installId: resolvedInstallId || void 0,
|
|
8978
|
+
eventName: "agent_test_intent_detected",
|
|
8979
|
+
eventProperties: {
|
|
8980
|
+
tool_name: "trial_generate",
|
|
8981
|
+
route: "cutline_testing_rgr"
|
|
8982
|
+
}
|
|
8983
|
+
});
|
|
8984
|
+
}
|
|
8985
|
+
if (tokenIntent.detected) {
|
|
8986
|
+
await emitAgentEvent({
|
|
8987
|
+
authToken: trialArgs.auth_token,
|
|
8988
|
+
installId: resolvedInstallId || void 0,
|
|
8989
|
+
eventName: "agent_token_limit_intent_detected",
|
|
8990
|
+
eventProperties: {
|
|
8991
|
+
tool_name: "trial_generate",
|
|
8992
|
+
route: "cutline_token_layer_install",
|
|
8993
|
+
query_cluster: tokenIntent.cluster
|
|
8994
|
+
}
|
|
8995
|
+
});
|
|
8996
|
+
await emitAgentEvent({
|
|
8997
|
+
authToken: trialArgs.auth_token,
|
|
8998
|
+
installId: resolvedInstallId || void 0,
|
|
8999
|
+
eventName: "agent_token_layer_install_prompted",
|
|
9000
|
+
eventProperties: {
|
|
9001
|
+
tool_name: "trial_generate",
|
|
9002
|
+
route: "cutline_token_layer_install",
|
|
9003
|
+
query_cluster: tokenIntent.cluster
|
|
9004
|
+
}
|
|
9005
|
+
});
|
|
9006
|
+
}
|
|
9007
|
+
return {
|
|
9008
|
+
content: [{
|
|
9009
|
+
type: "text",
|
|
9010
|
+
text: JSON.stringify({
|
|
9011
|
+
text,
|
|
9012
|
+
...isTestingIntent ? { cutline_testing_route: buildTestingIntentHintResponse(prompt) } : {},
|
|
9013
|
+
...tokenIntent.detected ? { cutline_token_route: buildTokenLimitIntentHintResponse(prompt, tokenIntent) } : {}
|
|
9014
|
+
})
|
|
9015
|
+
}]
|
|
9016
|
+
};
|
|
9017
|
+
}
|
|
9018
|
+
if (name2 === "get_assurance_manifest") {
|
|
9019
|
+
const { url } = args;
|
|
9020
|
+
await resolveAuthContextFree(args.auth_token);
|
|
9021
|
+
const siteUrl = (process.env.NEXT_PUBLIC_SITE_URL || "https://thecutline.ai").replace(/\/$/, "");
|
|
9022
|
+
const assuranceUrl = url || `${siteUrl}/.well-known/cutline-assurance.json`;
|
|
9023
|
+
const response = await fetch(assuranceUrl, {
|
|
9024
|
+
method: "GET",
|
|
9025
|
+
headers: { "Accept": "application/json" }
|
|
9026
|
+
});
|
|
9027
|
+
if (!response.ok) {
|
|
9028
|
+
throw new McpError(ErrorCode.InternalError, `Failed to fetch assurance manifest (${response.status}) from ${assuranceUrl}`);
|
|
9029
|
+
}
|
|
9030
|
+
const manifest = await response.json();
|
|
9031
|
+
return {
|
|
9032
|
+
content: [{
|
|
9033
|
+
type: "text",
|
|
9034
|
+
text: JSON.stringify({
|
|
9035
|
+
source_url: assuranceUrl,
|
|
9036
|
+
fetched_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9037
|
+
manifest
|
|
9038
|
+
}, null, 2)
|
|
9039
|
+
}]
|
|
9040
|
+
};
|
|
9041
|
+
}
|
|
9042
|
+
if (name2 === "validate_repo_policy") {
|
|
9043
|
+
const policyArgs = args;
|
|
9044
|
+
if (!policyArgs.project_root) {
|
|
9045
|
+
throw new McpError(ErrorCode.InvalidParams, "project_root is required");
|
|
9046
|
+
}
|
|
9047
|
+
await resolveAuthContextFree(policyArgs.auth_token);
|
|
9048
|
+
const env = process.env.CUTLINE_ENV === "staging" ? "staging" : "production";
|
|
9049
|
+
const resolvedInstallId = policyArgs.install_id || getStoredInstallId({ environment: env });
|
|
9050
|
+
const telemetryAttribution = resolveTelemetryAttribution({
|
|
9051
|
+
providedInstallId: policyArgs.install_id,
|
|
9052
|
+
resolvedInstallId: resolvedInstallId || void 0
|
|
9053
|
+
});
|
|
9054
|
+
const { existsSync, readFileSync } = await import("node:fs");
|
|
9055
|
+
const { resolve, join: join4 } = await import("node:path");
|
|
9056
|
+
const manifestPath = resolve(policyArgs.manifest_path || join4(policyArgs.project_root, "cutline.json"));
|
|
9057
|
+
if (!existsSync(manifestPath)) {
|
|
9058
|
+
await emitPolicyGateEvent({
|
|
9059
|
+
authToken: policyArgs.auth_token,
|
|
9060
|
+
installId: resolvedInstallId || void 0,
|
|
9061
|
+
status: "invalid_policy",
|
|
9062
|
+
policyName: "cutline-policy",
|
|
9063
|
+
failedChecks: 1,
|
|
9064
|
+
unknownChecks: 0,
|
|
9065
|
+
blockingReasonsCount: 1
|
|
9066
|
+
});
|
|
9067
|
+
return {
|
|
9068
|
+
content: [{
|
|
9069
|
+
type: "text",
|
|
9070
|
+
text: JSON.stringify(buildInvalidPolicyResponse({
|
|
9071
|
+
manifestPath,
|
|
9072
|
+
telemetryAttribution
|
|
9073
|
+
}), null, 2)
|
|
9074
|
+
}]
|
|
9075
|
+
};
|
|
9076
|
+
}
|
|
9077
|
+
let manifest;
|
|
9078
|
+
try {
|
|
9079
|
+
manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
|
|
9080
|
+
} catch (e) {
|
|
9081
|
+
throw new McpError(ErrorCode.InvalidParams, `Invalid cutline.json: ${e?.message || String(e)}`);
|
|
9082
|
+
}
|
|
9083
|
+
const requirements = buildRepoPolicyRequirements(manifest);
|
|
9084
|
+
const observed = buildRepoPolicyObserved(policyArgs.observed);
|
|
9085
|
+
const verdict = evaluateRepoPolicy(requirements, observed);
|
|
9086
|
+
const failedChecks = verdict.checks.filter((c) => c.status === "fail").length;
|
|
9087
|
+
const unknownChecks = verdict.checks.filter((c) => c.status === "unknown").length;
|
|
9088
|
+
await emitPolicyGateEvent({
|
|
9089
|
+
authToken: policyArgs.auth_token,
|
|
9090
|
+
installId: resolvedInstallId || void 0,
|
|
9091
|
+
status: verdict.status,
|
|
9092
|
+
policyName: manifest?.policy_name || "cutline-policy",
|
|
9093
|
+
failedChecks,
|
|
9094
|
+
unknownChecks,
|
|
9095
|
+
blockingReasonsCount: verdict.blocking_reasons.length
|
|
9096
|
+
});
|
|
9097
|
+
return {
|
|
9098
|
+
content: [{
|
|
9099
|
+
type: "text",
|
|
9100
|
+
text: JSON.stringify(buildPolicyVerdictResponse({
|
|
9101
|
+
verdict,
|
|
9102
|
+
manifestPath,
|
|
9103
|
+
policyName: manifest?.policy_name,
|
|
9104
|
+
schemaVersion: manifest?.schema_version,
|
|
9105
|
+
observedEvidence: policyArgs.observed || {},
|
|
9106
|
+
telemetryAttribution
|
|
9107
|
+
}), null, 2)
|
|
9108
|
+
}]
|
|
9109
|
+
};
|
|
8687
9110
|
}
|
|
8688
9111
|
if (name2 === "code_audit") {
|
|
8689
9112
|
const scanArgs = args;
|
|
@@ -8984,9 +9407,56 @@ Competitive threats: ${competitors}` : ""
|
|
|
8984
9407
|
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
8985
9408
|
}
|
|
8986
9409
|
case "agent_chat": {
|
|
8987
|
-
const
|
|
9410
|
+
const chatArgs = args;
|
|
9411
|
+
const { prompt, wikiMarkdown } = chatArgs;
|
|
8988
9412
|
const text = await cfGenerateChatSuggestion(prompt, wikiMarkdown);
|
|
8989
|
-
|
|
9413
|
+
const isTestingIntent = isTestingIntentPrompt(prompt);
|
|
9414
|
+
const tokenIntent = TOKEN_INTENT_HINT_ENABLED ? detectTokenLimitIntentPrompt(prompt) : { detected: false };
|
|
9415
|
+
const env = process.env.CUTLINE_ENV === "staging" ? "staging" : "production";
|
|
9416
|
+
const resolvedInstallId = chatArgs.install_id || getStoredInstallId({ environment: env });
|
|
9417
|
+
if (isTestingIntent) {
|
|
9418
|
+
await emitAgentEvent({
|
|
9419
|
+
authToken: chatArgs.auth_token,
|
|
9420
|
+
installId: resolvedInstallId || void 0,
|
|
9421
|
+
eventName: "agent_test_intent_detected",
|
|
9422
|
+
eventProperties: {
|
|
9423
|
+
tool_name: "agent_chat",
|
|
9424
|
+
route: "cutline_testing_rgr"
|
|
9425
|
+
}
|
|
9426
|
+
});
|
|
9427
|
+
}
|
|
9428
|
+
if (tokenIntent.detected) {
|
|
9429
|
+
await emitAgentEvent({
|
|
9430
|
+
authToken: chatArgs.auth_token,
|
|
9431
|
+
installId: resolvedInstallId || void 0,
|
|
9432
|
+
eventName: "agent_token_limit_intent_detected",
|
|
9433
|
+
eventProperties: {
|
|
9434
|
+
tool_name: "agent_chat",
|
|
9435
|
+
route: "cutline_token_layer_install",
|
|
9436
|
+
query_cluster: tokenIntent.cluster
|
|
9437
|
+
}
|
|
9438
|
+
});
|
|
9439
|
+
await emitAgentEvent({
|
|
9440
|
+
authToken: chatArgs.auth_token,
|
|
9441
|
+
installId: resolvedInstallId || void 0,
|
|
9442
|
+
eventName: "agent_token_layer_install_prompted",
|
|
9443
|
+
eventProperties: {
|
|
9444
|
+
tool_name: "agent_chat",
|
|
9445
|
+
route: "cutline_token_layer_install",
|
|
9446
|
+
query_cluster: tokenIntent.cluster
|
|
9447
|
+
}
|
|
9448
|
+
});
|
|
9449
|
+
}
|
|
9450
|
+
return {
|
|
9451
|
+
content: [{
|
|
9452
|
+
type: "text",
|
|
9453
|
+
text: JSON.stringify({
|
|
9454
|
+
text,
|
|
9455
|
+
...isTestingIntent ? { cutline_testing_route: buildTestingIntentHintResponse(prompt) } : {},
|
|
9456
|
+
...tokenIntent.detected ? { cutline_token_route: buildTokenLimitIntentHintResponse(prompt, tokenIntent) } : {}
|
|
9457
|
+
})
|
|
9458
|
+
}]
|
|
9459
|
+
};
|
|
8990
9460
|
}
|
|
8991
9461
|
// Integrations
|
|
8992
9462
|
case "integrations_create_issues": {
|
|
@@ -9344,7 +9814,7 @@ Meta: ${JSON.stringify(output.meta)}` }
|
|
|
9344
9814
|
}
|
|
9345
9815
|
const analysis = analyzeFileContext(fileContext);
|
|
9346
9816
|
let knownEntityNames = [];
|
|
9347
|
-
if (task_description || scope_entity_name) {
|
|
9817
|
+
if (task_description || code_snippet || scope_entity_name) {
|
|
9348
9818
|
try {
|
|
9349
9819
|
const entities = await getAllEntities(product_id);
|
|
9350
9820
|
knownEntityNames = entities.map((e) => e.name);
|
|
@@ -9353,6 +9823,7 @@ Meta: ${JSON.stringify(output.meta)}` }
|
|
|
9353
9823
|
}
|
|
9354
9824
|
const scopeExpansion = assessScopeExpansionIntent({
|
|
9355
9825
|
task_description,
|
|
9826
|
+
code_snippet,
|
|
9356
9827
|
file_paths,
|
|
9357
9828
|
known_entity_names: knownEntityNames,
|
|
9358
9829
|
hinted_entity_name: scope_entity_name
|
|
@@ -9481,9 +9952,8 @@ Meta: ${JSON.stringify(output.meta)}` }
|
|
|
9481
9952
|
} catch (e) {
|
|
9482
9953
|
}
|
|
9483
9954
|
}
|
|
9484
|
-
|
|
9955
|
+
const autoRgrPlan = planRgrPhases(finalResults);
|
|
9485
9956
|
if (autoPhase === "auto") {
|
|
9486
|
-
autoRgrPlan = planRgrPhases(finalResults);
|
|
9487
9957
|
if (autoRgrPlan.strategy === "phased") {
|
|
9488
9958
|
finalResults = finalResults.filter((c) => {
|
|
9489
9959
|
const phased = filterByPhase([c], "test_spec");
|
|
@@ -10555,6 +11025,11 @@ ${JSON.stringify(metrics, null, 2)}` }
|
|
|
10555
11025
|
...plan,
|
|
10556
11026
|
entity: rgrMatched[0].name,
|
|
10557
11027
|
complexity,
|
|
11028
|
+
auto_execution: {
|
|
11029
|
+
scope_increase_triggers_rgr: true,
|
|
11030
|
+
mode: "pervasive",
|
|
11031
|
+
expected_runner: "local_vitest"
|
|
11032
|
+
},
|
|
10558
11033
|
governance
|
|
10559
11034
|
}, null, 2)
|
|
10560
11035
|
}]
|
|
@@ -11081,7 +11556,7 @@ Meta: ${JSON.stringify({
|
|
|
11081
11556
|
mode: "product",
|
|
11082
11557
|
codeContext: payload.codeContext || null
|
|
11083
11558
|
});
|
|
11084
|
-
runInput.productId = newJobId;
|
|
11559
|
+
runInput.productId = String(auditArgs.product_id || newJobId);
|
|
11085
11560
|
await updatePremortem(newJobId, { payload: runInput });
|
|
11086
11561
|
return { jobId: newJobId };
|
|
11087
11562
|
}
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
requirePremiumWithAutoAuth,
|
|
15
15
|
updateExplorationSession,
|
|
16
16
|
validateRequestSize
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-RUCYK3TR.js";
|
|
18
18
|
|
|
19
19
|
// ../mcp/dist/mcp/src/exploration-server.js
|
|
20
20
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
requirePremiumWithAutoAuth,
|
|
14
14
|
validateAuth,
|
|
15
15
|
validateRequestSize
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-RUCYK3TR.js";
|
|
17
17
|
|
|
18
18
|
// ../mcp/dist/mcp/src/integrations-server.js
|
|
19
19
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
mapErrorToMcp,
|
|
14
14
|
requirePremiumWithAutoAuth,
|
|
15
15
|
validateRequestSize
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-RUCYK3TR.js";
|
|
17
17
|
|
|
18
18
|
// ../mcp/dist/mcp/src/output-server.js
|
|
19
19
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
requirePremiumWithAutoAuth,
|
|
22
22
|
validateAuth,
|
|
23
23
|
validateRequestSize
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-RUCYK3TR.js";
|
|
25
25
|
|
|
26
26
|
// ../mcp/dist/mcp/src/tools-server.js
|
|
27
27
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
type AgentEventName = 'install_completed' | 'first_tool_call_success' | 'tool_call_failed' | 'heartbeat' | 'policy_gate_passed' | 'policy_gate_blocked' | 'upgrade_clicked' | 'upgrade_completed' | 'agent_test_intent_detected' | 'agent_cutline_install_prompted' | 'agent_cutline_install_completed' | 'agent_first_rgr_test_call_success' | 'agent_token_limit_intent_detected' | 'agent_token_layer_install_prompted' | 'agent_token_layer_install_completed';
|
|
2
|
+
export declare function registerAgentInstall(input: {
|
|
3
|
+
idToken: string;
|
|
4
|
+
staging?: boolean;
|
|
5
|
+
projectRoot: string;
|
|
6
|
+
sourceSurface: string;
|
|
7
|
+
hostAgent?: string;
|
|
8
|
+
campaign?: string;
|
|
9
|
+
referrerUrl?: string;
|
|
10
|
+
metadata?: Record<string, unknown>;
|
|
11
|
+
}): Promise<string | null>;
|
|
12
|
+
export declare function trackAgentEvent(input: {
|
|
13
|
+
idToken: string;
|
|
14
|
+
installId: string;
|
|
15
|
+
eventName: AgentEventName;
|
|
16
|
+
eventProperties?: Record<string, unknown>;
|
|
17
|
+
staging?: boolean;
|
|
18
|
+
}): Promise<void>;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { getConfig } from './config.js';
|
|
3
|
+
import { loadConfig, saveConfig } from './config-store.js';
|
|
4
|
+
function installIdKey(staging) {
|
|
5
|
+
return staging ? 'agentInstallIdStaging' : 'agentInstallId';
|
|
6
|
+
}
|
|
7
|
+
export async function registerAgentInstall(input) {
|
|
8
|
+
const key = installIdKey(input.staging);
|
|
9
|
+
const existing = loadConfig();
|
|
10
|
+
const persistedInstallId = typeof existing[key] === 'string' ? existing[key] : null;
|
|
11
|
+
if (persistedInstallId)
|
|
12
|
+
return persistedInstallId;
|
|
13
|
+
const { BASE_URL } = getConfig({ staging: input.staging });
|
|
14
|
+
const installationKey = createHash('sha256')
|
|
15
|
+
.update(`${input.projectRoot}:${input.staging ? 'staging' : 'production'}`)
|
|
16
|
+
.digest('hex')
|
|
17
|
+
.slice(0, 32);
|
|
18
|
+
const workspaceId = createHash('sha256').update(input.projectRoot).digest('hex').slice(0, 24);
|
|
19
|
+
try {
|
|
20
|
+
const response = await fetch(`${BASE_URL}/api/agent/register`, {
|
|
21
|
+
method: 'POST',
|
|
22
|
+
headers: {
|
|
23
|
+
'Content-Type': 'application/json',
|
|
24
|
+
Authorization: `Bearer ${input.idToken}`,
|
|
25
|
+
},
|
|
26
|
+
body: JSON.stringify({
|
|
27
|
+
installation_key: installationKey,
|
|
28
|
+
source_surface: input.sourceSurface,
|
|
29
|
+
host_agent: input.hostAgent || 'cutline-mcp-cli',
|
|
30
|
+
workspace_id: workspaceId,
|
|
31
|
+
...(input.campaign ? { campaign: input.campaign } : {}),
|
|
32
|
+
...(input.referrerUrl ? { referrer_url: input.referrerUrl } : {}),
|
|
33
|
+
...(input.metadata ? { metadata: input.metadata } : {}),
|
|
34
|
+
}),
|
|
35
|
+
});
|
|
36
|
+
if (!response.ok)
|
|
37
|
+
return null;
|
|
38
|
+
const payload = await response.json();
|
|
39
|
+
if (!payload.install_id)
|
|
40
|
+
return null;
|
|
41
|
+
saveConfig({ [key]: payload.install_id });
|
|
42
|
+
return payload.install_id;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export async function trackAgentEvent(input) {
|
|
49
|
+
const { BASE_URL } = getConfig({ staging: input.staging });
|
|
50
|
+
try {
|
|
51
|
+
await fetch(`${BASE_URL}/api/agent/event`, {
|
|
52
|
+
method: 'POST',
|
|
53
|
+
headers: {
|
|
54
|
+
'Content-Type': 'application/json',
|
|
55
|
+
Authorization: `Bearer ${input.idToken}`,
|
|
56
|
+
},
|
|
57
|
+
body: JSON.stringify({
|
|
58
|
+
install_id: input.installId,
|
|
59
|
+
event_name: input.eventName,
|
|
60
|
+
event_properties: input.eventProperties || {},
|
|
61
|
+
}),
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
// Best effort telemetry; never block CLI command success.
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -2,6 +2,8 @@ export interface McpConfig {
|
|
|
2
2
|
refreshToken?: string;
|
|
3
3
|
environment?: 'production' | 'staging';
|
|
4
4
|
apiKey?: string;
|
|
5
|
+
agentInstallId?: string;
|
|
6
|
+
agentInstallIdStaging?: string;
|
|
5
7
|
}
|
|
6
8
|
export declare function saveConfig(config: McpConfig): void;
|
|
7
9
|
export declare function loadConfig(): McpConfig;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibekiln/cutline-mcp-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "CLI and MCP servers for Cutline — authenticate, then run constraint-aware MCP servers in Cursor or any MCP client.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|