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,34 +1,31 @@
1
- import { Command, Flags } from '@oclif/core'
1
+ import {Command, Flags} from '@oclif/core'
2
2
  import ora from 'ora'
3
3
  import chalk from 'chalk'
4
- import { detectPlatform } from '../../services/platform.js'
5
- import { isChezmoiInstalled, getChezmoiConfig, getManagedFiles, getChezmoiRemote } from '../../services/dotfiles.js'
6
- import { loadConfig } from '../../services/config.js'
7
- import { formatDotfilesStatus } from '../../formatters/dotfiles.js'
8
- import { DvmiError } from '../../utils/errors.js'
4
+ import {detectPlatform} from '../../services/platform.js'
5
+ import {isChezmoiInstalled, getChezmoiConfig, getManagedFiles, getChezmoiRemote} from '../../services/dotfiles.js'
6
+ import {loadConfig} from '../../services/config.js'
7
+ import {formatDotfilesStatus} from '../../formatters/dotfiles.js'
8
+ import {DvmiError} from '../../utils/errors.js'
9
9
 
10
10
  /** @import { DotfilesStatusResult } from '../../types.js' */
11
11
 
12
12
  export default class DotfilesStatus extends Command {
13
13
  static description = 'Show chezmoi dotfiles status: managed files, encryption state, and sync health'
14
14
 
15
- static examples = [
16
- '<%= config.bin %> dotfiles status',
17
- '<%= config.bin %> dotfiles status --json',
18
- ]
15
+ static examples = ['<%= config.bin %> dotfiles status', '<%= config.bin %> dotfiles status --json']
19
16
 
20
17
  static enableJsonFlag = true
21
18
 
22
19
  static flags = {
23
- help: Flags.help({ char: 'h' }),
20
+ help: Flags.help({char: 'h'}),
24
21
  }
25
22
 
26
23
  async run() {
27
- const { flags } = await this.parse(DotfilesStatus)
24
+ const {flags} = await this.parse(DotfilesStatus)
28
25
  const isJson = flags.json
29
26
 
30
27
  const platformInfo = await detectPlatform()
31
- const { platform } = platformInfo
28
+ const {platform} = platformInfo
32
29
 
33
30
  const config = await loadConfig()
34
31
  const enabled = config.dotfiles?.enabled === true
@@ -47,7 +44,7 @@ export default class DotfilesStatus extends Command {
47
44
  repo: null,
48
45
  sourceDir: null,
49
46
  files: [],
50
- summary: { total: 0, encrypted: 0, plaintext: 0 },
47
+ summary: {total: 0, encrypted: 0, plaintext: 0},
51
48
  }
52
49
 
53
50
  if (isJson) return notConfiguredResult
@@ -56,14 +53,17 @@ export default class DotfilesStatus extends Command {
56
53
  }
57
54
 
58
55
  if (!chezmoiInstalled) {
59
- const hint = platform === 'macos'
60
- ? 'Run `brew install chezmoi` or visit https://chezmoi.io/install'
61
- : 'Run `sh -c "$(curl -fsLS get.chezmoi.io)"` or visit https://chezmoi.io/install'
56
+ const hint =
57
+ platform === 'macos'
58
+ ? 'Run `brew install chezmoi` or visit https://chezmoi.io/install'
59
+ : 'Run `sh -c "$(curl -fsLS get.chezmoi.io)"` or visit https://chezmoi.io/install'
62
60
  throw new DvmiError('chezmoi is not installed', hint)
63
61
  }
64
62
 
65
63
  // Gather data
66
- const spinner = isJson ? null : ora({ spinner: 'arc', color: false, text: chalk.hex('#FF6B2B')('Gathering dotfiles status...') }).start()
64
+ const spinner = isJson
65
+ ? null
66
+ : ora({spinner: 'arc', color: false, text: chalk.hex('#FF6B2B')('Gathering dotfiles status...')}).start()
67
67
 
68
68
  const [chezmoiConfig, files, remote] = await Promise.all([
69
69
  getChezmoiConfig(),
@@ -1,13 +1,13 @@
1
- import { Command, Flags, Args } from '@oclif/core'
1
+ import {Command, Flags, Args} from '@oclif/core'
2
2
  import ora from 'ora'
3
3
  import chalk from 'chalk'
4
- import { confirm, input, select } from '@inquirer/prompts'
5
- import { detectPlatform } from '../../services/platform.js'
6
- import { isChezmoiInstalled, getChezmoiRemote, hasLocalChanges } from '../../services/dotfiles.js'
7
- import { loadConfig, saveConfig } from '../../services/config.js'
8
- import { exec, execOrThrow } from '../../services/shell.js'
9
- import { formatDotfilesSync } from '../../formatters/dotfiles.js'
10
- import { DvmiError } from '../../utils/errors.js'
4
+ import {confirm, input, select} from '@inquirer/prompts'
5
+ import {detectPlatform} from '../../services/platform.js'
6
+ import {isChezmoiInstalled, getChezmoiRemote, hasLocalChanges} from '../../services/dotfiles.js'
7
+ import {loadConfig, saveConfig} from '../../services/config.js'
8
+ import {exec, execOrThrow} from '../../services/shell.js'
9
+ import {formatDotfilesSync} from '../../formatters/dotfiles.js'
10
+ import {DvmiError} from '../../utils/errors.js'
11
11
 
12
12
  /** @import { DotfilesSyncResult } from '../../types.js' */
13
13
 
@@ -26,18 +26,18 @@ export default class DotfilesSync extends Command {
26
26
  static enableJsonFlag = true
27
27
 
28
28
  static flags = {
29
- help: Flags.help({ char: 'h' }),
30
- push: Flags.boolean({ description: 'Push local changes to remote', default: false }),
31
- pull: Flags.boolean({ description: 'Pull remote changes and apply', default: false }),
32
- 'dry-run': Flags.boolean({ description: 'Show what would change without applying', default: false }),
29
+ help: Flags.help({char: 'h'}),
30
+ push: Flags.boolean({description: 'Push local changes to remote', default: false}),
31
+ pull: Flags.boolean({description: 'Pull remote changes and apply', default: false}),
32
+ 'dry-run': Flags.boolean({description: 'Show what would change without applying', default: false}),
33
33
  }
34
34
 
35
35
  static args = {
36
- repo: Args.string({ description: 'Remote repository URL (for initial remote setup)', required: false }),
36
+ repo: Args.string({description: 'Remote repository URL (for initial remote setup)', required: false}),
37
37
  }
38
38
 
39
39
  async run() {
40
- const { flags, args } = await this.parse(DotfilesSync)
40
+ const {flags, args} = await this.parse(DotfilesSync)
41
41
  const isJson = flags.json
42
42
  const isPush = flags.push
43
43
  const isPull = flags.pull
@@ -56,30 +56,27 @@ export default class DotfilesSync extends Command {
56
56
  const isCI = process.env.CI === 'true'
57
57
  const isNonInteractive = !process.stdout.isTTY
58
58
  if ((isCI || isNonInteractive) && !isJson) {
59
- this.error(
60
- 'This command requires an interactive terminal (TTY). Run with --json for a non-interactive sync.',
61
- { exit: 1 },
62
- )
59
+ this.error('This command requires an interactive terminal (TTY). Run with --json for a non-interactive sync.', {
60
+ exit: 1,
61
+ })
63
62
  }
64
63
 
65
64
  const config = await loadConfig()
66
65
  if (!config.dotfiles?.enabled) {
67
- throw new DvmiError(
68
- 'Chezmoi dotfiles management is not configured',
69
- 'Run `dvmi dotfiles setup` first',
70
- )
66
+ throw new DvmiError('Chezmoi dotfiles management is not configured', 'Run `dvmi dotfiles setup` first')
71
67
  }
72
68
 
73
69
  const chezmoiInstalled = await isChezmoiInstalled()
74
70
  if (!chezmoiInstalled) {
75
71
  const platformInfo = await detectPlatform()
76
- const hint = platformInfo.platform === 'macos'
77
- ? 'Run `brew install chezmoi` or visit https://chezmoi.io/install'
78
- : 'Run `sh -c "$(curl -fsLS get.chezmoi.io)"` or visit https://chezmoi.io/install'
72
+ const hint =
73
+ platformInfo.platform === 'macos'
74
+ ? 'Run `brew install chezmoi` or visit https://chezmoi.io/install'
75
+ : 'Run `sh -c "$(curl -fsLS get.chezmoi.io)"` or visit https://chezmoi.io/install'
79
76
  throw new DvmiError('chezmoi is not installed', hint)
80
77
  }
81
78
 
82
- const remote = config.dotfiles?.repo ?? await getChezmoiRemote()
79
+ const remote = config.dotfiles?.repo ?? (await getChezmoiRemote())
83
80
 
84
81
  // --json mode: attempt push/pull or report status
85
82
  if (isJson) {
@@ -105,7 +102,13 @@ export default class DotfilesSync extends Command {
105
102
  }
106
103
 
107
104
  /** @type {DotfilesSyncResult} */
108
- return { action: 'skipped', repo: effectiveRemote ?? null, status: 'skipped', message: 'No action specified', conflicts: [] }
105
+ return {
106
+ action: 'skipped',
107
+ repo: effectiveRemote ?? null,
108
+ status: 'skipped',
109
+ message: 'No action specified',
110
+ conflicts: [],
111
+ }
109
112
  }
110
113
 
111
114
  // ---------------------------------------------------------------------------
@@ -140,22 +143,29 @@ export default class DotfilesSync extends Command {
140
143
  const action = await select({
141
144
  message: 'What would you like to do?',
142
145
  choices: [
143
- { name: 'Push local changes to remote', value: 'push' },
144
- { name: 'Pull remote changes and apply', value: 'pull' },
145
- { name: 'Cancel', value: 'cancel' },
146
+ {name: 'Push local changes to remote', value: 'push'},
147
+ {name: 'Pull remote changes and apply', value: 'pull'},
148
+ {name: 'Cancel', value: 'cancel'},
146
149
  ],
147
150
  })
148
151
 
149
152
  if (action === 'cancel') {
150
153
  /** @type {DotfilesSyncResult} */
151
- const cancelResult = { action: 'skipped', repo: effectiveRemote ?? null, status: 'skipped', message: 'Cancelled by user', conflicts: [] }
154
+ const cancelResult = {
155
+ action: 'skipped',
156
+ repo: effectiveRemote ?? null,
157
+ status: 'skipped',
158
+ message: 'Cancelled by user',
159
+ conflicts: [],
160
+ }
152
161
  this.log(formatDotfilesSync(cancelResult))
153
162
  return cancelResult
154
163
  }
155
164
 
156
- const result = action === 'push'
157
- ? await this._push(effectiveRemote, isDryRun, false)
158
- : await this._pull(effectiveRemote, isDryRun, false)
165
+ const result =
166
+ action === 'push'
167
+ ? await this._push(effectiveRemote, isDryRun, false)
168
+ : await this._pull(effectiveRemote, isDryRun, false)
159
169
 
160
170
  this.log(formatDotfilesSync(result))
161
171
  return result
@@ -174,25 +184,25 @@ export default class DotfilesSync extends Command {
174
184
  const choice = await select({
175
185
  message: 'Connect to an existing dotfiles repository or create a new one?',
176
186
  choices: [
177
- { name: 'Connect to existing repository', value: 'existing' },
178
- { name: 'Create new repository on GitHub', value: 'new' },
187
+ {name: 'Connect to existing repository', value: 'existing'},
188
+ {name: 'Create new repository on GitHub', value: 'new'},
179
189
  ],
180
190
  })
181
191
 
182
192
  let repoUrl = ''
183
193
 
184
194
  if (choice === 'existing') {
185
- repoUrl = await input({ message: 'Repository URL (SSH or HTTPS):' })
195
+ repoUrl = await input({message: 'Repository URL (SSH or HTTPS):'})
186
196
  } else {
187
- const repoName = await input({ message: 'Repository name:', default: 'dotfiles' })
188
- const isPrivate = await confirm({ message: 'Make repository private?', default: true })
197
+ const repoName = await input({message: 'Repository name:', default: 'dotfiles'})
198
+ const isPrivate = await confirm({message: 'Make repository private?', default: true})
189
199
 
190
200
  if (!isDryRun) {
191
201
  try {
192
202
  const visFlag = isPrivate ? '--private' : '--public'
193
203
  await execOrThrow('gh', ['repo', 'create', repoName, visFlag, '--confirm'])
194
204
  // Get the SSH URL from the created repo
195
- const { exec } = await import('../../services/shell.js')
205
+ const {exec} = await import('../../services/shell.js')
196
206
  const result = await exec('gh', ['repo', 'view', repoName, '--json', 'sshUrl', '--jq', '.sshUrl'])
197
207
  repoUrl = result.stdout.trim() || `git@github.com:${repoName}.git`
198
208
  } catch {
@@ -211,7 +221,7 @@ export default class DotfilesSync extends Command {
211
221
  await execOrThrow('chezmoi', ['git', '--', 'remote', 'add', 'origin', repoUrl])
212
222
  await execOrThrow('chezmoi', ['git', '--', 'push', '-u', 'origin', 'main'])
213
223
  // Save repo to dvmi config
214
- config.dotfiles = { ...(config.dotfiles ?? { enabled: true }), repo: repoUrl }
224
+ config.dotfiles = {...(config.dotfiles ?? {enabled: true}), repo: repoUrl}
215
225
  await saveConfig(config)
216
226
  } catch (err) {
217
227
  /** @type {DotfilesSyncResult} */
@@ -232,7 +242,9 @@ export default class DotfilesSync extends Command {
232
242
  action: 'init-remote',
233
243
  repo: repoUrl,
234
244
  status: isDryRun ? 'skipped' : 'success',
235
- message: isDryRun ? `Would configure remote: ${repoUrl}` : 'Remote repository configured and initial push completed',
245
+ message: isDryRun
246
+ ? `Would configure remote: ${repoUrl}`
247
+ : 'Remote repository configured and initial push completed',
236
248
  conflicts: [],
237
249
  }
238
250
  this.log(formatDotfilesSync(result))
@@ -271,7 +283,9 @@ export default class DotfilesSync extends Command {
271
283
  }
272
284
  }
273
285
 
274
- const spinner = isJson ? null : ora({ spinner: 'arc', color: false, text: chalk.hex('#FF6B2B')('Pushing to remote...') }).start()
286
+ const spinner = isJson
287
+ ? null
288
+ : ora({spinner: 'arc', color: false, text: chalk.hex('#FF6B2B')('Pushing to remote...')}).start()
275
289
 
276
290
  try {
277
291
  // Stage all changes
@@ -282,7 +296,7 @@ export default class DotfilesSync extends Command {
282
296
  await execOrThrow('chezmoi', ['git', '--', 'push', 'origin', 'HEAD'])
283
297
  spinner?.succeed(chalk.green('Pushed to remote'))
284
298
 
285
- return { action: 'push', repo: remote, status: 'success', message: 'Changes pushed to remote', conflicts: [] }
299
+ return {action: 'push', repo: remote, status: 'success', message: 'Changes pushed to remote', conflicts: []}
286
300
  } catch (err) {
287
301
  spinner?.fail(chalk.red('Push failed'))
288
302
  return {
@@ -327,7 +341,9 @@ export default class DotfilesSync extends Command {
327
341
  }
328
342
  }
329
343
 
330
- const spinner = isJson ? null : ora({ spinner: 'arc', color: false, text: chalk.hex('#FF6B2B')('Pulling from remote...') }).start()
344
+ const spinner = isJson
345
+ ? null
346
+ : ora({spinner: 'arc', color: false, text: chalk.hex('#FF6B2B')('Pulling from remote...')}).start()
331
347
 
332
348
  try {
333
349
  // Check if chezmoi init was done with this remote (first-time pull)
@@ -360,7 +376,7 @@ export default class DotfilesSync extends Command {
360
376
  }
361
377
  }
362
378
 
363
- return { action: 'pull', repo: remote, status: 'success', message: 'Remote changes applied', conflicts: [] }
379
+ return {action: 'pull', repo: remote, status: 'success', message: 'Remote changes applied', conflicts: []}
364
380
  } catch (err) {
365
381
  spinner?.fail(chalk.red('Pull failed'))
366
382
  return {