guardlink 1.0.0 → 1.1.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/CHANGELOG.md +28 -0
- package/README.md +14 -0
- package/dist/agents/config.d.ts +2 -0
- package/dist/agents/config.d.ts.map +1 -1
- package/dist/agents/config.js +1 -1
- package/dist/agents/config.js.map +1 -1
- package/dist/agents/prompts.d.ts +2 -2
- package/dist/agents/prompts.d.ts.map +1 -1
- package/dist/agents/prompts.js +223 -31
- package/dist/agents/prompts.js.map +1 -1
- package/dist/analyzer/sarif.js +1 -1
- package/dist/cli/index.js +2 -56
- package/dist/cli/index.js.map +1 -1
- package/dist/dashboard/data.d.ts.map +1 -1
- package/dist/dashboard/data.js +19 -12
- package/dist/dashboard/data.js.map +1 -1
- package/dist/dashboard/diagrams.d.ts.map +1 -1
- package/dist/dashboard/diagrams.js +310 -37
- package/dist/dashboard/diagrams.js.map +1 -1
- package/dist/dashboard/generate.d.ts.map +1 -1
- package/dist/dashboard/generate.js +21 -5
- package/dist/dashboard/generate.js.map +1 -1
- package/dist/init/picker.d.ts.map +1 -1
- package/dist/init/picker.js +2 -2
- package/dist/init/picker.js.map +1 -1
- package/dist/init/templates.js +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +8 -26
- package/dist/mcp/server.js.map +1 -1
- package/dist/parser/index.d.ts +1 -0
- package/dist/parser/index.d.ts.map +1 -1
- package/dist/parser/index.js +1 -0
- package/dist/parser/index.js.map +1 -1
- package/dist/parser/parse-line.js +3 -3
- package/dist/parser/parse-line.js.map +1 -1
- package/dist/parser/parse-project.js +1 -1
- package/dist/parser/validate.d.ts +19 -0
- package/dist/parser/validate.d.ts.map +1 -0
- package/dist/parser/validate.js +105 -0
- package/dist/parser/validate.js.map +1 -0
- package/dist/tui/commands.d.ts +1 -6
- package/dist/tui/commands.d.ts.map +1 -1
- package/dist/tui/commands.js +96 -221
- package/dist/tui/commands.js.map +1 -1
- package/dist/tui/config.d.ts +2 -0
- package/dist/tui/config.d.ts.map +1 -1
- package/dist/tui/config.js.map +1 -1
- package/dist/tui/index.d.ts.map +1 -1
- package/dist/tui/index.js +20 -24
- package/dist/tui/index.js.map +1 -1
- package/dist/tui/input.d.ts +2 -2
- package/dist/tui/input.js +2 -2
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/tui/commands.js
CHANGED
|
@@ -6,16 +6,16 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { resolve, basename } from 'node:path';
|
|
8
8
|
import { writeFileSync } from 'node:fs';
|
|
9
|
-
import { parseProject } from '../parser/index.js';
|
|
9
|
+
import { parseProject, findDanglingRefs, findUnmitigatedExposures } from '../parser/index.js';
|
|
10
10
|
import { initProject, detectProject, promptAgentSelection } from '../init/index.js';
|
|
11
11
|
import { generateReport } from '../report/index.js';
|
|
12
12
|
import { generateDashboardHTML } from '../dashboard/index.js';
|
|
13
|
-
import { computeStats, computeSeverity
|
|
13
|
+
import { computeStats, computeSeverity } from '../dashboard/data.js';
|
|
14
14
|
import { generateThreatReport, serializeModel, listThreatReports, loadThreatReportsForDashboard, FRAMEWORK_LABELS, FRAMEWORK_PROMPTS, buildUserMessage } from '../analyze/index.js';
|
|
15
15
|
import { diffModels, formatDiff, parseAtRef } from '../diff/index.js';
|
|
16
16
|
import { generateSarif } from '../analyzer/index.js';
|
|
17
|
-
import { C, severityBadge, severityText,
|
|
18
|
-
import { resolveLLMConfig, saveTuiConfig } from './config.js';
|
|
17
|
+
import { C, severityBadge, severityText, severityOrder, computeGrade, gradeColored, readCodeContext, bar, fileLink } from './format.js';
|
|
18
|
+
import { resolveLLMConfig, saveTuiConfig, loadTuiConfig } from './config.js';
|
|
19
19
|
import { AGENTS, parseAgentFlag, launchAgent, copyToClipboard, buildAnnotatePrompt } from '../agents/index.js';
|
|
20
20
|
import { describeConfigSource } from '../agents/config.js';
|
|
21
21
|
// ─── Shared context ──────────────────────────────────────────────────
|
|
@@ -61,11 +61,8 @@ export function cmdHelp() {
|
|
|
61
61
|
['/init [name]', 'Initialize GuardLink in this project'],
|
|
62
62
|
['/parse', 'Parse annotations, build threat model'],
|
|
63
63
|
['/status', 'Risk grade + summary stats'],
|
|
64
|
-
['/scan', 'Find unannotated security-relevant functions'],
|
|
65
64
|
['/validate [--strict]', 'Check for syntax errors + dangling refs'],
|
|
66
65
|
['', ''],
|
|
67
|
-
['/exposures [flags]', 'List exposures (--asset, --severity, --file, --threat)'],
|
|
68
|
-
['/show <n>', 'Detail view of exposure #n with code context'],
|
|
69
66
|
['/assets', 'Asset tree with threat/control counts'],
|
|
70
67
|
['/files', 'Annotated file tree with exposure counts'],
|
|
71
68
|
['/view <file>', 'Show all annotations in a file with code context'],
|
|
@@ -279,102 +276,6 @@ export function cmdStatus(ctx) {
|
|
|
279
276
|
}
|
|
280
277
|
console.log('');
|
|
281
278
|
}
|
|
282
|
-
// ─── /exposures ──────────────────────────────────────────────────────
|
|
283
|
-
export function cmdExposures(args, ctx) {
|
|
284
|
-
if (!ctx.model) {
|
|
285
|
-
console.log(C.warn(' No threat model. Run /parse first.'));
|
|
286
|
-
return;
|
|
287
|
-
}
|
|
288
|
-
const rows = computeExposures(ctx.model);
|
|
289
|
-
let filtered = rows.filter(r => !r.mitigated && !r.accepted); // open only by default
|
|
290
|
-
// Parse flags
|
|
291
|
-
const parts = args.split(/\s+/).filter(Boolean);
|
|
292
|
-
let showAll = false;
|
|
293
|
-
for (let i = 0; i < parts.length; i++) {
|
|
294
|
-
const flag = parts[i];
|
|
295
|
-
const val = parts[i + 1];
|
|
296
|
-
if (flag === '--asset' && val) {
|
|
297
|
-
filtered = filtered.filter(r => r.asset.includes(val));
|
|
298
|
-
i++;
|
|
299
|
-
}
|
|
300
|
-
else if (flag === '--severity' && val) {
|
|
301
|
-
filtered = filtered.filter(r => r.severity === val.toLowerCase());
|
|
302
|
-
i++;
|
|
303
|
-
}
|
|
304
|
-
else if (flag === '--file' && val) {
|
|
305
|
-
filtered = filtered.filter(r => r.file.includes(val));
|
|
306
|
-
i++;
|
|
307
|
-
}
|
|
308
|
-
else if (flag === '--threat' && val) {
|
|
309
|
-
filtered = filtered.filter(r => r.threat.includes(val));
|
|
310
|
-
i++;
|
|
311
|
-
}
|
|
312
|
-
else if (flag === '--all') {
|
|
313
|
-
filtered = rows;
|
|
314
|
-
showAll = true;
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
// Sort by severity
|
|
318
|
-
filtered.sort((a, b) => severityOrder(a.severity) - severityOrder(b.severity));
|
|
319
|
-
// Cache for /show
|
|
320
|
-
ctx.lastExposures = filtered.map(r => {
|
|
321
|
-
const original = ctx.model.exposures.find(e => e.asset === r.asset && e.threat === r.threat && e.location.file === r.file && e.location.line === r.line);
|
|
322
|
-
return original;
|
|
323
|
-
}).filter(Boolean);
|
|
324
|
-
if (filtered.length === 0) {
|
|
325
|
-
console.log(C.green(' No matching exposures found.'));
|
|
326
|
-
return;
|
|
327
|
-
}
|
|
328
|
-
console.log('');
|
|
329
|
-
// Determine terminal width for adaptive layout
|
|
330
|
-
const termWidth = process.stdout.columns || 100;
|
|
331
|
-
// Manual table (we need colored cells which formatTable can't do directly)
|
|
332
|
-
const header = ` ${C.dim('#'.padEnd(4))}${C.dim('SEVERITY'.padEnd(12))}${C.dim('ASSET'.padEnd(18))}${C.dim('THREAT'.padEnd(20))}${C.dim('FILE'.padEnd(30))}${C.dim('LINE')}`;
|
|
333
|
-
console.log(header);
|
|
334
|
-
console.log(C.dim(' ' + '─'.repeat(Math.min(termWidth - 4, 96))));
|
|
335
|
-
for (const [i, r] of filtered.entries()) {
|
|
336
|
-
const num = String(i + 1).padEnd(4);
|
|
337
|
-
const sev = severityTextPad(r.severity, 12);
|
|
338
|
-
const asset = trunc(r.asset, 16).padEnd(18);
|
|
339
|
-
const threat = trunc(r.threat, 18).padEnd(20);
|
|
340
|
-
const linkedFile = fileLinkTrunc(r.file, 28, r.line, ctx.root);
|
|
341
|
-
const filePad = ' '.repeat(Math.max(0, 30 - trunc(r.file, 28).length));
|
|
342
|
-
const line = ` ${num}${sev}${asset}${threat}${linkedFile}${filePad}${r.line}`;
|
|
343
|
-
console.log(line);
|
|
344
|
-
}
|
|
345
|
-
console.log('');
|
|
346
|
-
const countMsg = showAll
|
|
347
|
-
? ` ${filtered.length} exposure(s) total`
|
|
348
|
-
: ` ${filtered.length} open exposure(s)`;
|
|
349
|
-
console.log(C.dim(countMsg + ' · /show <n> for detail · --asset --severity --threat --file to filter'));
|
|
350
|
-
console.log('');
|
|
351
|
-
}
|
|
352
|
-
// ─── /show ───────────────────────────────────────────────────────────
|
|
353
|
-
export function cmdShow(args, ctx) {
|
|
354
|
-
const num = parseInt(args.trim(), 10);
|
|
355
|
-
if (!num || num < 1 || num > ctx.lastExposures.length) {
|
|
356
|
-
console.log(C.warn(` Usage: /show <n> where n is 1-${ctx.lastExposures.length || '?'}. Run /exposures first.`));
|
|
357
|
-
return;
|
|
358
|
-
}
|
|
359
|
-
const exp = ctx.lastExposures[num - 1];
|
|
360
|
-
console.log('');
|
|
361
|
-
console.log(` ${C.cyan('┌')} ${exp.asset} → ${exp.threat} ${severityBadge(exp.severity)}`);
|
|
362
|
-
if (exp.description) {
|
|
363
|
-
console.log(` ${C.cyan('│')} ${exp.description}`);
|
|
364
|
-
}
|
|
365
|
-
if (exp.external_refs.length > 0) {
|
|
366
|
-
console.log(` ${C.cyan('│')} ${C.dim(exp.external_refs.join(' · '))}`);
|
|
367
|
-
}
|
|
368
|
-
console.log(` ${C.cyan('│')} ${C.dim(fileLink(exp.location.file, exp.location.line, ctx.root))}`);
|
|
369
|
-
console.log(` ${C.cyan('│')}`);
|
|
370
|
-
// Code context
|
|
371
|
-
const { lines } = readCodeContext(exp.location.file, exp.location.line, ctx.root);
|
|
372
|
-
for (const l of lines) {
|
|
373
|
-
console.log(` ${C.cyan('│')} ${l}`);
|
|
374
|
-
}
|
|
375
|
-
console.log(` ${C.cyan('└')}`);
|
|
376
|
-
console.log('');
|
|
377
|
-
}
|
|
378
279
|
// ─── /assets ─────────────────────────────────────────────────────────
|
|
379
280
|
export function cmdAssets(ctx) {
|
|
380
281
|
if (!ctx.model) {
|
|
@@ -671,33 +572,6 @@ export async function cmdParse(ctx) {
|
|
|
671
572
|
console.log(C.error(` ✗ Parse failed: ${err.message}`));
|
|
672
573
|
}
|
|
673
574
|
}
|
|
674
|
-
// ─── /scan ───────────────────────────────────────────────────────────
|
|
675
|
-
export function cmdScan(ctx) {
|
|
676
|
-
if (!ctx.model) {
|
|
677
|
-
console.log(C.warn(' No threat model. Run /parse first.'));
|
|
678
|
-
return;
|
|
679
|
-
}
|
|
680
|
-
const cov = ctx.model.coverage;
|
|
681
|
-
const pct = cov.coverage_percent;
|
|
682
|
-
console.log('');
|
|
683
|
-
console.log(` ${C.bold('Coverage:')} ${cov.annotated_symbols}/${cov.total_symbols} symbols (${pct}%)`);
|
|
684
|
-
const unannotated = cov.unannotated_critical || [];
|
|
685
|
-
if (unannotated.length === 0) {
|
|
686
|
-
console.log(C.green(' All security-relevant symbols are annotated!'));
|
|
687
|
-
}
|
|
688
|
-
else {
|
|
689
|
-
console.log(C.warn(` ${unannotated.length} unannotated symbol(s):`));
|
|
690
|
-
console.log('');
|
|
691
|
-
const show = unannotated.slice(0, 25);
|
|
692
|
-
for (const u of show) {
|
|
693
|
-
console.log(` ${C.dim(fileLink(u.file, u.line, ctx.root))} ${u.kind} ${C.bold(u.name)}`);
|
|
694
|
-
}
|
|
695
|
-
if (unannotated.length > 25) {
|
|
696
|
-
console.log(C.dim(` ... and ${unannotated.length - 25} more`));
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
console.log('');
|
|
700
|
-
}
|
|
701
575
|
// ─── /validate ───────────────────────────────────────────────────────
|
|
702
576
|
export async function cmdValidate(ctx) {
|
|
703
577
|
console.log(C.dim(' Checking annotations...'));
|
|
@@ -817,69 +691,32 @@ export async function cmdSarif(args, ctx) {
|
|
|
817
691
|
}
|
|
818
692
|
console.log('');
|
|
819
693
|
}
|
|
820
|
-
// ─── Helpers: validate ───────────────────────────────────────────────
|
|
821
|
-
function findDanglingRefs(model) {
|
|
822
|
-
const diagnostics = [];
|
|
823
|
-
const definedIds = new Set();
|
|
824
|
-
for (const a of model.assets)
|
|
825
|
-
if (a.id)
|
|
826
|
-
definedIds.add(a.id);
|
|
827
|
-
for (const t of model.threats)
|
|
828
|
-
if (t.id)
|
|
829
|
-
definedIds.add(t.id);
|
|
830
|
-
for (const c of model.controls)
|
|
831
|
-
if (c.id)
|
|
832
|
-
definedIds.add(c.id);
|
|
833
|
-
for (const b of model.boundaries)
|
|
834
|
-
if (b.id)
|
|
835
|
-
definedIds.add(b.id);
|
|
836
|
-
const checkRef = (ref, loc) => {
|
|
837
|
-
if (ref.startsWith('#')) {
|
|
838
|
-
const id = ref.slice(1);
|
|
839
|
-
if (!definedIds.has(id)) {
|
|
840
|
-
diagnostics.push({
|
|
841
|
-
level: 'warning',
|
|
842
|
-
message: `Dangling reference: #${id} is never defined`,
|
|
843
|
-
file: loc.file,
|
|
844
|
-
line: loc.line,
|
|
845
|
-
});
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
};
|
|
849
|
-
for (const m of model.mitigations) {
|
|
850
|
-
checkRef(m.threat, m.location);
|
|
851
|
-
if (m.control)
|
|
852
|
-
checkRef(m.control, m.location);
|
|
853
|
-
}
|
|
854
|
-
for (const e of model.exposures)
|
|
855
|
-
checkRef(e.threat, e.location);
|
|
856
|
-
for (const a of model.acceptances)
|
|
857
|
-
checkRef(a.threat, a.location);
|
|
858
|
-
for (const t of model.transfers)
|
|
859
|
-
checkRef(t.threat, t.location);
|
|
860
|
-
if (model.validations) {
|
|
861
|
-
for (const v of model.validations)
|
|
862
|
-
checkRef(v.control, v.location);
|
|
863
|
-
}
|
|
864
|
-
return diagnostics;
|
|
865
|
-
}
|
|
866
|
-
function findUnmitigatedExposures(model) {
|
|
867
|
-
const mitigated = new Set();
|
|
868
|
-
for (const m of model.mitigations)
|
|
869
|
-
mitigated.add(`${m.asset}::${m.threat}`);
|
|
870
|
-
for (const a of model.acceptances)
|
|
871
|
-
mitigated.add(`${a.asset}::${a.threat}`);
|
|
872
|
-
return model.exposures.filter(e => !mitigated.has(`${e.asset}::${e.threat}`));
|
|
873
|
-
}
|
|
874
694
|
// ─── /model ──────────────────────────────────────────────────────────
|
|
695
|
+
const CLI_AGENT_OPTIONS = [
|
|
696
|
+
{ id: 'claude-code', name: 'Claude Code' },
|
|
697
|
+
{ id: 'codex', name: 'Codex CLI' },
|
|
698
|
+
{ id: 'gemini', name: 'Gemini CLI' },
|
|
699
|
+
];
|
|
700
|
+
const CLI_AGENT_NAMES = {
|
|
701
|
+
'claude-code': 'Claude Code',
|
|
702
|
+
'codex': 'Codex CLI',
|
|
703
|
+
'gemini': 'Gemini CLI',
|
|
704
|
+
};
|
|
875
705
|
export async function cmdModel(ctx) {
|
|
876
706
|
const current = resolveLLMConfig(ctx.root);
|
|
707
|
+
const tuiCfg = loadTuiConfig(ctx.root);
|
|
877
708
|
const source = describeConfigSource(ctx.root);
|
|
878
|
-
|
|
709
|
+
// Show current configuration
|
|
710
|
+
if (tuiCfg?.aiMode === 'cli-agent' && tuiCfg?.cliAgent) {
|
|
711
|
+
const agentName = CLI_AGENT_NAMES[tuiCfg.cliAgent] || tuiCfg.cliAgent;
|
|
712
|
+
console.log(` ${C.dim('Current:')} ${agentName} ${C.dim('(CLI Agent)')}`);
|
|
713
|
+
console.log(` ${C.dim('Source:')} ${source}`);
|
|
714
|
+
console.log('');
|
|
715
|
+
}
|
|
716
|
+
else if (current) {
|
|
879
717
|
console.log(` ${C.dim('Current:')} ${current.provider} / ${current.model}`);
|
|
880
718
|
console.log(` ${C.dim('Source:')} ${source}`);
|
|
881
719
|
console.log('');
|
|
882
|
-
// If config comes from env vars, offer to keep it
|
|
883
720
|
if (source.includes('env var')) {
|
|
884
721
|
const override = await ask(ctx, ' Override with project config? (y/N): ');
|
|
885
722
|
if (override.toLowerCase() !== 'y') {
|
|
@@ -892,50 +729,88 @@ export async function cmdModel(ctx) {
|
|
|
892
729
|
console.log(C.dim(' No AI provider configured.'));
|
|
893
730
|
console.log('');
|
|
894
731
|
}
|
|
895
|
-
//
|
|
896
|
-
|
|
897
|
-
console.log('
|
|
898
|
-
|
|
732
|
+
// Step 1: Choose mode — CLI Agents or API
|
|
733
|
+
console.log(' How would you like to use AI?');
|
|
734
|
+
console.log(` ${C.bold('1')} CLI Agents ${C.dim('(terminal-based coding agents)')}`);
|
|
735
|
+
console.log(` ${C.bold('2')} API ${C.dim('(direct LLM API calls)')}`);
|
|
899
736
|
console.log('');
|
|
900
|
-
const
|
|
901
|
-
const
|
|
902
|
-
if (
|
|
737
|
+
const modeChoice = await ask(ctx, ' Choice [1-2]: ');
|
|
738
|
+
const modeIdx = parseInt(modeChoice, 10);
|
|
739
|
+
if (modeIdx < 1 || modeIdx > 2) {
|
|
903
740
|
console.log(C.warn(' Cancelled.'));
|
|
904
741
|
return;
|
|
905
742
|
}
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
743
|
+
if (modeIdx === 1) {
|
|
744
|
+
// ── CLI Agent selection ──
|
|
745
|
+
console.log('');
|
|
746
|
+
console.log(' Select CLI Agent:');
|
|
747
|
+
CLI_AGENT_OPTIONS.forEach((a, i) => console.log(` ${C.bold(String(i + 1))} ${a.name}`));
|
|
748
|
+
console.log('');
|
|
749
|
+
const agentChoice = await ask(ctx, ` Agent [1-${CLI_AGENT_OPTIONS.length}]: `);
|
|
750
|
+
const agentIdx = parseInt(agentChoice, 10) - 1;
|
|
751
|
+
if (agentIdx < 0 || agentIdx >= CLI_AGENT_OPTIONS.length) {
|
|
752
|
+
console.log(C.warn(' Cancelled.'));
|
|
913
753
|
return;
|
|
914
754
|
}
|
|
755
|
+
const selectedAgent = CLI_AGENT_OPTIONS[agentIdx];
|
|
756
|
+
saveTuiConfig(ctx.root, {
|
|
757
|
+
aiMode: 'cli-agent',
|
|
758
|
+
cliAgent: selectedAgent.id,
|
|
759
|
+
});
|
|
760
|
+
console.log('');
|
|
761
|
+
console.log(` ${C.success('✓')} Configured: ${C.bold(selectedAgent.name)} ${C.dim('(CLI Agent)')}`);
|
|
762
|
+
console.log(C.dim(' Saved to .guardlink/config.json'));
|
|
763
|
+
console.log(C.dim(` Use /threat-report or /annotate — they will launch ${selectedAgent.name} automatically.`));
|
|
764
|
+
console.log('');
|
|
915
765
|
}
|
|
916
766
|
else {
|
|
917
|
-
|
|
767
|
+
// ── API provider selection ──
|
|
768
|
+
const providers = ['anthropic', 'openai', 'deepseek', 'openrouter', 'ollama'];
|
|
769
|
+
console.log('');
|
|
770
|
+
console.log(' Select provider:');
|
|
771
|
+
providers.forEach((p, i) => console.log(` ${C.bold(String(i + 1))} ${p}`));
|
|
772
|
+
console.log('');
|
|
773
|
+
const choice = await ask(ctx, ` Provider [1-${providers.length}]: `);
|
|
774
|
+
const idx = parseInt(choice, 10) - 1;
|
|
775
|
+
if (idx < 0 || idx >= providers.length) {
|
|
776
|
+
console.log(C.warn(' Cancelled.'));
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
779
|
+
const provider = providers[idx];
|
|
780
|
+
// API key
|
|
781
|
+
let apiKey = '';
|
|
782
|
+
if (provider !== 'ollama') {
|
|
783
|
+
apiKey = await ask(ctx, ' API Key: ');
|
|
784
|
+
if (!apiKey) {
|
|
785
|
+
console.log(C.warn(' Cancelled — no API key provided.'));
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
else {
|
|
790
|
+
apiKey = 'ollama-local';
|
|
791
|
+
}
|
|
792
|
+
// Model selection
|
|
793
|
+
const defaults = {
|
|
794
|
+
anthropic: 'claude-sonnet-4-5-20250929',
|
|
795
|
+
openai: 'gpt-4o',
|
|
796
|
+
openrouter: 'anthropic/claude-sonnet-4-5-20250929',
|
|
797
|
+
deepseek: 'deepseek-chat',
|
|
798
|
+
ollama: 'llama3.2',
|
|
799
|
+
};
|
|
800
|
+
const model = await ask(ctx, ` Model [${defaults[provider]}]: `);
|
|
801
|
+
saveTuiConfig(ctx.root, {
|
|
802
|
+
aiMode: 'api',
|
|
803
|
+
provider,
|
|
804
|
+
model: model || defaults[provider],
|
|
805
|
+
apiKey,
|
|
806
|
+
});
|
|
807
|
+
const displayKey = apiKey.length > 8 ? apiKey.slice(0, 6) + '•'.repeat(8) : '•'.repeat(8);
|
|
808
|
+
console.log('');
|
|
809
|
+
console.log(` ${C.success('✓')} Configured: ${C.bold(model || defaults[provider])} (${provider})`);
|
|
810
|
+
console.log(` Key: ${displayKey}`);
|
|
811
|
+
console.log(C.dim(' Saved to .guardlink/config.json'));
|
|
812
|
+
console.log('');
|
|
918
813
|
}
|
|
919
|
-
// Model selection
|
|
920
|
-
const defaults = {
|
|
921
|
-
anthropic: 'claude-sonnet-4-5-20250929',
|
|
922
|
-
openai: 'gpt-4o',
|
|
923
|
-
openrouter: 'anthropic/claude-sonnet-4-5-20250929',
|
|
924
|
-
deepseek: 'deepseek-chat',
|
|
925
|
-
ollama: 'llama3.2',
|
|
926
|
-
};
|
|
927
|
-
const model = await ask(ctx, ` Model [${defaults[provider]}]: `);
|
|
928
|
-
saveTuiConfig(ctx.root, {
|
|
929
|
-
provider,
|
|
930
|
-
model: model || defaults[provider],
|
|
931
|
-
apiKey,
|
|
932
|
-
});
|
|
933
|
-
const displayKey = apiKey.length > 8 ? apiKey.slice(0, 6) + '•'.repeat(8) : '•'.repeat(8);
|
|
934
|
-
console.log('');
|
|
935
|
-
console.log(` ${C.success('✓')} Configured: ${C.bold(model || defaults[provider])} (${provider})`);
|
|
936
|
-
console.log(` Key: ${displayKey}`);
|
|
937
|
-
console.log(C.dim(' Saved to .guardlink/config.json'));
|
|
938
|
-
console.log('');
|
|
939
814
|
}
|
|
940
815
|
// ─── /threat-report ──────────────────────────────────────────────────
|
|
941
816
|
export async function cmdThreatReport(args, ctx) {
|