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.
Files changed (96) hide show
  1. package/README.md +7 -0
  2. package/oclif.manifest.json +41 -1
  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 +95 -21
  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 +25 -17
  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,11 +1,11 @@
1
- import { Command, Args, Flags } from '@oclif/core'
1
+ import {Command, Args, Flags} from '@oclif/core'
2
2
  import ora from 'ora'
3
3
  import chalk from 'chalk'
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'
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 { args, flags } = await this.parse(PromptsBrowse)
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, { exit: err.exitCode, suggestions: [err.hint] })
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 { skills, total: skills.length }
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 { skills, total: skills.length }
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, { exit: err.exitCode, suggestions: [err.hint] })
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 { entries, total: entries.length, category }
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) => ({ name: `${e.name} ${chalk.dim(e.url)}`, value: e }))
144
- choices.push({ name: chalk.dim('← Exit'), value: /** @type {AwesomeEntry} */ (null) })
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 { entries, total: entries.length, category }
162
+ return {entries, total: entries.length, category}
163
163
  }
164
164
  }
@@ -1,11 +1,11 @@
1
- import { Command, Args, Flags } from '@oclif/core'
1
+ import {Command, Args, Flags} from '@oclif/core'
2
2
  import ora from 'ora'
3
3
  import chalk from 'chalk'
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'
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 { args, flags } = await this.parse(PromptsDownload)
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, { exit: err.exitCode, suggestions: [err.hint] })
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 { downloaded: [], skipped: [] }
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({ message: 'Select a prompt to download:', choices })
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, { overwrite: flags.overwrite })
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, { exit: err.exitCode, suggestions: [err.hint] })
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, { overwrite: true })
124
+ result = await downloadPrompt(relativePath, localDir, {overwrite: true})
126
125
  } catch (err) {
127
126
  if (err instanceof DvmiError) {
128
- this.error(err.message, { exit: err.exitCode, suggestions: [err.hint] })
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 { 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 { loadConfig } from '../../services/config.js'
5
- import { isUvInstalled, isSpecifyInstalled, installSpecifyCli, runSpecifyInit } from '../../services/speckit.js'
6
- import { DvmiError } from '../../utils/errors.js'
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 { flags } = await this.parse(PromptsInstallSpeckit)
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({ spinner: 'arc', color: false, text: chalk.hex('#FF6B2B')(label) }).start()
59
+ const spinner = ora({spinner: 'arc', color: false, text: chalk.hex('#FF6B2B')(label)}).start()
61
60
 
62
61
  try {
63
- await installSpecifyCli({ force: flags.reinstall })
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, { exit: 1, suggestions: [err.hint] })
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(), { ai: aiFlag, force: flags.force })
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, { exit: 1, suggestions: [err.hint] })
91
+ this.error(err.message, {exit: 1, suggestions: [err.hint]})
93
92
  }
94
93
  throw err
95
94
  }
@@ -1,10 +1,10 @@
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 { select } from '@inquirer/prompts'
5
- import { listPrompts } from '../../services/prompts.js'
6
- import { formatPromptTable, formatPromptBody } from '../../formatters/prompts.js'
7
- import { DvmiError } from '../../utils/errors.js'
4
+ import {select} from '@inquirer/prompts'
5
+ import {listPrompts} from '../../services/prompts.js'
6
+ import {formatPromptTable, formatPromptBody} from '../../formatters/prompts.js'
7
+ import {DvmiError} from '../../utils/errors.js'
8
8
 
9
9
  /** @import { Prompt } from '../../types.js' */
10
10
 
@@ -27,7 +27,7 @@ export default class PromptsList extends Command {
27
27
  }
28
28
 
29
29
  async run() {
30
- const { flags } = await this.parse(PromptsList)
30
+ const {flags} = await this.parse(PromptsList)
31
31
  const isJson = flags.json
32
32
 
33
33
  const spinner = isJson
@@ -45,7 +45,7 @@ export default class PromptsList extends Command {
45
45
  } catch (err) {
46
46
  spinner?.fail()
47
47
  if (err instanceof DvmiError) {
48
- this.error(err.message, { exit: err.exitCode, suggestions: [err.hint] })
48
+ this.error(err.message, {exit: err.exitCode, suggestions: [err.hint]})
49
49
  }
50
50
  throw err
51
51
  }
@@ -65,7 +65,7 @@ export default class PromptsList extends Command {
65
65
  : prompts
66
66
 
67
67
  if (isJson) {
68
- return { prompts: filtered, total: filtered.length }
68
+ return {prompts: filtered, total: filtered.length}
69
69
  }
70
70
 
71
71
  if (filtered.length === 0) {
@@ -73,7 +73,7 @@ export default class PromptsList extends Command {
73
73
  ? chalk.dim(`No prompts matching "${flags.filter}".`)
74
74
  : chalk.yellow('No prompts found in the repository.')
75
75
  this.log(msg)
76
- return { prompts: [], total: 0 }
76
+ return {prompts: [], total: 0}
77
77
  }
78
78
 
79
79
  const filterInfo = query ? chalk.dim(` — filter: ${chalk.white(`"${flags.filter}"`)}`) : ''
@@ -87,8 +87,8 @@ export default class PromptsList extends Command {
87
87
 
88
88
  // Interactive selection to view full prompt content
89
89
  try {
90
- const choices = filtered.map((p) => ({ name: p.title, value: p }))
91
- choices.push({ name: chalk.dim('← Exit'), value: /** @type {Prompt} */ (null) })
90
+ const choices = filtered.map((p) => ({name: p.title, value: p}))
91
+ choices.push({name: chalk.dim('← Exit'), value: /** @type {Prompt} */ (null)})
92
92
 
93
93
  const selected = await select({
94
94
  message: 'Select a prompt to view its content (or Exit):',
@@ -102,6 +102,6 @@ export default class PromptsList extends Command {
102
102
  // User pressed Ctrl+C — exit gracefully
103
103
  }
104
104
 
105
- return { prompts: filtered, total: filtered.length }
105
+ return {prompts: filtered, total: filtered.length}
106
106
  }
107
107
  }
@@ -1,12 +1,12 @@
1
- import { Command, Args, Flags } from '@oclif/core'
1
+ import {Command, Args, Flags} from '@oclif/core'
2
2
  import ora from 'ora'
3
3
  import chalk from 'chalk'
4
- import { select, confirm } from '@inquirer/prompts'
5
- import { join } from 'node:path'
6
- import { readdir } from 'node:fs/promises'
7
- import { resolveLocalPrompt, invokeTool, SUPPORTED_TOOLS } from '../../services/prompts.js'
8
- import { loadConfig } from '../../services/config.js'
9
- import { DvmiError } from '../../utils/errors.js'
4
+ import {select, confirm} from '@inquirer/prompts'
5
+ import {join} from 'node:path'
6
+ import {readdir} from 'node:fs/promises'
7
+ import {resolveLocalPrompt, invokeTool, SUPPORTED_TOOLS} from '../../services/prompts.js'
8
+ import {loadConfig} from '../../services/config.js'
9
+ import {DvmiError} from '../../utils/errors.js'
10
10
 
11
11
  /** @import { AITool } from '../../types.js' */
12
12
 
@@ -23,7 +23,7 @@ async function walkPrompts(dir, base) {
23
23
  const results = []
24
24
  let entries
25
25
  try {
26
- entries = await readdir(dir, { withFileTypes: true })
26
+ entries = await readdir(dir, {withFileTypes: true})
27
27
  } catch {
28
28
  return results
29
29
  }
@@ -67,7 +67,7 @@ export default class PromptsRun extends Command {
67
67
  }
68
68
 
69
69
  async run() {
70
- const { args, flags } = await this.parse(PromptsRun)
70
+ const {args, flags} = await this.parse(PromptsRun)
71
71
  const isJson = flags.json
72
72
 
73
73
  // Load config
@@ -78,8 +78,7 @@ export default class PromptsRun extends Command {
78
78
  /* use defaults */
79
79
  }
80
80
 
81
- const localDir =
82
- process.env.DVMI_PROMPTS_DIR ?? config.promptsDir ?? join(process.cwd(), DEFAULT_PROMPTS_DIR)
81
+ const localDir = process.env.DVMI_PROMPTS_DIR ?? config.promptsDir ?? join(process.cwd(), DEFAULT_PROMPTS_DIR)
83
82
 
84
83
  // Resolve tool: --tool flag > config.aiTool
85
84
  const toolName = /** @type {AITool | undefined} */ (flags.tool ?? config.aiTool)
@@ -111,7 +110,7 @@ export default class PromptsRun extends Command {
111
110
  prompt = await resolveLocalPrompt(args.path, localDir)
112
111
  } catch (err) {
113
112
  if (err instanceof DvmiError) {
114
- this.error(err.message, { exit: err.exitCode, suggestions: [err.hint] })
113
+ this.error(err.message, {exit: err.exitCode, suggestions: [err.hint]})
115
114
  }
116
115
  throw err
117
116
  }
@@ -142,7 +141,7 @@ export default class PromptsRun extends Command {
142
141
 
143
142
  relativePath = await select({
144
143
  message: 'Select a local prompt to run:',
145
- choices: localPaths.map((p) => ({ name: p, value: p })),
144
+ choices: localPaths.map((p) => ({name: p, value: p})),
146
145
  })
147
146
  }
148
147
 
@@ -168,7 +167,7 @@ export default class PromptsRun extends Command {
168
167
  } catch (err) {
169
168
  spinner.fail()
170
169
  if (err instanceof DvmiError) {
171
- this.error(err.message, { exit: err.exitCode, suggestions: [err.hint] })
170
+ this.error(err.message, {exit: err.exitCode, suggestions: [err.hint]})
172
171
  }
173
172
  throw err
174
173
  }
@@ -180,14 +179,12 @@ export default class PromptsRun extends Command {
180
179
  // This protects against prompt injection from tampered local files (originally
181
180
  // downloaded from remote repositories). Skipped in CI/non-interactive environments.
182
181
  if (!process.env.CI && process.stdin.isTTY) {
183
- const preview = prompt.body.length > 500
184
- ? prompt.body.slice(0, 500) + chalk.dim('\n…[truncated]')
185
- : prompt.body
182
+ const preview = prompt.body.length > 500 ? prompt.body.slice(0, 500) + chalk.dim('\n…[truncated]') : prompt.body
186
183
  this.log(chalk.yellow('Prompt preview:'))
187
184
  this.log(chalk.dim('─'.repeat(50)))
188
185
  this.log(chalk.dim(preview))
189
186
  this.log(chalk.dim('─'.repeat(50)) + '\n')
190
- const ok = await confirm({ message: `Run this prompt with ${toolName}?`, default: true })
187
+ const ok = await confirm({message: `Run this prompt with ${toolName}?`, default: true})
191
188
  if (!ok) {
192
189
  this.log(chalk.dim('Aborted.'))
193
190
  return
@@ -199,7 +196,7 @@ export default class PromptsRun extends Command {
199
196
  await invokeTool(toolName, prompt.body)
200
197
  } catch (err) {
201
198
  if (err instanceof DvmiError) {
202
- this.error(err.message, { exit: err.exitCode, suggestions: [err.hint] })
199
+ this.error(err.message, {exit: err.exitCode, suggestions: [err.hint]})
203
200
  }
204
201
  throw err
205
202
  }
@@ -1,9 +1,9 @@
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 { listRepos } from '../../services/github.js'
5
- import { loadConfig } from '../../services/config.js'
6
- import { renderTable } from '../../formatters/table.js'
4
+ import {listRepos} from '../../services/github.js'
5
+ import {loadConfig} from '../../services/config.js'
6
+ import {renderTable} from '../../formatters/table.js'
7
7
 
8
8
  /**
9
9
  * @param {string} lang
@@ -13,22 +13,22 @@ function langColor(lang) {
13
13
  const map = {
14
14
  javascript: chalk.yellow,
15
15
  typescript: chalk.blue,
16
- python: chalk.green,
17
- java: chalk.red,
18
- go: chalk.cyan,
19
- ruby: chalk.magenta,
20
- rust: chalk.hex('#CE422B'),
21
- kotlin: chalk.hex('#7F52FF'),
22
- swift: chalk.hex('#F05138'),
23
- php: chalk.hex('#777BB4'),
24
- shell: chalk.greenBright,
16
+ python: chalk.green,
17
+ java: chalk.red,
18
+ go: chalk.cyan,
19
+ ruby: chalk.magenta,
20
+ rust: chalk.hex('#CE422B'),
21
+ kotlin: chalk.hex('#7F52FF'),
22
+ swift: chalk.hex('#F05138'),
23
+ php: chalk.hex('#777BB4'),
24
+ shell: chalk.greenBright,
25
25
  }
26
26
  const fn = map[lang.toLowerCase()]
27
27
  return fn ? fn(lang) : chalk.dim(lang)
28
28
  }
29
29
 
30
30
  export default class RepoList extends Command {
31
- static description = 'Lista repository dell\'organizzazione'
31
+ static description = "Lista repository dell'organizzazione"
32
32
 
33
33
  static examples = [
34
34
  '<%= config.bin %> repo list',
@@ -41,13 +41,13 @@ export default class RepoList extends Command {
41
41
  static enableJsonFlag = true
42
42
 
43
43
  static flags = {
44
- language: Flags.string({ description: 'Filtra per linguaggio' }),
45
- topic: Flags.string({ description: 'Filtra per topic' }),
46
- search: Flags.string({ char: 's', description: 'Cerca in nome e descrizione (case-insensitive)' }),
44
+ language: Flags.string({description: 'Filtra per linguaggio'}),
45
+ topic: Flags.string({description: 'Filtra per topic'}),
46
+ search: Flags.string({char: 's', description: 'Cerca in nome e descrizione (case-insensitive)'}),
47
47
  }
48
48
 
49
49
  async run() {
50
- const { flags } = await this.parse(RepoList)
50
+ const {flags} = await this.parse(RepoList)
51
51
  const isJson = flags.json
52
52
  const config = await loadConfig()
53
53
 
@@ -55,7 +55,9 @@ export default class RepoList extends Command {
55
55
  this.error('GitHub org not configured. Run `dvmi init` to set up your environment.')
56
56
  }
57
57
 
58
- const spinner = isJson ? null : ora({ spinner: 'arc', color: false, text: chalk.hex('#FF6B2B')('Fetching repositories...') }).start()
58
+ const spinner = isJson
59
+ ? null
60
+ : ora({spinner: 'arc', color: false, text: chalk.hex('#FF6B2B')('Fetching repositories...')}).start()
59
61
  const repos = await listRepos(config.org, {
60
62
  language: flags.language,
61
63
  topic: flags.topic,
@@ -65,49 +67,63 @@ export default class RepoList extends Command {
65
67
  // Search filter (name + description)
66
68
  const searchQuery = flags.search?.toLowerCase()
67
69
  const filtered = searchQuery
68
- ? repos.filter((r) =>
69
- r.name.toLowerCase().includes(searchQuery) ||
70
- r.description.toLowerCase().includes(searchQuery),
70
+ ? repos.filter(
71
+ (r) => r.name.toLowerCase().includes(searchQuery) || r.description.toLowerCase().includes(searchQuery),
71
72
  )
72
73
  : repos
73
74
 
74
- if (isJson) return { repositories: filtered, total: filtered.length }
75
+ if (isJson) return {repositories: filtered, total: filtered.length}
75
76
 
76
77
  if (repos.length === 0) {
77
78
  this.log(chalk.yellow('No repositories found matching your filters.'))
78
- return { repositories: [], total: 0 }
79
+ return {repositories: [], total: 0}
79
80
  }
80
81
 
81
82
  if (filtered.length === 0) {
82
83
  this.log(chalk.dim(`No repositories matching "${flags.search}".`))
83
- return { repositories: [], total: 0 }
84
+ return {repositories: [], total: 0}
84
85
  }
85
86
 
86
87
  // Build filter info line
87
88
  const filterInfo = [
88
89
  flags.language && chalk.dim(`language: ${chalk.white(flags.language)}`),
89
- flags.topic && chalk.dim(`topic: ${chalk.white(flags.topic)}`),
90
- flags.search && chalk.dim(`search: ${chalk.white(`"${flags.search}"`)}`),
91
- ].filter(Boolean).join(chalk.dim(' · '))
90
+ flags.topic && chalk.dim(`topic: ${chalk.white(flags.topic)}`),
91
+ flags.search && chalk.dim(`search: ${chalk.white(`"${flags.search}"`)}`),
92
+ ]
93
+ .filter(Boolean)
94
+ .join(chalk.dim(' · '))
92
95
 
93
96
  this.log(
94
97
  chalk.bold(`\nRepositories in ${config.org}`) +
95
- (filterInfo ? chalk.dim(' — ') + filterInfo : '') +
96
- chalk.dim(` (${filtered.length}${filtered.length < repos.length ? `/${repos.length}` : ''})`) +
97
- '\n',
98
+ (filterInfo ? chalk.dim(' — ') + filterInfo : '') +
99
+ chalk.dim(` (${filtered.length}${filtered.length < repos.length ? `/${repos.length}` : ''})`) +
100
+ '\n',
98
101
  )
99
102
 
100
- this.log(renderTable(filtered, [
101
- { header: 'Name', key: 'name', width: 40 },
102
- { header: 'Language', key: 'language', width: 14, format: (v) => v || '—', colorize: (v) => v === '—' ? chalk.dim(v) : langColor(v) },
103
- { header: 'Last push', key: 'pushedAt', width: 12, format: (v) => {
104
- const d = new Date(String(v))
105
- return isNaN(d.getTime()) ? '' : d.toLocaleDateString()
106
- }},
107
- { header: 'Description', key: 'description', width: 60, format: (v) => String(v || '—') },
108
- ]))
103
+ this.log(
104
+ renderTable(filtered, [
105
+ {header: 'Name', key: 'name', width: 40},
106
+ {
107
+ header: 'Language',
108
+ key: 'language',
109
+ width: 14,
110
+ format: (v) => v || '—',
111
+ colorize: (v) => (v === '—' ? chalk.dim(v) : langColor(v)),
112
+ },
113
+ {
114
+ header: 'Last push',
115
+ key: 'pushedAt',
116
+ width: 12,
117
+ format: (v) => {
118
+ const d = new Date(String(v))
119
+ return isNaN(d.getTime()) ? '—' : d.toLocaleDateString()
120
+ },
121
+ },
122
+ {header: 'Description', key: 'description', width: 60, format: (v) => String(v || '—')},
123
+ ]),
124
+ )
109
125
 
110
126
  this.log('')
111
- return { repositories: filtered, total: filtered.length }
127
+ return {repositories: filtered, total: filtered.length}
112
128
  }
113
129
  }
@@ -1,12 +1,12 @@
1
- import { Command, Args, Flags } from '@oclif/core'
1
+ import {Command, Args, Flags} from '@oclif/core'
2
2
  import chalk from 'chalk'
3
3
  import ora from 'ora'
4
- import { searchCode } from '../services/github.js'
5
- import { loadConfig } from '../services/config.js'
6
- import { renderTable } from '../formatters/table.js'
4
+ import {searchCode} from '../services/github.js'
5
+ import {loadConfig} from '../services/config.js'
6
+ import {renderTable} from '../formatters/table.js'
7
7
 
8
8
  export default class Search extends Command {
9
- static description = 'Cerca codice nei repository dell\'organizzazione'
9
+ static description = "Cerca codice nei repository dell'organizzazione"
10
10
 
11
11
  static examples = [
12
12
  '<%= config.bin %> search "getUserById"',
@@ -17,17 +17,17 @@ export default class Search extends Command {
17
17
  static enableJsonFlag = true
18
18
 
19
19
  static args = {
20
- term: Args.string({ description: 'Termine di ricerca', required: true }),
20
+ term: Args.string({description: 'Termine di ricerca', required: true}),
21
21
  }
22
22
 
23
23
  static flags = {
24
- language: Flags.string({ description: 'Filtra per linguaggio' }),
25
- repo: Flags.string({ description: 'Cerca in un repo specifico' }),
26
- limit: Flags.integer({ description: 'Max risultati', default: 20 }),
24
+ language: Flags.string({description: 'Filtra per linguaggio'}),
25
+ repo: Flags.string({description: 'Cerca in un repo specifico'}),
26
+ limit: Flags.integer({description: 'Max risultati', default: 20}),
27
27
  }
28
28
 
29
29
  async run() {
30
- const { args, flags } = await this.parse(Search)
30
+ const {args, flags} = await this.parse(Search)
31
31
  const isJson = flags.json
32
32
  const config = await loadConfig()
33
33
 
@@ -43,20 +43,22 @@ export default class Search extends Command {
43
43
  })
44
44
  spinner?.stop()
45
45
 
46
- if (isJson) return { results, total: results.length }
46
+ if (isJson) return {results, total: results.length}
47
47
 
48
48
  if (results.length === 0) {
49
49
  this.log(chalk.yellow(`No results found for "${args.term}" in the organization.`))
50
- return { results: [], total: 0 }
50
+ return {results: [], total: 0}
51
51
  }
52
52
 
53
53
  this.log(chalk.bold(`\n${results.length} result(s) for "${args.term}":\n`))
54
- this.log(renderTable(results, [
55
- { header: 'Repo', key: 'repo', width: 25 },
56
- { header: 'File', key: 'file', width: 45 },
57
- { header: 'Match', key: 'match' },
58
- ]))
54
+ this.log(
55
+ renderTable(results, [
56
+ {header: 'Repo', key: 'repo', width: 25},
57
+ {header: 'File', key: 'file', width: 45},
58
+ {header: 'Match', key: 'match'},
59
+ ]),
60
+ )
59
61
 
60
- return { results, total: results.length }
62
+ return {results, total: results.length}
61
63
  }
62
64
  }