fullcourtdefense-cli 1.3.2 → 1.3.4
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/api.js +17 -1
- package/dist/commands/configure.d.ts +2 -0
- package/dist/commands/configure.js +83 -26
- package/dist/index.js +3 -3
- package/dist/version.json +1 -1
- package/package.json +1 -1
package/dist/api.js
CHANGED
|
@@ -19,7 +19,23 @@ class BotGuardApi {
|
|
|
19
19
|
},
|
|
20
20
|
body: body ? JSON.stringify(body) : undefined,
|
|
21
21
|
});
|
|
22
|
-
const
|
|
22
|
+
const contentType = resp.headers.get('content-type') || '';
|
|
23
|
+
const raw = await resp.text();
|
|
24
|
+
let data;
|
|
25
|
+
try {
|
|
26
|
+
data = raw ? JSON.parse(raw) : {};
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
if (raw.trimStart().startsWith('<!DOCTYPE') || raw.trimStart().startsWith('<html')) {
|
|
30
|
+
throw new Error(`API returned HTML instead of JSON (${resp.status} ${path}). `
|
|
31
|
+
+ 'The endpoint may not be deployed yet — retry with `fullcourtdefense setup --skip-validate true` '
|
|
32
|
+
+ 'or confirm https://api.fullcourtdefense.ai is reachable.');
|
|
33
|
+
}
|
|
34
|
+
throw new Error(`Invalid API response (${resp.status} ${path}): ${raw.slice(0, 200)}`);
|
|
35
|
+
}
|
|
36
|
+
if (!contentType.includes('json') && !data?.success && !resp.ok) {
|
|
37
|
+
// Some gateways omit content-type; JSON parse already succeeded.
|
|
38
|
+
}
|
|
23
39
|
if (!resp.ok || data.success === false) {
|
|
24
40
|
const err = data;
|
|
25
41
|
const msg = err.code ? `${err.error} (${err.code})` : (err.error || `API error (${resp.status})`);
|
|
@@ -6,5 +6,7 @@ export interface ConfigureArgs {
|
|
|
6
6
|
shieldKey?: string;
|
|
7
7
|
apiUrl?: string;
|
|
8
8
|
skipValidate?: boolean;
|
|
9
|
+
/** When true, use only flags/env/config — no prompts (for scripts). */
|
|
10
|
+
nonInteractive?: boolean;
|
|
9
11
|
}
|
|
10
12
|
export declare function configureCommand(args: ConfigureArgs, config: BotGuardConfig): Promise<void>;
|
|
@@ -43,50 +43,107 @@ async function ask(rl, question, fallback) {
|
|
|
43
43
|
const answer = (await rl.question(`${question}${suffix}: `)).trim();
|
|
44
44
|
return answer || fallback || '';
|
|
45
45
|
}
|
|
46
|
+
async function confirm(rl, question, defaultYes = true) {
|
|
47
|
+
const hint = defaultYes ? 'Y/n' : 'y/N';
|
|
48
|
+
const answer = (await rl.question(`${question} (${hint}): `)).trim().toLowerCase();
|
|
49
|
+
if (!answer)
|
|
50
|
+
return defaultYes;
|
|
51
|
+
return answer === 'y' || answer === 'yes';
|
|
52
|
+
}
|
|
46
53
|
function resolveApiKey(args, config) {
|
|
47
54
|
return args.apiKey || config.apiKey || process.env.FULLCOURTDEFENSE_API_KEY || '';
|
|
48
55
|
}
|
|
56
|
+
function defaultsFrom(args, config) {
|
|
57
|
+
return {
|
|
58
|
+
apiUrl: args.apiUrl || config.apiUrl || 'https://api.fullcourtdefense.ai',
|
|
59
|
+
apiKey: resolveApiKey(args, config),
|
|
60
|
+
organizationId: args.organizationId || config.organizationId || '',
|
|
61
|
+
shieldId: args.shieldId || config.shieldId || '',
|
|
62
|
+
shieldKey: args.shieldKey ?? config.shieldKey ?? '',
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function allFlagsProvided(args) {
|
|
66
|
+
return Boolean(args.apiKey && args.organizationId && args.shieldId && args.apiUrl);
|
|
67
|
+
}
|
|
68
|
+
function isValidationUnavailableError(error) {
|
|
69
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
70
|
+
return message.includes('HTML instead of JSON') || message.includes('404');
|
|
71
|
+
}
|
|
49
72
|
async function configureCommand(args, config) {
|
|
50
|
-
const
|
|
73
|
+
const saved = defaultsFrom(args, config);
|
|
74
|
+
const useWizard = !args.nonInteractive && !allFlagsProvided(args);
|
|
75
|
+
let finalApiUrl = saved.apiUrl;
|
|
76
|
+
let finalApiKey = saved.apiKey;
|
|
77
|
+
let finalOrgId = saved.organizationId;
|
|
78
|
+
let finalShieldId = saved.shieldId;
|
|
79
|
+
let finalShieldKey = saved.shieldKey;
|
|
80
|
+
const rl = useWizard ? readline.createInterface({ input: process_1.stdin, output: process_1.stdout }) : null;
|
|
51
81
|
try {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
82
|
+
if (useWizard && rl) {
|
|
83
|
+
console.log('');
|
|
84
|
+
console.log('FullCourtDefense setup — enter each value one at a time.');
|
|
85
|
+
console.log('Press Enter to keep the default shown in parentheses.');
|
|
86
|
+
console.log('Find these in the app: Settings → API Keys / Organization, Shield → Integrate.\n');
|
|
87
|
+
finalApiUrl = await ask(rl, '1/5 API URL', saved.apiUrl);
|
|
88
|
+
finalApiKey = await ask(rl, '2/5 Organization API key', saved.apiKey || undefined);
|
|
89
|
+
finalOrgId = await ask(rl, '3/5 Organization ID', saved.organizationId || undefined);
|
|
90
|
+
finalShieldId = await ask(rl, '4/5 Shield ID (sh_...)', saved.shieldId || undefined);
|
|
91
|
+
finalShieldKey = await ask(rl, '5/5 Shield key (blank if not required)', saved.shieldKey || undefined);
|
|
92
|
+
console.log('');
|
|
93
|
+
}
|
|
94
|
+
if (!finalApiKey) {
|
|
95
|
+
throw new Error('Organization API key is required.');
|
|
56
96
|
}
|
|
57
|
-
|
|
58
|
-
if (!organizationId) {
|
|
97
|
+
if (!finalOrgId) {
|
|
59
98
|
throw new Error('Organization ID is required.');
|
|
60
99
|
}
|
|
61
|
-
|
|
62
|
-
if (!shieldId || shieldId === 'sh_...') {
|
|
100
|
+
if (!finalShieldId || finalShieldId === 'sh_...') {
|
|
63
101
|
throw new Error('Shield ID is required.');
|
|
64
102
|
}
|
|
65
|
-
|
|
66
|
-
if (
|
|
103
|
+
let skipValidate = args.skipValidate === true;
|
|
104
|
+
if (!skipValidate) {
|
|
67
105
|
console.log('Validating organization, Shield ID, and Shield key against FullCourtDefense...');
|
|
68
|
-
const api = new api_1.BotGuardApi(
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
106
|
+
const api = new api_1.BotGuardApi(finalApiKey, finalApiUrl);
|
|
107
|
+
try {
|
|
108
|
+
const validated = await api.validateSetup({
|
|
109
|
+
organizationId: finalOrgId,
|
|
110
|
+
shieldId: finalShieldId,
|
|
111
|
+
shieldKey: finalShieldKey || undefined,
|
|
112
|
+
});
|
|
113
|
+
if (validated.organizationId !== finalOrgId) {
|
|
114
|
+
throw new Error(`Organization mismatch: API key belongs to "${validated.organizationName}" (${validated.organizationId}), not "${finalOrgId}".`);
|
|
115
|
+
}
|
|
116
|
+
console.log(`Verified: ${validated.organizationName} → Shield "${validated.shieldName}" (${validated.shieldId})`);
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
if (isValidationUnavailableError(error)) {
|
|
120
|
+
console.log('Server validation is not available yet (API endpoint not deployed).');
|
|
121
|
+
if (useWizard && rl) {
|
|
122
|
+
skipValidate = await confirm(rl, 'Save credentials locally anyway?', true);
|
|
123
|
+
if (!skipValidate)
|
|
124
|
+
throw error;
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
throw new Error(`${error instanceof Error ? error.message : String(error)} `
|
|
128
|
+
+ 'Use `fullcourtdefense setup --skip-validate true` or run interactive `fullcourtdefense setup` to save without validation.');
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
76
134
|
}
|
|
77
|
-
console.log(`Verified: ${validated.organizationName} → Shield "${validated.shieldName}" (${validated.shieldId})`);
|
|
78
135
|
}
|
|
79
136
|
const savedPath = (0, config_1.saveSetupConfig)({
|
|
80
|
-
apiKey,
|
|
81
|
-
organizationId,
|
|
82
|
-
shieldId,
|
|
83
|
-
shieldKey:
|
|
84
|
-
apiUrl,
|
|
137
|
+
apiKey: finalApiKey,
|
|
138
|
+
organizationId: finalOrgId,
|
|
139
|
+
shieldId: finalShieldId,
|
|
140
|
+
shieldKey: finalShieldKey || undefined,
|
|
141
|
+
apiUrl: finalApiUrl,
|
|
85
142
|
});
|
|
86
143
|
console.log(`Saved setup to ${savedPath}`);
|
|
87
144
|
console.log('All other CLI commands (install, discover --upload, scan, hooks) will use these credentials automatically.');
|
|
88
145
|
}
|
|
89
146
|
finally {
|
|
90
|
-
rl
|
|
147
|
+
rl?.close();
|
|
91
148
|
}
|
|
92
149
|
}
|
package/dist/index.js
CHANGED
|
@@ -111,7 +111,7 @@ function printHelp() {
|
|
|
111
111
|
doctor First step. Checks outbound HTTPS access to FullCourtDefense.
|
|
112
112
|
configure Saves org API key, Organization ID, Shield ID, and Shield key to ~/.fullcourtdefense.yml.
|
|
113
113
|
After setup, other commands read credentials automatically — no --shield-id/--api-key needed.
|
|
114
|
-
setup Same as configure —
|
|
114
|
+
setup Same as configure — step-by-step wizard; validates before saving.
|
|
115
115
|
install-all One-click: MCP gateway in every AI client + Cursor hooks + optional upload.
|
|
116
116
|
install Alias for install-all.
|
|
117
117
|
scan Runs the scan. Use --local for inside-organization scans.
|
|
@@ -154,9 +154,8 @@ function printHelp() {
|
|
|
154
154
|
fullcourtdefense doctor
|
|
155
155
|
This confirms the machine can reach https://api.fullcourtdefense.ai over HTTPS.
|
|
156
156
|
|
|
157
|
-
3. Save org + Shield credentials locally (one time):
|
|
157
|
+
3. Save org + Shield credentials locally (one time, prompts one field at a time):
|
|
158
158
|
fullcourtdefense setup
|
|
159
|
-
Saves to ~/.fullcourtdefense.yml — install, discover --upload, scan, and hooks use it automatically.
|
|
160
159
|
|
|
161
160
|
4. One-click protect every AI client on this machine:
|
|
162
161
|
fullcourtdefense install-all --mcp-command npm --mcp-args "run mcp" --upload
|
|
@@ -409,6 +408,7 @@ async function main() {
|
|
|
409
408
|
shieldKey: flags['shield-key'],
|
|
410
409
|
apiUrl: flags['api-url'],
|
|
411
410
|
skipValidate: flags['skip-validate'] === 'true',
|
|
411
|
+
nonInteractive: flags['non-interactive'] === 'true',
|
|
412
412
|
};
|
|
413
413
|
await (0, configure_1.configureCommand)(args, config);
|
|
414
414
|
break;
|
package/dist/version.json
CHANGED