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.
Files changed (96) hide show
  1. package/README.md +7 -0
  2. package/oclif.manifest.json +129 -89
  3. package/package.json +2 -1
  4. package/src/commands/auth/login.js +20 -16
  5. package/src/commands/changelog.js +12 -12
  6. package/src/commands/costs/get.js +14 -24
  7. package/src/commands/costs/trend.js +13 -24
  8. package/src/commands/create/repo.js +72 -54
  9. package/src/commands/docs/list.js +29 -25
  10. package/src/commands/docs/projects.js +58 -24
  11. package/src/commands/docs/read.js +56 -39
  12. package/src/commands/docs/search.js +37 -25
  13. package/src/commands/doctor.js +37 -35
  14. package/src/commands/dotfiles/add.js +51 -39
  15. package/src/commands/dotfiles/setup.js +62 -33
  16. package/src/commands/dotfiles/status.js +18 -18
  17. package/src/commands/dotfiles/sync.js +62 -46
  18. package/src/commands/init.js +143 -132
  19. package/src/commands/logs/index.js +10 -16
  20. package/src/commands/open.js +12 -12
  21. package/src/commands/pipeline/logs.js +8 -11
  22. package/src/commands/pipeline/rerun.js +21 -16
  23. package/src/commands/pipeline/status.js +28 -24
  24. package/src/commands/pr/create.js +40 -27
  25. package/src/commands/pr/detail.js +9 -7
  26. package/src/commands/pr/review.js +18 -19
  27. package/src/commands/pr/status.js +27 -21
  28. package/src/commands/prompts/browse.js +15 -15
  29. package/src/commands/prompts/download.js +15 -16
  30. package/src/commands/prompts/install-speckit.js +11 -12
  31. package/src/commands/prompts/list.js +12 -12
  32. package/src/commands/prompts/run.js +16 -19
  33. package/src/commands/repo/list.js +57 -41
  34. package/src/commands/search.js +20 -18
  35. package/src/commands/security/setup.js +38 -34
  36. package/src/commands/sync-config-ai/index.js +143 -0
  37. package/src/commands/tasks/assigned.js +43 -33
  38. package/src/commands/tasks/list.js +43 -33
  39. package/src/commands/tasks/today.js +32 -30
  40. package/src/commands/upgrade.js +18 -17
  41. package/src/commands/vuln/detail.js +8 -8
  42. package/src/commands/vuln/scan.js +39 -20
  43. package/src/commands/vuln/search.js +23 -18
  44. package/src/commands/welcome.js +2 -2
  45. package/src/commands/whoami.js +19 -23
  46. package/src/formatters/ai-config.js +127 -0
  47. package/src/formatters/charts.js +6 -23
  48. package/src/formatters/cost.js +1 -7
  49. package/src/formatters/dotfiles.js +48 -19
  50. package/src/formatters/markdown.js +11 -6
  51. package/src/formatters/openapi.js +7 -9
  52. package/src/formatters/prompts.js +69 -78
  53. package/src/formatters/security.js +2 -2
  54. package/src/formatters/status.js +1 -1
  55. package/src/formatters/table.js +1 -3
  56. package/src/formatters/vuln.js +33 -20
  57. package/src/help.js +162 -164
  58. package/src/hooks/init.js +1 -3
  59. package/src/hooks/postrun.js +5 -7
  60. package/src/index.js +1 -1
  61. package/src/services/ai-config-store.js +318 -0
  62. package/src/services/ai-env-deployer.js +444 -0
  63. package/src/services/ai-env-scanner.js +242 -0
  64. package/src/services/audit-detector.js +2 -2
  65. package/src/services/audit-runner.js +40 -31
  66. package/src/services/auth.js +9 -9
  67. package/src/services/awesome-copilot.js +7 -4
  68. package/src/services/aws-costs.js +22 -22
  69. package/src/services/clickup.js +26 -26
  70. package/src/services/cloudwatch-logs.js +5 -9
  71. package/src/services/config.js +13 -13
  72. package/src/services/docs.js +19 -20
  73. package/src/services/dotfiles.js +149 -51
  74. package/src/services/github.js +22 -24
  75. package/src/services/nvd.js +21 -31
  76. package/src/services/platform.js +2 -2
  77. package/src/services/prompts.js +23 -35
  78. package/src/services/security.js +135 -61
  79. package/src/services/shell.js +4 -4
  80. package/src/services/skills-sh.js +3 -9
  81. package/src/services/speckit.js +4 -7
  82. package/src/services/version-check.js +10 -10
  83. package/src/types.js +85 -0
  84. package/src/utils/aws-vault.js +18 -41
  85. package/src/utils/banner.js +5 -7
  86. package/src/utils/errors.js +42 -46
  87. package/src/utils/frontmatter.js +4 -4
  88. package/src/utils/gradient.js +18 -16
  89. package/src/utils/open-browser.js +3 -3
  90. package/src/utils/tui/form.js +1006 -0
  91. package/src/utils/tui/modal.js +15 -14
  92. package/src/utils/tui/navigable-table.js +16 -16
  93. package/src/utils/tui/tab-tui.js +800 -0
  94. package/src/utils/typewriter.js +3 -3
  95. package/src/utils/welcome.js +18 -21
  96. package/src/validators/repo-name.js +2 -2
@@ -1,5 +1,5 @@
1
- import { loadConfigSync } from '../services/config.js'
2
- import { execa } from 'execa'
1
+ import {loadConfigSync} from '../services/config.js'
2
+ import {execa} from 'execa'
3
3
 
4
4
  /**
5
5
  * Returns the aws-vault exec prefix to prepend to AWS CLI commands.
@@ -44,10 +44,7 @@ export function awsVaultPrefix(config = null) {
44
44
  * @returns {boolean}
45
45
  */
46
46
  export function hasAwsCredentialEnv() {
47
- return Boolean(
48
- process.env.AWS_ACCESS_KEY_ID ||
49
- process.env.AWS_SESSION_TOKEN,
50
- )
47
+ return Boolean(process.env.AWS_ACCESS_KEY_ID || process.env.AWS_SESSION_TOKEN)
51
48
  }
52
49
 
53
50
  /**
@@ -79,24 +76,14 @@ export async function reexecCurrentCommandWithAwsVault(config = null) {
79
76
  if (process.env.DVMI_AWS_VAULT_REEXEC === '1') return null
80
77
 
81
78
  try {
82
- const child = await execa(
83
- 'aws-vault',
84
- [
85
- 'exec',
86
- profile,
87
- '--',
88
- process.execPath,
89
- ...process.argv.slice(1),
90
- ],
91
- {
92
- reject: false,
93
- stdio: 'inherit',
94
- env: {
95
- ...process.env,
96
- DVMI_AWS_VAULT_REEXEC: '1',
97
- },
79
+ const child = await execa('aws-vault', ['exec', profile, '--', process.execPath, ...process.argv.slice(1)], {
80
+ reject: false,
81
+ stdio: 'inherit',
82
+ env: {
83
+ ...process.env,
84
+ DVMI_AWS_VAULT_REEXEC: '1',
98
85
  },
99
- )
86
+ })
100
87
 
101
88
  return child.exitCode ?? 1
102
89
  } catch {
@@ -117,25 +104,15 @@ export async function reexecCurrentCommandWithAwsVaultProfile(profile, extraEnv
117
104
  if (!profile) return null
118
105
 
119
106
  try {
120
- const child = await execa(
121
- 'aws-vault',
122
- [
123
- 'exec',
124
- profile,
125
- '--',
126
- process.execPath,
127
- ...process.argv.slice(1),
128
- ],
129
- {
130
- reject: false,
131
- stdio: 'inherit',
132
- env: {
133
- ...process.env,
134
- DVMI_AWS_VAULT_REEXEC: '1',
135
- ...extraEnv,
136
- },
107
+ const child = await execa('aws-vault', ['exec', profile, '--', process.execPath, ...process.argv.slice(1)], {
108
+ reject: false,
109
+ stdio: 'inherit',
110
+ env: {
111
+ ...process.env,
112
+ DVMI_AWS_VAULT_REEXEC: '1',
113
+ ...extraEnv,
137
114
  },
138
- )
115
+ })
139
116
 
140
117
  return child.exitCode ?? 1
141
118
  } catch {
@@ -1,6 +1,6 @@
1
1
  import figlet from 'figlet'
2
2
  import chalk from 'chalk'
3
- import { BRAND_GRADIENT, animateGradientBanner, isColorEnabled } from './gradient.js'
3
+ import {BRAND_GRADIENT, animateGradientBanner, isColorEnabled} from './gradient.js'
4
4
 
5
5
  // Brand colors
6
6
  export const ORANGE = '#FF6B2B'
@@ -24,13 +24,11 @@ function figletAsync(text, opts) {
24
24
  * @returns {Promise<void>}
25
25
  */
26
26
  export async function printBanner() {
27
- const art = await figletAsync('DVMI', { font: 'ANSI Shadow' })
28
- const artLines = art.split('\n').filter((l) => l.trim() !== '')
29
- const width = Math.max(...artLines.map((l) => l.length)) + 4
27
+ const art = await figletAsync('DVMI', {font: 'ANSI Shadow'})
28
+ const artLines = art.split('\n').filter((l) => l.trim() !== '')
29
+ const width = Math.max(...artLines.map((l) => l.length)) + 4
30
30
 
31
- const tagline = isColorEnabled
32
- ? chalk.hex(BLUE).bold(' Devvami Developer CLI')
33
- : ' Devvami Developer CLI'
31
+ const tagline = isColorEnabled ? chalk.hex(BLUE).bold(' Devvami Developer CLI') : ' Devvami Developer CLI'
34
32
 
35
33
  const separator = isColorEnabled
36
34
  ? chalk.hex(BLUE).dim('─'.repeat(Math.min(width, 60)))
@@ -2,14 +2,14 @@
2
2
  * Base CLI error with an actionable hint for the user.
3
3
  */
4
4
  export class DvmiError extends Error {
5
- /**
6
- * @param {string} message - Human-readable error message
7
- * @param {string} hint - Actionable suggestion to resolve the error
8
- * @param {number} [exitCode] - Process exit code (default: 1)
9
- */
10
- constructor(message, hint, exitCode = 1) {
11
- super(message)
12
- this.name = 'DvmiError'
5
+ /**
6
+ * @param {string} message - Human-readable error message
7
+ * @param {string} hint - Actionable suggestion to resolve the error
8
+ * @param {number} [exitCode] - Process exit code (default: 1)
9
+ */
10
+ constructor(message, hint, exitCode = 1) {
11
+ super(message)
12
+ this.name = 'DvmiError'
13
13
  /** @type {string} */
14
14
  this.hint = hint
15
15
  /** @type {number} */
@@ -21,43 +21,39 @@ export class DvmiError extends Error {
21
21
  * Validation error for invalid user input (exit code 2).
22
22
  */
23
23
  export class ValidationError extends DvmiError {
24
- /**
25
- * @param {string} message
26
- * @param {string} hint
27
- */
28
- constructor(message, hint) {
29
- super(message, hint, 2)
30
- this.name = 'ValidationError'
31
- // oclif reads this.oclif.exit to determine the process exit code
32
- this.oclif = { exit: 2 }
33
- }
34
- }
24
+ /**
25
+ * @param {string} message
26
+ * @param {string} hint
27
+ */
28
+ constructor(message, hint) {
29
+ super(message, hint, 2)
30
+ this.name = 'ValidationError'
31
+ // oclif reads this.oclif.exit to determine the process exit code
32
+ this.oclif = {exit: 2}
33
+ }
34
+ }
35
35
 
36
- /**
37
- * Auth error for missing or expired authentication.
38
- */
39
- export class AuthError extends DvmiError {
40
- /**
41
- * @param {string} service - Service name (e.g. "GitHub", "AWS")
42
- */
43
- constructor(service) {
44
- super(
45
- `${service} authentication required`,
46
- `Run \`dvmi auth login\` to authenticate`,
47
- 1,
48
- )
49
- this.name = 'AuthError'
50
- }
51
- }
36
+ /**
37
+ * Auth error for missing or expired authentication.
38
+ */
39
+ export class AuthError extends DvmiError {
40
+ /**
41
+ * @param {string} service - Service name (e.g. "GitHub", "AWS")
42
+ */
43
+ constructor(service) {
44
+ super(`${service} authentication required`, `Run \`dvmi auth login\` to authenticate`, 1)
45
+ this.name = 'AuthError'
46
+ }
47
+ }
52
48
 
53
- /**
54
- * Format an error for display in the terminal.
55
- * @param {Error} err
56
- * @returns {string}
57
- */
58
- export function formatError(err) {
59
- if (err instanceof DvmiError) {
60
- return `Error: ${err.message}\nHint: ${err.hint}`
61
- }
62
- return `Error: ${err.message}`
63
- }
49
+ /**
50
+ * Format an error for display in the terminal.
51
+ * @param {Error} err
52
+ * @returns {string}
53
+ */
54
+ export function formatError(err) {
55
+ if (err instanceof DvmiError) {
56
+ return `Error: ${err.message}\nHint: ${err.hint}`
57
+ }
58
+ return `Error: ${err.message}`
59
+ }
@@ -18,7 +18,7 @@ import yaml from 'js-yaml'
18
18
  export function parseFrontmatter(content) {
19
19
  const match = content.match(/^---\r?\n([\s\S]*?)---\r?\n?([\s\S]*)$/)
20
20
  if (!match) {
21
- return { frontmatter: {}, body: content }
21
+ return {frontmatter: {}, body: content}
22
22
  }
23
23
  const rawYaml = match[1]
24
24
  const body = match[2] ?? ''
@@ -28,9 +28,9 @@ export function parseFrontmatter(content) {
28
28
  parsed && typeof parsed === 'object' && !Array.isArray(parsed)
29
29
  ? /** @type {Record<string, unknown>} */ (parsed)
30
30
  : {}
31
- return { frontmatter, body }
31
+ return {frontmatter, body}
32
32
  } catch {
33
- return { frontmatter: {}, body: content }
33
+ return {frontmatter: {}, body: content}
34
34
  }
35
35
  }
36
36
 
@@ -47,6 +47,6 @@ export function serializeFrontmatter(frontmatter, body) {
47
47
  if (!frontmatter || Object.keys(frontmatter).length === 0) {
48
48
  return body
49
49
  }
50
- const yamlStr = yaml.dump(frontmatter, { lineWidth: -1 }).trimEnd()
50
+ const yamlStr = yaml.dump(frontmatter, {lineWidth: -1}).trimEnd()
51
51
  return `---\n${yamlStr}\n---\n${body}`
52
52
  }
@@ -12,9 +12,9 @@ import readline from 'node:readline'
12
12
 
13
13
  /** @type {GradientStop[]} */
14
14
  export const BRAND_GRADIENT = [
15
- [0, 212, 255], // #00D4FF — ciano elettrico
16
- [0, 100, 255], // #0064FF — blu vivido
17
- [100, 0, 220], // #6400DC — indaco profondo
15
+ [0, 212, 255], // #00D4FF — ciano elettrico
16
+ [0, 100, 255], // #0064FF — blu vivido
17
+ [100, 0, 220], // #6400DC — indaco profondo
18
18
  ]
19
19
 
20
20
  // ──────────────────────────────────────────────────────────────────────────────
@@ -51,24 +51,26 @@ export function gradientText(text, stops, phase = 0) {
51
51
 
52
52
  const segments = stops.length - 1
53
53
 
54
- return chars.map((char, i) => {
55
- if (char === ' ') return char
54
+ return chars
55
+ .map((char, i) => {
56
+ if (char === ' ') return char
56
57
 
57
- // Normalise t in [0, 1] with phase shift
58
- const t = ((i / Math.max(len - 1, 1)) + phase) % 1
58
+ // Normalise t in [0, 1] with phase shift
59
+ const t = (i / Math.max(len - 1, 1) + phase) % 1
59
60
 
60
- const seg = Math.min(Math.floor(t * segments), segments - 1)
61
- const localT = t * segments - seg
61
+ const seg = Math.min(Math.floor(t * segments), segments - 1)
62
+ const localT = t * segments - seg
62
63
 
63
- const [r1, g1, b1] = stops[seg]
64
- const [r2, g2, b2] = stops[seg + 1]
64
+ const [r1, g1, b1] = stops[seg]
65
+ const [r2, g2, b2] = stops[seg + 1]
65
66
 
66
- const r = Math.round(r1 + (r2 - r1) * localT)
67
- const g = Math.round(g1 + (g2 - g1) * localT)
68
- const b = Math.round(b1 + (b2 - b1) * localT)
67
+ const r = Math.round(r1 + (r2 - r1) * localT)
68
+ const g = Math.round(g1 + (g2 - g1) * localT)
69
+ const b = Math.round(b1 + (b2 - b1) * localT)
69
70
 
70
- return chalk.rgb(r, g, b)(char)
71
- }).join('')
71
+ return chalk.rgb(r, g, b)(char)
72
+ })
73
+ .join('')
72
74
  }
73
75
 
74
76
  // ──────────────────────────────────────────────────────────────────────────────
@@ -1,6 +1,6 @@
1
1
  import open from 'open'
2
- import { detectPlatform } from '../services/platform.js'
3
- import { exec } from '../services/shell.js'
2
+ import {detectPlatform} from '../services/platform.js'
3
+ import {exec} from '../services/shell.js'
4
4
 
5
5
  /**
6
6
  * Open a URL in the default browser, using the platform-appropriate command.
@@ -8,7 +8,7 @@ import { exec } from '../services/shell.js'
8
8
  * @returns {Promise<void>}
9
9
  */
10
10
  export async function openBrowser(url) {
11
- const { platform, openCommand } = await detectPlatform()
11
+ const {platform, openCommand} = await detectPlatform()
12
12
 
13
13
  if (platform === 'macos') {
14
14
  await open(url)