devvami 1.4.2 → 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 +129 -89
- 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 +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 +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 +16 -16
- 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, Flags} from '@oclif/core'
|
|
2
2
|
import chalk from 'chalk'
|
|
3
3
|
import ora from 'ora'
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import {listWorkflowRuns} from '../../services/github.js'
|
|
5
|
+
import {exec} from '../../services/shell.js'
|
|
6
|
+
import {renderTable, colorStatus} from '../../formatters/table.js'
|
|
7
7
|
|
|
8
8
|
export default class PipelineStatus extends Command {
|
|
9
9
|
static description = 'Stato GitHub Actions per il repo corrente'
|
|
@@ -17,46 +17,50 @@ export default class PipelineStatus extends Command {
|
|
|
17
17
|
static enableJsonFlag = true
|
|
18
18
|
|
|
19
19
|
static flags = {
|
|
20
|
-
branch: Flags.string({
|
|
21
|
-
limit: Flags.integer({
|
|
20
|
+
branch: Flags.string({description: 'Filtra per branch'}),
|
|
21
|
+
limit: Flags.integer({description: 'Numero di run da mostrare', default: 10}),
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
async run() {
|
|
25
|
-
const {
|
|
25
|
+
const {flags} = await this.parse(PipelineStatus)
|
|
26
26
|
const isJson = flags.json
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
// Detect repo from git remote
|
|
29
|
+
const remoteResult = await exec('git', ['remote', 'get-url', 'origin'])
|
|
30
|
+
if (remoteResult.exitCode !== 0) {
|
|
31
|
+
this.error('Not in a Git repository. Navigate to a repo or use `dvmi repo list`')
|
|
32
|
+
}
|
|
33
33
|
const match = remoteResult.stdout.match(/github\.com[:/]([^/]+)\/([^/.]+)/)
|
|
34
34
|
if (!match) this.error('Could not detect GitHub repository.')
|
|
35
35
|
const [, owner, repo] = match
|
|
36
36
|
|
|
37
|
-
const spinner = isJson
|
|
37
|
+
const spinner = isJson
|
|
38
|
+
? null
|
|
39
|
+
: ora({spinner: 'arc', color: false, text: chalk.hex('#FF6B2B')('Fetching pipeline runs...')}).start()
|
|
38
40
|
const runs = await listWorkflowRuns(owner, repo, {
|
|
39
41
|
branch: flags.branch,
|
|
40
42
|
limit: flags.limit,
|
|
41
43
|
})
|
|
42
44
|
spinner?.stop()
|
|
43
45
|
|
|
44
|
-
if (isJson) return {
|
|
46
|
+
if (isJson) return {runs}
|
|
45
47
|
|
|
46
48
|
if (runs.length === 0) {
|
|
47
49
|
this.log(chalk.dim('No workflow runs found.'))
|
|
48
|
-
return {
|
|
50
|
+
return {runs: []}
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
this.log(chalk.bold('\nGitHub Actions runs:\n'))
|
|
52
|
-
this.log(
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
54
|
+
this.log(
|
|
55
|
+
renderTable(runs, [
|
|
56
|
+
{header: 'Status', key: 'conclusion', width: 10, format: (v) => colorStatus(v ? String(v) : 'pending')},
|
|
57
|
+
{header: 'Workflow', key: 'name', width: 25},
|
|
58
|
+
{header: 'Branch', key: 'branch', width: 20},
|
|
59
|
+
{header: 'Duration', key: 'duration', width: 10, format: (v) => `${v}s`},
|
|
60
|
+
{header: 'Actor', key: 'actor', width: 15},
|
|
61
|
+
]),
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
return {runs}
|
|
61
65
|
}
|
|
62
66
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
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 {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
4
|
+
import {confirm, input} from '@inquirer/prompts'
|
|
5
|
+
import {createPR} from '../../services/github.js'
|
|
6
|
+
import {exec} from '../../services/shell.js'
|
|
7
|
+
import {readFile} from 'node:fs/promises'
|
|
8
|
+
import {existsSync} from 'node:fs'
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* @param {string} branchName
|
|
@@ -14,7 +14,7 @@ import { existsSync } from 'node:fs'
|
|
|
14
14
|
function titleFromBranch(branchName) {
|
|
15
15
|
const [type, ...rest] = branchName.split('/')
|
|
16
16
|
const desc = rest.join('/').replace(/-/g, ' ')
|
|
17
|
-
const typeMap = {
|
|
17
|
+
const typeMap = {feature: 'Feature', fix: 'Fix', chore: 'Chore', hotfix: 'Hotfix'}
|
|
18
18
|
return `${typeMap[type] ?? type}: ${desc}`
|
|
19
19
|
}
|
|
20
20
|
|
|
@@ -23,7 +23,7 @@ function titleFromBranch(branchName) {
|
|
|
23
23
|
* @returns {string[]}
|
|
24
24
|
*/
|
|
25
25
|
function labelFromType(branchType) {
|
|
26
|
-
const map = {
|
|
26
|
+
const map = {feature: ['feature'], fix: ['bug'], chore: ['chore'], hotfix: ['critical']}
|
|
27
27
|
return map[branchType] ?? []
|
|
28
28
|
}
|
|
29
29
|
|
|
@@ -39,13 +39,13 @@ export default class PRCreate extends Command {
|
|
|
39
39
|
static enableJsonFlag = true
|
|
40
40
|
|
|
41
41
|
static flags = {
|
|
42
|
-
title: Flags.string({
|
|
43
|
-
draft: Flags.boolean({
|
|
44
|
-
'dry-run': Flags.boolean({
|
|
42
|
+
title: Flags.string({description: 'Titolo PR (default: auto-generated)'}),
|
|
43
|
+
draft: Flags.boolean({description: 'Crea come draft', default: false}),
|
|
44
|
+
'dry-run': Flags.boolean({description: 'Preview senza eseguire', default: false}),
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
async run() {
|
|
48
|
-
const {
|
|
48
|
+
const {flags} = await this.parse(PRCreate)
|
|
49
49
|
const isJson = flags.json
|
|
50
50
|
const isDryRun = flags['dry-run']
|
|
51
51
|
// Get current branch
|
|
@@ -53,9 +53,9 @@ export default class PRCreate extends Command {
|
|
|
53
53
|
if (branchResult.exitCode !== 0) this.error('Not in a Git repository.')
|
|
54
54
|
const branch = branchResult.stdout
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
if (['main', 'master', 'develop'].includes(branch)) {
|
|
57
|
+
this.error(`You're on the default branch "${branch}". Create a feature branch first with \`dvmi branch create\``)
|
|
58
|
+
}
|
|
59
59
|
|
|
60
60
|
// Check for commits
|
|
61
61
|
const repoUrl = await exec('git', ['remote', 'get-url', 'origin'])
|
|
@@ -64,7 +64,9 @@ export default class PRCreate extends Command {
|
|
|
64
64
|
const [, owner, repo] = repoMatch
|
|
65
65
|
|
|
66
66
|
// Push branch if needed
|
|
67
|
-
const pushSpinner = isJson
|
|
67
|
+
const pushSpinner = isJson
|
|
68
|
+
? null
|
|
69
|
+
: ora({spinner: 'arc', color: false, text: chalk.hex('#FF6B2B')('Pushing branch...')}).start()
|
|
68
70
|
await exec('git', ['push', '-u', 'origin', branch])
|
|
69
71
|
pushSpinner?.stop()
|
|
70
72
|
|
|
@@ -80,32 +82,43 @@ export default class PRCreate extends Command {
|
|
|
80
82
|
|
|
81
83
|
// Generate title
|
|
82
84
|
const autoTitle = titleFromBranch(branch)
|
|
83
|
-
const title = flags.title ?? (isJson ? autoTitle : await input({
|
|
85
|
+
const title = flags.title ?? (isJson ? autoTitle : await input({message: 'PR title:', default: autoTitle}))
|
|
84
86
|
const branchType = branch.split('/')[0]
|
|
85
87
|
const labels = labelFromType(branchType)
|
|
86
88
|
|
|
87
|
-
const preview = {
|
|
89
|
+
const preview = {branch, base: 'main', title, labels, draft: flags.draft}
|
|
88
90
|
if (isDryRun) {
|
|
89
|
-
if (isJson) return {
|
|
91
|
+
if (isJson) return {pr: preview}
|
|
90
92
|
this.log(chalk.bold('Dry run — would create PR:'))
|
|
91
93
|
this.log(JSON.stringify(preview, null, 2))
|
|
92
|
-
return {
|
|
94
|
+
return {pr: preview}
|
|
93
95
|
}
|
|
94
96
|
|
|
95
97
|
if (!isJson) {
|
|
96
|
-
const ok = await confirm({
|
|
97
|
-
if (!ok) {
|
|
98
|
+
const ok = await confirm({message: `Create PR "${title}"?`})
|
|
99
|
+
if (!ok) {
|
|
100
|
+
this.log('Aborted.')
|
|
101
|
+
return
|
|
102
|
+
}
|
|
98
103
|
}
|
|
99
104
|
|
|
100
|
-
const spinner = isJson
|
|
105
|
+
const spinner = isJson
|
|
106
|
+
? null
|
|
107
|
+
: ora({spinner: 'arc', color: false, text: chalk.hex('#FF6B2B')('Creating PR...')}).start()
|
|
101
108
|
const pr = await createPR({
|
|
102
|
-
owner,
|
|
103
|
-
|
|
104
|
-
|
|
109
|
+
owner,
|
|
110
|
+
repo,
|
|
111
|
+
title,
|
|
112
|
+
body,
|
|
113
|
+
head: branch,
|
|
114
|
+
base: 'main',
|
|
115
|
+
draft: flags.draft,
|
|
116
|
+
labels,
|
|
117
|
+
reviewers: [],
|
|
105
118
|
})
|
|
106
119
|
spinner?.succeed(`PR created: ${pr.htmlUrl}`)
|
|
107
120
|
|
|
108
|
-
const result = {
|
|
121
|
+
const result = {pr: {number: pr.number, title, url: pr.htmlUrl, labels, draft: flags.draft}}
|
|
109
122
|
|
|
110
123
|
if (isJson) return result
|
|
111
124
|
this.log(chalk.green('✓') + ' ' + pr.htmlUrl)
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {Command, Args, Flags} from '@oclif/core'
|
|
2
2
|
import chalk from 'chalk'
|
|
3
3
|
import ora from 'ora'
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import {getPRDetail} from '../../services/github.js'
|
|
5
|
+
import {exec} from '../../services/shell.js'
|
|
6
6
|
|
|
7
7
|
export default class PRDetail extends Command {
|
|
8
8
|
static description = 'Dettaglio PR con commenti QA e checklist degli step'
|
|
@@ -16,15 +16,15 @@ export default class PRDetail extends Command {
|
|
|
16
16
|
static enableJsonFlag = true
|
|
17
17
|
|
|
18
18
|
static args = {
|
|
19
|
-
number: Args.integer({
|
|
19
|
+
number: Args.integer({description: 'Numero della PR', required: true}),
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
static flags = {
|
|
23
|
-
repo: Flags.string({
|
|
23
|
+
repo: Flags.string({description: 'Repository nel formato owner/repo (default: rilevato da git remote)'}),
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
async run() {
|
|
27
|
-
const {
|
|
27
|
+
const {args, flags} = await this.parse(PRDetail)
|
|
28
28
|
const isJson = flags.json
|
|
29
29
|
|
|
30
30
|
let owner, repo
|
|
@@ -39,7 +39,9 @@ export default class PRDetail extends Command {
|
|
|
39
39
|
;[, owner, repo] = match
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
const spinner = isJson
|
|
42
|
+
const spinner = isJson
|
|
43
|
+
? null
|
|
44
|
+
: ora({spinner: 'arc', color: false, text: chalk.hex('#FF6B2B')('Caricamento PR...')}).start()
|
|
43
45
|
const detail = await getPRDetail(owner, repo, args.number)
|
|
44
46
|
spinner?.stop()
|
|
45
47
|
|
|
@@ -1,22 +1,19 @@
|
|
|
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 {listMyPRs} from '../../services/github.js'
|
|
5
|
+
import {loadConfig} from '../../services/config.js'
|
|
6
|
+
import {renderTable, colorStatus} from '../../formatters/table.js'
|
|
7
7
|
|
|
8
8
|
export default class PRReview extends Command {
|
|
9
9
|
static description = 'Lista PR assegnate a te per la code review'
|
|
10
10
|
|
|
11
|
-
static examples = [
|
|
12
|
-
'<%= config.bin %> pr review',
|
|
13
|
-
'<%= config.bin %> pr review --json',
|
|
14
|
-
]
|
|
11
|
+
static examples = ['<%= config.bin %> pr review', '<%= config.bin %> pr review --json']
|
|
15
12
|
|
|
16
13
|
static enableJsonFlag = true
|
|
17
14
|
|
|
18
15
|
async run() {
|
|
19
|
-
const {
|
|
16
|
+
const {flags} = await this.parse(PRReview)
|
|
20
17
|
const isJson = flags.json
|
|
21
18
|
const config = await loadConfig()
|
|
22
19
|
|
|
@@ -24,28 +21,30 @@ export default class PRReview extends Command {
|
|
|
24
21
|
this.error("GitHub org non configurata. Esegui `dvmi init` per configurare l'ambiente.")
|
|
25
22
|
}
|
|
26
23
|
|
|
27
|
-
const spinner = isJson
|
|
28
|
-
|
|
24
|
+
const spinner = isJson
|
|
25
|
+
? null
|
|
26
|
+
: ora({spinner: 'arc', color: false, text: chalk.hex('#FF6B2B')('Caricamento PR in review...')}).start()
|
|
27
|
+
const {reviewing} = await listMyPRs(config.org)
|
|
29
28
|
spinner?.stop()
|
|
30
29
|
|
|
31
|
-
if (isJson) return {
|
|
30
|
+
if (isJson) return {reviewing}
|
|
32
31
|
|
|
33
32
|
if (reviewing.length === 0) {
|
|
34
33
|
this.log(chalk.dim('Nessuna PR assegnata per review.'))
|
|
35
|
-
return {
|
|
34
|
+
return {reviewing}
|
|
36
35
|
}
|
|
37
36
|
|
|
38
37
|
this.log(chalk.bold(`\nPR ASSEGNATE PER REVIEW (${reviewing.length}):`))
|
|
39
38
|
this.log(
|
|
40
39
|
renderTable(reviewing, [
|
|
41
|
-
{
|
|
42
|
-
{
|
|
43
|
-
{
|
|
44
|
-
{
|
|
45
|
-
{
|
|
40
|
+
{header: '#', key: 'number', width: 6},
|
|
41
|
+
{header: 'Titolo', key: 'title', width: 45},
|
|
42
|
+
{header: 'Autore', key: 'author', width: 20},
|
|
43
|
+
{header: 'Branch', key: 'headBranch', width: 30},
|
|
44
|
+
{header: 'CI', key: 'ciStatus', width: 10, format: (v) => colorStatus(String(v))},
|
|
46
45
|
]),
|
|
47
46
|
)
|
|
48
47
|
|
|
49
|
-
return {
|
|
48
|
+
return {reviewing}
|
|
50
49
|
}
|
|
51
50
|
}
|
|
@@ -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 {listMyPRs} from '../../services/github.js'
|
|
5
|
+
import {loadConfig} from '../../services/config.js'
|
|
6
|
+
import {renderTable, colorStatus} from '../../formatters/table.js'
|
|
7
7
|
|
|
8
8
|
export default class PRStatus extends Command {
|
|
9
9
|
static description = 'Stato delle tue PR aperte (come autore e come reviewer)'
|
|
@@ -17,12 +17,12 @@ export default class PRStatus extends Command {
|
|
|
17
17
|
static enableJsonFlag = true
|
|
18
18
|
|
|
19
19
|
static flags = {
|
|
20
|
-
author: Flags.boolean({
|
|
21
|
-
reviewer: Flags.boolean({
|
|
20
|
+
author: Flags.boolean({description: 'Solo PR dove sei autore', default: false}),
|
|
21
|
+
reviewer: Flags.boolean({description: 'Solo PR dove sei reviewer', default: false}),
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
async run() {
|
|
25
|
-
const {
|
|
25
|
+
const {flags} = await this.parse(PRStatus)
|
|
26
26
|
const isJson = flags.json
|
|
27
27
|
const config = await loadConfig()
|
|
28
28
|
|
|
@@ -30,8 +30,10 @@ export default class PRStatus extends Command {
|
|
|
30
30
|
this.error('GitHub org not configured. Run `dvmi init` to set up your environment.')
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
const spinner = isJson
|
|
34
|
-
|
|
33
|
+
const spinner = isJson
|
|
34
|
+
? null
|
|
35
|
+
: ora({spinner: 'arc', color: false, text: chalk.hex('#FF6B2B')('Fetching PRs...')}).start()
|
|
36
|
+
const {authored, reviewing} = await listMyPRs(config.org)
|
|
35
37
|
spinner?.stop()
|
|
36
38
|
|
|
37
39
|
const showAuthored = !flags.reviewer || flags.author
|
|
@@ -46,25 +48,29 @@ export default class PRStatus extends Command {
|
|
|
46
48
|
|
|
47
49
|
if (showAuthored && authored.length > 0) {
|
|
48
50
|
this.log(chalk.bold('\nYOUR PRS:'))
|
|
49
|
-
this.log(
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
this.log(
|
|
52
|
+
renderTable(authored, [
|
|
53
|
+
{header: 'Repo', key: 'headBranch', width: 30, format: (v) => String(v).split('/')[0]},
|
|
54
|
+
{header: 'Title', key: 'title', width: 40},
|
|
55
|
+
{header: 'CI', key: 'ciStatus', width: 10, format: (v) => colorStatus(String(v))},
|
|
56
|
+
{header: 'Review', key: 'reviewStatus', width: 20, format: (v) => colorStatus(String(v))},
|
|
57
|
+
]),
|
|
58
|
+
)
|
|
55
59
|
} else if (showAuthored) {
|
|
56
60
|
this.log(chalk.dim('No authored PRs found.'))
|
|
57
61
|
}
|
|
58
62
|
|
|
59
63
|
if (showReviewing && reviewing.length > 0) {
|
|
60
64
|
this.log(chalk.bold('\nREVIEW REQUESTED:'))
|
|
61
|
-
this.log(
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
65
|
+
this.log(
|
|
66
|
+
renderTable(reviewing, [
|
|
67
|
+
{header: 'Title', key: 'title', width: 40},
|
|
68
|
+
{header: 'Author', key: 'author', width: 20},
|
|
69
|
+
{header: 'CI', key: 'ciStatus', width: 10, format: (v) => colorStatus(String(v))},
|
|
70
|
+
]),
|
|
71
|
+
)
|
|
66
72
|
}
|
|
67
73
|
|
|
68
|
-
return {
|
|
74
|
+
return {authored, reviewing}
|
|
69
75
|
}
|
|
70
76
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
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 {
|
|
8
|
-
import {
|
|
4
|
+
import {select} from '@inquirer/prompts'
|
|
5
|
+
import {searchSkills} from '../../services/skills-sh.js'
|
|
6
|
+
import {fetchAwesomeEntries, AWESOME_CATEGORIES} from '../../services/awesome-copilot.js'
|
|
7
|
+
import {formatSkillTable, formatAwesomeTable} from '../../formatters/prompts.js'
|
|
8
|
+
import {DvmiError} from '../../utils/errors.js'
|
|
9
9
|
|
|
10
10
|
/** @import { Skill, AwesomeEntry } from '../../types.js' */
|
|
11
11
|
|
|
@@ -45,7 +45,7 @@ export default class PromptsBrowse extends Command {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
async run() {
|
|
48
|
-
const {
|
|
48
|
+
const {args, flags} = await this.parse(PromptsBrowse)
|
|
49
49
|
const isJson = flags.json
|
|
50
50
|
const source = args.source
|
|
51
51
|
|
|
@@ -81,7 +81,7 @@ export default class PromptsBrowse extends Command {
|
|
|
81
81
|
} catch (err) {
|
|
82
82
|
spinner?.fail()
|
|
83
83
|
if (err instanceof DvmiError) {
|
|
84
|
-
this.error(err.message, {
|
|
84
|
+
this.error(err.message, {exit: err.exitCode, suggestions: [err.hint]})
|
|
85
85
|
}
|
|
86
86
|
throw err
|
|
87
87
|
}
|
|
@@ -89,7 +89,7 @@ export default class PromptsBrowse extends Command {
|
|
|
89
89
|
spinner?.stop()
|
|
90
90
|
|
|
91
91
|
if (isJson) {
|
|
92
|
-
return {
|
|
92
|
+
return {skills, total: skills.length}
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
this.log(
|
|
@@ -99,7 +99,7 @@ export default class PromptsBrowse extends Command {
|
|
|
99
99
|
)
|
|
100
100
|
this.log(formatSkillTable(skills))
|
|
101
101
|
|
|
102
|
-
return {
|
|
102
|
+
return {skills, total: skills.length}
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
// source === 'awesome'
|
|
@@ -118,7 +118,7 @@ export default class PromptsBrowse extends Command {
|
|
|
118
118
|
} catch (err) {
|
|
119
119
|
spinner?.fail()
|
|
120
120
|
if (err instanceof DvmiError) {
|
|
121
|
-
this.error(err.message, {
|
|
121
|
+
this.error(err.message, {exit: err.exitCode, suggestions: [err.hint]})
|
|
122
122
|
}
|
|
123
123
|
throw err
|
|
124
124
|
}
|
|
@@ -126,7 +126,7 @@ export default class PromptsBrowse extends Command {
|
|
|
126
126
|
spinner?.stop()
|
|
127
127
|
|
|
128
128
|
if (isJson) {
|
|
129
|
-
return {
|
|
129
|
+
return {entries, total: entries.length, category}
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
this.log(
|
|
@@ -140,8 +140,8 @@ export default class PromptsBrowse extends Command {
|
|
|
140
140
|
|
|
141
141
|
if (entries.length > 0) {
|
|
142
142
|
try {
|
|
143
|
-
const choices = entries.map((e) => ({
|
|
144
|
-
choices.push({
|
|
143
|
+
const choices = entries.map((e) => ({name: `${e.name} ${chalk.dim(e.url)}`, value: e}))
|
|
144
|
+
choices.push({name: chalk.dim('← Exit'), value: /** @type {AwesomeEntry} */ (null)})
|
|
145
145
|
|
|
146
146
|
const selected = await select({
|
|
147
147
|
message: 'Select an entry to view its URL (or Exit):',
|
|
@@ -159,6 +159,6 @@ export default class PromptsBrowse extends Command {
|
|
|
159
159
|
}
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
-
return {
|
|
162
|
+
return {entries, total: entries.length, category}
|
|
163
163
|
}
|
|
164
164
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
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 {
|
|
8
|
-
import {
|
|
4
|
+
import {select, confirm} from '@inquirer/prompts'
|
|
5
|
+
import {join} from 'node:path'
|
|
6
|
+
import {listPrompts, downloadPrompt} from '../../services/prompts.js'
|
|
7
|
+
import {loadConfig} from '../../services/config.js'
|
|
8
|
+
import {DvmiError} from '../../utils/errors.js'
|
|
9
9
|
|
|
10
10
|
/** @import { Prompt } from '../../types.js' */
|
|
11
11
|
|
|
@@ -38,7 +38,7 @@ export default class PromptsDownload extends Command {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
async run() {
|
|
41
|
-
const {
|
|
41
|
+
const {args, flags} = await this.parse(PromptsDownload)
|
|
42
42
|
const isJson = flags.json
|
|
43
43
|
|
|
44
44
|
// Determine local prompts directory from config or default to cwd/.prompts
|
|
@@ -48,8 +48,7 @@ export default class PromptsDownload extends Command {
|
|
|
48
48
|
} catch {
|
|
49
49
|
/* use defaults */
|
|
50
50
|
}
|
|
51
|
-
const localDir =
|
|
52
|
-
process.env.DVMI_PROMPTS_DIR ?? config.promptsDir ?? join(process.cwd(), DEFAULT_PROMPTS_DIR)
|
|
51
|
+
const localDir = process.env.DVMI_PROMPTS_DIR ?? config.promptsDir ?? join(process.cwd(), DEFAULT_PROMPTS_DIR)
|
|
53
52
|
|
|
54
53
|
// Resolve path interactively if not provided (only in interactive mode)
|
|
55
54
|
let relativePath = args.path
|
|
@@ -74,7 +73,7 @@ export default class PromptsDownload extends Command {
|
|
|
74
73
|
} catch (err) {
|
|
75
74
|
spinner.fail()
|
|
76
75
|
if (err instanceof DvmiError) {
|
|
77
|
-
this.error(err.message, {
|
|
76
|
+
this.error(err.message, {exit: err.exitCode, suggestions: [err.hint]})
|
|
78
77
|
}
|
|
79
78
|
throw err
|
|
80
79
|
}
|
|
@@ -82,14 +81,14 @@ export default class PromptsDownload extends Command {
|
|
|
82
81
|
|
|
83
82
|
if (prompts.length === 0) {
|
|
84
83
|
this.log(chalk.yellow('No prompts found in the repository.'))
|
|
85
|
-
return {
|
|
84
|
+
return {downloaded: [], skipped: []}
|
|
86
85
|
}
|
|
87
86
|
|
|
88
87
|
const choices = prompts.map((p) => ({
|
|
89
88
|
name: `${p.path} ${chalk.dim(p.title)}`,
|
|
90
89
|
value: p.path,
|
|
91
90
|
}))
|
|
92
|
-
relativePath = await select({
|
|
91
|
+
relativePath = await select({message: 'Select a prompt to download:', choices})
|
|
93
92
|
}
|
|
94
93
|
|
|
95
94
|
// Attempt download (skips automatically if file exists and --overwrite not set)
|
|
@@ -103,11 +102,11 @@ export default class PromptsDownload extends Command {
|
|
|
103
102
|
|
|
104
103
|
let result
|
|
105
104
|
try {
|
|
106
|
-
result = await downloadPrompt(relativePath, localDir, {
|
|
105
|
+
result = await downloadPrompt(relativePath, localDir, {overwrite: flags.overwrite})
|
|
107
106
|
} catch (err) {
|
|
108
107
|
spinner?.fail()
|
|
109
108
|
if (err instanceof DvmiError) {
|
|
110
|
-
this.error(err.message, {
|
|
109
|
+
this.error(err.message, {exit: err.exitCode, suggestions: [err.hint]})
|
|
111
110
|
}
|
|
112
111
|
throw err
|
|
113
112
|
}
|
|
@@ -122,10 +121,10 @@ export default class PromptsDownload extends Command {
|
|
|
122
121
|
})
|
|
123
122
|
if (shouldOverwrite) {
|
|
124
123
|
try {
|
|
125
|
-
result = await downloadPrompt(relativePath, localDir, {
|
|
124
|
+
result = await downloadPrompt(relativePath, localDir, {overwrite: true})
|
|
126
125
|
} catch (err) {
|
|
127
126
|
if (err instanceof DvmiError) {
|
|
128
|
-
this.error(err.message, {
|
|
127
|
+
this.error(err.message, {exit: err.exitCode, suggestions: [err.hint]})
|
|
129
128
|
}
|
|
130
129
|
throw err
|
|
131
130
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {Command, Flags} from '@oclif/core'
|
|
2
2
|
import ora from 'ora'
|
|
3
3
|
import chalk from 'chalk'
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import {loadConfig} from '../../services/config.js'
|
|
5
|
+
import {isUvInstalled, isSpecifyInstalled, installSpecifyCli, runSpecifyInit} from '../../services/speckit.js'
|
|
6
|
+
import {DvmiError} from '../../utils/errors.js'
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Map from dvmi's `aiTool` config values to spec-kit's `--ai` flag values.
|
|
@@ -26,8 +26,7 @@ export default class PromptsInstallSpeckit extends Command {
|
|
|
26
26
|
|
|
27
27
|
static flags = {
|
|
28
28
|
ai: Flags.string({
|
|
29
|
-
description:
|
|
30
|
-
'AI agent to pass to `specify init --ai` (defaults to the aiTool set in `dvmi init`)',
|
|
29
|
+
description: 'AI agent to pass to `specify init --ai` (defaults to the aiTool set in `dvmi init`)',
|
|
31
30
|
options: ['opencode', 'copilot', 'claude', 'gemini', 'cursor-agent', 'codex', 'windsurf', 'kiro-cli', 'amp'],
|
|
32
31
|
}),
|
|
33
32
|
force: Flags.boolean({
|
|
@@ -42,7 +41,7 @@ export default class PromptsInstallSpeckit extends Command {
|
|
|
42
41
|
}
|
|
43
42
|
|
|
44
43
|
async run() {
|
|
45
|
-
const {
|
|
44
|
+
const {flags} = await this.parse(PromptsInstallSpeckit)
|
|
46
45
|
|
|
47
46
|
// ── 1. Require uv ────────────────────────────────────────────────────────
|
|
48
47
|
if (!(await isUvInstalled())) {
|
|
@@ -57,15 +56,15 @@ export default class PromptsInstallSpeckit extends Command {
|
|
|
57
56
|
|
|
58
57
|
if (!alreadyInstalled || flags.reinstall) {
|
|
59
58
|
const label = alreadyInstalled ? 'Reinstalling specify-cli...' : 'Installing specify-cli...'
|
|
60
|
-
const spinner = ora({
|
|
59
|
+
const spinner = ora({spinner: 'arc', color: false, text: chalk.hex('#FF6B2B')(label)}).start()
|
|
61
60
|
|
|
62
61
|
try {
|
|
63
|
-
await installSpecifyCli({
|
|
62
|
+
await installSpecifyCli({force: flags.reinstall})
|
|
64
63
|
spinner.succeed(chalk.green('specify-cli installed'))
|
|
65
64
|
} catch (err) {
|
|
66
65
|
spinner.fail()
|
|
67
66
|
if (err instanceof DvmiError) {
|
|
68
|
-
this.error(err.message, {
|
|
67
|
+
this.error(err.message, {exit: 1, suggestions: [err.hint]})
|
|
69
68
|
}
|
|
70
69
|
throw err
|
|
71
70
|
}
|
|
@@ -86,10 +85,10 @@ export default class PromptsInstallSpeckit extends Command {
|
|
|
86
85
|
this.log('')
|
|
87
86
|
|
|
88
87
|
try {
|
|
89
|
-
await runSpecifyInit(process.cwd(), {
|
|
88
|
+
await runSpecifyInit(process.cwd(), {ai: aiFlag, force: flags.force})
|
|
90
89
|
} catch (err) {
|
|
91
90
|
if (err instanceof DvmiError) {
|
|
92
|
-
this.error(err.message, {
|
|
91
|
+
this.error(err.message, {exit: 1, suggestions: [err.hint]})
|
|
93
92
|
}
|
|
94
93
|
throw err
|
|
95
94
|
}
|