devvami 1.4.1 → 1.5.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.
Files changed (96) hide show
  1. package/README.md +7 -0
  2. package/oclif.manifest.json +41 -1
  3. package/package.json +2 -1
  4. package/src/commands/auth/login.js +20 -16
  5. package/src/commands/changelog.js +12 -12
  6. package/src/commands/costs/get.js +14 -24
  7. package/src/commands/costs/trend.js +13 -24
  8. package/src/commands/create/repo.js +72 -54
  9. package/src/commands/docs/list.js +29 -25
  10. package/src/commands/docs/projects.js +58 -24
  11. package/src/commands/docs/read.js +56 -39
  12. package/src/commands/docs/search.js +37 -25
  13. package/src/commands/doctor.js +37 -35
  14. package/src/commands/dotfiles/add.js +51 -39
  15. package/src/commands/dotfiles/setup.js +62 -33
  16. package/src/commands/dotfiles/status.js +18 -18
  17. package/src/commands/dotfiles/sync.js +62 -46
  18. package/src/commands/init.js +143 -132
  19. package/src/commands/logs/index.js +10 -16
  20. package/src/commands/open.js +12 -12
  21. package/src/commands/pipeline/logs.js +8 -11
  22. package/src/commands/pipeline/rerun.js +21 -16
  23. package/src/commands/pipeline/status.js +28 -24
  24. package/src/commands/pr/create.js +40 -27
  25. package/src/commands/pr/detail.js +9 -7
  26. package/src/commands/pr/review.js +18 -19
  27. package/src/commands/pr/status.js +27 -21
  28. package/src/commands/prompts/browse.js +15 -15
  29. package/src/commands/prompts/download.js +15 -16
  30. package/src/commands/prompts/install-speckit.js +11 -12
  31. package/src/commands/prompts/list.js +12 -12
  32. package/src/commands/prompts/run.js +16 -19
  33. package/src/commands/repo/list.js +57 -41
  34. package/src/commands/search.js +20 -18
  35. package/src/commands/security/setup.js +38 -34
  36. package/src/commands/sync-config-ai/index.js +143 -0
  37. package/src/commands/tasks/assigned.js +43 -33
  38. package/src/commands/tasks/list.js +43 -33
  39. package/src/commands/tasks/today.js +32 -30
  40. package/src/commands/upgrade.js +18 -17
  41. package/src/commands/vuln/detail.js +8 -8
  42. package/src/commands/vuln/scan.js +95 -21
  43. package/src/commands/vuln/search.js +23 -18
  44. package/src/commands/welcome.js +2 -2
  45. package/src/commands/whoami.js +19 -23
  46. package/src/formatters/ai-config.js +127 -0
  47. package/src/formatters/charts.js +6 -23
  48. package/src/formatters/cost.js +1 -7
  49. package/src/formatters/dotfiles.js +48 -19
  50. package/src/formatters/markdown.js +11 -6
  51. package/src/formatters/openapi.js +7 -9
  52. package/src/formatters/prompts.js +69 -78
  53. package/src/formatters/security.js +2 -2
  54. package/src/formatters/status.js +1 -1
  55. package/src/formatters/table.js +1 -3
  56. package/src/formatters/vuln.js +33 -20
  57. package/src/help.js +162 -164
  58. package/src/hooks/init.js +1 -3
  59. package/src/hooks/postrun.js +5 -7
  60. package/src/index.js +1 -1
  61. package/src/services/ai-config-store.js +318 -0
  62. package/src/services/ai-env-deployer.js +444 -0
  63. package/src/services/ai-env-scanner.js +242 -0
  64. package/src/services/audit-detector.js +2 -2
  65. package/src/services/audit-runner.js +40 -31
  66. package/src/services/auth.js +9 -9
  67. package/src/services/awesome-copilot.js +7 -4
  68. package/src/services/aws-costs.js +22 -22
  69. package/src/services/clickup.js +26 -26
  70. package/src/services/cloudwatch-logs.js +5 -9
  71. package/src/services/config.js +13 -13
  72. package/src/services/docs.js +19 -20
  73. package/src/services/dotfiles.js +149 -51
  74. package/src/services/github.js +22 -24
  75. package/src/services/nvd.js +21 -31
  76. package/src/services/platform.js +2 -2
  77. package/src/services/prompts.js +23 -35
  78. package/src/services/security.js +135 -61
  79. package/src/services/shell.js +4 -4
  80. package/src/services/skills-sh.js +3 -9
  81. package/src/services/speckit.js +4 -7
  82. package/src/services/version-check.js +10 -10
  83. package/src/types.js +85 -0
  84. package/src/utils/aws-vault.js +18 -41
  85. package/src/utils/banner.js +5 -7
  86. package/src/utils/errors.js +42 -46
  87. package/src/utils/frontmatter.js +4 -4
  88. package/src/utils/gradient.js +18 -16
  89. package/src/utils/open-browser.js +3 -3
  90. package/src/utils/tui/form.js +1006 -0
  91. package/src/utils/tui/modal.js +15 -14
  92. package/src/utils/tui/navigable-table.js +25 -17
  93. package/src/utils/tui/tab-tui.js +800 -0
  94. package/src/utils/typewriter.js +3 -3
  95. package/src/utils/welcome.js +18 -21
  96. package/src/validators/repo-name.js +2 -2
@@ -1,9 +1,9 @@
1
- import { Command } from '@oclif/core'
1
+ import {Command} from '@oclif/core'
2
2
  import chalk from 'chalk'
3
3
  import ora from 'ora'
4
- import { getTasksToday, isAuthenticated } from '../../services/clickup.js'
5
- import { loadConfig } from '../../services/config.js'
6
- import { renderTable } from '../../formatters/table.js'
4
+ import {getTasksToday, isAuthenticated} from '../../services/clickup.js'
5
+ import {loadConfig} from '../../services/config.js'
6
+ import {renderTable} from '../../formatters/table.js'
7
7
 
8
8
  /**
9
9
  * Return today's date as a local YYYY-MM-DD string.
@@ -15,17 +15,15 @@ function localTodayString() {
15
15
  }
16
16
 
17
17
  export default class TasksToday extends Command {
18
- static description = 'Task in lavorazione oggi: data odierna nel range [startDate, dueDate]. Include task scaduti non conclusi.'
18
+ static description =
19
+ 'Task in lavorazione oggi: data odierna nel range [startDate, dueDate]. Include task scaduti non conclusi.'
19
20
 
20
- static examples = [
21
- '<%= config.bin %> tasks today',
22
- '<%= config.bin %> tasks today --json',
23
- ]
21
+ static examples = ['<%= config.bin %> tasks today', '<%= config.bin %> tasks today --json']
24
22
 
25
23
  static enableJsonFlag = true
26
24
 
27
25
  async run() {
28
- const { flags } = await this.parse(TasksToday)
26
+ const {flags} = await this.parse(TasksToday)
29
27
  const isJson = flags.json
30
28
  const config = await loadConfig()
31
29
 
@@ -36,38 +34,42 @@ export default class TasksToday extends Command {
36
34
  const teamId = config.clickup?.teamId
37
35
  if (!teamId) this.error('ClickUp team ID not configured. Run `dvmi init` to configure ClickUp.')
38
36
 
39
- const spinner = isJson ? null : ora({ spinner: 'arc', color: false, text: chalk.hex('#FF6B2B')('Fetching today\'s tasks...') }).start()
37
+ const spinner = isJson
38
+ ? null
39
+ : ora({spinner: 'arc', color: false, text: chalk.hex('#FF6B2B')("Fetching today's tasks...")}).start()
40
40
  const tasks = await getTasksToday(teamId)
41
41
  spinner?.stop()
42
42
 
43
- if (isJson) return { tasks }
43
+ if (isJson) return {tasks}
44
44
 
45
45
  if (tasks.length === 0) {
46
46
  this.log(chalk.dim('No tasks for today.'))
47
47
  this.log(chalk.dim('Check `dvmi tasks list` for all assigned tasks.'))
48
- return { tasks: [] }
48
+ return {tasks: []}
49
49
  }
50
50
 
51
51
  const today = localTodayString()
52
52
 
53
- this.log(chalk.bold('\nToday\'s tasks:\n'))
54
- this.log(renderTable(tasks, [
55
- { header: 'Title', key: 'name', width: 45 },
56
- { header: 'Status', key: 'status', width: 15 },
57
- {
58
- header: 'Due',
59
- key: 'dueDate',
60
- width: 12,
61
- format: (v) => v ?? '—',
62
- colorize: (v) => {
63
- if (!v) return chalk.dim('—')
64
- if (v < today) return chalk.red.bold(v)
65
- return v
53
+ this.log(chalk.bold("\nToday's tasks:\n"))
54
+ this.log(
55
+ renderTable(tasks, [
56
+ {header: 'Title', key: 'name', width: 45},
57
+ {header: 'Status', key: 'status', width: 15},
58
+ {
59
+ header: 'Due',
60
+ key: 'dueDate',
61
+ width: 12,
62
+ format: (v) => v ?? '—',
63
+ colorize: (v) => {
64
+ if (!v) return chalk.dim('—')
65
+ if (v < today) return chalk.red.bold(v)
66
+ return v
67
+ },
66
68
  },
67
- },
68
- { header: 'Link', key: 'url', format: (v) => v ?? '—' },
69
- ]))
69
+ {header: 'Link', key: 'url', format: (v) => v ?? '—'},
70
+ ]),
71
+ )
70
72
 
71
- return { tasks }
73
+ return {tasks}
72
74
  }
73
75
  }
@@ -1,25 +1,24 @@
1
- import { Command } from '@oclif/core'
1
+ import {Command} from '@oclif/core'
2
2
  import chalk from 'chalk'
3
3
  import ora from 'ora'
4
- import { checkForUpdate } from '../services/version-check.js'
5
- import { exec } from '../services/shell.js'
4
+ import {checkForUpdate} from '../services/version-check.js'
5
+ import {exec} from '../services/shell.js'
6
6
 
7
7
  export default class Upgrade extends Command {
8
- static description = 'Aggiorna la CLI all\'ultima versione disponibile'
8
+ static description = "Aggiorna la CLI all'ultima versione disponibile"
9
9
 
10
- static examples = [
11
- '<%= config.bin %> upgrade',
12
- '<%= config.bin %> upgrade --json',
13
- ]
10
+ static examples = ['<%= config.bin %> upgrade', '<%= config.bin %> upgrade --json']
14
11
 
15
12
  static enableJsonFlag = true
16
13
 
17
14
  async run() {
18
- const { flags } = await this.parse(Upgrade)
15
+ const {flags} = await this.parse(Upgrade)
19
16
  const isJson = flags.json
20
17
 
21
- const spinner = isJson ? null : ora({ spinner: 'arc', color: false, text: chalk.hex('#FF6B2B')('Checking for updates...') }).start()
22
- const { hasUpdate, current, latest } = await checkForUpdate({ force: true })
18
+ const spinner = isJson
19
+ ? null
20
+ : ora({spinner: 'arc', color: false, text: chalk.hex('#FF6B2B')('Checking for updates...')}).start()
21
+ const {hasUpdate, current, latest} = await checkForUpdate({force: true})
23
22
  spinner?.stop()
24
23
 
25
24
  // Guard against malformed version strings from the GitHub Releases API
@@ -29,7 +28,7 @@ export default class Upgrade extends Command {
29
28
 
30
29
  if (!hasUpdate) {
31
30
  const msg = `You're already on the latest version (${current})`
32
- if (isJson) return { currentVersion: current, latestVersion: latest, updated: false }
31
+ if (isJson) return {currentVersion: current, latestVersion: latest, updated: false}
33
32
  this.log(chalk.green('✓') + ' ' + msg)
34
33
  return
35
34
  }
@@ -38,17 +37,19 @@ export default class Upgrade extends Command {
38
37
  this.log(`Updating from ${chalk.yellow(current)} to ${chalk.green(latest)}`)
39
38
  }
40
39
 
41
- const updateSpinner = isJson ? null : ora({ spinner: 'arc', color: false, text: chalk.hex('#FF6B2B')('Installing update...') }).start()
42
- // Non passare --registry globale: verrebbe usato anche per le dipendenze.
43
- // ~/.npmrc ha già devvami:registry per il solo scope corretto.
44
- const result = await exec('npm', ['install', '-g', `devvami@${latest}`])
40
+ const updateSpinner = isJson
41
+ ? null
42
+ : ora({spinner: 'arc', color: false, text: chalk.hex('#FF6B2B')('Installing update...')}).start()
43
+ // Non passare --registry globale: verrebbe usato anche per le dipendenze.
44
+ // ~/.npmrc ha già devvami:registry per il solo scope corretto.
45
+ const result = await exec('npm', ['install', '-g', `devvami@${latest}`])
45
46
  if (result.exitCode !== 0) {
46
47
  updateSpinner?.fail('Update failed')
47
48
  this.error(`Update failed: ${result.stderr}`)
48
49
  }
49
50
  updateSpinner?.succeed(`Updated to ${latest}`)
50
51
 
51
- const response = { currentVersion: current, latestVersion: latest, updated: true }
52
+ const response = {currentVersion: current, latestVersion: latest, updated: true}
52
53
  if (isJson) return response
53
54
 
54
55
  this.log(chalk.green('✓') + ` Successfully updated to ${latest}`)
@@ -1,9 +1,9 @@
1
- import { Command, Args, Flags } from '@oclif/core'
1
+ import {Command, Args, Flags} from '@oclif/core'
2
2
  import ora from 'ora'
3
- import { getCveDetail } from '../../services/nvd.js'
4
- import { formatCveDetail } from '../../formatters/vuln.js'
5
- import { openBrowser } from '../../utils/open-browser.js'
6
- import { ValidationError } from '../../utils/errors.js'
3
+ import {getCveDetail} from '../../services/nvd.js'
4
+ import {formatCveDetail} from '../../formatters/vuln.js'
5
+ import {openBrowser} from '../../utils/open-browser.js'
6
+ import {ValidationError} from '../../utils/errors.js'
7
7
 
8
8
  export default class VulnDetail extends Command {
9
9
  static description = 'View full details for a specific CVE'
@@ -17,7 +17,7 @@ export default class VulnDetail extends Command {
17
17
  static enableJsonFlag = true
18
18
 
19
19
  static args = {
20
- cveId: Args.string({ description: 'CVE identifier (e.g. CVE-2021-44228)', required: true }),
20
+ cveId: Args.string({description: 'CVE identifier (e.g. CVE-2021-44228)', required: true}),
21
21
  }
22
22
 
23
23
  static flags = {
@@ -29,9 +29,9 @@ export default class VulnDetail extends Command {
29
29
  }
30
30
 
31
31
  async run() {
32
- const { args, flags } = await this.parse(VulnDetail)
32
+ const {args, flags} = await this.parse(VulnDetail)
33
33
  const isJson = flags.json
34
- const { cveId } = args
34
+ const {cveId} = args
35
35
 
36
36
  if (!cveId || !/^CVE-\d{4}-\d{4,}$/i.test(cveId)) {
37
37
  throw new ValidationError(
@@ -1,10 +1,29 @@
1
- import { Command, Flags } from '@oclif/core'
2
- import { writeFile } from 'node:fs/promises'
1
+ import {Command, Flags} from '@oclif/core'
2
+ import {writeFile} from 'node:fs/promises'
3
3
  import ora from 'ora'
4
4
  import chalk from 'chalk'
5
- import { detectEcosystems, supportedEcosystemsMessage } from '../../services/audit-detector.js'
6
- import { runAudit, summarizeFindings, filterBySeverity } from '../../services/audit-runner.js'
7
- import { formatFindingsTable, formatScanSummary, formatMarkdownReport } from '../../formatters/vuln.js'
5
+ import {detectEcosystems, supportedEcosystemsMessage} from '../../services/audit-detector.js'
6
+ import {runAudit, summarizeFindings, filterBySeverity} from '../../services/audit-runner.js'
7
+ import {
8
+ formatFindingsTable,
9
+ formatScanSummary,
10
+ formatMarkdownReport,
11
+ truncate,
12
+ colorSeverity,
13
+ } from '../../formatters/vuln.js'
14
+ import {getCveDetail} from '../../services/nvd.js'
15
+ import {startInteractiveTable} from '../../utils/tui/navigable-table.js'
16
+
17
+ // Minimum terminal rows required to show the interactive TUI (same threshold as vuln search)
18
+ const MIN_TTY_ROWS = 6
19
+
20
+ // Column widths for the navigable table (match the static findings table)
21
+ const COL_WIDTHS = {
22
+ pkg: 20,
23
+ version: 12,
24
+ severity: 10,
25
+ cve: 20,
26
+ }
8
27
 
9
28
  export default class VulnScan extends Command {
10
29
  static description = 'Scan the current directory for known vulnerabilities in dependencies'
@@ -36,9 +55,9 @@ export default class VulnScan extends Command {
36
55
  }
37
56
 
38
57
  async run() {
39
- const { flags } = await this.parse(VulnScan)
58
+ const {flags} = await this.parse(VulnScan)
40
59
  const isJson = flags.json
41
- const { severity, 'no-fail': noFail, report } = flags
60
+ const {severity, 'no-fail': noFail, report} = flags
42
61
 
43
62
  const projectPath = process.env.DVMI_SCAN_DIR ?? process.cwd()
44
63
  const scanDate = new Date().toISOString()
@@ -53,8 +72,8 @@ export default class VulnScan extends Command {
53
72
  scanDate,
54
73
  ecosystems: [],
55
74
  findings: [],
56
- summary: { critical: 0, high: 0, medium: 0, low: 0, unknown: 0, total: 0 },
57
- errors: [{ ecosystem: 'none', message: 'No supported package manager detected.' }],
75
+ summary: {critical: 0, high: 0, medium: 0, low: 0, unknown: 0, total: 0},
76
+ errors: [{ecosystem: 'none', message: 'No supported package manager detected.'}],
58
77
  }
59
78
  }
60
79
 
@@ -86,11 +105,11 @@ export default class VulnScan extends Command {
86
105
  for (const eco of ecosystems) {
87
106
  const spinner = isJson ? null : ora(` Scanning ${eco.name} dependencies...`).start()
88
107
 
89
- const { findings, error } = await runAudit(eco)
108
+ const {findings, error} = await runAudit(eco)
90
109
 
91
110
  if (error) {
92
111
  spinner?.fail(` Scanning ${eco.name} dependencies... failed`)
93
- errors.push({ ecosystem: eco.name, message: error })
112
+ errors.push({ecosystem: eco.name, message: error})
94
113
  } else {
95
114
  spinner?.succeed(` Scanning ${eco.name} dependencies... done`)
96
115
  allFindings.push(...findings)
@@ -112,7 +131,7 @@ export default class VulnScan extends Command {
112
131
  errors,
113
132
  }
114
133
 
115
- // Write report if requested
134
+ // Write report if requested (always, regardless of TTY mode)
116
135
  if (report) {
117
136
  const markdown = formatMarkdownReport(result)
118
137
  await writeFile(report, markdown, 'utf8')
@@ -128,17 +147,71 @@ export default class VulnScan extends Command {
128
147
  return result
129
148
  }
130
149
 
131
- if (filteredFindings.length > 0) {
132
- this.log(chalk.bold(` Findings (${filteredFindings.length} ${filteredFindings.length === 1 ? 'vulnerability' : 'vulnerabilities'})`))
133
- this.log('')
134
- this.log(formatFindingsTable(filteredFindings))
135
- this.log('')
136
- this.log(chalk.bold(' Summary'))
137
- this.log(formatScanSummary(summary))
138
- this.log('')
139
- this.log(chalk.yellow(` ⚠ ${filteredFindings.length} ${filteredFindings.length === 1 ? 'vulnerability' : 'vulnerabilities'} found. Run \`dvmi vuln detail <CVE-ID>\` for details.`))
150
+ // ── TTY interactive table ──────────────────────────────────────────────────
151
+ // In a real TTY with enough rows and at least one finding, replace the static
152
+ // table with the navigable TUI (same experience as `dvmi vuln search`).
153
+ const ttyRows = process.stdout.rows ?? 0
154
+ const useTUI = process.stdout.isTTY && filteredFindings.length > 0 && ttyRows >= MIN_TTY_ROWS
155
+
156
+ if (useTUI) {
157
+ const count = filteredFindings.length
158
+ const label = count === 1 ? 'finding' : 'findings'
159
+ const heading = `Vulnerability Scan: ${count} ${label}`
160
+
161
+ const termCols = process.stdout.columns || 80
162
+ // Title width: whatever is left after Package + Version + Severity + CVE + separators
163
+ const fixedCols = COL_WIDTHS.pkg + COL_WIDTHS.version + COL_WIDTHS.severity + COL_WIDTHS.cve
164
+ const separators = 5 * 2 // 5 gaps between 5 columns
165
+ const titleWidth = Math.max(15, Math.min(50, termCols - fixedCols - separators))
166
+
167
+ const rows = filteredFindings.map((f) => ({
168
+ id: f.cveId ?? null,
169
+ pkg: f.package,
170
+ version: f.installedVersion ?? '—',
171
+ severity: f.severity,
172
+ cve: f.cveId ?? '—',
173
+ title: truncate(f.title ?? '—', titleWidth),
174
+ advisoryUrl: f.advisoryUrl ?? null,
175
+ }))
176
+
177
+ /** @type {import('../../utils/tui/navigable-table.js').TableColumnDef[]} */
178
+ const columns = [
179
+ {header: 'Package', key: 'pkg', width: COL_WIDTHS.pkg},
180
+ {header: 'Version', key: 'version', width: COL_WIDTHS.version},
181
+ {header: 'Severity', key: 'severity', width: COL_WIDTHS.severity, colorize: (v) => colorSeverity(v)},
182
+ {
183
+ header: 'CVE',
184
+ key: 'cve',
185
+ width: COL_WIDTHS.cve,
186
+ colorize: (v) => (v !== '—' ? chalk.cyan(v) : chalk.gray(v)),
187
+ },
188
+ {header: 'Title', key: 'title', width: titleWidth},
189
+ ]
190
+
191
+ await startInteractiveTable(rows, columns, heading, filteredFindings.length, getCveDetail)
192
+ } else {
193
+ // Non-TTY fallback: static table + summary (unchanged from pre-TUI behaviour)
194
+ if (filteredFindings.length > 0) {
195
+ this.log(
196
+ chalk.bold(
197
+ ` Findings (${filteredFindings.length} ${filteredFindings.length === 1 ? 'vulnerability' : 'vulnerabilities'})`,
198
+ ),
199
+ )
200
+ this.log('')
201
+ this.log(formatFindingsTable(filteredFindings))
202
+ this.log('')
203
+ this.log(chalk.bold(' Summary'))
204
+ this.log(formatScanSummary(summary))
205
+ this.log('')
206
+ this.log(
207
+ chalk.yellow(
208
+ ` ⚠ ${filteredFindings.length} ${filteredFindings.length === 1 ? 'vulnerability' : 'vulnerabilities'} found. Run \`dvmi vuln detail <CVE-ID>\` for details.`,
209
+ ),
210
+ )
211
+ }
140
212
  }
141
213
 
214
+ // Always print audit errors (e.g. tool not installed) after findings/TUI
142
215
  if (errors.length > 0) {
143
216
  this.log('')
144
217
  for (const err of errors) {
@@ -146,6 +219,7 @@ export default class VulnScan extends Command {
146
219
  }
147
220
  }
148
221
 
222
+ // Preserve exit code semantics: exit 1 when vulns found (unless --no-fail)
149
223
  if (filteredFindings.length > 0 && !noFail) {
150
224
  this.exit(1)
151
225
  }
@@ -1,10 +1,10 @@
1
- import { Command, Args, Flags } from '@oclif/core'
1
+ import {Command, Args, Flags} from '@oclif/core'
2
2
  import ora from 'ora'
3
3
  import chalk from 'chalk'
4
- import { searchCves, getCveDetail } from '../../services/nvd.js'
5
- import { formatCveSearchTable, colorSeverity, formatScore, formatDate, truncate } from '../../formatters/vuln.js'
6
- import { startInteractiveTable } from '../../utils/tui/navigable-table.js'
7
- import { ValidationError } from '../../utils/errors.js'
4
+ import {searchCves, getCveDetail} from '../../services/nvd.js'
5
+ import {formatCveSearchTable, colorSeverity, formatScore, formatDate, truncate} from '../../formatters/vuln.js'
6
+ import {startInteractiveTable} from '../../utils/tui/navigable-table.js'
7
+ import {ValidationError} from '../../utils/errors.js'
8
8
 
9
9
  // Minimum terminal rows required to show the interactive TUI
10
10
  const MIN_TTY_ROWS = 6
@@ -33,7 +33,10 @@ export default class VulnSearch extends Command {
33
33
  static enableJsonFlag = true
34
34
 
35
35
  static args = {
36
- keyword: Args.string({ description: 'Product, library, or keyword to search for (optional — omit to see all recent CVEs)', required: false }),
36
+ keyword: Args.string({
37
+ description: 'Product, library, or keyword to search for (optional — omit to see all recent CVEs)',
38
+ required: false,
39
+ }),
37
40
  }
38
41
 
39
42
  static flags = {
@@ -55,11 +58,11 @@ export default class VulnSearch extends Command {
55
58
  }
56
59
 
57
60
  async run() {
58
- const { args, flags } = await this.parse(VulnSearch)
61
+ const {args, flags} = await this.parse(VulnSearch)
59
62
  const isJson = flags.json
60
63
 
61
- const { keyword } = args
62
- const { days, severity, limit } = flags
64
+ const {keyword} = args
65
+ const {days, severity, limit} = flags
63
66
 
64
67
  if (days < 1 || days > 120) {
65
68
  throw new ValidationError(
@@ -75,13 +78,15 @@ export default class VulnSearch extends Command {
75
78
  )
76
79
  }
77
80
 
78
- const spinner = isJson ? null : ora(keyword ? `Searching NVD for "${keyword}"...` : `Fetching recent CVEs (last ${days} days)...`).start()
81
+ const spinner = isJson
82
+ ? null
83
+ : ora(keyword ? `Searching NVD for "${keyword}"...` : `Fetching recent CVEs (last ${days} days)...`).start()
79
84
 
80
85
  try {
81
- const { results, totalResults } = await searchCves({ keyword, days, severity, limit })
86
+ const {results, totalResults} = await searchCves({keyword, days, severity, limit})
82
87
  spinner?.stop()
83
88
 
84
- const result = { keyword: keyword ?? null, days, severity: severity ?? null, totalResults, results }
89
+ const result = {keyword: keyword ?? null, days, severity: severity ?? null, totalResults, results}
85
90
 
86
91
  if (isJson) return result
87
92
 
@@ -108,12 +113,12 @@ export default class VulnSearch extends Command {
108
113
 
109
114
  /** @type {import('../../utils/tui/navigable-table.js').TableColumnDef[]} */
110
115
  const columns = [
111
- { header: 'CVE ID', key: 'id', width: COL_WIDTHS.id, colorize: (v) => chalk.cyan(v) },
112
- { header: 'Severity', key: 'severity', width: COL_WIDTHS.severity, colorize: (v) => colorSeverity(v) },
113
- { header: 'Score', key: 'score', width: COL_WIDTHS.score },
114
- { header: 'Published', key: 'published', width: COL_WIDTHS.published },
115
- { header: 'Description', key: 'description', width: descWidth },
116
- { header: 'Reference', key: 'reference', width: COL_WIDTHS.reference },
116
+ {header: 'CVE ID', key: 'id', width: COL_WIDTHS.id, colorize: (v) => chalk.cyan(v)},
117
+ {header: 'Severity', key: 'severity', width: COL_WIDTHS.severity, colorize: (v) => colorSeverity(v)},
118
+ {header: 'Score', key: 'score', width: COL_WIDTHS.score},
119
+ {header: 'Published', key: 'published', width: COL_WIDTHS.published},
120
+ {header: 'Description', key: 'description', width: descWidth},
121
+ {header: 'Reference', key: 'reference', width: COL_WIDTHS.reference},
117
122
  ]
118
123
 
119
124
  await startInteractiveTable(rows, columns, heading, totalResults, getCveDetail)
@@ -1,5 +1,5 @@
1
- import { Command } from '@oclif/core'
2
- import { printWelcomeScreen } from '../utils/welcome.js'
1
+ import {Command} from '@oclif/core'
2
+ import {printWelcomeScreen} from '../utils/welcome.js'
3
3
 
4
4
  /**
5
5
  * Display the dvmi cyberpunk mission dashboard.
@@ -1,66 +1,62 @@
1
- import { Command } from '@oclif/core'
1
+ import {Command} from '@oclif/core'
2
2
  import chalk from 'chalk'
3
3
  import ora from 'ora'
4
- import { createOctokit } from '../services/github.js'
5
- import { checkAWSAuth } from '../services/auth.js'
6
- import { getCurrentVersion } from '../services/version-check.js'
7
- import { CONFIG_PATH, loadConfig } from '../services/config.js'
8
- import { getUser, isAuthenticated } from '../services/clickup.js'
4
+ import {createOctokit} from '../services/github.js'
5
+ import {checkAWSAuth} from '../services/auth.js'
6
+ import {getCurrentVersion} from '../services/version-check.js'
7
+ import {CONFIG_PATH, loadConfig} from '../services/config.js'
8
+ import {getUser, isAuthenticated} from '../services/clickup.js'
9
9
 
10
10
  export default class Whoami extends Command {
11
11
  static description = 'Mostra la tua identita su GitHub, AWS e ClickUp'
12
12
 
13
- static examples = [
14
- '<%= config.bin %> whoami',
15
- '<%= config.bin %> whoami --json',
16
- ]
13
+ static examples = ['<%= config.bin %> whoami', '<%= config.bin %> whoami --json']
17
14
 
18
15
  static enableJsonFlag = true
19
16
 
20
17
  async run() {
21
- const { flags } = await this.parse(Whoami)
18
+ const {flags} = await this.parse(Whoami)
22
19
  const isJson = flags.json
23
20
 
24
- const spinner = isJson ? null : ora({ spinner: 'arc', color: false, text: chalk.hex('#FF6B2B')('Fetching identity...') }).start()
21
+ const spinner = isJson
22
+ ? null
23
+ : ora({spinner: 'arc', color: false, text: chalk.hex('#FF6B2B')('Fetching identity...')}).start()
25
24
 
26
25
  const [ghResult, awsResult, version, cuResult] = await Promise.allSettled([
27
26
  (async () => {
28
27
  const octokit = await createOctokit()
29
- const { data: user } = await octokit.rest.users.getAuthenticated()
30
- return { username: user.login, name: user.name ?? '', org: '', teams: [] }
28
+ const {data: user} = await octokit.rest.users.getAuthenticated()
29
+ return {username: user.login, name: user.name ?? '', org: '', teams: []}
31
30
  })(),
32
31
  checkAWSAuth(),
33
32
  getCurrentVersion(),
34
33
  (async () => {
35
34
  if (!(await isAuthenticated())) return null
36
35
  const [user, config] = await Promise.all([getUser(), loadConfig()])
37
- return { username: user.username, teamName: config.clickup?.teamName ?? null }
36
+ return {username: user.username, teamName: config.clickup?.teamName ?? null}
38
37
  })(),
39
38
  ])
40
39
 
41
40
  spinner?.stop()
42
41
 
43
- const github =
44
- ghResult.status === 'fulfilled'
45
- ? ghResult.value
46
- : { username: null, error: '[NOT AUTHENTICATED]' }
42
+ const github = ghResult.status === 'fulfilled' ? ghResult.value : {username: null, error: '[NOT AUTHENTICATED]'}
47
43
 
48
44
  const aws =
49
45
  awsResult.status === 'fulfilled' && awsResult.value.authenticated
50
- ? { accountId: awsResult.value.account, role: awsResult.value.role }
51
- : { accountId: null, error: '[NOT AUTHENTICATED]' }
46
+ ? {accountId: awsResult.value.account, role: awsResult.value.role}
47
+ : {accountId: null, error: '[NOT AUTHENTICATED]'}
52
48
 
53
49
  const clickup =
54
50
  cuResult.status === 'fulfilled' && cuResult.value
55
51
  ? cuResult.value
56
- : { username: null, teamName: null, error: '[NOT AUTHENTICATED]' }
52
+ : {username: null, teamName: null, error: '[NOT AUTHENTICATED]'}
57
53
 
58
54
  const cli = {
59
55
  version: version.status === 'fulfilled' ? version.value : '?',
60
56
  configPath: CONFIG_PATH,
61
57
  }
62
58
 
63
- const result = { github, aws, clickup, cli }
59
+ const result = {github, aws, clickup, cli}
64
60
 
65
61
  if (isJson) return result
66
62