@bussolabs/closeyourit-cli 0.17.1 → 0.19.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/README.md CHANGED
@@ -79,6 +79,12 @@ CLOSEYOURIT_TOKEN=cyi_u_… CLOSEYOURIT_API_URL=https://www.closeyour.it \
79
79
  | `errors list --project <id\|key> [--status] [--page]` | List error groups. |
80
80
  | `errors show <id> --project <id\|key>` | Show one error group. |
81
81
  | `errors resolve\|reopen\|mute\|unmute <id> --project <id\|key>` | Triage an error group. |
82
+ | `vulnerabilities list [--project <id\|key>] [--severity] [--status] [--package] [--page]` | List known dependency vulnerabilities across every visible project, worst first. |
83
+ | `vulnerabilities show <id>` | Show one: advisory, package, manifest, fixing version. |
84
+ | `vulnerabilities ignore <id> [--note]` / `reopen <id>` | Accept living with the risk, or change your mind (requires vulnerabilities.triage). |
85
+ | `vulnerabilities promote <id>` | Open a ticket from it — automatic promotion only fires on high/critical. |
86
+ | `vulnerabilities rescan --project <id\|key>` | Rescan now, without waiting for the nightly run. |
87
+ | `vulnerabilities runtimes [--project <id\|key>] [--page]` | Support state of the declared runtimes (Ruby, Node, Flutter…). |
82
88
  | `tokens list --project <id\|key>` | List ingest tokens. |
83
89
  | `tokens create --project <id\|key> --name <name> --environment-id <id>` | Create a token (secret shown once). |
84
90
  | `tokens provision --project <id\|key> --name <name> --environment-id <code\|id> --to-project <id\|key> --to-environment-id <code\|id> --secret-name <NAME>` | Generate and deliver a token without revealing its value. GitHub sync is enabled by default; use `--no-sync-github` for vault-only delivery. |
@@ -0,0 +1,15 @@
1
+ import { BaseCommand } from '../../base';
2
+ export default class ServersIgnoredContainers extends BaseCommand {
3
+ static args: {
4
+ id: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
5
+ };
6
+ static description: string;
7
+ static examples: string[];
8
+ static flags: {
9
+ clear: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
10
+ set: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
11
+ };
12
+ run(): Promise<unknown>;
13
+ private render;
14
+ private split;
15
+ }
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const base_1 = require("../../base");
5
+ // CYRA-519 — i nomi che su una macchina NON sono servizi (container di compilazione e test, che
6
+ // nascono e muoiono a ogni lavorazione) si scrivevano solo dalla pagina web: chi lavora da terminale
7
+ // doveva aprire il browser per una lista di due righe.
8
+ //
9
+ // Senza flag il comando legge; --set sostituisce l'elenco, --clear lo svuota. Sostituisce e non
10
+ // accoda: un elenco di due nomi si riscrive per intero, e "aggiungi" andrebbe letto prima.
11
+ class ServersIgnoredContainers extends base_1.BaseCommand {
12
+ static args = {
13
+ id: core_1.Args.string({ description: 'Server id', required: true }),
14
+ };
15
+ static description = 'Read or set the container names this server does not treat as services';
16
+ static examples = [
17
+ '<%= config.bin %> servers ignored-containers <server-id>',
18
+ '<%= config.bin %> servers ignored-containers <server-id> --set buildkit,runner',
19
+ '<%= config.bin %> servers ignored-containers <server-id> --clear',
20
+ ];
21
+ static flags = {
22
+ clear: core_1.Flags.boolean({ description: 'Remove every ignored container name', exclusive: ['set'] }),
23
+ set: core_1.Flags.string({ description: 'Comma-separated names to ignore (replaces the list)' }),
24
+ };
25
+ async run() {
26
+ const { args, flags } = await this.parse(ServersIgnoredContainers);
27
+ const path = `/cli/v1/servers/${encodeURIComponent(args.id)}`;
28
+ if (flags.set === undefined && !flags.clear) {
29
+ const res = await this.api.get(path);
30
+ if (!this.jsonEnabled())
31
+ this.log(this.render(res.data));
32
+ return res;
33
+ }
34
+ const patterns = flags.clear ? [] : this.split(flags.set ?? '');
35
+ const res = await this.api.put(path, { ignored_container_patterns: patterns });
36
+ if (!this.jsonEnabled())
37
+ this.log(this.render(res.data));
38
+ return res;
39
+ }
40
+ render(data) {
41
+ const patterns = data?.ignored_container_patterns ?? [];
42
+ return patterns.length === 0 ? 'No ignored container names' : patterns.join('\n');
43
+ }
44
+ split(value) {
45
+ return value
46
+ .split(',')
47
+ .map((name) => name.trim())
48
+ .filter(Boolean);
49
+ }
50
+ }
51
+ exports.default = ServersIgnoredContainers;
@@ -0,0 +1,13 @@
1
+ import { BaseCommand } from '../../base';
2
+ export default class VulnerabilitiesIgnore extends BaseCommand {
3
+ static aliases: string[];
4
+ static description: string;
5
+ static examples: string[];
6
+ static args: {
7
+ id: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
8
+ };
9
+ static flags: {
10
+ note: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
11
+ };
12
+ run(): Promise<unknown>;
13
+ }
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const base_1 = require("../../base");
5
+ class VulnerabilitiesIgnore extends base_1.BaseCommand {
6
+ static aliases = ['vulns:ignore'];
7
+ static description = 'Ignore a vulnerability: you accept living with the risk';
8
+ static examples = [
9
+ '<%= config.bin %> vulnerabilities ignore <finding-id>',
10
+ '<%= config.bin %> vulnerabilities ignore <finding-id> --note "Not reachable from our code."',
11
+ ];
12
+ static args = {
13
+ id: core_1.Args.string({ description: 'Vulnerability finding id', required: true }),
14
+ };
15
+ static flags = {
16
+ note: core_1.Flags.string({ description: 'Why you are keeping it (shown alongside the ignored row)' }),
17
+ };
18
+ async run() {
19
+ const { args, flags } = await this.parse(VulnerabilitiesIgnore);
20
+ const res = await this.api.put(`/cli/v1/vulnerabilities/${args.id}/ignore`, { triage_note: flags.note });
21
+ if (!this.jsonEnabled()) {
22
+ // An ignored row never reopens on its own: the next scan won't undo a decision already taken.
23
+ this.log(`Vulnerability ${args.id} ignored.`);
24
+ }
25
+ return res;
26
+ }
27
+ }
28
+ exports.default = VulnerabilitiesIgnore;
@@ -0,0 +1,14 @@
1
+ import { BaseCommand } from '../../base';
2
+ export default class VulnerabilitiesList extends BaseCommand {
3
+ static aliases: string[];
4
+ static description: string;
5
+ static examples: string[];
6
+ static flags: {
7
+ page: import("@oclif/core/lib/interfaces").OptionFlag<number, import("@oclif/core/lib/interfaces").CustomOptions>;
8
+ project: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
9
+ severity: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
10
+ status: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
11
+ package: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
12
+ };
13
+ run(): Promise<unknown>;
14
+ }
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const base_1 = require("../../base");
5
+ const output_1 = require("../../lib/output");
6
+ class VulnerabilitiesList extends base_1.BaseCommand {
7
+ static aliases = ['vulns:list'];
8
+ static description = 'List known vulnerabilities of the project dependencies (cross-project)';
9
+ static examples = [
10
+ '<%= config.bin %> vulnerabilities list',
11
+ '<%= config.bin %> vulnerabilities list --severity critical',
12
+ '<%= config.bin %> vulnerabilities list --project acme-api --status open',
13
+ '<%= config.bin %> vulnerabilities list --package nokogiri --json',
14
+ ];
15
+ static flags = {
16
+ // Optional here (the endpoint is cross-project): without it, the list spans every visible project.
17
+ project: core_1.Flags.string({ char: 'p', description: 'Filter by project id (UUID) or key' }),
18
+ severity: core_1.Flags.string({ description: 'Filter by severity (critical, high, moderate, low, unknown)' }),
19
+ status: core_1.Flags.string({ description: 'Filter by status (open, resolved, ignored)' }),
20
+ package: core_1.Flags.string({ description: 'Filter by package name (partial match)' }),
21
+ ...base_1.pageFlag,
22
+ };
23
+ async run() {
24
+ const { flags } = await this.parse(VulnerabilitiesList);
25
+ const query = new URLSearchParams({ page: String(flags.page) });
26
+ if (flags.project)
27
+ query.set('project_id', await this.resolveProjectId(flags.project));
28
+ if (flags.severity)
29
+ query.set('severity', flags.severity);
30
+ if (flags.status)
31
+ query.set('status', flags.status);
32
+ if (flags.package)
33
+ query.set('package', flags.package);
34
+ const res = await this.api.get(`/cli/v1/vulnerabilities?${query.toString()}`);
35
+ const findings = res.data ?? [];
36
+ if (!this.jsonEnabled()) {
37
+ this.log((0, output_1.renderTable)(
38
+ // ID first, like `errors list`: every other subcommand takes it as its argument, so a table
39
+ // without it forces `--json` just to triage what you are already looking at.
40
+ ['ID', 'PROJECT', 'PACKAGE', 'SEVERITY', 'STATUS', 'FIXED IN', 'ADVISORY'], findings.map((finding) => [
41
+ String(finding.id ?? ''),
42
+ String(finding.project?.key ?? ''),
43
+ String(finding.package?.coordinates ?? ''),
44
+ (0, output_1.statusCell)(finding.advisory?.severity),
45
+ (0, output_1.statusCell)(finding.status),
46
+ // No fixed version means OSV declares none: the only move is dropping or replacing the dep.
47
+ String(finding.fixed_version ?? '—'),
48
+ String(finding.advisory?.display_id ?? ''),
49
+ ])));
50
+ }
51
+ return res;
52
+ }
53
+ }
54
+ exports.default = VulnerabilitiesList;
@@ -0,0 +1,10 @@
1
+ import { BaseCommand } from '../../base';
2
+ export default class VulnerabilitiesPromote extends BaseCommand {
3
+ static aliases: string[];
4
+ static description: string;
5
+ static examples: string[];
6
+ static args: {
7
+ id: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
8
+ };
9
+ run(): Promise<unknown>;
10
+ }
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const base_1 = require("../../base");
5
+ const output_1 = require("../../lib/output");
6
+ class VulnerabilitiesPromote extends base_1.BaseCommand {
7
+ static aliases = ['vulns:promote'];
8
+ static description = 'Open a ticket from a vulnerability (automatic promotion only fires on high/critical)';
9
+ static examples = ['<%= config.bin %> vulnerabilities promote <finding-id>'];
10
+ static args = {
11
+ id: core_1.Args.string({ description: 'Vulnerability finding id', required: true }),
12
+ };
13
+ async run() {
14
+ const { args } = await this.parse(VulnerabilitiesPromote);
15
+ // Idempotent server-side: promoting twice returns R422-VULN-001, never a second ticket.
16
+ const res = await this.api.put(`/cli/v1/vulnerabilities/${args.id}/promotion`);
17
+ if (!this.jsonEnabled()) {
18
+ this.log((0, output_1.renderRecord)(res.data ?? {}));
19
+ }
20
+ return res;
21
+ }
22
+ }
23
+ exports.default = VulnerabilitiesPromote;
@@ -0,0 +1,10 @@
1
+ import { BaseCommand } from '../../base';
2
+ export default class VulnerabilitiesReopen extends BaseCommand {
3
+ static aliases: string[];
4
+ static description: string;
5
+ static examples: string[];
6
+ static args: {
7
+ id: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
8
+ };
9
+ run(): Promise<unknown>;
10
+ }
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const base_1 = require("../../base");
5
+ class VulnerabilitiesReopen extends base_1.BaseCommand {
6
+ static aliases = ['vulns:reopen'];
7
+ static description = 'Reopen an ignored vulnerability: you changed your mind';
8
+ static examples = ['<%= config.bin %> vulnerabilities reopen <finding-id>'];
9
+ static args = {
10
+ id: core_1.Args.string({ description: 'Vulnerability finding id', required: true }),
11
+ };
12
+ async run() {
13
+ const { args } = await this.parse(VulnerabilitiesReopen);
14
+ // Reopening is deleting the ignore, not a verb of its own — hence DELETE on the same resource.
15
+ const res = await this.api.delete(`/cli/v1/vulnerabilities/${args.id}/ignore`);
16
+ if (!this.jsonEnabled()) {
17
+ this.log(`Vulnerability ${args.id} reopened.`);
18
+ }
19
+ return res;
20
+ }
21
+ }
22
+ exports.default = VulnerabilitiesReopen;
@@ -0,0 +1,10 @@
1
+ import { BaseCommand } from '../../base';
2
+ export default class VulnerabilitiesRescan extends BaseCommand {
3
+ static aliases: string[];
4
+ static description: string;
5
+ static examples: string[];
6
+ static flags: {
7
+ project: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
8
+ };
9
+ run(): Promise<unknown>;
10
+ }
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const base_1 = require("../../base");
4
+ class VulnerabilitiesRescan extends base_1.BaseCommand {
5
+ static aliases = ['vulns:rescan'];
6
+ static description = 'Rescan a project now, without waiting for the nightly run';
7
+ static examples = ['<%= config.bin %> vulnerabilities rescan --project acme-api'];
8
+ // Required here, unlike the other subcommands: a scan always targets one project.
9
+ static flags = { ...base_1.projectFlag };
10
+ async run() {
11
+ const { flags } = await this.parse(VulnerabilitiesRescan);
12
+ const projectId = await this.resolveProjectId(flags.project);
13
+ const res = await this.api.post('/cli/v1/vulnerabilities/rescan', { project_id: projectId });
14
+ if (!this.jsonEnabled()) {
15
+ this.log(`Scan queued for project ${flags.project}.`);
16
+ }
17
+ return res;
18
+ }
19
+ }
20
+ exports.default = VulnerabilitiesRescan;
@@ -0,0 +1,11 @@
1
+ import { BaseCommand } from '../../base';
2
+ export default class VulnerabilitiesRuntimes extends BaseCommand {
3
+ static aliases: string[];
4
+ static description: string;
5
+ static examples: string[];
6
+ static flags: {
7
+ page: import("@oclif/core/lib/interfaces").OptionFlag<number, import("@oclif/core/lib/interfaces").CustomOptions>;
8
+ project: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
9
+ };
10
+ run(): Promise<unknown>;
11
+ }
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const base_1 = require("../../base");
5
+ const output_1 = require("../../lib/output");
6
+ class VulnerabilitiesRuntimes extends base_1.BaseCommand {
7
+ static aliases = ['vulns:runtimes'];
8
+ static description = 'List the support state of the declared runtimes (Ruby, Node, Flutter…)';
9
+ static examples = [
10
+ '<%= config.bin %> vulnerabilities runtimes',
11
+ '<%= config.bin %> vulnerabilities runtimes --project acme-api --json',
12
+ ];
13
+ static flags = {
14
+ project: core_1.Flags.string({ char: 'p', description: 'Filter by project id (UUID) or key' }),
15
+ ...base_1.pageFlag,
16
+ };
17
+ async run() {
18
+ const { flags } = await this.parse(VulnerabilitiesRuntimes);
19
+ const query = new URLSearchParams({ page: String(flags.page) });
20
+ if (flags.project)
21
+ query.set('project_id', await this.resolveProjectId(flags.project));
22
+ const res = await this.api.get(`/cli/v1/vulnerabilities/runtimes?${query.toString()}`);
23
+ const runtimes = res.data ?? [];
24
+ if (!this.jsonEnabled()) {
25
+ this.log((0, output_1.renderTable)(['PROJECT', 'RUNTIME', 'VERSION', 'STATE', 'EOL', 'LATEST'], runtimes.map((runtime) => [
26
+ String(runtime.project?.key ?? ''),
27
+ String(runtime.name ?? ''),
28
+ String(runtime.version ?? ''),
29
+ (0, output_1.statusCell)(runtime.state),
30
+ // No EOL date means endoflife.date doesn't know the product: no alarm is possible.
31
+ String(runtime.eol_on ?? '—'),
32
+ String(runtime.latest ?? '—'),
33
+ ])));
34
+ }
35
+ return res;
36
+ }
37
+ }
38
+ exports.default = VulnerabilitiesRuntimes;
@@ -0,0 +1,10 @@
1
+ import { BaseCommand } from '../../base';
2
+ export default class VulnerabilitiesShow extends BaseCommand {
3
+ static aliases: string[];
4
+ static description: string;
5
+ static examples: string[];
6
+ static args: {
7
+ id: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
8
+ };
9
+ run(): Promise<unknown>;
10
+ }
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const base_1 = require("../../base");
5
+ const output_1 = require("../../lib/output");
6
+ /** A nested object of the payload, or `{}` when the field is absent. */
7
+ function nested(data, key) {
8
+ const value = data[key];
9
+ return value && typeof value === 'object' ? value : {};
10
+ }
11
+ class VulnerabilitiesShow extends base_1.BaseCommand {
12
+ static aliases = ['vulns:show'];
13
+ static description = 'Show a single vulnerability: advisory, package, manifest and the fixing version';
14
+ static examples = ['<%= config.bin %> vulnerabilities show <finding-id>'];
15
+ static args = {
16
+ id: core_1.Args.string({ description: 'Vulnerability finding id', required: true }),
17
+ };
18
+ async run() {
19
+ const { args } = await this.parse(VulnerabilitiesShow);
20
+ const res = await this.api.get(`/cli/v1/vulnerabilities/${args.id}`);
21
+ if (!this.jsonEnabled()) {
22
+ const data = res.data ?? {};
23
+ // Same blocks the promoted ticket carries: whoever reads either one decides with the same facts.
24
+ this.log((0, output_1.renderRecord)(data));
25
+ this.log((0, output_1.section)('Project', '📦'));
26
+ this.log((0, output_1.renderRecord)(nested(data, 'project')));
27
+ this.log((0, output_1.section)('Package', '🧩'));
28
+ this.log((0, output_1.renderRecord)({ ...nested(data, 'package'), manifest: nested(data, 'manifest').path ?? null }));
29
+ this.log((0, output_1.section)('Advisory', '🔒'));
30
+ this.log((0, output_1.renderRecord)(nested(data, 'advisory')));
31
+ }
32
+ return res;
33
+ }
34
+ }
35
+ exports.default = VulnerabilitiesShow;
@@ -1,2 +1,9 @@
1
- /** Event types accepted by alert rules — mirror of the backend enum (Alerting::Rule). */
1
+ /**
2
+ * Event types accepted by alert rules — mirror of the backend enum (Alerting::Rule).
3
+ *
4
+ * CYRA-519: the list had stopped at the first fifteen, so `--event-type` rejected every type added
5
+ * since (containers, replication, inodes, automation, vulnerabilities…) — the flag looked broken
6
+ * while the backend accepted them. Keep this in the same order as the backend enum: appending there
7
+ * means appending here.
8
+ */
2
9
  export declare const ALERT_EVENT_TYPES: string[];
@@ -1,7 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ALERT_EVENT_TYPES = void 0;
4
- /** Event types accepted by alert rules — mirror of the backend enum (Alerting::Rule). */
4
+ /**
5
+ * Event types accepted by alert rules — mirror of the backend enum (Alerting::Rule).
6
+ *
7
+ * CYRA-519: the list had stopped at the first fifteen, so `--event-type` rejected every type added
8
+ * since (containers, replication, inodes, automation, vulnerabilities…) — the flag looked broken
9
+ * while the backend accepted them. Keep this in the same order as the backend enum: appending there
10
+ * means appending here.
11
+ */
5
12
  exports.ALERT_EVENT_TYPES = [
6
13
  'error_new',
7
14
  'error_regression',
@@ -18,4 +25,32 @@ exports.ALERT_EVENT_TYPES = [
18
25
  'server_temp',
19
26
  'server_service_failed',
20
27
  'server_smart_failing',
28
+ 'error_spike',
29
+ 'log_alert',
30
+ 'server_db_down',
31
+ 'server_db_connections',
32
+ 'server_replication_lag',
33
+ 'agents_stalled',
34
+ 'server_container_down',
35
+ 'agents_host_failing',
36
+ 'uptime_slow',
37
+ 'analytics_traffic_drop',
38
+ 'analytics_traffic_spike',
39
+ 'idea_created',
40
+ 'idea_commented',
41
+ 'workload_due_soon',
42
+ 'dataset_training_completed',
43
+ 'dataset_training_failed',
44
+ 'agents_host_stale',
45
+ 'vulnerability_new',
46
+ 'runtime_eol',
47
+ 'embedding_down',
48
+ 'server_container_up',
49
+ 'server_db_connection_usage',
50
+ 'server_data_volume_disk',
51
+ 'server_inode',
52
+ 'server_replication_down',
53
+ 'server_replication_up',
54
+ 'server_container_restart_loop',
55
+ 'server_container_stable',
21
56
  ];
@@ -142,6 +142,16 @@ const STATUS_TONE = {
142
142
  active: 'ok',
143
143
  expired: 'neutral',
144
144
  revoked: 'neutral',
145
+ // vulnerability advisory severity (GHSA scale). `unknown` is already neutral above — which is the
146
+ // right reading: an advisory OSV didn't classify is not a severe advisory.
147
+ critical: 'down',
148
+ high: 'down',
149
+ moderate: 'warn',
150
+ low: 'neutral',
151
+ // runtime support state
152
+ ending_soon: 'warn',
153
+ eol: 'down',
154
+ supported: 'ok',
145
155
  };
146
156
  /**
147
157
  * Badge colour (Tailwind family name) → tone. Ticket status/priority labels are org-customisable,