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.
- package/README.md +7 -0
- package/oclif.manifest.json +41 -1
- 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 +143 -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 +95 -21
- 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 +127 -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 +318 -0
- package/src/services/ai-env-deployer.js +444 -0
- package/src/services/ai-env-scanner.js +242 -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 +85 -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 +1006 -0
- package/src/utils/tui/modal.js +15 -14
- package/src/utils/tui/navigable-table.js +25 -17
- package/src/utils/tui/tab-tui.js +800 -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} 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,10 +1,29 @@
|
|
|
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 {
|
|
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 {
|
|
58
|
+
const {flags} = await this.parse(VulnScan)
|
|
40
59
|
const isJson = flags.json
|
|
41
|
-
const {
|
|
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: {
|
|
57
|
-
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.'}],
|
|
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 {
|
|
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({
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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 {
|
|
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
|
|