codeant-cli 0.4.11 → 0.4.13

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeant-cli",
3
- "version": "0.4.11",
3
+ "version": "0.4.13",
4
4
  "description": "Code review CLI tool",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,32 @@
1
+ import { fetchApi } from '../../utils/fetchApi.js';
2
+
3
+ export async function runAnalysisFeatureFlagsGet({ repo } = {}) {
4
+ if (!repo) {
5
+ const err = new Error('--repo is required');
6
+ err.exitCode = 1;
7
+ throw err;
8
+ }
9
+ return fetchApi('/extension/analysis/config/feature-flags/get', 'POST', { repo });
10
+ }
11
+
12
+ export async function runAnalysisFeatureFlagsUpdate({ repo, flags } = {}) {
13
+ if (!repo) {
14
+ const err = new Error('--repo is required');
15
+ err.exitCode = 1;
16
+ throw err;
17
+ }
18
+ if (!flags) {
19
+ const err = new Error('--flags is required');
20
+ err.exitCode = 1;
21
+ throw err;
22
+ }
23
+ let feature_flags;
24
+ try {
25
+ feature_flags = JSON.parse(flags);
26
+ } catch {
27
+ const err = new Error('--flags must be valid JSON (e.g. \'{"sast_analysis":"enabled"}\')');
28
+ err.exitCode = 1;
29
+ throw err;
30
+ }
31
+ return fetchApi('/extension/analysis/config/feature-flags/update', 'POST', { repo, feature_flags });
32
+ }
@@ -0,0 +1,9 @@
1
+ import { fetchApi } from '../../utils/fetchApi.js';
2
+
3
+ export async function runAuditLogsSettings({ days, page, limit } = {}) {
4
+ return fetchApi('/extension/account/audit/settings', 'POST', {
5
+ days: days !== undefined ? Number(days) : 30,
6
+ page: page !== undefined ? Number(page) : 1,
7
+ limit: limit !== undefined ? Number(limit) : 50,
8
+ });
9
+ }
@@ -0,0 +1,33 @@
1
+ import { fetchApi } from '../../utils/fetchApi.js';
2
+
3
+ export async function runBranchesAll({ repo } = {}) {
4
+ if (!repo) {
5
+ const err = new Error('--repo is required');
6
+ err.exitCode = 1;
7
+ throw err;
8
+ }
9
+ return fetchApi('/extension/branches/all', 'POST', { repo });
10
+ }
11
+
12
+ export async function runBranchesDefault({ repo } = {}) {
13
+ if (!repo) {
14
+ const err = new Error('--repo is required');
15
+ err.exitCode = 1;
16
+ throw err;
17
+ }
18
+ return fetchApi('/extension/branches/default', 'POST', { repo });
19
+ }
20
+
21
+ export async function runBranchesUpdateDefault({ repo, branch } = {}) {
22
+ if (!repo) {
23
+ const err = new Error('--repo is required');
24
+ err.exitCode = 1;
25
+ throw err;
26
+ }
27
+ if (!branch) {
28
+ const err = new Error('--branch is required');
29
+ err.exitCode = 1;
30
+ throw err;
31
+ }
32
+ return fetchApi('/extension/branches/update-default', 'POST', { repo, branch });
33
+ }
@@ -0,0 +1,40 @@
1
+ import { fetchApi } from '../../utils/fetchApi.js';
2
+
3
+ export async function runCveReportingList({ repo, status, reportId } = {}) {
4
+ return fetchApi('/extension/analysis/cve-reporting/list', 'POST', { repo, status, report_id: reportId });
5
+ }
6
+
7
+ export async function runCveReportingCreate({ name, repo, scheduleConfig, reportConfig, description, notificationConfig, createdBy } = {}) {
8
+ if (!name) {
9
+ const err = new Error('--name is required');
10
+ err.exitCode = 1;
11
+ throw err;
12
+ }
13
+ return fetchApi('/extension/analysis/cve-reporting/create', 'POST', {
14
+ name,
15
+ repo,
16
+ schedule_config: scheduleConfig ? JSON.parse(scheduleConfig) : undefined,
17
+ report_config: reportConfig ? JSON.parse(reportConfig) : undefined,
18
+ description,
19
+ notification_config: notificationConfig ? JSON.parse(notificationConfig) : undefined,
20
+ created_by: createdBy,
21
+ });
22
+ }
23
+
24
+ export async function runCveReportingUpdate({ reportId, repo, status, name, description, scheduleConfig, reportConfig, notificationConfig } = {}) {
25
+ if (!reportId) {
26
+ const err = new Error('--report-id is required');
27
+ err.exitCode = 1;
28
+ throw err;
29
+ }
30
+ return fetchApi('/extension/analysis/cve-reporting/update', 'POST', {
31
+ report_id: reportId,
32
+ repo,
33
+ status,
34
+ name,
35
+ description,
36
+ schedule_config: scheduleConfig ? JSON.parse(scheduleConfig) : undefined,
37
+ report_config: reportConfig ? JSON.parse(reportConfig) : undefined,
38
+ notification_config: notificationConfig ? JSON.parse(notificationConfig) : undefined,
39
+ });
40
+ }
@@ -0,0 +1,34 @@
1
+ import { fetchApi } from '../../utils/fetchApi.js';
2
+
3
+ export async function runFeatureFlagsGet({ repo, v2 = false } = {}) {
4
+ if (!repo) {
5
+ const err = new Error('--repo is required');
6
+ err.exitCode = 1;
7
+ throw err;
8
+ }
9
+ const version = v2 ? 'v2' : 'v1';
10
+ return fetchApi(`/extension/config/feature-flags/${version}/get`, 'POST', { repo });
11
+ }
12
+
13
+ export async function runFeatureFlagsUpdate({ repo, flags, v2 = false } = {}) {
14
+ if (!repo) {
15
+ const err = new Error('--repo is required');
16
+ err.exitCode = 1;
17
+ throw err;
18
+ }
19
+ if (!flags) {
20
+ const err = new Error('--flags is required');
21
+ err.exitCode = 1;
22
+ throw err;
23
+ }
24
+ let feature_flags;
25
+ try {
26
+ feature_flags = JSON.parse(flags);
27
+ } catch {
28
+ const err = new Error('--flags must be valid JSON (e.g. \'{"pr_review":"enable"}\')');
29
+ err.exitCode = 1;
30
+ throw err;
31
+ }
32
+ const version = v2 ? 'v2' : 'v1';
33
+ return fetchApi(`/extension/config/feature-flags/${version}/update`, 'POST', { repo, feature_flags });
34
+ }
@@ -0,0 +1,413 @@
1
+ import { runAuditLogsSettings } from './audit-logs.js';
2
+ import { runRepos } from './repos.js';
3
+ import { runBranchesAll, runBranchesDefault, runBranchesUpdateDefault } from './branches.js';
4
+ import { runFeatureFlagsGet, runFeatureFlagsUpdate } from './feature-flags.js';
5
+ import { runAnalysisFeatureFlagsGet, runAnalysisFeatureFlagsUpdate } from './analysis-feature-flags.js';
6
+ import { runPrInstructionsGet, runPrInstructionsSave, runPrInstructionsEdit, runPrInstructionsDelete } from './pr-instructions.js';
7
+ import { runRecurringScanslist, runRecurringScansCreate, runRecurringScansUpdate } from './recurring-scans.js';
8
+ import { runSprintReportsList, runSprintReportsCreate, runSprintReportsUpdate } from './sprint-reports.js';
9
+ import { runCveReportingList, runCveReportingCreate, runCveReportingUpdate } from './cve-reporting.js';
10
+ import {
11
+ runTeamSubscriptionsList,
12
+ runTeamUsersList,
13
+ runTeamUserGet,
14
+ runTeamUserCreate,
15
+ runTeamUserDelete,
16
+ runTeamUserUpdate,
17
+ runTeamRbacGet,
18
+ runTeamRbacSave,
19
+ runTeamPlanEnd,
20
+ } from './team-management.js';
21
+
22
+ /**
23
+ * Register all `codeant settings <verb>` subcommands.
24
+ *
25
+ * Each subcommand implementation lives in its own file alongside this index
26
+ * and exports a single async function named run<Command>(opts).
27
+ *
28
+ * To add a new subcommand:
29
+ * 1. Create src/commands/settings/<name>.js exporting run<Name>(opts).
30
+ * 2. Import it here.
31
+ * 3. Add a settings.command(...).action(...) block below following the
32
+ * existing pattern.
33
+ *
34
+ * @param {import('commander').Command} program
35
+ * @param {{ runCmd: Function }} helpers
36
+ */
37
+ export default function registerSettingsCommands(program, { runCmd }) {
38
+ const settings = program
39
+ .command('settings')
40
+ .description('Manage CodeAnt AI settings');
41
+
42
+ // ── repos ──────────────────────────────────────────────────────────────────
43
+ settings
44
+ .command('repos')
45
+ .description('List repositories')
46
+ .option('--org <org>', 'Organization name (auto-picked when only one is authenticated)')
47
+ .action((opts) => runCmd(() => runRepos({ org: opts.org })));
48
+
49
+ // ── audit-logs settings ────────────────────────────────────────────────────
50
+ settings
51
+ .command('audit-logs')
52
+ .description('List settings change audit logs for the org')
53
+ .option('--days <n>', 'Number of days to look back', '30')
54
+ .option('--page <n>', 'Page number', '1')
55
+ .option('--limit <n>', 'Results per page (max 200)', '50')
56
+ .action((opts) => runCmd(() => runAuditLogsSettings({ days: opts.days, page: opts.page, limit: opts.limit })));
57
+
58
+ // ── branches all ───────────────────────────────────────────────────────────
59
+ settings
60
+ .command('branches-all')
61
+ .description('List all branches for a repository')
62
+ .requiredOption('--repo <repo>', 'Repository (owner/repo)')
63
+ .action((opts) => runCmd(() => runBranchesAll({ repo: opts.repo })));
64
+
65
+ // ── branches default ───────────────────────────────────────────────────────
66
+ settings
67
+ .command('branches-default')
68
+ .description('Get the default branch for a repository')
69
+ .requiredOption('--repo <repo>', 'Repository (owner/repo)')
70
+ .action((opts) => runCmd(() => runBranchesDefault({ repo: opts.repo })));
71
+
72
+ // ── branches update-default ────────────────────────────────────────────────
73
+ settings
74
+ .command('branches-update-default')
75
+ .description('Set the default branch for a repository')
76
+ .requiredOption('--repo <repo>', 'Repository (owner/repo)')
77
+ .requiredOption('--branch <name>', 'Branch name to set as default')
78
+ .action((opts) => runCmd(() => runBranchesUpdateDefault({ repo: opts.repo, branch: opts.branch })));
79
+
80
+ // ── feature-flags get ──────────────────────────────────────────────────────
81
+ settings
82
+ .command('feature-flags-get')
83
+ .description('Get feature flags for a repository')
84
+ .requiredOption('--repo <repo>', 'Repository (owner/repo)')
85
+ .option('--v2', 'Use v2 feature flags', false)
86
+ .action((opts) => runCmd(() => runFeatureFlagsGet({ repo: opts.repo, v2: opts.v2 })));
87
+
88
+ // ── feature-flags update ───────────────────────────────────────────────────
89
+ settings
90
+ .command('feature-flags-update')
91
+ .description('Update feature flags for a repository')
92
+ .requiredOption('--repo <repo>', 'Repository (owner/repo)')
93
+ .requiredOption('--flags <json>', 'Feature flags as a JSON string (e.g. \'{"pr_review":"enable"}\')')
94
+ .option('--v2', 'Use v2 feature flags', false)
95
+ .action((opts) => runCmd(() => runFeatureFlagsUpdate({ repo: opts.repo, flags: opts.flags, v2: opts.v2 })));
96
+
97
+ // ── analysis feature-flags get ─────────────────────────────────────────────
98
+ settings
99
+ .command('analysis-feature-flags-get')
100
+ .description('Get analysis feature flags for a repository')
101
+ .requiredOption('--repo <repo>', 'Repository (owner/repo)')
102
+ .action((opts) => runCmd(() => runAnalysisFeatureFlagsGet({ repo: opts.repo })));
103
+
104
+ // ── analysis feature-flags update ─────────────────────────────────────────
105
+ settings
106
+ .command('analysis-feature-flags-update')
107
+ .description('Update analysis feature flags for a repository')
108
+ .requiredOption('--repo <repo>', 'Repository (owner/repo)')
109
+ .requiredOption('--flags <json>', 'Feature flags as a JSON string (e.g. \'{"sast_analysis":"enabled"}\')')
110
+ .action((opts) => runCmd(() => runAnalysisFeatureFlagsUpdate({ repo: opts.repo, flags: opts.flags })));
111
+
112
+ // ── pr-instructions get ────────────────────────────────────────────────────
113
+ settings
114
+ .command('pr-instructions-get')
115
+ .description('Get PR review instructions')
116
+ .requiredOption('--type <type>', 'Instruction type (e.g. custom)')
117
+ .action((opts) => runCmd(() => runPrInstructionsGet({ instructionsType: opts.type })));
118
+
119
+ // ── pr-instructions save ───────────────────────────────────────────────────
120
+ settings
121
+ .command('pr-instructions-save')
122
+ .description('Save a new PR review instruction')
123
+ .option('--type <type>', 'Instruction type (e.g. custom)')
124
+ .option('--file-pattern <pattern>', 'File pattern (e.g. *.py)')
125
+ .option('--description <text>', 'Instruction description')
126
+ .option('--description-file <path>', 'Path to a file containing the description')
127
+ .option('--instruction-id <id>', 'Instruction ID (for upsert)')
128
+ .action((opts) => runCmd(() => runPrInstructionsSave({
129
+ instructionType: opts.type,
130
+ filePattern: opts.filePattern,
131
+ description: opts.description,
132
+ descriptionFile: opts.descriptionFile,
133
+ instructionId: opts.instructionId,
134
+ })));
135
+
136
+ // ── pr-instructions edit ───────────────────────────────────────────────────
137
+ settings
138
+ .command('pr-instructions-edit')
139
+ .description('Edit an existing PR review instruction')
140
+ .requiredOption('--instruction-id <id>', 'Instruction ID to edit')
141
+ .option('--type <type>', 'Instruction type (e.g. custom)')
142
+ .option('--description <text>', 'Updated description')
143
+ .option('--description-file <path>', 'Path to a file containing the description')
144
+ .option('--file-pattern <pattern>', 'Updated file pattern')
145
+ .action((opts) => runCmd(() => runPrInstructionsEdit({
146
+ instructionType: opts.type,
147
+ instructionId: opts.instructionId,
148
+ description: opts.description,
149
+ descriptionFile: opts.descriptionFile,
150
+ filePattern: opts.filePattern,
151
+ })));
152
+
153
+ // ── pr-instructions delete ─────────────────────────────────────────────────
154
+ settings
155
+ .command('pr-instructions-delete')
156
+ .description('Delete a PR review instruction')
157
+ .requiredOption('--instruction-id <id>', 'Instruction ID to delete')
158
+ .option('--type <type>', 'Instruction type (e.g. custom)')
159
+ .action((opts) => runCmd(() => runPrInstructionsDelete({
160
+ instructionType: opts.type,
161
+ instructionId: opts.instructionId,
162
+ })));
163
+
164
+ // ── recurring-scans list ───────────────────────────────────────────────────
165
+ settings
166
+ .command('recurring-scans-list')
167
+ .description('List recurring scan schedules')
168
+ .option('--repo <repo>', 'Filter by repository (owner/repo)')
169
+ .option('--status <status>', 'Filter by status (e.g. ACTIVE)')
170
+ .option('--schedule-id <id>', 'Filter by schedule ID')
171
+ .action((opts) => runCmd(() => runRecurringScanslist({ repo: opts.repo, status: opts.status, scheduleId: opts.scheduleId })));
172
+
173
+ // ── recurring-scans create ─────────────────────────────────────────────────
174
+ settings
175
+ .command('recurring-scans-create')
176
+ .description('Create a recurring scan schedule')
177
+ .requiredOption('--name <name>', 'Schedule name')
178
+ .option('--repo <repo>', 'Repository (owner/repo)')
179
+ .option('--schedule-config <json>', 'Schedule config as JSON (e.g. \'{"frequency":"weekly"}\')')
180
+ .option('--scan-config <json>', 'Scan config as JSON')
181
+ .option('--description <text>', 'Description')
182
+ .option('--notification-config <json>', 'Notification config as JSON')
183
+ .option('--created-by <user>', 'Creator identifier')
184
+ .action((opts) => runCmd(() => runRecurringScansCreate({
185
+ name: opts.name,
186
+ repo: opts.repo,
187
+ scheduleConfig: opts.scheduleConfig,
188
+ scanConfig: opts.scanConfig,
189
+ description: opts.description,
190
+ notificationConfig: opts.notificationConfig,
191
+ createdBy: opts.createdBy,
192
+ })));
193
+
194
+ // ── recurring-scans update ─────────────────────────────────────────────────
195
+ settings
196
+ .command('recurring-scans-update')
197
+ .description('Update a recurring scan schedule')
198
+ .requiredOption('--schedule-id <id>', 'Schedule ID to update')
199
+ .requiredOption('--repo <repo>', 'Repository (owner/repo)')
200
+ .option('--status <status>', 'New status (e.g. ACTIVE, INACTIVE)')
201
+ .option('--name <name>', 'New name')
202
+ .option('--description <text>', 'New description')
203
+ .option('--schedule-config <json>', 'Schedule config as JSON')
204
+ .option('--scan-config <json>', 'Scan config as JSON')
205
+ .option('--notification-config <json>', 'Notification config as JSON')
206
+ .action((opts) => runCmd(() => runRecurringScansUpdate({
207
+ scheduleId: opts.scheduleId,
208
+ repo: opts.repo,
209
+ status: opts.status,
210
+ name: opts.name,
211
+ description: opts.description,
212
+ scheduleConfig: opts.scheduleConfig,
213
+ scanConfig: opts.scanConfig,
214
+ notificationConfig: opts.notificationConfig,
215
+ })));
216
+
217
+ // ── sprint-reports list ────────────────────────────────────────────────────
218
+ settings
219
+ .command('sprint-reports-list')
220
+ .description('List sprint report configurations')
221
+ .option('--repo <repo>', 'Filter by repository (owner/repo)')
222
+ .option('--status <status>', 'Filter by status (e.g. ACTIVE)')
223
+ .option('--config-id <id>', 'Filter by config ID')
224
+ .action((opts) => runCmd(() => runSprintReportsList({ repo: opts.repo, status: opts.status, configId: opts.configId })));
225
+
226
+ // ── sprint-reports create ──────────────────────────────────────────────────
227
+ settings
228
+ .command('sprint-reports-create')
229
+ .description('Create a sprint report configuration')
230
+ .requiredOption('--name <name>', 'Report name')
231
+ .option('--repo <repo>', 'Repository (owner/repo)')
232
+ .option('--schedule-config <json>', 'Schedule config as JSON (e.g. \'{"frequency":"weekly"}\')')
233
+ .option('--report-config <json>', 'Report config as JSON')
234
+ .option('--description <text>', 'Description')
235
+ .option('--notification-config <json>', 'Notification config as JSON')
236
+ .option('--created-by <user>', 'Creator identifier')
237
+ .action((opts) => runCmd(() => runSprintReportsCreate({
238
+ name: opts.name,
239
+ repo: opts.repo,
240
+ scheduleConfig: opts.scheduleConfig,
241
+ reportConfig: opts.reportConfig,
242
+ description: opts.description,
243
+ notificationConfig: opts.notificationConfig,
244
+ createdBy: opts.createdBy,
245
+ })));
246
+
247
+ // ── sprint-reports update ──────────────────────────────────────────────────
248
+ settings
249
+ .command('sprint-reports-update')
250
+ .description('Update a sprint report configuration')
251
+ .requiredOption('--config-id <id>', 'Config ID to update')
252
+ .requiredOption('--repo <repo>', 'Repository (owner/repo)')
253
+ .option('--status <status>', 'New status (e.g. ACTIVE, INACTIVE)')
254
+ .option('--name <name>', 'New name')
255
+ .option('--description <text>', 'New description')
256
+ .option('--schedule-config <json>', 'Schedule config as JSON')
257
+ .option('--report-config <json>', 'Report config as JSON')
258
+ .option('--notification-config <json>', 'Notification config as JSON')
259
+ .action((opts) => runCmd(() => runSprintReportsUpdate({
260
+ configId: opts.configId,
261
+ repo: opts.repo,
262
+ status: opts.status,
263
+ name: opts.name,
264
+ description: opts.description,
265
+ scheduleConfig: opts.scheduleConfig,
266
+ reportConfig: opts.reportConfig,
267
+ notificationConfig: opts.notificationConfig,
268
+ })));
269
+
270
+ // ── cve-reporting list ─────────────────────────────────────────────────────
271
+ settings
272
+ .command('cve-reporting-list')
273
+ .description('List CVE report configurations')
274
+ .option('--repo <repo>', 'Filter by repository (owner/repo)')
275
+ .option('--status <status>', 'Filter by status (e.g. ACTIVE)')
276
+ .option('--report-id <id>', 'Filter by report ID')
277
+ .action((opts) => runCmd(() => runCveReportingList({ repo: opts.repo, status: opts.status, reportId: opts.reportId })));
278
+
279
+ // ── cve-reporting create ───────────────────────────────────────────────────
280
+ settings
281
+ .command('cve-reporting-create')
282
+ .description('Create a CVE report configuration')
283
+ .requiredOption('--name <name>', 'Report name')
284
+ .option('--repo <repo>', 'Repository (owner/repo)')
285
+ .option('--schedule-config <json>', 'Schedule config as JSON (e.g. \'{"frequency":"weekly"}\')')
286
+ .option('--report-config <json>', 'Report config as JSON')
287
+ .option('--description <text>', 'Description')
288
+ .option('--notification-config <json>', 'Notification config as JSON')
289
+ .option('--created-by <user>', 'Creator identifier')
290
+ .action((opts) => runCmd(() => runCveReportingCreate({
291
+ name: opts.name,
292
+ repo: opts.repo,
293
+ scheduleConfig: opts.scheduleConfig,
294
+ reportConfig: opts.reportConfig,
295
+ description: opts.description,
296
+ notificationConfig: opts.notificationConfig,
297
+ createdBy: opts.createdBy,
298
+ })));
299
+
300
+ // ── cve-reporting update ───────────────────────────────────────────────────
301
+ settings
302
+ .command('cve-reporting-update')
303
+ .description('Update a CVE report configuration')
304
+ .requiredOption('--report-id <id>', 'Report ID to update')
305
+ .requiredOption('--repo <repo>', 'Repository (owner/repo)')
306
+ .option('--status <status>', 'New status (e.g. ACTIVE, INACTIVE)')
307
+ .option('--name <name>', 'New name')
308
+ .option('--description <text>', 'New description')
309
+ .option('--schedule-config <json>', 'Schedule config as JSON')
310
+ .option('--report-config <json>', 'Report config as JSON')
311
+ .option('--notification-config <json>', 'Notification config as JSON')
312
+ .action((opts) => runCmd(() => runCveReportingUpdate({
313
+ reportId: opts.reportId,
314
+ repo: opts.repo,
315
+ status: opts.status,
316
+ name: opts.name,
317
+ description: opts.description,
318
+ scheduleConfig: opts.scheduleConfig,
319
+ reportConfig: opts.reportConfig,
320
+ notificationConfig: opts.notificationConfig,
321
+ })));
322
+
323
+ // ── team-subscriptions-list ────────────────────────────────────────────────
324
+ settings
325
+ .command('team-subscriptions-list')
326
+ .description('List all subscriptions for the account')
327
+ .action(() => runCmd(() => runTeamSubscriptionsList()));
328
+
329
+ // ── team-users-list ────────────────────────────────────────────────────────
330
+ settings
331
+ .command('team-users-list')
332
+ .description('List users for a subscription')
333
+ .requiredOption('--subscription-id <id>', 'Subscription ID')
334
+ .action((opts) => runCmd(() => runTeamUsersList({ subscriptionId: opts.subscriptionId })));
335
+
336
+ // ── team-user-get ──────────────────────────────────────────────────────────
337
+ settings
338
+ .command('team-user-get')
339
+ .description('Get a user by ID')
340
+ .requiredOption('--user-id <id>', 'User ID')
341
+ .action((opts) => runCmd(() => runTeamUserGet({ userId: opts.userId })));
342
+
343
+ // ── team-user-create ───────────────────────────────────────────────────────
344
+ settings
345
+ .command('team-user-create')
346
+ .description('Create a new team user')
347
+ .requiredOption('--user-id <id>', 'User ID')
348
+ .requiredOption('--email <email>', 'User email')
349
+ .requiredOption('--name <name>', 'User display name')
350
+ .requiredOption('--role <role>', 'User role (e.g. developer, admin)')
351
+ .option('--plan-ids <json>', 'Plan IDs as JSON array (e.g. \'["plan_1"]\')', '[]')
352
+ .option('--subscription-ids <json>', 'Subscription IDs as JSON array', '[]')
353
+ .option('--invited', 'Send invitation email', false)
354
+ .action((opts) => runCmd(() => runTeamUserCreate({
355
+ userId: opts.userId,
356
+ email: opts.email,
357
+ name: opts.name,
358
+ role: opts.role,
359
+ planIds: opts.planIds,
360
+ subscriptionIds: opts.subscriptionIds,
361
+ invited: opts.invited,
362
+ })));
363
+
364
+ // ── team-user-delete ───────────────────────────────────────────────────────
365
+ settings
366
+ .command('team-user-delete')
367
+ .description('Delete a team user')
368
+ .requiredOption('--user-id <id>', 'User ID to delete')
369
+ .action((opts) => runCmd(() => runTeamUserDelete({ userId: opts.userId })));
370
+
371
+ // ── team-user-update ───────────────────────────────────────────────────────
372
+ settings
373
+ .command('team-user-update')
374
+ .description('Update a team user')
375
+ .requiredOption('--user-id <id>', 'User ID to update')
376
+ .requiredOption('--email <email>', 'User email')
377
+ .requiredOption('--name <name>', 'User display name')
378
+ .requiredOption('--role <role>', 'User role (e.g. developer, admin)')
379
+ .requiredOption('--status <status>', 'User status (e.g. active, inactive)')
380
+ .option('--plan-ids <json>', 'Plan IDs as JSON array', '[]')
381
+ .option('--subscription-ids <json>', 'Subscription IDs as JSON array', '[]')
382
+ .action((opts) => runCmd(() => runTeamUserUpdate({
383
+ userId: opts.userId,
384
+ email: opts.email,
385
+ name: opts.name,
386
+ role: opts.role,
387
+ status: opts.status,
388
+ planIds: opts.planIds,
389
+ subscriptionIds: opts.subscriptionIds,
390
+ })));
391
+
392
+ // ── team-rbac-get ──────────────────────────────────────────────────────────
393
+ settings
394
+ .command('team-rbac-get')
395
+ .description('Get RBAC settings for a repository')
396
+ .requiredOption('--repo <repo>', 'Repository (owner/repo)')
397
+ .action((opts) => runCmd(() => runTeamRbacGet({ repo: opts.repo })));
398
+
399
+ // ── team-rbac-save ─────────────────────────────────────────────────────────
400
+ settings
401
+ .command('team-rbac-save')
402
+ .description('Save RBAC settings for a repository')
403
+ .requiredOption('--repo <repo>', 'Repository (owner/repo)')
404
+ .requiredOption('--data <json>', 'RBAC settings as JSON (e.g. \'{"enabled":true}\')')
405
+ .action((opts) => runCmd(() => runTeamRbacSave({ repo: opts.repo, data: opts.data })));
406
+
407
+ // ── team-plan-end ──────────────────────────────────────────────────────────
408
+ settings
409
+ .command('team-plan-end')
410
+ .description('End/expire the active subscription plan')
411
+ .option('--include-users', 'Also remove all users from the plan', false)
412
+ .action((opts) => runCmd(() => runTeamPlanEnd({ includeUsers: opts.includeUsers })));
413
+ }
@@ -0,0 +1,44 @@
1
+ import { fetchApi } from '../../utils/fetchApi.js';
2
+
3
+ export async function runPrInstructionsGet({ instructionsType } = {}) {
4
+ return fetchApi('/extension/config/pullrequest/instructions/get', 'POST', {
5
+ instructions_type: instructionsType,
6
+ });
7
+ }
8
+
9
+ export async function runPrInstructionsSave({ instructionType, filePattern, description, descriptionFile, instructionId } = {}) {
10
+ return fetchApi('/extension/config/pullrequest/instructions/save', 'POST', {
11
+ instruction_type: instructionType,
12
+ file_pattern: filePattern,
13
+ description,
14
+ description_file: descriptionFile,
15
+ instruction_id: instructionId,
16
+ });
17
+ }
18
+
19
+ export async function runPrInstructionsEdit({ instructionType, instructionId, description, descriptionFile, filePattern } = {}) {
20
+ if (!instructionId) {
21
+ const err = new Error('--instruction-id is required');
22
+ err.exitCode = 1;
23
+ throw err;
24
+ }
25
+ return fetchApi('/extension/config/pullrequest/instructions/edit', 'POST', {
26
+ instruction_type: instructionType,
27
+ instruction_id: instructionId,
28
+ description,
29
+ description_file: descriptionFile,
30
+ file_pattern: filePattern,
31
+ });
32
+ }
33
+
34
+ export async function runPrInstructionsDelete({ instructionType, instructionId } = {}) {
35
+ if (!instructionId) {
36
+ const err = new Error('--instruction-id is required');
37
+ err.exitCode = 1;
38
+ throw err;
39
+ }
40
+ return fetchApi('/extension/config/pullrequest/instructions/delete', 'POST', {
41
+ instruction_type: instructionType,
42
+ instruction_id: instructionId,
43
+ });
44
+ }
@@ -0,0 +1,40 @@
1
+ import { fetchApi } from '../../utils/fetchApi.js';
2
+
3
+ export async function runRecurringScanslist({ repo, status, scheduleId } = {}) {
4
+ return fetchApi('/extension/analysis/recurring-scans/list', 'POST', { repo, status, schedule_id: scheduleId });
5
+ }
6
+
7
+ export async function runRecurringScansCreate({ name, repo, scheduleConfig, scanConfig, description, notificationConfig, createdBy } = {}) {
8
+ if (!name) {
9
+ const err = new Error('--name is required');
10
+ err.exitCode = 1;
11
+ throw err;
12
+ }
13
+ return fetchApi('/extension/analysis/recurring-scans/create', 'POST', {
14
+ name,
15
+ repo,
16
+ schedule_config: scheduleConfig ? JSON.parse(scheduleConfig) : undefined,
17
+ scan_config: scanConfig ? JSON.parse(scanConfig) : undefined,
18
+ description,
19
+ notification_config: notificationConfig ? JSON.parse(notificationConfig) : undefined,
20
+ created_by: createdBy,
21
+ });
22
+ }
23
+
24
+ export async function runRecurringScansUpdate({ scheduleId, repo, status, name, description, scheduleConfig, scanConfig, notificationConfig } = {}) {
25
+ if (!scheduleId) {
26
+ const err = new Error('--schedule-id is required');
27
+ err.exitCode = 1;
28
+ throw err;
29
+ }
30
+ return fetchApi('/extension/analysis/recurring-scans/update', 'POST', {
31
+ schedule_id: scheduleId,
32
+ repo,
33
+ status,
34
+ name,
35
+ description,
36
+ schedule_config: scheduleConfig ? JSON.parse(scheduleConfig) : undefined,
37
+ scan_config: scanConfig ? JSON.parse(scanConfig) : undefined,
38
+ notification_config: notificationConfig ? JSON.parse(notificationConfig) : undefined,
39
+ });
40
+ }
@@ -0,0 +1,2 @@
1
+ // Delegates to the scans implementation — same endpoint, no duplication needed.
2
+ export { runRepos } from '../scans/repos.js';
@@ -0,0 +1,40 @@
1
+ import { fetchApi } from '../../utils/fetchApi.js';
2
+
3
+ export async function runSprintReportsList({ repo, status, configId } = {}) {
4
+ return fetchApi('/extension/metrics/sprint-reports/list', 'POST', { repo, status, config_id: configId });
5
+ }
6
+
7
+ export async function runSprintReportsCreate({ name, repo, scheduleConfig, reportConfig, description, notificationConfig, createdBy } = {}) {
8
+ if (!name) {
9
+ const err = new Error('--name is required');
10
+ err.exitCode = 1;
11
+ throw err;
12
+ }
13
+ return fetchApi('/extension/metrics/sprint-reports/create', 'POST', {
14
+ name,
15
+ repo,
16
+ schedule_config: scheduleConfig ? JSON.parse(scheduleConfig) : undefined,
17
+ report_config: reportConfig ? JSON.parse(reportConfig) : undefined,
18
+ description,
19
+ notification_config: notificationConfig ? JSON.parse(notificationConfig) : undefined,
20
+ created_by: createdBy,
21
+ });
22
+ }
23
+
24
+ export async function runSprintReportsUpdate({ configId, repo, status, name, description, scheduleConfig, reportConfig, notificationConfig } = {}) {
25
+ if (!configId) {
26
+ const err = new Error('--config-id is required');
27
+ err.exitCode = 1;
28
+ throw err;
29
+ }
30
+ return fetchApi('/extension/metrics/sprint-reports/update', 'POST', {
31
+ config_id: configId,
32
+ repo,
33
+ status,
34
+ name,
35
+ description,
36
+ schedule_config: scheduleConfig ? JSON.parse(scheduleConfig) : undefined,
37
+ report_config: reportConfig ? JSON.parse(reportConfig) : undefined,
38
+ notification_config: notificationConfig ? JSON.parse(notificationConfig) : undefined,
39
+ });
40
+ }
@@ -0,0 +1,55 @@
1
+ import { fetchApi } from '../../utils/fetchApi.js';
2
+
3
+ export async function runTeamSubscriptionsList() {
4
+ return fetchApi('/extension/account/subscriptions/list', 'POST', {});
5
+ }
6
+
7
+ export async function runTeamUsersList({ subscriptionId } = {}) {
8
+ return fetchApi('/extension/account/subscription/users/list', 'POST', { subscription_id: subscriptionId });
9
+ }
10
+
11
+ export async function runTeamUserGet({ userId } = {}) {
12
+ return fetchApi('/extension/account/users/user/get', 'POST', { user_id: userId });
13
+ }
14
+
15
+ export async function runTeamUserCreate({ userId, email, name, role, planIds, subscriptionIds, invited } = {}) {
16
+ return fetchApi('/extension/account/users/user/create', 'POST', {
17
+ users: [{
18
+ user_id: userId,
19
+ email,
20
+ name,
21
+ role,
22
+ plan_ids: planIds ? JSON.parse(planIds) : [],
23
+ subscription_ids: subscriptionIds ? JSON.parse(subscriptionIds) : [],
24
+ }],
25
+ invited: invited ?? false,
26
+ });
27
+ }
28
+
29
+ export async function runTeamUserDelete({ userId } = {}) {
30
+ return fetchApi('/extension/account/users/user/delete', 'POST', { user_id: userId });
31
+ }
32
+
33
+ export async function runTeamUserUpdate({ userId, email, name, role, status, planIds, subscriptionIds } = {}) {
34
+ return fetchApi('/extension/account/users/user/update', 'POST', {
35
+ user_id: userId,
36
+ email,
37
+ name,
38
+ role,
39
+ status,
40
+ plan_ids: planIds ? JSON.parse(planIds) : [],
41
+ subscription_ids: subscriptionIds ? JSON.parse(subscriptionIds) : [],
42
+ });
43
+ }
44
+
45
+ export async function runTeamRbacGet({ repo } = {}) {
46
+ return fetchApi('/extension/account/rbac/settings/get', 'POST', { repo });
47
+ }
48
+
49
+ export async function runTeamRbacSave({ repo, data } = {}) {
50
+ return fetchApi('/extension/account/rbac/settings/save', 'POST', { repo, data: JSON.parse(data) });
51
+ }
52
+
53
+ export async function runTeamPlanEnd({ includeUsers } = {}) {
54
+ return fetchApi('/extension/account/plan/subscriptions/end', 'POST', { include_users: includeUsers ?? false });
55
+ }
package/src/index.js CHANGED
@@ -19,6 +19,7 @@ import * as scm from './scm/index.js';
19
19
  import { setConfigValue } from './utils/config.js';
20
20
  import { track, shutdown as analyticsShutdown, isTelemetryDisabled } from './utils/analytics.js';
21
21
  import registerScansCommands from './commands/scans/index.js';
22
+ import registerSettingsCommands from './commands/settings/index.js';
22
23
 
23
24
  // Read version from package.json
24
25
  const require = createRequire(import.meta.url);
@@ -393,6 +394,9 @@ program
393
394
  // ─── Scans commands ───
394
395
  registerScansCommands(program, { runCmd });
395
396
 
397
+ // ─── Settings commands ───
398
+ registerSettingsCommands(program, { runCmd });
399
+
396
400
  // ─── Telemetry control ───
397
401
  program
398
402
  .command('set-telemetry <enabled>')
@@ -10,7 +10,7 @@ export function getClient() {
10
10
  return new Gitlab(opts);
11
11
  }
12
12
 
13
- // GitLab uses project ID or "owner/repo" encoded as "owner%2Frepo"
13
+ // GitLab uses project ID or the full path — gitbeaker's endpoint tag encodes it
14
14
  export function projectId(name) {
15
- return encodeURIComponent(name);
15
+ return name;
16
16
  }
package/src/scm/index.js CHANGED
@@ -65,10 +65,13 @@ export function detectRemote() {
65
65
  export function detectRepoName() {
66
66
  const origin = getOrigin();
67
67
  if (!origin) return null;
68
- // Handle SSH: git@github.com:owner/repo.git
69
- // Handle HTTPS: https://github.com/owner/repo.git
70
- const match = origin.match(/[/:]([\w.-]+\/[\w.-]+?)(?:\.git)?$/);
71
- return match ? match[1] : null;
68
+ // SSH: git@host:path/to/repo[.git]
69
+ const sshMatch = origin.match(/^(?:[^@]+@)?[^:]+:(.+?)(?:\.git)?$/);
70
+ if (sshMatch) return sshMatch[1];
71
+ // HTTPS: https://[user:pass@]host/path/to/repo[.git]
72
+ const httpsMatch = origin.match(/^(?:https?|git):\/\/[^/]+\/(.+?)(?:\.git)?$/);
73
+ if (httpsMatch) return httpsMatch[1];
74
+ return null;
72
75
  }
73
76
 
74
77
  // Auto-detect default branch
@@ -23,6 +23,7 @@ const fetchApi = async (endpoint, method = 'GET', body = null) => {
23
23
 
24
24
  try {
25
25
  const response = await fetch(url, options);
26
+ console.error('API Response Status:', response.status);
26
27
 
27
28
  if (response.status === 403) {
28
29
  throw new Error('Access denied (403). Please run `codeant logout` and then `codeant login` to re-authenticate.');