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
package/src/services/nvd.js
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import {loadConfig} from './config.js'
|
|
2
|
+
import {DvmiError} from '../utils/errors.js'
|
|
3
3
|
|
|
4
4
|
/** @import { CveSearchResult, CveDetail } from '../types.js' */
|
|
5
5
|
|
|
6
6
|
const NVD_BASE_URL = 'https://services.nvd.nist.gov/rest/json/cves/2.0'
|
|
7
7
|
|
|
8
8
|
/** NVD attribution required in all interactive output. */
|
|
9
|
-
export const NVD_ATTRIBUTION =
|
|
10
|
-
'This product uses data from the NVD API but is not endorsed or certified by the NVD.'
|
|
9
|
+
export const NVD_ATTRIBUTION = 'This product uses data from the NVD API but is not endorsed or certified by the NVD.'
|
|
11
10
|
|
|
12
11
|
/**
|
|
13
12
|
* Normalize a raw NVD severity string to the 4-tier canonical form.
|
|
@@ -31,11 +30,7 @@ export function normalizeSeverity(raw) {
|
|
|
31
30
|
* @returns {{ score: number|null, severity: string, vector: string|null }}
|
|
32
31
|
*/
|
|
33
32
|
function extractCvss(metrics) {
|
|
34
|
-
const sources = [
|
|
35
|
-
(metrics?.cvssMetricV31 ?? []),
|
|
36
|
-
(metrics?.cvssMetricV40 ?? []),
|
|
37
|
-
(metrics?.cvssMetricV2 ?? []),
|
|
38
|
-
]
|
|
33
|
+
const sources = [metrics?.cvssMetricV31 ?? [], metrics?.cvssMetricV40 ?? [], metrics?.cvssMetricV2 ?? []]
|
|
39
34
|
|
|
40
35
|
for (const list of sources) {
|
|
41
36
|
if (Array.isArray(list) && list.length > 0) {
|
|
@@ -50,7 +45,7 @@ function extractCvss(metrics) {
|
|
|
50
45
|
}
|
|
51
46
|
}
|
|
52
47
|
|
|
53
|
-
return {
|
|
48
|
+
return {score: null, severity: 'Unknown', vector: null}
|
|
54
49
|
}
|
|
55
50
|
|
|
56
51
|
/**
|
|
@@ -88,15 +83,12 @@ function buildParams(params) {
|
|
|
88
83
|
async function nvdFetch(params, apiKey) {
|
|
89
84
|
const url = `${NVD_BASE_URL}?${params.toString()}`
|
|
90
85
|
/** @type {Record<string, string>} */
|
|
91
|
-
const headers = {
|
|
86
|
+
const headers = {Accept: 'application/json'}
|
|
92
87
|
if (apiKey) headers['apiKey'] = apiKey
|
|
93
88
|
|
|
94
|
-
const res = await fetch(url, {
|
|
89
|
+
const res = await fetch(url, {headers})
|
|
95
90
|
if (!res.ok) {
|
|
96
|
-
throw new DvmiError(
|
|
97
|
-
`NVD API returned HTTP ${res.status}`,
|
|
98
|
-
'Check your network connection or try again later.',
|
|
99
|
-
)
|
|
91
|
+
throw new DvmiError(`NVD API returned HTTP ${res.status}`, 'Check your network connection or try again later.')
|
|
100
92
|
}
|
|
101
93
|
return res.json()
|
|
102
94
|
}
|
|
@@ -108,7 +100,7 @@ async function nvdFetch(params, apiKey) {
|
|
|
108
100
|
*/
|
|
109
101
|
function parseCveSearchResult(raw) {
|
|
110
102
|
const cve = raw.cve
|
|
111
|
-
const {
|
|
103
|
+
const {score, severity} = extractCvss(cve.metrics ?? {})
|
|
112
104
|
return {
|
|
113
105
|
id: cve.id,
|
|
114
106
|
description: getEnDescription(cve.descriptions),
|
|
@@ -127,7 +119,7 @@ function parseCveSearchResult(raw) {
|
|
|
127
119
|
*/
|
|
128
120
|
function parseCveDetail(raw) {
|
|
129
121
|
const cve = raw.cve
|
|
130
|
-
const {
|
|
122
|
+
const {score, severity, vector} = extractCvss(cve.metrics ?? {})
|
|
131
123
|
|
|
132
124
|
// Weaknesses: flatten all CWE descriptions
|
|
133
125
|
const weaknesses = (cve.weaknesses ?? []).flatMap((w) =>
|
|
@@ -149,10 +141,11 @@ function parseCveDetail(raw) {
|
|
|
149
141
|
const product = parts[4] ?? 'unknown'
|
|
150
142
|
const versionStart = m.versionStartIncluding ?? m.versionStartExcluding ?? ''
|
|
151
143
|
const versionEnd = m.versionEndExcluding ?? m.versionEndIncluding ?? ''
|
|
152
|
-
const versions =
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
144
|
+
const versions =
|
|
145
|
+
versionStart && versionEnd
|
|
146
|
+
? `${versionStart} to ${versionEnd}`
|
|
147
|
+
: versionStart || versionEnd || (parts[5] ?? '*')
|
|
148
|
+
return {vendor, product, versions}
|
|
156
149
|
}),
|
|
157
150
|
),
|
|
158
151
|
)
|
|
@@ -188,7 +181,7 @@ function parseCveDetail(raw) {
|
|
|
188
181
|
* @param {number} [options.limit=20] - Maximum results to return
|
|
189
182
|
* @returns {Promise<{ results: CveSearchResult[], totalResults: number }>}
|
|
190
183
|
*/
|
|
191
|
-
export async function searchCves({
|
|
184
|
+
export async function searchCves({keyword, days = 14, severity, limit = 20}) {
|
|
192
185
|
const config = await loadConfig()
|
|
193
186
|
const apiKey = config.nvd?.apiKey
|
|
194
187
|
|
|
@@ -202,17 +195,17 @@ export async function searchCves({ keyword, days = 14, severity, limit = 20 }) {
|
|
|
202
195
|
const trimmedKeyword = keyword?.trim()
|
|
203
196
|
|
|
204
197
|
const params = buildParams({
|
|
205
|
-
...(trimmedKeyword ? {
|
|
198
|
+
...(trimmedKeyword ? {keywordSearch: trimmedKeyword} : {}),
|
|
206
199
|
pubStartDate,
|
|
207
200
|
pubEndDate,
|
|
208
201
|
resultsPerPage: limit,
|
|
209
|
-
...(severity ? {
|
|
202
|
+
...(severity ? {cvssV3Severity: severity.toUpperCase()} : {}),
|
|
210
203
|
})
|
|
211
204
|
|
|
212
205
|
const data = /** @type {any} */ (await nvdFetch(params, apiKey))
|
|
213
206
|
|
|
214
207
|
const results = (data.vulnerabilities ?? []).map(parseCveSearchResult)
|
|
215
|
-
return {
|
|
208
|
+
return {results, totalResults: data.totalResults ?? results.length}
|
|
216
209
|
}
|
|
217
210
|
|
|
218
211
|
/**
|
|
@@ -231,14 +224,11 @@ export async function getCveDetail(cveId) {
|
|
|
231
224
|
const config = await loadConfig()
|
|
232
225
|
const apiKey = config.nvd?.apiKey
|
|
233
226
|
|
|
234
|
-
const params = buildParams({
|
|
227
|
+
const params = buildParams({cveId: cveId.toUpperCase()})
|
|
235
228
|
const data = /** @type {any} */ (await nvdFetch(params, apiKey))
|
|
236
229
|
|
|
237
230
|
if (!data.vulnerabilities || data.vulnerabilities.length === 0) {
|
|
238
|
-
throw new DvmiError(
|
|
239
|
-
`CVE not found: ${cveId}`,
|
|
240
|
-
'Verify the CVE ID is correct and exists in the NVD database.',
|
|
241
|
-
)
|
|
231
|
+
throw new DvmiError(`CVE not found: ${cveId}`, 'Verify the CVE ID is correct and exists in the NVD database.')
|
|
242
232
|
}
|
|
243
233
|
|
|
244
234
|
return parseCveDetail(data.vulnerabilities[0])
|
package/src/services/platform.js
CHANGED
package/src/services/prompts.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
1
|
+
import {mkdir, writeFile, readFile, access} from 'node:fs/promises'
|
|
2
|
+
import {join, dirname, resolve, sep} from 'node:path'
|
|
3
|
+
import {execa} from 'execa'
|
|
4
|
+
import {createOctokit} from './github.js'
|
|
5
|
+
import {which} from './shell.js'
|
|
6
|
+
import {parseFrontmatter, serializeFrontmatter} from '../utils/frontmatter.js'
|
|
7
|
+
import {DvmiError} from '../utils/errors.js'
|
|
8
8
|
|
|
9
9
|
/** @import { Prompt, AITool } from '../types.js' */
|
|
10
10
|
|
|
@@ -13,15 +13,15 @@ import { DvmiError } from '../utils/errors.js'
|
|
|
13
13
|
* @type {Record<AITool, { bin: string[], promptFlag: string }>}
|
|
14
14
|
*/
|
|
15
15
|
export const SUPPORTED_TOOLS = {
|
|
16
|
-
opencode: {
|
|
17
|
-
copilot: {
|
|
16
|
+
opencode: {bin: ['opencode'], promptFlag: '--prompt'},
|
|
17
|
+
copilot: {bin: ['gh', 'copilot'], promptFlag: '-p'},
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* GitHub repository containing the personal prompt collection.
|
|
22
22
|
* @type {{ owner: string, repo: string }}
|
|
23
23
|
*/
|
|
24
|
-
export const PROMPT_REPO = {
|
|
24
|
+
export const PROMPT_REPO = {owner: 'savez', repo: 'prompt-for-ai'}
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
27
|
* Default branch used when fetching the repository tree.
|
|
@@ -75,7 +75,7 @@ function categoryFromPath(filePath) {
|
|
|
75
75
|
*/
|
|
76
76
|
function contentToPrompt(path, base64Content) {
|
|
77
77
|
const raw = Buffer.from(base64Content, 'base64').toString('utf8')
|
|
78
|
-
const {
|
|
78
|
+
const {frontmatter, body} = parseFrontmatter(raw)
|
|
79
79
|
return {
|
|
80
80
|
path,
|
|
81
81
|
title: typeof frontmatter.title === 'string' ? frontmatter.title : titleFromPath(path),
|
|
@@ -101,7 +101,7 @@ export async function listPrompts() {
|
|
|
101
101
|
const octokit = await createOctokit()
|
|
102
102
|
let tree
|
|
103
103
|
try {
|
|
104
|
-
const {
|
|
104
|
+
const {data} = await octokit.rest.git.getTree({
|
|
105
105
|
owner: PROMPT_REPO.owner,
|
|
106
106
|
repo: PROMPT_REPO.repo,
|
|
107
107
|
tree_sha: DEFAULT_BRANCH,
|
|
@@ -132,7 +132,7 @@ export async function listPrompts() {
|
|
|
132
132
|
|
|
133
133
|
const prompts = await Promise.all(
|
|
134
134
|
mdFiles.map(async (item) => {
|
|
135
|
-
const {
|
|
135
|
+
const {data} = await octokit.rest.repos.getContent({
|
|
136
136
|
owner: PROMPT_REPO.owner,
|
|
137
137
|
repo: PROMPT_REPO.repo,
|
|
138
138
|
path: item.path ?? '',
|
|
@@ -169,10 +169,7 @@ export async function fetchPromptByPath(relativePath) {
|
|
|
169
169
|
} catch (err) {
|
|
170
170
|
const status = /** @type {{ status?: number }} */ (err).status
|
|
171
171
|
if (status === 404) {
|
|
172
|
-
throw new DvmiError(
|
|
173
|
-
`Prompt not found: ${relativePath}`,
|
|
174
|
-
`Run \`dvmi prompts list\` to see available prompts`,
|
|
175
|
-
)
|
|
172
|
+
throw new DvmiError(`Prompt not found: ${relativePath}`, `Run \`dvmi prompts list\` to see available prompts`)
|
|
176
173
|
}
|
|
177
174
|
throw err
|
|
178
175
|
}
|
|
@@ -207,17 +204,14 @@ export async function downloadPrompt(relativePath, localDir, opts = {}) {
|
|
|
207
204
|
// Prevent path traversal: destPath must remain within localDir
|
|
208
205
|
const safeBase = resolve(localDir) + sep
|
|
209
206
|
if (!resolve(destPath).startsWith(safeBase)) {
|
|
210
|
-
throw new DvmiError(
|
|
211
|
-
`Invalid prompt path: "${relativePath}"`,
|
|
212
|
-
'Path must stay within the prompts directory',
|
|
213
|
-
)
|
|
207
|
+
throw new DvmiError(`Invalid prompt path: "${relativePath}"`, 'Path must stay within the prompts directory')
|
|
214
208
|
}
|
|
215
209
|
|
|
216
210
|
// Fast-path: skip without a network round-trip if file exists and no overwrite
|
|
217
211
|
if (!opts.overwrite) {
|
|
218
212
|
try {
|
|
219
213
|
await access(destPath)
|
|
220
|
-
return {
|
|
214
|
+
return {path: destPath, skipped: true}
|
|
221
215
|
} catch {
|
|
222
216
|
// File does not exist — fall through to download
|
|
223
217
|
}
|
|
@@ -237,10 +231,10 @@ export async function downloadPrompt(relativePath, localDir, opts = {}) {
|
|
|
237
231
|
|
|
238
232
|
const content = serializeFrontmatter(fm, prompt.body)
|
|
239
233
|
|
|
240
|
-
await mkdir(dirname(destPath), {
|
|
241
|
-
await writeFile(destPath, content, {
|
|
234
|
+
await mkdir(dirname(destPath), {recursive: true, mode: 0o700})
|
|
235
|
+
await writeFile(destPath, content, {encoding: 'utf8', mode: 0o600})
|
|
242
236
|
|
|
243
|
-
return {
|
|
237
|
+
return {path: destPath, skipped: false}
|
|
244
238
|
}
|
|
245
239
|
|
|
246
240
|
/**
|
|
@@ -258,10 +252,7 @@ export async function resolveLocalPrompt(relativePath, localDir) {
|
|
|
258
252
|
// Prevent path traversal: fullPath must remain within localDir
|
|
259
253
|
const safeBase = resolve(localDir) + sep
|
|
260
254
|
if (!resolve(fullPath).startsWith(safeBase)) {
|
|
261
|
-
throw new DvmiError(
|
|
262
|
-
`Invalid prompt path: "${relativePath}"`,
|
|
263
|
-
'Path must stay within the prompts directory',
|
|
264
|
-
)
|
|
255
|
+
throw new DvmiError(`Invalid prompt path: "${relativePath}"`, 'Path must stay within the prompts directory')
|
|
265
256
|
}
|
|
266
257
|
|
|
267
258
|
let raw
|
|
@@ -274,7 +265,7 @@ export async function resolveLocalPrompt(relativePath, localDir) {
|
|
|
274
265
|
)
|
|
275
266
|
}
|
|
276
267
|
|
|
277
|
-
const {
|
|
268
|
+
const {frontmatter, body} = parseFrontmatter(raw)
|
|
278
269
|
return {
|
|
279
270
|
path: relativePath,
|
|
280
271
|
title: typeof frontmatter.title === 'string' ? frontmatter.title : titleFromPath(relativePath),
|
|
@@ -301,10 +292,7 @@ export async function resolveLocalPrompt(relativePath, localDir) {
|
|
|
301
292
|
export async function invokeTool(toolName, promptContent) {
|
|
302
293
|
const tool = SUPPORTED_TOOLS[toolName]
|
|
303
294
|
if (!tool) {
|
|
304
|
-
throw new DvmiError(
|
|
305
|
-
`Unknown AI tool: "${toolName}"`,
|
|
306
|
-
`Supported tools: ${Object.keys(SUPPORTED_TOOLS).join(', ')}`,
|
|
307
|
-
)
|
|
295
|
+
throw new DvmiError(`Unknown AI tool: "${toolName}"`, `Supported tools: ${Object.keys(SUPPORTED_TOOLS).join(', ')}`)
|
|
308
296
|
}
|
|
309
297
|
|
|
310
298
|
// Verify binary availability
|
|
@@ -322,5 +310,5 @@ export async function invokeTool(toolName, promptContent) {
|
|
|
322
310
|
}
|
|
323
311
|
|
|
324
312
|
// Spawn tool with prompt content — inherits stdio so TUI/interactive tools work
|
|
325
|
-
await execa(bin, [...subArgs, tool.promptFlag, promptContent], {
|
|
313
|
+
await execa(bin, [...subArgs, tool.promptFlag, promptContent], {stdio: 'inherit'})
|
|
326
314
|
}
|