@vibekiln/cutline-mcp-cli 0.4.1 → 0.4.3
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/dist/commands/init.js +6 -0
- package/dist/commands/setup.js +54 -10
- package/dist/servers/{chunk-6Y3AEXE3.js → chunk-X2B5QUWO.js} +30 -16
- package/dist/servers/cutline-server.js +10 -3
- package/dist/servers/{data-client-PPEF2XUI.js → data-client-AQ5DGSAR.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/package.json +1 -1
package/dist/commands/init.js
CHANGED
|
@@ -204,6 +204,9 @@ function ensureGitignore(projectRoot, patterns) {
|
|
|
204
204
|
export async function initCommand(options) {
|
|
205
205
|
const projectRoot = resolve(options.projectRoot ?? process.cwd());
|
|
206
206
|
const config = readCutlineConfig(projectRoot);
|
|
207
|
+
const scanUrl = options.staging
|
|
208
|
+
? 'https://cutline-staging.web.app/scan'
|
|
209
|
+
: 'https://thecutline.ai/scan';
|
|
207
210
|
console.log(chalk.bold('\n🔧 Cutline Init\n'));
|
|
208
211
|
// Authenticate and determine tier
|
|
209
212
|
const spinner = ora('Checking authentication...').start();
|
|
@@ -212,6 +215,8 @@ export async function initCommand(options) {
|
|
|
212
215
|
if (!auth) {
|
|
213
216
|
spinner.warn(chalk.yellow('Not authenticated — generating free-tier rules'));
|
|
214
217
|
console.log(chalk.dim(' Run `cutline-mcp login` to authenticate for richer config.\n'));
|
|
218
|
+
console.log(chalk.dim(' Or run a quick scan in the web app:'), chalk.cyan(scanUrl));
|
|
219
|
+
console.log();
|
|
215
220
|
}
|
|
216
221
|
else {
|
|
217
222
|
tier = auth.tier;
|
|
@@ -292,6 +297,7 @@ export async function initCommand(options) {
|
|
|
292
297
|
else if (tier === 'free') {
|
|
293
298
|
console.log(chalk.dim('\n Upgrade to Premium for product-specific constraint graphs and .cutline.md'));
|
|
294
299
|
console.log(chalk.dim(' →'), chalk.cyan('cutline-mcp upgrade'), chalk.dim('or https://thecutline.ai/upgrade'));
|
|
300
|
+
console.log(chalk.dim(' Free scan in browser:'), chalk.cyan(scanUrl));
|
|
295
301
|
}
|
|
296
302
|
if (generatedApiKey) {
|
|
297
303
|
console.log();
|
package/dist/commands/setup.js
CHANGED
|
@@ -67,12 +67,12 @@ async function fetchProducts(idToken, options) {
|
|
|
67
67
|
headers: { Authorization: `Bearer ${idToken}` },
|
|
68
68
|
});
|
|
69
69
|
if (!res.ok)
|
|
70
|
-
return [];
|
|
70
|
+
return { products: [], requestOk: false };
|
|
71
71
|
const data = await res.json();
|
|
72
|
-
return data.products ?? [];
|
|
72
|
+
return { products: data.products ?? [], requestOk: true };
|
|
73
73
|
}
|
|
74
74
|
catch {
|
|
75
|
-
return [];
|
|
75
|
+
return { products: [], requestOk: false };
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
78
|
function prompt(question) {
|
|
@@ -237,12 +237,51 @@ export async function setupCommand(options) {
|
|
|
237
237
|
const projectRoot = resolve(options.projectRoot ?? process.cwd());
|
|
238
238
|
const configPath = join(projectRoot, '.cutline', 'config.json');
|
|
239
239
|
const hasExistingConfig = existsSync(configPath);
|
|
240
|
-
let
|
|
241
|
-
|
|
240
|
+
let existingConfig = null;
|
|
241
|
+
let hasUsableExistingConfig = false;
|
|
242
|
+
let graphConnected = false;
|
|
243
|
+
if (hasExistingConfig) {
|
|
244
|
+
try {
|
|
245
|
+
existingConfig = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
246
|
+
hasUsableExistingConfig = Boolean(existingConfig?.product_id);
|
|
247
|
+
graphConnected = hasUsableExistingConfig;
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
existingConfig = null;
|
|
251
|
+
hasUsableExistingConfig = false;
|
|
252
|
+
graphConnected = false;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
// Validate that an existing binding is visible to the current account.
|
|
256
|
+
// This prevents showing "connected" when the repo has a product_id from
|
|
257
|
+
// a different account.
|
|
258
|
+
if (hasUsableExistingConfig && idToken) {
|
|
259
|
+
const currentEmail = (email ?? '').trim().toLowerCase();
|
|
260
|
+
const boundEmail = String(existingConfig?.linked_email ?? '').trim().toLowerCase();
|
|
261
|
+
if (boundEmail && currentEmail && boundEmail !== currentEmail) {
|
|
262
|
+
graphConnected = false;
|
|
263
|
+
hasUsableExistingConfig = false;
|
|
264
|
+
console.log(chalk.yellow(` Existing product binding is for ${existingConfig?.linked_email}, not ${email}.`));
|
|
265
|
+
console.log(chalk.dim(' Will not use this graph for the current account.\n'));
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
const { products, requestOk } = await fetchProducts(idToken, { staging: options.staging });
|
|
269
|
+
if (requestOk) {
|
|
270
|
+
const canAccessBoundProduct = products.some((p) => p.id === existingConfig?.product_id);
|
|
271
|
+
if (!canAccessBoundProduct) {
|
|
272
|
+
graphConnected = false;
|
|
273
|
+
hasUsableExistingConfig = false;
|
|
274
|
+
console.log(chalk.yellow(` Existing product graph is not accessible from ${email}.`));
|
|
275
|
+
console.log(chalk.dim(' Re-linking is required for this account.\n'));
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
if (tier === 'premium' && idToken && !hasUsableExistingConfig) {
|
|
242
281
|
const productSpinner = ora('Fetching your product graphs...').start();
|
|
243
|
-
const products = await fetchProducts(idToken, { staging: options.staging });
|
|
282
|
+
const { products, requestOk } = await fetchProducts(idToken, { staging: options.staging });
|
|
244
283
|
productSpinner.stop();
|
|
245
|
-
if (products.length > 0) {
|
|
284
|
+
if (requestOk && products.length > 0) {
|
|
246
285
|
console.log(chalk.bold(' Connect to a product graph\n'));
|
|
247
286
|
products.forEach((p, i) => {
|
|
248
287
|
const date = p.createdAt ? chalk.dim(` (${new Date(p.createdAt).toLocaleDateString()})`) : '';
|
|
@@ -260,6 +299,7 @@ export async function setupCommand(options) {
|
|
|
260
299
|
writeFileSync(configPath, JSON.stringify({
|
|
261
300
|
product_id: selected.id,
|
|
262
301
|
product_name: selected.name,
|
|
302
|
+
linked_email: email ?? null,
|
|
263
303
|
}, null, 2) + '\n');
|
|
264
304
|
console.log(chalk.green(`\n ✓ Connected to "${selected.name}"`));
|
|
265
305
|
console.log(chalk.dim(` ${configPath}\n`));
|
|
@@ -269,16 +309,20 @@ export async function setupCommand(options) {
|
|
|
269
309
|
console.log(chalk.dim('\n Skipped. Run `cutline-mcp setup` again to connect later.\n'));
|
|
270
310
|
}
|
|
271
311
|
}
|
|
272
|
-
else {
|
|
312
|
+
else if (requestOk) {
|
|
273
313
|
console.log(chalk.dim(' No completed product graphs found.'));
|
|
274
314
|
console.log(chalk.dim(' Ask your AI agent to "Run a deep dive on my product idea" first,'));
|
|
275
315
|
console.log(chalk.dim(' then re-run'), chalk.cyan('cutline-mcp setup'), chalk.dim('to connect it.'));
|
|
276
316
|
console.log();
|
|
277
317
|
}
|
|
318
|
+
else {
|
|
319
|
+
console.log(chalk.yellow(' Could not verify product graphs (network/auth issue).'));
|
|
320
|
+
console.log(chalk.dim(' Re-run'), chalk.cyan('cutline-mcp setup'), chalk.dim('after auth/network is healthy.\n'));
|
|
321
|
+
}
|
|
278
322
|
}
|
|
279
|
-
else if (
|
|
323
|
+
else if (hasUsableExistingConfig) {
|
|
280
324
|
try {
|
|
281
|
-
const existing = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
325
|
+
const existing = existingConfig ?? JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
282
326
|
console.log(chalk.green(` ✓ Connected to product graph:`), chalk.white(existing.product_name || existing.product_id));
|
|
283
327
|
console.log();
|
|
284
328
|
}
|
|
@@ -305,13 +305,17 @@ 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 getStoredApiKey() {
|
|
308
|
+
function getStoredApiKey(options) {
|
|
309
|
+
const includeConfig = options?.includeConfig ?? true;
|
|
309
310
|
if (process.env.CUTLINE_API_KEY) {
|
|
310
311
|
return {
|
|
311
312
|
apiKey: process.env.CUTLINE_API_KEY,
|
|
312
313
|
environment: process.env.CUTLINE_ENV === "staging" ? "staging" : "production"
|
|
313
314
|
};
|
|
314
315
|
}
|
|
316
|
+
if (!includeConfig) {
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
315
319
|
const config = readLocalCutlineConfig();
|
|
316
320
|
if (config?.apiKey) {
|
|
317
321
|
return { apiKey: config.apiKey, environment: config.environment };
|
|
@@ -406,12 +410,15 @@ function getSiteUrl(environment) {
|
|
|
406
410
|
return environment === "staging" ? "https://cutline-staging.web.app" : "https://thecutline.ai";
|
|
407
411
|
}
|
|
408
412
|
async function getPublicSiteUrlForCurrentAuth() {
|
|
413
|
+
const stored = await getStoredToken();
|
|
414
|
+
if (stored?.environment) {
|
|
415
|
+
return getSiteUrl(stored.environment);
|
|
416
|
+
}
|
|
409
417
|
const apiKeyInfo = getStoredApiKey();
|
|
410
418
|
if (apiKeyInfo?.environment) {
|
|
411
419
|
return getSiteUrl(apiKeyInfo.environment);
|
|
412
420
|
}
|
|
413
|
-
|
|
414
|
-
return getSiteUrl(stored?.environment);
|
|
421
|
+
return getSiteUrl();
|
|
415
422
|
}
|
|
416
423
|
var cachedBaseUrl;
|
|
417
424
|
var cachedIdToken;
|
|
@@ -455,27 +462,34 @@ async function exchangeRefreshForId(refreshToken, environment) {
|
|
|
455
462
|
return data.id_token;
|
|
456
463
|
}
|
|
457
464
|
async function resolveAuth() {
|
|
458
|
-
const
|
|
459
|
-
if (
|
|
465
|
+
const envApiKeyInfo = getStoredApiKey({ includeConfig: false });
|
|
466
|
+
if (envApiKeyInfo) {
|
|
460
467
|
return {
|
|
461
|
-
baseUrl: getBaseUrl(
|
|
462
|
-
idToken:
|
|
468
|
+
baseUrl: getBaseUrl(envApiKeyInfo.environment),
|
|
469
|
+
idToken: envApiKeyInfo.apiKey
|
|
463
470
|
};
|
|
464
471
|
}
|
|
465
472
|
if (cachedIdToken && Date.now() < tokenExpiresAt && cachedBaseUrl) {
|
|
466
473
|
return { baseUrl: cachedBaseUrl, idToken: cachedIdToken };
|
|
467
474
|
}
|
|
468
475
|
const stored = await getStoredToken();
|
|
469
|
-
if (
|
|
470
|
-
|
|
476
|
+
if (stored) {
|
|
477
|
+
const env = stored.environment || "production";
|
|
478
|
+
const baseUrl = getBaseUrl(env);
|
|
479
|
+
const idToken = await exchangeRefreshForId(stored.refreshToken, env);
|
|
480
|
+
cachedBaseUrl = baseUrl;
|
|
481
|
+
cachedIdToken = idToken;
|
|
482
|
+
tokenExpiresAt = Date.now() + 50 * 60 * 1e3;
|
|
483
|
+
return { baseUrl, idToken };
|
|
484
|
+
}
|
|
485
|
+
const configApiKeyInfo = getStoredApiKey();
|
|
486
|
+
if (configApiKeyInfo) {
|
|
487
|
+
return {
|
|
488
|
+
baseUrl: getBaseUrl(configApiKeyInfo.environment),
|
|
489
|
+
idToken: configApiKeyInfo.apiKey
|
|
490
|
+
};
|
|
471
491
|
}
|
|
472
|
-
|
|
473
|
-
const baseUrl = getBaseUrl(env);
|
|
474
|
-
const idToken = await exchangeRefreshForId(stored.refreshToken, env);
|
|
475
|
-
cachedBaseUrl = baseUrl;
|
|
476
|
-
cachedIdToken = idToken;
|
|
477
|
-
tokenExpiresAt = Date.now() + 50 * 60 * 1e3;
|
|
478
|
-
return { baseUrl, idToken };
|
|
492
|
+
throw new Error("Not authenticated. Run 'cutline-mcp login' or set CUTLINE_API_KEY.");
|
|
479
493
|
}
|
|
480
494
|
async function proxy(action, params = {}) {
|
|
481
495
|
const { baseUrl, idToken } = await resolveAuth();
|
|
@@ -75,7 +75,7 @@ import {
|
|
|
75
75
|
upsertEntities,
|
|
76
76
|
upsertNodes,
|
|
77
77
|
validateRequestSize
|
|
78
|
-
} from "./chunk-
|
|
78
|
+
} from "./chunk-X2B5QUWO.js";
|
|
79
79
|
import {
|
|
80
80
|
GraphTraverser,
|
|
81
81
|
computeGenericGraphMetrics,
|
|
@@ -7076,6 +7076,12 @@ async function handleSecurityScan(genericGraphId, uid, args, deps) {
|
|
|
7076
7076
|
total: scaFindings.length,
|
|
7077
7077
|
critical: scaFindings.filter((f) => f.severity === "critical").length,
|
|
7078
7078
|
high: scaFindings.filter((f) => f.severity === "high").length,
|
|
7079
|
+
all: scaFindings.map((f) => ({
|
|
7080
|
+
id: f.id,
|
|
7081
|
+
package: `${f.package_name}@${f.package_version}`,
|
|
7082
|
+
severity: f.severity,
|
|
7083
|
+
fix: f.fixed_version ? `Upgrade to ${f.fixed_version}` : void 0
|
|
7084
|
+
})),
|
|
7079
7085
|
top: scaFindings.slice(0, 5).map((f) => ({
|
|
7080
7086
|
id: f.id,
|
|
7081
7087
|
package: `${f.package_name}@${f.package_version}`,
|
|
@@ -7169,11 +7175,12 @@ function formatAuditOutput(result, reportId, publicSiteUrl = "https://thecutline
|
|
|
7169
7175
|
if (securityVisible && result.scaFindings && result.scaFindings.total > 0) {
|
|
7170
7176
|
const sca = result.scaFindings;
|
|
7171
7177
|
lines.push(``, `## Dependency Vulnerabilities (SCA)`, ``, `**${sca.total} known vulnerabilities** found (${sca.critical} critical, ${sca.high} high)`, ``);
|
|
7172
|
-
|
|
7178
|
+
const scaEntries = fullReport && sca.all?.length ? sca.all : sca.top;
|
|
7179
|
+
for (const v of scaEntries) {
|
|
7173
7180
|
const fix = v.fix ? ` \u2192 ${v.fix}` : "";
|
|
7174
7181
|
lines.push(`- **[${v.severity.toUpperCase()}]** ${v.package}: ${v.id}${fix}`);
|
|
7175
7182
|
}
|
|
7176
|
-
if (sca.total > sca.top.length) {
|
|
7183
|
+
if (!fullReport && sca.total > sca.top.length) {
|
|
7177
7184
|
lines.push(`- ...and ${sca.total - sca.top.length} more`);
|
|
7178
7185
|
}
|
|
7179
7186
|
}
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
requirePremiumWithAutoAuth,
|
|
15
15
|
updateExplorationSession,
|
|
16
16
|
validateRequestSize
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-X2B5QUWO.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-X2B5QUWO.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-X2B5QUWO.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-X2B5QUWO.js";
|
|
25
25
|
|
|
26
26
|
// ../mcp/dist/mcp/src/tools-server.js
|
|
27
27
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
package/package.json
CHANGED