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 CHANGED
@@ -19,7 +19,23 @@ class BotGuardApi {
19
19
  },
20
20
  body: body ? JSON.stringify(body) : undefined,
21
21
  });
22
- const data = await resp.json();
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 rl = readline.createInterface({ input: process_1.stdin, output: process_1.stdout });
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
- const apiUrl = args.apiUrl || await ask(rl, 'FullCourtDefense API URL', config.apiUrl || 'https://api.fullcourtdefense.ai');
53
- const apiKey = resolveApiKey(args, config) || await ask(rl, 'Organization API key (from Settings → API Keys)', '');
54
- if (!apiKey) {
55
- throw new Error('Organization API key is required to validate your setup.');
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
- const organizationId = args.organizationId || await ask(rl, 'Organization ID (from Settings → Organization)', config.organizationId || '');
58
- if (!organizationId) {
97
+ if (!finalOrgId) {
59
98
  throw new Error('Organization ID is required.');
60
99
  }
61
- const shieldId = args.shieldId || await ask(rl, 'Shield ID', config.shieldId || 'sh_...');
62
- if (!shieldId || shieldId === 'sh_...') {
100
+ if (!finalShieldId || finalShieldId === 'sh_...') {
63
101
  throw new Error('Shield ID is required.');
64
102
  }
65
- const shieldKey = args.shieldKey ?? await ask(rl, 'Shield key if locked, blank if not required', config.shieldKey || '');
66
- if (args.skipValidate !== true) {
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(apiKey, apiUrl);
69
- const validated = await api.validateSetup({
70
- organizationId,
71
- shieldId,
72
- shieldKey: shieldKey || undefined,
73
- });
74
- if (validated.organizationId !== organizationId) {
75
- throw new Error(`Organization mismatch: API key belongs to "${validated.organizationName}" (${validated.organizationId}), not "${organizationId}".`);
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: shieldKey || undefined,
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.close();
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 — validates credentials against the server before saving.
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
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "1.3.2"
2
+ "version": "1.3.4"
3
3
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fullcourtdefense-cli",
3
- "version": "1.3.2",
3
+ "version": "1.3.4",
4
4
  "description": "Full Court Defense CLI — security scanning for AI agents from your terminal",
5
5
  "main": "dist/index.js",
6
6
  "bin": {