devvami 1.4.2 → 1.5.1

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 +72 -0
  2. package/oclif.manifest.json +275 -235
  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 +257 -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 +39 -20
  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 +215 -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 +349 -0
  62. package/src/services/ai-env-deployer.js +650 -0
  63. package/src/services/ai-env-scanner.js +983 -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 +117 -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 +1184 -0
  91. package/src/utils/tui/modal.js +15 -14
  92. package/src/utils/tui/navigable-table.js +16 -16
  93. package/src/utils/tui/tab-tui.js +1089 -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, Flags } from '@oclif/core'
1
+ import {Command, Flags} from '@oclif/core'
2
2
  import chalk from 'chalk'
3
3
  import ora from 'ora'
4
- import { getTasks, getTasksByList, isAuthenticated } from '../../services/clickup.js'
5
- import { loadConfig } from '../../services/config.js'
6
- import { renderTable } from '../../formatters/table.js'
4
+ import {getTasks, getTasksByList, isAuthenticated} from '../../services/clickup.js'
5
+ import {loadConfig} from '../../services/config.js'
6
+ import {renderTable} from '../../formatters/table.js'
7
7
 
8
8
  export default class TasksList extends Command {
9
9
  static description = 'Task ClickUp assegnati a te'
@@ -21,7 +21,7 @@ export default class TasksList extends Command {
21
21
  static enableJsonFlag = true
22
22
 
23
23
  static flags = {
24
- status: Flags.string({ description: 'Filtra per status (open, in_progress, done)' }),
24
+ status: Flags.string({description: 'Filtra per status (open, in_progress, done)'}),
25
25
  search: Flags.string({
26
26
  char: 's',
27
27
  description: 'Cerca nel titolo del task (case-insensitive)',
@@ -32,7 +32,7 @@ export default class TasksList extends Command {
32
32
  }
33
33
 
34
34
  async run() {
35
- const { flags } = await this.parse(TasksList)
35
+ const {flags} = await this.parse(TasksList)
36
36
  const isJson = flags.json
37
37
  const config = await loadConfig()
38
38
 
@@ -47,7 +47,9 @@ export default class TasksList extends Command {
47
47
  this.error('ClickUp team ID not configured. Run `dvmi init` to configure ClickUp.')
48
48
  }
49
49
 
50
- const spinner = isJson ? null : ora({ spinner: 'arc', color: false, text: chalk.hex('#FF6B2B')('Fetching tasks...') }).start()
50
+ const spinner = isJson
51
+ ? null
52
+ : ora({spinner: 'arc', color: false, text: chalk.hex('#FF6B2B')('Fetching tasks...')}).start()
51
53
 
52
54
  /** @param {number} count */
53
55
  const onProgress = (count) => {
@@ -56,40 +58,38 @@ export default class TasksList extends Command {
56
58
 
57
59
  let tasks
58
60
  if (flags['list-id']) {
59
- tasks = await getTasksByList(flags['list-id'], { status: flags.status }, onProgress).catch((err) => {
61
+ tasks = await getTasksByList(flags['list-id'], {status: flags.status}, onProgress).catch((err) => {
60
62
  spinner?.stop()
61
63
  this.error(err.message)
62
64
  })
63
65
  } else {
64
- tasks = await getTasks(/** @type {string} */ (teamId), { status: flags.status }, onProgress)
66
+ tasks = await getTasks(/** @type {string} */ (teamId), {status: flags.status}, onProgress)
65
67
  }
66
68
  spinner?.stop()
67
69
 
68
70
  // Apply search filter
69
71
  const searchQuery = flags.search?.toLowerCase()
70
- const filtered = searchQuery
71
- ? tasks.filter((t) => t.name.toLowerCase().includes(searchQuery))
72
- : tasks
72
+ const filtered = searchQuery ? tasks.filter((t) => t.name.toLowerCase().includes(searchQuery)) : tasks
73
73
 
74
- if (isJson) return { tasks: filtered }
74
+ if (isJson) return {tasks: filtered}
75
75
 
76
76
  if (tasks.length === 0) {
77
77
  this.log(chalk.dim('No tasks assigned to you.'))
78
- return { tasks: [] }
78
+ return {tasks: []}
79
79
  }
80
80
 
81
81
  if (filtered.length === 0) {
82
82
  this.log(chalk.dim(`No tasks matching filters.`))
83
- return { tasks: [] }
83
+ return {tasks: []}
84
84
  }
85
85
 
86
86
  // Priority label + color
87
87
  const priorityLabel = (p) => ['', 'URGENT', 'HIGH', 'NORMAL', 'LOW'][p] ?? String(p)
88
88
  const priorityColor = (label) => {
89
89
  if (label === 'URGENT') return chalk.red.bold(label)
90
- if (label === 'HIGH') return chalk.yellow(label)
90
+ if (label === 'HIGH') return chalk.yellow(label)
91
91
  if (label === 'NORMAL') return chalk.white(label)
92
- if (label === 'LOW') return chalk.dim(label)
92
+ if (label === 'LOW') return chalk.dim(label)
93
93
  return label
94
94
  }
95
95
 
@@ -98,7 +98,7 @@ export default class TasksList extends Command {
98
98
  const s = status.toLowerCase()
99
99
  if (s.includes('done') || s.includes('complet') || s.includes('closed')) return chalk.green(status)
100
100
  if (s.includes('progress') || s.includes('active') || s.includes('open')) return chalk.cyan(status)
101
- if (s.includes('block') || s.includes('review') || s.includes('wait')) return chalk.yellow(status)
101
+ if (s.includes('block') || s.includes('review') || s.includes('wait')) return chalk.yellow(status)
102
102
  return chalk.dim(status)
103
103
  }
104
104
 
@@ -107,27 +107,37 @@ export default class TasksList extends Command {
107
107
  flags.status && chalk.dim(`status: ${chalk.white(flags.status)}`),
108
108
  flags.search && chalk.dim(`search: ${chalk.white(`"${flags.search}"`)}`),
109
109
  flags['list-id'] && chalk.dim(`list-id: ${chalk.white(flags['list-id'])}`),
110
- ].filter(Boolean).join(chalk.dim(' · '))
110
+ ]
111
+ .filter(Boolean)
112
+ .join(chalk.dim(' · '))
111
113
 
112
114
  this.log(
113
115
  chalk.bold('\nYour tasks') +
114
- (filterInfo ? chalk.dim(' — ') + filterInfo : '') +
115
- chalk.dim(` (${filtered.length}${filtered.length < tasks.length ? `/${tasks.length}` : ''})`) +
116
- '\n',
116
+ (filterInfo ? chalk.dim(' — ') + filterInfo : '') +
117
+ chalk.dim(` (${filtered.length}${filtered.length < tasks.length ? `/${tasks.length}` : ''})`) +
118
+ '\n',
117
119
  )
118
120
 
119
- this.log(renderTable(filtered, [
120
- { header: 'ID', key: 'id', width: 10 },
121
- { header: 'Link', key: 'url', width: 42, format: (v) => v ?? '—' },
122
- { header: 'Priority', key: 'priority', width: 8, format: (v) => priorityLabel(Number(v)), colorize: priorityColor },
123
- { header: 'Status', key: 'status', width: 15, colorize: statusColor },
124
- { header: 'Due', key: 'dueDate', width: 12, format: (v) => v ?? '—' },
125
- { header: 'Lista', key: 'listName', width: 20, format: (v) => v ?? '—' },
126
- { header: 'Cartella', key: 'folderName', width: 20, format: (v) => v ?? '—' },
127
- { header: 'Description', key: 'name', width: 55 },
128
- ]))
121
+ this.log(
122
+ renderTable(filtered, [
123
+ {header: 'ID', key: 'id', width: 10},
124
+ {header: 'Link', key: 'url', width: 42, format: (v) => v ?? '—'},
125
+ {
126
+ header: 'Priority',
127
+ key: 'priority',
128
+ width: 8,
129
+ format: (v) => priorityLabel(Number(v)),
130
+ colorize: priorityColor,
131
+ },
132
+ {header: 'Status', key: 'status', width: 15, colorize: statusColor},
133
+ {header: 'Due', key: 'dueDate', width: 12, format: (v) => v ?? '—'},
134
+ {header: 'Lista', key: 'listName', width: 20, format: (v) => v ?? '—'},
135
+ {header: 'Cartella', key: 'folderName', width: 20, format: (v) => v ?? '—'},
136
+ {header: 'Description', key: 'name', width: 55},
137
+ ]),
138
+ )
129
139
 
130
140
  this.log('')
131
- return { tasks: filtered }
141
+ return {tasks: filtered}
132
142
  }
133
143
  }
@@ -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,12 +1,18 @@
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, truncate, colorSeverity } from '../../formatters/vuln.js'
8
- import { getCveDetail } from '../../services/nvd.js'
9
- import { startInteractiveTable } from '../../utils/tui/navigable-table.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'
10
16
 
11
17
  // Minimum terminal rows required to show the interactive TUI (same threshold as vuln search)
12
18
  const MIN_TTY_ROWS = 6
@@ -49,9 +55,9 @@ export default class VulnScan extends Command {
49
55
  }
50
56
 
51
57
  async run() {
52
- const { flags } = await this.parse(VulnScan)
58
+ const {flags} = await this.parse(VulnScan)
53
59
  const isJson = flags.json
54
- const { severity, 'no-fail': noFail, report } = flags
60
+ const {severity, 'no-fail': noFail, report} = flags
55
61
 
56
62
  const projectPath = process.env.DVMI_SCAN_DIR ?? process.cwd()
57
63
  const scanDate = new Date().toISOString()
@@ -66,8 +72,8 @@ export default class VulnScan extends Command {
66
72
  scanDate,
67
73
  ecosystems: [],
68
74
  findings: [],
69
- summary: { critical: 0, high: 0, medium: 0, low: 0, unknown: 0, total: 0 },
70
- 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.'}],
71
77
  }
72
78
  }
73
79
 
@@ -99,11 +105,11 @@ export default class VulnScan extends Command {
99
105
  for (const eco of ecosystems) {
100
106
  const spinner = isJson ? null : ora(` Scanning ${eco.name} dependencies...`).start()
101
107
 
102
- const { findings, error } = await runAudit(eco)
108
+ const {findings, error} = await runAudit(eco)
103
109
 
104
110
  if (error) {
105
111
  spinner?.fail(` Scanning ${eco.name} dependencies... failed`)
106
- errors.push({ ecosystem: eco.name, message: error })
112
+ errors.push({ecosystem: eco.name, message: error})
107
113
  } else {
108
114
  spinner?.succeed(` Scanning ${eco.name} dependencies... done`)
109
115
  allFindings.push(...findings)
@@ -170,25 +176,38 @@ export default class VulnScan extends Command {
170
176
 
171
177
  /** @type {import('../../utils/tui/navigable-table.js').TableColumnDef[]} */
172
178
  const columns = [
173
- { header: 'Package', key: 'pkg', width: COL_WIDTHS.pkg },
174
- { header: 'Version', key: 'version', width: COL_WIDTHS.version },
175
- { header: 'Severity', key: 'severity', width: COL_WIDTHS.severity, colorize: (v) => colorSeverity(v) },
176
- { header: 'CVE', key: 'cve', width: COL_WIDTHS.cve, colorize: (v) => (v !== '—' ? chalk.cyan(v) : chalk.gray(v)) },
177
- { header: 'Title', key: 'title', width: titleWidth },
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},
178
189
  ]
179
190
 
180
191
  await startInteractiveTable(rows, columns, heading, filteredFindings.length, getCveDetail)
181
192
  } else {
182
193
  // Non-TTY fallback: static table + summary (unchanged from pre-TUI behaviour)
183
194
  if (filteredFindings.length > 0) {
184
- this.log(chalk.bold(` Findings (${filteredFindings.length} ${filteredFindings.length === 1 ? 'vulnerability' : 'vulnerabilities'})`))
195
+ this.log(
196
+ chalk.bold(
197
+ ` Findings (${filteredFindings.length} ${filteredFindings.length === 1 ? 'vulnerability' : 'vulnerabilities'})`,
198
+ ),
199
+ )
185
200
  this.log('')
186
201
  this.log(formatFindingsTable(filteredFindings))
187
202
  this.log('')
188
203
  this.log(chalk.bold(' Summary'))
189
204
  this.log(formatScanSummary(summary))
190
205
  this.log('')
191
- this.log(chalk.yellow(` ⚠ ${filteredFindings.length} ${filteredFindings.length === 1 ? 'vulnerability' : 'vulnerabilities'} found. Run \`dvmi vuln detail <CVE-ID>\` for details.`))
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
+ )
192
211
  }
193
212
  }
194
213
 
@@ -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