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.
- package/README.md +72 -0
- package/oclif.manifest.json +275 -235
- 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 +257 -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 +215 -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 +349 -0
- package/src/services/ai-env-deployer.js +650 -0
- package/src/services/ai-env-scanner.js +983 -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 +117 -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 +1184 -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 +1089 -0
- package/src/utils/typewriter.js +3 -3
- package/src/utils/welcome.js +18 -21
- 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
|
+
}
|
package/src/formatters/charts.js
CHANGED
|
@@ -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({
|
|
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({
|
|
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
|
package/src/formatters/cost.js
CHANGED
|
@@ -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(
|
|
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 (
|
|
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 (
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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(
|
|
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 {
|
|
1
|
+
import {marked} from 'marked'
|
|
2
2
|
import chalk from 'chalk'
|
|
3
|
-
import {
|
|
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
|
|
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({
|
|
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({
|
|
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, {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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({
|
|
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 {
|
|
130
|
+
return {channels, error: null}
|
|
133
131
|
}
|
|
134
132
|
|
|
135
133
|
/**
|