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.
- package/README.md +72 -0
- package/oclif.manifest.json +275 -235
- package/package.json +2 -1
- package/src/commands/auth/login.js +20 -16
- package/src/commands/changelog.js +12 -12
- package/src/commands/costs/get.js +14 -24
- package/src/commands/costs/trend.js +13 -24
- package/src/commands/create/repo.js +72 -54
- package/src/commands/docs/list.js +29 -25
- package/src/commands/docs/projects.js +58 -24
- package/src/commands/docs/read.js +56 -39
- package/src/commands/docs/search.js +37 -25
- package/src/commands/doctor.js +37 -35
- package/src/commands/dotfiles/add.js +51 -39
- package/src/commands/dotfiles/setup.js +62 -33
- package/src/commands/dotfiles/status.js +18 -18
- package/src/commands/dotfiles/sync.js +62 -46
- package/src/commands/init.js +143 -132
- package/src/commands/logs/index.js +10 -16
- package/src/commands/open.js +12 -12
- package/src/commands/pipeline/logs.js +8 -11
- package/src/commands/pipeline/rerun.js +21 -16
- package/src/commands/pipeline/status.js +28 -24
- package/src/commands/pr/create.js +40 -27
- package/src/commands/pr/detail.js +9 -7
- package/src/commands/pr/review.js +18 -19
- package/src/commands/pr/status.js +27 -21
- package/src/commands/prompts/browse.js +15 -15
- package/src/commands/prompts/download.js +15 -16
- package/src/commands/prompts/install-speckit.js +11 -12
- package/src/commands/prompts/list.js +12 -12
- package/src/commands/prompts/run.js +16 -19
- package/src/commands/repo/list.js +57 -41
- package/src/commands/search.js +20 -18
- package/src/commands/security/setup.js +38 -34
- package/src/commands/sync-config-ai/index.js +257 -0
- package/src/commands/tasks/assigned.js +43 -33
- package/src/commands/tasks/list.js +43 -33
- package/src/commands/tasks/today.js +32 -30
- package/src/commands/upgrade.js +18 -17
- package/src/commands/vuln/detail.js +8 -8
- package/src/commands/vuln/scan.js +39 -20
- package/src/commands/vuln/search.js +23 -18
- package/src/commands/welcome.js +2 -2
- package/src/commands/whoami.js +19 -23
- package/src/formatters/ai-config.js +215 -0
- package/src/formatters/charts.js +6 -23
- package/src/formatters/cost.js +1 -7
- package/src/formatters/dotfiles.js +48 -19
- package/src/formatters/markdown.js +11 -6
- package/src/formatters/openapi.js +7 -9
- package/src/formatters/prompts.js +69 -78
- package/src/formatters/security.js +2 -2
- package/src/formatters/status.js +1 -1
- package/src/formatters/table.js +1 -3
- package/src/formatters/vuln.js +33 -20
- package/src/help.js +162 -164
- package/src/hooks/init.js +1 -3
- package/src/hooks/postrun.js +5 -7
- package/src/index.js +1 -1
- package/src/services/ai-config-store.js +349 -0
- package/src/services/ai-env-deployer.js +650 -0
- package/src/services/ai-env-scanner.js +983 -0
- package/src/services/audit-detector.js +2 -2
- package/src/services/audit-runner.js +40 -31
- package/src/services/auth.js +9 -9
- package/src/services/awesome-copilot.js +7 -4
- package/src/services/aws-costs.js +22 -22
- package/src/services/clickup.js +26 -26
- package/src/services/cloudwatch-logs.js +5 -9
- package/src/services/config.js +13 -13
- package/src/services/docs.js +19 -20
- package/src/services/dotfiles.js +149 -51
- package/src/services/github.js +22 -24
- package/src/services/nvd.js +21 -31
- package/src/services/platform.js +2 -2
- package/src/services/prompts.js +23 -35
- package/src/services/security.js +135 -61
- package/src/services/shell.js +4 -4
- package/src/services/skills-sh.js +3 -9
- package/src/services/speckit.js +4 -7
- package/src/services/version-check.js +10 -10
- package/src/types.js +117 -0
- package/src/utils/aws-vault.js +18 -41
- package/src/utils/banner.js +5 -7
- package/src/utils/errors.js +42 -46
- package/src/utils/frontmatter.js +4 -4
- package/src/utils/gradient.js +18 -16
- package/src/utils/open-browser.js +3 -3
- package/src/utils/tui/form.js +1184 -0
- package/src/utils/tui/modal.js +15 -14
- package/src/utils/tui/navigable-table.js +16 -16
- package/src/utils/tui/tab-tui.js +1089 -0
- package/src/utils/typewriter.js +3 -3
- package/src/utils/welcome.js +18 -21
- package/src/validators/repo-name.js +2 -2
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {Command, Flags} from '@oclif/core'
|
|
2
2
|
import chalk from 'chalk'
|
|
3
3
|
import ora from 'ora'
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
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({
|
|
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 {
|
|
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
|
|
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'], {
|
|
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), {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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')
|
|
90
|
+
if (label === 'HIGH') return chalk.yellow(label)
|
|
91
91
|
if (label === 'NORMAL') return chalk.white(label)
|
|
92
|
-
if (label === 'LOW')
|
|
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'))
|
|
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
|
-
]
|
|
110
|
+
]
|
|
111
|
+
.filter(Boolean)
|
|
112
|
+
.join(chalk.dim(' · '))
|
|
111
113
|
|
|
112
114
|
this.log(
|
|
113
115
|
chalk.bold('\nYour tasks') +
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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(
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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 {
|
|
141
|
+
return {tasks: filtered}
|
|
132
142
|
}
|
|
133
143
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {Command} from '@oclif/core'
|
|
2
2
|
import chalk from 'chalk'
|
|
3
3
|
import ora from 'ora'
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
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 =
|
|
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 {
|
|
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
|
|
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 {
|
|
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 {
|
|
48
|
+
return {tasks: []}
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
const today = localTodayString()
|
|
52
52
|
|
|
53
|
-
this.log(chalk.bold(
|
|
54
|
-
this.log(
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
69
|
-
|
|
69
|
+
{header: 'Link', key: 'url', format: (v) => v ?? '—'},
|
|
70
|
+
]),
|
|
71
|
+
)
|
|
70
72
|
|
|
71
|
-
return {
|
|
73
|
+
return {tasks}
|
|
72
74
|
}
|
|
73
75
|
}
|
package/src/commands/upgrade.js
CHANGED
|
@@ -1,25 +1,24 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {Command} from '@oclif/core'
|
|
2
2
|
import chalk from 'chalk'
|
|
3
3
|
import ora from 'ora'
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
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 =
|
|
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 {
|
|
15
|
+
const {flags} = await this.parse(Upgrade)
|
|
19
16
|
const isJson = flags.json
|
|
20
17
|
|
|
21
|
-
const spinner = isJson
|
|
22
|
-
|
|
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 {
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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 = {
|
|
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 {
|
|
1
|
+
import {Command, Args, Flags} from '@oclif/core'
|
|
2
2
|
import ora from 'ora'
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
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({
|
|
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 {
|
|
32
|
+
const {args, flags} = await this.parse(VulnDetail)
|
|
33
33
|
const isJson = flags.json
|
|
34
|
-
const {
|
|
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 {
|
|
2
|
-
import {
|
|
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 {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
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 {
|
|
58
|
+
const {flags} = await this.parse(VulnScan)
|
|
53
59
|
const isJson = flags.json
|
|
54
|
-
const {
|
|
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: {
|
|
70
|
-
errors: [{
|
|
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 {
|
|
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({
|
|
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
|
-
{
|
|
174
|
-
{
|
|
175
|
-
{
|
|
176
|
-
{
|
|
177
|
-
|
|
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(
|
|
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(
|
|
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 {
|
|
1
|
+
import {Command, Args, Flags} from '@oclif/core'
|
|
2
2
|
import ora from 'ora'
|
|
3
3
|
import chalk from 'chalk'
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
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({
|
|
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 {
|
|
61
|
+
const {args, flags} = await this.parse(VulnSearch)
|
|
59
62
|
const isJson = flags.json
|
|
60
63
|
|
|
61
|
-
const {
|
|
62
|
-
const {
|
|
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
|
|
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 {
|
|
86
|
+
const {results, totalResults} = await searchCves({keyword, days, severity, limit})
|
|
82
87
|
spinner?.stop()
|
|
83
88
|
|
|
84
|
-
const result = {
|
|
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
|
-
{
|
|
112
|
-
{
|
|
113
|
-
{
|
|
114
|
-
{
|
|
115
|
-
{
|
|
116
|
-
{
|
|
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)
|
package/src/commands/welcome.js
CHANGED
package/src/commands/whoami.js
CHANGED
|
@@ -1,66 +1,62 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {Command} from '@oclif/core'
|
|
2
2
|
import chalk from 'chalk'
|
|
3
3
|
import ora from 'ora'
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
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 {
|
|
18
|
+
const {flags} = await this.parse(Whoami)
|
|
22
19
|
const isJson = flags.json
|
|
23
20
|
|
|
24
|
-
const spinner = isJson
|
|
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 {
|
|
30
|
-
return {
|
|
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 {
|
|
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
|
-
? {
|
|
51
|
-
: {
|
|
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
|
-
: {
|
|
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 = {
|
|
59
|
+
const result = {github, aws, clickup, cli}
|
|
64
60
|
|
|
65
61
|
if (isJson) return result
|
|
66
62
|
|