devvami 1.4.2 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/README.md +72 -0
  2. package/oclif.manifest.json +275 -235
  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 +257 -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 +215 -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 +349 -0
  62. package/src/services/ai-env-deployer.js +650 -0
  63. package/src/services/ai-env-scanner.js +983 -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 +117 -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 +1184 -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 +1089 -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
@@ -0,0 +1,215 @@
1
+ import chalk from 'chalk'
2
+
3
+ /** @import { DetectedEnvironment, CategoryEntry, NativeEntry } from '../types.js' */
4
+
5
+ // ──────────────────────────────────────────────────────────────────────────────
6
+ // Internal helpers
7
+ // ──────────────────────────────────────────────────────────────────────────────
8
+
9
+ /**
10
+ * Pad a string to a fixed width, truncating with '…' if needed.
11
+ * @param {string} str
12
+ * @param {number} width
13
+ * @returns {string}
14
+ */
15
+ function padCell(str, width) {
16
+ if (!str) str = ''
17
+ if (str.length > width) return str.slice(0, width - 1) + '…'
18
+ return str.padEnd(width)
19
+ }
20
+
21
+ // ──────────────────────────────────────────────────────────────────────────────
22
+ // Environments table formatter
23
+ // ──────────────────────────────────────────────────────────────────────────────
24
+
25
+ /**
26
+ * Format a list of detected environments as a table string for display in the TUI.
27
+ * Columns: Environment (name), Status, Scope, MCPs, Commands, Skills, Agents
28
+ * @param {DetectedEnvironment[]} detectedEnvs
29
+ * @param {number} [termCols]
30
+ * @returns {string[]} Array of formatted lines (no ANSI clear/home)
31
+ */
32
+ export function formatEnvironmentsTable(detectedEnvs, termCols = 120) {
33
+ const COL_ENV = 22
34
+ const COL_STATUS = 24
35
+ const COL_SCOPE = 8
36
+ const COL_COUNT = 9
37
+
38
+ const headerParts = [
39
+ chalk.bold.white(padCell('Environment', COL_ENV)),
40
+ chalk.bold.white(padCell('Status', COL_STATUS)),
41
+ chalk.bold.white(padCell('Scope', COL_SCOPE)),
42
+ chalk.bold.white(padCell('MCPs', COL_COUNT)),
43
+ chalk.bold.white(padCell('Commands', COL_COUNT)),
44
+ chalk.bold.white(padCell('Rules', COL_COUNT)),
45
+ chalk.bold.white(padCell('Skills', COL_COUNT)),
46
+ chalk.bold.white(padCell('Agents', COL_COUNT)),
47
+ ]
48
+
49
+ const dividerWidth = COL_ENV + COL_STATUS + COL_SCOPE + COL_COUNT * 5 + 7 * 2
50
+ const lines = []
51
+ lines.push(headerParts.join(' '))
52
+ lines.push(chalk.dim('─'.repeat(Math.min(termCols, dividerWidth))))
53
+
54
+ for (const env of detectedEnvs) {
55
+ const hasUnreadable = env.unreadable.length > 0
56
+ const statusText = hasUnreadable ? 'Detected (unreadable)' : 'Detected'
57
+ const statusStr = hasUnreadable
58
+ ? chalk.yellow(padCell(statusText, COL_STATUS))
59
+ : chalk.green(padCell(statusText, COL_STATUS))
60
+ const scopeStr = padCell(env.scope ?? 'project', COL_SCOPE)
61
+
62
+ const total = (/** @type {string} */ type) => (env.counts?.[type] ?? 0) + (env.nativeCounts?.[type] ?? 0)
63
+ const mcpStr = padCell(String(total('mcp')), COL_COUNT)
64
+ const cmdStr = padCell(String(total('command')), COL_COUNT)
65
+ const ruleStr = env.supportedCategories.includes('rule')
66
+ ? padCell(String(total('rule')), COL_COUNT)
67
+ : padCell('—', COL_COUNT)
68
+ const skillStr = env.supportedCategories.includes('skill')
69
+ ? padCell(String(total('skill')), COL_COUNT)
70
+ : padCell('—', COL_COUNT)
71
+ const agentStr = env.supportedCategories.includes('agent')
72
+ ? padCell(String(total('agent')), COL_COUNT)
73
+ : padCell('—', COL_COUNT)
74
+
75
+ lines.push([padCell(env.name, COL_ENV), statusStr, scopeStr, mcpStr, cmdStr, ruleStr, skillStr, agentStr].join(' '))
76
+ }
77
+
78
+ return lines
79
+ }
80
+
81
+ /** @type {Record<string, string>} */
82
+ const ENV_SHORT_NAMES = {
83
+ 'vscode-copilot': 'VSCode',
84
+ 'claude-code': 'Claude',
85
+ 'claude-desktop': 'Desktop',
86
+ opencode: 'OpenCode',
87
+ 'gemini-cli': 'Gemini',
88
+ 'copilot-cli': 'Copilot',
89
+ cursor: 'Cursor',
90
+ windsurf: 'Windsurf',
91
+ 'continue-dev': 'Continue',
92
+ zed: 'Zed',
93
+ 'amazon-q': 'Amazon Q',
94
+ }
95
+
96
+ /**
97
+ * Mask an environment variable value for display.
98
+ * Shows first 6 characters followed by ***.
99
+ * @param {string} value
100
+ * @returns {string}
101
+ */
102
+ export function maskEnvVarValue(value) {
103
+ if (!value || value.length <= 6) return '***'
104
+ return value.slice(0, 6) + '***'
105
+ }
106
+
107
+ // ──────────────────────────────────────────────────────────────────────────────
108
+ // Native entries table formatter
109
+ // ──────────────────────────────────────────────────────────────────────────────
110
+
111
+ /**
112
+ * Format native entries as a table for display in a category tab's Native section.
113
+ * @param {NativeEntry[]} entries
114
+ * @param {number} [termCols]
115
+ * @returns {string[]}
116
+ */
117
+ export function formatNativeEntriesTable(entries, termCols = 120) {
118
+ const COL_NAME = 24
119
+ const COL_ENV = 16
120
+ const COL_LEVEL = 8
121
+ const COL_CONFIG = 36
122
+
123
+ const headerParts = [
124
+ chalk.bold.white(padCell('Name', COL_NAME)),
125
+ chalk.bold.white(padCell('Environment', COL_ENV)),
126
+ chalk.bold.white(padCell('Level', COL_LEVEL)),
127
+ chalk.bold.white(padCell('Config', COL_CONFIG)),
128
+ ]
129
+
130
+ const dividerWidth = COL_NAME + COL_ENV + COL_LEVEL + COL_CONFIG + 3 * 2
131
+ const lines = []
132
+ lines.push(headerParts.join(' '))
133
+ lines.push(chalk.dim('─'.repeat(Math.min(termCols, dividerWidth))))
134
+
135
+ for (const entry of entries) {
136
+ const envShort = ENV_SHORT_NAMES[entry.environmentId] ?? entry.environmentId
137
+ const levelStr = padCell(entry.level, COL_LEVEL)
138
+
139
+ // Build config summary
140
+ const params = /** @type {any} */ (entry.params ?? {})
141
+ let configSummary = ''
142
+ if (entry.type === 'mcp') {
143
+ if (params.command) {
144
+ const args = Array.isArray(params.args) ? params.args.slice(0, 2).join(' ') : ''
145
+ configSummary = [params.command, args].filter(Boolean).join(' ')
146
+ } else if (params.url) {
147
+ configSummary = params.url
148
+ }
149
+ // Mask env vars
150
+ if (params.env && Object.keys(params.env).length > 0) {
151
+ const maskedVars = Object.keys(params.env)
152
+ .map((k) => `${k}=${maskEnvVarValue(params.env[k])}`)
153
+ .join(', ')
154
+ configSummary = configSummary ? `${configSummary} [${maskedVars}]` : maskedVars
155
+ }
156
+ } else {
157
+ configSummary = params.description ?? params.content?.slice(0, 30) ?? ''
158
+ }
159
+
160
+ lines.push([
161
+ padCell(entry.name, COL_NAME),
162
+ padCell(envShort, COL_ENV),
163
+ levelStr,
164
+ padCell(configSummary, COL_CONFIG),
165
+ ].join(' '))
166
+ }
167
+
168
+ return lines
169
+ }
170
+
171
+ // ──────────────────────────────────────────────────────────────────────────────
172
+ // Categories table formatter
173
+ // ──────────────────────────────────────────────────────────────────────────────
174
+
175
+ /**
176
+ * Format a list of category entries as a table string for display in the TUI.
177
+ * Columns: Name, Type, Status, Environments
178
+ * @param {CategoryEntry[]} entries
179
+ * @param {number} [termCols]
180
+ * @returns {string[]} Array of formatted lines (no ANSI clear/home)
181
+ */
182
+ export function formatCategoriesTable(entries, termCols = 120) {
183
+ const COL_NAME = 24
184
+ const COL_TYPE = 9
185
+ const COL_STATUS = 10
186
+ const COL_ENVS = 36
187
+
188
+ const headerParts = [
189
+ chalk.bold.white(padCell('Name', COL_NAME)),
190
+ chalk.bold.white(padCell('Type', COL_TYPE)),
191
+ chalk.bold.white(padCell('Status', COL_STATUS)),
192
+ chalk.bold.white(padCell('Environments', COL_ENVS)),
193
+ ]
194
+
195
+ const dividerWidth = COL_NAME + COL_TYPE + COL_STATUS + COL_ENVS + 3 * 2
196
+ const lines = []
197
+ lines.push(headerParts.join(' '))
198
+ lines.push(chalk.dim('─'.repeat(Math.min(termCols, dividerWidth))))
199
+
200
+ for (const entry of entries) {
201
+ const statusStr = entry.active
202
+ ? (/** @type {any} */ (entry)).drifted
203
+ ? chalk.yellow(padCell('⚠ Drifted', COL_STATUS))
204
+ : chalk.green(padCell('Active', COL_STATUS))
205
+ : chalk.dim(padCell('Inactive', COL_STATUS))
206
+
207
+ const envNames = entry.environments.map((id) => ENV_SHORT_NAMES[id] ?? id).join(', ')
208
+
209
+ lines.push(
210
+ [padCell(entry.name, COL_NAME), padCell(entry.type, COL_TYPE), statusStr, padCell(envNames, COL_ENVS)].join(' '),
211
+ )
212
+ }
213
+
214
+ return lines
215
+ }
@@ -3,16 +3,7 @@ import chalk from 'chalk'
3
3
  /** @import { ChartSeries } from '../types.js' */
4
4
 
5
5
  // Colour palette for multi-series charts (cycles if more than 8 series)
6
- const PALETTE = [
7
- chalk.cyan,
8
- chalk.yellow,
9
- chalk.green,
10
- chalk.magenta,
11
- chalk.blue,
12
- chalk.red,
13
- chalk.white,
14
- chalk.gray,
15
- ]
6
+ const PALETTE = [chalk.cyan, chalk.yellow, chalk.green, chalk.magenta, chalk.blue, chalk.red, chalk.white, chalk.gray]
16
7
 
17
8
  /**
18
9
  * Get the terminal width, falling back to 80 columns.
@@ -54,9 +45,7 @@ export function barChart(series, options = {}) {
54
45
 
55
46
  // Combine all series into per-label totals for scaling
56
47
  const allLabels = series[0]?.labels ?? []
57
- const totals = allLabels.map((_, i) =>
58
- series.reduce((sum, s) => sum + (s.values[i] ?? 0), 0),
59
- )
48
+ const totals = allLabels.map((_, i) => series.reduce((sum, s) => sum + (s.values[i] ?? 0), 0))
60
49
  const maxTotal = Math.max(...totals, 0)
61
50
 
62
51
  const lines = []
@@ -71,7 +60,7 @@ export function barChart(series, options = {}) {
71
60
 
72
61
  // Build chart column by column (one char per day)
73
62
  // We render it as a 2D grid: rows = height levels, cols = days
74
- const grid = Array.from({ length: BAR_HEIGHT }, () => Array(allLabels.length).fill(' '))
63
+ const grid = Array.from({length: BAR_HEIGHT}, () => Array(allLabels.length).fill(' '))
75
64
 
76
65
  for (let col = 0; col < allLabels.length; col++) {
77
66
  const total = totals[col]
@@ -110,9 +99,7 @@ export function barChart(series, options = {}) {
110
99
 
111
100
  // X-axis date labels (sample every ~10 positions)
112
101
  const step = Math.max(1, Math.ceil(allLabels.length / Math.floor(chartWidth / 10)))
113
- const xLabels = allLabels
114
- .filter((_, i) => i % step === 0)
115
- .map((l) => l.slice(5)) // "MM-DD"
102
+ const xLabels = allLabels.filter((_, i) => i % step === 0).map((l) => l.slice(5)) // "MM-DD"
116
103
  lines.push(' '.repeat(labelColWidth + 1) + xLabels.join(' '))
117
104
 
118
105
  // Legend for multi-series
@@ -156,9 +143,7 @@ export function lineChart(series, options = {}) {
156
143
  }
157
144
 
158
145
  // Build a 2D canvas: rows = chartHeight, cols = chartWidth
159
- const canvas = Array.from({ length: chartHeight }, () =>
160
- Array(chartWidth).fill(' '),
161
- )
146
+ const canvas = Array.from({length: chartHeight}, () => Array(chartWidth).fill(' '))
162
147
 
163
148
  const step = Math.max(1, Math.ceil(allLabels.length / chartWidth))
164
149
 
@@ -186,9 +171,7 @@ export function lineChart(series, options = {}) {
186
171
 
187
172
  // X-axis date labels
188
173
  const xStep = Math.max(1, Math.ceil(allLabels.length / Math.floor(chartWidth / 10)))
189
- const xLabels = allLabels
190
- .filter((_, i) => i % xStep === 0)
191
- .map((l) => l.slice(5))
174
+ const xLabels = allLabels.filter((_, i) => i % xStep === 0).map((l) => l.slice(5))
192
175
  lines.push(' '.repeat(labelColWidth + 1) + xLabels.join(' '))
193
176
 
194
177
  // Legend
@@ -57,11 +57,5 @@ export function formatCostTable(entries, label, groupBy = 'service') {
57
57
  .map((e) => ` ${rowLabel(e, groupBy).padEnd(40)} ${formatCurrency(e.amount)}`)
58
58
  .join('\n')
59
59
  const divider = '─'.repeat(50)
60
- return [
61
- `Costs for: ${label}`,
62
- divider,
63
- rows,
64
- divider,
65
- ` ${'Total'.padEnd(40)} ${formatCurrency(total)}`,
66
- ].join('\n')
60
+ return [`Costs for: ${label}`, divider, rows, divider, ` ${'Total'.padEnd(40)} ${formatCurrency(total)}`].join('\n')
67
61
  }
@@ -21,7 +21,9 @@ export function formatDotfilesSetup(result) {
21
21
  BORDER,
22
22
  '',
23
23
  chalk.bold(` Platform: ${chalk.cyan(result.platform)}`),
24
- chalk.bold(` Status: ${result.status === 'success' ? chalk.green('success') : result.status === 'skipped' ? chalk.dim('skipped') : chalk.red('failed')}`),
24
+ chalk.bold(
25
+ ` Status: ${result.status === 'success' ? chalk.green('success') : result.status === 'skipped' ? chalk.dim('skipped') : chalk.red('failed')}`,
26
+ ),
25
27
  ]
26
28
 
27
29
  if (result.sourceDir) {
@@ -79,11 +81,41 @@ export function formatDotfilesSummary(summary) {
79
81
  */
80
82
  function inferCategory(filePath) {
81
83
  const lower = filePath.toLowerCase()
82
- if (lower.includes('.ssh') || lower.includes('.gnupg') || lower.includes('gpg') || lower.includes('secret') || lower.includes('credential') || lower.includes('token') || lower.includes('password')) return 'Security'
84
+ if (
85
+ lower.includes('.ssh') ||
86
+ lower.includes('.gnupg') ||
87
+ lower.includes('gpg') ||
88
+ lower.includes('secret') ||
89
+ lower.includes('credential') ||
90
+ lower.includes('token') ||
91
+ lower.includes('password')
92
+ )
93
+ return 'Security'
83
94
  if (lower.includes('.gitconfig') || lower.includes('.gitignore') || lower.includes('.git')) return 'Git'
84
- if (lower.includes('zshrc') || lower.includes('bashrc') || lower.includes('bash_profile') || lower.includes('zprofile') || lower.includes('fish')) return 'Shell'
85
- if (lower.includes('vim') || lower.includes('nvim') || lower.includes('emacs') || lower.includes('vscode') || lower.includes('cursor')) return 'Editor'
86
- if (lower.includes('brew') || lower.includes('npm') || lower.includes('yarn') || lower.includes('pip') || lower.includes('gem')) return 'Package'
95
+ if (
96
+ lower.includes('zshrc') ||
97
+ lower.includes('bashrc') ||
98
+ lower.includes('bash_profile') ||
99
+ lower.includes('zprofile') ||
100
+ lower.includes('fish')
101
+ )
102
+ return 'Shell'
103
+ if (
104
+ lower.includes('vim') ||
105
+ lower.includes('nvim') ||
106
+ lower.includes('emacs') ||
107
+ lower.includes('vscode') ||
108
+ lower.includes('cursor')
109
+ )
110
+ return 'Editor'
111
+ if (
112
+ lower.includes('brew') ||
113
+ lower.includes('npm') ||
114
+ lower.includes('yarn') ||
115
+ lower.includes('pip') ||
116
+ lower.includes('gem')
117
+ )
118
+ return 'Package'
87
119
  return 'Other'
88
120
  }
89
121
 
@@ -167,13 +199,7 @@ export function formatDotfilesStatus(result) {
167
199
  * @returns {string}
168
200
  */
169
201
  export function formatDotfilesAdd(result) {
170
- const lines = [
171
- '',
172
- BORDER,
173
- chalk.bold(' Dotfiles Add — Summary'),
174
- BORDER,
175
- '',
176
- ]
202
+ const lines = ['', BORDER, chalk.bold(' Dotfiles Add — Summary'), BORDER, '']
177
203
 
178
204
  if (result.added.length > 0) {
179
205
  lines.push(chalk.bold(` Added (${result.added.length}):`))
@@ -219,12 +245,13 @@ export function formatDotfilesAdd(result) {
219
245
  * @returns {string}
220
246
  */
221
247
  export function formatDotfilesSync(result) {
222
- const actionLabel = {
223
- push: 'Push',
224
- pull: 'Pull',
225
- 'init-remote': 'Remote Setup',
226
- skipped: 'Skipped',
227
- }[result.action] ?? result.action
248
+ const actionLabel =
249
+ {
250
+ push: 'Push',
251
+ pull: 'Pull',
252
+ 'init-remote': 'Remote Setup',
253
+ skipped: 'Skipped',
254
+ }[result.action] ?? result.action
228
255
 
229
256
  const lines = [
230
257
  '',
@@ -233,7 +260,9 @@ export function formatDotfilesSync(result) {
233
260
  BORDER,
234
261
  '',
235
262
  chalk.white(` Action: ${chalk.cyan(actionLabel)}`),
236
- chalk.white(` Status: ${result.status === 'success' ? chalk.green('success') : result.status === 'skipped' ? chalk.dim('skipped') : chalk.red('failed')}`),
263
+ chalk.white(
264
+ ` Status: ${result.status === 'success' ? chalk.green('success') : result.status === 'skipped' ? chalk.dim('skipped') : chalk.red('failed')}`,
265
+ ),
237
266
  ]
238
267
 
239
268
  if (result.repo) {
@@ -1,6 +1,6 @@
1
- import { marked } from 'marked'
1
+ import {marked} from 'marked'
2
2
  import chalk from 'chalk'
3
- import { deflate } from 'pako'
3
+ import {deflate} from 'pako'
4
4
 
5
5
  // Custom terminal renderer — outputs ANSI-formatted text using chalk.
6
6
  // marked-terminal@7 is incompatible with all currently released versions of marked
@@ -30,7 +30,12 @@ const terminalRenderer = {
30
30
  return '\n' + lines.join('\n') + '\n\n'
31
31
  },
32
32
  blockquote(quote) {
33
- return quote.split('\n').map((l) => chalk.dim('│ ') + chalk.italic(l)).join('\n') + '\n'
33
+ return (
34
+ quote
35
+ .split('\n')
36
+ .map((l) => chalk.dim('│ ') + chalk.italic(l))
37
+ .join('\n') + '\n'
38
+ )
34
39
  },
35
40
  link(href, _title, text) {
36
41
  return `${text} ${chalk.dim(`(${href})`)}`
@@ -61,7 +66,7 @@ const terminalRenderer = {
61
66
  },
62
67
  }
63
68
 
64
- marked.use({ renderer: terminalRenderer })
69
+ marked.use({renderer: terminalRenderer})
65
70
 
66
71
  /**
67
72
  * Render a markdown string as ANSI-formatted terminal output.
@@ -95,14 +100,14 @@ export function extractMermaidBlocks(content) {
95
100
  export function toMermaidLiveUrl(diagramCode) {
96
101
  const state = JSON.stringify({
97
102
  code: diagramCode,
98
- mermaid: JSON.stringify({ theme: 'default' }),
103
+ mermaid: JSON.stringify({theme: 'default'}),
99
104
  updateDiagram: true,
100
105
  grid: true,
101
106
  panZoom: true,
102
107
  rough: false,
103
108
  })
104
109
  const data = new TextEncoder().encode(state)
105
- const compressed = deflate(data, { level: 9 })
110
+ const compressed = deflate(data, {level: 9})
106
111
  const encoded = Buffer.from(compressed).toString('base64url')
107
112
  return `https://mermaid.live/view#pako:${encoded}`
108
113
  }
@@ -1,4 +1,4 @@
1
- import { load } from 'js-yaml'
1
+ import {load} from 'js-yaml'
2
2
 
3
3
  /** @import { APIEndpoint, AsyncChannel } from '../types.js' */
4
4
 
@@ -45,7 +45,7 @@ export function isAsyncApi(doc) {
45
45
  export function parseOpenApi(content) {
46
46
  const doc = parseYamlOrJson(content)
47
47
  if (!doc || !isOpenApi(doc)) {
48
- return { endpoints: [], error: 'Not a valid OpenAPI/Swagger document' }
48
+ return {endpoints: [], error: 'Not a valid OpenAPI/Swagger document'}
49
49
  }
50
50
 
51
51
  /** @type {APIEndpoint[]} */
@@ -58,9 +58,7 @@ export function parseOpenApi(content) {
58
58
  if (!['get', 'post', 'put', 'patch', 'delete', 'head', 'options'].includes(method)) continue
59
59
  const operation = /** @type {Record<string, unknown>} */ (op)
60
60
  const rawParams = /** @type {Array<Record<string, unknown>>} */ (operation.parameters ?? [])
61
- const parameters = rawParams
62
- .map((p) => (p.required ? `${p.name}*` : String(p.name)))
63
- .join(', ')
61
+ const parameters = rawParams.map((p) => (p.required ? `${p.name}*` : String(p.name))).join(', ')
64
62
  endpoints.push({
65
63
  method: method.toUpperCase(),
66
64
  path,
@@ -70,7 +68,7 @@ export function parseOpenApi(content) {
70
68
  }
71
69
  }
72
70
 
73
- return { endpoints, error: null }
71
+ return {endpoints, error: null}
74
72
  }
75
73
 
76
74
  /**
@@ -81,7 +79,7 @@ export function parseOpenApi(content) {
81
79
  export function parseAsyncApi(content) {
82
80
  const doc = parseYamlOrJson(content)
83
81
  if (!doc || !isAsyncApi(doc)) {
84
- return { channels: [], error: 'Not a valid AsyncAPI document' }
82
+ return {channels: [], error: 'Not a valid AsyncAPI document'}
85
83
  }
86
84
 
87
85
  /** @type {AsyncChannel[]} */
@@ -98,7 +96,7 @@ export function parseAsyncApi(content) {
98
96
  return String(ch.$ref ?? '').includes(channelName) || String(ch ?? '') === channelName
99
97
  })
100
98
  if (matchingOps.length === 0) {
101
- channels.push({ channel: channelName, operation: '—', summary: '', message: '—' })
99
+ channels.push({channel: channelName, operation: '—', summary: '', message: '—'})
102
100
  }
103
101
  for (const op of matchingOps) {
104
102
  const msgTitle = resolveMessageTitle(op.messages)
@@ -129,7 +127,7 @@ export function parseAsyncApi(content) {
129
127
  }
130
128
  }
131
129
 
132
- return { channels, error: null }
130
+ return {channels, error: null}
133
131
  }
134
132
 
135
133
  /**